@legioncodeinc/honeycomb 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.
package/bundle/cli.js CHANGED
@@ -6,38 +6,52 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // dist/src/commands/contracts.js
9
+ var VERB_GROUPS = Object.freeze([
10
+ { key: "memory", label: "Memory & recall" },
11
+ { key: "knowledge", label: "Knowledge & skills" },
12
+ { key: "agents", label: "Agents, routing & config" },
13
+ { key: "account", label: "Account & workspaces" },
14
+ { key: "system", label: "Setup & system" }
15
+ ]);
9
16
  var VERB_TABLE = Object.freeze([
10
- { verb: "setup", cls: "local", summary: "detect assistants, wire hooks, bring up the daemon" },
11
- { verb: "install", cls: "local", summary: "bring up the daemon (health-gated) + open the dashboard (PRD-050a)" },
12
- { verb: "status", cls: "local", summary: "daemon connectivity + login + D1\u2013D5 environment health" },
13
- { verb: "daemon", cls: "local", summary: "start | stop | status the loopback daemon (3850)" },
14
- { verb: "dashboard", cls: "local", summary: "launch the daemon-served dashboard (020b)" },
15
- { verb: "telemetry", cls: "local", summary: "show exactly what adoption telemetry has been / would be sent (--show, PRD-050e)" },
16
- { verb: "pollinate", cls: "storage", summary: "trigger a pollinating consolidation pass on the daemon (009/026)" },
17
- { verb: "maintenance", cls: "storage", summary: "run version-history compaction over version-bumped tables (030)" },
18
- { verb: "remember", cls: "storage", summary: "write a memory through the daemon (--type fact|convention|preference|decision|gotcha|reference)" },
19
- { verb: "recall", cls: "storage", summary: "recall memories through the daemon" },
20
- { verb: "memory", cls: "storage", summary: "lifecycle: conflicts (list/resolve), stale-refs (list), inspect <id> --lifecycle (058d)" },
21
- { verb: "agent", cls: "storage", summary: "run an agent turn through the daemon" },
22
- { verb: "ontology", cls: "storage", summary: "inspect/propose ontology changes through the daemon" },
23
- { verb: "secret", cls: "storage", summary: "manage named secrets through the daemon" },
24
- { verb: "settings", cls: "storage", summary: "get/set/list vault settings + provider\u2192model selector through the daemon" },
25
- { verb: "asset", cls: "storage", summary: "register/promote/demote/style skills+agents through the tier\xD7style lattice (033)" },
26
- { verb: "skill", cls: "storage", summary: "skillify scope/pull/unpull/force/promote through the daemon (promote = cross-project, 049c)" },
27
- { verb: "skillify", cls: "storage", summary: "pull team skills from the daemon (016c)" },
28
- { verb: "hook", cls: "local", summary: "inspect/wire harness hooks" },
29
- { verb: "route", cls: "storage", summary: "manage inference routes through the daemon" },
30
- { verb: "sources", cls: "storage", summary: "connect/index/purge sources through the daemon" },
31
- { verb: "graph", cls: "storage", summary: "build/query the codebase graph through the daemon" },
32
- { verb: "goal", cls: "storage", summary: "manage goals/KPIs through the daemon" },
33
- { verb: "whoami", cls: "auth", summary: "show the authenticated user, org, and workspace (GET /me)" },
34
- { verb: "org", cls: "auth", summary: "list/switch org (passthrough to the auth dispatcher)" },
35
- { verb: "workspace", cls: "auth", summary: "list/switch/use workspace (passthrough to the auth dispatcher)" },
36
- { verb: "workspaces", cls: "auth", summary: "list workspaces in the active org (alias of `workspace list`)" },
37
- { verb: "project", cls: "auth", summary: "list/bind/use projects + show the resolved per-folder scope (049d)" },
38
- { verb: "sessions", cls: "storage", summary: "list/prune captured sessions through the daemon" },
39
- { verb: "uninstall", cls: "local", summary: "reverse only Honeycomb's changes" },
40
- { verb: "update", cls: "local", summary: "self-update the CLI, daemon, and bundles" }
17
+ // Memory & recall the product's core write/read/lifecycle surface.
18
+ { verb: "remember", cls: "storage", group: "memory", summary: "write a memory through the daemon (--type fact|convention|preference|decision|gotcha|reference)" },
19
+ { verb: "recall", cls: "storage", group: "memory", summary: "recall memories through the daemon" },
20
+ { verb: "memory", cls: "storage", group: "memory", summary: "lifecycle: conflicts (list/resolve), stale-refs (list), inspect <id> --lifecycle (058d)" },
21
+ { verb: "sessions", cls: "storage", group: "memory", summary: "list/prune captured sessions through the daemon" },
22
+ { verb: "pollinate", cls: "storage", group: "memory", summary: "trigger a pollinating consolidation pass on the daemon (009/026)" },
23
+ { verb: "maintenance", cls: "storage", group: "memory", summary: "run version-history compaction over version-bumped tables (030)" },
24
+ // Knowledge & skills — skills, assets, ontology, the codebase graph, and goals.
25
+ { verb: "skill", cls: "storage", group: "knowledge", summary: "skillify scope/pull/unpull/force/promote through the daemon (promote = cross-project, 049c)" },
26
+ { verb: "skillify", cls: "storage", group: "knowledge", summary: "pull team skills from the daemon (016c)" },
27
+ { verb: "asset", cls: "storage", group: "knowledge", summary: "register/promote/demote/style skills+agents through the tier\xD7style lattice (033)" },
28
+ { verb: "ontology", cls: "storage", group: "knowledge", summary: "inspect/propose ontology changes through the daemon" },
29
+ { verb: "graph", cls: "storage", group: "knowledge", summary: "build/query the codebase graph through the daemon" },
30
+ { verb: "sources", cls: "storage", group: "knowledge", summary: "connect/index/purge sources through the daemon" },
31
+ { verb: "goal", cls: "storage", group: "knowledge", summary: "manage goals/KPIs through the daemon" },
32
+ // Agents, routing & config agent turns, inference routes, secrets, and vault settings.
33
+ { verb: "agent", cls: "storage", group: "agents", summary: "run an agent turn through the daemon" },
34
+ { verb: "route", cls: "storage", group: "agents", summary: "manage inference routes through the daemon" },
35
+ { verb: "secret", cls: "storage", group: "agents", summary: "manage named secrets through the daemon" },
36
+ { verb: "settings", cls: "storage", group: "agents", summary: "get/set/list vault settings + provider\u2192model selector through the daemon" },
37
+ // Account & workspaces — auth, identity, and the org/workspace/project scope surface.
38
+ { verb: "login", cls: "auth", group: "account", summary: "authenticate via device flow, or --token <key> for headless (023)" },
39
+ { verb: "logout", cls: "auth", group: "account", summary: "remove the shared credentials and sign out (023)" },
40
+ { verb: "whoami", cls: "auth", group: "account", summary: "show the authenticated user, org, and workspace (GET /me)" },
41
+ { verb: "org", cls: "auth", group: "account", summary: "list/switch org (passthrough to the auth dispatcher)" },
42
+ { verb: "workspace", cls: "auth", group: "account", summary: "list/switch/use workspace (passthrough to the auth dispatcher)" },
43
+ { verb: "workspaces", cls: "auth", group: "account", summary: "list workspaces in the active org (alias of `workspace list`)" },
44
+ { verb: "project", cls: "auth", group: "account", summary: "list/bind/use projects + show the resolved per-folder scope (049d)" },
45
+ // Setup & system install/onboard, daemon lifecycle, dashboard, hooks, telemetry, update.
46
+ { verb: "setup", cls: "local", group: "system", summary: "detect assistants, wire hooks, bring up the daemon" },
47
+ { verb: "install", cls: "local", group: "system", summary: "bring up the daemon (health-gated) + open the dashboard (PRD-050a)" },
48
+ { verb: "status", cls: "local", group: "system", summary: "daemon connectivity + login + D1\u2013D5 environment health" },
49
+ { verb: "daemon", cls: "local", group: "system", summary: "start | stop | status the loopback daemon (3850)" },
50
+ { verb: "dashboard", cls: "local", group: "system", summary: "launch the daemon-served dashboard (020b)" },
51
+ { verb: "hook", cls: "local", group: "system", summary: "inspect/wire harness hooks" },
52
+ { verb: "telemetry", cls: "local", group: "system", summary: "show exactly what adoption telemetry has been / would be sent (--show, PRD-050e)" },
53
+ { verb: "update", cls: "local", group: "system", summary: "self-update the CLI, daemon, and bundles" },
54
+ { verb: "uninstall", cls: "local", group: "system", summary: "reverse only Honeycomb's changes" }
41
55
  ]);
