@indigoai-us/hq-cli 5.108.0 → 5.108.2

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.
Files changed (108) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/assets/mesh-daemon/README.md +23 -0
  3. package/assets/mesh-daemon/launchd.plist.template +33 -0
  4. package/assets/mesh-daemon/systemd.system.service.template +27 -0
  5. package/assets/mesh-daemon/systemd.user.service.template +18 -0
  6. package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +267 -0
  7. package/dist/commands/cloud.d.ts +6 -0
  8. package/dist/commands/cloud.js +26 -3
  9. package/dist/commands/core.js +5 -4
  10. package/dist/commands/mesh.d.ts +15 -0
  11. package/dist/commands/mesh.js +732 -4
  12. package/dist/commands/search.d.ts +23 -0
  13. package/dist/commands/search.js +42 -1
  14. package/dist/lib/doctor/checks/work-context.d.ts +23 -0
  15. package/dist/lib/doctor/checks/work-context.js +195 -0
  16. package/dist/lib/doctor/registry.js +4 -0
  17. package/dist/lib/mesh/api.d.ts +18 -0
  18. package/dist/lib/mesh/api.js +55 -0
  19. package/dist/lib/mesh/cache.js +35 -4
  20. package/dist/lib/mesh/client.d.ts +118 -0
  21. package/dist/lib/mesh/client.js +268 -0
  22. package/dist/lib/mesh/live/backoff.d.ts +16 -0
  23. package/dist/lib/mesh/live/backoff.js +20 -0
  24. package/dist/lib/mesh/live/daemon/board-refresh.d.ts +49 -0
  25. package/dist/lib/mesh/live/daemon/board-refresh.js +185 -0
  26. package/dist/lib/mesh/live/daemon/credentials.d.ts +71 -0
  27. package/dist/lib/mesh/live/daemon/credentials.js +227 -0
  28. package/dist/lib/mesh/live/daemon/doctor.d.ts +37 -0
  29. package/dist/lib/mesh/live/daemon/doctor.js +139 -0
  30. package/dist/lib/mesh/live/daemon/index.d.ts +24 -0
  31. package/dist/lib/mesh/live/daemon/index.js +14 -0
  32. package/dist/lib/mesh/live/daemon/install.d.ts +83 -0
  33. package/dist/lib/mesh/live/daemon/install.js +330 -0
  34. package/dist/lib/mesh/live/daemon/log.d.ts +11 -0
  35. package/dist/lib/mesh/live/daemon/log.js +79 -0
  36. package/dist/lib/mesh/live/daemon/paths.d.ts +18 -0
  37. package/dist/lib/mesh/live/daemon/paths.js +31 -0
  38. package/dist/lib/mesh/live/daemon/pid-lock.d.ts +48 -0
  39. package/dist/lib/mesh/live/daemon/pid-lock.js +134 -0
  40. package/dist/lib/mesh/live/daemon/presence.d.ts +101 -0
  41. package/dist/lib/mesh/live/daemon/presence.js +250 -0
  42. package/dist/lib/mesh/live/daemon/presign.d.ts +18 -0
  43. package/dist/lib/mesh/live/daemon/presign.js +65 -0
  44. package/dist/lib/mesh/live/daemon/run.d.ts +74 -0
  45. package/dist/lib/mesh/live/daemon/run.js +400 -0
  46. package/dist/lib/mesh/live/daemon/state.d.ts +24 -0
  47. package/dist/lib/mesh/live/daemon/state.js +57 -0
  48. package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +170 -0
  49. package/dist/lib/mesh/live/daemon/transcript-watch.js +520 -0
  50. package/dist/lib/mesh/live/enqueue.d.ts +48 -0
  51. package/dist/lib/mesh/live/enqueue.js +100 -0
  52. package/dist/lib/mesh/live/flush.d.ts +56 -0
  53. package/dist/lib/mesh/live/flush.js +360 -0
  54. package/dist/lib/mesh/live/format-spool-line.d.ts +42 -0
  55. package/dist/lib/mesh/live/format-spool-line.js +106 -0
  56. package/dist/lib/mesh/live/index.d.ts +22 -0
  57. package/dist/lib/mesh/live/index.js +16 -0
  58. package/dist/lib/mesh/live/paths.d.ts +13 -0
  59. package/dist/lib/mesh/live/paths.js +30 -0
  60. package/dist/lib/mesh/live/session-events-client.d.ts +58 -0
  61. package/dist/lib/mesh/live/session-events-client.js +120 -0
  62. package/dist/lib/mesh/live/session-identity.d.ts +39 -0
  63. package/dist/lib/mesh/live/session-identity.js +91 -0
  64. package/dist/lib/mesh/live/spool.d.ts +52 -0
  65. package/dist/lib/mesh/live/spool.js +193 -0
  66. package/dist/lib/mesh/live/ulid.d.ts +12 -0
  67. package/dist/lib/mesh/live/ulid.js +39 -0
  68. package/dist/lib/mesh/live/validate-session-event.d.ts +39 -0
  69. package/dist/lib/mesh/live/validate-session-event.js +128 -0
  70. package/dist/lib/search-index/index.d.ts +45 -0
  71. package/dist/lib/search-index/index.js +76 -0
  72. package/dist/lib/work-context/atomic.d.ts +18 -0
  73. package/dist/lib/work-context/atomic.js +132 -0
  74. package/dist/lib/work-context/company.d.ts +103 -0
  75. package/dist/lib/work-context/company.js +274 -0
  76. package/dist/lib/work-context/config.d.ts +59 -0
  77. package/dist/lib/work-context/config.js +172 -0
  78. package/dist/lib/work-context/contract.d.ts +81 -0
  79. package/dist/lib/work-context/contract.js +65 -0
  80. package/dist/lib/work-context/errors.d.ts +27 -0
  81. package/dist/lib/work-context/errors.js +46 -0
  82. package/dist/lib/work-context/index.d.ts +23 -0
  83. package/dist/lib/work-context/index.js +20 -0
  84. package/dist/lib/work-context/migrate.d.ts +84 -0
  85. package/dist/lib/work-context/migrate.js +287 -0
  86. package/dist/lib/work-context/organize.d.ts +130 -0
  87. package/dist/lib/work-context/organize.js +471 -0
  88. package/dist/lib/work-context/outbox.d.ts +97 -0
  89. package/dist/lib/work-context/outbox.js +357 -0
  90. package/dist/lib/work-context/paths.d.ts +14 -0
  91. package/dist/lib/work-context/paths.js +39 -0
  92. package/dist/lib/work-context/project.d.ts +78 -0
  93. package/dist/lib/work-context/project.js +247 -0
  94. package/dist/lib/work-context/reconcile.d.ts +85 -0
  95. package/dist/lib/work-context/reconcile.js +651 -0
  96. package/dist/lib/work-context/repo-remote.d.ts +43 -0
  97. package/dist/lib/work-context/repo-remote.js +200 -0
  98. package/dist/lib/work-context/state.d.ts +112 -0
  99. package/dist/lib/work-context/state.js +162 -0
  100. package/dist/main.js +40 -3
  101. package/dist/utils/cognito-session.d.ts +85 -9
  102. package/dist/utils/cognito-session.js +216 -9
  103. package/dist/utils/qmd-model-download-error.d.ts +27 -0
  104. package/dist/utils/qmd-model-download-error.js +114 -0
  105. package/dist/utils/qmd-query-document-error.d.ts +23 -0
  106. package/dist/utils/qmd-query-document-error.js +117 -0
  107. package/dist/utils/sentry-fingerprint.js +2 -0
  108. package/package.json +5 -1
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Contract-3 realtime credential vend + 80%-of-lifetime renewal.
3
+ * Never logs or persists credential material.
4
+ */
5
+ import { DEFAULT_VAULT_API_URL } from "../../../../utils/cognito-session.js";
6
+ import { vaultApiFetch } from "../../../../utils/vault-api.js";
7
+ import { readAgentIdentityCompanyUid } from "../../../work-context/company.js";
8
+ export const REALTIME_CREDENTIALS_PATH = "/v1/realtime/credentials";
9
+ export const MQTT_KEEPALIVE_SECONDS = 15;
10
+ /** Renew at 80% of credential lifetime. */
11
+ export const CREDENTIAL_RENEWAL_FRACTION = 0.8;
12
+ export const MIN_RENEWAL_DELAY_MS = 5_000;
13
+ export const realTimerHost = {
14
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
15
+ clearTimeout: (h) => clearTimeout(h),
16
+ now: () => Date.now(),
17
+ };
18
+ export function renewalDelayMs(nowMs, expirationIso) {
19
+ const expMs = Date.parse(expirationIso);
20
+ if (Number.isNaN(expMs))
21
+ return 30 * 60_000;
22
+ const lifetime = expMs - nowMs;
23
+ return Math.max(MIN_RENEWAL_DELAY_MS, lifetime * CREDENTIAL_RENEWAL_FRACTION);
24
+ }
25
+ function actorUidFromTopic(topic) {
26
+ if (typeof topic !== "string")
27
+ return undefined;
28
+ const m = /^hq\/[^/]+\/presence\/((?:prs|agt)_[A-Za-z0-9]+)$/.exec(topic.trim());
29
+ return m?.[1];
30
+ }
31
+ function actorUidFromPersonalTopic(topic) {
32
+ if (typeof topic !== "string")
33
+ return undefined;
34
+ const m = /^hq\/((?:prs|agt)_[A-Za-z0-9]+)\//.exec(topic.trim());
35
+ return m?.[1];
36
+ }
37
+ export function normalizeContract3Bundle(raw) {
38
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
39
+ throw new Error("contract-3 vend response is not an object");
40
+ }
41
+ const r = raw;
42
+ if (r.contractVersion !== 3) {
43
+ throw new Error(`expected contractVersion 3, got ${String(r.contractVersion)}`);
44
+ }
45
+ const creds = r.credentials;
46
+ if (!creds ||
47
+ typeof creds.accessKeyId !== "string" ||
48
+ typeof creds.secretAccessKey !== "string" ||
49
+ typeof creds.sessionToken !== "string") {
50
+ throw new Error("contract-3 vend missing credentials");
51
+ }
52
+ if (typeof r.clientId !== "string" || !r.clientId.trim()) {
53
+ throw new Error("contract-3 vend missing clientId");
54
+ }
55
+ if (typeof r.iotEndpoint !== "string" || !r.iotEndpoint.trim()) {
56
+ throw new Error("contract-3 vend missing iotEndpoint");
57
+ }
58
+ if (typeof r.region !== "string" || !r.region.trim()) {
59
+ throw new Error("contract-3 vend missing region");
60
+ }
61
+ const companiesRaw = Array.isArray(r.companies) ? r.companies : [];
62
+ const companies = [];
63
+ for (const item of companiesRaw) {
64
+ if (!item || typeof item !== "object" || Array.isArray(item))
65
+ continue;
66
+ const row = item;
67
+ if (typeof row.companyUid !== "string" || typeof row.presenceTopic !== "string") {
68
+ continue;
69
+ }
70
+ companies.push({
71
+ companyUid: row.companyUid,
72
+ presenceTopic: row.presenceTopic,
73
+ });
74
+ }
75
+ if (companies.length === 0) {
76
+ throw new Error("contract-3 vend returned no companies");
77
+ }
78
+ const topics = r.topics && typeof r.topics === "object" && !Array.isArray(r.topics)
79
+ ? r.topics
80
+ : null;
81
+ const actorUid = actorUidFromTopic(companies[0]?.presenceTopic) ||
82
+ actorUidFromPersonalTopic(topics?.dm) ||
83
+ actorUidFromPersonalTopic(r.topic) ||
84
+ "";
85
+ if (!actorUid) {
86
+ throw new Error("contract-3 vend could not derive actorUid");
87
+ }
88
+ const actorType = actorUid.startsWith("agt_")
89
+ ? "agent"
90
+ : "human";
91
+ const expiresAt = (typeof r.expiresAt === "string" && r.expiresAt) ||
92
+ (typeof creds.expiration === "string" && creds.expiration) ||
93
+ "";
94
+ if (!expiresAt) {
95
+ throw new Error("contract-3 vend missing expiresAt");
96
+ }
97
+ const dropped = Array.isArray(r.droppedCompanies)
98
+ ? r.droppedCompanies.filter((x) => typeof x === "string")
99
+ : [];
100
+ return {
101
+ contractVersion: 3,
102
+ credentials: {
103
+ accessKeyId: creds.accessKeyId,
104
+ secretAccessKey: creds.secretAccessKey,
105
+ sessionToken: creds.sessionToken,
106
+ expiration: expiresAt,
107
+ },
108
+ iotEndpoint: r.iotEndpoint,
109
+ region: r.region,
110
+ clientId: r.clientId,
111
+ actorUid,
112
+ actorType,
113
+ companies,
114
+ droppedCompanies: dropped,
115
+ expiresAt,
116
+ };
117
+ }
118
+ /**
119
+ * When `HQ_AGENT_IDENTITY_FILE` names a companyUid, keep only that company
120
+ * from the vend (must be present) and refuse every other company. Presence
121
+ * and session events then attribute to the box agent for its company only.
122
+ * No identity file → return the bundle unchanged (person laptop).
123
+ */
124
+ export function scopeBundleToAgentIdentity(bundle, env = process.env) {
125
+ const identityUid = readAgentIdentityCompanyUid(env);
126
+ if (!identityUid)
127
+ return bundle;
128
+ const matched = bundle.companies.filter((c) => c.companyUid === identityUid);
129
+ if (matched.length === 0) {
130
+ throw new Error(`identity companyUid is not in the realtime vend; refusing to publish`);
131
+ }
132
+ return { ...bundle, companies: matched };
133
+ }
134
+ export function createContract3Fetcher(opts) {
135
+ return async () => {
136
+ let bundle;
137
+ if (opts.post) {
138
+ const res = await opts.post(REALTIME_CREDENTIALS_PATH, {
139
+ contractVersion: 3,
140
+ });
141
+ if (res.status < 200 || res.status >= 300) {
142
+ throw new Error(`credential vend failed: HTTP ${res.status}`);
143
+ }
144
+ bundle = normalizeContract3Bundle(res.body);
145
+ }
146
+ else {
147
+ const res = await vaultApiFetch({
148
+ token: opts.token,
149
+ path: REALTIME_CREDENTIALS_PATH,
150
+ method: "POST",
151
+ body: { contractVersion: 3 },
152
+ baseUrl: opts.baseUrl ?? DEFAULT_VAULT_API_URL,
153
+ });
154
+ const text = await res.text();
155
+ let body = {};
156
+ if (text) {
157
+ try {
158
+ body = JSON.parse(text);
159
+ }
160
+ catch {
161
+ body = { raw: text.slice(0, 200) };
162
+ }
163
+ }
164
+ if (!res.ok) {
165
+ throw new Error(`credential vend failed: HTTP ${res.status}`);
166
+ }
167
+ bundle = normalizeContract3Bundle(body);
168
+ }
169
+ return scopeBundleToAgentIdentity(bundle, opts.env ?? process.env);
170
+ };
171
+ }
172
+ /**
173
+ * Proactive renewal: at 80% of lifetime, fetch fresh creds and call onRenewed.
174
+ * Does not drop the connection itself — caller reconnects with the new URL.
175
+ */
176
+ export class CredentialRenewalManager {
177
+ fetchCredentials;
178
+ onRenewed;
179
+ onError;
180
+ timers;
181
+ retryDelayMs;
182
+ handle = null;
183
+ stopped = false;
184
+ constructor(fetchCredentials, onRenewed, onError = () => { }, timers = realTimerHost, retryDelayMs = 30_000) {
185
+ this.fetchCredentials = fetchCredentials;
186
+ this.onRenewed = onRenewed;
187
+ this.onError = onError;
188
+ this.timers = timers;
189
+ this.retryDelayMs = retryDelayMs;
190
+ }
191
+ schedule(bundle) {
192
+ if (this.stopped)
193
+ return;
194
+ this.clear();
195
+ const delay = renewalDelayMs(this.timers.now(), bundle.expiresAt);
196
+ this.handle = this.timers.setTimeout(() => void this.renew(), delay);
197
+ }
198
+ async renew() {
199
+ if (this.stopped)
200
+ return;
201
+ try {
202
+ const bundle = await this.fetchCredentials();
203
+ if (this.stopped)
204
+ return;
205
+ this.onRenewed(bundle);
206
+ this.schedule(bundle);
207
+ }
208
+ catch (err) {
209
+ this.onError(err);
210
+ if (this.stopped)
211
+ return;
212
+ this.clear();
213
+ this.handle = this.timers.setTimeout(() => void this.renew(), this.retryDelayMs);
214
+ }
215
+ }
216
+ stop() {
217
+ this.stopped = true;
218
+ this.clear();
219
+ }
220
+ clear() {
221
+ if (this.handle !== null) {
222
+ this.timers.clearTimeout(this.handle);
223
+ this.handle = null;
224
+ }
225
+ }
226
+ }
227
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * hq mesh daemon doctor — running/MQTT/companies/spool/held/dead-letter/
3
+ * outbox/last-flush, and unhealthy when unacked spool age > 60s while online.
4
+ */
5
+ import { type CognitoActorKind, type CognitoTokenSource } from "../../../../utils/cognito-session.js";
6
+ import { type DaemonStateFile } from "./state.js";
7
+ export declare const UNHEALTHY_SPOOL_AGE_MS = 60000;
8
+ export interface DaemonDoctorReport {
9
+ running: boolean;
10
+ pid?: number;
11
+ mqttState: string;
12
+ companiesOnline: string[];
13
+ spoolDepth: number;
14
+ heldCount: number;
15
+ deadLetterCount: number;
16
+ outboxDepth: number;
17
+ lastFlushAt?: string;
18
+ lastFlushResult?: DaemonStateFile["lastFlushResult"];
19
+ unhealthy: boolean;
20
+ unhealthyReasons: string[];
21
+ oldestSpoolAgeMs?: number;
22
+ /** person login cache vs machine-creds mint. */
23
+ tokenSource: CognitoTokenSource;
24
+ /** human | agent | unknown — from token claims / machine signal. */
25
+ actorKind: CognitoActorKind;
26
+ }
27
+ export interface DaemonDoctorDeps {
28
+ home?: string;
29
+ meshRoot?: string;
30
+ workContextRoot?: string;
31
+ env?: NodeJS.ProcessEnv;
32
+ isProcessAlive?: (pid: number) => boolean;
33
+ now?: () => number;
34
+ }
35
+ export declare function collectDaemonDoctor(deps?: DaemonDoctorDeps): DaemonDoctorReport;
36
+ export declare function formatDaemonDoctor(report: DaemonDoctorReport): string[];
37
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1,139 @@
1
+ /**
2
+ * hq mesh daemon doctor — running/MQTT/companies/spool/held/dead-letter/
3
+ * outbox/last-flush, and unhealthy when unacked spool age > 60s while online.
4
+ */
5
+ import * as fs from "node:fs";
6
+ import * as os from "node:os";
7
+ import { describeCognitoTokenSource, } from "../../../../utils/cognito-session.js";
8
+ import { outboxStats, } from "../../../work-context/outbox.js";
9
+ import { workContextRoot } from "../../../work-context/paths.js";
10
+ import { countJsonlLines, } from "../spool.js";
11
+ import { workMeshDeadLetterPath, workMeshHeldPath, workMeshRoot, workMeshSpoolPath, } from "../paths.js";
12
+ import { daemonDir, daemonPidPath } from "./paths.js";
13
+ import { pidLockStatus, readPidLock } from "./pid-lock.js";
14
+ import { readDaemonState } from "./state.js";
15
+ export const UNHEALTHY_SPOOL_AGE_MS = 60_000;
16
+ function oldestJsonlAgeMs(filePath, nowMs) {
17
+ if (!fs.existsSync(filePath))
18
+ return undefined;
19
+ try {
20
+ const text = fs.readFileSync(filePath, "utf8");
21
+ let oldest;
22
+ for (const line of text.split("\n")) {
23
+ if (!line.trim())
24
+ continue;
25
+ try {
26
+ const obj = JSON.parse(line);
27
+ const at = (typeof obj.at === "string" && obj.at) ||
28
+ (typeof obj.heldAt === "string" && obj.heldAt) ||
29
+ (obj.event && typeof obj.event.at === "string" && obj.event.at) ||
30
+ undefined;
31
+ if (!at)
32
+ continue;
33
+ const t = Date.parse(at);
34
+ if (!Number.isFinite(t))
35
+ continue;
36
+ const age = nowMs - t;
37
+ if (oldest === undefined || age > oldest)
38
+ oldest = age;
39
+ }
40
+ catch {
41
+ /* skip */
42
+ }
43
+ }
44
+ return oldest;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ export function collectDaemonDoctor(deps = {}) {
51
+ const home = deps.home ?? os.homedir();
52
+ const env = deps.env ?? process.env;
53
+ const meshRoot = deps.meshRoot ?? workMeshRoot(home, env);
54
+ const ctxRoot = deps.workContextRoot ?? workContextRoot(home, env);
55
+ const dir = daemonDir(meshRoot, home, env);
56
+ const now = deps.now ?? Date.now;
57
+ const isProcessAlive = deps.isProcessAlive ??
58
+ ((pid) => {
59
+ try {
60
+ process.kill(pid, 0);
61
+ return true;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ });
67
+ const lockDeps = { isProcessAlive };
68
+ const lockPath = daemonPidPath(dir);
69
+ const lock = pidLockStatus(lockPath, lockDeps);
70
+ const record = readPidLock(lockPath);
71
+ const running = lock === "held";
72
+ const state = readDaemonState(dir);
73
+ const spoolPath = workMeshSpoolPath(meshRoot);
74
+ const heldPath = workMeshHeldPath(meshRoot);
75
+ const deadPath = workMeshDeadLetterPath(meshRoot);
76
+ const spoolDepth = countJsonlLines(spoolPath);
77
+ const heldCount = countJsonlLines(heldPath);
78
+ const deadLetterCount = countJsonlLines(deadPath);
79
+ const outbox = outboxStats(ctxRoot);
80
+ const mqttState = state?.mqttState ?? (running ? "unknown" : "closed");
81
+ const companiesOnline = state?.companiesOnline ?? [];
82
+ const oldestSpoolAgeMs = oldestJsonlAgeMs(spoolPath, now());
83
+ const unhealthyReasons = [];
84
+ const online = mqttState === "connected" && companiesOnline.length > 0;
85
+ if (online &&
86
+ spoolDepth > 0 &&
87
+ oldestSpoolAgeMs !== undefined &&
88
+ oldestSpoolAgeMs > UNHEALTHY_SPOOL_AGE_MS) {
89
+ unhealthyReasons.push(`unacknowledged spool age ${Math.round(oldestSpoolAgeMs / 1000)}s exceeds 60s while online`);
90
+ }
91
+ if (deadLetterCount > 0) {
92
+ unhealthyReasons.push(`dead-letter count=${deadLetterCount}`);
93
+ }
94
+ const auth = describeCognitoTokenSource({ home, env });
95
+ return {
96
+ running,
97
+ pid: running ? record?.pid : undefined,
98
+ mqttState,
99
+ companiesOnline,
100
+ spoolDepth,
101
+ heldCount,
102
+ deadLetterCount,
103
+ outboxDepth: outbox.depth,
104
+ lastFlushAt: state?.lastFlushAt,
105
+ lastFlushResult: state?.lastFlushResult,
106
+ unhealthy: unhealthyReasons.length > 0,
107
+ unhealthyReasons,
108
+ oldestSpoolAgeMs,
109
+ tokenSource: auth.tokenSource,
110
+ actorKind: auth.actorKind,
111
+ };
112
+ }
113
+ export function formatDaemonDoctor(report) {
114
+ const lines = [
115
+ `daemon: ${report.running ? `running pid=${report.pid}` : "not running"}`,
116
+ `mqtt: ${report.mqttState}`,
117
+ `companies online: ${report.companiesOnline.length ? report.companiesOnline.join(", ") : "(none)"}`,
118
+ `spool depth: ${report.spoolDepth}`,
119
+ `held: ${report.heldCount}`,
120
+ `dead-letter: ${report.deadLetterCount}`,
121
+ `outbox depth: ${report.outboxDepth}`,
122
+ `token source: ${report.tokenSource}`,
123
+ `actor kind: ${report.actorKind}`,
124
+ `last flush: ${report.lastFlushAt ?? "(never)"}${report.lastFlushResult
125
+ ? ` ok=${report.lastFlushResult.ok} posted=${report.lastFlushResult.posted}`
126
+ : ""}`,
127
+ ];
128
+ if (report.unhealthy) {
129
+ lines.push(`health: UNHEALTHY`);
130
+ for (const reason of report.unhealthyReasons) {
131
+ lines.push(` - ${reason}`);
132
+ }
133
+ }
134
+ else {
135
+ lines.push(`health: ok`);
136
+ }
137
+ return lines;
138
+ }
139
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,24 @@
1
+ export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME, DAEMON_STATE_NAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, daemonDir, daemonLogPath, daemonPidPath, daemonStatePath, sessionBoardPath, } from "./paths.js";
2
+ export { acquirePidLock, defaultPidLockDeps, parsePidLock, pidLockStatus, readPidLock, releasePidLock, resolveDaemonDir, } from "./pid-lock.js";
3
+ export type { PidLockDeps, PidLockRecord, PidLockResult } from "./pid-lock.js";
4
+ export { CREDENTIAL_RENEWAL_FRACTION, MQTT_KEEPALIVE_SECONDS, REALTIME_CREDENTIALS_PATH, CredentialRenewalManager, createContract3Fetcher, normalizeContract3Bundle, realTimerHost, renewalDelayMs, } from "./credentials.js";
5
+ export type { Contract3Bundle, CredentialsFetcher, PresenceCompany, TimerHost, } from "./credentials.js";
6
+ export { amzDateOf, hex, presignIotWssUrl, rfc3986Encode } from "./presign.js";
7
+ export type { IotCredentials } from "./presign.js";
8
+ export { PresenceClient, backoffDelayMs, buildPresencePayload, defaultMqttConnect, isOwnPresenceTopic, } from "./presence.js";
9
+ export type { MeshMqttClientLike, MqttConnectFn, MqttConnectionState, PresenceClientOptions, PresencePayload, } from "./presence.js";
10
+ export { defaultDaemonState, patchDaemonState, readDaemonState, writeDaemonState, } from "./state.js";
11
+ export type { DaemonStateFile } from "./state.js";
12
+ export { appendDaemonLog, daemonLogLine, ensureDaemonLog, resolveDaemonAssetDir, rotateDaemonLogIfNeeded, } from "./log.js";
13
+ export { BOARD_REFRESH_INTERVAL_MS, createVaultBoardReader, formatBoardMarkdown, refreshBoundSessionBoards, writeBoardMarkdown, } from "./board-refresh.js";
14
+ export type { BoardReader, BoardRefreshDeps, BoardStorySnapshot } from "./board-refresh.js";
15
+ export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonService, readAssetTemplate, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveHqBinary, resolveNodeBinary, uninstallDaemonService, } from "./install.js";
16
+ export type { DaemonInstallPlatform, InstallPaths, InstallResult, } from "./install.js";
17
+ export { UNHEALTHY_SPOOL_AGE_MS, collectDaemonDoctor, formatDaemonDoctor, } from "./doctor.js";
18
+ export type { DaemonDoctorDeps, DaemonDoctorReport } from "./doctor.js";
19
+ export { scopeBundleToAgentIdentity, } from "./credentials.js";
20
+ export { FLUSH_INTERVAL_MS, SPOOL_DEBOUNCE_MS, runMeshDaemon, } from "./run.js";
21
+ export type { DaemonHandle, DaemonRunDeps } from "./run.js";
22
+ export { TRANSCRIPT_ADAPTER_VERSION, TRANSCRIPT_EVENT_SOURCE, TRANSCRIPT_FRESH_MS, TRANSCRIPT_HOOK_COVER_MS, TRANSCRIPT_MARKER, TRANSCRIPT_SESSION_END_MS, TRANSCRIPT_TURN_QUIET_MS, TRANSCRIPT_WATCH_INTERVAL_MS, TranscriptWatcher, decodeClaudeProjectDirName, defaultTranscriptFs, discoverTranscripts, isTranscriptHookCovered, noteHookSessionsFromSpoolFile, resolveTranscriptRegistration, sessionIdFromClaudeTranscriptPath, sessionIdFromCodexTranscriptPath, } from "./transcript-watch.js";
23
+ export type { DiscoveredTranscript, TranscriptFs, TranscriptKind, TranscriptWatchDeps, TranscriptWatchTickResult, } from "./transcript-watch.js";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,14 @@
1
+ export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME, DAEMON_STATE_NAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, daemonDir, daemonLogPath, daemonPidPath, daemonStatePath, sessionBoardPath, } from "./paths.js";
2
+ export { acquirePidLock, defaultPidLockDeps, parsePidLock, pidLockStatus, readPidLock, releasePidLock, resolveDaemonDir, } from "./pid-lock.js";
3
+ export { CREDENTIAL_RENEWAL_FRACTION, MQTT_KEEPALIVE_SECONDS, REALTIME_CREDENTIALS_PATH, CredentialRenewalManager, createContract3Fetcher, normalizeContract3Bundle, realTimerHost, renewalDelayMs, } from "./credentials.js";
4
+ export { amzDateOf, hex, presignIotWssUrl, rfc3986Encode } from "./presign.js";
5
+ export { PresenceClient, backoffDelayMs, buildPresencePayload, defaultMqttConnect, isOwnPresenceTopic, } from "./presence.js";
6
+ export { defaultDaemonState, patchDaemonState, readDaemonState, writeDaemonState, } from "./state.js";
7
+ export { appendDaemonLog, daemonLogLine, ensureDaemonLog, resolveDaemonAssetDir, rotateDaemonLogIfNeeded, } from "./log.js";
8
+ export { BOARD_REFRESH_INTERVAL_MS, createVaultBoardReader, formatBoardMarkdown, refreshBoundSessionBoards, writeBoardMarkdown, } from "./board-refresh.js";
9
+ export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonService, readAssetTemplate, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveHqBinary, resolveNodeBinary, uninstallDaemonService, } from "./install.js";
10
+ export { UNHEALTHY_SPOOL_AGE_MS, collectDaemonDoctor, formatDaemonDoctor, } from "./doctor.js";
11
+ export { scopeBundleToAgentIdentity, } from "./credentials.js";
12
+ export { FLUSH_INTERVAL_MS, SPOOL_DEBOUNCE_MS, runMeshDaemon, } from "./run.js";
13
+ export { TRANSCRIPT_ADAPTER_VERSION, TRANSCRIPT_EVENT_SOURCE, TRANSCRIPT_FRESH_MS, TRANSCRIPT_HOOK_COVER_MS, TRANSCRIPT_MARKER, TRANSCRIPT_SESSION_END_MS, TRANSCRIPT_TURN_QUIET_MS, TRANSCRIPT_WATCH_INTERVAL_MS, TranscriptWatcher, decodeClaudeProjectDirName, defaultTranscriptFs, discoverTranscripts, isTranscriptHookCovered, noteHookSessionsFromSpoolFile, resolveTranscriptRegistration, sessionIdFromClaudeTranscriptPath, sessionIdFromCodexTranscriptPath, } from "./transcript-watch.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,83 @@
1
+ /**
2
+ * User-level service install for hq mesh daemon.
3
+ *
4
+ * - macOS: LaunchAgent ai.getindigo.hq-mesh-daemon
5
+ * - Linux: systemd user unit hq-mesh-daemon.service
6
+ * (documented fallback: root-owned unit running as ec2-user)
7
+ *
8
+ * The unit embeds the hq binary resolved at install time. Logs go to
9
+ * ~/.hq/work-mesh/daemon/log (rotation at 5MB handled by the daemon).
10
+ * Tests must not actually install services — use a temp HOME/prefix.
11
+ */
12
+ import * as fs from "node:fs";
13
+ export type DaemonInstallPlatform = "darwin" | "linux" | "other";
14
+ export interface InstallPaths {
15
+ home: string;
16
+ hqBinary: string;
17
+ nodeBinary: string;
18
+ daemonDir: string;
19
+ logPath: string;
20
+ launchAgentsDir: string;
21
+ plistPath: string;
22
+ systemdUserDir: string;
23
+ systemdUnitPath: string;
24
+ /** When true, render the root/ec2-user system unit instead of user unit. */
25
+ systemUser?: string;
26
+ }
27
+ export interface InstallResult {
28
+ platform: DaemonInstallPlatform;
29
+ action: "install" | "uninstall" | "status";
30
+ paths: InstallPaths;
31
+ rendered?: string;
32
+ dest?: string;
33
+ running?: boolean;
34
+ message: string;
35
+ }
36
+ export declare function detectPlatform(platform?: NodeJS.Platform): DaemonInstallPlatform;
37
+ /** Resolve the hq binary the current shell uses (argv[1] or PATH). */
38
+ export declare function resolveHqBinary(argv?: string[], env?: NodeJS.ProcessEnv): string;
39
+ export declare function resolveNodeBinary(execPath?: string, env?: NodeJS.ProcessEnv): string;
40
+ export declare function buildInstallPaths(opts: {
41
+ home?: string;
42
+ hqBinary?: string;
43
+ nodeBinary?: string;
44
+ meshRoot?: string;
45
+ systemUser?: string;
46
+ env?: NodeJS.ProcessEnv;
47
+ argv?: string[];
48
+ execPath?: string;
49
+ }): InstallPaths;
50
+ export declare function renderLaunchdPlist(paths: InstallPaths): string;
51
+ export declare function renderSystemdUserUnit(paths: InstallPaths): string;
52
+ /**
53
+ * Root-owned unit running as ec2-user (agent boxes without a user session bus).
54
+ * Documented fallback — written only when --system-user is passed.
55
+ */
56
+ export declare function renderSystemdSystemUnit(paths: InstallPaths): string;
57
+ export declare function installDaemonService(opts: {
58
+ platform?: DaemonInstallPlatform;
59
+ paths?: InstallPaths;
60
+ /** When set, write the system unit template (does not sudo-install). */
61
+ systemUser?: string;
62
+ writeFileSync?: typeof fs.writeFileSync;
63
+ mkdirSync?: typeof fs.mkdirSync;
64
+ /** Optional: actually load the service (disabled in tests). */
65
+ activate?: (platform: DaemonInstallPlatform, dest: string) => void;
66
+ }): InstallResult;
67
+ export declare function uninstallDaemonService(opts: {
68
+ platform?: DaemonInstallPlatform;
69
+ paths?: InstallPaths;
70
+ unlinkSync?: typeof fs.unlinkSync;
71
+ existsSync?: typeof fs.existsSync;
72
+ deactivate?: (platform: DaemonInstallPlatform, dest: string) => void;
73
+ }): InstallResult;
74
+ export declare function daemonServiceStatus(opts: {
75
+ platform?: DaemonInstallPlatform;
76
+ paths?: InstallPaths;
77
+ existsSync?: typeof fs.existsSync;
78
+ /** Optional live probe (launchctl/systemctl); tests inject. */
79
+ isLoaded?: (platform: DaemonInstallPlatform, dest: string) => boolean;
80
+ }): InstallResult;
81
+ /** Read baked template files from assets/ (optional; renderers are primary). */
82
+ export declare function readAssetTemplate(name: "launchd.plist.template" | "systemd.user.service.template" | "systemd.system.service.template"): string | null;
83
+ //# sourceMappingURL=install.d.ts.map