@latitude-data/openclaw-telemetry-cli 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@ For details on the plugin runtime itself — what it sends, the span tree, and t
6
6
 
7
7
  ## Requirements
8
8
 
9
- - **OpenClaw 2026.4.25 or newer** on PATH.
9
+ - **OpenClaw 2026.8.1 or newer** on PATH.
10
10
  - A **Latitude API key** from `https://console.latitude.so/projects/<your-slug>/settings/keys` and the matching **project slug**.
11
11
 
12
12
  ## Install
@@ -14,7 +14,7 @@ For details on the plugin runtime itself — what it sends, the span tree, and t
14
14
  Interactive (recommended for first-time setup):
15
15
 
16
16
  ```bash
17
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 install
17
+ npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 install
18
18
  ```
19
19
 
20
20
  The CLI prompts for your API key and project slug, installs the plugin, writes the config, validates it, and offers to restart the gateway.
@@ -22,7 +22,7 @@ The CLI prompts for your API key and project slug, installs the plugin, writes t
22
22
  Non-interactive / CI:
23
23
 
24
24
  ```bash
25
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 install \
25
+ npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 install \
26
26
  --api-key="$LATITUDE_API_KEY" \
27
27
  --project=my-project \
28
28
  --yes \
@@ -35,12 +35,12 @@ npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 install \
35
35
 
36
36
  In order, on every install:
37
37
 
38
- 1. **Verifies your OpenClaw version** (`openclaw --version` ≥ 2026.4.25). Aborts with an upgrade message on older versions.
39
- 2. **Verifies the runtime contract** — that `@latitude-data/openclaw-telemetry@0.0.8` exists on npm. (Catches half-published releases.)
40
- 3. **Detects any prior install** via `<configDir>/plugins/installs.json`. Renders `Upgrading 0.0.7 → 0.0.8` (or `Re-applying 0.0.8 (idempotent)`) if found.
38
+ 1. **Verifies your OpenClaw version** (`openclaw --version` ≥ 2026.8.1). Aborts with an upgrade message on older versions.
39
+ 2. **Verifies the runtime contract** — that `@latitude-data/openclaw-telemetry@0.1.0` exists on npm. (Catches half-published releases.)
40
+ 3. **Detects any prior install** via `<configDir>/plugins/installs.json`. Renders `Upgrading 0.0.8 → 0.1.0` (or `Re-applying 0.1.0 (idempotent)`) if found.
41
41
  4. **Prompts for your API key and project slug** — interactive only; flags / `--yes` skip the prompts.
42
42
  5. **Backs up `openclaw.json`** to `openclaw.json.latitude-bak` before any change.
43
- 6. **Runs `openclaw plugins install @latitude-data/openclaw-telemetry@0.0.8 --force`** — npm fetch, security scan, extension placement, install record, disabled `plugins.entries[id]`.
43
+ 6. **Runs `openclaw plugins install @latitude-data/openclaw-telemetry@0.1.0 --force --accept-capabilities`** — npm fetch, extension placement, install record, capability consent, disabled `plugins.entries[id]`. Running the installer is the consent OpenClaw asks for.
44
44
  7. **Writes the plugin config** atomically (temp + rename): `apiKey`, `project`, `baseUrl` (only if `--staging` / `--dev`), `config.allowConversationAccess` AND `hooks.allowConversationAccess` (always coupled — both `true` by default; `--no-content` to emit structural-only telemetry).
45
45
  8. **Adds the plugin id to `plugins.allow`** to silence OpenClaw's "untracked code" warning. Pass `--no-trust` to opt out.
46
46
  9. **Validates** the result with `openclaw config validate --json`. If it fails, restores the backup and aborts with the validator's message.
@@ -57,6 +57,7 @@ In order, on every install:
57
57
  | `--no-content` | Disable conversation capture (still emits structural-only telemetry — timing, tokens, ids). Mirrored into both `config.allowConversationAccess` and `hooks.allowConversationAccess`. |
58
58
  | `--allow-conversation` | Force conversation capture on (overrides existing config). |
59
59
  | `--no-trust` | Skip auto-adding the plugin id to `plugins.allow`. |
60
+ | `--runtime-spec=<spec>` | Install this spec instead of the pinned npm version, e.g. `npm-pack:/path/to/plugin.tgz` for a pre-release build. Skips the npm registry lockstep check. |
60
61
  | `--openclaw-dir=<path>` | Override the OpenClaw config directory. See [Custom config directory](#custom-config-directory). |
61
62
  | `--dry-run` | Preview every change without touching the filesystem or spawning subprocesses. See [Dry run](#dry-run). |
62
63
  | `--restart` | Always restart the gateway, even non-TTY. CI escape hatch for "do everything". |
@@ -72,7 +73,7 @@ In order, on every install:
72
73
  Preview every change without touching the filesystem or spawning subprocesses:
73
74
 
74
75
  ```bash
75
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 install --dry-run \
76
+ npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 install --dry-run \
76
77
  --api-key=lat_xxx --project=my-project
77
78
  ```
78
79
 
@@ -94,7 +95,7 @@ The resolved path is printed at install start. The CLI passes `OPENCLAW_HOME=<re
94
95
  ### Structural-only telemetry
95
96
 
96
97
  ```bash
97
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 install --no-content \
98
+ npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 install --no-content \
98
99
  --api-key=lat_xxx --project=my-project
99
100
  ```
100
101
 
@@ -104,11 +105,11 @@ The plugin still emits the full span tree — timings, token usage, model name,
104
105
 
105
106
  ```bash
106
107
  # Staging
107
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 install --staging --yes \
108
+ npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 install --staging --yes \
108
109
  --api-key=lat_xxx --project=my-project
109
110
 
110
111
  # Local dev
111
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 install --dev --yes \
112
+ npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 install --dev --yes \
112
113
  --api-key=lat_xxx --project=my-project
113
114
  ```
114
115
 
@@ -117,7 +118,7 @@ npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 install --dev --yes \
117
118
  ## Uninstall
118
119
 
119
120
  ```bash
120
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.8 uninstall
121
+ npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 uninstall
121
122
  ```
122
123
 
123
124
  The uninstall flow:
@@ -132,7 +133,7 @@ The backup at `openclaw.json.latitude-bak` is kept after uninstall — delete it
132
133
 
133
134
  ## Lockstep policy
134
135
 
135
- This CLI installs **exactly** `@latitude-data/openclaw-telemetry@0.0.8` (pinned in source as `RUNTIME_VERSION`). Every CLI release is paired with a runtime release of the same version number; bumping the runtime requires bumping the CLI in the same commit and re-publishing both.
136
+ This CLI installs **exactly** `@latitude-data/openclaw-telemetry@0.1.0` (pinned in source as `RUNTIME_VERSION`). Every CLI release is paired with a runtime release of the same version number; bumping the runtime requires bumping the CLI in the same commit and re-publishing both.
136
137
 
137
138
  If npm doesn't have the exact pinned version (half-published release), the CLI aborts with `Upgrade the CLI: npm install -g @latitude-data/openclaw-telemetry-cli@latest`.
138
139
 
@@ -140,7 +141,7 @@ Pinning to an exact version also satisfies OpenClaw's `Pin install specs to exac
140
141
 
141
142
  ## Why this is a separate package
142
143
 
143
- OpenClaw 2026.4.25+ runs an install-time security scan on `openclaw plugins install` (`plugins.code_safety`, looking for `dangerous-exec` / `env-harvesting` patterns). This CLI uses `child_process.spawn` to invoke OpenClaw — a `dangerous-exec` flag. Keeping the CLI in a **separate npm package** that's installed via `npx` / `npm install -g` rather than `openclaw plugins install` means the CLI never goes through OpenClaw's scanner. The runtime plugin ([`@latitude-data/openclaw-telemetry`](https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/openclaw)) is installed by this CLI on the operator's behalf and is itself scanner-clean (no `child_process`, no `node:fs` runtime reads).
144
+ OpenClaw scans plugin packages on `openclaw plugins install` for patterns such as `dangerous-exec` and `env-harvesting`, and this CLI uses `child_process.spawn` to invoke OpenClaw. Keeping the CLI in a **separate npm package** that's installed via `npx` / `npm install -g` rather than `openclaw plugins install` keeps the installer out of that scan and out of the plugin's capability surface. The runtime plugin ([`@latitude-data/openclaw-telemetry`](https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/openclaw)) is installed by this CLI on the operator's behalf and spawns nothing.
144
145
 