42
56
  function lookupVerb(verb) {
43
57
  return VERB_TABLE.find((v) => v.verb === verb);
@@ -514,6 +528,15 @@ var PROVIDER_CATALOG = Object.freeze([
514
528
  // Suggestions only — OpenRouter accepts a free-form `vendor/model` id (passthrough).
515
529
  models: Object.freeze(["anthropic/claude-sonnet-4.6", "openai/gpt-4o"]),
516
530
  openEnded: true
531
+ }),
532
+ Object.freeze({
533
+ // PRD-063a: Portkey is a GATEWAY, not a model vendor — its `portkey.config` id is
534
+ // free-form (a config or virtual-key id copied from the Portkey dashboard), so it is
535
+ // `openEnded: true` like OpenRouter and carries NO curated model list of its own.
536
+ id: "portkey",
537
+ label: "Portkey",
538
+ models: Object.freeze([]),
539
+ openEnded: true
517
540
  })
518
541
  ]);
519
542
  function providerEntry(provider) {
@@ -16488,10 +16511,10 @@ function flagValue3(argv, flag) {
16488
16511
  return v !== void 0 && !v.startsWith("--") ? v : void 0;
16489
16512
  }
16490
16513
  function parseSkillId(raw) {
16491
- const sep3 = raw.lastIndexOf("--");
16492
- if (sep3 <= 0)
16514
+ const sep4 = raw.lastIndexOf("--");
16515
+ if (sep4 <= 0)
16493
16516
  return { name: raw, author: "" };
16494
- return { name: raw.slice(0, sep3), author: raw.slice(sep3 + 2) };
16517
+ return { name: raw.slice(0, sep4), author: raw.slice(sep4 + 2) };
16495
16518
  }
16496
16519
  function buildSkillRequest(argv) {
16497
16520
  const sub = subcommandOf(argv);
@@ -17235,7 +17258,7 @@ function buildAllowedProperties(input) {
17235
17258
  }
17236
17259
  var systemTelemetryClock = () => (/* @__PURE__ */ new Date()).toISOString();
17237
17260
  var DEFAULT_EMIT_TIMEOUT_MS = 2e3;
17238
- var HONEYCOMB_VERSION = true ? "0.1.7" : "0.0.0-dev";
17261
+ var HONEYCOMB_VERSION = true ? "0.1.9" : "0.0.0-dev";
17239
17262
  async function emitTelemetry(event, opts, deps = {}) {
17240
17263
  const env = deps.env ?? process.env;
17241
17264
  const key = deps.posthogKey ?? POSTHOG_KEY;
@@ -17378,7 +17401,7 @@ function renderGlassBoxText(view) {
17378
17401
  // dist/src/shared/constants.js
17379
17402
  var DAEMON_PORT = 3850;
17380
17403
  var DAEMON_HOST = "127.0.0.1";
17381
- var HONEYCOMB_VERSION2 = true ? "0.1.7" : "0.0.0-dev";
17404
+ var HONEYCOMB_VERSION2 = true ? "0.1.9" : "0.0.0-dev";
17382
17405
  var PRODUCT_SLUG = "honeycomb";
17383
17406
 
17384
17407
  // dist/src/commands/install.js
@@ -17461,6 +17484,19 @@ function openDashboardWithFallback(opener, out) {
17461
17484
  const opened = opener(loopback);
17462
17485
  out(opened ? `\u2192 opening dashboard at ${loopback}\u2026` : `\u2192 dashboard is ready at ${loopback} (open it in your browser).`);
17463
17486
  }
17487
+ async function reportDaemonSupervision(deps, out) {
17488
+ if (deps.lifecycle === void 0)
17489
+ return;
17490
+ try {
17491
+ const status3 = await deps.lifecycle.status();
17492
+ if (status3.serviceManager !== void 0) {
17493
+ out(`\u2713 daemon registered as an OS service (${status3.serviceManager}): it restarts on crash and starts on boot.`);
17494
+ } else {
17495
+ out("note: daemon running as a detached process (OS service registration unavailable on this host).");
17496
+ }
17497
+ } catch {
17498
+ }
17499
+ }
17464
17500
  async function runInstallCommand(argv, deps) {
17465
17501
  const out = deps.out ?? ((line) => console.log(line));
17466
17502
  const opener = deps.openDashboard ?? openLocalDashboardUrl;
@@ -17477,6 +17513,7 @@ async function runInstallCommand(argv, deps) {
17477
17513
  return { exitCode: 1 };
17478
17514
  }
17479
17515
  out(`\u2713 daemon up on ${DAEMON_HOST}:${DAEMON_PORT}.`);
17516
+ await reportDaemonSupervision(deps, out);
17480
17517
  const wrote = writeInstalledMarker(ref, deps.dir, out);
17481
17518
  if (wrote)
17482
17519
  out(`\u2713 onboarding marked installed (ref: ${ref}).`);
@@ -17535,12 +17572,26 @@ function parseInvocation(argv) {
17535
17572
  const tail = verb === "" ? argv.slice(i) : argv.slice(i + 1);
17536
17573
  return { verb, argv: tail, flags };
17537
17574
  }
17575
+ var HONEYCOMB_BANNER = [
17576
+ " __ __ __",
17577
+ " / \\__/ \\__/ \\ H O N E Y C O M B",
17578
+ " \\__/ \\__/ \\__/",
17579
+ " / \\__/ \\__/ \\ shared agent memory for your coding tools",
17580
+ " \\__/ \\__/ \\__/"
17581
+ ].join("\n");
17538
17582
  function usageText() {
17539
- const lines = [`${PRODUCT_SLUG} v${HONEYCOMB_VERSION2}`, "", "usage: honeycomb <command> [options]", "", "commands:"];
17540
- for (const spec of VERB_TABLE) {
17541
- lines.push(` ${spec.verb.padEnd(11)} ${spec.summary}`);
17583
+ const lines = [HONEYCOMB_BANNER, "", `${PRODUCT_SLUG} v${HONEYCOMB_VERSION2}`, "", "usage: honeycomb <command> [options]", ""];
17584
+ const pad = VERB_TABLE.reduce((w, s2) => Math.max(w, s2.verb.length), 0) + 2;
17585
+ for (const { key, label } of VERB_GROUPS) {
17586
+ const rows = VERB_TABLE.filter((s2) => s2.group === key);
17587
+ if (rows.length === 0)
17588
+ continue;
17589
+ lines.push(`${label}:`);
17590
+ for (const spec of rows)
17591
+ lines.push(` ${spec.verb.padEnd(pad)}${spec.summary}`);
17592
+ lines.push("");
17542
17593
  }
17543
- lines.push("", "global flags: --help --version --json --dry-run");
17594
+ lines.push("global flags: --help --version --json --dry-run");
17544
17595
  return lines.join("\n");
17545
17596
  }
17546
17597
  function lifecycleOf(deps) {
@@ -17663,13 +17714,14 @@ function createDispatcher() {
17663
17714
 
17664
17715
  // dist/src/cli/runtime.js
17665
17716
  import { spawn } from "node:child_process";
17666
- import { homedir as homedir12 } from "node:os";
17667
- import { dirname as dirname8, join as join12, resolve as resolve7 } from "node:path";
17717
+ import { mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync2 } from "node:fs";
17718
+ import { homedir as homedir13 } from "node:os";
17719
+ import { dirname as dirname8, join as join13, resolve as resolve8 } from "node:path";
17668
17720
  import { fileURLToPath as fileURLToPath3 } from "node:url";
17669
17721
 
17670
17722
  // dist/src/daemon/runtime/auth/deeplake-issuer.js
17671
17723
  import { execFileSync as execFileSync4 } from "node:child_process";
17672
- var realSleeper = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
17724
+ var realSleeper = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
17673
17725
  var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
17674
17726
  var DEEPLAKE_ORG_HEADER = "X-Activeloop-Org-Id";
17675
17727
  var DEEPLAKE_CLIENT_VALUE = "honeycomb";
@@ -18139,6 +18191,51 @@ var ROUTER_HISTORY_COLUMNS = Object.freeze([
18139
18191
  { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
18140
18192
  { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" }
18141
18193
  ]);
18194
+ var ROI_METRICS_COLUMNS = Object.freeze([
18195
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
18196
+ { name: "session_id", sql: "TEXT NOT NULL DEFAULT ''" },
18197
+ { name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
18198
+ { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
18199
+ { name: "agent_id", sql: "TEXT NOT NULL DEFAULT 'default'" },
18200
+ { name: "project_id", sql: "TEXT NOT NULL DEFAULT ''" },
18201
+ { name: "team_id", sql: "TEXT NOT NULL DEFAULT ''" },
18202
+ // GATED: '' until a verified backend-token claim populates it (f-AC-6/f-AC-7).
18203
+ { name: "user_id", sql: "TEXT NOT NULL DEFAULT ''" },
18204
+ { name: "input_tokens", sql: "BIGINT NOT NULL DEFAULT 0" },
18205
+ { name: "output_tokens", sql: "BIGINT NOT NULL DEFAULT 0" },
18206
+ { name: "cache_read_tokens", sql: "BIGINT NOT NULL DEFAULT 0" },
18207
+ { name: "cache_creation_tokens", sql: "BIGINT NOT NULL DEFAULT 0" },
18208
+ // MEASURED / MODELED / GROSS / INFRA money — BIGINT integer cents, never FLOAT (f-AC-4).
18209
+ { name: "measured_cache_savings_cents", sql: "BIGINT NOT NULL DEFAULT 0" },
18210
+ { name: "modeled_savings_cents", sql: "BIGINT NOT NULL DEFAULT 0" },
18211
+ { name: "modeled_assumption_ref", sql: "TEXT NOT NULL DEFAULT ''" },
18212
+ { name: "gross_cost_cents", sql: "BIGINT NOT NULL DEFAULT 0" },
18213
+ { name: "infra_cost_cents", sql: "BIGINT NOT NULL DEFAULT 0" },
18214
+ // cost_basis ∈ {measured, allocated, none}; allocation_method '' unless allocated (f-AC-5).
18215
+ { name: "cost_basis", sql: "TEXT NOT NULL DEFAULT 'none'" },
18216
+ { name: "allocation_method", sql: "TEXT NOT NULL DEFAULT ''" },
18217
+ { name: "price_ref", sql: "TEXT NOT NULL DEFAULT ''" },
18218
+ { name: "period_start", sql: "TEXT NOT NULL DEFAULT ''" },
18219
+ { name: "period_end", sql: "TEXT NOT NULL DEFAULT ''" },
18220
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" }
18221
+ ]);
18222
+ var ROI_COST_BASES = Object.freeze(["measured", "allocated", "none"]);
18223
+ var TEAMS_COLUMNS = Object.freeze([
18224
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
18225
+ { name: "team_id", sql: "TEXT NOT NULL DEFAULT ''" },
18226
+ { name: "team_name", sql: "TEXT NOT NULL DEFAULT ''" },
18227
+ // 'agent' rows work today; 'user' rows inert until user_id verified.
18228
+ { name: "member_type", sql: "TEXT NOT NULL DEFAULT 'agent'" },
18229
+ { name: "member_id", sql: "TEXT NOT NULL DEFAULT ''" },
18230
+ { name: "role", sql: "TEXT NOT NULL DEFAULT 'member'" },
18231
+ { name: "active", sql: "BIGINT NOT NULL DEFAULT 1" },
18232
+ { name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
18233
+ { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
18234
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 0" },
18235
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
18236
+ { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
18237
+ ]);
18238
+ var TEAM_MEMBER_TYPES = Object.freeze(["agent", "user"]);
18142
18239
  var TENANCY_TABLES = defineGroup([
18143
18240
  {
18144
18241
  name: "agents",
@@ -18177,6 +18274,25 @@ var TENANCY_TABLES = defineGroup([
18177
18274
  pattern: "append-only",
18178
18275
  embeddingColumns: [],
18179
18276
  scope: "tenant"
18277
+ },
18278
+ {
18279
+ // PRD-060f (f-AC-1/f-AC-2): the shared spend ledger. APPEND-ONLY — one
18280
+ // immutable row per session, a re-price APPENDs a new row (new price_ref),
18281
+ // NEVER an in-place UPDATE; the canonical row is MAX(created_at) per session.
18282
+ name: "roi_metrics",
18283
+ columns: ROI_METRICS_COLUMNS,
18284
+ pattern: "append-only",
18285
+ embeddingColumns: [],
18286
+ scope: "tenant"
18287
+ },
18288
+ {
18289
+ // PRD-060f (f-AC-8): the roster. VERSION-BUMPED — one row per (team, member),
18290
+ // an edit APPENDs version N+1, read ORDER BY version DESC (same as api_keys).
18291
+ name: "teams",
18292
+ columns: TEAMS_COLUMNS,
18293
+ pattern: "version-bumped",
18294
+ embeddingColumns: [],
18295
+ scope: "tenant"
18180
18296
  }
18181
18297
  ]);
18182
18298
  var SCRYPT_PARAMS = Object.freeze({ N: 16384, r: 8, p: 1, keyLen: 32 });
@@ -19903,6 +20019,27 @@ var EMPTY_DASHBOARD_DATA = Object.freeze({
19903
20019
  rules: { rules: [] },
19904
20020
  skillSync: { skills: [] }
19905
20021
  });
20022
+ var EMPTY_ROI_VIEW = Object.freeze({
20023
+ savings: {
20024
+ status: "absent",
20025
+ measuredCents: 0,
20026
+ modeledCents: 0,
20027
+ assumption: { kind: "", assumptionText: "", signedOff: false },
20028
+ blendedCentsPerMtok: null
20029
+ },
20030
+ infra: { status: "absent", cents: 0, costBasis: "none" },
20031
+ pollination: { status: "absent", cents: 0, lines: [] },
20032
+ net: { status: "absent", computed: false, netCents: 0, modeled: true, costBasis: "none" },
20033
+ rollups: [],
20034
+ perUserAvailable: false,
20035
+ scopedAcrossDevices: false,
20036
+ ratesAsOf: ""
20037
+ });
20038
+ var EMPTY_ROI_TREND = Object.freeze({
20039
+ status: "absent",
20040
+ series: [],
20041
+ startedAt: ""
20042
+ });
19906
20043
 
19907
20044
  // dist/src/dashboard/views.js
19908
20045
  var GRAPH_BUILD_PROMPT = "Run `honeycomb graph build` to build the codebase graph.";
@@ -20034,6 +20171,273 @@ async function launchDashboard(options = {}) {
20034
20171
  return renderDashboard(source);
20035
20172
  }
20036
20173
 
20174
+ // dist/src/cli/daemon-service.js
20175
+ import { createRequire } from "node:module";
20176
+ import { homedir as homedir12 } from "node:os";
20177
+ import { join as join12, normalize, resolve as resolve7, sep as sep3 } from "node:path";
20178
+ var SERVICE_LABEL = "ai.honeycomb.daemon";
20179
+ var SERVICE_TASK_NAME = "HoneycombDaemon";
20180
+ var SERVICE_MODE_ENV = "HONEYCOMB_DAEMON_SERVICE";
20181
+ function detectServiceManager(env = process.env, platform2 = process.platform) {
20182
+ if ((env[SERVICE_MODE_ENV] ?? "").trim().toLowerCase() === "spawn")
20183
+ return null;
20184
+ if (platform2 === "darwin")
20185
+ return "launchd";
20186
+ if (platform2 === "win32")
20187
+ return "schtasks";
20188
+ if (platform2 === "linux") {
20189
+ const xdg = (env.XDG_RUNTIME_DIR ?? "").trim();
20190
+ if (xdg.length > 0)
20191
+ return "systemd-user";
20192
+ return null;
20193
+ }
20194
+ return null;
20195
+ }
20196
+ function defaultServiceRunner() {
20197
+ const require2 = createRequire(import.meta.url);
20198
+ const cp = require2("node:child_process");
20199
+ const fs = require2("node:fs");
20200
+ return {
20201
+ run(cmd, args) {
20202
+ const out = cp.execFileSync(cmd, [...args], {
20203
+ encoding: "utf8",
20204
+ stdio: ["ignore", "pipe", "pipe"],
20205
+ timeout: 15e3,
20206
+ windowsHide: true
20207
+ });
20208
+ return typeof out === "string" ? out.trim() : "";
20209
+ },
20210
+ writeFile(path, contents) {
20211
+ fs.mkdirSync(join12(path, ".."), { recursive: true });
20212
+ fs.writeFileSync(path, contents, { encoding: "utf8" });
20213
+ },
20214
+ removeFile(path) {
20215
+ try {
20216
+ fs.rmSync(path, { force: true });
20217
+ } catch {
20218
+ }
20219
+ },
20220
+ fileExists(path) {
20221
+ try {
20222
+ return fs.existsSync(path);
20223
+ } catch {
20224
+ return false;
20225
+ }
20226
+ }
20227
+ };
20228
+ }
20229
+ function containedUnitPath(home, segments) {
20230
+ const resolvedHome = normalize(resolve7(home));
20231
+ const candidate = normalize(resolve7(resolvedHome, ...segments));
20232
+ const homeWithSep = resolvedHome.endsWith(sep3) ? resolvedHome : resolvedHome + sep3;
20233
+ if (candidate !== resolvedHome && !candidate.startsWith(homeWithSep)) {
20234
+ throw new Error("resolved service unit path escapes the home dir");
20235
+ }
20236
+ return candidate;
20237
+ }
20238
+ function launchdPlistPath(home) {
20239
+ return containedUnitPath(home, ["Library", "LaunchAgents", `${SERVICE_LABEL}.plist`]);
20240
+ }
20241
+ function systemdUnitPath(home) {
20242
+ return containedUnitPath(home, [".config", "systemd", "user", `${SERVICE_LABEL}.service`]);
20243
+ }
20244
+ function xmlEscape(value) {
20245
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20246
+ }
20247
+ function renderLaunchdPlist(spec) {
20248
+ const argv = [spec.nodePath, ...spec.nodeFlags, spec.entry];
20249
+ const argvXml = argv.map((a) => ` <string>${xmlEscape(a)}</string>`).join("\n");
20250
+ return `<?xml version="1.0" encoding="UTF-8"?>
20251
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
20252
+ <plist version="1.0">
20253
+ <dict>
20254
+ <key>Label</key>
20255
+ <string>${SERVICE_LABEL}</string>
20256
+ <key>ProgramArguments</key>
20257
+ <array>
20258
+ ${argvXml}
20259
+ </array>
20260
+ <key>WorkingDirectory</key>
20261
+ <string>${xmlEscape(spec.workspace)}</string>
20262
+ <key>EnvironmentVariables</key>
20263
+ <dict>
20264
+ <key>HONEYCOMB_WORKSPACE</key>
20265
+ <string>${xmlEscape(spec.workspace)}</string>
20266
+ </dict>
20267
+ <key>RunAtLoad</key>
20268
+ <true/>
20269
+ <key>KeepAlive</key>
20270
+ <true/>
20271
+ <key>ProcessType</key>
20272
+ <string>Background</string>
20273
+ </dict>
20274
+ </plist>
20275
+ `;
20276
+ }
20277
+ function renderSystemdUnit(spec) {
20278
+ const execStart = [spec.nodePath, ...spec.nodeFlags, spec.entry].map((a) => `"${a}"`).join(" ");
20279
+ return `[Unit]
20280
+ Description=Honeycomb primary daemon (127.0.0.1:3850)
20281
+ After=network.target
20282
+
20283
+ [Service]
20284
+ Type=simple
20285
+ ExecStart=${execStart}
20286
+ WorkingDirectory=${spec.workspace}
20287
+ Environment=HONEYCOMB_WORKSPACE=${spec.workspace}
20288
+ Restart=always
20289
+ RestartSec=5
20290
+
20291
+ [Install]
20292
+ WantedBy=default.target
20293
+ `;
20294
+ }
20295
+ function assertCmdSafe(value) {
20296
+ if (/[&|<>^"%\r\n]/.test(value)) {
20297
+ throw new Error("unsafe character in service path; refusing to build schtasks /TR command");
20298
+ }
20299
+ }
20300
+ function buildSchtasksCreateArgs(spec) {
20301
+ assertCmdSafe(spec.workspace);
20302
+ assertCmdSafe(spec.nodePath);
20303
+ assertCmdSafe(spec.entry);
20304
+ const nodeFlags = spec.nodeFlags.length > 0 ? `${spec.nodeFlags.join(" ")} ` : "";
20305
+ const tr = `cmd /c "cd /d "${spec.workspace}" && set HONEYCOMB_WORKSPACE=${spec.workspace} && "${spec.nodePath}" ${nodeFlags}"${spec.entry}""`;
20306
+ return [
20307
+ "/Create",
20308
+ "/TN",
20309
+ SERVICE_TASK_NAME,
20310
+ "/TR",
20311
+ tr,
20312
+ "/SC",
20313
+ "ONLOGON",
20314
+ "/RL",
20315
+ "LIMITED",
20316
+ "/F"
20317
+ ];
20318
+ }
20319
+ function specHome(spec) {
20320
+ return spec.home ?? homedir12();
20321
+ }
20322
+ function createDaemonServiceController(manager, runner = defaultServiceRunner()) {
20323
+ if (manager === "launchd")
20324
+ return launchdController(runner);
20325
+ if (manager === "systemd-user")
20326
+ return systemdController(runner);
20327
+ return schtasksController(runner);
20328
+ }
20329
+ function launchdController(runner) {
20330
+ function domain2() {
20331
+ return `gui/${process.getuid?.() ?? 501}`;
20332
+ }
20333
+ return {
20334
+ manager: "launchd",
20335
+ register(spec) {
20336
+ const path = launchdPlistPath(specHome(spec));
20337
+ runner.writeFile(path, renderLaunchdPlist(spec));
20338
+ runner.run("launchctl", ["bootstrap", domain2(), path]);
20339
+ return { ok: true, manager: "launchd" };
20340
+ },
20341
+ unregister(spec) {
20342
+ const path = launchdPlistPath(specHome(spec));
20343
+ try {
20344
+ runner.run("launchctl", ["bootout", `${domain2()}/${SERVICE_LABEL}`]);
20345
+ } catch {
20346
+ }
20347
+ runner.removeFile(path);
20348
+ return { ok: true, manager: "launchd" };
20349
+ },
20350
+ restart(_spec) {
20351
+ runner.run("launchctl", ["kickstart", "-k", `${domain2()}/${SERVICE_LABEL}`]);
20352
+ return { ok: true, manager: "launchd" };
20353
+ },
20354
+ stop(_spec) {
20355
+ runner.run("launchctl", ["kill", "SIGTERM", `${domain2()}/${SERVICE_LABEL}`]);
20356
+ return { ok: true, manager: "launchd" };
20357
+ },
20358
+ isRegistered(spec) {
20359
+ return runner.fileExists(launchdPlistPath(specHome(spec)));
20360
+ }
20361
+ };
20362
+ }
20363
+ function systemdController(runner) {
20364
+ const unit = `${SERVICE_LABEL}.service`;
20365
+ return {
20366
+ manager: "systemd-user",
20367
+ register(spec) {
20368
+ const path = systemdUnitPath(specHome(spec));
20369
+ runner.writeFile(path, renderSystemdUnit(spec));
20370
+ runner.run("systemctl", ["--user", "daemon-reload"]);
20371
+ runner.run("systemctl", ["--user", "enable", "--now", unit]);
20372
+ return { ok: true, manager: "systemd-user" };
20373
+ },
20374
+ unregister(spec) {
20375
+ try {
20376
+ runner.run("systemctl", ["--user", "disable", "--now", unit]);
20377
+ } catch {
20378
+ }
20379
+ runner.removeFile(systemdUnitPath(specHome(spec)));
20380
+ try {
20381
+ runner.run("systemctl", ["--user", "daemon-reload"]);
20382
+ } catch {
20383
+ }
20384
+ return { ok: true, manager: "systemd-user" };
20385
+ },
20386
+ restart(_spec) {
20387
+ runner.run("systemctl", ["--user", "restart", unit]);
20388
+ return { ok: true, manager: "systemd-user" };
20389
+ },
20390
+ stop(_spec) {
20391
+ runner.run("systemctl", ["--user", "stop", unit]);
20392
+ return { ok: true, manager: "systemd-user" };
20393
+ },
20394
+ isRegistered(spec) {
20395
+ return runner.fileExists(systemdUnitPath(specHome(spec)));
20396
+ }
20397
+ };
20398
+ }
20399
+ function schtasksController(runner) {
20400
+ return {
20401
+ manager: "schtasks",
20402
+ register(spec) {
20403
+ runner.run("schtasks", buildSchtasksCreateArgs(spec));
20404
+ runner.run("schtasks", ["/Run", "/TN", SERVICE_TASK_NAME]);
20405
+ return { ok: true, manager: "schtasks" };
20406
+ },
20407
+ unregister(_spec) {
20408
+ try {
20409
+ runner.run("schtasks", ["/End", "/TN", SERVICE_TASK_NAME]);
20410
+ } catch {
20411
+ }
20412
+ try {
20413
+ runner.run("schtasks", ["/Delete", "/TN", SERVICE_TASK_NAME, "/F"]);
20414
+ } catch {
20415
+ }
20416
+ return { ok: true, manager: "schtasks" };
20417
+ },
20418
+ restart(spec) {
20419
+ try {
20420
+ runner.run("schtasks", ["/End", "/TN", SERVICE_TASK_NAME]);
20421
+ } catch {
20422
+ }
20423
+ runner.run("schtasks", ["/Run", "/TN", SERVICE_TASK_NAME]);
20424
+ return { ok: true, manager: "schtasks" };
20425
+ },
20426
+ stop(_spec) {
20427
+ runner.run("schtasks", ["/End", "/TN", SERVICE_TASK_NAME]);
20428
+ return { ok: true, manager: "schtasks" };
20429
+ },
20430
+ isRegistered(_spec) {
20431
+ try {
20432
+ runner.run("schtasks", ["/Query", "/TN", SERVICE_TASK_NAME]);
20433
+ return true;
20434
+ } catch {
20435
+ return false;
20436
+ }
20437
+ }
20438
+ };
20439
+ }
20440
+
20037
20441
  // dist/src/cli/runtime.js
20038
20442
  function daemonBaseUrl2() {
20039
20443
  return `http://${DAEMON_HOST}:${DAEMON_PORT}`;
@@ -20051,19 +20455,38 @@ function tenancyHeaders(creds) {
20051
20455
  var DAEMON_NODE_FLAGS = ["--experimental-sqlite"];
20052
20456
  function resolveDaemonEntry() {
20053
20457
  const here = dirname8(fileURLToPath3(import.meta.url));
20054
- const bundledSibling = resolve7(here, "..", "daemon", "index.js");
20055
- const devSibling = resolve7(here, "..", "..", "..", "daemon", "index.js");
20458
+ const bundledSibling = resolve8(here, "..", "daemon", "index.js");
20459
+ const devSibling = resolve8(here, "..", "..", "..", "daemon", "index.js");
20056
20460
  return process.env.HONEYCOMB_DAEMON_ENTRY ?? bundledSibling ?? devSibling;
20057
20461
  }
20058
20462
  var DEFAULT_START_TIMEOUT_MS = 45e3;
20059
20463
  var START_POLL_INTERVAL_MS = 150;
20060
20464
  function runtimeDir() {
20061
- return join12(homedir12(), ".honeycomb");
20465
+ return join13(homedir13(), ".honeycomb");
20466
+ }
20467
+ function resolveDaemonWorkspace() {
20468
+ const fromEnv = process.env.HONEYCOMB_WORKSPACE;
20469
+ const candidates = [fromEnv !== void 0 && fromEnv.length > 0 ? fromEnv : process.cwd(), runtimeDir()];
20470
+ for (const dir of candidates) {
20471
+ if (canWriteDir(dir))
20472
+ return dir;
20473
+ }
20474
+ return runtimeDir();
20475
+ }
20476
+ function canWriteDir(dir) {
20477
+ try {
20478
+ mkdirSync8(dir, { recursive: true });
20479
+ const probe = mkdtempSync(join13(dir, ".hc-spawn-probe-"));
20480
+ rmSync2(probe, { recursive: true, force: true });
20481
+ return true;
20482
+ } catch {
20483
+ return false;
20484
+ }
20062
20485
  }
20063
20486
  async function readDaemonPid() {
20064
20487
  const { readFile: readFile2 } = await import("node:fs/promises");
20065
20488
  try {
20066
- const raw = (await readFile2(join12(runtimeDir(), "daemon.pid"), "utf8")).trim();
20489
+ const raw = (await readFile2(join13(runtimeDir(), "daemon.pid"), "utf8")).trim();
20067
20490
  const pid = Number.parseInt(raw, 10);
20068
20491
  return Number.isInteger(pid) && pid > 0 ? pid : null;
20069
20492
  } catch {
@@ -20078,44 +20501,128 @@ function isPidAlive(pid) {
20078
20501
  return err?.code === "EPERM";
20079
20502
  }
20080
20503
  }
20081
- function buildDaemonLifecycle(client) {
20504
+ function buildServiceSpec() {
20505
+ return {
20506
+ nodePath: process.execPath,
20507
+ entry: resolveDaemonEntry(),
20508
+ nodeFlags: DAEMON_NODE_FLAGS,
20509
+ workspace: resolveDaemonWorkspace()
20510
+ };
20511
+ }
20512
+ async function spawnDaemonAndWait(client) {
20513
+ const entry = resolveDaemonEntry();
20514
+ const workspace = resolveDaemonWorkspace();
20515
+ const child = spawn(process.execPath, [...DAEMON_NODE_FLAGS, entry], {
20516
+ detached: true,
20517
+ stdio: "ignore",
20518
+ cwd: workspace,
20519
+ env: { ...process.env, HONEYCOMB_WORKSPACE: workspace },
20520
+ // Hide the transient console window on Windows: a detached daemon spawn is never an
20521
+ // interactive terminal the user needs to see.
20522
+ windowsHide: true
20523
+ });
20524
+ child.unref();
20525
+ const deadline = Date.now() + DEFAULT_START_TIMEOUT_MS;
20526
+ while (Date.now() < deadline) {
20527
+ await new Promise((r) => setTimeout(r, START_POLL_INTERVAL_MS));
20528
+ if (await client.ping())
20529
+ return { started: true, alreadyRunning: false };
20530
+ }
20531
+ return { started: false, alreadyRunning: false };
20532
+ }
20533
+ async function waitForHealth(client) {
20534
+ const deadline = Date.now() + DEFAULT_START_TIMEOUT_MS;
20535
+ while (Date.now() < deadline) {
20536
+ if (await client.ping())
20537
+ return true;
20538
+ await new Promise((r) => setTimeout(r, START_POLL_INTERVAL_MS));
20539
+ }
20540
+ return false;
20541
+ }
20542
+ async function signalDaemonStop() {
20543
+ const pid = await readDaemonPid();
20544
+ if (pid === null || !isPidAlive(pid))
20545
+ return false;
20546
+ try {
20547
+ process.kill(pid, "SIGTERM");
20548
+ return true;
20549
+ } catch {
20550
+ return false;
20551
+ }
20552
+ }
20553
+ function buildDaemonLifecycle(client, options = {}) {
20554
+ const manager = options.serviceManager !== void 0 ? options.serviceManager : detectServiceManager();
20555
+ const controllerFor = options.controllerFor ?? ((m) => createDaemonServiceController(m));
20556
+ let controller = null;
20557
+ function serviceController() {
20558
+ if (manager === null)
20559
+ return null;
20560
+ if (controller === null) {
20561
+ try {
20562
+ controller = controllerFor(manager);
20563
+ } catch {
20564
+ return null;
20565
+ }
20566
+ }
20567
+ return controller;
20568
+ }
20082
20569
  return {
20083
20570
  async start() {
20084
20571
  if (await client.ping())
20085
20572
  return { started: false, alreadyRunning: true };
20086
- const entry = resolveDaemonEntry();
20087
- const child = spawn(process.execPath, [...DAEMON_NODE_FLAGS, entry], {
20088
- detached: true,
20089
- stdio: "ignore",
20090
- env: process.env,
20091
- // Hide the transient console window on Windows — a detached daemon spawn is never
20092
- // an interactive terminal the user needs to see.
20093
- windowsHide: true
20094
- });
20095
- child.unref();
20096
- const deadline = Date.now() + DEFAULT_START_TIMEOUT_MS;
20097
- while (Date.now() < deadline) {
20098
- await new Promise((r) => setTimeout(r, START_POLL_INTERVAL_MS));
20099
- if (await client.ping())
20100
- return { started: true, alreadyRunning: false };
20101
- }
20102
- return { started: false, alreadyRunning: false };
20573
+ const svc = serviceController();
20574
+ if (svc !== null) {
20575
+ try {
20576
+ svc.register(buildServiceSpec());
20577
+ if (await waitForHealth(client))
20578
+ return { started: true, alreadyRunning: false };
20579
+ return { started: false, alreadyRunning: false };
20580
+ } catch {
20581
+ }
20582
+ }
20583
+ return spawnDaemonAndWait(client);
20103
20584
  },
20104
20585
  async stop() {
20105
- const pid = await readDaemonPid();
20106
- if (pid === null || !isPidAlive(pid))
20107
- return { stopped: false };
20108
- try {
20109
- process.kill(pid, "SIGTERM");
20110
- return { stopped: true };
20111
- } catch {
20112
- return { stopped: false };
20586
+ const svc = serviceController();
20587
+ if (svc !== null) {
20588
+ try {
20589
+ svc.stop(buildServiceSpec());
20590
+ return { stopped: true };
20591
+ } catch {
20592
+ }
20113
20593
  }
20594
+ return { stopped: await signalDaemonStop() };
20114
20595
  },
20115
20596
  async status() {
20116
20597
  const pid = await readDaemonPid();
20117
20598
  const running = pid !== null && isPidAlive(pid);
20118
- return running ? { running, pid, port: DAEMON_PORT } : { running: false, port: DAEMON_PORT };
20599
+ const svc = serviceController();
20600
+ let serviceManager;
20601
+ if (svc !== null) {
20602
+ try {
20603
+ if (svc.isRegistered(buildServiceSpec()))
20604
+ serviceManager = svc.manager;
20605
+ } catch {
20606
+ }
20607
+ }
20608
+ if (running) {
20609
+ return serviceManager !== void 0 ? { running, pid, port: DAEMON_PORT, serviceManager } : { running, pid, port: DAEMON_PORT };
20610
+ }
20611
+ return serviceManager !== void 0 ? { running: false, port: DAEMON_PORT, serviceManager } : { running: false, port: DAEMON_PORT };
20612
+ },
20613
+ async restart() {
20614
+ const svc = serviceController();
20615
+ if (svc !== null && svc.isRegistered(buildServiceSpec())) {
20616
+ try {
20617
+ svc.restart(buildServiceSpec());
20618
+ const ok2 = await waitForHealth(client);
20619
+ return { restarted: ok2, viaService: true };
20620
+ } catch {
20621
+ }
20622
+ }
20623
+ await signalDaemonStop();
20624
+ const { started, alreadyRunning } = await spawnDaemonAndWait(client);
20625
+ return { restarted: started || alreadyRunning, viaService: false };
20119
20626
  }
20120
20627
  };
20121
20628
  }