@latitude-data/openclaw-telemetry-cli 0.0.7 → 0.0.9

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/LICENSE CHANGED
@@ -1,157 +1,21 @@
1
- # GNU LESSER GENERAL PUBLIC LICENSE
2
-
3
- Version 3, 29 June 2007
4
-
5
- Copyright (C) 2007 Free Software Foundation, Inc.
6
- <https://fsf.org/>
7
-
8
- Everyone is permitted to copy and distribute verbatim copies of this
9
- license document, but changing it is not allowed.
10
-
11
- This version of the GNU Lesser General Public License incorporates the
12
- terms and conditions of version 3 of the GNU General Public License,
13
- supplemented by the additional permissions listed below.
14
-
15
- ## 0. Additional Definitions.
16
-
17
- As used herein, "this License" refers to version 3 of the GNU Lesser
18
- General Public License, and the "GNU GPL" refers to version 3 of the
19
- GNU General Public License.
20
-
21
- "The Library" refers to a covered work governed by this License, other
22
- than an Application or a Combined Work as defined below.
23
-
24
- An "Application" is any work that makes use of an interface provided
25
- by the Library, but which is not otherwise based on the Library.
26
- Defining a subclass of a class defined by the Library is deemed a mode
27
- of using an interface provided by the Library.
28
-
29
- A "Combined Work" is a work produced by combining or linking an
30
- Application with the Library. The particular version of the Library
31
- with which the Combined Work was made is also called the "Linked
32
- Version".
33
-
34
- The "Minimal Corresponding Source" for a Combined Work means the
35
- Corresponding Source for the Combined Work, excluding any source code
36
- for portions of the Combined Work that, considered in isolation, are
37
- based on the Application, and not on the Linked Version.
38
-
39
- The "Corresponding Application Code" for a Combined Work means the
40
- object code and/or source code for the Application, including any data
41
- and utility programs needed for reproducing the Combined Work from the
42
- Application, but excluding the System Libraries of the Combined Work.
43
-
44
- ## 1. Exception to Section 3 of the GNU GPL.
45
-
46
- You may convey a covered work under sections 3 and 4 of this License
47
- without being bound by section 3 of the GNU GPL.
48
-
49
- ## 2. Conveying Modified Versions.
50
-
51
- If you modify a copy of the Library, and, in your modifications, a
52
- facility refers to a function or data to be supplied by an Application
53
- that uses the facility (other than as an argument passed when the
54
- facility is invoked), then you may convey a copy of the modified
55
- version:
56
-
57
- - a) under this License, provided that you make a good faith effort
58
- to ensure that, in the event an Application does not supply the
59
- function or data, the facility still operates, and performs
60
- whatever part of its purpose remains meaningful, or
61
- - b) under the GNU GPL, with none of the additional permissions of
62
- this License applicable to that copy.
63
-
64
- ## 3. Object Code Incorporating Material from Library Header Files.
65
-
66
- The object code form of an Application may incorporate material from a
67
- header file that is part of the Library. You may convey such object
68
- code under terms of your choice, provided that, if the incorporated
69
- material is not limited to numerical parameters, data structure
70
- layouts and accessors, or small macros, inline functions and templates
71
- (ten or fewer lines in length), you do both of the following:
72
-
73
- - a) Give prominent notice with each copy of the object code that
74
- the Library is used in it and that the Library and its use are
75
- covered by this License.
76
- - b) Accompany the object code with a copy of the GNU GPL and this
77
- license document.
78
-
79
- ## 4. Combined Works.
80
-
81
- You may convey a Combined Work under terms of your choice that, taken
82
- together, effectively do not restrict modification of the portions of
83
- the Library contained in the Combined Work and reverse engineering for
84
- debugging such modifications, if you also do each of the following:
85
-
86
- - a) Give prominent notice with each copy of the Combined Work that
87
- the Library is used in it and that the Library and its use are
88
- covered by this License.
89
- - b) Accompany the Combined Work with a copy of the GNU GPL and this
90
- license document.
91
- - c) For a Combined Work that displays copyright notices during
92
- execution, include the copyright notice for the Library among
93
- these notices, as well as a reference directing the user to the
94
- copies of the GNU GPL and this license document.
95
- - d) Do one of the following:
96
- - 0) Convey the Minimal Corresponding Source under the terms of
97
- this License, and the Corresponding Application Code in a form
98
- suitable for, and under terms that permit, the user to
99
- recombine or relink the Application with a modified version of
100
- the Linked Version to produce a modified Combined Work, in the
101
- manner specified by section 6 of the GNU GPL for conveying
102
- Corresponding Source.
103
- - 1) Use a suitable shared library mechanism for linking with
104
- the Library. A suitable mechanism is one that (a) uses at run
105
- time a copy of the Library already present on the user's
106
- computer system, and (b) will operate properly with a modified
107
- version of the Library that is interface-compatible with the
108
- Linked Version.
109
- - e) Provide Installation Information, but only if you would
110
- otherwise be required to provide such information under section 6
111
- of the GNU GPL, and only to the extent that such information is
112
- necessary to install and execute a modified version of the
113
- Combined Work produced by recombining or relinking the Application
114
- with a modified version of the Linked Version. (If you use option
115
- 4d0, the Installation Information must accompany the Minimal
116
- Corresponding Source and Corresponding Application Code. If you
117
- use option 4d1, you must provide the Installation Information in
118
- the manner specified by section 6 of the GNU GPL for conveying
119
- Corresponding Source.)
120
-
121
- ## 5. Combined Libraries.
122
-
123
- You may place library facilities that are a work based on the Library
124
- side by side in a single library together with other library
125
- facilities that are not Applications and are not covered by this
126
- License, and convey such a combined library under terms of your
127
- choice, if you do both of the following:
128
-
129
- - a) Accompany the combined library with a copy of the same work
130
- based on the Library, uncombined with any other library
131
- facilities, conveyed under the terms of this License.
132
- - b) Give prominent notice with the combined library that part of it
133
- is a work based on the Library, and explaining where to find the
134
- accompanying uncombined form of the same work.
135
-
136
- ## 6. Revised Versions of the GNU Lesser General Public License.
137
-
138
- The Free Software Foundation may publish revised and/or new versions
139
- of the GNU Lesser General Public License from time to time. Such new
140
- versions will be similar in spirit to the present version, but may
141
- differ in detail to address new problems or concerns.
142
-
143
- Each version is given a distinguishing version number. If the Library
144
- as you received it specifies that a certain numbered version of the
145
- GNU Lesser General Public License "or any later version" applies to
146
- it, you have the option of following the terms and conditions either
147
- of that published version or of any later version published by the
148
- Free Software Foundation. If the Library as you received it does not
149
- specify a version number of the GNU Lesser General Public License, you
150
- may choose any version of the GNU Lesser General Public License ever
151
- published by the Free Software Foundation.
152
-
153
- If the Library as you received it specifies that a proxy can decide
154
- whether future versions of the GNU Lesser General Public License shall
155
- apply, that proxy's public statement of acceptance of any version is
156
- permanent authorization for you to choose that version for the
157
- Library.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Latitude Data SL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -7,14 +7,14 @@ For details on the plugin runtime itself — what it sends, the span tree, and t
7
7
  ## Requirements