145
146
  See the [runtime CHANGELOG](https://github.com/latitude-dev/latitude-llm/blob/main/packages/telemetry/openclaw/CHANGELOG.md) for the wider history of the split.
146
147
 
package/dist/cli.js CHANGED
@@ -158,7 +158,7 @@ function lcsDiff(a, b) {
158
158
  * versions is intentional — we'd rather fail loudly than ship a
159
159
  * config the gateway will quarantine or hooks the dispatcher will block.
160
160
  */
161
- const MIN_OPENCLAW_VERSION = "2026.4.25";
161
+ const MIN_OPENCLAW_VERSION = "2026.8.1";
162
162
  const DEFAULT_TIMEOUT_MS = 1e4;
163
163
  /**
164
164
  * Spawn `openclaw <args>` synchronously. Reports failure modes structurally so
@@ -518,7 +518,7 @@ function readCliVersion() {
518
518
  * supply-chain audit warning automatically.
519
519
  */
520
520
  const RUNTIME_PACKAGE_NAME = "@latitude-data/openclaw-telemetry";
521
- const RUNTIME_VERSION = "0.0.8";
521
+ const RUNTIME_VERSION = "0.1.0";
522
522
  const DOCS_URL = "https://docs.latitude.so/telemetry/openclaw";
523
523
  const PRODUCTION_ENV = {
524
524
  name: "production",
@@ -554,7 +554,8 @@ function urlsFor(env) {
554
554
  const VALUE_FLAGS = new Set([
555
555
  "api-key",
556
556
  "project",
557
- "openclaw-dir"
557
+ "openclaw-dir",
558
+ "runtime-spec"
558
559
  ]);
559
560
  function parseFlags(argv) {
560
561
  const [subcommand, ...rest] = argv;
@@ -601,6 +602,7 @@ function normalizeInstallFlags(flags) {
601
602
  environment,
602
603
  allowConversationAccess,
603
604
  noTrust: flags["no-trust"] === true,
605
+ runtimeSpec: typeof flags["runtime-spec"] === "string" ? flags["runtime-spec"] : void 0,
604
606
  openclawDir: typeof flags["openclaw-dir"] === "string" ? flags["openclaw-dir"] : void 0,
605
607
  dryRun: flags["dry-run"] === true,
606
608
  restart,
@@ -627,7 +629,7 @@ async function runInteractiveInstall(flags) {
627
629
  ensureOpenclawIsCompatible();
628
630
  const { resolved, paths } = resolvePaths(flags);
629
631
  log.info(`Using config dir: ${pc.dim(paths.configDir)} ${pc.dim(`(source: ${resolved.source})`)}`);
630
- await ensureRuntimeOnNpm();
632
+ if (!flags.runtimeSpec) await ensureRuntimeOnNpm();
631
633
  const installedVersion = readInstalledRuntimeVersion(paths.installsPath);
632
634
  if (installedVersion !== void 0) if (installedVersion === RUNTIME_VERSION) log.info(`${PLUGIN_ID} ${pc.dim(installedVersion)} already installed — re-applying (idempotent).`);
633
635
  else log.info(`Upgrading ${PLUGIN_ID} from ${pc.dim(installedVersion)} → ${pc.cyan(RUNTIME_VERSION)}`);
@@ -652,6 +654,7 @@ async function runInteractiveInstall(flags) {
652
654
  envConfig,
653
655
  allowConversationAccess: flags.allowConversationAccess,
654
656
  noTrust: flags.noTrust === true,
657
+ runtimeSpec: flags.runtimeSpec,
655
658
  paths,
656
659
  dryRun: flags.dryRun === true
657
660
  });
@@ -663,7 +666,7 @@ async function runFlagDrivenInstall(flags) {
663
666
  ensureOpenclawIsCompatible();
664
667
  const { resolved, paths } = resolvePaths(flags);
665
668
  process.stdout.write(`Using config dir: ${paths.configDir} (source: ${resolved.source})\n`);
666
- await ensureRuntimeOnNpm();
669
+ if (!flags.runtimeSpec) await ensureRuntimeOnNpm();
667
670
  const apiKey = flags.apiKey;
668
671
  const project = flags.project;
669
672
  if (!apiKey || !project) throw new Error("Non-interactive install requires --api-key=... and --project=... (or run in a TTY).");
@@ -673,6 +676,7 @@ async function runFlagDrivenInstall(flags) {
673
676
  envConfig: flags.environment ?? PRODUCTION_ENV,
674
677
  allowConversationAccess: flags.allowConversationAccess,
675
678
  noTrust: flags.noTrust === true,
679
+ runtimeSpec: flags.runtimeSpec,
676
680
  paths,
677
681
  dryRun: flags.dryRun === true
678
682
  });
@@ -708,8 +712,15 @@ function onCancel() {
708
712
  cancel("Cancelled — nothing was changed");
709
713
  process.exit(1);
710
714
  }
711
- async function applyChanges({ apiKey, project, envConfig, allowConversationAccess, noTrust, paths, dryRun }) {
712
- const installSpec = `${RUNTIME_PACKAGE_NAME}@${RUNTIME_VERSION}`;
715
+ async function applyChanges({ apiKey, project, envConfig, allowConversationAccess, noTrust, runtimeSpec, paths, dryRun }) {
716
+ const installSpec = runtimeSpec ?? `${RUNTIME_PACKAGE_NAME}@${RUNTIME_VERSION}`;
717
+ const installArgs = [
718
+ "plugins",
719
+ "install",
720
+ installSpec,
721
+ "--force",
722
+ "--accept-capabilities"
723
+ ];
713
724
  const before = readSettings(paths.settingsPath);
714
725
  const after = structuredClone(before);
715
726
  migrateLegacyEntries(after);
@@ -721,7 +732,7 @@ async function applyChanges({ apiKey, project, envConfig, allowConversationAcces
721
732
  });
722
733
  if (!noTrust) addToPluginsAllow(after);
723
734
  if (dryRun) {
724
- log.info(`Would run: ${pc.dim(`openclaw plugins install ${installSpec} --force`)}`);
735
+ log.info(`Would run: ${pc.dim(`openclaw ${installArgs.join(" ")}`)}`);
725
736
  const diff = jsonDiff(before, after, {
726
737
  fromLabel: paths.settingsPath,
727
738
  toLabel: `${paths.settingsPath} (proposed)`
@@ -734,12 +745,7 @@ async function applyChanges({ apiKey, project, envConfig, allowConversationAcces
734
745
  backupSettings(paths.settingsPath, paths.settingsBackupPath);
735
746
  const installSpinner = spinner();
736
747
  installSpinner.start(`Installing plugin via openclaw plugins install ${installSpec}`);
737
- const installResult = runOpenclaw([
738
- "plugins",
739
- "install",
740
- installSpec,
741
- "--force"
742
- ], {
748
+ const installResult = runOpenclaw(installArgs, {
743
749
  timeoutMs: 6e4,
744
750
  env: envForSubprocess(paths)
745
751
  });
@@ -839,7 +845,7 @@ function ensureOpenclawIsCompatible() {
839
845
  cancel(`Couldn't parse OpenClaw version output${v.raw ? ` (got: ${pc.dim(v.raw)})` : ""}. Run \`openclaw --version\` and report the output.`);
840
846
  process.exit(1);
841
847
  }
842
- if (compareCalver(v.version, "2026.4.25") < 0) {
848
+ if (compareCalver(v.version, "2026.8.1") < 0) {
843
849
  cancel(`OpenClaw ${v.version} is older than the minimum supported version (${MIN_OPENCLAW_VERSION}). Run \`npm install -g openclaw@latest\` and re-run install.`);
844
850
  process.exit(1);
845
851
  }
@@ -1005,6 +1011,8 @@ install options:
1005
1011
  --no-content Skip raw prompt/response/tool I/O capture
1006
1012
  --allow-conversation Force conversation capture on (overrides existing config)
1007
1013
  --no-trust Skip adding plugin id to plugins.allow
1014
+ --runtime-spec=<spec> Install this spec instead of the pinned npm version
1015
+ (e.g. npm-pack:/path/to/plugin.tgz for a pre-release build)
1008
1016
  --openclaw-dir=<path> Override OpenClaw config dir (default: $OPENCLAW_HOME, ./openclaw.json,
1009
1017
  or ~/.openclaw — see resolution order in README)
1010
1018
  --dry-run Show the diff against current openclaw.json and exit (no writes)
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":["resolvePath"],"sources":["../src/config-dir.ts","../src/diff.ts","../src/openclaw-cli.ts","../src/settings-file.ts","../src/version.ts","../src/setup.ts","../src/cli.ts"],"sourcesContent":["import { existsSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { isAbsolute, join, resolve as resolvePath } from \"node:path\"\n\n/**\n * Resolution order for the OpenClaw config directory (the parent of\n * `openclaw.json`):\n *\n * 1. `--openclaw-dir <path>` flag (most explicit; absolute or cwd-relative).\n * 2. `OPENCLAW_HOME` env var (next-most explicit; absolute or cwd-relative).\n * 3. `./openclaw.json` exists in the current working directory → use cwd\n * (matches `cd into a project that has its own openclaw.json` UX).\n * 4. `~/.openclaw` (default; matches OpenClaw's hardcoded default).\n *\n * The CLI passes `OPENCLAW_HOME=<resolved>` to every spawned `openclaw`\n * subprocess so the runtime side resolves to the same dir we're writing to.\n *\n * Caveat — at the time of writing, OpenClaw's CLI does not advertise\n * `OPENCLAW_HOME` support; the runtime side may still resolve `~/.openclaw`\n * regardless of what we set. The post-install `openclaw config validate\n * --json` step (in `setup.ts`) catches this and restores the backup if the\n * runtime fell back to the wrong dir. Don't trust the resolved path alone —\n * trust the validation result.\n */\nexport interface ResolvedConfigDir {\n /** Absolute path to the chosen config directory. */\n readonly dir: string\n /** Where the value came from, for logging / debug. */\n readonly source: \"flag\" | \"env\" | \"cwd\" | \"default\"\n}\n\ninterface ResolveOptions {\n /** Value of the `--openclaw-dir` flag, if set. */\n readonly flag?: string | undefined\n /** Override `OPENCLAW_HOME` env var (defaults to `process.env.OPENCLAW_HOME`). */\n readonly env?: string | undefined\n /** Override cwd (for tests). Defaults to `process.cwd()`. */\n readonly cwd?: string | undefined\n /** Override home dir (for tests). Defaults to `os.homedir()`. */\n readonly home?: string | undefined\n}\n\nexport function resolveConfigDir(opts: ResolveOptions = {}): ResolvedConfigDir {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n\n if (typeof opts.flag === \"string\" && opts.flag.length > 0) {\n return { dir: absolutize(opts.flag, cwd), source: \"flag\" }\n }\n const envValue = opts.env !== undefined ? opts.env : process.env.OPENCLAW_HOME\n if (typeof envValue === \"string\" && envValue.length > 0) {\n return { dir: absolutize(envValue, cwd), source: \"env\" }\n }\n if (existsSync(join(cwd, \"openclaw.json\"))) {\n return { dir: cwd, source: \"cwd\" }\n }\n return { dir: join(home, \".openclaw\"), source: \"default\" }\n}\n\nfunction absolutize(p: string, cwd: string): string {\n return isAbsolute(p) ? p : resolvePath(cwd, p)\n}\n\n/** Settings paths derived from a config dir. */\nexport interface SettingsPaths {\n readonly configDir: string\n readonly settingsPath: string\n readonly settingsBackupPath: string\n /** OpenClaw's plugins install record — read-only, used for upgrade-detection. */\n readonly installsPath: string\n}\n\nexport function pathsFor(configDir: string): SettingsPaths {\n return {\n configDir,\n settingsPath: join(configDir, \"openclaw.json\"),\n settingsBackupPath: join(configDir, \"openclaw.json.latitude-bak\"),\n installsPath: join(configDir, \"plugins\", \"installs.json\"),\n }\n}\n","/**\n * Tiny purpose-built JSON line-diff for `--dry-run` output. Renders changes\n * to `openclaw.json` in a human-readable unified-diff-ish format without\n * pulling in a real diff library (zero new deps).\n *\n * The shape of `openclaw.json` is small (a few hundred lines tops, with\n * stable key ordering after our edits), so the naive line-by-line approach\n * produces a perfectly readable output. We mark added lines with `+ ` and\n * removed lines with `- `, with a few lines of context around each change.\n */\nconst CONTEXT_LINES = 3\n\ninterface DiffOptions {\n /** Header label for the \"before\" side (default: `current`). */\n fromLabel?: string\n /** Header label for the \"after\" side (default: `proposed`). */\n toLabel?: string\n}\n\n/**\n * Render a unified-ish diff between two unknown JSON values. Returns an\n * empty string if they're identical after normalization.\n */\nexport function jsonDiff(before: unknown, after: unknown, opts: DiffOptions = {}): string {\n const fromLabel = opts.fromLabel ?? \"current\"\n const toLabel = opts.toLabel ?? \"proposed\"\n\n const beforeJson = `${JSON.stringify(before ?? {}, null, 2)}\\n`\n const afterJson = `${JSON.stringify(after ?? {}, null, 2)}\\n`\n\n if (beforeJson === afterJson) return \"\"\n\n const beforeLines = beforeJson.split(\"\\n\")\n const afterLines = afterJson.split(\"\\n\")\n\n // Compute LCS-driven minimal edit script. For files of this size the\n // O(n*m) DP is fine — `openclaw.json` rarely exceeds a few hundred lines.\n const ops = lcsDiff(beforeLines, afterLines)\n\n const out: string[] = []\n out.push(`--- ${fromLabel}`)\n out.push(`+++ ${toLabel}`)\n\n // Render hunks (groups of contiguous changes + their context).\n let i = 0\n while (i < ops.length) {\n if (ops[i]?.kind === \"eq\") {\n i++\n continue\n }\n // Found a change — collect the surrounding hunk.\n const hunkStart = Math.max(0, i - CONTEXT_LINES)\n let hunkEnd = i\n while (hunkEnd < ops.length) {\n if (ops[hunkEnd]?.kind === \"eq\") {\n // Stop the hunk after CONTEXT_LINES of equality, unless the very\n // next non-equal op is within CONTEXT_LINES * 2 (then we collapse\n // adjacent change blocks into one hunk).\n let runOfEq = 0\n let k = hunkEnd\n while (k < ops.length && ops[k]?.kind === \"eq\" && runOfEq < CONTEXT_LINES * 2) {\n runOfEq++\n k++\n }\n if (k >= ops.length || ops[k]?.kind === \"eq\") {\n hunkEnd = Math.min(hunkEnd + CONTEXT_LINES, ops.length)\n break\n }\n hunkEnd = k\n continue\n }\n hunkEnd++\n }\n\n for (let j = hunkStart; j < hunkEnd && j < ops.length; j++) {\n const op = ops[j]\n if (!op) continue\n if (op.kind === \"eq\") out.push(` ${op.line}`)\n else if (op.kind === \"add\") out.push(`+ ${op.line}`)\n else if (op.kind === \"del\") out.push(`- ${op.line}`)\n }\n i = hunkEnd\n }\n\n return out.join(\"\\n\")\n}\n\ntype Op = { kind: \"eq\"; line: string } | { kind: \"add\"; line: string } | { kind: \"del\"; line: string }\n\nfunction lcsDiff(a: readonly string[], b: readonly string[]): Op[] {\n const n = a.length\n const m = b.length\n // dp[i][j] = LCS length of a[0..i) and b[0..j)\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0))\n for (let i = 1; i <= n; i++) {\n for (let j = 1; j <= m; j++) {\n if (a[i - 1] === b[j - 1]) {\n dp[i]![j] = (dp[i - 1]?.[j - 1] ?? 0) + 1\n } else {\n dp[i]![j] = Math.max(dp[i - 1]?.[j] ?? 0, dp[i]?.[j - 1] ?? 0)\n }\n }\n }\n // Walk back to produce the edit script.\n const ops: Op[] = []\n let i = n\n let j = m\n while (i > 0 && j > 0) {\n if (a[i - 1] === b[j - 1]) {\n ops.push({ kind: \"eq\", line: a[i - 1] as string })\n i--\n j--\n } else if ((dp[i - 1]?.[j] ?? 0) >= (dp[i]?.[j - 1] ?? 0)) {\n ops.push({ kind: \"del\", line: a[i - 1] as string })\n i--\n } else {\n ops.push({ kind: \"add\", line: b[j - 1] as string })\n j--\n }\n }\n while (i > 0) {\n ops.push({ kind: \"del\", line: a[i - 1] as string })\n i--\n }\n while (j > 0) {\n ops.push({ kind: \"add\", line: b[j - 1] as string })\n j--\n }\n ops.reverse()\n return ops\n}\n","import { type SpawnSyncReturns, spawnSync } from \"node:child_process\"\n\n/**\n * Lowest OpenClaw version we support. The reporter verified hook-dispatch\n * gating works correctly here; older versions either reject\n * `hooks.allowConversationAccess` outright (≤ 2026.4.21) or have unverified\n * gating behaviour (2026.4.22 – 2026.4.24). Refusing to install on older\n * versions is intentional — we'd rather fail loudly than ship a\n * config the gateway will quarantine or hooks the dispatcher will block.\n */\nexport const MIN_OPENCLAW_VERSION = \"2026.4.25\"\n\nconst DEFAULT_TIMEOUT_MS = 10_000\n\ntype RunResult =\n | { ok: true; stdout: string; stderr: string; code: 0 }\n | { ok: false; reason: \"enoent\"; stdout: \"\"; stderr: \"\"; code: null }\n | { ok: false; reason: \"timeout\"; stdout: string; stderr: string; code: null }\n | { ok: false; reason: \"exit\"; stdout: string; stderr: string; code: number }\n\n/**\n * Spawn `openclaw <args>` synchronously. Reports failure modes structurally so\n * callers can decide how to degrade (missing binary vs. timed out vs. exited\n * non-zero). Never throws; ENOENT becomes `{ reason: \"enoent\" }`.\n *\n * `opts.env` lets callers overlay environment variables on top of `process.env`\n * — used by the install flow to inject `OPENCLAW_HOME` so `openclaw plugins\n * install`, `openclaw config validate`, and `openclaw gateway restart` all\n * resolve the same config dir as our settings-file edits. The overlay is\n * additive: the caller passes only the keys to override and we merge them on\n * top of `process.env`.\n */\nexport function runOpenclaw(\n args: string[],\n opts: { timeoutMs?: number; stdin?: string; env?: NodeJS.ProcessEnv } = {},\n): RunResult {\n const env = opts.env ? { ...process.env, ...opts.env } : undefined\n const result: SpawnSyncReturns<string> = spawnSync(\"openclaw\", args, {\n encoding: \"utf-8\",\n timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n input: opts.stdin,\n // Inherit env so the user's PATH and any LATITUDE_* / OPENCLAW_* vars\n // reach the child process. When opts.env is set, it's merged on top\n // (e.g. OPENCLAW_HOME from --openclaw-dir).\n ...(env ? { env } : {}),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n })\n\n // spawnSync surfaces I/O errors through `result.error` rather than a thrown\n // exception when called this way. Order matters: a timeout shows up as\n // `error.code === \"ETIMEDOUT\"` AND/OR `signal === \"SIGTERM\"|\"SIGKILL\"`\n // depending on platform — classify it before the generic `if (err)`\n // catch-all so callers reliably get `reason: \"timeout\"` (and the\n // timeout-specific error messages in setup.ts aren't dead code).\n const err = result.error as (NodeJS.ErrnoException & { code?: string }) | undefined\n if (err?.code === \"ENOENT\") {\n return { ok: false, reason: \"enoent\", stdout: \"\", stderr: \"\", code: null }\n }\n if (err?.code === \"ETIMEDOUT\" || result.signal === \"SIGTERM\" || result.signal === \"SIGKILL\") {\n return {\n ok: false,\n reason: \"timeout\",\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n code: null,\n }\n }\n if (err) {\n // Treat any other spawn error as an unparseable exit failure.\n return {\n ok: false,\n reason: \"exit\",\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? String(err),\n code: typeof result.status === \"number\" ? result.status : 1,\n }\n }\n\n if (result.status === 0) {\n return { ok: true, stdout: result.stdout ?? \"\", stderr: result.stderr ?? \"\", code: 0 }\n }\n\n return {\n ok: false,\n reason: \"exit\",\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n code: typeof result.status === \"number\" ? result.status : 1,\n }\n}\n\ntype VersionLookup =\n | { ok: true; version: string; raw: string }\n | { ok: false; error: \"missing\" | \"unparseable\"; raw?: string | undefined }\n\n/**\n * Run `openclaw --version` and parse out the version string.\n *\n * Banner format (per OpenClaw `src/cli/banner.ts` `formatCliBannerLine`):\n * `🦞 OpenClaw <version> (<commit-sha>)`\n *\n * The banner is normally suppressed when `--version` is the flag, but the\n * version itself still goes to stdout. We accept either layout (with or\n * without the lobster + commit sha) so we don't break if OpenClaw later\n * prints just the bare version string.\n */\nexport function getOpenclawVersion(): VersionLookup {\n const result = runOpenclaw([\"--version\"], { timeoutMs: 5_000 })\n if (!result.ok) {\n if (result.reason === \"enoent\") return { ok: false, error: \"missing\" }\n return { ok: false, error: \"unparseable\", raw: result.stdout || result.stderr }\n }\n\n const raw = result.stdout.trim()\n // Match a CalVer triple anywhere in the output. Be liberal — banner\n // decorations (emoji, \"OpenClaw\" prefix, trailing \"(sha)\") all just\n // surround a single `YYYY.M.PATCH` somewhere.\n const match = raw.match(/(\\d{4}\\.\\d+\\.\\d+)/)\n if (!match) return { ok: false, error: \"unparseable\", raw }\n return { ok: true, version: match[1] as string, raw }\n}\n\n/**\n * Compare two CalVer strings (`YYYY.M.PATCH`). Returns -1 if `a` < `b`,\n * 0 if equal, 1 if `a` > `b`. Strings with extra components or non-numeric\n * pieces fall back to a per-component string comparison so unexpected\n * formats don't crash the installer.\n */\nexport function compareCalver(a: string, b: string): -1 | 0 | 1 {\n const ap = a.split(\".\")\n const bp = b.split(\".\")\n const len = Math.max(ap.length, bp.length)\n for (let i = 0; i < len; i++) {\n const ai = ap[i] ?? \"0\"\n const bi = bp[i] ?? \"0\"\n const an = Number(ai)\n const bn = Number(bi)\n if (Number.isFinite(an) && Number.isFinite(bn)) {\n if (an < bn) return -1\n if (an > bn) return 1\n continue\n }\n if (ai < bi) return -1\n if (ai > bi) return 1\n }\n return 0\n}\n","import { copyFileSync, existsSync, readFileSync, renameSync, writeFileSync } from \"node:fs\"\n\n/** Plugin id used both as the npm package name and as the OpenClaw plugin id. */\nexport const PLUGIN_ID = \"@latitude-data/openclaw-telemetry\"\n\n/**\n * The shape OpenClaw's strict zod schema accepts for a single\n * `plugins.entries[id]` block. We keep this minimal — only the fields we\n * actually write — so we don't drop anything when round-tripping an entry\n * with fields we don't know about.\n */\ninterface OpenClawPluginEntry {\n enabled?: boolean\n /**\n * Strict zod object on OpenClaw 2026.4.25+: only `allowPromptInjection` and\n * `allowConversationAccess` are accepted. We populate the latter — it's the\n * dispatch gate the OpenClaw runtime checks before forwarding LLM/tool/agent\n * events to non-bundled plugins. Without it, the gateway logs\n * `[plugins] typed hook \"...\" blocked because non-bundled plugins must set\n * plugins.entries.<id>.hooks.allowConversationAccess=true` and the plugin's\n * handlers never fire.\n */\n hooks?: {\n allowPromptInjection?: boolean\n allowConversationAccess?: boolean\n [key: string]: unknown\n }\n /**\n * Free-form bucket OpenClaw passes to the plugin at activation\n * (`api.pluginConfig`). Our credentials and feature flags live here.\n */\n config?: Record<string, unknown>\n // Pass through anything else that may already be there — `subagent`, etc.\n [key: string]: unknown\n}\n\nexport interface OpenClawSettings {\n plugins?: {\n enabled?: boolean\n entries?: Record<string, OpenClawPluginEntry>\n /**\n * Operator-managed allowlist of plugin ids that may auto-load. OpenClaw\n * warns at every gateway start when a non-bundled plugin loads without\n * provenance via `plugins.allow` or an install record.\n */\n allow?: string[]\n load?: { paths?: string[] }\n [key: string]: unknown\n }\n [key: string]: unknown\n}\n\nexport interface LatitudePluginConfig {\n apiKey: string\n project: string\n baseUrl?: string | undefined\n allowConversationAccess?: boolean | undefined\n debug?: boolean | undefined\n}\n\nexport function readSettings(settingsPath: string): OpenClawSettings {\n if (!existsSync(settingsPath)) return {}\n try {\n const raw = readFileSync(settingsPath, \"utf-8\")\n const parsed = JSON.parse(raw) as OpenClawSettings\n return parsed && typeof parsed === \"object\" ? parsed : {}\n } catch {\n return {}\n }\n}\n\n/**\n * Write `openclaw.json` atomically: serialize to a sibling tempfile, then\n * rename over the target. `rename` is atomic on POSIX and effectively atomic\n * on Windows for our case — a crash mid-serialization can no longer leave a\n * truncated `openclaw.json` (combined with `.latitude-bak`, recovery is\n * always possible).\n *\n * Earlier versions used `writeFileSync` directly; that left a corruption\n * window where SIGTERM between `open()` and the final `write()` would leave\n * a partial JSON file the gateway couldn't parse.\n */\nexport function writeSettings(settingsPath: string, settings: OpenClawSettings): void {\n const tmp = `${settingsPath}.tmp.${process.pid}`\n writeFileSync(tmp, `${JSON.stringify(settings, null, 2)}\\n`, \"utf-8\")\n renameSync(tmp, settingsPath)\n}\n\nexport function backupSettings(settingsPath: string, backupPath: string): void {\n if (existsSync(settingsPath)) copyFileSync(settingsPath, backupPath)\n}\n\n/**\n * Restore the backup over the live settings file. Used when a post-write\n * step (e.g. `openclaw config validate --json`) reports the new file is\n * invalid — better to roll back to a known-good state than leave a broken\n * config that'll fail at gateway-restart time.\n */\nexport function restoreBackup(settingsPath: string, backupPath: string): boolean {\n if (!existsSync(backupPath)) return false\n copyFileSync(backupPath, settingsPath)\n return true\n}\n\ninterface SetPluginEntryPatch {\n /** New API key. Always overwrites — comes from the install prompt. */\n apiKey: string\n /** New project slug. Always overwrites — comes from the install prompt. */\n project: string\n /**\n * New `baseUrl`. `undefined` clears any existing override (used when\n * installing back to production). Anything else overwrites.\n */\n baseUrl: string | undefined\n /**\n * `true`/`false` overwrites both `config.allowConversationAccess` AND\n * `hooks.allowConversationAccess` (always coupled — see `setPluginEntry`).\n * `undefined` preserves the existing value, or defaults to `true` for a\n * fresh install.\n */\n allowConversationAccess?: boolean | undefined\n /**\n * `true`/`false` overwrites; `undefined` preserves the existing value (the\n * installer doesn't pass `debug` — it's a hand-edit affordance).\n */\n debug?: boolean | undefined\n /**\n * `true`/`false` overwrites; `undefined` preserves existing, defaulting to\n * `true` for a fresh install. This keeps a paused plugin (`enabled: false`\n * hand-edited in openclaw.json) paused across re-installs.\n */\n enabled?: boolean | undefined\n}\n\n/**\n * Set the `plugins.entries[id]` block for our plugin.\n *\n * Two places in the entry get written:\n *\n * - `.config` (free-form `record(string, unknown)`): credentials, baseUrl,\n * and our copy of `allowConversationAccess`. This is what the plugin\n * runtime reads via `api.pluginConfig`.\n * - `.hooks.allowConversationAccess`: controls whether OpenClaw's hook\n * dispatcher actually forwards `llm_input` / `llm_output` / tool /\n * `agent_end` events to our handlers. Without it set to `true` on\n * OpenClaw 2026.4.25+, every typed hook is blocked at the dispatcher\n * and the plugin's handlers never fire.\n *\n * The two flags mean different things — `hooks.*` is the dispatch gate,\n * `config.*` is the payload-content gate — but for THIS plugin we always\n * couple them: dispatch off + payload on is useless (no payloads to gate),\n * and dispatch on + payload off is a legitimate \"structural-only telemetry\"\n * mode (timing, tokens, ids, agent name; no message bodies). Always writing\n * both from the same source keeps the operator's mental model simple.\n *\n * Re-install idempotency: only `apiKey` / `project` / `baseUrl` always\n * overwrite (these come from install prompts). `enabled`, `debug`, and\n * `allowConversationAccess` are preserved when not provided in the patch.\n */\nexport function setPluginEntry(settings: OpenClawSettings, patch: SetPluginEntryPatch): void {\n const plugins = settings.plugins ?? {}\n const entries = plugins.entries ?? {}\n const existing = entries[PLUGIN_ID] ?? {}\n const existingConfig = (existing.config ?? {}) as Record<string, unknown>\n const existingHooks = existing.hooks ?? {}\n\n const nextConfig: Record<string, unknown> = {\n ...existingConfig,\n apiKey: patch.apiKey,\n project: patch.project,\n }\n if (patch.baseUrl !== undefined) {\n nextConfig.baseUrl = patch.baseUrl\n } else {\n delete nextConfig.baseUrl\n }\n if (patch.allowConversationAccess !== undefined) {\n nextConfig.allowConversationAccess = patch.allowConversationAccess\n }\n if (patch.debug !== undefined) {\n nextConfig.debug = patch.debug\n }\n\n // Resolve the effective allowConversationAccess for the hooks block.\n // We mirror whatever ended up in nextConfig.allowConversationAccess (just\n // computed above) so the two flags always agree. If neither the patch nor\n // the existing config sets it, fall back to the existing hooks value, then\n // to `true` (matches the README's first-install promise).\n const effectiveAccess =\n typeof nextConfig.allowConversationAccess === \"boolean\"\n ? nextConfig.allowConversationAccess\n : typeof existingHooks.allowConversationAccess === \"boolean\"\n ? existingHooks.allowConversationAccess\n : true\n\n const nextHooks: OpenClawPluginEntry[\"hooks\"] = {\n ...existingHooks,\n allowConversationAccess: effectiveAccess,\n }\n\n // Preserve a hand-edited `enabled: false` across re-installs. Fresh install\n // (no existing entry, no explicit patch) defaults to true.\n const nextEnabled = patch.enabled ?? existing.enabled ?? true\n\n entries[PLUGIN_ID] = {\n ...existing,\n enabled: nextEnabled,\n hooks: nextHooks,\n config: nextConfig,\n }\n plugins.entries = entries\n settings.plugins = plugins\n}\n\n/** Remove the plugin entry entirely. Used by uninstall as defense-in-depth. */\nexport function removePluginEntry(settings: OpenClawSettings): boolean {\n const plugins = settings.plugins\n if (!plugins?.entries) return false\n if (!(PLUGIN_ID in plugins.entries)) return false\n delete plugins.entries[PLUGIN_ID]\n return true\n}\n\n/**\n * Add the plugin id to `plugins.allow`. Idempotent — returns `true` only when\n * the array changed. OpenClaw warns at every gateway start when a non-bundled\n * plugin auto-loads without provenance via `plugins.allow` or an install\n * record. We get one warning cleared by going through `openclaw plugins\n * install` (provenance) and the other by adding ourselves to allow.\n *\n * Defensive against hand-edited non-array values: if `plugins.allow` is\n * present but not an array (e.g. someone wrote a string), we replace it\n * with a single-element array rather than spreading the bad value.\n */\nexport function addToPluginsAllow(settings: OpenClawSettings): boolean {\n const plugins = settings.plugins ?? {}\n const existing = plugins.allow\n const allow = Array.isArray(existing) ? existing : []\n if (allow.includes(PLUGIN_ID)) return false\n plugins.allow = [...allow, PLUGIN_ID]\n settings.plugins = plugins\n return true\n}\n\n/**\n * Inverse of `addToPluginsAllow`. Defense-in-depth — `openclaw plugins\n * uninstall` already strips the entry, but the install path can be skipped\n * (e.g. the user removed the plugin manually) and we want re-install/uninstall\n * round-trips to be tidy regardless.\n */\nexport function removeFromPluginsAllow(settings: OpenClawSettings): boolean {\n const allow = settings.plugins?.allow\n if (!Array.isArray(allow) || !allow.includes(PLUGIN_ID)) return false\n if (settings.plugins) {\n settings.plugins.allow = allow.filter((id) => id !== PLUGIN_ID)\n }\n return true\n}\n\nexport function hasLatitudePlugin(settings: OpenClawSettings): boolean {\n return Boolean(settings.plugins?.entries && PLUGIN_ID in settings.plugins.entries)\n}\n\n/**\n * Strip leftover keys from older installers that the strict zod schema\n * rejects on current OpenClaw versions.\n *\n * 0.0.1 wrote `LATITUDE_*` keys directly under `settings.env`. OpenClaw's\n * root schema is strict; the `env` block accepts only `{shellEnv, vars}`,\n * so those keys cause the gateway to quarantine the config as\n * `clobbered.<ts>` and roll back. We sweep them on every install.\n *\n * Note: 0.0.1 also wrote `hooks.allowConversationAccess` (when that key was\n * not yet in the schema). We deliberately do NOT strip it anymore — on\n * OpenClaw 2026.4.25+ the key IS in the schema and IS load-bearing for\n * dispatch. `setPluginEntry` overwrites it on every install with the right\n * value, so any 0.0.1 leftover is reconciled there.\n */\nexport function migrateLegacyEntries(settings: OpenClawSettings): { changed: boolean } {\n let changed = false\n\n const env = settings.env\n if (env && typeof env === \"object\" && !Array.isArray(env)) {\n const envObj = env as Record<string, unknown>\n for (const key of [\"LATITUDE_API_KEY\", \"LATITUDE_PROJECT\", \"LATITUDE_BASE_URL\"]) {\n if (key in envObj) {\n delete envObj[key]\n changed = true\n }\n }\n // Drop the env object only if it's now empty; don't touch a real\n // OpenClaw env block (`{shellEnv, vars}`) that already had those keys.\n if (Object.keys(envObj).length === 0) {\n delete settings.env\n }\n }\n\n return { changed }\n}\n\n/**\n * Read OpenClaw's plugins install record (`<configDir>/plugins/installs.json`)\n * and return the version recorded for our plugin id, if any. Used by the\n * install flow to render `Upgrading 0.0.6 → 0.0.7` UX.\n *\n * Best-effort: returns undefined on any read/parse error, on missing file,\n * on schemas we don't recognize. Callers must handle the undefined case\n * (typically by rendering a \"fresh install\" path).\n */\nexport function readInstalledRuntimeVersion(installsPath: string): string | undefined {\n if (!existsSync(installsPath)) return undefined\n try {\n const raw = readFileSync(installsPath, \"utf-8\")\n const parsed = JSON.parse(raw) as unknown\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined\n const entries = (parsed as { entries?: Record<string, unknown> }).entries\n if (!entries || typeof entries !== \"object\") return undefined\n const ours = entries[PLUGIN_ID]\n if (!ours || typeof ours !== \"object\") return undefined\n const version = (ours as { version?: unknown }).version\n return typeof version === \"string\" ? version : undefined\n } catch {\n return undefined\n }\n}\n","import { readFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\n/**\n * Read this CLI's own version from its `package.json`. Used by:\n * - `cli.ts` for `--version`\n * - `setup.ts` in the npm-registry-404 abort message, so the upgrade\n * instruction shows the version the user is actually running. Earlier\n * versions read `process.env.npm_package_version`, but that env var\n * is only set by `npm run` — not by `npx`, not by globally-installed\n * bins. The vast majority of `latitude-openclaw` invocations come\n * through one of those, so the env-var approach printed\n * `(this version)` instead of the real version every time.\n */\nexport function readCliVersion(): string {\n // dist/cli.js → ../package.json (after bundling); src/version.ts →\n // ../package.json under vitest's source-mode runner. Both shapes have\n // the package.json one directory up from the entry file.\n const here = dirname(fileURLToPath(import.meta.url))\n const pkgPath = join(here, \"..\", \"package.json\")\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as { version?: string }\n return pkg.version ?? \"unknown\"\n } catch {\n return \"unknown\"\n }\n}\n","import { existsSync, mkdirSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\nimport { cancel, confirm, intro, isCancel, log, note, outro, password, spinner, text } from \"@clack/prompts\"\nimport pc from \"picocolors\"\nimport { pathsFor, type ResolvedConfigDir, resolveConfigDir, type SettingsPaths } from \"./config-dir.ts\"\nimport { jsonDiff } from \"./diff.ts\"\nimport { compareCalver, getOpenclawVersion, MIN_OPENCLAW_VERSION, runOpenclaw } from \"./openclaw-cli.ts\"\nimport {\n addToPluginsAllow,\n backupSettings,\n hasLatitudePlugin,\n type LatitudePluginConfig,\n migrateLegacyEntries,\n type OpenClawSettings,\n PLUGIN_ID,\n readInstalledRuntimeVersion,\n readSettings,\n removeFromPluginsAllow,\n removePluginEntry,\n restoreBackup,\n setPluginEntry,\n writeSettings,\n} from \"./settings-file.ts\"\nimport { readCliVersion } from \"./version.ts\"\n\n/**\n * The runtime version this CLI installs. Lockstep with the runtime — bump\n * both packages in the same commit. The CLI's npm-spec install is pinned to\n * this exact version (not a floating tag), so we never accidentally install\n * a runtime that doesn't match the contract this CLI was built against.\n *\n * Pinning also satisfies OpenClaw's `Pin install specs to exact versions`\n * supply-chain audit warning automatically.\n */\nconst RUNTIME_PACKAGE_NAME = \"@latitude-data/openclaw-telemetry\"\nconst RUNTIME_VERSION = \"0.0.8\"\n\nconst DOCS_URL = \"https://docs.latitude.so/telemetry/openclaw\"\n\ninterface EnvironmentConfig {\n name: \"production\" | \"staging\" | \"dev\"\n label: string\n app: string\n ingest: string\n}\n\nconst PRODUCTION_ENV: EnvironmentConfig = {\n name: \"production\",\n label: \"production\",\n app: \"https://console.latitude.so\",\n ingest: \"https://ingest.latitude.so\",\n}\nconst STAGING_ENV: EnvironmentConfig = {\n name: \"staging\",\n label: \"staging\",\n app: \"https://staging.latitude.so\",\n ingest: \"https://staging-ingest.latitude.so\",\n}\nconst DEV_ENV: EnvironmentConfig = {\n name: \"dev\",\n label: \"local dev\",\n app: \"http://localhost:3000\",\n ingest: \"http://localhost:3002\",\n}\n\nfunction urlsFor(env: EnvironmentConfig): {\n apiKeys: (slug: string) => string\n projects: string\n projectView: (slug: string) => string\n} {\n return {\n apiKeys: (slug: string) => `${env.app}/projects/${slug}/settings/keys`,\n projects: env.app,\n projectView: (slug: string) => `${env.app}/projects/${slug}`,\n }\n}\n\ninterface InstallFlags {\n apiKey?: string | undefined\n project?: string | undefined\n environment?: EnvironmentConfig | undefined\n /**\n * Tristate: `true` = capture (`--allow-conversation`), `false` = scrub\n * (`--no-content`), `undefined` = preserve existing or first-install\n * default. Tristate keeps re-install idempotent for hand-edited values.\n */\n allowConversationAccess?: boolean | undefined\n /** When true, skip adding the plugin id to `plugins.allow`. */\n noTrust?: boolean\n /** Override for the OpenClaw config directory. */\n openclawDir?: string | undefined\n /** When true, render the diff and the install spec without making any changes. */\n dryRun?: boolean\n /**\n * Restart-the-gateway behaviour:\n * - `\"force\"` (--restart): always restart, even non-TTY.\n * - `\"never\"` (--no-restart): never restart, even on TTY.\n * - `\"auto\"` (default): prompt on TTY, skip otherwise.\n */\n restart?: \"force\" | \"never\" | \"auto\"\n noPrompt?: boolean\n yes?: boolean\n}\n\n/**\n * Flags that take a value (either `--key=value` or `--key value`). Bare\n * `--key` is reserved for booleans and shouldn't consume the next argv\n * token as its value — operators expect `--no-content` next to `--yes`\n * to mean two booleans, not \"no-content takes the value `--yes`\".\n */\nconst VALUE_FLAGS = new Set([\"api-key\", \"project\", \"openclaw-dir\"])\n\nexport function parseFlags(argv: string[]): {\n subcommand: string | undefined\n flags: Record<string, string | boolean>\n} {\n const [subcommand, ...rest] = argv\n const flags: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n if (!arg || !arg.startsWith(\"--\")) continue\n const eq = arg.indexOf(\"=\")\n if (eq >= 0) {\n flags[arg.slice(2, eq)] = arg.slice(eq + 1)\n continue\n }\n const key = arg.slice(2)\n const next = rest[i + 1]\n // Space-separated form (`--key value`): only consume the next token\n // when the key is in VALUE_FLAGS and the next token isn't itself a\n // flag. This matches the standard convention everyone expects from\n // CLIs, while keeping bare boolean flags from accidentally swallowing\n // adjacent flags as values.\n if (VALUE_FLAGS.has(key) && next !== undefined && !next.startsWith(\"--\")) {\n flags[key] = next\n i += 1\n } else {\n flags[key] = true\n }\n }\n return { subcommand, flags }\n}\n\nexport function normalizeInstallFlags(flags: Record<string, string | boolean>): InstallFlags {\n let environment: EnvironmentConfig | undefined\n if (flags.staging === true) environment = STAGING_ENV\n if (flags.dev === true) {\n if (environment) throw new Error(\"--staging and --dev are mutually exclusive\")\n environment = DEV_ENV\n }\n // Tristate: leave undefined unless the user explicitly asked one way or the\n // other. Re-install then preserves whatever's already in openclaw.json.\n let allowConversationAccess: boolean | undefined\n if (flags[\"no-content\"] === true || flags[\"no-conversation\"] === true) allowConversationAccess = false\n if (flags[\"allow-conversation\"] === true) allowConversationAccess = true\n\n let restart: \"force\" | \"never\" | \"auto\" = \"auto\"\n if (flags[\"no-restart\"] === true) restart = \"never\"\n if (flags.restart === true) {\n if (flags[\"no-restart\"] === true) throw new Error(\"--restart and --no-restart are mutually exclusive\")\n restart = \"force\"\n }\n\n return {\n apiKey: typeof flags[\"api-key\"] === \"string\" ? flags[\"api-key\"] : undefined,\n project: typeof flags.project === \"string\" ? flags.project : undefined,\n environment,\n allowConversationAccess,\n noTrust: flags[\"no-trust\"] === true,\n openclawDir: typeof flags[\"openclaw-dir\"] === \"string\" ? flags[\"openclaw-dir\"] : undefined,\n dryRun: flags[\"dry-run\"] === true,\n restart,\n noPrompt: flags[\"no-prompt\"] === true || flags.yes === true,\n yes: flags.yes === true,\n }\n}\n\n// ─── Install ────────────────────────────────────────────────────────────────\n\nexport async function runInstall(flags: InstallFlags = {}): Promise<void> {\n const canPrompt = !flags.noPrompt && process.stdin.isTTY === true\n if (!canPrompt) return runFlagDrivenInstall(flags)\n await runInteractiveInstall(flags)\n}\n\nfunction resolvePaths(flags: InstallFlags): { resolved: ResolvedConfigDir; paths: SettingsPaths } {\n const resolved = resolveConfigDir({ flag: flags.openclawDir })\n return { resolved, paths: pathsFor(resolved.dir) }\n}\n\nfunction envForSubprocess(paths: SettingsPaths): NodeJS.ProcessEnv {\n // Pass OPENCLAW_HOME to the subprocess so its own resolution lines up\n // with ours. Caveat documented in config-dir.ts — at the time of writing\n // OpenClaw's CLI doesn't advertise OPENCLAW_HOME support; the post-write\n // `openclaw config validate --json` step catches a config-dir mismatch.\n return { OPENCLAW_HOME: paths.configDir }\n}\n\nasync function runInteractiveInstall(flags: InstallFlags): Promise<void> {\n intro(pc.bgCyan(pc.black(\" Latitude · OpenClaw telemetry \")))\n\n // Bail before any prompts if the host OpenClaw is too old. We'd rather\n // tell the user to upgrade than walk them through a config we can't make\n // work on their version.\n ensureOpenclawIsCompatible()\n\n const { resolved, paths } = resolvePaths(flags)\n log.info(`Using config dir: ${pc.dim(paths.configDir)} ${pc.dim(`(source: ${resolved.source})`)}`)\n\n // Lockstep contract check. If the runtime version we're built against\n // isn't on npm yet (half-published release), abort with a clear upgrade\n // path before we waste any of the operator's time on prompts.\n await ensureRuntimeOnNpm()\n\n // Render the upgrade UX before any spawn. Reads OpenClaw's own install\n // record so the version is what's *actually* running, not what was last\n // hand-edited into openclaw.json.\n const installedVersion = readInstalledRuntimeVersion(paths.installsPath)\n if (installedVersion !== undefined) {\n if (installedVersion === RUNTIME_VERSION) {\n log.info(`${PLUGIN_ID} ${pc.dim(installedVersion)} already installed — re-applying (idempotent).`)\n } else {\n log.info(`Upgrading ${PLUGIN_ID} from ${pc.dim(installedVersion)} → ${pc.cyan(RUNTIME_VERSION)}`)\n }\n }\n\n const existing = readSettings(paths.settingsPath)\n const existingConfig =\n (existing.plugins?.entries?.[PLUGIN_ID]?.config as LatitudePluginConfig | undefined) ?? undefined\n const envConfig = flags.environment ?? PRODUCTION_ENV\n const urls = urlsFor(envConfig)\n\n const aboutLines = [\n \"Captures every OpenClaw agent run and ships it to Latitude as\",\n \"OpenTelemetry traces — full system prompt, tool I/O, messages,\",\n \"token usage, and agent name on every span.\",\n \"\",\n `${pc.dim(\"Docs\")} ${pc.cyan(DOCS_URL)}`,\n ]\n if (envConfig.name !== \"production\") {\n aboutLines.push(\"\", pc.yellow(`Using ${envConfig.label} environment (${envConfig.ingest})`))\n }\n note(aboutLines.join(\"\\n\"), \"About\")\n\n log.info(`Create a project at ${pc.cyan(urls.projects)}`)\n\n const project = await promptProject(existingConfig?.project, flags.project)\n log.info(`Get an API key at ${pc.cyan(urls.apiKeys(project))}`)\n const apiKey = await promptApiKey(existingConfig?.apiKey, flags.apiKey)\n\n await applyChanges({\n apiKey,\n project,\n envConfig,\n allowConversationAccess: flags.allowConversationAccess,\n noTrust: flags.noTrust === true,\n paths,\n dryRun: flags.dryRun === true,\n })\n\n if (!flags.dryRun) {\n await maybeRestartGateway(flags.restart ?? \"auto\", paths)\n }\n\n note(\n [\n flags.dryRun ? \"Dry-run only — nothing was written.\" : \"Plugin installed and configured.\",\n `View your traces at ${pc.cyan(urls.projectView(project))}`,\n ].join(\"\\n\"),\n \"Next step\",\n )\n outro(flags.dryRun ? pc.dim(\"(dry-run)\") : pc.green(\"✓ Installed\"))\n}\n\nasync function runFlagDrivenInstall(flags: InstallFlags): Promise<void> {\n ensureOpenclawIsCompatible()\n const { resolved, paths } = resolvePaths(flags)\n process.stdout.write(`Using config dir: ${paths.configDir} (source: ${resolved.source})\\n`)\n await ensureRuntimeOnNpm()\n\n const apiKey = flags.apiKey\n const project = flags.project\n if (!apiKey || !project) {\n throw new Error(\"Non-interactive install requires --api-key=... and --project=... (or run in a TTY).\")\n }\n const envConfig = flags.environment ?? PRODUCTION_ENV\n await applyChanges({\n apiKey,\n project,\n envConfig,\n allowConversationAccess: flags.allowConversationAccess,\n noTrust: flags.noTrust === true,\n paths,\n dryRun: flags.dryRun === true,\n })\n if (flags.dryRun) {\n process.stdout.write(\"Dry-run only — nothing was written.\\n\")\n return\n }\n await maybeRestartGateway(flags.restart ?? \"auto\", paths)\n process.stdout.write(`Installed Latitude plugin in ${paths.settingsPath}\\n`)\n}\n\nasync function promptApiKey(_existing: string | undefined, flag: string | undefined): Promise<string> {\n if (flag) return flag\n const result = await password({\n message: \"Latitude API key\",\n mask: \"•\",\n validate: (v) => (v && v.length > 0 ? undefined : \"Required\"),\n })\n if (isCancel(result)) return onCancel()\n return result\n}\n\nasync function promptProject(existing: string | undefined, flag: string | undefined): Promise<string> {\n if (flag) return flag\n const result = await text({\n message: \"Latitude project slug\",\n placeholder: existing ?? \"my-openclaw-project\",\n ...(existing ? { initialValue: existing } : {}),\n validate: (v) => (v && v.length > 0 ? undefined : \"Required\"),\n })\n if (isCancel(result)) return onCancel()\n return result\n}\n\nfunction onCancel(): never {\n cancel(\"Cancelled — nothing was changed\")\n process.exit(1)\n}\n\ninterface ApplyParams {\n apiKey: string\n project: string\n envConfig: EnvironmentConfig\n /** Tristate — see `InstallFlags.allowConversationAccess`. */\n allowConversationAccess: boolean | undefined\n noTrust: boolean\n paths: SettingsPaths\n dryRun: boolean\n}\n\nasync function applyChanges({\n apiKey,\n project,\n envConfig,\n allowConversationAccess,\n noTrust,\n paths,\n dryRun,\n}: ApplyParams): Promise<void> {\n const installSpec = `${RUNTIME_PACKAGE_NAME}@${RUNTIME_VERSION}`\n\n // Build the proposed post-install settings without writing them. The\n // dry-run path uses this directly; the real path writes the same value.\n const before = readSettings(paths.settingsPath)\n const after = structuredClone(before) as OpenClawSettings\n migrateLegacyEntries(after)\n setPluginEntry(after, {\n apiKey,\n project,\n baseUrl: envConfig.name === \"production\" ? undefined : envConfig.ingest,\n allowConversationAccess,\n })\n if (!noTrust) addToPluginsAllow(after)\n\n if (dryRun) {\n log.info(`Would run: ${pc.dim(`openclaw plugins install ${installSpec} --force`)}`)\n const diff = jsonDiff(before, after, { fromLabel: paths.settingsPath, toLabel: `${paths.settingsPath} (proposed)` })\n if (diff.length > 0) {\n process.stdout.write(`${diff}\\n`)\n } else {\n log.info(`No openclaw.json changes — current config already matches.`)\n }\n return\n }\n\n // 1. Take the backup BEFORE openclaw plugins install touches openclaw.json\n // so it represents the user's true pre-install state. `openclaw plugins\n // install` creates plugins.entries[id], so backing up after would lose\n // the original \"no entry\" state and make recovery harder if any later\n // step fails.\n ensureSettingsDir(paths)\n backupSettings(paths.settingsPath, paths.settingsBackupPath)\n\n // 2. Hand placement off to OpenClaw via the npm-spec form. OpenClaw fetches\n // the package from npm, runs its install-time security scan, copies\n // files into <configDir>/extensions/<encoded-id>/, writes the install\n // record into <configDir>/plugins/installs.json, and creates a (disabled,\n // configless) plugins.entries[id] in openclaw.json. We layer config +\n // hooks + allow on top in step 3. --force lets us overwrite an existing\n // install (e.g. when re-running on top of a previous version).\n const installSpinner = spinner()\n installSpinner.start(`Installing plugin via openclaw plugins install ${installSpec}`)\n const installResult = runOpenclaw([\"plugins\", \"install\", installSpec, \"--force\"], {\n timeoutMs: 60_000,\n env: envForSubprocess(paths),\n })\n if (!installResult.ok) {\n installSpinner.stop(\"openclaw plugins install failed\")\n if (installResult.reason === \"enoent\") {\n throw new Error(\"`openclaw` not found on PATH. Install OpenClaw first (https://openclaw.ai/install) and re-run.\")\n }\n if (installResult.reason === \"timeout\") {\n throw new Error(\"openclaw plugins install timed out after 60s. Try running it manually to see what's stuck.\")\n }\n const detail = installResult.stderr.trim() || installResult.stdout.trim() || `exit code ${installResult.code}`\n throw new Error(`openclaw plugins install failed: ${detail}`)\n }\n installSpinner.stop(\"Plugin registered with OpenClaw\")\n\n // 3. Layer our config, hooks, and (optionally) plugins.allow on top of the\n // entry OpenClaw just created. Atomic write — temp + rename — combined\n // with the .latitude-bak backup means a SIGTERM mid-write can never\n // leave openclaw.json in a half-serialized state.\n //\n // Snapshot the post-`openclaw plugins install` state for rollback. The\n // pre-install backup at `.latitude-bak` covers the typical case where\n // openclaw.json existed before our flow, but `backupSettings` is a no-op\n // when there's no source file to copy — that's fresh installs (no prior\n // openclaw.json). For those, the post-install state captured here is the\n // valid rollback target: it reflects exactly what `openclaw plugins\n // install` produced, which is always schema-valid (a disabled\n // plugins.entries[id] block + whatever else OpenClaw bootstrapped).\n const postInstallSnapshot = readSettings(paths.settingsPath)\n\n const settingsSpinner = spinner()\n settingsSpinner.start(\"Updating openclaw.json\")\n const settings = readSettings(paths.settingsPath)\n migrateLegacyEntries(settings)\n setPluginEntry(settings, {\n apiKey,\n project,\n baseUrl: envConfig.name === \"production\" ? undefined : envConfig.ingest,\n allowConversationAccess,\n // `debug` intentionally not passed — `setPluginEntry` preserves\n // hand-edited values; fresh installs leave the key absent (runtime\n // default is `false`).\n })\n if (!noTrust) {\n addToPluginsAllow(settings)\n }\n writeSettings(paths.settingsPath, settings)\n settingsSpinner.stop(`Updated ${paths.settingsPath}`)\n if (existsSync(paths.settingsBackupPath)) log.info(`Backup saved at ${pc.dim(paths.settingsBackupPath)}`)\n if (noTrust) {\n log.warning(\n `--no-trust set; OpenClaw will warn at every gateway start that ${PLUGIN_ID} is untrusted. Add it to plugins.allow yourself when you're ready.`,\n )\n }\n\n // 4. Validate. Catches schema regressions and config-dir mismatch (where\n // OpenClaw didn't pick up our OPENCLAW_HOME and wrote the install\n // record to a different file from where we wrote the entry) before the\n // operator restarts the gateway and finds out the hard way.\n const validateSpinner = spinner()\n validateSpinner.start(\"Validating openclaw.json\")\n const validateResult = runOpenclaw([\"config\", \"validate\", \"--json\"], {\n timeoutMs: 10_000,\n env: envForSubprocess(paths),\n })\n if (!validateResult.ok || isInvalidConfigPayload(validateResult.stdout)) {\n validateSpinner.stop(\"openclaw config validate failed\")\n const rollback = rollbackSettings(paths, postInstallSnapshot)\n const detail =\n validateResult.ok === false\n ? validateResult.stderr.trim() || validateResult.stdout.trim() || `exit code ${validateResult.code}`\n : validateResult.stdout.trim() || \"config validate reported invalid config\"\n throw new Error(`openclaw config validate reported a problem after our changes:\\n ${detail}\\n${rollback}`)\n }\n validateSpinner.stop(\"Config valid\")\n}\n\n/**\n * Two-tier rollback for validation failures:\n *\n * 1. If `.latitude-bak` exists (typical case — operator had openclaw.json\n * before this flow), restore from it. That's the user's true\n * pre-install state.\n * 2. If `.latitude-bak` doesn't exist (fresh install — no openclaw.json\n * pre-flow, so `backupSettings` no-op'd), write `postInstallSnapshot`\n * back. That's the state immediately after `openclaw plugins install`\n * and before any of our config layering — schema-valid, just a\n * disabled plugin entry. Operator can re-run install or run\n * `openclaw plugins uninstall` to fully revert OpenClaw's bookkeeping.\n *\n * Returns the human-readable recovery line to append to the thrown error.\n */\nfunction rollbackSettings(paths: SettingsPaths, postInstallSnapshot: OpenClawSettings): string {\n if (existsSync(paths.settingsBackupPath)) {\n const restored = restoreBackup(paths.settingsPath, paths.settingsBackupPath)\n return restored\n ? `Backup restored from ${paths.settingsBackupPath} — your config is back to the pre-install state.`\n : `Backup at ${paths.settingsBackupPath} could not be restored automatically; restore it manually.`\n }\n // Fresh-install path: write the post-install snapshot back. Atomic, same\n // as the install write.\n try {\n writeSettings(paths.settingsPath, postInstallSnapshot)\n return (\n `${paths.settingsPath} rolled back to the post-\\`openclaw plugins install\\` state ` +\n `(plugin entry exists but disabled). Run \\`openclaw plugins uninstall ${PLUGIN_ID} --force\\` ` +\n \"to fully revert if you don't intend to retry.\"\n )\n } catch (err) {\n return (\n `Couldn't roll back ${paths.settingsPath} (${String(err)}). ` +\n `Run \\`openclaw plugins uninstall ${PLUGIN_ID} --force\\` and inspect the file manually.`\n )\n }\n}\n\n/** Best-effort detection of `{\"valid\": false, ...}` in the validate output. */\nfunction isInvalidConfigPayload(stdout: string): boolean {\n const trimmed = stdout.trim()\n if (!trimmed) return false\n try {\n const parsed = JSON.parse(trimmed) as { valid?: unknown }\n return parsed.valid === false\n } catch {\n // Validator may print non-JSON banners — if exit code was 0, treat as valid.\n return false\n }\n}\n\nfunction ensureSettingsDir(paths: SettingsPaths): void {\n const dir = dirname(paths.settingsPath)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n}\n\n/**\n * Verify `openclaw` is on PATH AND its version is >= MIN_OPENCLAW_VERSION.\n * Aborts with a clear upgrade message otherwise. Called before any user\n * prompts so we don't waste their time collecting credentials we can't\n * use.\n */\nfunction ensureOpenclawIsCompatible(): void {\n const v = getOpenclawVersion()\n if (!v.ok) {\n if (v.error === \"missing\") {\n cancel(\"OpenClaw CLI not found on PATH. Install or update via `npm install -g openclaw@latest` and re-run.\")\n process.exit(1)\n }\n cancel(\n `Couldn't parse OpenClaw version output${v.raw ? ` (got: ${pc.dim(v.raw)})` : \"\"}. Run \\`openclaw --version\\` and report the output.`,\n )\n process.exit(1)\n }\n if (compareCalver(v.version, MIN_OPENCLAW_VERSION) < 0) {\n cancel(\n `OpenClaw ${v.version} is older than the minimum supported version (${MIN_OPENCLAW_VERSION}). Run \\`npm install -g openclaw@latest\\` and re-run install.`,\n )\n process.exit(1)\n }\n log.info(`OpenClaw ${pc.dim(v.version)} (>= ${MIN_OPENCLAW_VERSION})`)\n}\n\n/**\n * Lockstep contract check. The CLI installs a pinned `RUNTIME_VERSION`; if\n * that version isn't published yet (half-released release pipeline), abort\n * with a clear upgrade path so the operator's gateway doesn't end up with a\n * runtime that doesn't match this CLI's expectations.\n *\n * Best-effort — uses the npm registry's HTTP API rather than spawning\n * `npm view` so we don't require npm to be on PATH. Network failure\n * (offline) → warn-and-continue; the install spawn would fail visibly\n * anyway if the package wasn't there.\n */\nasync function ensureRuntimeOnNpm(): Promise<void> {\n const url = `https://registry.npmjs.org/${encodeURIComponent(RUNTIME_PACKAGE_NAME)}/${encodeURIComponent(RUNTIME_VERSION)}`\n try {\n const res = await fetch(url, { method: \"GET\", signal: AbortSignal.timeout(5_000) })\n if (res.status === 404) {\n cancel(\n `CLI ${readCliVersion()} expects ${RUNTIME_PACKAGE_NAME}@${RUNTIME_VERSION} but npm doesn't have that exact version. ` +\n `Upgrade the CLI: npm install -g @latitude-data/openclaw-telemetry-cli@latest`,\n )\n process.exit(1)\n }\n if (!res.ok) {\n log.warning(`npm registry check returned HTTP ${res.status}; continuing.`)\n }\n } catch (err) {\n // Offline / DNS / timeout — the install spawn will surface any real\n // problem.\n log.warning(`Couldn't reach npm registry to verify ${RUNTIME_PACKAGE_NAME}@${RUNTIME_VERSION}: ${String(err)}`)\n }\n}\n\n/**\n * Restart the OpenClaw gateway, prompting on TTY by default. The behaviour\n * is governed by `flags.restart`:\n *\n * - `\"force\"` (--restart): always restart, even non-TTY. CI escape hatch.\n * - `\"never\"` (--no-restart): never restart, even on TTY.\n * - `\"auto\"` (default): prompt on TTY (\"Restart now? [Y/n]\"); skip\n * non-TTY (and print the manual command). Hybrid that keeps interactive\n * UX safe while CI defaults to leaving the operator in control.\n */\nasync function maybeRestartGateway(mode: \"force\" | \"never\" | \"auto\", paths: SettingsPaths): Promise<void> {\n if (mode === \"never\") {\n log.info(`Skipping gateway restart (--no-restart). Run \\`openclaw gateway restart\\` when ready.`)\n return\n }\n\n let shouldRestart: boolean\n if (mode === \"force\") {\n shouldRestart = true\n } else {\n // \"auto\": prompt on TTY, skip non-TTY.\n if (process.stdin.isTTY !== true) {\n log.info(`Run \\`openclaw gateway restart\\` to load the plugin.`)\n return\n }\n const confirmed = await confirm({\n message: \"Restart the OpenClaw gateway now to load the plugin?\",\n initialValue: true,\n })\n if (isCancel(confirmed) || confirmed !== true) {\n log.info(`Run \\`openclaw gateway restart\\` when ready.`)\n return\n }\n shouldRestart = true\n }\n\n if (!shouldRestart) return\n\n log.warning(\"Restarting gateway. In-flight runs may be interrupted.\")\n const restartSpinner = spinner()\n restartSpinner.start(\"openclaw gateway restart\")\n const restartResult = runOpenclaw([\"gateway\", \"restart\"], {\n timeoutMs: 60_000,\n env: envForSubprocess(paths),\n })\n if (!restartResult.ok) {\n restartSpinner.stop(\"Gateway restart failed\")\n const detail = restartResult.stderr.trim() || restartResult.stdout.trim() || `exit code ${restartResult.code}`\n log.warning(`openclaw gateway restart failed: ${detail}. Restart it yourself when ready.`)\n return\n }\n restartSpinner.stop(\"Gateway restarted\")\n}\n\n// ─── Uninstall ──────────────────────────────────────────────────────────────\n\ninterface UninstallFlags {\n noPrompt?: boolean\n openclawDir?: string | undefined\n restart?: \"force\" | \"never\" | \"auto\"\n}\n\nexport function normalizeUninstallFlags(flags: Record<string, string | boolean>): UninstallFlags {\n let restart: \"force\" | \"never\" | \"auto\" = \"auto\"\n if (flags[\"no-restart\"] === true) restart = \"never\"\n if (flags.restart === true) {\n if (flags[\"no-restart\"] === true) throw new Error(\"--restart and --no-restart are mutually exclusive\")\n restart = \"force\"\n }\n return {\n noPrompt: flags[\"no-prompt\"] === true || flags.yes === true,\n openclawDir: typeof flags[\"openclaw-dir\"] === \"string\" ? flags[\"openclaw-dir\"] : undefined,\n restart,\n }\n}\n\nexport async function runUninstall(flags: UninstallFlags = {}): Promise<void> {\n intro(pc.bgYellow(pc.black(\" Latitude · OpenClaw telemetry — uninstall \")))\n\n const resolved = resolveConfigDir({ flag: flags.openclawDir })\n const paths = pathsFor(resolved.dir)\n log.info(`Using config dir: ${pc.dim(paths.configDir)} ${pc.dim(`(source: ${resolved.source})`)}`)\n\n const settings = readSettings(paths.settingsPath)\n const hasEntry = hasLatitudePlugin(settings)\n\n // Without an entry, there's nothing for `openclaw plugins uninstall` to\n // remove either — short-circuit cleanly. If the user wants to uninstall\n // even when our entry is gone (e.g. we crashed mid-install), they can\n // run `openclaw plugins uninstall` themselves.\n if (!hasEntry) {\n note(\"No Latitude plugin entry found — nothing to remove.\", \"Status\")\n outro(pc.dim(\"Nothing changed\"))\n return\n }\n\n const plan = [\n `Run \\`openclaw plugins uninstall ${PLUGIN_ID} --force\\` (removes files, install record, and plugin entry)`,\n `Sweep any leftover LATITUDE_* keys from settings.env`,\n `Backup of openclaw.json saved at ${paths.settingsBackupPath}`,\n ]\n note(plan.join(\"\\n\"), \"Plan\")\n\n if (!flags.noPrompt) {\n if (process.stdin.isTTY !== true) {\n // Symmetric with `runFlagDrivenInstall` requiring --api-key/--project\n // when non-TTY: there's no way for the operator to confirm a\n // destructive uninstall in a piped/CI invocation, so we refuse rather\n // than silently going ahead. `npx -y ...-cli uninstall` accidentally\n // landing inside a CI script otherwise wipes the plugin without any\n // confirming signal.\n throw new Error(\n \"Non-interactive uninstall requires --yes / --no-prompt to confirm. Re-run with --yes to bypass the prompt explicitly.\",\n )\n }\n const ok = await confirm({ message: \"Proceed?\", initialValue: true })\n if (isCancel(ok) || ok !== true) return onCancel()\n }\n\n // 1. Take the backup BEFORE openclaw plugins uninstall touches\n // openclaw.json so it represents the user's true pre-uninstall state\n // (entry + config + plugins.allow). Backing up after would capture\n // the already-stripped state, defeating the point of the backup.\n backupSettings(paths.settingsPath, paths.settingsBackupPath)\n\n // 2. Hand uninstall to OpenClaw. It removes files, the install record,\n // plugins.entries[id], plugins.allow, plugins.deny, plugins.load.paths\n // — see src/plugins/uninstall.ts.\n const s = spinner()\n s.start(\"Reverting via openclaw plugins uninstall\")\n const uninstallResult = runOpenclaw([\"plugins\", \"uninstall\", PLUGIN_ID, \"--force\"], {\n timeoutMs: 60_000,\n env: envForSubprocess(paths),\n })\n if (!uninstallResult.ok) {\n s.stop(\"openclaw plugins uninstall failed\")\n if (uninstallResult.reason === \"enoent\") {\n log.warning(\n \"`openclaw` not found on PATH. Falling back to local cleanup — files at <configDir>/extensions/ may remain.\",\n )\n } else {\n const detail =\n uninstallResult.stderr.trim() || uninstallResult.stdout.trim() || `exit code ${uninstallResult.code}`\n log.warning(`openclaw plugins uninstall reported: ${detail}. Continuing with local cleanup.`)\n }\n } else {\n s.stop(\"Plugin removed by OpenClaw\")\n }\n\n // 3. Defensive cleanup — `openclaw plugins uninstall` already strips the\n // entry and plugins.allow on success, but if it failed above (enoent\n // / non-zero) we still want our state out of openclaw.json. These\n // are all idempotent.\n const cleanupSpinner = spinner()\n cleanupSpinner.start(\"Reverting openclaw.json\")\n const post = readSettings(paths.settingsPath)\n removePluginEntry(post)\n removeFromPluginsAllow(post)\n migrateLegacyEntries(post)\n writeSettings(paths.settingsPath, post)\n cleanupSpinner.stop(\"Done\")\n\n await maybeRestartGateway(flags.restart ?? \"auto\", paths)\n\n outro(pc.green(\"✓ Uninstalled\"))\n}\n","#!/usr/bin/env node\nimport { normalizeInstallFlags, normalizeUninstallFlags, parseFlags, runInstall, runUninstall } from \"./setup.ts\"\nimport { readCliVersion } from \"./version.ts\"\n\nconst USAGE = `usage: latitude-openclaw <command> [options]\n\ncommands:\n install Install the plugin (interactive when stdin is a TTY)\n uninstall Remove the plugin entry and files\n --version, -v Print the package version\n --help, -h Print this message\n\ninstall options:\n --api-key=<key> Pass the API key non-interactively\n --project=<slug> Pass the project slug non-interactively\n --staging Target https://staging.latitude.so / staging-ingest\n --dev Target http://localhost:3000 / 3002\n --no-content Skip raw prompt/response/tool I/O capture\n --allow-conversation Force conversation capture on (overrides existing config)\n --no-trust Skip adding plugin id to plugins.allow\n --openclaw-dir=<path> Override OpenClaw config dir (default: $OPENCLAW_HOME, ./openclaw.json,\n or ~/.openclaw — see resolution order in README)\n --dry-run Show the diff against current openclaw.json and exit (no writes)\n --restart Always restart the gateway, even non-TTY\n --no-restart Never restart the gateway, even on TTY\n --yes / --no-prompt Skip all prompts (required for non-TTY / CI)\n\nuninstall options:\n --openclaw-dir=<path> Override OpenClaw config dir (same precedence as install)\n --restart Always restart the gateway, even non-TTY\n --no-restart Never restart the gateway, even on TTY\n --yes / --no-prompt Skip the confirmation prompt\n`\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2)\n // Top-level --version / --help are handled before parseFlags so users can\n // type them as the first arg without hitting the \"unknown subcommand\" path.\n if (argv[0] === \"--version\" || argv[0] === \"-v\") {\n process.stdout.write(`${readCliVersion()}\\n`)\n return\n }\n if (argv[0] === \"--help\" || argv[0] === \"-h\") {\n process.stdout.write(USAGE)\n return\n }\n\n const { subcommand, flags } = parseFlags(argv)\n if (subcommand === \"install\" || subcommand === undefined) {\n await runInstall(normalizeInstallFlags(flags))\n return\n }\n if (subcommand === \"uninstall\") {\n await runUninstall(normalizeUninstallFlags(flags))\n return\n }\n process.stderr.write(`unknown subcommand: ${subcommand}\\n`)\n process.stderr.write(USAGE)\n process.exit(1)\n}\n\nmain().catch((err) => {\n process.stderr.write(`${String(err)}\\n`)\n process.exit(1)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":["resolvePath"],"sources":["../src/config-dir.ts","../src/diff.ts","../src/openclaw-cli.ts","../src/settings-file.ts","../src/version.ts","../src/setup.ts","../src/cli.ts"],"sourcesContent":["import { existsSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { isAbsolute, join, resolve as resolvePath } from \"node:path\"\n\n/**\n * Resolution order for the OpenClaw config directory (the parent of\n * `openclaw.json`):\n *\n * 1. `--openclaw-dir <path>` flag (most explicit; absolute or cwd-relative).\n * 2. `OPENCLAW_HOME` env var (next-most explicit; absolute or cwd-relative).\n * 3. `./openclaw.json` exists in the current working directory → use cwd\n * (matches `cd into a project that has its own openclaw.json` UX).\n * 4. `~/.openclaw` (default; matches OpenClaw's hardcoded default).\n *\n * The CLI passes `OPENCLAW_HOME=<resolved>` to every spawned `openclaw`\n * subprocess so the runtime side resolves to the same dir we're writing to.\n *\n * Caveat — at the time of writing, OpenClaw's CLI does not advertise\n * `OPENCLAW_HOME` support; the runtime side may still resolve `~/.openclaw`\n * regardless of what we set. The post-install `openclaw config validate\n * --json` step (in `setup.ts`) catches this and restores the backup if the\n * runtime fell back to the wrong dir. Don't trust the resolved path alone —\n * trust the validation result.\n */\nexport interface ResolvedConfigDir {\n /** Absolute path to the chosen config directory. */\n readonly dir: string\n /** Where the value came from, for logging / debug. */\n readonly source: \"flag\" | \"env\" | \"cwd\" | \"default\"\n}\n\ninterface ResolveOptions {\n /** Value of the `--openclaw-dir` flag, if set. */\n readonly flag?: string | undefined\n /** Override `OPENCLAW_HOME` env var (defaults to `process.env.OPENCLAW_HOME`). */\n readonly env?: string | undefined\n /** Override cwd (for tests). Defaults to `process.cwd()`. */\n readonly cwd?: string | undefined\n /** Override home dir (for tests). Defaults to `os.homedir()`. */\n readonly home?: string | undefined\n}\n\nexport function resolveConfigDir(opts: ResolveOptions = {}): ResolvedConfigDir {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n\n if (typeof opts.flag === \"string\" && opts.flag.length > 0) {\n return { dir: absolutize(opts.flag, cwd), source: \"flag\" }\n }\n const envValue = opts.env !== undefined ? opts.env : process.env.OPENCLAW_HOME\n if (typeof envValue === \"string\" && envValue.length > 0) {\n return { dir: absolutize(envValue, cwd), source: \"env\" }\n }\n if (existsSync(join(cwd, \"openclaw.json\"))) {\n return { dir: cwd, source: \"cwd\" }\n }\n return { dir: join(home, \".openclaw\"), source: \"default\" }\n}\n\nfunction absolutize(p: string, cwd: string): string {\n return isAbsolute(p) ? p : resolvePath(cwd, p)\n}\n\n/** Settings paths derived from a config dir. */\nexport interface SettingsPaths {\n readonly configDir: string\n readonly settingsPath: string\n readonly settingsBackupPath: string\n /** OpenClaw's plugins install record — read-only, used for upgrade-detection. */\n readonly installsPath: string\n}\n\nexport function pathsFor(configDir: string): SettingsPaths {\n return {\n configDir,\n settingsPath: join(configDir, \"openclaw.json\"),\n settingsBackupPath: join(configDir, \"openclaw.json.latitude-bak\"),\n installsPath: join(configDir, \"plugins\", \"installs.json\"),\n }\n}\n","/**\n * Tiny purpose-built JSON line-diff for `--dry-run` output. Renders changes\n * to `openclaw.json` in a human-readable unified-diff-ish format without\n * pulling in a real diff library (zero new deps).\n *\n * The shape of `openclaw.json` is small (a few hundred lines tops, with\n * stable key ordering after our edits), so the naive line-by-line approach\n * produces a perfectly readable output. We mark added lines with `+ ` and\n * removed lines with `- `, with a few lines of context around each change.\n */\nconst CONTEXT_LINES = 3\n\ninterface DiffOptions {\n /** Header label for the \"before\" side (default: `current`). */\n fromLabel?: string\n /** Header label for the \"after\" side (default: `proposed`). */\n toLabel?: string\n}\n\n/**\n * Render a unified-ish diff between two unknown JSON values. Returns an\n * empty string if they're identical after normalization.\n */\nexport function jsonDiff(before: unknown, after: unknown, opts: DiffOptions = {}): string {\n const fromLabel = opts.fromLabel ?? \"current\"\n const toLabel = opts.toLabel ?? \"proposed\"\n\n const beforeJson = `${JSON.stringify(before ?? {}, null, 2)}\\n`\n const afterJson = `${JSON.stringify(after ?? {}, null, 2)}\\n`\n\n if (beforeJson === afterJson) return \"\"\n\n const beforeLines = beforeJson.split(\"\\n\")\n const afterLines = afterJson.split(\"\\n\")\n\n // Compute LCS-driven minimal edit script. For files of this size the\n // O(n*m) DP is fine — `openclaw.json` rarely exceeds a few hundred lines.\n const ops = lcsDiff(beforeLines, afterLines)\n\n const out: string[] = []\n out.push(`--- ${fromLabel}`)\n out.push(`+++ ${toLabel}`)\n\n // Render hunks (groups of contiguous changes + their context).\n let i = 0\n while (i < ops.length) {\n if (ops[i]?.kind === \"eq\") {\n i++\n continue\n }\n // Found a change — collect the surrounding hunk.\n const hunkStart = Math.max(0, i - CONTEXT_LINES)\n let hunkEnd = i\n while (hunkEnd < ops.length) {\n if (ops[hunkEnd]?.kind === \"eq\") {\n // Stop the hunk after CONTEXT_LINES of equality, unless the very\n // next non-equal op is within CONTEXT_LINES * 2 (then we collapse\n // adjacent change blocks into one hunk).\n let runOfEq = 0\n let k = hunkEnd\n while (k < ops.length && ops[k]?.kind === \"eq\" && runOfEq < CONTEXT_LINES * 2) {\n runOfEq++\n k++\n }\n if (k >= ops.length || ops[k]?.kind === \"eq\") {\n hunkEnd = Math.min(hunkEnd + CONTEXT_LINES, ops.length)\n break\n }\n hunkEnd = k\n continue\n }\n hunkEnd++\n }\n\n for (let j = hunkStart; j < hunkEnd && j < ops.length; j++) {\n const op = ops[j]\n if (!op) continue\n if (op.kind === \"eq\") out.push(` ${op.line}`)\n else if (op.kind === \"add\") out.push(`+ ${op.line}`)\n else if (op.kind === \"del\") out.push(`- ${op.line}`)\n }\n i = hunkEnd\n }\n\n return out.join(\"\\n\")\n}\n\ntype Op = { kind: \"eq\"; line: string } | { kind: \"add\"; line: string } | { kind: \"del\"; line: string }\n\nfunction lcsDiff(a: readonly string[], b: readonly string[]): Op[] {\n const n = a.length\n const m = b.length\n // dp[i][j] = LCS length of a[0..i) and b[0..j)\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0))\n for (let i = 1; i <= n; i++) {\n for (let j = 1; j <= m; j++) {\n if (a[i - 1] === b[j - 1]) {\n dp[i]![j] = (dp[i - 1]?.[j - 1] ?? 0) + 1\n } else {\n dp[i]![j] = Math.max(dp[i - 1]?.[j] ?? 0, dp[i]?.[j - 1] ?? 0)\n }\n }\n }\n // Walk back to produce the edit script.\n const ops: Op[] = []\n let i = n\n let j = m\n while (i > 0 && j > 0) {\n if (a[i - 1] === b[j - 1]) {\n ops.push({ kind: \"eq\", line: a[i - 1] as string })\n i--\n j--\n } else if ((dp[i - 1]?.[j] ?? 0) >= (dp[i]?.[j - 1] ?? 0)) {\n ops.push({ kind: \"del\", line: a[i - 1] as string })\n i--\n } else {\n ops.push({ kind: \"add\", line: b[j - 1] as string })\n j--\n }\n }\n while (i > 0) {\n ops.push({ kind: \"del\", line: a[i - 1] as string })\n i--\n }\n while (j > 0) {\n ops.push({ kind: \"add\", line: b[j - 1] as string })\n j--\n }\n ops.reverse()\n return ops\n}\n","import { type SpawnSyncReturns, spawnSync } from \"node:child_process\"\n\n/**\n * Lowest OpenClaw version we support. The reporter verified hook-dispatch\n * gating works correctly here; older versions either reject\n * `hooks.allowConversationAccess` outright (≤ 2026.4.21) or have unverified\n * gating behaviour (2026.4.22 – 2026.4.24). Refusing to install on older\n * versions is intentional — we'd rather fail loudly than ship a\n * config the gateway will quarantine or hooks the dispatcher will block.\n */\nexport const MIN_OPENCLAW_VERSION = \"2026.8.1\"\n\nconst DEFAULT_TIMEOUT_MS = 10_000\n\ntype RunResult =\n | { ok: true; stdout: string; stderr: string; code: 0 }\n | { ok: false; reason: \"enoent\"; stdout: \"\"; stderr: \"\"; code: null }\n | { ok: false; reason: \"timeout\"; stdout: string; stderr: string; code: null }\n | { ok: false; reason: \"exit\"; stdout: string; stderr: string; code: number }\n\n/**\n * Spawn `openclaw <args>` synchronously. Reports failure modes structurally so\n * callers can decide how to degrade (missing binary vs. timed out vs. exited\n * non-zero). Never throws; ENOENT becomes `{ reason: \"enoent\" }`.\n *\n * `opts.env` lets callers overlay environment variables on top of `process.env`\n * — used by the install flow to inject `OPENCLAW_HOME` so `openclaw plugins\n * install`, `openclaw config validate`, and `openclaw gateway restart` all\n * resolve the same config dir as our settings-file edits. The overlay is\n * additive: the caller passes only the keys to override and we merge them on\n * top of `process.env`.\n */\nexport function runOpenclaw(\n args: string[],\n opts: { timeoutMs?: number; stdin?: string; env?: NodeJS.ProcessEnv } = {},\n): RunResult {\n const env = opts.env ? { ...process.env, ...opts.env } : undefined\n const result: SpawnSyncReturns<string> = spawnSync(\"openclaw\", args, {\n encoding: \"utf-8\",\n timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n input: opts.stdin,\n // Inherit env so the user's PATH and any LATITUDE_* / OPENCLAW_* vars\n // reach the child process. When opts.env is set, it's merged on top\n // (e.g. OPENCLAW_HOME from --openclaw-dir).\n ...(env ? { env } : {}),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n })\n\n // spawnSync surfaces I/O errors through `result.error` rather than a thrown\n // exception when called this way. Order matters: a timeout shows up as\n // `error.code === \"ETIMEDOUT\"` AND/OR `signal === \"SIGTERM\"|\"SIGKILL\"`\n // depending on platform — classify it before the generic `if (err)`\n // catch-all so callers reliably get `reason: \"timeout\"` (and the\n // timeout-specific error messages in setup.ts aren't dead code).\n const err = result.error as (NodeJS.ErrnoException & { code?: string }) | undefined\n if (err?.code === \"ENOENT\") {\n return { ok: false, reason: \"enoent\", stdout: \"\", stderr: \"\", code: null }\n }\n if (err?.code === \"ETIMEDOUT\" || result.signal === \"SIGTERM\" || result.signal === \"SIGKILL\") {\n return {\n ok: false,\n reason: \"timeout\",\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n code: null,\n }\n }\n if (err) {\n // Treat any other spawn error as an unparseable exit failure.\n return {\n ok: false,\n reason: \"exit\",\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? String(err),\n code: typeof result.status === \"number\" ? result.status : 1,\n }\n }\n\n if (result.status === 0) {\n return { ok: true, stdout: result.stdout ?? \"\", stderr: result.stderr ?? \"\", code: 0 }\n }\n\n return {\n ok: false,\n reason: \"exit\",\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n code: typeof result.status === \"number\" ? result.status : 1,\n }\n}\n\ntype VersionLookup =\n | { ok: true; version: string; raw: string }\n | { ok: false; error: \"missing\" | \"unparseable\"; raw?: string | undefined }\n\n/**\n * Run `openclaw --version` and parse out the version string.\n *\n * Banner format (per OpenClaw `src/cli/banner.ts` `formatCliBannerLine`):\n * `🦞 OpenClaw <version> (<commit-sha>)`\n *\n * The banner is normally suppressed when `--version` is the flag, but the\n * version itself still goes to stdout. We accept either layout (with or\n * without the lobster + commit sha) so we don't break if OpenClaw later\n * prints just the bare version string.\n */\nexport function getOpenclawVersion(): VersionLookup {\n const result = runOpenclaw([\"--version\"], { timeoutMs: 5_000 })\n if (!result.ok) {\n if (result.reason === \"enoent\") return { ok: false, error: \"missing\" }\n return { ok: false, error: \"unparseable\", raw: result.stdout || result.stderr }\n }\n\n const raw = result.stdout.trim()\n // Match a CalVer triple anywhere in the output. Be liberal — banner\n // decorations (emoji, \"OpenClaw\" prefix, trailing \"(sha)\") all just\n // surround a single `YYYY.M.PATCH` somewhere.\n const match = raw.match(/(\\d{4}\\.\\d+\\.\\d+)/)\n if (!match) return { ok: false, error: \"unparseable\", raw }\n return { ok: true, version: match[1] as string, raw }\n}\n\n/**\n * Compare two CalVer strings (`YYYY.M.PATCH`). Returns -1 if `a` < `b`,\n * 0 if equal, 1 if `a` > `b`. Strings with extra components or non-numeric\n * pieces fall back to a per-component string comparison so unexpected\n * formats don't crash the installer.\n */\nexport function compareCalver(a: string, b: string): -1 | 0 | 1 {\n const ap = a.split(\".\")\n const bp = b.split(\".\")\n const len = Math.max(ap.length, bp.length)\n for (let i = 0; i < len; i++) {\n const ai = ap[i] ?? \"0\"\n const bi = bp[i] ?? \"0\"\n const an = Number(ai)\n const bn = Number(bi)\n if (Number.isFinite(an) && Number.isFinite(bn)) {\n if (an < bn) return -1\n if (an > bn) return 1\n continue\n }\n if (ai < bi) return -1\n if (ai > bi) return 1\n }\n return 0\n}\n","import { copyFileSync, existsSync, readFileSync, renameSync, writeFileSync } from \"node:fs\"\n\n/** Plugin id used both as the npm package name and as the OpenClaw plugin id. */\nexport const PLUGIN_ID = \"@latitude-data/openclaw-telemetry\"\n\n/**\n * The shape OpenClaw's strict zod schema accepts for a single\n * `plugins.entries[id]` block. We keep this minimal — only the fields we\n * actually write — so we don't drop anything when round-tripping an entry\n * with fields we don't know about.\n */\ninterface OpenClawPluginEntry {\n enabled?: boolean\n /**\n * Strict zod object on OpenClaw 2026.4.25+: only `allowPromptInjection` and\n * `allowConversationAccess` are accepted. We populate the latter — it's the\n * dispatch gate the OpenClaw runtime checks before forwarding LLM/tool/agent\n * events to non-bundled plugins. Without it, the gateway logs\n * `[plugins] typed hook \"...\" blocked because non-bundled plugins must set\n * plugins.entries.<id>.hooks.allowConversationAccess=true` and the plugin's\n * handlers never fire.\n */\n hooks?: {\n allowPromptInjection?: boolean\n allowConversationAccess?: boolean\n [key: string]: unknown\n }\n /**\n * Free-form bucket OpenClaw passes to the plugin at activation\n * (`api.pluginConfig`). Our credentials and feature flags live here.\n */\n config?: Record<string, unknown>\n // Pass through anything else that may already be there — `subagent`, etc.\n [key: string]: unknown\n}\n\nexport interface OpenClawSettings {\n plugins?: {\n enabled?: boolean\n entries?: Record<string, OpenClawPluginEntry>\n /**\n * Operator-managed allowlist of plugin ids that may auto-load. OpenClaw\n * warns at every gateway start when a non-bundled plugin loads without\n * provenance via `plugins.allow` or an install record.\n */\n allow?: string[]\n load?: { paths?: string[] }\n [key: string]: unknown\n }\n [key: string]: unknown\n}\n\nexport interface LatitudePluginConfig {\n apiKey: string\n project: string\n baseUrl?: string | undefined\n allowConversationAccess?: boolean | undefined\n debug?: boolean | undefined\n}\n\nexport function readSettings(settingsPath: string): OpenClawSettings {\n if (!existsSync(settingsPath)) return {}\n try {\n const raw = readFileSync(settingsPath, \"utf-8\")\n const parsed = JSON.parse(raw) as OpenClawSettings\n return parsed && typeof parsed === \"object\" ? parsed : {}\n } catch {\n return {}\n }\n}\n\n/**\n * Write `openclaw.json` atomically: serialize to a sibling tempfile, then\n * rename over the target. `rename` is atomic on POSIX and effectively atomic\n * on Windows for our case — a crash mid-serialization can no longer leave a\n * truncated `openclaw.json` (combined with `.latitude-bak`, recovery is\n * always possible).\n *\n * Earlier versions used `writeFileSync` directly; that left a corruption\n * window where SIGTERM between `open()` and the final `write()` would leave\n * a partial JSON file the gateway couldn't parse.\n */\nexport function writeSettings(settingsPath: string, settings: OpenClawSettings): void {\n const tmp = `${settingsPath}.tmp.${process.pid}`\n writeFileSync(tmp, `${JSON.stringify(settings, null, 2)}\\n`, \"utf-8\")\n renameSync(tmp, settingsPath)\n}\n\nexport function backupSettings(settingsPath: string, backupPath: string): void {\n if (existsSync(settingsPath)) copyFileSync(settingsPath, backupPath)\n}\n\n/**\n * Restore the backup over the live settings file. Used when a post-write\n * step (e.g. `openclaw config validate --json`) reports the new file is\n * invalid — better to roll back to a known-good state than leave a broken\n * config that'll fail at gateway-restart time.\n */\nexport function restoreBackup(settingsPath: string, backupPath: string): boolean {\n if (!existsSync(backupPath)) return false\n copyFileSync(backupPath, settingsPath)\n return true\n}\n\ninterface SetPluginEntryPatch {\n /** New API key. Always overwrites — comes from the install prompt. */\n apiKey: string\n /** New project slug. Always overwrites — comes from the install prompt. */\n project: string\n /**\n * New `baseUrl`. `undefined` clears any existing override (used when\n * installing back to production). Anything else overwrites.\n */\n baseUrl: string | undefined\n /**\n * `true`/`false` overwrites both `config.allowConversationAccess` AND\n * `hooks.allowConversationAccess` (always coupled — see `setPluginEntry`).\n * `undefined` preserves the existing value, or defaults to `true` for a\n * fresh install.\n */\n allowConversationAccess?: boolean | undefined\n /**\n * `true`/`false` overwrites; `undefined` preserves the existing value (the\n * installer doesn't pass `debug` — it's a hand-edit affordance).\n */\n debug?: boolean | undefined\n /**\n * `true`/`false` overwrites; `undefined` preserves existing, defaulting to\n * `true` for a fresh install. This keeps a paused plugin (`enabled: false`\n * hand-edited in openclaw.json) paused across re-installs.\n */\n enabled?: boolean | undefined\n}\n\n/**\n * Set the `plugins.entries[id]` block for our plugin.\n *\n * Two places in the entry get written:\n *\n * - `.config` (free-form `record(string, unknown)`): credentials, baseUrl,\n * and our copy of `allowConversationAccess`. This is what the plugin\n * runtime reads via `api.pluginConfig`.\n * - `.hooks.allowConversationAccess`: controls whether OpenClaw's hook\n * dispatcher actually forwards `llm_input` / `llm_output` / tool /\n * `agent_end` events to our handlers. Without it set to `true` on\n * OpenClaw 2026.4.25+, every typed hook is blocked at the dispatcher\n * and the plugin's handlers never fire.\n *\n * The two flags mean different things — `hooks.*` is the dispatch gate,\n * `config.*` is the payload-content gate — but for THIS plugin we always\n * couple them: dispatch off + payload on is useless (no payloads to gate),\n * and dispatch on + payload off is a legitimate \"structural-only telemetry\"\n * mode (timing, tokens, ids, agent name; no message bodies). Always writing\n * both from the same source keeps the operator's mental model simple.\n *\n * Re-install idempotency: only `apiKey` / `project` / `baseUrl` always\n * overwrite (these come from install prompts). `enabled`, `debug`, and\n * `allowConversationAccess` are preserved when not provided in the patch.\n */\nexport function setPluginEntry(settings: OpenClawSettings, patch: SetPluginEntryPatch): void {\n const plugins = settings.plugins ?? {}\n const entries = plugins.entries ?? {}\n const existing = entries[PLUGIN_ID] ?? {}\n const existingConfig = (existing.config ?? {}) as Record<string, unknown>\n const existingHooks = existing.hooks ?? {}\n\n const nextConfig: Record<string, unknown> = {\n ...existingConfig,\n apiKey: patch.apiKey,\n project: patch.project,\n }\n if (patch.baseUrl !== undefined) {\n nextConfig.baseUrl = patch.baseUrl\n } else {\n delete nextConfig.baseUrl\n }\n if (patch.allowConversationAccess !== undefined) {\n nextConfig.allowConversationAccess = patch.allowConversationAccess\n }\n if (patch.debug !== undefined) {\n nextConfig.debug = patch.debug\n }\n\n // Resolve the effective allowConversationAccess for the hooks block.\n // We mirror whatever ended up in nextConfig.allowConversationAccess (just\n // computed above) so the two flags always agree. If neither the patch nor\n // the existing config sets it, fall back to the existing hooks value, then\n // to `true` (matches the README's first-install promise).\n const effectiveAccess =\n typeof nextConfig.allowConversationAccess === \"boolean\"\n ? nextConfig.allowConversationAccess\n : typeof existingHooks.allowConversationAccess === \"boolean\"\n ? existingHooks.allowConversationAccess\n : true\n\n const nextHooks: OpenClawPluginEntry[\"hooks\"] = {\n ...existingHooks,\n allowConversationAccess: effectiveAccess,\n }\n\n // Preserve a hand-edited `enabled: false` across re-installs. Fresh install\n // (no existing entry, no explicit patch) defaults to true.\n const nextEnabled = patch.enabled ?? existing.enabled ?? true\n\n entries[PLUGIN_ID] = {\n ...existing,\n enabled: nextEnabled,\n hooks: nextHooks,\n config: nextConfig,\n }\n plugins.entries = entries\n settings.plugins = plugins\n}\n\n/** Remove the plugin entry entirely. Used by uninstall as defense-in-depth. */\nexport function removePluginEntry(settings: OpenClawSettings): boolean {\n const plugins = settings.plugins\n if (!plugins?.entries) return false\n if (!(PLUGIN_ID in plugins.entries)) return false\n delete plugins.entries[PLUGIN_ID]\n return true\n}\n\n/**\n * Add the plugin id to `plugins.allow`. Idempotent — returns `true` only when\n * the array changed. OpenClaw warns at every gateway start when a non-bundled\n * plugin auto-loads without provenance via `plugins.allow` or an install\n * record. We get one warning cleared by going through `openclaw plugins\n * install` (provenance) and the other by adding ourselves to allow.\n *\n * Defensive against hand-edited non-array values: if `plugins.allow` is\n * present but not an array (e.g. someone wrote a string), we replace it\n * with a single-element array rather than spreading the bad value.\n */\nexport function addToPluginsAllow(settings: OpenClawSettings): boolean {\n const plugins = settings.plugins ?? {}\n const existing = plugins.allow\n const allow = Array.isArray(existing) ? existing : []\n if (allow.includes(PLUGIN_ID)) return false\n plugins.allow = [...allow, PLUGIN_ID]\n settings.plugins = plugins\n return true\n}\n\n/**\n * Inverse of `addToPluginsAllow`. Defense-in-depth — `openclaw plugins\n * uninstall` already strips the entry, but the install path can be skipped\n * (e.g. the user removed the plugin manually) and we want re-install/uninstall\n * round-trips to be tidy regardless.\n */\nexport function removeFromPluginsAllow(settings: OpenClawSettings): boolean {\n const allow = settings.plugins?.allow\n if (!Array.isArray(allow) || !allow.includes(PLUGIN_ID)) return false\n if (settings.plugins) {\n settings.plugins.allow = allow.filter((id) => id !== PLUGIN_ID)\n }\n return true\n}\n\nexport function hasLatitudePlugin(settings: OpenClawSettings): boolean {\n return Boolean(settings.plugins?.entries && PLUGIN_ID in settings.plugins.entries)\n}\n\n/**\n * Strip leftover keys from older installers that the strict zod schema\n * rejects on current OpenClaw versions.\n *\n * 0.0.1 wrote `LATITUDE_*` keys directly under `settings.env`. OpenClaw's\n * root schema is strict; the `env` block accepts only `{shellEnv, vars}`,\n * so those keys cause the gateway to quarantine the config as\n * `clobbered.<ts>` and roll back. We sweep them on every install.\n *\n * Note: 0.0.1 also wrote `hooks.allowConversationAccess` (when that key was\n * not yet in the schema). We deliberately do NOT strip it anymore — on\n * OpenClaw 2026.4.25+ the key IS in the schema and IS load-bearing for\n * dispatch. `setPluginEntry` overwrites it on every install with the right\n * value, so any 0.0.1 leftover is reconciled there.\n */\nexport function migrateLegacyEntries(settings: OpenClawSettings): { changed: boolean } {\n let changed = false\n\n const env = settings.env\n if (env && typeof env === \"object\" && !Array.isArray(env)) {\n const envObj = env as Record<string, unknown>\n for (const key of [\"LATITUDE_API_KEY\", \"LATITUDE_PROJECT\", \"LATITUDE_BASE_URL\"]) {\n if (key in envObj) {\n delete envObj[key]\n changed = true\n }\n }\n // Drop the env object only if it's now empty; don't touch a real\n // OpenClaw env block (`{shellEnv, vars}`) that already had those keys.\n if (Object.keys(envObj).length === 0) {\n delete settings.env\n }\n }\n\n return { changed }\n}\n\n/**\n * Read OpenClaw's plugins install record (`<configDir>/plugins/installs.json`)\n * and return the version recorded for our plugin id, if any. Used by the\n * install flow to render `Upgrading 0.0.6 → 0.0.7` UX.\n *\n * Best-effort: returns undefined on any read/parse error, on missing file,\n * on schemas we don't recognize. Callers must handle the undefined case\n * (typically by rendering a \"fresh install\" path).\n */\nexport function readInstalledRuntimeVersion(installsPath: string): string | undefined {\n if (!existsSync(installsPath)) return undefined\n try {\n const raw = readFileSync(installsPath, \"utf-8\")\n const parsed = JSON.parse(raw) as unknown\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined\n const entries = (parsed as { entries?: Record<string, unknown> }).entries\n if (!entries || typeof entries !== \"object\") return undefined\n const ours = entries[PLUGIN_ID]\n if (!ours || typeof ours !== \"object\") return undefined\n const version = (ours as { version?: unknown }).version\n return typeof version === \"string\" ? version : undefined\n } catch {\n return undefined\n }\n}\n","import { readFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\n/**\n * Read this CLI's own version from its `package.json`. Used by:\n * - `cli.ts` for `--version`\n * - `setup.ts` in the npm-registry-404 abort message, so the upgrade\n * instruction shows the version the user is actually running. Earlier\n * versions read `process.env.npm_package_version`, but that env var\n * is only set by `npm run` — not by `npx`, not by globally-installed\n * bins. The vast majority of `latitude-openclaw` invocations come\n * through one of those, so the env-var approach printed\n * `(this version)` instead of the real version every time.\n */\nexport function readCliVersion(): string {\n // dist/cli.js → ../package.json (after bundling); src/version.ts →\n // ../package.json under vitest's source-mode runner. Both shapes have\n // the package.json one directory up from the entry file.\n const here = dirname(fileURLToPath(import.meta.url))\n const pkgPath = join(here, \"..\", \"package.json\")\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as { version?: string }\n return pkg.version ?? \"unknown\"\n } catch {\n return \"unknown\"\n }\n}\n","import { existsSync, mkdirSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\nimport { cancel, confirm, intro, isCancel, log, note, outro, password, spinner, text } from \"@clack/prompts\"\nimport pc from \"picocolors\"\nimport { pathsFor, type ResolvedConfigDir, resolveConfigDir, type SettingsPaths } from \"./config-dir.ts\"\nimport { jsonDiff } from \"./diff.ts\"\nimport { compareCalver, getOpenclawVersion, MIN_OPENCLAW_VERSION, runOpenclaw } from \"./openclaw-cli.ts\"\nimport {\n addToPluginsAllow,\n backupSettings,\n hasLatitudePlugin,\n type LatitudePluginConfig,\n migrateLegacyEntries,\n type OpenClawSettings,\n PLUGIN_ID,\n readInstalledRuntimeVersion,\n readSettings,\n removeFromPluginsAllow,\n removePluginEntry,\n restoreBackup,\n setPluginEntry,\n writeSettings,\n} from \"./settings-file.ts\"\nimport { readCliVersion } from \"./version.ts\"\n\n/**\n * The runtime version this CLI installs. Lockstep with the runtime — bump\n * both packages in the same commit. The CLI's npm-spec install is pinned to\n * this exact version (not a floating tag), so we never accidentally install\n * a runtime that doesn't match the contract this CLI was built against.\n *\n * Pinning also satisfies OpenClaw's `Pin install specs to exact versions`\n * supply-chain audit warning automatically.\n */\nconst RUNTIME_PACKAGE_NAME = \"@latitude-data/openclaw-telemetry\"\nconst RUNTIME_VERSION = \"0.1.0\"\n\nconst DOCS_URL = \"https://docs.latitude.so/telemetry/openclaw\"\n\ninterface EnvironmentConfig {\n name: \"production\" | \"staging\" | \"dev\"\n label: string\n app: string\n ingest: string\n}\n\nconst PRODUCTION_ENV: EnvironmentConfig = {\n name: \"production\",\n label: \"production\",\n app: \"https://console.latitude.so\",\n ingest: \"https://ingest.latitude.so\",\n}\nconst STAGING_ENV: EnvironmentConfig = {\n name: \"staging\",\n label: \"staging\",\n app: \"https://staging.latitude.so\",\n ingest: \"https://staging-ingest.latitude.so\",\n}\nconst DEV_ENV: EnvironmentConfig = {\n name: \"dev\",\n label: \"local dev\",\n app: \"http://localhost:3000\",\n ingest: \"http://localhost:3002\",\n}\n\nfunction urlsFor(env: EnvironmentConfig): {\n apiKeys: (slug: string) => string\n projects: string\n projectView: (slug: string) => string\n} {\n return {\n apiKeys: (slug: string) => `${env.app}/projects/${slug}/settings/keys`,\n projects: env.app,\n projectView: (slug: string) => `${env.app}/projects/${slug}`,\n }\n}\n\ninterface InstallFlags {\n apiKey?: string | undefined\n project?: string | undefined\n environment?: EnvironmentConfig | undefined\n /**\n * Tristate: `true` = capture (`--allow-conversation`), `false` = scrub\n * (`--no-content`), `undefined` = preserve existing or first-install\n * default. Tristate keeps re-install idempotent for hand-edited values.\n */\n allowConversationAccess?: boolean | undefined\n /** When true, skip adding the plugin id to `plugins.allow`. */\n noTrust?: boolean\n /**\n * Install spec handed to `openclaw plugins install` instead of the pinned\n * npm version, e.g. `npm-pack:/tmp/plugin.tgz` for a pre-release build.\n * Skips the npm registry lockstep check.\n */\n runtimeSpec?: string | undefined\n /** Override for the OpenClaw config directory. */\n openclawDir?: string | undefined\n /** When true, render the diff and the install spec without making any changes. */\n dryRun?: boolean\n /**\n * Restart-the-gateway behaviour:\n * - `\"force\"` (--restart): always restart, even non-TTY.\n * - `\"never\"` (--no-restart): never restart, even on TTY.\n * - `\"auto\"` (default): prompt on TTY, skip otherwise.\n */\n restart?: \"force\" | \"never\" | \"auto\"\n noPrompt?: boolean\n yes?: boolean\n}\n\n/**\n * Flags that take a value (either `--key=value` or `--key value`). Bare\n * `--key` is reserved for booleans and shouldn't consume the next argv\n * token as its value — operators expect `--no-content` next to `--yes`\n * to mean two booleans, not \"no-content takes the value `--yes`\".\n */\nconst VALUE_FLAGS = new Set([\"api-key\", \"project\", \"openclaw-dir\", \"runtime-spec\"])\n\nexport function parseFlags(argv: string[]): {\n subcommand: string | undefined\n flags: Record<string, string | boolean>\n} {\n const [subcommand, ...rest] = argv\n const flags: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n if (!arg || !arg.startsWith(\"--\")) continue\n const eq = arg.indexOf(\"=\")\n if (eq >= 0) {\n flags[arg.slice(2, eq)] = arg.slice(eq + 1)\n continue\n }\n const key = arg.slice(2)\n const next = rest[i + 1]\n // Space-separated form (`--key value`): only consume the next token\n // when the key is in VALUE_FLAGS and the next token isn't itself a\n // flag. This matches the standard convention everyone expects from\n // CLIs, while keeping bare boolean flags from accidentally swallowing\n // adjacent flags as values.\n if (VALUE_FLAGS.has(key) && next !== undefined && !next.startsWith(\"--\")) {\n flags[key] = next\n i += 1\n } else {\n flags[key] = true\n }\n }\n return { subcommand, flags }\n}\n\nexport function normalizeInstallFlags(flags: Record<string, string | boolean>): InstallFlags {\n let environment: EnvironmentConfig | undefined\n if (flags.staging === true) environment = STAGING_ENV\n if (flags.dev === true) {\n if (environment) throw new Error(\"--staging and --dev are mutually exclusive\")\n environment = DEV_ENV\n }\n // Tristate: leave undefined unless the user explicitly asked one way or the\n // other. Re-install then preserves whatever's already in openclaw.json.\n let allowConversationAccess: boolean | undefined\n if (flags[\"no-content\"] === true || flags[\"no-conversation\"] === true) allowConversationAccess = false\n if (flags[\"allow-conversation\"] === true) allowConversationAccess = true\n\n let restart: \"force\" | \"never\" | \"auto\" = \"auto\"\n if (flags[\"no-restart\"] === true) restart = \"never\"\n if (flags.restart === true) {\n if (flags[\"no-restart\"] === true) throw new Error(\"--restart and --no-restart are mutually exclusive\")\n restart = \"force\"\n }\n\n return {\n apiKey: typeof flags[\"api-key\"] === \"string\" ? flags[\"api-key\"] : undefined,\n project: typeof flags.project === \"string\" ? flags.project : undefined,\n environment,\n allowConversationAccess,\n noTrust: flags[\"no-trust\"] === true,\n runtimeSpec: typeof flags[\"runtime-spec\"] === \"string\" ? flags[\"runtime-spec\"] : undefined,\n openclawDir: typeof flags[\"openclaw-dir\"] === \"string\" ? flags[\"openclaw-dir\"] : undefined,\n dryRun: flags[\"dry-run\"] === true,\n restart,\n noPrompt: flags[\"no-prompt\"] === true || flags.yes === true,\n yes: flags.yes === true,\n }\n}\n\n// ─── Install ────────────────────────────────────────────────────────────────\n\nexport async function runInstall(flags: InstallFlags = {}): Promise<void> {\n const canPrompt = !flags.noPrompt && process.stdin.isTTY === true\n if (!canPrompt) return runFlagDrivenInstall(flags)\n await runInteractiveInstall(flags)\n}\n\nfunction resolvePaths(flags: InstallFlags): { resolved: ResolvedConfigDir; paths: SettingsPaths } {\n const resolved = resolveConfigDir({ flag: flags.openclawDir })\n return { resolved, paths: pathsFor(resolved.dir) }\n}\n\nfunction envForSubprocess(paths: SettingsPaths): NodeJS.ProcessEnv {\n // Pass OPENCLAW_HOME to the subprocess so its own resolution lines up\n // with ours. Caveat documented in config-dir.ts — at the time of writing\n // OpenClaw's CLI doesn't advertise OPENCLAW_HOME support; the post-write\n // `openclaw config validate --json` step catches a config-dir mismatch.\n return { OPENCLAW_HOME: paths.configDir }\n}\n\nasync function runInteractiveInstall(flags: InstallFlags): Promise<void> {\n intro(pc.bgCyan(pc.black(\" Latitude · OpenClaw telemetry \")))\n\n // Bail before any prompts if the host OpenClaw is too old. We'd rather\n // tell the user to upgrade than walk them through a config we can't make\n // work on their version.\n ensureOpenclawIsCompatible()\n\n const { resolved, paths } = resolvePaths(flags)\n log.info(`Using config dir: ${pc.dim(paths.configDir)} ${pc.dim(`(source: ${resolved.source})`)}`)\n\n // Lockstep contract check. If the runtime version we're built against\n // isn't on npm yet (half-published release), abort with a clear upgrade\n // path before we waste any of the operator's time on prompts.\n if (!flags.runtimeSpec) await ensureRuntimeOnNpm()\n\n // Render the upgrade UX before any spawn. Reads OpenClaw's own install\n // record so the version is what's *actually* running, not what was last\n // hand-edited into openclaw.json.\n const installedVersion = readInstalledRuntimeVersion(paths.installsPath)\n if (installedVersion !== undefined) {\n if (installedVersion === RUNTIME_VERSION) {\n log.info(`${PLUGIN_ID} ${pc.dim(installedVersion)} already installed — re-applying (idempotent).`)\n } else {\n log.info(`Upgrading ${PLUGIN_ID} from ${pc.dim(installedVersion)} → ${pc.cyan(RUNTIME_VERSION)}`)\n }\n }\n\n const existing = readSettings(paths.settingsPath)\n const existingConfig =\n (existing.plugins?.entries?.[PLUGIN_ID]?.config as LatitudePluginConfig | undefined) ?? undefined\n const envConfig = flags.environment ?? PRODUCTION_ENV\n const urls = urlsFor(envConfig)\n\n const aboutLines = [\n \"Captures every OpenClaw agent run and ships it to Latitude as\",\n \"OpenTelemetry traces — full system prompt, tool I/O, messages,\",\n \"token usage, and agent name on every span.\",\n \"\",\n `${pc.dim(\"Docs\")} ${pc.cyan(DOCS_URL)}`,\n ]\n if (envConfig.name !== \"production\") {\n aboutLines.push(\"\", pc.yellow(`Using ${envConfig.label} environment (${envConfig.ingest})`))\n }\n note(aboutLines.join(\"\\n\"), \"About\")\n\n log.info(`Create a project at ${pc.cyan(urls.projects)}`)\n\n const project = await promptProject(existingConfig?.project, flags.project)\n log.info(`Get an API key at ${pc.cyan(urls.apiKeys(project))}`)\n const apiKey = await promptApiKey(existingConfig?.apiKey, flags.apiKey)\n\n await applyChanges({\n apiKey,\n project,\n envConfig,\n allowConversationAccess: flags.allowConversationAccess,\n noTrust: flags.noTrust === true,\n runtimeSpec: flags.runtimeSpec,\n paths,\n dryRun: flags.dryRun === true,\n })\n\n if (!flags.dryRun) {\n await maybeRestartGateway(flags.restart ?? \"auto\", paths)\n }\n\n note(\n [\n flags.dryRun ? \"Dry-run only — nothing was written.\" : \"Plugin installed and configured.\",\n `View your traces at ${pc.cyan(urls.projectView(project))}`,\n ].join(\"\\n\"),\n \"Next step\",\n )\n outro(flags.dryRun ? pc.dim(\"(dry-run)\") : pc.green(\"✓ Installed\"))\n}\n\nasync function runFlagDrivenInstall(flags: InstallFlags): Promise<void> {\n ensureOpenclawIsCompatible()\n const { resolved, paths } = resolvePaths(flags)\n process.stdout.write(`Using config dir: ${paths.configDir} (source: ${resolved.source})\\n`)\n if (!flags.runtimeSpec) await ensureRuntimeOnNpm()\n\n const apiKey = flags.apiKey\n const project = flags.project\n if (!apiKey || !project) {\n throw new Error(\"Non-interactive install requires --api-key=... and --project=... (or run in a TTY).\")\n }\n const envConfig = flags.environment ?? PRODUCTION_ENV\n await applyChanges({\n apiKey,\n project,\n envConfig,\n allowConversationAccess: flags.allowConversationAccess,\n noTrust: flags.noTrust === true,\n runtimeSpec: flags.runtimeSpec,\n paths,\n dryRun: flags.dryRun === true,\n })\n if (flags.dryRun) {\n process.stdout.write(\"Dry-run only — nothing was written.\\n\")\n return\n }\n await maybeRestartGateway(flags.restart ?? \"auto\", paths)\n process.stdout.write(`Installed Latitude plugin in ${paths.settingsPath}\\n`)\n}\n\nasync function promptApiKey(_existing: string | undefined, flag: string | undefined): Promise<string> {\n if (flag) return flag\n const result = await password({\n message: \"Latitude API key\",\n mask: \"•\",\n validate: (v) => (v && v.length > 0 ? undefined : \"Required\"),\n })\n if (isCancel(result)) return onCancel()\n return result\n}\n\nasync function promptProject(existing: string | undefined, flag: string | undefined): Promise<string> {\n if (flag) return flag\n const result = await text({\n message: \"Latitude project slug\",\n placeholder: existing ?? \"my-openclaw-project\",\n ...(existing ? { initialValue: existing } : {}),\n validate: (v) => (v && v.length > 0 ? undefined : \"Required\"),\n })\n if (isCancel(result)) return onCancel()\n return result\n}\n\nfunction onCancel(): never {\n cancel(\"Cancelled — nothing was changed\")\n process.exit(1)\n}\n\ninterface ApplyParams {\n apiKey: string\n project: string\n envConfig: EnvironmentConfig\n /** Tristate — see `InstallFlags.allowConversationAccess`. */\n allowConversationAccess: boolean | undefined\n noTrust: boolean\n runtimeSpec?: string | undefined\n paths: SettingsPaths\n dryRun: boolean\n}\n\nasync function applyChanges({\n apiKey,\n project,\n envConfig,\n allowConversationAccess,\n noTrust,\n runtimeSpec,\n paths,\n dryRun,\n}: ApplyParams): Promise<void> {\n const installSpec = runtimeSpec ?? `${RUNTIME_PACKAGE_NAME}@${RUNTIME_VERSION}`\n const installArgs = [\"plugins\", \"install\", installSpec, \"--force\", \"--accept-capabilities\"]\n\n // Build the proposed post-install settings without writing them. The\n // dry-run path uses this directly; the real path writes the same value.\n const before = readSettings(paths.settingsPath)\n const after = structuredClone(before) as OpenClawSettings\n migrateLegacyEntries(after)\n setPluginEntry(after, {\n apiKey,\n project,\n baseUrl: envConfig.name === \"production\" ? undefined : envConfig.ingest,\n allowConversationAccess,\n })\n if (!noTrust) addToPluginsAllow(after)\n\n if (dryRun) {\n log.info(`Would run: ${pc.dim(`openclaw ${installArgs.join(\" \")}`)}`)\n const diff = jsonDiff(before, after, { fromLabel: paths.settingsPath, toLabel: `${paths.settingsPath} (proposed)` })\n if (diff.length > 0) {\n process.stdout.write(`${diff}\\n`)\n } else {\n log.info(`No openclaw.json changes — current config already matches.`)\n }\n return\n }\n\n // 1. Take the backup BEFORE openclaw plugins install touches openclaw.json\n // so it represents the user's true pre-install state. `openclaw plugins\n // install` creates plugins.entries[id], so backing up after would lose\n // the original \"no entry\" state and make recovery harder if any later\n // step fails.\n ensureSettingsDir(paths)\n backupSettings(paths.settingsPath, paths.settingsBackupPath)\n\n // 2. Hand placement off to OpenClaw via the npm-spec form. OpenClaw fetches\n // the package from npm, runs its install-time security scan, copies\n // files into <configDir>/extensions/<encoded-id>/, writes the install\n // record into <configDir>/plugins/installs.json, and creates a (disabled,\n // configless) plugins.entries[id] in openclaw.json. We layer config +\n // hooks + allow on top in step 3. --force lets us overwrite an existing\n // install (e.g. when re-running on top of a previous version), and\n // --accept-capabilities records the operator's consent to the plugin's\n // declared surface, which OpenClaw requires for any non-bundled plugin.\n const installSpinner = spinner()\n installSpinner.start(`Installing plugin via openclaw plugins install ${installSpec}`)\n const installResult = runOpenclaw(installArgs, {\n timeoutMs: 60_000,\n env: envForSubprocess(paths),\n })\n if (!installResult.ok) {\n installSpinner.stop(\"openclaw plugins install failed\")\n if (installResult.reason === \"enoent\") {\n throw new Error(\"`openclaw` not found on PATH. Install OpenClaw first (https://openclaw.ai/install) and re-run.\")\n }\n if (installResult.reason === \"timeout\") {\n throw new Error(\"openclaw plugins install timed out after 60s. Try running it manually to see what's stuck.\")\n }\n const detail = installResult.stderr.trim() || installResult.stdout.trim() || `exit code ${installResult.code}`\n throw new Error(`openclaw plugins install failed: ${detail}`)\n }\n installSpinner.stop(\"Plugin registered with OpenClaw\")\n\n // 3. Layer our config, hooks, and (optionally) plugins.allow on top of the\n // entry OpenClaw just created. Atomic write — temp + rename — combined\n // with the .latitude-bak backup means a SIGTERM mid-write can never\n // leave openclaw.json in a half-serialized state.\n //\n // Snapshot the post-`openclaw plugins install` state for rollback. The\n // pre-install backup at `.latitude-bak` covers the typical case where\n // openclaw.json existed before our flow, but `backupSettings` is a no-op\n // when there's no source file to copy — that's fresh installs (no prior\n // openclaw.json). For those, the post-install state captured here is the\n // valid rollback target: it reflects exactly what `openclaw plugins\n // install` produced, which is always schema-valid (a disabled\n // plugins.entries[id] block + whatever else OpenClaw bootstrapped).\n const postInstallSnapshot = readSettings(paths.settingsPath)\n\n const settingsSpinner = spinner()\n settingsSpinner.start(\"Updating openclaw.json\")\n const settings = readSettings(paths.settingsPath)\n migrateLegacyEntries(settings)\n setPluginEntry(settings, {\n apiKey,\n project,\n baseUrl: envConfig.name === \"production\" ? undefined : envConfig.ingest,\n allowConversationAccess,\n // `debug` intentionally not passed — `setPluginEntry` preserves\n // hand-edited values; fresh installs leave the key absent (runtime\n // default is `false`).\n })\n if (!noTrust) {\n addToPluginsAllow(settings)\n }\n writeSettings(paths.settingsPath, settings)\n settingsSpinner.stop(`Updated ${paths.settingsPath}`)\n if (existsSync(paths.settingsBackupPath)) log.info(`Backup saved at ${pc.dim(paths.settingsBackupPath)}`)\n if (noTrust) {\n log.warning(\n `--no-trust set; OpenClaw will warn at every gateway start that ${PLUGIN_ID} is untrusted. Add it to plugins.allow yourself when you're ready.`,\n )\n }\n\n // 4. Validate. Catches schema regressions and config-dir mismatch (where\n // OpenClaw didn't pick up our OPENCLAW_HOME and wrote the install\n // record to a different file from where we wrote the entry) before the\n // operator restarts the gateway and finds out the hard way.\n const validateSpinner = spinner()\n validateSpinner.start(\"Validating openclaw.json\")\n const validateResult = runOpenclaw([\"config\", \"validate\", \"--json\"], {\n timeoutMs: 10_000,\n env: envForSubprocess(paths),\n })\n if (!validateResult.ok || isInvalidConfigPayload(validateResult.stdout)) {\n validateSpinner.stop(\"openclaw config validate failed\")\n const rollback = rollbackSettings(paths, postInstallSnapshot)\n const detail =\n validateResult.ok === false\n ? validateResult.stderr.trim() || validateResult.stdout.trim() || `exit code ${validateResult.code}`\n : validateResult.stdout.trim() || \"config validate reported invalid config\"\n throw new Error(`openclaw config validate reported a problem after our changes:\\n ${detail}\\n${rollback}`)\n }\n validateSpinner.stop(\"Config valid\")\n}\n\n/**\n * Two-tier rollback for validation failures:\n *\n * 1. If `.latitude-bak` exists (typical case — operator had openclaw.json\n * before this flow), restore from it. That's the user's true\n * pre-install state.\n * 2. If `.latitude-bak` doesn't exist (fresh install — no openclaw.json\n * pre-flow, so `backupSettings` no-op'd), write `postInstallSnapshot`\n * back. That's the state immediately after `openclaw plugins install`\n * and before any of our config layering — schema-valid, just a\n * disabled plugin entry. Operator can re-run install or run\n * `openclaw plugins uninstall` to fully revert OpenClaw's bookkeeping.\n *\n * Returns the human-readable recovery line to append to the thrown error.\n */\nfunction rollbackSettings(paths: SettingsPaths, postInstallSnapshot: OpenClawSettings): string {\n if (existsSync(paths.settingsBackupPath)) {\n const restored = restoreBackup(paths.settingsPath, paths.settingsBackupPath)\n return restored\n ? `Backup restored from ${paths.settingsBackupPath} — your config is back to the pre-install state.`\n : `Backup at ${paths.settingsBackupPath} could not be restored automatically; restore it manually.`\n }\n // Fresh-install path: write the post-install snapshot back. Atomic, same\n // as the install write.\n try {\n writeSettings(paths.settingsPath, postInstallSnapshot)\n return (\n `${paths.settingsPath} rolled back to the post-\\`openclaw plugins install\\` state ` +\n `(plugin entry exists but disabled). Run \\`openclaw plugins uninstall ${PLUGIN_ID} --force\\` ` +\n \"to fully revert if you don't intend to retry.\"\n )\n } catch (err) {\n return (\n `Couldn't roll back ${paths.settingsPath} (${String(err)}). ` +\n `Run \\`openclaw plugins uninstall ${PLUGIN_ID} --force\\` and inspect the file manually.`\n )\n }\n}\n\n/** Best-effort detection of `{\"valid\": false, ...}` in the validate output. */\nfunction isInvalidConfigPayload(stdout: string): boolean {\n const trimmed = stdout.trim()\n if (!trimmed) return false\n try {\n const parsed = JSON.parse(trimmed) as { valid?: unknown }\n return parsed.valid === false\n } catch {\n // Validator may print non-JSON banners — if exit code was 0, treat as valid.\n return false\n }\n}\n\nfunction ensureSettingsDir(paths: SettingsPaths): void {\n const dir = dirname(paths.settingsPath)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n}\n\n/**\n * Verify `openclaw` is on PATH AND its version is >= MIN_OPENCLAW_VERSION.\n * Aborts with a clear upgrade message otherwise. Called before any user\n * prompts so we don't waste their time collecting credentials we can't\n * use.\n */\nfunction ensureOpenclawIsCompatible(): void {\n const v = getOpenclawVersion()\n if (!v.ok) {\n if (v.error === \"missing\") {\n cancel(\"OpenClaw CLI not found on PATH. Install or update via `npm install -g openclaw@latest` and re-run.\")\n process.exit(1)\n }\n cancel(\n `Couldn't parse OpenClaw version output${v.raw ? ` (got: ${pc.dim(v.raw)})` : \"\"}. Run \\`openclaw --version\\` and report the output.`,\n )\n process.exit(1)\n }\n if (compareCalver(v.version, MIN_OPENCLAW_VERSION) < 0) {\n cancel(\n `OpenClaw ${v.version} is older than the minimum supported version (${MIN_OPENCLAW_VERSION}). Run \\`npm install -g openclaw@latest\\` and re-run install.`,\n )\n process.exit(1)\n }\n log.info(`OpenClaw ${pc.dim(v.version)} (>= ${MIN_OPENCLAW_VERSION})`)\n}\n\n/**\n * Lockstep contract check. The CLI installs a pinned `RUNTIME_VERSION`; if\n * that version isn't published yet (half-released release pipeline), abort\n * with a clear upgrade path so the operator's gateway doesn't end up with a\n * runtime that doesn't match this CLI's expectations.\n *\n * Best-effort — uses the npm registry's HTTP API rather than spawning\n * `npm view` so we don't require npm to be on PATH. Network failure\n * (offline) → warn-and-continue; the install spawn would fail visibly\n * anyway if the package wasn't there.\n */\nasync function ensureRuntimeOnNpm(): Promise<void> {\n const url = `https://registry.npmjs.org/${encodeURIComponent(RUNTIME_PACKAGE_NAME)}/${encodeURIComponent(RUNTIME_VERSION)}`\n try {\n const res = await fetch(url, { method: \"GET\", signal: AbortSignal.timeout(5_000) })\n if (res.status === 404) {\n cancel(\n `CLI ${readCliVersion()} expects ${RUNTIME_PACKAGE_NAME}@${RUNTIME_VERSION} but npm doesn't have that exact version. ` +\n `Upgrade the CLI: npm install -g @latitude-data/openclaw-telemetry-cli@latest`,\n )\n process.exit(1)\n }\n if (!res.ok) {\n log.warning(`npm registry check returned HTTP ${res.status}; continuing.`)\n }\n } catch (err) {\n // Offline / DNS / timeout — the install spawn will surface any real\n // problem.\n log.warning(`Couldn't reach npm registry to verify ${RUNTIME_PACKAGE_NAME}@${RUNTIME_VERSION}: ${String(err)}`)\n }\n}\n\n/**\n * Restart the OpenClaw gateway, prompting on TTY by default. The behaviour\n * is governed by `flags.restart`:\n *\n * - `\"force\"` (--restart): always restart, even non-TTY. CI escape hatch.\n * - `\"never\"` (--no-restart): never restart, even on TTY.\n * - `\"auto\"` (default): prompt on TTY (\"Restart now? [Y/n]\"); skip\n * non-TTY (and print the manual command). Hybrid that keeps interactive\n * UX safe while CI defaults to leaving the operator in control.\n */\nasync function maybeRestartGateway(mode: \"force\" | \"never\" | \"auto\", paths: SettingsPaths): Promise<void> {\n if (mode === \"never\") {\n log.info(`Skipping gateway restart (--no-restart). Run \\`openclaw gateway restart\\` when ready.`)\n return\n }\n\n let shouldRestart: boolean\n if (mode === \"force\") {\n shouldRestart = true\n } else {\n // \"auto\": prompt on TTY, skip non-TTY.\n if (process.stdin.isTTY !== true) {\n log.info(`Run \\`openclaw gateway restart\\` to load the plugin.`)\n return\n }\n const confirmed = await confirm({\n message: \"Restart the OpenClaw gateway now to load the plugin?\",\n initialValue: true,\n })\n if (isCancel(confirmed) || confirmed !== true) {\n log.info(`Run \\`openclaw gateway restart\\` when ready.`)\n return\n }\n shouldRestart = true\n }\n\n if (!shouldRestart) return\n\n log.warning(\"Restarting gateway. In-flight runs may be interrupted.\")\n const restartSpinner = spinner()\n restartSpinner.start(\"openclaw gateway restart\")\n const restartResult = runOpenclaw([\"gateway\", \"restart\"], {\n timeoutMs: 60_000,\n env: envForSubprocess(paths),\n })\n if (!restartResult.ok) {\n restartSpinner.stop(\"Gateway restart failed\")\n const detail = restartResult.stderr.trim() || restartResult.stdout.trim() || `exit code ${restartResult.code}`\n log.warning(`openclaw gateway restart failed: ${detail}. Restart it yourself when ready.`)\n return\n }\n restartSpinner.stop(\"Gateway restarted\")\n}\n\n// ─── Uninstall ──────────────────────────────────────────────────────────────\n\ninterface UninstallFlags {\n noPrompt?: boolean\n openclawDir?: string | undefined\n restart?: \"force\" | \"never\" | \"auto\"\n}\n\nexport function normalizeUninstallFlags(flags: Record<string, string | boolean>): UninstallFlags {\n let restart: \"force\" | \"never\" | \"auto\" = \"auto\"\n if (flags[\"no-restart\"] === true) restart = \"never\"\n if (flags.restart === true) {\n if (flags[\"no-restart\"] === true) throw new Error(\"--restart and --no-restart are mutually exclusive\")\n restart = \"force\"\n }\n return {\n noPrompt: flags[\"no-prompt\"] === true || flags.yes === true,\n openclawDir: typeof flags[\"openclaw-dir\"] === \"string\" ? flags[\"openclaw-dir\"] : undefined,\n restart,\n }\n}\n\nexport async function runUninstall(flags: UninstallFlags = {}): Promise<void> {\n intro(pc.bgYellow(pc.black(\" Latitude · OpenClaw telemetry — uninstall \")))\n\n const resolved = resolveConfigDir({ flag: flags.openclawDir })\n const paths = pathsFor(resolved.dir)\n log.info(`Using config dir: ${pc.dim(paths.configDir)} ${pc.dim(`(source: ${resolved.source})`)}`)\n\n const settings = readSettings(paths.settingsPath)\n const hasEntry = hasLatitudePlugin(settings)\n\n // Without an entry, there's nothing for `openclaw plugins uninstall` to\n // remove either — short-circuit cleanly. If the user wants to uninstall\n // even when our entry is gone (e.g. we crashed mid-install), they can\n // run `openclaw plugins uninstall` themselves.\n if (!hasEntry) {\n note(\"No Latitude plugin entry found — nothing to remove.\", \"Status\")\n outro(pc.dim(\"Nothing changed\"))\n return\n }\n\n const plan = [\n `Run \\`openclaw plugins uninstall ${PLUGIN_ID} --force\\` (removes files, install record, and plugin entry)`,\n `Sweep any leftover LATITUDE_* keys from settings.env`,\n `Backup of openclaw.json saved at ${paths.settingsBackupPath}`,\n ]\n note(plan.join(\"\\n\"), \"Plan\")\n\n if (!flags.noPrompt) {\n if (process.stdin.isTTY !== true) {\n // Symmetric with `runFlagDrivenInstall` requiring --api-key/--project\n // when non-TTY: there's no way for the operator to confirm a\n // destructive uninstall in a piped/CI invocation, so we refuse rather\n // than silently going ahead. `npx -y ...-cli uninstall` accidentally\n // landing inside a CI script otherwise wipes the plugin without any\n // confirming signal.\n throw new Error(\n \"Non-interactive uninstall requires --yes / --no-prompt to confirm. Re-run with --yes to bypass the prompt explicitly.\",\n )\n }\n const ok = await confirm({ message: \"Proceed?\", initialValue: true })\n if (isCancel(ok) || ok !== true) return onCancel()\n }\n\n // 1. Take the backup BEFORE openclaw plugins uninstall touches\n // openclaw.json so it represents the user's true pre-uninstall state\n // (entry + config + plugins.allow). Backing up after would capture\n // the already-stripped state, defeating the point of the backup.\n backupSettings(paths.settingsPath, paths.settingsBackupPath)\n\n // 2. Hand uninstall to OpenClaw. It removes files, the install record,\n // plugins.entries[id], plugins.allow, plugins.deny, plugins.load.paths\n // — see src/plugins/uninstall.ts.\n const s = spinner()\n s.start(\"Reverting via openclaw plugins uninstall\")\n const uninstallResult = runOpenclaw([\"plugins\", \"uninstall\", PLUGIN_ID, \"--force\"], {\n timeoutMs: 60_000,\n env: envForSubprocess(paths),\n })\n if (!uninstallResult.ok) {\n s.stop(\"openclaw plugins uninstall failed\")\n if (uninstallResult.reason === \"enoent\") {\n log.warning(\n \"`openclaw` not found on PATH. Falling back to local cleanup — files at <configDir>/extensions/ may remain.\",\n )\n } else {\n const detail =\n uninstallResult.stderr.trim() || uninstallResult.stdout.trim() || `exit code ${uninstallResult.code}`\n log.warning(`openclaw plugins uninstall reported: ${detail}. Continuing with local cleanup.`)\n }\n } else {\n s.stop(\"Plugin removed by OpenClaw\")\n }\n\n // 3. Defensive cleanup — `openclaw plugins uninstall` already strips the\n // entry and plugins.allow on success, but if it failed above (enoent\n // / non-zero) we still want our state out of openclaw.json. These\n // are all idempotent.\n const cleanupSpinner = spinner()\n cleanupSpinner.start(\"Reverting openclaw.json\")\n const post = readSettings(paths.settingsPath)\n removePluginEntry(post)\n removeFromPluginsAllow(post)\n migrateLegacyEntries(post)\n writeSettings(paths.settingsPath, post)\n cleanupSpinner.stop(\"Done\")\n\n await maybeRestartGateway(flags.restart ?? \"auto\", paths)\n\n outro(pc.green(\"✓ Uninstalled\"))\n}\n","#!/usr/bin/env node\nimport { normalizeInstallFlags, normalizeUninstallFlags, parseFlags, runInstall, runUninstall } from \"./setup.ts\"\nimport { readCliVersion } from \"./version.ts\"\n\nconst USAGE = `usage: latitude-openclaw <command> [options]\n\ncommands:\n install Install the plugin (interactive when stdin is a TTY)\n uninstall Remove the plugin entry and files\n --version, -v Print the package version\n --help, -h Print this message\n\ninstall options:\n --api-key=<key> Pass the API key non-interactively\n --project=<slug> Pass the project slug non-interactively\n --staging Target https://staging.latitude.so / staging-ingest\n --dev Target http://localhost:3000 / 3002\n --no-content Skip raw prompt/response/tool I/O capture\n --allow-conversation Force conversation capture on (overrides existing config)\n --no-trust Skip adding plugin id to plugins.allow\n --runtime-spec=<spec> Install this spec instead of the pinned npm version\n (e.g. npm-pack:/path/to/plugin.tgz for a pre-release build)\n --openclaw-dir=<path> Override OpenClaw config dir (default: $OPENCLAW_HOME, ./openclaw.json,\n or ~/.openclaw — see resolution order in README)\n --dry-run Show the diff against current openclaw.json and exit (no writes)\n --restart Always restart the gateway, even non-TTY\n --no-restart Never restart the gateway, even on TTY\n --yes / --no-prompt Skip all prompts (required for non-TTY / CI)\n\nuninstall options:\n --openclaw-dir=<path> Override OpenClaw config dir (same precedence as install)\n --restart Always restart the gateway, even non-TTY\n --no-restart Never restart the gateway, even on TTY\n --yes / --no-prompt Skip the confirmation prompt\n`\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2)\n // Top-level --version / --help are handled before parseFlags so users can\n // type them as the first arg without hitting the \"unknown subcommand\" path.\n if (argv[0] === \"--version\" || argv[0] === \"-v\") {\n process.stdout.write(`${readCliVersion()}\\n`)\n return\n }\n if (argv[0] === \"--help\" || argv[0] === \"-h\") {\n process.stdout.write(USAGE)\n return\n }\n\n const { subcommand, flags } = parseFlags(argv)\n if (subcommand === \"install\" || subcommand === undefined) {\n await runInstall(normalizeInstallFlags(flags))\n return\n }\n if (subcommand === \"uninstall\") {\n await runUninstall(normalizeUninstallFlags(flags))\n return\n }\n process.stderr.write(`unknown subcommand: ${subcommand}\\n`)\n process.stderr.write(USAGE)\n process.exit(1)\n}\n\nmain().catch((err) => {\n process.stderr.write(`${String(err)}\\n`)\n process.exit(1)\n})\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latitude-data/openclaw-telemetry-cli",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
4
4
  "description": "One-shot installer for the @latitude-data/openclaw-telemetry OpenClaw plugin. Configures credentials, plugins.allow, and the gateway-restart prompt against ~/.openclaw/openclaw.json.",
5
5
  "author": "Latitude Data SL <hello@latitude.so>",
6
6
  "license": "MIT",
@@ -39,9 +39,9 @@
39
39
  "@biomejs/biome": "2.4.6",
40
40
  "@types/node": "22.14.1",
41
41
  "tsdown": "0.21.9",
42
- "@typescript/native-preview": "7.0.0-dev.20260421.2",
42
+ "@typescript/native-preview": "7.0.0-dev.20260705.1",
43
43
  "typescript": "6.0.3",
44
- "vitest": "4.1.0"
44
+ "vitest": "4.1.8"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "typescript": "^6.0.3"