mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-27 18:34:58 +08:00
d8748e3a45
- .env backup and removal are now one atomic action: a failed backup throws into executePlan's catch and the deletion never runs (the bash original's set -e gave the same guarantee; the port had lost it) - containers are re-listed by install label at removal time instead of removed from scan-time ids — the live host can spawn containers during the confirm phase - uninstall telemetry no longer creates data/install-id (persistId:false on emit), so --dry-run truly changes nothing and the already-clean exit can fire - runtime-tail failure notes are printed before the Done line instead of being discarded - uninstall.sh translates the old short flags (-n/-y) instead of silently dropping them (-n used to fall through to a real interactive uninstall) - nanoclaw.sh gates the TS uninstaller on node (tsx's interpreter), not pnpm, which the direct-exec path never uses - detectExistingInstall also checks the system-level systemd unit - a delete-onecli-agent spawn failure now notes the manual command instead of claiming the agent was already gone - setupLog.userInput is skipped when logs/ is absent so the uninstall doesn't recreate it Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
/**
|
|
* Thin PostHog emitter shared across setup:auto code. Fire-and-forget —
|
|
* never throws, never blocks. Reuses data/install-id (same file bash
|
|
* uses in setup/lib/diagnostics.sh) so events from the bash and node
|
|
* halves of a single install join into one funnel.
|
|
*
|
|
* Honors NANOCLAW_NO_DIAGNOSTICS=1.
|
|
*/
|
|
import { randomUUID } from 'crypto';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const POSTHOG_KEY = 'phc_fx1Hhx9ucz8GuaJC8LVZWO8u03yXZZJJ6ObS4yplnaP';
|
|
const POSTHOG_URL = 'https://us.i.posthog.com/capture/';
|
|
const INSTALL_ID_PATH = path.join('data', 'install-id');
|
|
|
|
let cached: string | null = null;
|
|
|
|
/**
|
|
* `persist: false` reads an existing id but never creates `data/install-id`
|
|
* — required by the uninstall path, which must not mutate the filesystem
|
|
* before (or instead of) removing it. Events in one process still join:
|
|
* the generated id is cached.
|
|
*/
|
|
export function installId(persist = true): string {
|
|
if (cached) return cached;
|
|
try {
|
|
const existing = fs.readFileSync(INSTALL_ID_PATH, 'utf-8').trim();
|
|
if (existing) {
|
|
cached = existing;
|
|
return existing;
|
|
}
|
|
} catch {
|
|
// fall through to create
|
|
}
|
|
const id = randomUUID().toLowerCase();
|
|
if (persist) {
|
|
try {
|
|
fs.mkdirSync(path.dirname(INSTALL_ID_PATH), { recursive: true });
|
|
fs.writeFileSync(INSTALL_ID_PATH, id);
|
|
} catch {
|
|
// best-effort; still return the id so the event fires
|
|
}
|
|
}
|
|
cached = id;
|
|
return id;
|
|
}
|
|
|
|
export function emit(
|
|
event: string,
|
|
props: Record<string, string | number | boolean | undefined> = {},
|
|
opts: { persistId?: boolean } = {},
|
|
): void {
|
|
if (process.env.NANOCLAW_NO_DIAGNOSTICS === '1') return;
|
|
|
|
const cleaned: Record<string, unknown> = { platform: process.platform };
|
|
for (const [k, v] of Object.entries(props)) {
|
|
if (v === undefined) continue;
|
|
cleaned[k] = v;
|
|
}
|
|
|
|
const body = JSON.stringify({
|
|
api_key: POSTHOG_KEY,
|
|
event,
|
|
distinct_id: installId(opts.persistId !== false),
|
|
properties: cleaned,
|
|
});
|
|
|
|
const ctrl = new AbortController();
|
|
const timer = setTimeout(() => ctrl.abort(), 3000);
|
|
void fetch(POSTHOG_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body,
|
|
signal: ctrl.signal,
|
|
})
|
|
.catch(() => {})
|
|
.finally(() => clearTimeout(timer));
|
|
}
|