@bli-cockpit/cli 0.1.7 → 0.1.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.
@@ -0,0 +1,154 @@
1
+ import { mkdir, rm, stat, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { DEFAULT_DASHBOARD_URL, getCollectorRuntimePaths } from "./local-state.js";
5
+ /** launchd LaunchAgent label; matches docs/runbooks/cockpit-launchd-sync.md. */
6
+ export const AUTOSTART_LABEL = "com.bli.cockpit.sync";
7
+ const DEFAULT_INTERVAL_SECONDS = 1800;
8
+ const UNSUPPORTED_MESSAGE = "macOS-only for now; see docs/runbooks/cockpit-launchd-sync.md";
9
+ function plistPathFor(homeDir) {
10
+ return path.join(homeDir, "Library", "LaunchAgents", `${AUTOSTART_LABEL}.plist`);
11
+ }
12
+ function unsupportedResult(homeDir) {
13
+ return {
14
+ status: "unsupported",
15
+ label: AUTOSTART_LABEL,
16
+ plist_path: plistPathFor(homeDir ?? os.homedir()),
17
+ message: UNSUPPORTED_MESSAGE,
18
+ };
19
+ }
20
+ /**
21
+ * Installs (or refreshes) the launchd LaunchAgent that keeps `cockpit sync`
22
+ * running at login and every `intervalSeconds`. Mirrors the plist in
23
+ * docs/runbooks/cockpit-launchd-sync.md exactly, but writes resolved absolute
24
+ * log paths (launchd does not expand `$HOME`). The unload before load makes the
25
+ * install idempotent — re-running picks up a changed repo/url/interval.
26
+ */
27
+ export async function installAutostartAgent(options) {
28
+ const platform = options.platform ?? process.platform;
29
+ if (platform !== "darwin")
30
+ return unsupportedResult(options.homeDir);
31
+ const homeDir = options.homeDir ?? os.homedir();
32
+ const workDir = path.resolve(options.repoRoot ?? process.cwd());
33
+ const dashboardUrl = options.dashboardUrl ?? DEFAULT_DASHBOARD_URL;
34
+ const intervalSeconds = options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS;
35
+ const paths = getCollectorRuntimePaths(homeDir);
36
+ const plistPath = plistPathFor(homeDir);
37
+ const stdoutPath = path.join(paths.state_dir, "sync.log");
38
+ const stderrPath = path.join(paths.state_dir, "sync.err.log");
39
+ await mkdir(path.dirname(plistPath), { recursive: true });
40
+ await mkdir(paths.state_dir, { recursive: true });
41
+ await writeFile(plistPath, renderPlist({
42
+ workDir,
43
+ dashboardUrl,
44
+ intervalSeconds,
45
+ stdoutPath,
46
+ stderrPath,
47
+ }), "utf8");
48
+ // Unload first so a changed plist is actually picked up; a not-yet-loaded
49
+ // agent makes unload fail harmlessly, so the error is ignored.
50
+ await options.exec("launchctl", ["unload", plistPath]).catch(() => undefined);
51
+ const load = await options.exec("launchctl", ["load", plistPath]);
52
+ const loaded = load.code === 0;
53
+ return {
54
+ status: "installed",
55
+ label: AUTOSTART_LABEL,
56
+ plist_path: plistPath,
57
+ loaded,
58
+ interval_seconds: intervalSeconds,
59
+ work_dir: workDir,
60
+ dashboard_url: dashboardUrl,
61
+ ...(loaded
62
+ ? {}
63
+ : {
64
+ message: `launchctl load exited ${load.code}: ${load.stderr.trim() || "unknown error"}`,
65
+ }),
66
+ };
67
+ }
68
+ /**
69
+ * Removes the LaunchAgent. Reports `absent` when there was nothing to remove so
70
+ * the command is safe to run repeatedly.
71
+ */
72
+ export async function uninstallAutostartAgent(options) {
73
+ const platform = options.platform ?? process.platform;
74
+ if (platform !== "darwin")
75
+ return unsupportedResult(options.homeDir);
76
+ const homeDir = options.homeDir ?? os.homedir();
77
+ const plistPath = plistPathFor(homeDir);
78
+ if (!(await fileExists(plistPath))) {
79
+ return { status: "absent", label: AUTOSTART_LABEL, plist_path: plistPath };
80
+ }
81
+ await options.exec("launchctl", ["unload", plistPath]).catch(() => undefined);
82
+ await rm(plistPath, { force: true });
83
+ return { status: "uninstalled", label: AUTOSTART_LABEL, plist_path: plistPath };
84
+ }
85
+ /**
86
+ * Reports whether the agent is installed and loaded. `absent` when no plist
87
+ * exists; otherwise `launchctl list <label>` exit code distinguishes `loaded`
88
+ * (0) from `not_loaded` (non-zero).
89
+ */
90
+ export async function autostartStatus(options) {
91
+ const platform = options.platform ?? process.platform;
92
+ if (platform !== "darwin")
93
+ return unsupportedResult(options.homeDir);
94
+ const homeDir = options.homeDir ?? os.homedir();
95
+ const plistPath = plistPathFor(homeDir);
96
+ if (!(await fileExists(plistPath))) {
97
+ return { status: "absent", label: AUTOSTART_LABEL, plist_path: plistPath };
98
+ }
99
+ const list = await options.exec("launchctl", ["list", AUTOSTART_LABEL]);
100
+ return {
101
+ status: list.code === 0 ? "loaded" : "not_loaded",
102
+ label: AUTOSTART_LABEL,
103
+ plist_path: plistPath,
104
+ };
105
+ }
106
+ function renderPlist(options) {
107
+ // The repo path is shell-quoted because it lands inside a `/bin/zsh -lc "…"`
108
+ // command string; the whole command is then XML-escaped for the <string>.
109
+ const command = `npm exec --yes --package=@bli-cockpit/cli@latest -- cockpit sync --repo ${shellQuote(options.workDir)} --dashboard-url ${shellQuote(options.dashboardUrl)} --json`;
110
+ return [
111
+ '<?xml version="1.0" encoding="UTF-8"?>',
112
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
113
+ '<plist version="1.0">',
114
+ "<dict>",
115
+ " <key>Label</key>",
116
+ ` <string>${xmlEscape(AUTOSTART_LABEL)}</string>`,
117
+ " <key>ProgramArguments</key>",
118
+ " <array>",
119
+ " <string>/bin/zsh</string>",
120
+ " <string>-lc</string>",
121
+ ` <string>${xmlEscape(command)}</string>`,
122
+ " </array>",
123
+ " <key>StartInterval</key>",
124
+ ` <integer>${options.intervalSeconds}</integer>`,
125
+ " <key>RunAtLoad</key>",
126
+ " <true/>",
127
+ " <key>EnvironmentVariables</key>",
128
+ " <dict>",
129
+ " <key>PATH</key>",
130
+ " <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>",
131
+ " </dict>",
132
+ " <key>StandardOutPath</key>",
133
+ ` <string>${xmlEscape(options.stdoutPath)}</string>`,
134
+ " <key>StandardErrorPath</key>",
135
+ ` <string>${xmlEscape(options.stderrPath)}</string>`,
136
+ "</dict>",
137
+ "</plist>",
138
+ "",
139
+ ].join("\n");
140
+ }
141
+ function shellQuote(value) {
142
+ return `'${value.replace(/'/g, "'\\''")}'`;
143
+ }
144
+ function xmlEscape(value) {
145
+ return value
146
+ .replace(/&/g, "&amp;")
147
+ .replace(/</g, "&lt;")
148
+ .replace(/>/g, "&gt;")
149
+ .replace(/"/g, "&quot;")
150
+ .replace(/'/g, "&apos;");
151
+ }
152
+ async function fileExists(filePath) {
153
+ return stat(filePath).then(() => true, () => false);
154
+ }
@@ -1,6 +1,8 @@
1
+ import { execFile } from "node:child_process";
1
2
  import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { createCollectorServer } from "../server.js";
5
+ import { autostartStatus, installAutostartAgent, uninstallAutostartAgent, } from "../autostart.js";
4
6
  import { DEFAULT_DASHBOARD_URL, getCollectorRuntimePaths, inspectLocalCollectorStatus, installLocalCollector, logoutLocalCollector, pairLocalCollector, readLocalCollectorSessionFile, readLocalSessionReference, startLocalWorkContext, } from "../local-state.js";
5
7
  import { LocalUploadBlockedError, postCodexSessionReport, syncLocalAmbientEnvelope, } from "../upload.js";
6
8
  import { scanAndAttributeCodexSessions, } from "../adapters/codex-attribution.js";
@@ -21,6 +23,7 @@ export const rootCommandNames = new Set([
21
23
  "status",
22
24
  "sessions",
23
25
  "serve",
26
+ "autostart",
24
27
  ]);
25
28
  export async function runLocalCockpitCli(argv, io = defaultIo()) {
26
29
  if (isLocalHelpRequest(argv)) {
@@ -57,6 +60,8 @@ export async function runLocalCockpitCli(argv, io = defaultIo()) {
57
60
  return await runSessions(command, io);
58
61
  case "serve":
59
62
  return await runServe(command, io);
63
+ case "autostart":
64
+ return await runAutostart(command, io);
60
65
  }
61
66
  }
62
67
  catch (error) {
@@ -78,6 +83,7 @@ export function localCommandHelp(command) {
78
83
  " cockpit status [--repo <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
79
84
  " cockpit sessions [--source codex|claude] [--repo <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
80
85
  " cockpit serve [--port <port>] [--repo <path>]",
86
+ " cockpit autostart [install|uninstall|status] [--repo <path>] [--dashboard-url <url>] [--interval-seconds <n>] [--json]",
81
87
  ].join("\n");
82
88
  }
83
89
  function localSubcommandHelp(command) {
@@ -89,7 +95,7 @@ function localSubcommandHelp(command) {
89
95
  "",
90
96
  "Installs, pairs, starts work context(s), syncs once, and prints readiness proof.",
91
97
  "If --repo is a parent folder, scans child git repos/worktrees and rolls them up by repo.",
92
- "Use --email on shared or reused machines; mismatched existing sessions are re-paired.",
98
+ "Run with no flags in a terminal and it prompts for the dashboard email; pass --email to skip the prompt (and on shared/reused machines, where mismatched sessions are re-paired).",
93
99
  ],
94
100
  ],
95
101
  [
@@ -169,6 +175,17 @@ function localSubcommandHelp(command) {
169
175
  "Starts the local collector HTTP status server.",
170
176
  ],
171
177
  ],
178
+ [
179
+ "autostart",
180
+ [
181
+ "Usage: cockpit autostart [install|uninstall|status] [--repo <path>] [--dashboard-url <url>] [--interval-seconds <n>] [--json]",
182
+ "",
183
+ "Installs a macOS launchd LaunchAgent that runs `cockpit sync` at login and",
184
+ "every 30 min (default), surviving reboots — so machines never drift to Stale.",
185
+ "Action defaults to `install`. `--repo` is the parent work folder to sync.",
186
+ "macOS-only for now; see docs/runbooks/cockpit-launchd-sync.md.",
187
+ ],
188
+ ],
172
189
  ]);
173
190
  return (helpByCommand.get(command) ?? [localCommandHelp()]).join("\n");
174
191
  }
@@ -200,6 +217,8 @@ function parseLocalArgs(argv) {
200
217
  return parseSessionsArgs(argv.slice(1));
201
218
  case "serve":
202
219
  return parseServeArgs(argv.slice(1));
220
+ case "autostart":
221
+ return parseAutostartArgs(argv.slice(1));
203
222
  default:
204
223
  throw new Error(`Unknown local command: ${command ?? ""}`);
205
224
  }
@@ -422,6 +441,34 @@ function parseServeArgs(args) {
422
441
  port,
423
442
  };
424
443
  }