8
8
 
9
9
  - **OpenClaw 2026.4.25 or newer** on PATH.
10
- - A **Latitude API key** and **project slug** — both from [console.latitude.so/settings/api-keys](https://console.latitude.so/settings/api-keys).
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
13
13
 
14
14
  Interactive (recommended for first-time setup):
15
15
 
16
16
  ```bash
17
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.7 install
17
+ npx -y @latitude-data/openclaw-telemetry-cli@0.0.9 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.7 install \
25
+ npx -y @latitude-data/openclaw-telemetry-cli@0.0.9 install \
26
26
  --api-key="$LATITUDE_API_KEY" \
27
27
  --project=my-project \
28
28
  --yes \
@@ -36,11 +36,11 @@ npx -y @latitude-data/openclaw-telemetry-cli@0.0.7 install \
36
36
  In order, on every install:
37
37
 
38
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.7` exists on npm. (Catches half-published releases.)
40
- 3. **Detects any prior install** via `<configDir>/plugins/installs.json`. Renders `Upgrading 0.0.6 → 0.0.7` (or `Re-applying 0.0.7 (idempotent)`) if found.
39
+ 2. **Verifies the runtime contract** — that `@latitude-data/openclaw-telemetry@0.0.9` exists on npm. (Catches half-published releases.)
40
+ 3. **Detects any prior install** via `<configDir>/plugins/installs.json`. Renders `Upgrading 0.0.8 → 0.0.9` (or `Re-applying 0.0.9 (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.7 --force`** — npm fetch, security scan, extension placement, install record, disabled `plugins.entries[id]`.
43
+ 6. **Runs `openclaw plugins install @latitude-data/openclaw-telemetry@0.0.9 --force`** — npm fetch, security scan, extension placement, install record, disabled `plugins.entries[id]`.
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.
@@ -72,7 +72,7 @@ In order, on every install:
72
72
  Preview every change without touching the filesystem or spawning subprocesses:
73
73
 
74
74
  ```bash
75
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.7 install --dry-run \
75
+ npx -y @latitude-data/openclaw-telemetry-cli@0.0.9 install --dry-run \
76
76
  --api-key=lat_xxx --project=my-project
77
77
  ```
78
78
 
@@ -94,7 +94,7 @@ The resolved path is printed at install start. The CLI passes `OPENCLAW_HOME=<re
94
94
  ### Structural-only telemetry
95
95
 
96
96
  ```bash
97
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.7 install --no-content \
97
+ npx -y @latitude-data/openclaw-telemetry-cli@0.0.9 install --no-content \
98
98
  --api-key=lat_xxx --project=my-project
99
99
  ```
100
100
 
@@ -104,11 +104,11 @@ The plugin still emits the full span tree — timings, token usage, model name,
104
104
 
105
105
  ```bash
106
106
  # Staging
107
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.7 install --staging --yes \
107
+ npx -y @latitude-data/openclaw-telemetry-cli@0.0.9 install --staging --yes \
108
108
  --api-key=lat_xxx --project=my-project
109
109
 
110
110
  # Local dev
111
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.7 install --dev --yes \
111
+ npx -y @latitude-data/openclaw-telemetry-cli@0.0.9 install --dev --yes \
112
112
  --api-key=lat_xxx --project=my-project
113
113
  ```
114
114
 
@@ -117,7 +117,7 @@ npx -y @latitude-data/openclaw-telemetry-cli@0.0.7 install --dev --yes \
117
117
  ## Uninstall
118
118
 
119
119
  ```bash
120
- npx -y @latitude-data/openclaw-telemetry-cli@0.0.7 uninstall
120
+ npx -y @latitude-data/openclaw-telemetry-cli@0.0.9 uninstall
121
121
  ```
122
122
 
123
123
  The uninstall flow:
@@ -132,7 +132,7 @@ The backup at `openclaw.json.latitude-bak` is kept after uninstall — delete it
132
132
 
133
133
  ## Lockstep policy
134
134
 
135
- This CLI installs **exactly** `@latitude-data/openclaw-telemetry@0.0.7` (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.
135
+ This CLI installs **exactly** `@latitude-data/openclaw-telemetry@0.0.9` (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
136
 
137
137
  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
138
 
package/dist/cli.js CHANGED
@@ -518,8 +518,8 @@ 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.7";
522
- const DOCS_URL = "https://docs.latitude.so/openclaw-telemetry";
521
+ const RUNTIME_VERSION = "0.0.9";
522
+ const DOCS_URL = "https://docs.latitude.so/telemetry/openclaw";
523
523
  const PRODUCTION_ENV = {
524
524
  name: "production",
525
525
  label: "production",
@@ -540,7 +540,7 @@ const DEV_ENV = {
540
540
  };
541
541
  function urlsFor(env) {
542
542
  return {
543
- apiKeys: `${env.app}/settings/api-keys`,
543
+ apiKeys: (slug) => `${env.app}/projects/${slug}/settings/keys`,
544
544
  projects: env.app,
545
545
  projectView: (slug) => `${env.app}/projects/${slug}`
546
546
  };
@@ -643,12 +643,11 @@ async function runInteractiveInstall(flags) {
643
643
  ];
644
644
  if (envConfig.name !== "production") aboutLines.push("", pc.yellow(`Using ${envConfig.label} environment (${envConfig.ingest})`));
645
645
  note(aboutLines.join("\n"), "About");
646
- log.info(`Get an API key at ${pc.cyan(urls.apiKeys)}`);
647
646
  log.info(`Create a project at ${pc.cyan(urls.projects)}`);
648
- const apiKey = await promptApiKey(existingConfig?.apiKey, flags.apiKey);
649
647
  const project = await promptProject(existingConfig?.project, flags.project);
648
+ log.info(`Get an API key at ${pc.cyan(urls.apiKeys(project))}`);
650
649
  await applyChanges({
651
- apiKey,
650
+ apiKey: await promptApiKey(existingConfig?.apiKey, flags.apiKey),
652
651
  project,
653
652
  envConfig,
654
653
  allowConversationAccess: flags.allowConversationAccess,
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.7\"\n\nconst DOCS_URL = \"https://docs.latitude.so/openclaw-telemetry\"\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: string\n projects: string\n projectView: (slug: string) => string\n} {\n return {\n apiKeys: `${env.app}/settings/api-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(`Get an API key at ${pc.cyan(urls.apiKeys)}`)\n log.info(`Create a project at ${pc.cyan(urls.projects)}`)\n\n const apiKey = await promptApiKey(existingConfig?.apiKey, flags.apiKey)\n const project = await promptProject(existingConfig?.project, flags.project)\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.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.9\"\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":";;;;;;;;;AA0CA,SAAgB,iBAAiB,OAAuB,EAAE,EAAqB;CAC7E,MAAM,MAAM,KAAK,OAAO,QAAQ,KAAK;CACrC,MAAM,OAAO,KAAK,QAAQ,SAAS;AAEnC,KAAI,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,EACtD,QAAO;EAAE,KAAK,WAAW,KAAK,MAAM,IAAI;EAAE,QAAQ;EAAQ;CAE5D,MAAM,WAAW,KAAK,QAAQ,KAAA,IAAY,KAAK,MAAM,QAAQ,IAAI;AACjE,KAAI,OAAO,aAAa,YAAY,SAAS,SAAS,EACpD,QAAO;EAAE,KAAK,WAAW,UAAU,IAAI;EAAE,QAAQ;EAAO;AAE1D,KAAI,WAAW,KAAK,KAAK,gBAAgB,CAAC,CACxC,QAAO;EAAE,KAAK;EAAK,QAAQ;EAAO;AAEpC,QAAO;EAAE,KAAK,KAAK,MAAM,YAAY;EAAE,QAAQ;EAAW;;AAG5D,SAAS,WAAW,GAAW,KAAqB;AAClD,QAAO,WAAW,EAAE,GAAG,IAAIA,QAAY,KAAK,EAAE;;AAYhD,SAAgB,SAAS,WAAkC;AACzD,QAAO;EACL;EACA,cAAc,KAAK,WAAW,gBAAgB;EAC9C,oBAAoB,KAAK,WAAW,6BAA6B;EACjE,cAAc,KAAK,WAAW,WAAW,gBAAgB;EAC1D;;;;;;;;;;;;;;ACpEH,MAAM,gBAAgB;;;;;AAatB,SAAgB,SAAS,QAAiB,OAAgB,OAAoB,EAAE,EAAU;CACxF,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,UAAU,KAAK,WAAW;CAEhC,MAAM,aAAa,GAAG,KAAK,UAAU,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC;CAC5D,MAAM,YAAY,GAAG,KAAK,UAAU,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC;AAE1D,KAAI,eAAe,UAAW,QAAO;CAOrC,MAAM,MAAM,QALQ,WAAW,MAAM,KAAK,EACvB,UAAU,MAAM,KAAK,CAII;CAE5C,MAAM,MAAgB,EAAE;AACxB,KAAI,KAAK,OAAO,YAAY;AAC5B,KAAI,KAAK,OAAO,UAAU;CAG1B,IAAI,IAAI;AACR,QAAO,IAAI,IAAI,QAAQ;AACrB,MAAI,IAAI,IAAI,SAAS,MAAM;AACzB;AACA;;EAGF,MAAM,YAAY,KAAK,IAAI,GAAG,IAAI,cAAc;EAChD,IAAI,UAAU;AACd,SAAO,UAAU,IAAI,QAAQ;AAC3B,OAAI,IAAI,UAAU,SAAS,MAAM;IAI/B,IAAI,UAAU;IACd,IAAI,IAAI;AACR,WAAO,IAAI,IAAI,UAAU,IAAI,IAAI,SAAS,QAAQ,UAAU,gBAAgB,GAAG;AAC7E;AACA;;AAEF,QAAI,KAAK,IAAI,UAAU,IAAI,IAAI,SAAS,MAAM;AAC5C,eAAU,KAAK,IAAI,UAAU,eAAe,IAAI,OAAO;AACvD;;AAEF,cAAU;AACV;;AAEF;;AAGF,OAAK,IAAI,IAAI,WAAW,IAAI,WAAW,IAAI,IAAI,QAAQ,KAAK;GAC1D,MAAM,KAAK,IAAI;AACf,OAAI,CAAC,GAAI;AACT,OAAI,GAAG,SAAS,KAAM,KAAI,KAAK,KAAK,GAAG,OAAO;YACrC,GAAG,SAAS,MAAO,KAAI,KAAK,KAAK,GAAG,OAAO;YAC3C,GAAG,SAAS,MAAO,KAAI,KAAK,KAAK,GAAG,OAAO;;AAEtD,MAAI;;AAGN,QAAO,IAAI,KAAK,KAAK;;AAKvB,SAAS,QAAQ,GAAsB,GAA4B;CACjE,MAAM,IAAI,EAAE;CACZ,MAAM,IAAI,EAAE;CAEZ,MAAM,KAAiB,MAAM,KAAK,EAAE,QAAQ,IAAI,GAAG,QAAQ,IAAI,MAAc,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;AAC5F,MAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,KAAI,EAAE,IAAI,OAAO,EAAE,IAAI,GACrB,IAAG,GAAI,MAAM,GAAG,IAAI,KAAK,IAAI,MAAM,KAAK;KAExC,IAAG,GAAI,KAAK,KAAK,IAAI,GAAG,IAAI,KAAK,MAAM,GAAG,GAAG,KAAK,IAAI,MAAM,EAAE;CAKpE,MAAM,MAAY,EAAE;CACpB,IAAI,IAAI;CACR,IAAI,IAAI;AACR,QAAO,IAAI,KAAK,IAAI,EAClB,KAAI,EAAE,IAAI,OAAO,EAAE,IAAI,IAAI;AACzB,MAAI,KAAK;GAAE,MAAM;GAAM,MAAM,EAAE,IAAI;GAAc,CAAC;AAClD;AACA;aACU,GAAG,IAAI,KAAK,MAAM,OAAO,GAAG,KAAK,IAAI,MAAM,IAAI;AACzD,MAAI,KAAK;GAAE,MAAM;GAAO,MAAM,EAAE,IAAI;GAAc,CAAC;AACnD;QACK;AACL,MAAI,KAAK;GAAE,MAAM;GAAO,MAAM,EAAE,IAAI;GAAc,CAAC;AACnD;;AAGJ,QAAO,IAAI,GAAG;AACZ,MAAI,KAAK;GAAE,MAAM;GAAO,MAAM,EAAE,IAAI;GAAc,CAAC;AACnD;;AAEF,QAAO,IAAI,GAAG;AACZ,MAAI,KAAK;GAAE,MAAM;GAAO,MAAM,EAAE,IAAI;GAAc,CAAC;AACnD;;AAEF,KAAI,SAAS;AACb,QAAO;;;;;;;;;;;;ACvHT,MAAa,uBAAuB;AAEpC,MAAM,qBAAqB;;;;;;;;;;;;;AAoB3B,SAAgB,YACd,MACA,OAAwE,EAAE,EAC/D;CACX,MAAM,MAAM,KAAK,MAAM;EAAE,GAAG,QAAQ;EAAK,GAAG,KAAK;EAAK,GAAG,KAAA;CACzD,MAAM,SAAmC,UAAU,YAAY,MAAM;EACnE,UAAU;EACV,SAAS,KAAK,aAAa;EAC3B,OAAO,KAAK;EAIZ,GAAI,MAAM,EAAE,KAAK,GAAG,EAAE;EACtB,OAAO;GAAC;GAAQ;GAAQ;GAAO;EAChC,CAAC;CAQF,MAAM,MAAM,OAAO;AACnB,KAAI,KAAK,SAAS,SAChB,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAU,QAAQ;EAAI,QAAQ;EAAI,MAAM;EAAM;AAE5E,KAAI,KAAK,SAAS,eAAe,OAAO,WAAW,aAAa,OAAO,WAAW,UAChF,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,QAAQ,OAAO,UAAU;EACzB,QAAQ,OAAO,UAAU;EACzB,MAAM;EACP;AAEH,KAAI,IAEF,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,QAAQ,OAAO,UAAU;EACzB,QAAQ,OAAO,UAAU,OAAO,IAAI;EACpC,MAAM,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;EAC3D;AAGH,KAAI,OAAO,WAAW,EACpB,QAAO;EAAE,IAAI;EAAM,QAAQ,OAAO,UAAU;EAAI,QAAQ,OAAO,UAAU;EAAI,MAAM;EAAG;AAGxF,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,QAAQ,OAAO,UAAU;EACzB,QAAQ,OAAO,UAAU;EACzB,MAAM,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;EAC3D;;;;;;;;;;;;;AAkBH,SAAgB,qBAAoC;CAClD,MAAM,SAAS,YAAY,CAAC,YAAY,EAAE,EAAE,WAAW,KAAO,CAAC;AAC/D,KAAI,CAAC,OAAO,IAAI;AACd,MAAI,OAAO,WAAW,SAAU,QAAO;GAAE,IAAI;GAAO,OAAO;GAAW;AACtE,SAAO;GAAE,IAAI;GAAO,OAAO;GAAe,KAAK,OAAO,UAAU,OAAO;GAAQ;;CAGjF,MAAM,MAAM,OAAO,OAAO,MAAM;CAIhC,MAAM,QAAQ,IAAI,MAAM,oBAAoB;AAC5C,KAAI,CAAC,MAAO,QAAO;EAAE,IAAI;EAAO,OAAO;EAAe;EAAK;AAC3D,QAAO;EAAE,IAAI;EAAM,SAAS,MAAM;EAAc;EAAK;;;;;;;;AASvD,SAAgB,cAAc,GAAW,GAAuB;CAC9D,MAAM,KAAK,EAAE,MAAM,IAAI;CACvB,MAAM,KAAK,EAAE,MAAM,IAAI;CACvB,MAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,GAAG,OAAO;AAC1C,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;EAC5B,MAAM,KAAK,GAAG,MAAM;EACpB,MAAM,KAAK,GAAG,MAAM;EACpB,MAAM,KAAK,OAAO,GAAG;EACrB,MAAM,KAAK,OAAO,GAAG;AACrB,MAAI,OAAO,SAAS,GAAG,IAAI,OAAO,SAAS,GAAG,EAAE;AAC9C,OAAI,KAAK,GAAI,QAAO;AACpB,OAAI,KAAK,GAAI,QAAO;AACpB;;AAEF,MAAI,KAAK,GAAI,QAAO;AACpB,MAAI,KAAK,GAAI,QAAO;;AAEtB,QAAO;;;;;AC9IT,MAAa,YAAY;AAyDzB,SAAgB,aAAa,cAAwC;AACnE,KAAI,CAAC,WAAW,aAAa,CAAE,QAAO,EAAE;AACxC,KAAI;EACF,MAAM,MAAM,aAAa,cAAc,QAAQ;EAC/C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,SAAO,UAAU,OAAO,WAAW,WAAW,SAAS,EAAE;SACnD;AACN,SAAO,EAAE;;;;;;;;;;;;;;AAeb,SAAgB,cAAc,cAAsB,UAAkC;CACpF,MAAM,MAAM,GAAG,aAAa,OAAO,QAAQ;AAC3C,eAAc,KAAK,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,KAAK,QAAQ;AACrE,YAAW,KAAK,aAAa;;AAG/B,SAAgB,eAAe,cAAsB,YAA0B;AAC7E,KAAI,WAAW,aAAa,CAAE,cAAa,cAAc,WAAW;;;;;;;;AAStE,SAAgB,cAAc,cAAsB,YAA6B;AAC/E,KAAI,CAAC,WAAW,WAAW,CAAE,QAAO;AACpC,cAAa,YAAY,aAAa;AACtC,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DT,SAAgB,eAAe,UAA4B,OAAkC;CAC3F,MAAM,UAAU,SAAS,WAAW,EAAE;CACtC,MAAM,UAAU,QAAQ,WAAW,EAAE;CACrC,MAAM,WAAW,QAAA,wCAAsB,EAAE;CACzC,MAAM,iBAAkB,SAAS,UAAU,EAAE;CAC7C,MAAM,gBAAgB,SAAS,SAAS,EAAE;CAE1C,MAAM,aAAsC;EAC1C,GAAG;EACH,QAAQ,MAAM;EACd,SAAS,MAAM;EAChB;AACD,KAAI,MAAM,YAAY,KAAA,EACpB,YAAW,UAAU,MAAM;KAE3B,QAAO,WAAW;AAEpB,KAAI,MAAM,4BAA4B,KAAA,EACpC,YAAW,0BAA0B,MAAM;AAE7C,KAAI,MAAM,UAAU,KAAA,EAClB,YAAW,QAAQ,MAAM;CAQ3B,MAAM,kBACJ,OAAO,WAAW,4BAA4B,YAC1C,WAAW,0BACX,OAAO,cAAc,4BAA4B,YAC/C,cAAc,0BACd;CAER,MAAM,YAA0C;EAC9C,GAAG;EACH,yBAAyB;EAC1B;CAID,MAAM,cAAc,MAAM,WAAW,SAAS,WAAW;AAEzD,SAAQ,aAAa;EACnB,GAAG;EACH,SAAS;EACT,OAAO;EACP,QAAQ;EACT;AACD,SAAQ,UAAU;AAClB,UAAS,UAAU;;;AAIrB,SAAgB,kBAAkB,UAAqC;CACrE,MAAM,UAAU,SAAS;AACzB,KAAI,CAAC,SAAS,QAAS,QAAO;AAC9B,KAAI,EAAA,uCAAe,QAAQ,SAAU,QAAO;AAC5C,QAAO,QAAQ,QAAQ;AACvB,QAAO;;;;;;;;;;;;;AAcT,SAAgB,kBAAkB,UAAqC;CACrE,MAAM,UAAU,SAAS,WAAW,EAAE;CACtC,MAAM,WAAW,QAAQ;CACzB,MAAM,QAAQ,MAAM,QAAQ,SAAS,GAAG,WAAW,EAAE;AACrD,KAAI,MAAM,SAAA,oCAAmB,CAAE,QAAO;AACtC,SAAQ,QAAQ,CAAC,GAAG,OAAO,UAAU;AACrC,UAAS,UAAU;AACnB,QAAO;;;;;;;;AAST,SAAgB,uBAAuB,UAAqC;CAC1E,MAAM,QAAQ,SAAS,SAAS;AAChC,KAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,SAAA,oCAAmB,CAAE,QAAO;AAChE,KAAI,SAAS,QACX,UAAS,QAAQ,QAAQ,MAAM,QAAQ,OAAO,OAAO,UAAU;AAEjE,QAAO;;AAGT,SAAgB,kBAAkB,UAAqC;AACrE,QAAO,QAAQ,SAAS,SAAS,WAAA,uCAAwB,SAAS,QAAQ,QAAQ;;;;;;;;;;;;;;;;;AAkBpF,SAAgB,qBAAqB,UAAkD;CACrF,IAAI,UAAU;CAEd,MAAM,MAAM,SAAS;AACrB,KAAI,OAAO,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,IAAI,EAAE;EACzD,MAAM,SAAS;AACf,OAAK,MAAM,OAAO;GAAC;GAAoB;GAAoB;GAAoB,CAC7E,KAAI,OAAO,QAAQ;AACjB,UAAO,OAAO;AACd,aAAU;;AAKd,MAAI,OAAO,KAAK,OAAO,CAAC,WAAW,EACjC,QAAO,SAAS;;AAIpB,QAAO,EAAE,SAAS;;;;;;;;;;;AAYpB,SAAgB,4BAA4B,cAA0C;AACpF,KAAI,CAAC,WAAW,aAAa,CAAE,QAAO,KAAA;AACtC,KAAI;EACF,MAAM,MAAM,aAAa,cAAc,QAAQ;EAC/C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,CAAE,QAAO,KAAA;EAC3E,MAAM,UAAW,OAAiD;AAClE,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO,KAAA;EACpD,MAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,KAAA;EAC9C,MAAM,UAAW,KAA+B;AAChD,SAAO,OAAO,YAAY,WAAW,UAAU,KAAA;SACzC;AACN;;;;;;;;;;;;;;;;ACnTJ,SAAgB,iBAAyB;CAKvC,MAAM,UAAU,KADH,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACzB,MAAM,eAAe;AAChD,KAAI;AAEF,SADY,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC,CAC3C,WAAW;SAChB;AACN,SAAO;;;;;;;;;;;;;;ACSX,MAAM,uBAAuB;AAC7B,MAAM,kBAAkB;AAExB,MAAM,WAAW;AASjB,MAAM,iBAAoC;CACxC,MAAM;CACN,OAAO;CACP,KAAK;CACL,QAAQ;CACT;AACD,MAAM,cAAiC;CACrC,MAAM;CACN,OAAO;CACP,KAAK;CACL,QAAQ;CACT;AACD,MAAM,UAA6B;CACjC,MAAM;CACN,OAAO;CACP,KAAK;CACL,QAAQ;CACT;AAED,SAAS,QAAQ,KAIf;AACA,QAAO;EACL,UAAU,SAAiB,GAAG,IAAI,IAAI,YAAY,KAAK;EACvD,UAAU,IAAI;EACd,cAAc,SAAiB,GAAG,IAAI,IAAI,YAAY;EACvD;;;;;;;;AAoCH,MAAM,cAAc,IAAI,IAAI;CAAC;CAAW;CAAW;CAAe,CAAC;AAEnE,SAAgB,WAAW,MAGzB;CACA,MAAM,CAAC,YAAY,GAAG,QAAQ;CAC9B,MAAM,QAA0C,EAAE;AAClD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;AACjB,MAAI,CAAC,OAAO,CAAC,IAAI,WAAW,KAAK,CAAE;EACnC,MAAM,KAAK,IAAI,QAAQ,IAAI;AAC3B,MAAI,MAAM,GAAG;AACX,SAAM,IAAI,MAAM,GAAG,GAAG,IAAI,IAAI,MAAM,KAAK,EAAE;AAC3C;;EAEF,MAAM,MAAM,IAAI,MAAM,EAAE;EACxB,MAAM,OAAO,KAAK,IAAI;AAMtB,MAAI,YAAY,IAAI,IAAI,IAAI,SAAS,KAAA,KAAa,CAAC,KAAK,WAAW,KAAK,EAAE;AACxE,SAAM,OAAO;AACb,QAAK;QAEL,OAAM,OAAO;;AAGjB,QAAO;EAAE;EAAY;EAAO;;AAG9B,SAAgB,sBAAsB,OAAuD;CAC3F,IAAI;AACJ,KAAI,MAAM,YAAY,KAAM,eAAc;AAC1C,KAAI,MAAM,QAAQ,MAAM;AACtB,MAAI,YAAa,OAAM,IAAI,MAAM,6CAA6C;AAC9E,gBAAc;;CAIhB,IAAI;AACJ,KAAI,MAAM,kBAAkB,QAAQ,MAAM,uBAAuB,KAAM,2BAA0B;AACjG,KAAI,MAAM,0BAA0B,KAAM,2BAA0B;CAEpE,IAAI,UAAsC;AAC1C,KAAI,MAAM,kBAAkB,KAAM,WAAU;AAC5C,KAAI,MAAM,YAAY,MAAM;AAC1B,MAAI,MAAM,kBAAkB,KAAM,OAAM,IAAI,MAAM,oDAAoD;AACtG,YAAU;;AAGZ,QAAO;EACL,QAAQ,OAAO,MAAM,eAAe,WAAW,MAAM,aAAa,KAAA;EAClE,SAAS,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU,KAAA;EAC7D;EACA;EACA,SAAS,MAAM,gBAAgB;EAC/B,aAAa,OAAO,MAAM,oBAAoB,WAAW,MAAM,kBAAkB,KAAA;EACjF,QAAQ,MAAM,eAAe;EAC7B;EACA,UAAU,MAAM,iBAAiB,QAAQ,MAAM,QAAQ;EACvD,KAAK,MAAM,QAAQ;EACpB;;AAKH,eAAsB,WAAW,QAAsB,EAAE,EAAiB;AAExE,KAAI,EADc,CAAC,MAAM,YAAY,QAAQ,MAAM,UAAU,MAC7C,QAAO,qBAAqB,MAAM;AAClD,OAAM,sBAAsB,MAAM;;AAGpC,SAAS,aAAa,OAA4E;CAChG,MAAM,WAAW,iBAAiB,EAAE,MAAM,MAAM,aAAa,CAAC;AAC9D,QAAO;EAAE;EAAU,OAAO,SAAS,SAAS,IAAI;EAAE;;AAGpD,SAAS,iBAAiB,OAAyC;AAKjE,QAAO,EAAE,eAAe,MAAM,WAAW;;AAG3C,eAAe,sBAAsB,OAAoC;AACvE,OAAM,GAAG,OAAO,GAAG,MAAM,kCAAkC,CAAC,CAAC;AAK7D,6BAA4B;CAE5B,MAAM,EAAE,UAAU,UAAU,aAAa,MAAM;AAC/C,KAAI,KAAK,qBAAqB,GAAG,IAAI,MAAM,UAAU,CAAC,GAAG,GAAG,IAAI,YAAY,SAAS,OAAO,GAAG,GAAG;AAKlG,OAAM,oBAAoB;CAK1B,MAAM,mBAAmB,4BAA4B,MAAM,aAAa;AACxE,KAAI,qBAAqB,KAAA,EACvB,KAAI,qBAAqB,gBACvB,KAAI,KAAK,GAAG,UAAU,GAAG,GAAG,IAAI,iBAAiB,CAAC,gDAAgD;KAElG,KAAI,KAAK,aAAa,UAAU,QAAQ,GAAG,IAAI,iBAAiB,CAAC,KAAK,GAAG,KAAK,gBAAgB,GAAG;CAKrG,MAAM,iBADW,aAAa,MAAM,aAAa,CAErC,SAAS,UAAA,sCAAsB,UAA+C,KAAA;CAC1F,MAAM,YAAY,MAAM,eAAe;CACvC,MAAM,OAAO,QAAQ,UAAU;CAE/B,MAAM,aAAa;EACjB;EACA;EACA;EACA;EACA,GAAG,GAAG,IAAI,OAAO,CAAC,KAAK,GAAG,KAAK,SAAS;EACzC;AACD,KAAI,UAAU,SAAS,aACrB,YAAW,KAAK,IAAI,GAAG,OAAO,SAAS,UAAU,MAAM,gBAAgB,UAAU,OAAO,GAAG,CAAC;AAE9F,MAAK,WAAW,KAAK,KAAK,EAAE,QAAQ;AAEpC,KAAI,KAAK,uBAAuB,GAAG,KAAK,KAAK,SAAS,GAAG;CAEzD,MAAM,UAAU,MAAM,cAAc,gBAAgB,SAAS,MAAM,QAAQ;AAC3E,KAAI,KAAK,qBAAqB,GAAG,KAAK,KAAK,QAAQ,QAAQ,CAAC,GAAG;AAG/D,OAAM,aAAa;EACjB,QAHa,MAAM,aAAa,gBAAgB,QAAQ,MAAM,OAAO;EAIrE;EACA;EACA,yBAAyB,MAAM;EAC/B,SAAS,MAAM,YAAY;EAC3B;EACA,QAAQ,MAAM,WAAW;EAC1B,CAAC;AAEF,KAAI,CAAC,MAAM,OACT,OAAM,oBAAoB,MAAM,WAAW,QAAQ,MAAM;AAG3D,MACE,CACE,MAAM,SAAS,wCAAwC,oCACvD,wBAAwB,GAAG,KAAK,KAAK,YAAY,QAAQ,CAAC,GAC3D,CAAC,KAAK,KAAK,EACZ,YACD;AACD,OAAM,MAAM,SAAS,GAAG,IAAI,YAAY,GAAG,GAAG,MAAM,cAAc,CAAC;;AAGrE,eAAe,qBAAqB,OAAoC;AACtE,6BAA4B;CAC5B,MAAM,EAAE,UAAU,UAAU,aAAa,MAAM;AAC/C,SAAQ,OAAO,MAAM,qBAAqB,MAAM,UAAU,YAAY,SAAS,OAAO,KAAK;AAC3F,OAAM,oBAAoB;CAE1B,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,MAAM;AACtB,KAAI,CAAC,UAAU,CAAC,QACd,OAAM,IAAI,MAAM,sFAAsF;AAGxG,OAAM,aAAa;EACjB;EACA;EACA,WAJgB,MAAM,eAAe;EAKrC,yBAAyB,MAAM;EAC/B,SAAS,MAAM,YAAY;EAC3B;EACA,QAAQ,MAAM,WAAW;EAC1B,CAAC;AACF,KAAI,MAAM,QAAQ;AAChB,UAAQ,OAAO,MAAM,wCAAwC;AAC7D;;AAEF,OAAM,oBAAoB,MAAM,WAAW,QAAQ,MAAM;AACzD,SAAQ,OAAO,MAAM,gCAAgC,MAAM,aAAa,IAAI;;AAG9E,eAAe,aAAa,WAA+B,MAA2C;AACpG,KAAI,KAAM,QAAO;CACjB,MAAM,SAAS,MAAM,SAAS;EAC5B,SAAS;EACT,MAAM;EACN,WAAW,MAAO,KAAK,EAAE,SAAS,IAAI,KAAA,IAAY;EACnD,CAAC;AACF,KAAI,SAAS,OAAO,CAAE,QAAO,UAAU;AACvC,QAAO;;AAGT,eAAe,cAAc,UAA8B,MAA2C;AACpG,KAAI,KAAM,QAAO;CACjB,MAAM,SAAS,MAAM,KAAK;EACxB,SAAS;EACT,aAAa,YAAY;EACzB,GAAI,WAAW,EAAE,cAAc,UAAU,GAAG,EAAE;EAC9C,WAAW,MAAO,KAAK,EAAE,SAAS,IAAI,KAAA,IAAY;EACnD,CAAC;AACF,KAAI,SAAS,OAAO,CAAE,QAAO,UAAU;AACvC,QAAO;;AAGT,SAAS,WAAkB;AACzB,QAAO,kCAAkC;AACzC,SAAQ,KAAK,EAAE;;AAcjB,eAAe,aAAa,EAC1B,QACA,SACA,WACA,yBACA,SACA,OACA,UAC6B;CAC7B,MAAM,cAAc,GAAG,qBAAqB,GAAG;CAI/C,MAAM,SAAS,aAAa,MAAM,aAAa;CAC/C,MAAM,QAAQ,gBAAgB,OAAO;AACrC,sBAAqB,MAAM;AAC3B,gBAAe,OAAO;EACpB;EACA;EACA,SAAS,UAAU,SAAS,eAAe,KAAA,IAAY,UAAU;EACjE;EACD,CAAC;AACF,KAAI,CAAC,QAAS,mBAAkB,MAAM;AAEtC,KAAI,QAAQ;AACV,MAAI,KAAK,cAAc,GAAG,IAAI,4BAA4B,YAAY,UAAU,GAAG;EACnF,MAAM,OAAO,SAAS,QAAQ,OAAO;GAAE,WAAW,MAAM;GAAc,SAAS,GAAG,MAAM,aAAa;GAAc,CAAC;AACpH,MAAI,KAAK,SAAS,EAChB,SAAQ,OAAO,MAAM,GAAG,KAAK,IAAI;MAEjC,KAAI,KAAK,6DAA6D;AAExE;;AAQF,mBAAkB,MAAM;AACxB,gBAAe,MAAM,cAAc,MAAM,mBAAmB;CAS5D,MAAM,iBAAiB,SAAS;AAChC,gBAAe,MAAM,kDAAkD,cAAc;CACrF,MAAM,gBAAgB,YAAY;EAAC;EAAW;EAAW;EAAa;EAAU,EAAE;EAChF,WAAW;EACX,KAAK,iBAAiB,MAAM;EAC7B,CAAC;AACF,KAAI,CAAC,cAAc,IAAI;AACrB,iBAAe,KAAK,kCAAkC;AACtD,MAAI,cAAc,WAAW,SAC3B,OAAM,IAAI,MAAM,iGAAiG;AAEnH,MAAI,cAAc,WAAW,UAC3B,OAAM,IAAI,MAAM,6FAA6F;EAE/G,MAAM,SAAS,cAAc,OAAO,MAAM,IAAI,cAAc,OAAO,MAAM,IAAI,aAAa,cAAc;AACxG,QAAM,IAAI,MAAM,oCAAoC,SAAS;;AAE/D,gBAAe,KAAK,kCAAkC;CAetD,MAAM,sBAAsB,aAAa,MAAM,aAAa;CAE5D,MAAM,kBAAkB,SAAS;AACjC,iBAAgB,MAAM,yBAAyB;CAC/C,MAAM,WAAW,aAAa,MAAM,aAAa;AACjD,sBAAqB,SAAS;AAC9B,gBAAe,UAAU;EACvB;EACA;EACA,SAAS,UAAU,SAAS,eAAe,KAAA,IAAY,UAAU;EACjE;EAID,CAAC;AACF,KAAI,CAAC,QACH,mBAAkB,SAAS;AAE7B,eAAc,MAAM,cAAc,SAAS;AAC3C,iBAAgB,KAAK,WAAW,MAAM,eAAe;AACrD,KAAI,WAAW,MAAM,mBAAmB,CAAE,KAAI,KAAK,mBAAmB,GAAG,IAAI,MAAM,mBAAmB,GAAG;AACzG,KAAI,QACF,KAAI,QACF,kEAAkE,UAAU,oEAC7E;CAOH,MAAM,kBAAkB,SAAS;AACjC,iBAAgB,MAAM,2BAA2B;CACjD,MAAM,iBAAiB,YAAY;EAAC;EAAU;EAAY;EAAS,EAAE;EACnE,WAAW;EACX,KAAK,iBAAiB,MAAM;EAC7B,CAAC;AACF,KAAI,CAAC,eAAe,MAAM,uBAAuB,eAAe,OAAO,EAAE;AACvE,kBAAgB,KAAK,kCAAkC;EACvD,MAAM,WAAW,iBAAiB,OAAO,oBAAoB;EAC7D,MAAM,SACJ,eAAe,OAAO,QAClB,eAAe,OAAO,MAAM,IAAI,eAAe,OAAO,MAAM,IAAI,aAAa,eAAe,SAC5F,eAAe,OAAO,MAAM,IAAI;AACtC,QAAM,IAAI,MAAM,qEAAqE,OAAO,IAAI,WAAW;;AAE7G,iBAAgB,KAAK,eAAe;;;;;;;;;;;;;;;;;AAkBtC,SAAS,iBAAiB,OAAsB,qBAA+C;AAC7F,KAAI,WAAW,MAAM,mBAAmB,CAEtC,QADiB,cAAc,MAAM,cAAc,MAAM,mBAAmB,GAExE,wBAAwB,MAAM,mBAAmB,oDACjD,aAAa,MAAM,mBAAmB;AAI5C,KAAI;AACF,gBAAc,MAAM,cAAc,oBAAoB;AACtD,SACE,GAAG,MAAM,aAAa,mIACkD,UAAU;UAG7E,KAAK;AACZ,SACE,sBAAsB,MAAM,aAAa,IAAI,OAAO,IAAI,CAAC,sCACrB,UAAU;;;;AAMpD,SAAS,uBAAuB,QAAyB;CACvD,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI;AAEF,SADe,KAAK,MAAM,QAAQ,CACpB,UAAU;SAClB;AAEN,SAAO;;;AAIX,SAAS,kBAAkB,OAA4B;CACrD,MAAM,MAAM,QAAQ,MAAM,aAAa;AACvC,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;;;;;;;;AAS3D,SAAS,6BAAmC;CAC1C,MAAM,IAAI,oBAAoB;AAC9B,KAAI,CAAC,EAAE,IAAI;AACT,MAAI,EAAE,UAAU,WAAW;AACzB,UAAO,qGAAqG;AAC5G,WAAQ,KAAK,EAAE;;AAEjB,SACE,yCAAyC,EAAE,MAAM,UAAU,GAAG,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,qDAClF;AACD,UAAQ,KAAK,EAAE;;AAEjB,KAAI,cAAc,EAAE,SAAA,YAA8B,GAAG,GAAG;AACtD,SACE,YAAY,EAAE,QAAQ,gDAAgD,qBAAqB,+DAC5F;AACD,UAAQ,KAAK,EAAE;;AAEjB,KAAI,KAAK,YAAY,GAAG,IAAI,EAAE,QAAQ,CAAC,OAAO,qBAAqB,GAAG;;;;;;;;;;;;;AAcxE,eAAe,qBAAoC;CACjD,MAAM,MAAM,8BAA8B,mBAAmB,qBAAqB,CAAC,GAAG,mBAAmB,gBAAgB;AACzH,KAAI;EACF,MAAM,MAAM,MAAM,MAAM,KAAK;GAAE,QAAQ;GAAO,QAAQ,YAAY,QAAQ,IAAM;GAAE,CAAC;AACnF,MAAI,IAAI,WAAW,KAAK;AACtB,UACE,OAAO,gBAAgB,CAAC,WAAW,qBAAqB,GAAG,gBAAgB,wHAE5E;AACD,WAAQ,KAAK,EAAE;;AAEjB,MAAI,CAAC,IAAI,GACP,KAAI,QAAQ,oCAAoC,IAAI,OAAO,eAAe;UAErE,KAAK;AAGZ,MAAI,QAAQ,yCAAyC,qBAAqB,GAAG,gBAAgB,IAAI,OAAO,IAAI,GAAG;;;;;;;;;;;;;AAcnH,eAAe,oBAAoB,MAAkC,OAAqC;AACxG,KAAI,SAAS,SAAS;AACpB,MAAI,KAAK,wFAAwF;AACjG;;CAGF,IAAI;AACJ,KAAI,SAAS,QACX,iBAAgB;MACX;AAEL,MAAI,QAAQ,MAAM,UAAU,MAAM;AAChC,OAAI,KAAK,uDAAuD;AAChE;;EAEF,MAAM,YAAY,MAAM,QAAQ;GAC9B,SAAS;GACT,cAAc;GACf,CAAC;AACF,MAAI,SAAS,UAAU,IAAI,cAAc,MAAM;AAC7C,OAAI,KAAK,+CAA+C;AACxD;;AAEF,kBAAgB;;AAGlB,KAAI,CAAC,cAAe;AAEpB,KAAI,QAAQ,yDAAyD;CACrE,MAAM,iBAAiB,SAAS;AAChC,gBAAe,MAAM,2BAA2B;CAChD,MAAM,gBAAgB,YAAY,CAAC,WAAW,UAAU,EAAE;EACxD,WAAW;EACX,KAAK,iBAAiB,MAAM;EAC7B,CAAC;AACF,KAAI,CAAC,cAAc,IAAI;AACrB,iBAAe,KAAK,yBAAyB;EAC7C,MAAM,SAAS,cAAc,OAAO,MAAM,IAAI,cAAc,OAAO,MAAM,IAAI,aAAa,cAAc;AACxG,MAAI,QAAQ,oCAAoC,OAAO,mCAAmC;AAC1F;;AAEF,gBAAe,KAAK,oBAAoB;;AAW1C,SAAgB,wBAAwB,OAAyD;CAC/F,IAAI,UAAsC;AAC1C,KAAI,MAAM,kBAAkB,KAAM,WAAU;AAC5C,KAAI,MAAM,YAAY,MAAM;AAC1B,MAAI,MAAM,kBAAkB,KAAM,OAAM,IAAI,MAAM,oDAAoD;AACtG,YAAU;;AAEZ,QAAO;EACL,UAAU,MAAM,iBAAiB,QAAQ,MAAM,QAAQ;EACvD,aAAa,OAAO,MAAM,oBAAoB,WAAW,MAAM,kBAAkB,KAAA;EACjF;EACD;;AAGH,eAAsB,aAAa,QAAwB,EAAE,EAAiB;AAC5E,OAAM,GAAG,SAAS,GAAG,MAAM,8CAA8C,CAAC,CAAC;CAE3E,MAAM,WAAW,iBAAiB,EAAE,MAAM,MAAM,aAAa,CAAC;CAC9D,MAAM,QAAQ,SAAS,SAAS,IAAI;AACpC,KAAI,KAAK,qBAAqB,GAAG,IAAI,MAAM,UAAU,CAAC,GAAG,GAAG,IAAI,YAAY,SAAS,OAAO,GAAG,GAAG;AASlG,KAAI,CANa,kBADA,aAAa,MAAM,aAAa,CACL,EAM7B;AACb,OAAK,uDAAuD,SAAS;AACrE,QAAM,GAAG,IAAI,kBAAkB,CAAC;AAChC;;AAQF,MALa;EACX,oCAAoC,UAAU;EAC9C;EACA,oCAAoC,MAAM;EAC3C,CACS,KAAK,KAAK,EAAE,OAAO;AAE7B,KAAI,CAAC,MAAM,UAAU;AACnB,MAAI,QAAQ,MAAM,UAAU,KAO1B,OAAM,IAAI,MACR,wHACD;EAEH,MAAM,KAAK,MAAM,QAAQ;GAAE,SAAS;GAAY,cAAc;GAAM,CAAC;AACrE,MAAI,SAAS,GAAG,IAAI,OAAO,KAAM,QAAO,UAAU;;AAOpD,gBAAe,MAAM,cAAc,MAAM,mBAAmB;CAK5D,MAAM,IAAI,SAAS;AACnB,GAAE,MAAM,2CAA2C;CACnD,MAAM,kBAAkB,YAAY;EAAC;EAAW;EAAa;EAAW;EAAU,EAAE;EAClF,WAAW;EACX,KAAK,iBAAiB,MAAM;EAC7B,CAAC;AACF,KAAI,CAAC,gBAAgB,IAAI;AACvB,IAAE,KAAK,oCAAoC;AAC3C,MAAI,gBAAgB,WAAW,SAC7B,KAAI,QACF,6GACD;OACI;GACL,MAAM,SACJ,gBAAgB,OAAO,MAAM,IAAI,gBAAgB,OAAO,MAAM,IAAI,aAAa,gBAAgB;AACjG,OAAI,QAAQ,wCAAwC,OAAO,kCAAkC;;OAG/F,GAAE,KAAK,6BAA6B;CAOtC,MAAM,iBAAiB,SAAS;AAChC,gBAAe,MAAM,0BAA0B;CAC/C,MAAM,OAAO,aAAa,MAAM,aAAa;AAC7C,mBAAkB,KAAK;AACvB,wBAAuB,KAAK;AAC5B,sBAAqB,KAAK;AAC1B,eAAc,MAAM,cAAc,KAAK;AACvC,gBAAe,KAAK,OAAO;AAE3B,OAAM,oBAAoB,MAAM,WAAW,QAAQ,MAAM;AAEzD,OAAM,GAAG,MAAM,gBAAgB,CAAC;;;;AC7uBlC,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8Bd,eAAe,OAAsB;CACnC,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAGlC,KAAI,KAAK,OAAO,eAAe,KAAK,OAAO,MAAM;AAC/C,UAAQ,OAAO,MAAM,GAAG,gBAAgB,CAAC,IAAI;AAC7C;;AAEF,KAAI,KAAK,OAAO,YAAY,KAAK,OAAO,MAAM;AAC5C,UAAQ,OAAO,MAAM,MAAM;AAC3B;;CAGF,MAAM,EAAE,YAAY,UAAU,WAAW,KAAK;AAC9C,KAAI,eAAe,aAAa,eAAe,KAAA,GAAW;AACxD,QAAM,WAAW,sBAAsB,MAAM,CAAC;AAC9C;;AAEF,KAAI,eAAe,aAAa;AAC9B,QAAM,aAAa,wBAAwB,MAAM,CAAC;AAClD;;AAEF,SAAQ,OAAO,MAAM,uBAAuB,WAAW,IAAI;AAC3D,SAAQ,OAAO,MAAM,MAAM;AAC3B,SAAQ,KAAK,EAAE;;AAGjB,MAAM,CAAC,OAAO,QAAQ;AACpB,SAAQ,OAAO,MAAM,GAAG,OAAO,IAAI,CAAC,IAAI;AACxC,SAAQ,KAAK,EAAE;EACf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latitude-data/openclaw-telemetry-cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
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",
@@ -32,8 +32,8 @@
32
32
  }
33
33
  },
34
34
  "dependencies": {
35
- "@clack/prompts": "^1.2.0",
36
- "picocolors": "^1.1.1"
35
+ "@clack/prompts": "1.4.0",
36
+ "picocolors": "1.1.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@biomejs/biome": "2.4.6",
@@ -41,7 +41,7 @@
41
41
  "tsdown": "0.21.9",
42
42
  "@typescript/native-preview": "7.0.0-dev.20260421.2",
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"