@latitude-data/openclaw-telemetry 0.0.2 → 0.0.3
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/dist/cli.js +50 -15
- package/dist/cli.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -1
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { cancel, confirm, intro, isCancel, log, note, outro, password, spinner, text } from "@clack/prompts";
|
|
5
6
|
import pc from "picocolors";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
//#region src/settings-file.ts
|
|
9
9
|
const CONFIG_DIR = join(homedir(), ".openclaw");
|
|
@@ -128,6 +128,7 @@ function installPluginFiles() {
|
|
|
128
128
|
const pkgSrc = join(packageRoot, "package.json");
|
|
129
129
|
if (!existsSync(manifestSrc)) throw new Error(`Cannot install: missing openclaw.plugin.json at ${manifestSrc}. This is a packaging bug — please file an issue.`);
|
|
130
130
|
if (!existsSync(distSrc)) throw new Error(`Cannot install: missing compiled dist at ${distSrc}.`);
|
|
131
|
+
if (!existsSync(pkgSrc)) throw new Error(`Cannot install: missing package.json at ${pkgSrc}. This is a packaging bug — please file an issue.`);
|
|
131
132
|
if (existsSync(PLUGIN_INSTALL_DIR)) rmSync(PLUGIN_INSTALL_DIR, {
|
|
132
133
|
recursive: true,
|
|
133
134
|
force: true
|
|
@@ -135,20 +136,20 @@ function installPluginFiles() {
|
|
|
135
136
|
mkdirSync(PLUGIN_INSTALL_DIR, { recursive: true });
|
|
136
137
|
copyFileSync(manifestSrc, join(PLUGIN_INSTALL_DIR, "openclaw.plugin.json"));
|
|
137
138
|
cpSync(distSrc, join(PLUGIN_INSTALL_DIR, "dist"), { recursive: true });
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
}
|
|
139
|
+
const sourcePkg = JSON.parse(readFileSync(pkgSrc, "utf-8"));
|
|
140
|
+
const extensions = sourcePkg.openclaw?.extensions ?? ["./dist/plugin.js"];
|
|
141
|
+
const minimalPkg = {
|
|
142
|
+
name: sourcePkg.name,
|
|
143
|
+
version: sourcePkg.version,
|
|
144
|
+
type: sourcePkg.type ?? "module",
|
|
145
|
+
main: sourcePkg.main ?? "./dist/plugin.js",
|
|
146
|
+
private: true,
|
|
147
|
+
openclaw: { extensions }
|
|
148
|
+
};
|
|
149
|
+
writeFileSync(join(PLUGIN_INSTALL_DIR, "package.json"), `${JSON.stringify(minimalPkg, null, 2)}\n`, "utf-8");
|
|
149
150
|
return {
|
|
150
151
|
destination: PLUGIN_INSTALL_DIR,
|
|
151
|
-
entryPath:
|
|
152
|
+
entryPath: resolve(PLUGIN_INSTALL_DIR, extensions[0] ?? "./dist/plugin.js")
|
|
152
153
|
};
|
|
153
154
|
}
|
|
154
155
|
/** Remove the materialized plugin directory under `~/.openclaw/extensions/`. */
|
|
@@ -358,8 +359,42 @@ async function runUninstall(flags = {}) {
|
|
|
358
359
|
}
|
|
359
360
|
//#endregion
|
|
360
361
|
//#region src/cli.ts
|
|
362
|
+
const USAGE = `usage: latitude-openclaw <command> [options]
|
|
363
|
+
|
|
364
|
+
commands:
|
|
365
|
+
install Install the plugin (interactive when stdin is a TTY)
|
|
366
|
+
uninstall Remove the plugin entry and files
|
|
367
|
+
--version, -v Print the package version
|
|
368
|
+
--help, -h Print this message
|
|
369
|
+
|
|
370
|
+
install options:
|
|
371
|
+
--api-key=<key> Pass the API key non-interactively
|
|
372
|
+
--project=<slug> Pass the project slug non-interactively
|
|
373
|
+
--staging Target https://staging.latitude.so / staging-ingest
|
|
374
|
+
--dev Target http://localhost:3000 / 3002
|
|
375
|
+
--no-content Skip raw prompt/response/tool I/O capture
|
|
376
|
+
--allow-conversation Force conversation capture on (overrides existing config)
|
|
377
|
+
--yes / --no-prompt Skip all prompts (required for non-TTY / CI)
|
|
378
|
+
`;
|
|
379
|
+
function readVersion() {
|
|
380
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
381
|
+
try {
|
|
382
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
383
|
+
} catch {
|
|
384
|
+
return "unknown";
|
|
385
|
+
}
|
|
386
|
+
}
|
|
361
387
|
async function main() {
|
|
362
|
-
const
|
|
388
|
+
const argv = process.argv.slice(2);
|
|
389
|
+
if (argv[0] === "--version" || argv[0] === "-v") {
|
|
390
|
+
process.stdout.write(`${readVersion()}\n`);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
394
|
+
process.stdout.write(USAGE);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const { subcommand, flags } = parseFlags(argv);
|
|
363
398
|
if (subcommand === "install" || subcommand === void 0) {
|
|
364
399
|
await runInstall(normalizeInstallFlags(flags));
|
|
365
400
|
return;
|
|
@@ -369,7 +404,7 @@ async function main() {
|
|
|
369
404
|
return;
|
|
370
405
|
}
|
|
371
406
|
process.stderr.write(`unknown subcommand: ${subcommand}\n`);
|
|
372
|
-
process.stderr.write(
|
|
407
|
+
process.stderr.write(USAGE);
|
|
373
408
|
process.exit(1);
|
|
374
409
|
}
|
|
375
410
|
main().catch((err) => {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/settings-file.ts","../src/install-files.ts","../src/setup.ts","../src/cli.ts"],"sourcesContent":["import { copyFileSync, existsSync, readFileSync, writeFileSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nconst CONFIG_DIR = join(homedir(), \".openclaw\")\nexport const SETTINGS_PATH = join(CONFIG_DIR, \"openclaw.json\")\nexport const SETTINGS_BACKUP_PATH = join(CONFIG_DIR, \"openclaw.json.latitude-bak\")\nconst EXTENSIONS_DIR = join(CONFIG_DIR, \"extensions\")\nexport const PLUGIN_INSTALL_DIR = join(EXTENSIONS_DIR, \"latitude-telemetry\")\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 deliberately keep this minimal — only the\n * fields we actually write — so we don't drop anything when round-tripping\n * an existing entry that uses fields we don't know about.\n */\ninterface OpenClawPluginEntry {\n enabled?: boolean\n /**\n * The free-form bucket OpenClaw passes to the plugin at activation. This is\n * where we store all our credentials and feature flags — `hooks` is strict,\n * `env` at root is not a free-form key/value passthrough, and using\n * `.config` keeps everything namespaced to our plugin and survives any\n * version of OpenClaw because the field is a `record(string, unknown)`.\n */\n config?: Record<string, unknown>\n // Pass through anything else that may already be there — `hooks`, `subagent`, etc.\n [key: string]: unknown\n}\n\nexport interface OpenClawSettings {\n plugins?: {\n enabled?: boolean\n entries?: Record<string, OpenClawPluginEntry>\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(): OpenClawSettings {\n if (!existsSync(SETTINGS_PATH)) return {}\n try {\n const raw = readFileSync(SETTINGS_PATH, \"utf-8\")\n const parsed = JSON.parse(raw) as OpenClawSettings\n return parsed && typeof parsed === \"object\" ? parsed : {}\n } catch {\n return {}\n }\n}\n\nexport function writeSettings(settings: OpenClawSettings): void {\n writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\\n`, \"utf-8\")\n}\n\nexport function backupSettings(): void {\n if (existsSync(SETTINGS_PATH)) copyFileSync(SETTINGS_PATH, SETTINGS_BACKUP_PATH)\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; `undefined` preserves the existing value (or\n * leaves the key absent if there is none).\n */\n allowConversationAccess?: boolean | undefined\n /** Same semantics as `allowConversationAccess`. */\n debug?: boolean | undefined\n /**\n * `true`/`false` overwrites; `undefined` preserves the existing value, or\n * defaults to `true` for a fresh install. This keeps a paused plugin\n * (`enabled: false` 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. Writes credentials and\n * options into the `.config` bucket — never under `hooks` (strict zod) and\n * never as top-level `env` keys (root schema rejects them).\n *\n * Re-install idempotency: only `apiKey` / `project` / `baseUrl` always\n * overwrite (these come from the install prompts). `enabled`, `debug`, and\n * `allowConversationAccess` are preserved when not provided in the patch,\n * so a user who hand-edited `enabled: false` or `debug: true` doesn't lose\n * their choice on a re-install.\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\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 // Preserve user-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 config: nextConfig,\n }\n plugins.entries = entries\n settings.plugins = plugins\n}\n\n/** Remove the plugin entry entirely. */\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\nexport function hasLatitudePlugin(settings: OpenClawSettings): boolean {\n return Boolean(settings.plugins?.entries && PLUGIN_ID in settings.plugins.entries)\n}\n\n/**\n * Strip any leftover top-level keys our older installer (<= 0.0.1) wrote. The\n * 0.0.1 installer wrote `hooks.allowConversationAccess` (rejected by strict\n * zod) and `LATITUDE_*` keys at root-level `env` (also rejected). Both are\n * cleaned up here so re-running install lands a config OpenClaw will validate.\n */\nexport function migrateLegacyEntries(settings: OpenClawSettings): { changed: boolean } {\n let changed = false\n\n // Wipe `hooks.allowConversationAccess` from our entry — OpenClaw < 2026.4.22\n // rejects unknown keys under the strict `hooks` shape.\n const entry = settings.plugins?.entries?.[PLUGIN_ID]\n if (entry && typeof entry === \"object\") {\n const hooks = (entry as { hooks?: Record<string, unknown> }).hooks\n if (hooks && typeof hooks === \"object\" && \"allowConversationAccess\" in hooks) {\n delete hooks.allowConversationAccess\n // If the hooks object is now empty, drop it entirely so we don't leave\n // a `\"hooks\": {}` carcass behind.\n if (Object.keys(hooks).length === 0) {\n delete (entry as { hooks?: unknown }).hooks\n }\n changed = true\n }\n }\n\n // Strip `LATITUDE_*` keys our 0.0.1 installer mistakenly wrote under\n // `settings.env`. OpenClaw's root schema is strict; the `env` block accepts\n // only `{shellEnv, vars}`, so any `LATITUDE_*` key sitting directly under\n // `env` causes the gateway to quarantine the file. (0.0.1 only ever wrote\n // to `settings.env.LATITUDE_*`, never to top-level `settings.LATITUDE_*`,\n // so we don't bother sweeping the root.)\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 if there's nothing left in it AND it's not OpenClaw's\n // canonical {shellEnv, vars} shape — only do this when we know it was ours.\n if (Object.keys(envObj).length === 0) {\n delete settings.env\n }\n }\n\n return { changed }\n}\n","import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\"\nimport { dirname, join, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { PLUGIN_INSTALL_DIR } from \"./settings-file.ts\"\n\n/**\n * Copies the plugin's runtime files into `~/.openclaw/extensions/latitude-telemetry/`\n * so OpenClaw's discovery (`<configDir>/extensions/<plugin>`) picks it up.\n *\n * `npx -y @latitude-data/openclaw-telemetry` runs the CLI from a temporary npm\n * location — relying on that path persisting across runs (or even across the\n * gateway restart that follows install) is unsafe. So we materialize a stable\n * copy under the user's OpenClaw config dir, the same path OpenClaw scans on\n * startup. Layout we produce:\n *\n * ~/.openclaw/extensions/latitude-telemetry/\n * openclaw.plugin.json <- the manifest, required by discovery\n * package.json <- minimal — keeps node module-resolution happy\n * dist/ <- compiled plugin entrypoint(s)\n */\nexport function installPluginFiles(): { destination: string; entryPath: string } {\n // The CLI runs from `dist/cli.js`. Resolve the package root from there.\n const here = dirname(fileURLToPath(import.meta.url))\n const packageRoot = resolve(here, \"..\") // dist/cli.js -> dist/ -> package root\n\n const manifestSrc = join(packageRoot, \"openclaw.plugin.json\")\n const distSrc = join(packageRoot, \"dist\")\n const pkgSrc = join(packageRoot, \"package.json\")\n\n if (!existsSync(manifestSrc)) {\n throw new Error(\n `Cannot install: missing openclaw.plugin.json at ${manifestSrc}. This is a packaging bug — please file an issue.`,\n )\n }\n if (!existsSync(distSrc)) {\n throw new Error(`Cannot install: missing compiled dist at ${distSrc}.`)\n }\n\n // Wipe and recreate so a re-install isn't polluted by a previous version's\n // files. Idempotent.\n if (existsSync(PLUGIN_INSTALL_DIR)) rmSync(PLUGIN_INSTALL_DIR, { recursive: true, force: true })\n mkdirSync(PLUGIN_INSTALL_DIR, { recursive: true })\n\n copyFileSync(manifestSrc, join(PLUGIN_INSTALL_DIR, \"openclaw.plugin.json\"))\n cpSync(distSrc, join(PLUGIN_INSTALL_DIR, \"dist\"), { recursive: true })\n\n // Write a minimal package.json so anything that runs `require(\"./dist/plugin.js\")`\n // from a `type: module` context still resolves cleanly (we mirror the source\n // package's `type` and `main` fields).\n if (existsSync(pkgSrc)) {\n const sourcePkg = JSON.parse(readFileSync(pkgSrc, \"utf-8\")) as {\n name?: string\n version?: string\n type?: string\n main?: string\n }\n const minimalPkg = {\n name: sourcePkg.name,\n version: sourcePkg.version,\n type: sourcePkg.type ?? \"module\",\n main: sourcePkg.main ?? \"./dist/plugin.js\",\n private: true,\n }\n writeFileSync(join(PLUGIN_INSTALL_DIR, \"package.json\"), `${JSON.stringify(minimalPkg, null, 2)}\\n`, \"utf-8\")\n }\n\n return {\n destination: PLUGIN_INSTALL_DIR,\n entryPath: join(PLUGIN_INSTALL_DIR, \"dist\", \"plugin.js\"),\n }\n}\n\n/** Remove the materialized plugin directory under `~/.openclaw/extensions/`. */\nexport function removePluginFiles(): boolean {\n if (!existsSync(PLUGIN_INSTALL_DIR)) return false\n rmSync(PLUGIN_INSTALL_DIR, { recursive: true, force: true })\n return true\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 { installPluginFiles, removePluginFiles } from \"./install-files.ts\"\nimport {\n backupSettings,\n hasLatitudePlugin,\n type LatitudePluginConfig,\n migrateLegacyEntries,\n PLUGIN_ID,\n PLUGIN_INSTALL_DIR,\n readSettings,\n removePluginEntry,\n SETTINGS_BACKUP_PATH,\n SETTINGS_PATH,\n setPluginEntry,\n writeSettings,\n} from \"./settings-file.ts\"\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 (the user passed `--allow-conversation`),\n * `false` = scrub (the user passed `--no-content`), `undefined` = preserve\n * existing or fall through to the first-install default. Keeping this\n * tristate is what makes re-installs idempotent for hand-edited values.\n */\n allowConversationAccess?: boolean | undefined\n noPrompt?: boolean\n yes?: boolean\n}\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 (const arg of rest) {\n if (!arg.startsWith(\"--\")) continue\n const eq = arg.indexOf(\"=\")\n if (eq >= 0) {\n flags[arg.slice(2, eq)] = arg.slice(eq + 1)\n } else {\n flags[arg.slice(2)] = 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\n // the other. Re-install then preserves whatever's 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 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 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\nasync function runInteractiveInstall(flags: InstallFlags): Promise<void> {\n intro(pc.bgCyan(pc.black(\" Latitude · OpenClaw telemetry \")))\n\n const existing = readSettings()\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 })\n\n note(\n [\n \"Restart the OpenClaw gateway for the plugin to load:\",\n pc.dim(\" openclaw gateway restart\"),\n \"\",\n `View your traces at ${pc.cyan(urls.projectView(project))}`,\n ].join(\"\\n\"),\n \"Next step\",\n )\n outro(pc.green(\"✓ Installed\"))\n}\n\nasync function runFlagDrivenInstall(flags: InstallFlags): Promise<void> {\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 })\n process.stdout.write(`Installed Latitude plugin in ${SETTINGS_PATH}\\n`)\n process.stdout.write(`Plugin files at ${PLUGIN_INSTALL_DIR}\\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}\n\nasync function applyChanges({ apiKey, project, envConfig, allowConversationAccess }: ApplyParams): Promise<void> {\n // 1. Materialize plugin runtime files into ~/.openclaw/extensions/latitude-telemetry/\n // so OpenClaw's plugin discovery picks them up. This MUST happen before\n // we write the openclaw.json entry, otherwise the gateway file-watcher\n // will see the entry, fail to find the plugin, and emit a warning.\n const filesSpinner = spinner()\n filesSpinner.start(\"Installing plugin files\")\n const { destination } = installPluginFiles()\n filesSpinner.stop(`Plugin files installed at ${destination}`)\n\n // 2. Update openclaw.json with the plugin entry.\n const settingsSpinner = spinner()\n settingsSpinner.start(\"Updating openclaw.json\")\n ensureSettingsDir()\n backupSettings()\n const settings = readSettings()\n // Migrate any leftover keys our 0.0.1 installer wrote that the strict zod\n // schema rejects. Without this, re-installing on top of a 0.0.1 install\n // would leave the gateway quarantining the file as `clobbered`.\n migrateLegacyEntries(settings)\n\n // Decide allowConversationAccess for this install:\n // - explicit flag (true|false) wins\n // - else preserve whatever's already in openclaw.json\n // - else first-install default is `true` (matches the README's promise)\n const existingConfig = (settings.plugins?.entries?.[PLUGIN_ID]?.config ?? {}) as Partial<LatitudePluginConfig>\n const finalAllowConversationAccess = allowConversationAccess ?? existingConfig.allowConversationAccess ?? true\n\n setPluginEntry(settings, {\n apiKey,\n project,\n baseUrl: envConfig.name === \"production\" ? undefined : envConfig.ingest,\n allowConversationAccess: finalAllowConversationAccess,\n // `debug` is intentionally not passed — `setPluginEntry` preserves the\n // user's hand-edited value. Fresh installs leave the key absent (the\n // runtime default is `false`).\n })\n writeSettings(settings)\n settingsSpinner.stop(`Updated ${SETTINGS_PATH}`)\n if (existsSync(SETTINGS_BACKUP_PATH)) log.info(`Backup saved at ${pc.dim(SETTINGS_BACKUP_PATH)}`)\n}\n\nfunction ensureSettingsDir(): void {\n const dir = dirname(SETTINGS_PATH)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n}\n\n// ─── Uninstall ──────────────────────────────────────────────────────────────\n\ninterface UninstallFlags {\n noPrompt?: boolean\n}\n\nexport async function runUninstall(flags: UninstallFlags = {}): Promise<void> {\n intro(pc.bgYellow(pc.black(\" Latitude · OpenClaw telemetry — uninstall \")))\n const settings = readSettings()\n\n const hasEntry = hasLatitudePlugin(settings)\n const hasFiles = existsSync(PLUGIN_INSTALL_DIR)\n\n if (!hasEntry && !hasFiles) {\n note(\"No Latitude plugin entry or files found — nothing to remove.\", \"Status\")\n outro(pc.dim(\"Nothing changed\"))\n return\n }\n\n const plan = [\n hasEntry ? `Remove \"${PLUGIN_ID}\" plugin entry from ${SETTINGS_PATH}` : null,\n hasFiles ? `Delete plugin files at ${PLUGIN_INSTALL_DIR}` : null,\n `Backup of openclaw.json saved at ${SETTINGS_BACKUP_PATH}`,\n ].filter(Boolean) as string[]\n note(plan.join(\"\\n\"), \"Plan\")\n\n if (!flags.noPrompt && process.stdin.isTTY === true) {\n const ok = await confirm({ message: \"Proceed?\", initialValue: true })\n if (isCancel(ok) || ok !== true) return onCancel()\n }\n\n const s = spinner()\n s.start(\"Reverting settings\")\n if (hasEntry) {\n backupSettings()\n removePluginEntry(settings)\n // Also clean any legacy keys our 0.0.1 installer left behind so the file\n // is fully back to a clean state.\n migrateLegacyEntries(settings)\n writeSettings(settings)\n }\n if (hasFiles) removePluginFiles()\n s.stop(\"Done\")\n outro(pc.green(\"✓ Uninstalled\"))\n}\n","import { normalizeInstallFlags, parseFlags, runInstall, runUninstall } from \"./setup.ts\"\n\nasync function main(): Promise<void> {\n const { subcommand, flags } = parseFlags(process.argv.slice(2))\n if (subcommand === \"install\" || subcommand === undefined) {\n await runInstall(normalizeInstallFlags(flags))\n return\n }\n if (subcommand === \"uninstall\") {\n await runUninstall({ noPrompt: flags[\"no-prompt\"] === true || flags.yes === true })\n return\n }\n process.stderr.write(`unknown subcommand: ${subcommand}\\n`)\n process.stderr.write(\n \"usage: latitude-openclaw [install|uninstall] [--api-key=...] [--project=...] [--staging|--dev] [--yes]\\n\",\n )\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":[],"sources":["../src/settings-file.ts","../src/install-files.ts","../src/setup.ts","../src/cli.ts"],"sourcesContent":["import { copyFileSync, existsSync, readFileSync, writeFileSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nconst CONFIG_DIR = join(homedir(), \".openclaw\")\nexport const SETTINGS_PATH = join(CONFIG_DIR, \"openclaw.json\")\nexport const SETTINGS_BACKUP_PATH = join(CONFIG_DIR, \"openclaw.json.latitude-bak\")\nconst EXTENSIONS_DIR = join(CONFIG_DIR, \"extensions\")\nexport const PLUGIN_INSTALL_DIR = join(EXTENSIONS_DIR, \"latitude-telemetry\")\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 deliberately keep this minimal — only the\n * fields we actually write — so we don't drop anything when round-tripping\n * an existing entry that uses fields we don't know about.\n */\ninterface OpenClawPluginEntry {\n enabled?: boolean\n /**\n * The free-form bucket OpenClaw passes to the plugin at activation. This is\n * where we store all our credentials and feature flags — `hooks` is strict,\n * `env` at root is not a free-form key/value passthrough, and using\n * `.config` keeps everything namespaced to our plugin and survives any\n * version of OpenClaw because the field is a `record(string, unknown)`.\n */\n config?: Record<string, unknown>\n // Pass through anything else that may already be there — `hooks`, `subagent`, etc.\n [key: string]: unknown\n}\n\nexport interface OpenClawSettings {\n plugins?: {\n enabled?: boolean\n entries?: Record<string, OpenClawPluginEntry>\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(): OpenClawSettings {\n if (!existsSync(SETTINGS_PATH)) return {}\n try {\n const raw = readFileSync(SETTINGS_PATH, \"utf-8\")\n const parsed = JSON.parse(raw) as OpenClawSettings\n return parsed && typeof parsed === \"object\" ? parsed : {}\n } catch {\n return {}\n }\n}\n\nexport function writeSettings(settings: OpenClawSettings): void {\n writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\\n`, \"utf-8\")\n}\n\nexport function backupSettings(): void {\n if (existsSync(SETTINGS_PATH)) copyFileSync(SETTINGS_PATH, SETTINGS_BACKUP_PATH)\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; `undefined` preserves the existing value (or\n * leaves the key absent if there is none).\n */\n allowConversationAccess?: boolean | undefined\n /** Same semantics as `allowConversationAccess`. */\n debug?: boolean | undefined\n /**\n * `true`/`false` overwrites; `undefined` preserves the existing value, or\n * defaults to `true` for a fresh install. This keeps a paused plugin\n * (`enabled: false` 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. Writes credentials and\n * options into the `.config` bucket — never under `hooks` (strict zod) and\n * never as top-level `env` keys (root schema rejects them).\n *\n * Re-install idempotency: only `apiKey` / `project` / `baseUrl` always\n * overwrite (these come from the install prompts). `enabled`, `debug`, and\n * `allowConversationAccess` are preserved when not provided in the patch,\n * so a user who hand-edited `enabled: false` or `debug: true` doesn't lose\n * their choice on a re-install.\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\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 // Preserve user-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 config: nextConfig,\n }\n plugins.entries = entries\n settings.plugins = plugins\n}\n\n/** Remove the plugin entry entirely. */\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\nexport function hasLatitudePlugin(settings: OpenClawSettings): boolean {\n return Boolean(settings.plugins?.entries && PLUGIN_ID in settings.plugins.entries)\n}\n\n/**\n * Strip any leftover top-level keys our older installer (<= 0.0.1) wrote. The\n * 0.0.1 installer wrote `hooks.allowConversationAccess` (rejected by strict\n * zod) and `LATITUDE_*` keys at root-level `env` (also rejected). Both are\n * cleaned up here so re-running install lands a config OpenClaw will validate.\n */\nexport function migrateLegacyEntries(settings: OpenClawSettings): { changed: boolean } {\n let changed = false\n\n // Wipe `hooks.allowConversationAccess` from our entry — OpenClaw < 2026.4.22\n // rejects unknown keys under the strict `hooks` shape.\n const entry = settings.plugins?.entries?.[PLUGIN_ID]\n if (entry && typeof entry === \"object\") {\n const hooks = (entry as { hooks?: Record<string, unknown> }).hooks\n if (hooks && typeof hooks === \"object\" && \"allowConversationAccess\" in hooks) {\n delete hooks.allowConversationAccess\n // If the hooks object is now empty, drop it entirely so we don't leave\n // a `\"hooks\": {}` carcass behind.\n if (Object.keys(hooks).length === 0) {\n delete (entry as { hooks?: unknown }).hooks\n }\n changed = true\n }\n }\n\n // Strip `LATITUDE_*` keys our 0.0.1 installer mistakenly wrote under\n // `settings.env`. OpenClaw's root schema is strict; the `env` block accepts\n // only `{shellEnv, vars}`, so any `LATITUDE_*` key sitting directly under\n // `env` causes the gateway to quarantine the file. (0.0.1 only ever wrote\n // to `settings.env.LATITUDE_*`, never to top-level `settings.LATITUDE_*`,\n // so we don't bother sweeping the root.)\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 if there's nothing left in it AND it's not OpenClaw's\n // canonical {shellEnv, vars} shape — only do this when we know it was ours.\n if (Object.keys(envObj).length === 0) {\n delete settings.env\n }\n }\n\n return { changed }\n}\n","import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\"\nimport { dirname, join, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { PLUGIN_INSTALL_DIR } from \"./settings-file.ts\"\n\n/**\n * Copies the plugin's runtime files into `~/.openclaw/extensions/latitude-telemetry/`\n * so OpenClaw's discovery (`<configDir>/extensions/<plugin>`) picks it up.\n *\n * `npx -y @latitude-data/openclaw-telemetry` runs the CLI from a temporary npm\n * location — relying on that path persisting across runs (or even across the\n * gateway restart that follows install) is unsafe. So we materialize a stable\n * copy under the user's OpenClaw config dir, the same path OpenClaw scans on\n * startup. Layout we produce:\n *\n * ~/.openclaw/extensions/latitude-telemetry/\n * openclaw.plugin.json <- the manifest, required by discovery\n * package.json <- minimal — keeps node module-resolution happy\n * dist/ <- compiled plugin entrypoint(s)\n */\nexport function installPluginFiles(): { destination: string; entryPath: string } {\n // The CLI runs from `dist/cli.js`. Resolve the package root from there.\n const here = dirname(fileURLToPath(import.meta.url))\n const packageRoot = resolve(here, \"..\") // dist/cli.js -> dist/ -> package root\n\n const manifestSrc = join(packageRoot, \"openclaw.plugin.json\")\n const distSrc = join(packageRoot, \"dist\")\n const pkgSrc = join(packageRoot, \"package.json\")\n\n if (!existsSync(manifestSrc)) {\n throw new Error(\n `Cannot install: missing openclaw.plugin.json at ${manifestSrc}. This is a packaging bug — please file an issue.`,\n )\n }\n if (!existsSync(distSrc)) {\n throw new Error(`Cannot install: missing compiled dist at ${distSrc}.`)\n }\n if (!existsSync(pkgSrc)) {\n // package.json is now load-bearing for OpenClaw discovery (it carries\n // `openclaw.extensions`), so missing it would silently regress to the\n // \"plugin not found\" failure mode 0.0.3 was meant to fix. Match the\n // other packaging-bug checks above and fail loudly instead.\n throw new Error(\n `Cannot install: missing package.json at ${pkgSrc}. This is a packaging bug — please file an issue.`,\n )\n }\n\n // Wipe and recreate so a re-install isn't polluted by a previous version's\n // files. Idempotent.\n if (existsSync(PLUGIN_INSTALL_DIR)) rmSync(PLUGIN_INSTALL_DIR, { recursive: true, force: true })\n mkdirSync(PLUGIN_INSTALL_DIR, { recursive: true })\n\n copyFileSync(manifestSrc, join(PLUGIN_INSTALL_DIR, \"openclaw.plugin.json\"))\n cpSync(distSrc, join(PLUGIN_INSTALL_DIR, \"dist\"), { recursive: true })\n\n // Write a minimal package.json. Three fields matter for OpenClaw:\n // - `name`/`version` for diagnostics.\n // - `type: \"module\"` so node treats `dist/plugin.js` as ESM.\n // - `openclaw.extensions: [...]` — THIS is what OpenClaw's plugin\n // discovery uses to find the plugin entry file. The\n // `openclaw.plugin.json` manifest provides id + configSchema, but\n // discovery's `resolvePackageExtensionEntries` reads\n // `package.json[\"openclaw\"].extensions`. Without this, discovery\n // falls through to looking for `index.{ts,js,mjs,cjs}` at the dir\n // root, doesn't find one, and skips us — which manifests as\n // \"plugin not found\" at gateway start.\n const sourcePkg = JSON.parse(readFileSync(pkgSrc, \"utf-8\")) as {\n name?: string\n version?: string\n type?: string\n main?: string\n openclaw?: { extensions?: string[] }\n }\n const extensions = sourcePkg.openclaw?.extensions ?? [\"./dist/plugin.js\"]\n const minimalPkg = {\n name: sourcePkg.name,\n version: sourcePkg.version,\n type: sourcePkg.type ?? \"module\",\n main: sourcePkg.main ?? \"./dist/plugin.js\",\n private: true,\n openclaw: { extensions },\n }\n writeFileSync(join(PLUGIN_INSTALL_DIR, \"package.json\"), `${JSON.stringify(minimalPkg, null, 2)}\\n`, \"utf-8\")\n\n // Derive the entry path from the resolved extensions rather than\n // hard-coding it, so callers reading `entryPath` stay consistent if the\n // package's openclaw.extensions ever points somewhere other than\n // `./dist/plugin.js` (e.g. multi-entry plugins).\n const primaryEntry = extensions[0] ?? \"./dist/plugin.js\"\n return {\n destination: PLUGIN_INSTALL_DIR,\n entryPath: resolve(PLUGIN_INSTALL_DIR, primaryEntry),\n }\n}\n\n/** Remove the materialized plugin directory under `~/.openclaw/extensions/`. */\nexport function removePluginFiles(): boolean {\n if (!existsSync(PLUGIN_INSTALL_DIR)) return false\n rmSync(PLUGIN_INSTALL_DIR, { recursive: true, force: true })\n return true\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 { installPluginFiles, removePluginFiles } from \"./install-files.ts\"\nimport {\n backupSettings,\n hasLatitudePlugin,\n type LatitudePluginConfig,\n migrateLegacyEntries,\n PLUGIN_ID,\n PLUGIN_INSTALL_DIR,\n readSettings,\n removePluginEntry,\n SETTINGS_BACKUP_PATH,\n SETTINGS_PATH,\n setPluginEntry,\n writeSettings,\n} from \"./settings-file.ts\"\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 (the user passed `--allow-conversation`),\n * `false` = scrub (the user passed `--no-content`), `undefined` = preserve\n * existing or fall through to the first-install default. Keeping this\n * tristate is what makes re-installs idempotent for hand-edited values.\n */\n allowConversationAccess?: boolean | undefined\n noPrompt?: boolean\n yes?: boolean\n}\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 (const arg of rest) {\n if (!arg.startsWith(\"--\")) continue\n const eq = arg.indexOf(\"=\")\n if (eq >= 0) {\n flags[arg.slice(2, eq)] = arg.slice(eq + 1)\n } else {\n flags[arg.slice(2)] = 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\n // the other. Re-install then preserves whatever's 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 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 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\nasync function runInteractiveInstall(flags: InstallFlags): Promise<void> {\n intro(pc.bgCyan(pc.black(\" Latitude · OpenClaw telemetry \")))\n\n const existing = readSettings()\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 })\n\n note(\n [\n \"Restart the OpenClaw gateway for the plugin to load:\",\n pc.dim(\" openclaw gateway restart\"),\n \"\",\n `View your traces at ${pc.cyan(urls.projectView(project))}`,\n ].join(\"\\n\"),\n \"Next step\",\n )\n outro(pc.green(\"✓ Installed\"))\n}\n\nasync function runFlagDrivenInstall(flags: InstallFlags): Promise<void> {\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 })\n process.stdout.write(`Installed Latitude plugin in ${SETTINGS_PATH}\\n`)\n process.stdout.write(`Plugin files at ${PLUGIN_INSTALL_DIR}\\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}\n\nasync function applyChanges({ apiKey, project, envConfig, allowConversationAccess }: ApplyParams): Promise<void> {\n // 1. Materialize plugin runtime files into ~/.openclaw/extensions/latitude-telemetry/\n // so OpenClaw's plugin discovery picks them up. This MUST happen before\n // we write the openclaw.json entry, otherwise the gateway file-watcher\n // will see the entry, fail to find the plugin, and emit a warning.\n const filesSpinner = spinner()\n filesSpinner.start(\"Installing plugin files\")\n const { destination } = installPluginFiles()\n filesSpinner.stop(`Plugin files installed at ${destination}`)\n\n // 2. Update openclaw.json with the plugin entry.\n const settingsSpinner = spinner()\n settingsSpinner.start(\"Updating openclaw.json\")\n ensureSettingsDir()\n backupSettings()\n const settings = readSettings()\n // Migrate any leftover keys our 0.0.1 installer wrote that the strict zod\n // schema rejects. Without this, re-installing on top of a 0.0.1 install\n // would leave the gateway quarantining the file as `clobbered`.\n migrateLegacyEntries(settings)\n\n // Decide allowConversationAccess for this install:\n // - explicit flag (true|false) wins\n // - else preserve whatever's already in openclaw.json\n // - else first-install default is `true` (matches the README's promise)\n const existingConfig = (settings.plugins?.entries?.[PLUGIN_ID]?.config ?? {}) as Partial<LatitudePluginConfig>\n const finalAllowConversationAccess = allowConversationAccess ?? existingConfig.allowConversationAccess ?? true\n\n setPluginEntry(settings, {\n apiKey,\n project,\n baseUrl: envConfig.name === \"production\" ? undefined : envConfig.ingest,\n allowConversationAccess: finalAllowConversationAccess,\n // `debug` is intentionally not passed — `setPluginEntry` preserves the\n // user's hand-edited value. Fresh installs leave the key absent (the\n // runtime default is `false`).\n })\n writeSettings(settings)\n settingsSpinner.stop(`Updated ${SETTINGS_PATH}`)\n if (existsSync(SETTINGS_BACKUP_PATH)) log.info(`Backup saved at ${pc.dim(SETTINGS_BACKUP_PATH)}`)\n}\n\nfunction ensureSettingsDir(): void {\n const dir = dirname(SETTINGS_PATH)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n}\n\n// ─── Uninstall ──────────────────────────────────────────────────────────────\n\ninterface UninstallFlags {\n noPrompt?: boolean\n}\n\nexport async function runUninstall(flags: UninstallFlags = {}): Promise<void> {\n intro(pc.bgYellow(pc.black(\" Latitude · OpenClaw telemetry — uninstall \")))\n const settings = readSettings()\n\n const hasEntry = hasLatitudePlugin(settings)\n const hasFiles = existsSync(PLUGIN_INSTALL_DIR)\n\n if (!hasEntry && !hasFiles) {\n note(\"No Latitude plugin entry or files found — nothing to remove.\", \"Status\")\n outro(pc.dim(\"Nothing changed\"))\n return\n }\n\n const plan = [\n hasEntry ? `Remove \"${PLUGIN_ID}\" plugin entry from ${SETTINGS_PATH}` : null,\n hasFiles ? `Delete plugin files at ${PLUGIN_INSTALL_DIR}` : null,\n `Backup of openclaw.json saved at ${SETTINGS_BACKUP_PATH}`,\n ].filter(Boolean) as string[]\n note(plan.join(\"\\n\"), \"Plan\")\n\n if (!flags.noPrompt && process.stdin.isTTY === true) {\n const ok = await confirm({ message: \"Proceed?\", initialValue: true })\n if (isCancel(ok) || ok !== true) return onCancel()\n }\n\n const s = spinner()\n s.start(\"Reverting settings\")\n if (hasEntry) {\n backupSettings()\n removePluginEntry(settings)\n // Also clean any legacy keys our 0.0.1 installer left behind so the file\n // is fully back to a clean state.\n migrateLegacyEntries(settings)\n writeSettings(settings)\n }\n if (hasFiles) removePluginFiles()\n s.stop(\"Done\")\n outro(pc.green(\"✓ Uninstalled\"))\n}\n","import { readFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { normalizeInstallFlags, parseFlags, runInstall, runUninstall } from \"./setup.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 --yes / --no-prompt Skip all prompts (required for non-TTY / CI)\n`\n\nfunction readVersion(): string {\n // dist/cli.js → ../package.json\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\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(`${readVersion()}\\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({ noPrompt: flags[\"no-prompt\"] === true || flags.yes === true })\n return\n }\n process.stderr.write(`unknown subcommand: ${subcommand}\\n`)\n process.stderr.write(USAGE)\n process.exit(1)\n}\n\nmain().catch((err) => {\n process.stderr.write(`${String(err)}\\n`)\n process.exit(1)\n})\n"],"mappings":"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"}
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "@latitude-data/openclaw-telemetry",
|
|
3
3
|
"name": "Latitude Telemetry",
|
|
4
4
|
"description": "Streams every OpenClaw agent run to Latitude as OTLP traces — full prompt, message history, assistant output, tool I/O, token usage, and agent name.",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.3",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@latitude-data/openclaw-telemetry",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "OpenClaw plugin that streams LLM calls, tool executions, and agent runs to Latitude as OTLP traces",
|
|
5
5
|
"author": "Latitude Data SL <hello@latitude.so>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
},
|
|
28
28
|
"main": "./dist/plugin.js",
|
|
29
29
|
"types": "./dist/plugin.d.ts",
|
|
30
|
+
"openclaw": {
|
|
31
|
+
"extensions": [
|
|
32
|
+
"./dist/plugin.js"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
30
35
|
"exports": {
|
|
31
36
|
".": {
|
|
32
37
|
"types": "./dist/plugin.d.ts",
|