444
+ function parseAutostartArgs(args) {
445
+ const values = parseNamedArgs(args, {
446
+ allowedFlags: [
447
+ "--home",
448
+ "--repo",
449
+ "--dashboard-url",
450
+ "--interval-seconds",
451
+ "--json",
452
+ ],
453
+ valueFlags: ["--home", "--repo", "--dashboard-url", "--interval-seconds"],
454
+ });
455
+ if (values.positionals.length > 1) {
456
+ throw new Error("autostart accepts at most one action (install|uninstall|status).");
457
+ }
458
+ const action = values.positionals[0] ?? "install";
459
+ if (action !== "install" && action !== "uninstall" && action !== "status") {
460
+ throw new Error("autostart action must be install, uninstall, or status.");
461
+ }
462
+ return {
463
+ kind: "autostart",
464
+ action,
465
+ homeDir: optionalNonEmpty(values.flags.get("--home")),
466
+ repoRoot: optionalNonEmpty(values.flags.get("--repo")),
467
+ dashboardUrl: normalizeUrl(values.flags.get("--dashboard-url") ?? DEFAULT_DASHBOARD_URL),
468
+ intervalSeconds: optionalPositiveInteger(values.flags.get("--interval-seconds"), "--interval-seconds") ?? 1800,
469
+ json: values.booleans.has("--json"),
470
+ };
471
+ }
425
472
  function parseNamedArgs(args, options) {
426
473
  const allowed = new Set(options.allowedFlags);
427
474
  const valueFlags = new Set(options.valueFlags);
@@ -469,6 +516,76 @@ async function runInstall(command, io) {
469
516
  writeLine(io.stdout, "Next: run `cockpit login`, then `cockpit start` inside the repo; add `--ticket <id>` only when ticket work begins.");
470
517
  return 0;
471
518
  }
519
+ function isInteractiveStdin(io) {
520
+ return Boolean(io.stdin.isTTY);
521
+ }
522
+ /**
523
+ * Reads one line from stdin after writing a prompt. Shared by the onboard email
524
+ * prompt and the autostart prompt; callers gate on `isInteractiveStdin` first so
525
+ * headless / piped / spawned runs never block on input.
526
+ */
527
+ async function readLine(io, prompt) {
528
+ io.stdout.write(prompt);
529
+ io.stdin.setEncoding("utf8");
530
+ return new Promise((resolve) => {
531
+ const onData = (chunk) => {
532
+ io.stdin.removeListener("data", onData);
533
+ io.stdin.pause();
534
+ resolve(chunk);
535
+ };
536
+ io.stdin.resume();
537
+ io.stdin.on("data", onData);
538
+ });
539
+ }
540
+ /**
541
+ * Asks for the dashboard email so `cockpit onboard` (no flags) need not force a
542
+ * `--email`. Only called when stdin is a TTY and not in --json mode. An empty
543
+ * answer or a non-email skips rather than failing, matching `optionalEmail`'s
544
+ * leniency (the approving admin's account then owns the device).
545
+ */
546
+ async function promptOnboardEmail(io) {
547
+ const raw = await readLine(io, "Dashboard email (press enter to skip): ");
548
+ const answer = raw.trim().toLowerCase();
549
+ if (!answer)
550
+ return undefined;
551
+ if (!answer.includes("@")) {
552
+ writeLine(io.stderr, `"${answer}" is not an email; continuing without one — the approving admin's account will own this device.`);
553
+ return undefined;
554
+ }
555
+ return answer;
556
+ }
557
+ /**
558
+ * After a successful onboard, offers to install the launchd autostart agent so a
559
+ * Mac mini keeps syncing without anyone re-running cockpit — this is the fix for
560
+ * interns drifting to Stale. Only in an interactive, non-JSON run with a real
561
+ * exec runner (`io.exec`): headless / piped / spawned onboards and tests that
562
+ * pass no exec skip it entirely. Declining leaves onboarding's success untouched.
563
+ */
564
+ async function maybeOfferAutostart(command, io) {
565
+ if (command.json || !isInteractiveStdin(io) || !io.exec)
566
+ return;
567
+ const answer = (await readLine(io, "Keep Cockpit syncing in the background, even after restart? [Y/n] "))
568
+ .trim()
569
+ .toLowerCase();
570
+ if (answer === "n" || answer === "no") {
571
+ writeLine(io.stdout, "Skipped background autostart. Run `cockpit autostart install` anytime.");
572
+ return;
573
+ }
574
+ const result = await installAutostartAgent({
575
+ homeDir: command.homeDir,
576
+ repoRoot: command.repoRoot,
577
+ dashboardUrl: command.dashboardUrl,
578
+ exec: io.exec,
579
+ });
580
+ if (result.status === "unsupported") {
581
+ writeLine(io.stdout, `Background autostart unsupported: ${result.message}`);
582
+ return;
583
+ }
584
+ writeLine(io.stdout, result.loaded
585
+ ? "Background autostart installed; Cockpit syncs at login and every 30 min."
586
+ : "Background autostart installed, but launchctl load reported a problem; check `cockpit autostart status`.");
587
+ writeLine(io.stdout, `Plist: ${result.plist_path}`);
588
+ }
472
589
  async function runOnboard(command, io) {
473
590
  let install = null;
474
591
  let pair = null;
@@ -480,6 +597,10 @@ async function runOnboard(command, io) {
480
597
  writeLine(io.stdout, `Dashboard: ${command.dashboardUrl}`);
481
598
  writeLine(io.stdout, `Ticket: ${command.activeTicketId ?? "general ambient"}`);
482
599
  }
600
+ let claimedOwnerEmail = command.claimedOwnerEmail;
601
+ if (!claimedOwnerEmail && !command.json && isInteractiveStdin(io)) {
602
+ claimedOwnerEmail = await promptOnboardEmail(io);
603
+ }
483
604
  install = await installLocalCollector({
484
605
  homeDir: command.homeDir,
485
606
  repoRoot: command.repoRoot,
@@ -496,7 +617,7 @@ async function runOnboard(command, io) {
496
617
  branch: command.branch,
497
618
  });
498
619
  const installedSession = await readOnboardSessionReuseCandidate(command.homeDir);
499
- const canReuseInstalledSession = canReuseOnboardSession(installedSession, command.claimedOwnerEmail, command.dashboardUrl);
620
+ const canReuseInstalledSession = canReuseOnboardSession(installedSession, claimedOwnerEmail, command.dashboardUrl);
500
621
  if (installedStatus.session_state === "valid" && canReuseInstalledSession) {
501
622
  if (!command.json) {
502
623
  writeLine(io.stdout, "2/5 Existing valid device session found; pairing skipped.");
@@ -512,7 +633,7 @@ async function runOnboard(command, io) {
512
633
  pair = await pairLocalCollector({
513
634
  homeDir: command.homeDir,
514
635
  dashboardUrl: command.dashboardUrl,
515
- claimedOwnerEmail: command.claimedOwnerEmail,
636
+ claimedOwnerEmail,
516
637
  deviceName: command.deviceName,
517
638
  pollIntervalMs: command.pollIntervalMs,
518
639
  timeoutMs: command.timeoutMs,
@@ -538,6 +659,8 @@ async function runOnboard(command, io) {
538
659
  codex_sessions: multi.codex_sessions,
539
660
  }, null, 2));
540
661
  }
662
+ if (multi.ok)
663
+ await maybeOfferAutostart(command, io);
541
664
  return multi.ok ? 0 : 1;
542
665
  }
543
666
  const context = await startLocalWorkContext({
@@ -602,6 +725,7 @@ async function runOnboard(command, io) {
602
725
  writeLine(io.stdout, `Upload state: ${status.upload_state}`);
603
726
  writeLine(io.stdout, "PASS: Cockpit collector is ready for harvest.");
604
727
  writeLine(io.stdout, `Open: ${command.dashboardUrl}/my-work`);
728
+ await maybeOfferAutostart(command, io);
605
729
  return 0;
606
730
  }
607
731
  catch (error) {
@@ -1649,6 +1773,61 @@ async function runServe(command, io) {
1649
1773
  });
1650
1774
  return 0;
1651
1775
  }
1776
+ async function runAutostart(command, io) {
1777
+ const exec = io.exec ?? defaultExec();
1778
+ const result = command.action === "install"
1779
+ ? await installAutostartAgent({
1780
+ homeDir: command.homeDir,
1781
+ repoRoot: command.repoRoot,
1782
+ dashboardUrl: command.dashboardUrl,
1783
+ intervalSeconds: command.intervalSeconds,
1784
+ exec,
1785
+ })
1786
+ : command.action === "uninstall"
1787
+ ? await uninstallAutostartAgent({ homeDir: command.homeDir, exec })
1788
+ : await autostartStatus({ homeDir: command.homeDir, exec });
1789
+ if (command.json) {
1790
+ writeLine(io.stdout, JSON.stringify(result, null, 2));
1791
+ return result.status === "unsupported" ? 1 : 0;
1792
+ }
1793
+ writeAutostartResult(io, result);
1794
+ return result.status === "unsupported" ? 1 : 0;
1795
+ }
1796
+ function writeAutostartResult(io, result) {
1797
+ switch (result.status) {
1798
+ case "installed":
1799
+ writeLine(io.stdout, result.loaded
1800
+ ? "Cockpit autostart installed and loaded."
1801
+ : "Cockpit autostart installed (launchctl load reported a problem).");
1802
+ writeLine(io.stdout, `Label: ${result.label}`);
1803
+ writeLine(io.stdout, `Plist: ${result.plist_path}`);
1804
+ writeLine(io.stdout, `Interval: every ${result.interval_seconds}s`);
1805
+ writeLine(io.stdout, `Repo: ${result.work_dir}`);
1806
+ writeLine(io.stdout, `Dashboard: ${result.dashboard_url}`);
1807
+ if (!result.loaded && result.message)
1808
+ writeLine(io.stderr, result.message);
1809
+ return;
1810
+ case "uninstalled":
1811
+ writeLine(io.stdout, "Cockpit autostart removed.");
1812
+ writeLine(io.stdout, `Plist: ${result.plist_path}`);
1813
+ return;
1814
+ case "absent":
1815
+ writeLine(io.stdout, "Cockpit autostart is not installed.");
1816
+ writeLine(io.stdout, `Plist: ${result.plist_path}`);
1817
+ return;
1818
+ case "loaded":
1819
+ writeLine(io.stdout, "Cockpit autostart is installed and loaded.");
1820
+ writeLine(io.stdout, `Plist: ${result.plist_path}`);
1821
+ return;
1822
+ case "not_loaded":
1823
+ writeLine(io.stdout, "Cockpit autostart plist exists but is not loaded; run `cockpit autostart install` to reload.");
1824
+ writeLine(io.stdout, `Plist: ${result.plist_path}`);
1825
+ return;
1826
+ case "unsupported":
1827
+ writeLine(io.stderr, `Autostart unsupported: ${result.message}`);
1828
+ return;
1829
+ }
1830
+ }
1652
1831
  function assertNoPositionals(positionals, command) {
1653
1832
  if (positionals.length > 0) {
1654
1833
  throw new Error(`${command} does not accept positional arguments.`);
@@ -1718,6 +1897,16 @@ function base64UrlToBase64(value) {
1718
1897
  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
1719
1898
  return `${normalized}${"=".repeat((4 - (normalized.length % 4)) % 4)}`;
1720
1899
  }
1900
+ function defaultExec() {
1901
+ return (cmd, args) => new Promise((resolve) => {
1902
+ execFile(cmd, args, { encoding: "utf8" }, (err, stdout, stderr) => {
1903
+ // execFile's error `.code` is the exit code when numeric, but a string
1904
+ // (e.g. "ENOENT") for spawn failures — treat those as a generic 1.
1905
+ const code = err == null ? 0 : typeof err.code === "number" ? err.code : 1;
1906
+ resolve({ code, stdout: stdout ?? "", stderr: stderr ?? "" });
1907
+ });
1908
+ });
1909
+ }
1721
1910
  function defaultIo() {
1722
1911
  if (!globalThis.fetch) {
1723
1912
  throw new Error("global fetch is unavailable; use Node.js 20 or newer.");
@@ -1728,6 +1917,7 @@ function defaultIo() {
1728
1917
  stderr: process.stderr,
1729
1918
  env: process.env,
1730
1919
  fetch: globalThis.fetch.bind(globalThis),
1920
+ exec: defaultExec(),
1731
1921
  };
1732
1922
  }
1733
1923
  function writeLine(stream, text) {
@@ -5,8 +5,8 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { resolveRepoWorktreeIdentity, } from "./repo-identity.js";
7
7
  import { summarizeLocalUploadSpool } from "./spool/local-spool.js";
8
- export const LOCAL_COLLECTOR_VERSION = "0.1.7";
9
- export const DEFAULT_DASHBOARD_URL = "http://127.0.0.1:3100";
8
+ export const LOCAL_COLLECTOR_VERSION = "0.1.9";
9
+ export const DEFAULT_DASHBOARD_URL = "https://bli-cockpit-dashboard.vercel.app";
10
10
  export function getCollectorRuntimePaths(homeDir = os.homedir()) {
11
11
  const paths = getUserLocalCockpitPaths(homeDir);
12
12
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {