@indigoai-us/hq-cli 5.108.1 → 5.108.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/CHANGELOG.md +79 -0
- package/assets/mesh-daemon/README.md +23 -0
- package/assets/mesh-daemon/launchd.plist.template +33 -0
- package/assets/mesh-daemon/systemd.system.service.template +27 -0
- package/assets/mesh-daemon/systemd.user.service.template +18 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +267 -0
- package/dist/commands/core.js +5 -4
- package/dist/commands/index-cmd.js +13 -0
- package/dist/commands/mesh.d.ts +15 -0
- package/dist/commands/mesh.js +732 -4
- package/dist/commands/search.d.ts +23 -0
- package/dist/commands/search.js +42 -1
- package/dist/lib/doctor/checks/work-context.d.ts +23 -0
- package/dist/lib/doctor/checks/work-context.js +195 -0
- package/dist/lib/doctor/registry.js +4 -0
- package/dist/lib/mesh/api.d.ts +18 -0
- package/dist/lib/mesh/api.js +55 -0
- package/dist/lib/mesh/cache.js +35 -4
- package/dist/lib/mesh/client.d.ts +118 -0
- package/dist/lib/mesh/client.js +268 -0
- package/dist/lib/mesh/live/backoff.d.ts +16 -0
- package/dist/lib/mesh/live/backoff.js +20 -0
- package/dist/lib/mesh/live/daemon/board-refresh.d.ts +49 -0
- package/dist/lib/mesh/live/daemon/board-refresh.js +185 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +71 -0
- package/dist/lib/mesh/live/daemon/credentials.js +227 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +37 -0
- package/dist/lib/mesh/live/daemon/doctor.js +139 -0
- package/dist/lib/mesh/live/daemon/index.d.ts +24 -0
- package/dist/lib/mesh/live/daemon/index.js +14 -0
- package/dist/lib/mesh/live/daemon/install.d.ts +83 -0
- package/dist/lib/mesh/live/daemon/install.js +330 -0
- package/dist/lib/mesh/live/daemon/log.d.ts +11 -0
- package/dist/lib/mesh/live/daemon/log.js +79 -0
- package/dist/lib/mesh/live/daemon/paths.d.ts +18 -0
- package/dist/lib/mesh/live/daemon/paths.js +31 -0
- package/dist/lib/mesh/live/daemon/pid-lock.d.ts +48 -0
- package/dist/lib/mesh/live/daemon/pid-lock.js +134 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +101 -0
- package/dist/lib/mesh/live/daemon/presence.js +250 -0
- package/dist/lib/mesh/live/daemon/presign.d.ts +18 -0
- package/dist/lib/mesh/live/daemon/presign.js +65 -0
- package/dist/lib/mesh/live/daemon/run.d.ts +74 -0
- package/dist/lib/mesh/live/daemon/run.js +400 -0
- package/dist/lib/mesh/live/daemon/state.d.ts +24 -0
- package/dist/lib/mesh/live/daemon/state.js +57 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +170 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.js +520 -0
- package/dist/lib/mesh/live/enqueue.d.ts +48 -0
- package/dist/lib/mesh/live/enqueue.js +100 -0
- package/dist/lib/mesh/live/flush.d.ts +56 -0
- package/dist/lib/mesh/live/flush.js +360 -0
- package/dist/lib/mesh/live/format-spool-line.d.ts +42 -0
- package/dist/lib/mesh/live/format-spool-line.js +106 -0
- package/dist/lib/mesh/live/index.d.ts +22 -0
- package/dist/lib/mesh/live/index.js +16 -0
- package/dist/lib/mesh/live/paths.d.ts +13 -0
- package/dist/lib/mesh/live/paths.js +30 -0
- package/dist/lib/mesh/live/session-events-client.d.ts +58 -0
- package/dist/lib/mesh/live/session-events-client.js +120 -0
- package/dist/lib/mesh/live/session-identity.d.ts +39 -0
- package/dist/lib/mesh/live/session-identity.js +91 -0
- package/dist/lib/mesh/live/spool.d.ts +52 -0
- package/dist/lib/mesh/live/spool.js +193 -0
- package/dist/lib/mesh/live/ulid.d.ts +12 -0
- package/dist/lib/mesh/live/ulid.js +39 -0
- package/dist/lib/mesh/live/validate-session-event.d.ts +39 -0
- package/dist/lib/mesh/live/validate-session-event.js +128 -0
- package/dist/lib/search-index/index.d.ts +98 -0
- package/dist/lib/search-index/index.js +174 -2
- package/dist/lib/work-context/atomic.d.ts +18 -0
- package/dist/lib/work-context/atomic.js +132 -0
- package/dist/lib/work-context/company.d.ts +103 -0
- package/dist/lib/work-context/company.js +274 -0
- package/dist/lib/work-context/config.d.ts +59 -0
- package/dist/lib/work-context/config.js +172 -0
- package/dist/lib/work-context/contract.d.ts +81 -0
- package/dist/lib/work-context/contract.js +65 -0
- package/dist/lib/work-context/errors.d.ts +27 -0
- package/dist/lib/work-context/errors.js +46 -0
- package/dist/lib/work-context/index.d.ts +23 -0
- package/dist/lib/work-context/index.js +20 -0
- package/dist/lib/work-context/migrate.d.ts +84 -0
- package/dist/lib/work-context/migrate.js +287 -0
- package/dist/lib/work-context/organize.d.ts +130 -0
- package/dist/lib/work-context/organize.js +471 -0
- package/dist/lib/work-context/outbox.d.ts +97 -0
- package/dist/lib/work-context/outbox.js +357 -0
- package/dist/lib/work-context/paths.d.ts +14 -0
- package/dist/lib/work-context/paths.js +39 -0
- package/dist/lib/work-context/project.d.ts +78 -0
- package/dist/lib/work-context/project.js +247 -0
- package/dist/lib/work-context/reconcile.d.ts +85 -0
- package/dist/lib/work-context/reconcile.js +651 -0
- package/dist/lib/work-context/repo-remote.d.ts +43 -0
- package/dist/lib/work-context/repo-remote.js +200 -0
- package/dist/lib/work-context/state.d.ts +112 -0
- package/dist/lib/work-context/state.js +162 -0
- package/dist/main.js +93 -4
- package/dist/utils/cognito-session.d.ts +85 -9
- package/dist/utils/cognito-session.js +216 -9
- package/dist/utils/qmd-model-download-error.d.ts +27 -0
- package/dist/utils/qmd-model-download-error.js +114 -0
- package/dist/utils/qmd-query-document-error.d.ts +23 -0
- package/dist/utils/qmd-query-document-error.js +117 -0
- package/dist/utils/qmd-workdir-missing-error.d.ts +18 -0
- package/dist/utils/qmd-workdir-missing-error.js +78 -0
- package/dist/utils/sentry-fingerprint.js +7 -0
- package/package.json +5 -1
package/dist/commands/mesh.js
CHANGED
|
@@ -5,9 +5,25 @@
|
|
|
5
5
|
* require hq-pack-work-mesh on disk and does not start MQTT listen.
|
|
6
6
|
* Distinct from `hq doctor` (hook guardrails).
|
|
7
7
|
*/
|
|
8
|
+
import * as os from "node:os";
|
|
8
9
|
import chalk from "chalk";
|
|
9
10
|
import { loadCachedTokens } from "../utils/cognito-session.js";
|
|
10
|
-
import
|
|
11
|
+
import * as readline from "node:readline/promises";
|
|
12
|
+
import { STORY_STATUSES, appendThreadEvent, callerLabelFromToken, ensureProjectThread, eventPayload, listActiveThreads, patchStoryStatus, resolveActiveMembershipCompany, resolveMeshPrincipalUid, warmMeshConversationCache, } from "../lib/mesh/api.js";
|
|
13
|
+
import { createCandidatesFetcher, createMigratePoster, createOrganizePoster, createWorkSessionDeliverer, fetchCompanyLive, formatCompanyLiveTable, openMeshTransport, probeMigrationCapabilityForMemberships, requireToken, } from "../lib/mesh/client.js";
|
|
14
|
+
import { clearDefaultCompany, getDefaultCompany, readDeviceConfig, recordMigrationCapabilitySnapshot, setDefaultCompany, } from "../lib/work-context/config.js";
|
|
15
|
+
import { DefaultCompanyLockedError, DefaultCompanyUnavailableError, } from "../lib/work-context/errors.js";
|
|
16
|
+
import { isValidSessionId } from "../lib/mesh/live/session-identity.js";
|
|
17
|
+
import { CLI_KIND_TO_SCHEMA, EnqueueValidationError, enqueueSessionEvent, } from "../lib/mesh/live/index.js";
|
|
18
|
+
import { flushSessionEvents } from "../lib/mesh/live/flush.js";
|
|
19
|
+
import { createSessionEventsPoster, resolveVaultApiBase, } from "../lib/mesh/live/session-events-client.js";
|
|
20
|
+
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
21
|
+
import { buildInstallPaths, collectDaemonDoctor, daemonServiceStatus, detectPlatform, formatDaemonDoctor, installDaemonService, runMeshDaemon, uninstallDaemonService, } from "../lib/mesh/live/daemon/index.js";
|
|
22
|
+
import { workContextRoot } from "../lib/work-context/paths.js";
|
|
23
|
+
import { formatMigrateConfirmation, submitSessionMigration, } from "../lib/work-context/migrate.js";
|
|
24
|
+
import { formatOrganizeList, prepareOrganizeDecision, settleOrganizeAskWithoutBind, submitOrganizeDecision, } from "../lib/work-context/organize.js";
|
|
25
|
+
import { readSessionState } from "../lib/work-context/state.js";
|
|
26
|
+
import { loadObservationFromFile, markUntracked, parseObservationJson, reconcileObservation, } from "../lib/work-context/reconcile.js";
|
|
11
27
|
export function formatCheckLines(threads, company, projectId) {
|
|
12
28
|
if (threads.length === 0) {
|
|
13
29
|
return ["Work mesh: no active project threads found."];
|
|
@@ -36,10 +52,12 @@ function requireProject(opts) {
|
|
|
36
52
|
fail("`--project <slug>` is required.");
|
|
37
53
|
return project;
|
|
38
54
|
}
|
|
55
|
+
/** Shared transport for existing mesh verbs (US-007A). */
|
|
39
56
|
async function withCompany(opts) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
return openMeshTransport(opts);
|
|
58
|
+
}
|
|
59
|
+
function workContextHomeRoot() {
|
|
60
|
+
return workContextRoot(os.homedir(), process.env);
|
|
43
61
|
}
|
|
44
62
|
async function runCheck(opts) {
|
|
45
63
|
const { token, company } = await withCompany(opts);
|
|
@@ -107,6 +125,520 @@ async function runDoctor(opts) {
|
|
|
107
125
|
` (directory=${warmed.directory} inbox=${warmed.inbox}` +
|
|
108
126
|
` contacts=${warmed.contacts} pair-threads=${warmed.threads})`);
|
|
109
127
|
}
|
|
128
|
+
async function runContextReconcile(opts) {
|
|
129
|
+
if (!opts.observationFile && !opts.observationJson) {
|
|
130
|
+
fail("Provide --observation-file <path> or --observation-json <json>");
|
|
131
|
+
}
|
|
132
|
+
if (opts.observationFile && opts.observationJson) {
|
|
133
|
+
fail("Pass only one of --observation-file or --observation-json");
|
|
134
|
+
}
|
|
135
|
+
const obs = opts.observationFile
|
|
136
|
+
? loadObservationFromFile(opts.observationFile)
|
|
137
|
+
: parseObservationJson(opts.observationJson);
|
|
138
|
+
const root = workContextHomeRoot();
|
|
139
|
+
let deliver;
|
|
140
|
+
let fetchCandidates;
|
|
141
|
+
let validateMembership;
|
|
142
|
+
let offline = Boolean(opts.offline);
|
|
143
|
+
if (!offline) {
|
|
144
|
+
try {
|
|
145
|
+
const token = await requireToken();
|
|
146
|
+
deliver = createWorkSessionDeliverer({ token });
|
|
147
|
+
fetchCandidates = createCandidatesFetcher({ token });
|
|
148
|
+
validateMembership = async (candidate) => {
|
|
149
|
+
const membership = await resolveActiveMembershipCompany(token, candidate);
|
|
150
|
+
if (!membership)
|
|
151
|
+
return false;
|
|
152
|
+
return { uid: membership.companyUid };
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
offline = true;
|
|
157
|
+
if (!opts.machine) {
|
|
158
|
+
console.error(chalk.yellow("No Cognito session; reconciling offline (outbox only)."));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const outcome = await reconcileObservation(obs, {
|
|
163
|
+
root,
|
|
164
|
+
env: process.env,
|
|
165
|
+
deliver,
|
|
166
|
+
fetchCandidates,
|
|
167
|
+
offline,
|
|
168
|
+
validateMembership,
|
|
169
|
+
});
|
|
170
|
+
for (const line of outcome.diagnostics) {
|
|
171
|
+
console.error(line);
|
|
172
|
+
}
|
|
173
|
+
if (opts.machine) {
|
|
174
|
+
console.log(JSON.stringify(outcome.result));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.log(JSON.stringify(outcome.result, null, 2));
|
|
178
|
+
}
|
|
179
|
+
process.exitCode = outcome.exitCode;
|
|
180
|
+
}
|
|
181
|
+
async function runContextDefaultGet(opts) {
|
|
182
|
+
const root = workContextHomeRoot();
|
|
183
|
+
const cfg = readDeviceConfig({ root });
|
|
184
|
+
const current = getDefaultCompany({ root });
|
|
185
|
+
if (opts.json) {
|
|
186
|
+
console.log(JSON.stringify({ ok: true, config: cfg, defaultCompany: current }, null, 2));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!current) {
|
|
190
|
+
console.log("No device default company (default-company mode may be dark).");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
console.log(`Default company: ${current.slug}${current.uid ? ` (${current.uid})` : ""} enabled=${current.enabled}`);
|
|
194
|
+
}
|
|
195
|
+
async function runContextDefaultSet(slug, opts) {
|
|
196
|
+
const root = workContextHomeRoot();
|
|
197
|
+
void opts.company; // retained for CLI compatibility; unlock is memberships-wide
|
|
198
|
+
let token;
|
|
199
|
+
try {
|
|
200
|
+
token = await requireToken();
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
token = undefined;
|
|
204
|
+
}
|
|
205
|
+
// US-017B: unlock only when EVERY active membership advertises migration.
|
|
206
|
+
let migrationCapability = false;
|
|
207
|
+
if (token) {
|
|
208
|
+
const probe = await probeMigrationCapabilityForMemberships(token);
|
|
209
|
+
recordMigrationCapabilitySnapshot(probe, { root });
|
|
210
|
+
migrationCapability = probe.unlocked;
|
|
211
|
+
if (probe.offline && !opts.allowWithoutMigration) {
|
|
212
|
+
console.error(chalk.red("DEFAULT_COMPANY_LOCKED: cannot probe migration capability (offline or not logged in)"));
|
|
213
|
+
process.exitCode = 1;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!probe.unlocked && !opts.allowWithoutMigration) {
|
|
217
|
+
const detail = probe.companies.length === 0
|
|
218
|
+
? "no active memberships"
|
|
219
|
+
: probe.companies
|
|
220
|
+
.filter((c) => !c.migration)
|
|
221
|
+
.map((c) => c.companySlug || c.companyUid)
|
|
222
|
+
.join(", ");
|
|
223
|
+
console.error(chalk.red(`DEFAULT_COMPANY_LOCKED: migration capability is not true for every company you belong to (${detail || "locked"})`));
|
|
224
|
+
process.exitCode = 1;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (!opts.allowWithoutMigration) {
|
|
229
|
+
console.error(chalk.red("DEFAULT_COMPANY_LOCKED: cannot probe migration capability (no Cognito session)"));
|
|
230
|
+
process.exitCode = 1;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!migrationCapability && opts.allowWithoutMigration) {
|
|
234
|
+
console.error(chalk.yellow("Warning: --allow-without-migration bypasses DEFAULT_COMPANY_LOCKED; a wrong default is hard to correct until migration is available for every company you belong to."));
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const cfg = await setDefaultCompany(slug, {
|
|
238
|
+
root,
|
|
239
|
+
migrationCapability,
|
|
240
|
+
allowWithoutMigration: opts.allowWithoutMigration,
|
|
241
|
+
validateMembership: async (candidate) => {
|
|
242
|
+
if (!token) {
|
|
243
|
+
throw new DefaultCompanyUnavailableError(`Cannot verify membership for company "${candidate}" (no Cognito session; offline or not logged in)`);
|
|
244
|
+
}
|
|
245
|
+
let membership;
|
|
246
|
+
try {
|
|
247
|
+
membership = await resolveActiveMembershipCompany(token, candidate);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
throw new DefaultCompanyUnavailableError(`Cannot verify membership for company "${candidate}": ${err instanceof Error ? err.message : String(err)}`);
|
|
251
|
+
}
|
|
252
|
+
if (!membership) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return { uid: membership.companyUid };
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
if (opts.json) {
|
|
259
|
+
console.log(JSON.stringify({ ok: true, config: cfg }, null, 2));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const stored = cfg.defaultCompany;
|
|
263
|
+
console.log(`Default company set to ${stored?.slug ?? slug}${stored?.uid ? ` (${stored.uid})` : ""}`);
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
if (err instanceof DefaultCompanyLockedError ||
|
|
267
|
+
err instanceof DefaultCompanyUnavailableError) {
|
|
268
|
+
console.error(chalk.red(`${err.code}: ${err.message}`));
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async function defaultConfirm(message) {
|
|
276
|
+
const rl = readline.createInterface({
|
|
277
|
+
input: process.stdin,
|
|
278
|
+
output: process.stderr,
|
|
279
|
+
});
|
|
280
|
+
try {
|
|
281
|
+
const answer = await rl.question(`${message}\nConfirm migrate? [y/N] `);
|
|
282
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
rl.close();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/** Exported for confirmation-gate unit tests (US-017B). */
|
|
289
|
+
export async function runContextCorrect(opts, deps = {}) {
|
|
290
|
+
const sessionId = opts.session?.trim();
|
|
291
|
+
if (!sessionId || !isValidSessionId(sessionId)) {
|
|
292
|
+
fail("`--session <sid>` is required and must be a valid session id");
|
|
293
|
+
}
|
|
294
|
+
const toCompany = opts.toCompany?.trim();
|
|
295
|
+
if (!toCompany) {
|
|
296
|
+
fail("`--to-company <slug>` is required");
|
|
297
|
+
}
|
|
298
|
+
const root = workContextHomeRoot();
|
|
299
|
+
const state = readSessionState(sessionId, root);
|
|
300
|
+
if (!state) {
|
|
301
|
+
fail(`No work-context state for session ${sessionId}`);
|
|
302
|
+
}
|
|
303
|
+
if (!state.companyUid) {
|
|
304
|
+
fail(`Session ${sessionId} has no verified companyUid; cannot migrate across companies`);
|
|
305
|
+
}
|
|
306
|
+
const token = await requireToken();
|
|
307
|
+
const dest = await resolveActiveMembershipCompany(token, toCompany);
|
|
308
|
+
if (!dest) {
|
|
309
|
+
fail(`Not an active member of destination company "${toCompany}" (or slug is ambiguous)`);
|
|
310
|
+
}
|
|
311
|
+
const destination = {};
|
|
312
|
+
if (opts.project?.trim())
|
|
313
|
+
destination.projectId = opts.project.trim();
|
|
314
|
+
if (opts.task?.trim())
|
|
315
|
+
destination.taskId = opts.task.trim();
|
|
316
|
+
const summary = formatMigrateConfirmation({
|
|
317
|
+
sessionId,
|
|
318
|
+
sourceCompanySlug: state.companySlug,
|
|
319
|
+
sourceCompanyUid: state.companyUid,
|
|
320
|
+
destinationCompanySlug: dest.companySlug,
|
|
321
|
+
destinationCompanyUid: dest.companyUid,
|
|
322
|
+
destination,
|
|
323
|
+
});
|
|
324
|
+
// Confirmation gate: without --yes or interactive yes, nothing is sent.
|
|
325
|
+
if (!opts.yes) {
|
|
326
|
+
const confirm = deps.confirm ?? defaultConfirm;
|
|
327
|
+
console.error(summary);
|
|
328
|
+
const ok = await confirm("Proceed with structured cross-company migration?");
|
|
329
|
+
if (!ok) {
|
|
330
|
+
if (opts.json || opts.machine) {
|
|
331
|
+
console.log(JSON.stringify({ ok: false, cancelled: true, action: "correct" }, null, opts.machine ? 0 : 2));
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
console.error(chalk.yellow("Migration cancelled; nothing was sent."));
|
|
335
|
+
}
|
|
336
|
+
process.exitCode = 1;
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
console.error(summary);
|
|
342
|
+
console.error(chalk.dim("(--yes: confirmation skipped)"));
|
|
343
|
+
}
|
|
344
|
+
const expectedVersion = opts.expectedVersion !== undefined
|
|
345
|
+
? Number.parseInt(opts.expectedVersion, 10)
|
|
346
|
+
: undefined;
|
|
347
|
+
if (opts.expectedVersion !== undefined &&
|
|
348
|
+
(!Number.isInteger(expectedVersion) || expectedVersion < 0)) {
|
|
349
|
+
fail("`--expected-version` must be a non-negative integer");
|
|
350
|
+
}
|
|
351
|
+
const postMigrate = createMigratePoster({ token });
|
|
352
|
+
const result = await submitSessionMigration({
|
|
353
|
+
sessionId,
|
|
354
|
+
root,
|
|
355
|
+
destinationCompanyUid: dest.companyUid,
|
|
356
|
+
destinationCompanySlug: dest.companySlug,
|
|
357
|
+
destination,
|
|
358
|
+
...(expectedVersion !== undefined ? { expectedVersion } : {}),
|
|
359
|
+
postMigrate,
|
|
360
|
+
});
|
|
361
|
+
// Refresh capability snapshot opportunistically (unlock may now be relevant).
|
|
362
|
+
try {
|
|
363
|
+
const probe = await probeMigrationCapabilityForMemberships(token);
|
|
364
|
+
recordMigrationCapabilitySnapshot(probe, { root });
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
/* ignore */
|
|
368
|
+
}
|
|
369
|
+
if (!result.ok) {
|
|
370
|
+
console.error(chalk.red(`${result.errorCode ?? "MIGRATE_FAILED"}: ${result.message ?? "migrate failed"}`));
|
|
371
|
+
process.exitCode = 1;
|
|
372
|
+
if (opts.json || opts.machine) {
|
|
373
|
+
console.log(JSON.stringify({ ...result, ok: false, action: "correct" }, null, opts.machine ? 0 : 2));
|
|
374
|
+
}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (opts.json || opts.machine) {
|
|
378
|
+
console.log(JSON.stringify({ ...result, ok: true, action: "correct" }, null, opts.machine ? 0 : 2));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const r = result.receipt;
|
|
382
|
+
console.log(`Work mesh: migrated ${sessionId} → ${dest.companySlug} (${r.destinationCompanyUid})` +
|
|
383
|
+
(r.destination.projectId ? ` project=${r.destination.projectId}` : "") +
|
|
384
|
+
(r.destination.taskId ? ` task=${r.destination.taskId}` : "") +
|
|
385
|
+
` receipt=${r.receipt.operationId} (${r.receipt.status})`);
|
|
386
|
+
}
|
|
387
|
+
async function runContextDefaultClear(opts) {
|
|
388
|
+
const root = workContextHomeRoot();
|
|
389
|
+
const cfg = clearDefaultCompany({ root });
|
|
390
|
+
if (opts.json) {
|
|
391
|
+
console.log(JSON.stringify({ ok: true, config: cfg }, null, 2));
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
console.log("Default company cleared.");
|
|
395
|
+
}
|
|
396
|
+
async function runContextUntracked(sessionId, opts) {
|
|
397
|
+
if (!isValidSessionId(sessionId)) {
|
|
398
|
+
fail(`Invalid session id: ${sessionId}`);
|
|
399
|
+
}
|
|
400
|
+
const outcome = markUntracked(sessionId, { root: workContextHomeRoot() });
|
|
401
|
+
for (const line of outcome.diagnostics) {
|
|
402
|
+
console.error(line);
|
|
403
|
+
}
|
|
404
|
+
if (opts.machine) {
|
|
405
|
+
console.log(JSON.stringify(outcome.result));
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
console.log(JSON.stringify(outcome.result, null, 2));
|
|
409
|
+
}
|
|
410
|
+
process.exitCode = outcome.exitCode;
|
|
411
|
+
}
|
|
412
|
+
async function runContextOrganize(opts) {
|
|
413
|
+
const sessionId = opts.session?.trim();
|
|
414
|
+
if (!sessionId || !isValidSessionId(sessionId)) {
|
|
415
|
+
fail("`--session <sid>` is required and must be a valid session id");
|
|
416
|
+
}
|
|
417
|
+
const root = workContextHomeRoot();
|
|
418
|
+
// Cancel / timeout / incapable → leave unresolved; never invent a project.
|
|
419
|
+
if (opts.cancel || opts.settle) {
|
|
420
|
+
const reason = opts.settle === "timeout"
|
|
421
|
+
? "timeout"
|
|
422
|
+
: opts.settle === "incapable"
|
|
423
|
+
? "incapable"
|
|
424
|
+
: "cancelled";
|
|
425
|
+
const state = settleOrganizeAskWithoutBind({ sessionId, root, reason });
|
|
426
|
+
if (opts.json || opts.machine) {
|
|
427
|
+
console.log(JSON.stringify({ ok: true, action: "settle", reason, state }, null, opts.machine ? 0 : 2));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
console.log(`Work mesh: session ${sessionId} left unresolved (${reason}); ask will not repeat.`);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const isList = opts.list ||
|
|
434
|
+
(!opts.decision &&
|
|
435
|
+
!opts.option &&
|
|
436
|
+
!opts.createProject &&
|
|
437
|
+
!opts.createTask &&
|
|
438
|
+
!opts.untracked);
|
|
439
|
+
if (isList) {
|
|
440
|
+
let fetchCandidates;
|
|
441
|
+
let offline = Boolean(opts.offline);
|
|
442
|
+
if (!offline) {
|
|
443
|
+
try {
|
|
444
|
+
const token = await requireToken();
|
|
445
|
+
fetchCandidates = createCandidatesFetcher({ token });
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
offline = true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const listed = await prepareOrganizeDecision({
|
|
452
|
+
sessionId,
|
|
453
|
+
root,
|
|
454
|
+
fetchCandidates,
|
|
455
|
+
offline,
|
|
456
|
+
});
|
|
457
|
+
if (opts.json || opts.machine) {
|
|
458
|
+
console.log(JSON.stringify({ ok: true, action: "list", ...listed }, null, opts.machine ? 0 : 2));
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
for (const line of formatOrganizeList(listed)) {
|
|
462
|
+
console.log(line);
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
let postOrganize;
|
|
467
|
+
if (!opts.untracked && !opts.offline) {
|
|
468
|
+
try {
|
|
469
|
+
const token = await requireToken();
|
|
470
|
+
postOrganize = createOrganizePoster({ token });
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
fail("Cognito session required to submit organize (server receipt only)");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const submitted = await submitOrganizeDecision({
|
|
477
|
+
sessionId,
|
|
478
|
+
root,
|
|
479
|
+
decisionId: opts.decision,
|
|
480
|
+
optionId: opts.option,
|
|
481
|
+
createProjectTitle: opts.createProject,
|
|
482
|
+
createTaskTitle: opts.createTask,
|
|
483
|
+
untracked: opts.untracked,
|
|
484
|
+
postOrganize,
|
|
485
|
+
});
|
|
486
|
+
if (!submitted.ok) {
|
|
487
|
+
console.error(chalk.red(`${submitted.errorCode ?? "ORGANIZE_FAILED"}: ${submitted.message ?? "organize failed"}`));
|
|
488
|
+
process.exitCode = 1;
|
|
489
|
+
if (opts.json || opts.machine) {
|
|
490
|
+
console.log(JSON.stringify({ ...submitted, ok: false, action: "submit" }, null, opts.machine ? 0 : 2));
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (opts.json || opts.machine) {
|
|
495
|
+
console.log(JSON.stringify({ ...submitted, ok: true, action: "submit" }, null, opts.machine ? 0 : 2));
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (submitted.localUntracked) {
|
|
499
|
+
console.log(`Work mesh: session ${sessionId} marked untracked (local receipt only)`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const r = submitted.receipt;
|
|
503
|
+
console.log(`Work mesh: organized ${sessionId} → ${r.projectId}` +
|
|
504
|
+
(r.taskId ? `/${r.taskId}` : "") +
|
|
505
|
+
` receipt=${r.receipt.operationId} (${r.receipt.status})`);
|
|
506
|
+
}
|
|
507
|
+
async function runSessionStatus(opts) {
|
|
508
|
+
if (!opts.company?.trim()) {
|
|
509
|
+
fail("`--company <slug>` is required for `hq mesh session status`");
|
|
510
|
+
}
|
|
511
|
+
const { token, company } = await withCompany({ company: opts.company });
|
|
512
|
+
const live = await fetchCompanyLive(token, company.companyUid);
|
|
513
|
+
if (opts.json) {
|
|
514
|
+
console.log(JSON.stringify({ ok: true, action: "session-status", company, live }, null, 2));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
for (const line of formatCompanyLiveTable(live, company.companySlug || company.companyUid)) {
|
|
518
|
+
console.log(line);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const HARNESSES = new Set([
|
|
522
|
+
"claude-code",
|
|
523
|
+
"codex",
|
|
524
|
+
"grok",
|
|
525
|
+
"hq-sessions",
|
|
526
|
+
"agent-box",
|
|
527
|
+
]);
|
|
528
|
+
function workMeshHomeRoot() {
|
|
529
|
+
return workMeshRoot(os.homedir(), process.env);
|
|
530
|
+
}
|
|
531
|
+
async function runSessionEnqueue(cliKind, opts) {
|
|
532
|
+
if (!opts.enqueue) {
|
|
533
|
+
fail("`hq mesh session …` requires --enqueue (local spool append; no network).");
|
|
534
|
+
}
|
|
535
|
+
const kind = CLI_KIND_TO_SCHEMA[cliKind];
|
|
536
|
+
if (!kind)
|
|
537
|
+
fail(`Unknown session verb: ${cliKind}`);
|
|
538
|
+
const harness = (opts.harness?.trim() || "");
|
|
539
|
+
if (!HARNESSES.has(harness)) {
|
|
540
|
+
fail("--harness is required (claude-code|codex|grok|hq-sessions|agent-box)");
|
|
541
|
+
}
|
|
542
|
+
const adapterVersion = opts.adapterVersion?.trim();
|
|
543
|
+
if (!adapterVersion)
|
|
544
|
+
fail("--adapter-version is required");
|
|
545
|
+
const seq = Number(opts.seq);
|
|
546
|
+
if (!Number.isInteger(seq) || seq < 1)
|
|
547
|
+
fail("--seq must be an integer >= 1");
|
|
548
|
+
let toolWrites;
|
|
549
|
+
if (opts.toolWrites !== undefined) {
|
|
550
|
+
toolWrites = Number(opts.toolWrites);
|
|
551
|
+
if (!Number.isInteger(toolWrites) || toolWrites < 0) {
|
|
552
|
+
fail("--tool-writes must be an integer >= 0");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const result = enqueueSessionEvent({
|
|
557
|
+
kind: kind,
|
|
558
|
+
sessionId: opts.sessionId,
|
|
559
|
+
harness,
|
|
560
|
+
adapterVersion,
|
|
561
|
+
runtimeVersion: opts.runtimeVersion,
|
|
562
|
+
seq,
|
|
563
|
+
eventId: opts.eventId,
|
|
564
|
+
at: opts.at,
|
|
565
|
+
taskId: opts.taskId,
|
|
566
|
+
status: opts.status,
|
|
567
|
+
reason: opts.reason,
|
|
568
|
+
summary: opts.summary,
|
|
569
|
+
cwd: opts.cwd,
|
|
570
|
+
hqRoot: opts.hqRoot,
|
|
571
|
+
companySlug: opts.companySlug,
|
|
572
|
+
project: opts.project,
|
|
573
|
+
task: opts.task,
|
|
574
|
+
toolWrites,
|
|
575
|
+
root: workMeshHomeRoot(),
|
|
576
|
+
env: process.env,
|
|
577
|
+
});
|
|
578
|
+
if (opts.json) {
|
|
579
|
+
console.log(JSON.stringify({
|
|
580
|
+
ok: true,
|
|
581
|
+
action: "enqueue",
|
|
582
|
+
kind,
|
|
583
|
+
spoolPath: result.spoolPath,
|
|
584
|
+
eventId: result.event.eventId,
|
|
585
|
+
}, null, 2));
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
// Quiet success for hooks/scripts — eventId on stderr only when not json.
|
|
589
|
+
console.error(`work-mesh: enqueued ${kind} ${result.event.eventId}`);
|
|
590
|
+
}
|
|
591
|
+
catch (err) {
|
|
592
|
+
if (err instanceof EnqueueValidationError) {
|
|
593
|
+
console.error(chalk.red(`enqueue validation failed: ${err.message}`));
|
|
594
|
+
process.exitCode = 1;
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
throw err;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async function runSessionFlush(opts) {
|
|
601
|
+
const token = await requireToken();
|
|
602
|
+
const poster = createSessionEventsPoster({
|
|
603
|
+
token,
|
|
604
|
+
baseUrl: resolveVaultApiBase(process.env),
|
|
605
|
+
});
|
|
606
|
+
const summary = await flushSessionEvents({
|
|
607
|
+
workMeshRoot: workMeshHomeRoot(),
|
|
608
|
+
workContextRoot: workContextHomeRoot(),
|
|
609
|
+
poster,
|
|
610
|
+
});
|
|
611
|
+
if (opts.json) {
|
|
612
|
+
console.log(JSON.stringify({ ok: true, action: "flush", ...summary }, null, 2));
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
console.log(`work-mesh flush: claimed=${summary.claimed} posted=${summary.posted}` +
|
|
616
|
+
` held=${summary.held} dropped=${summary.dropped}` +
|
|
617
|
+
` deadLettered=${summary.deadLettered} restored=${summary.restored}` +
|
|
618
|
+
` batches=${summary.batches}`);
|
|
619
|
+
}
|
|
620
|
+
function addSessionEnqueueFlags(cmd) {
|
|
621
|
+
return cmd
|
|
622
|
+
.option("--enqueue", "Append one line to ~/.hq/work-mesh/spool.jsonl (no network)")
|
|
623
|
+
.option("--session-id <id>", "Session id (else HQ_SESSION_ID / harness env)")
|
|
624
|
+
.option("--harness <name>", "claude-code|codex|grok|hq-sessions|agent-box")
|
|
625
|
+
.option("--adapter-version <ver>", "Hook / adapter version")
|
|
626
|
+
.option("--runtime-version <ver>", "Host runtime version")
|
|
627
|
+
.option("--seq <n>", "Monotonic per-session sequence (>= 1)")
|
|
628
|
+
.option("--event-id <ulid>", "Explicit event ULID (default: generate)")
|
|
629
|
+
.option("--at <iso>", "Event timestamp (default: now)")
|
|
630
|
+
.option("--task-id <id>", "Canonical task id")
|
|
631
|
+
.option("--status <status>", "queued|in_progress|review|done")
|
|
632
|
+
.option("--reason <text>", "Blocked reason (max 500)")
|
|
633
|
+
.option("--summary <text>", "Short summary (max 280)")
|
|
634
|
+
.option("--cwd <path>", "Local-only working directory")
|
|
635
|
+
.option("--hq-root <path>", "Local-only HQ tree root")
|
|
636
|
+
.option("--company-slug <slug>", "Local-only company slug hint")
|
|
637
|
+
.option("--project <name>", "Local-only project hint")
|
|
638
|
+
.option("--task <label>", "Local-only task label hint")
|
|
639
|
+
.option("--tool-writes <n>", "Local-only tool write count")
|
|
640
|
+
.option("--json", "Print machine-readable JSON");
|
|
641
|
+
}
|
|
110
642
|
function wrap(action) {
|
|
111
643
|
return async () => {
|
|
112
644
|
try {
|
|
@@ -169,5 +701,201 @@ export function registerMeshCommand(program) {
|
|
|
169
701
|
.option("--apply", "Reserved — Board PUTs from local prd are not in the CLI yet")
|
|
170
702
|
.option("--json", "Print machine-readable JSON")
|
|
171
703
|
.action((opts) => wrap(() => runDoctor(opts))());
|
|
704
|
+
const context = mesh
|
|
705
|
+
.command("context")
|
|
706
|
+
.description("Work context reconcile, organize, correct, device default, and untracked consent");
|
|
707
|
+
context
|
|
708
|
+
.command("reconcile")
|
|
709
|
+
.description("Resolve session identity and company; durable local accept before network")
|
|
710
|
+
.option("--observation-file <path>", "Path to a SessionObservation JSON file")
|
|
711
|
+
.option("--observation-json <json>", "SessionObservation as a JSON string")
|
|
712
|
+
.option("--machine", "Write exactly one ContextResult JSON line to stdout")
|
|
713
|
+
.option("--offline", "Skip network; leave register operations queued in the outbox")
|
|
714
|
+
.action((opts) => wrap(() => runContextReconcile(opts))());
|
|
715
|
+
context
|
|
716
|
+
.command("organize")
|
|
717
|
+
.description("List or submit one-time project/task decisions (US-007B / US-005B)")
|
|
718
|
+
.argument("[action]", "Pass 'list' to print pending decisions (default when no submit flags)")
|
|
719
|
+
.requiredOption("--session <sid>", "Canonical session id")
|
|
720
|
+
.option("--list", "List pending decisions (same as action list)")
|
|
721
|
+
.option("--decision <decisionId>", "Pending decision id")
|
|
722
|
+
.option("--option <optionId>", "Stable option id from the pending decision")
|
|
723
|
+
.option("--create-project <title>", "Create a new project then bind")
|
|
724
|
+
.option("--create-task <title>", "Create a new task on the session project then bind")
|
|
725
|
+
.option("--untracked", "Mark the session untracked (local receipt only)")
|
|
726
|
+
.option("--cancel", "Settle ask without binding (leave unresolved; never repeat)")
|
|
727
|
+
.option("--settle <reason>", "Settle ask: cancelled|timeout|incapable (leave unresolved)")
|
|
728
|
+
.option("--offline", "Do not fetch candidates / submit to server")
|
|
729
|
+
.option("--json", "Print machine-readable JSON")
|
|
730
|
+
.option("--machine", "Compact JSON on stdout")
|
|
731
|
+
.action((action, opts) => wrap(() => runContextOrganize({
|
|
732
|
+
...opts,
|
|
733
|
+
list: opts.list || action === "list",
|
|
734
|
+
}))());
|
|
735
|
+
context
|
|
736
|
+
.command("correct")
|
|
737
|
+
.description("Explicit cross-company session correction via server migrate (US-017B)")
|
|
738
|
+
.requiredOption("--session <sid>", "Canonical session id")
|
|
739
|
+
.requiredOption("--to-company <slug>", "Destination company slug")
|
|
740
|
+
.option("--project <id>", "Optional destination project id")
|
|
741
|
+
.option("--task <id>", "Optional destination task id")
|
|
742
|
+
.option("--expected-version <n>", "OCC expectedVersion (defaults to local state version or 0)")
|
|
743
|
+
.option("-y, --yes", "Skip the interactive confirmation prompt (for scripts)")
|
|
744
|
+
.option("--json", "Print machine-readable JSON")
|
|
745
|
+
.option("--machine", "Compact JSON on stdout")
|
|
746
|
+
.action((opts) => wrap(() => runContextCorrect(opts))());
|
|
747
|
+
const def = context
|
|
748
|
+
.command("default")
|
|
749
|
+
.description("Manage the device-local default company preference");
|
|
750
|
+
def
|
|
751
|
+
.command("get")
|
|
752
|
+
.description("Show the device default company (never activeCompany)")
|
|
753
|
+
.option("--json", "Print machine-readable JSON")
|
|
754
|
+
.action((opts) => wrap(() => runContextDefaultGet(opts))());
|
|
755
|
+
def
|
|
756
|
+
.command("set")
|
|
757
|
+
.description("Set the device default company slug (unlocked when migration is true for every membership)")
|
|
758
|
+
.argument("<slug>", "Company slug")
|
|
759
|
+
.option("--allow-without-migration", "Bypass DEFAULT_COMPANY_LOCKED with a warning")
|
|
760
|
+
.option("--company <slug|uid>", "Deprecated no-op; unlock probes every active membership")
|
|
761
|
+
.option("--json", "Print machine-readable JSON")
|
|
762
|
+
.action((slug, opts) => wrap(() => runContextDefaultSet(slug, opts))());
|
|
763
|
+
def
|
|
764
|
+
.command("clear")
|
|
765
|
+
.description("Clear the device default company")
|
|
766
|
+
.option("--json", "Print machine-readable JSON")
|
|
767
|
+
.action((opts) => wrap(() => runContextDefaultClear(opts))());
|
|
768
|
+
context
|
|
769
|
+
.command("untracked")
|
|
770
|
+
.description("Record an explicit user-origin untracked consent receipt (local only)")
|
|
771
|
+
.argument("<sessionId>", "Canonical session id")
|
|
772
|
+
.option("--machine", "Write exactly one ContextResult JSON line to stdout")
|
|
773
|
+
.action((sessionId, opts) => wrap(() => runContextUntracked(sessionId, opts))());
|
|
774
|
+
const session = mesh
|
|
775
|
+
.command("session")
|
|
776
|
+
.description("Local session-event spool: enqueue (printf-compatible), flush, and live status");
|
|
777
|
+
const sessionVerbs = [
|
|
778
|
+
{ name: "start", description: "Enqueue session_start" },
|
|
779
|
+
{ name: "turn-start", description: "Enqueue turn_start" },
|
|
780
|
+
{ name: "turn-end", description: "Enqueue turn_end" },
|
|
781
|
+
{ name: "end", description: "Enqueue session_end" },
|
|
782
|
+
{ name: "task-status", description: "Enqueue task_status" },
|
|
783
|
+
{ name: "blocked", description: "Enqueue blocked" },
|
|
784
|
+
{ name: "note", description: "Enqueue note" },
|
|
785
|
+
];
|
|
786
|
+
for (const verb of sessionVerbs) {
|
|
787
|
+
addSessionEnqueueFlags(session.command(verb.name).description(verb.description)).action((opts) => wrap(() => runSessionEnqueue(verb.name, opts))());
|
|
788
|
+
}
|
|
789
|
+
session
|
|
790
|
+
.command("flush")
|
|
791
|
+
.description("Claim spool/held by rename, hold or drop by context state, POST batches of ≤100")
|
|
792
|
+
.option("--json", "Print machine-readable JSON")
|
|
793
|
+
.action((opts) => wrap(() => runSessionFlush(opts))());
|
|
794
|
+
session
|
|
795
|
+
.command("status")
|
|
796
|
+
.description("Print the company-wide live read (US-004) as a table or --json")
|
|
797
|
+
.requiredOption("--company <slug|uid>", "Company slug or cloud uid")
|
|
798
|
+
.option("--json", "Print machine-readable JSON")
|
|
799
|
+
.action((opts) => wrap(() => runSessionStatus(opts))());
|
|
800
|
+
const daemon = mesh
|
|
801
|
+
.command("daemon")
|
|
802
|
+
.description("Resident spool drainer + MQTT presence (install as a user service)");
|
|
803
|
+
daemon
|
|
804
|
+
.command("run")
|
|
805
|
+
.description("Start the single-instance daemon (pid lock, spool watch, presence)")
|
|
806
|
+
.action(() => wrap(() => runDaemonRun())());
|
|
807
|
+
daemon
|
|
808
|
+
.command("install")
|
|
809
|
+
.description("Install LaunchAgent (macOS) or systemd user unit (Linux)")
|
|
810
|
+
.option("--system-user <user>", "Write a root-owned unit template for agent boxes (e.g. ec2-user)")
|
|
811
|
+
.option("--json", "Print machine-readable JSON")
|
|
812
|
+
.action((opts) => wrap(() => runDaemonInstall(opts))());
|
|
813
|
+
daemon
|
|
814
|
+
.command("uninstall")
|
|
815
|
+
.description("Remove the installed user-level daemon service")
|
|
816
|
+
.option("--json", "Print machine-readable JSON")
|
|
817
|
+
.action((opts) => wrap(() => runDaemonUninstall(opts))());
|
|
818
|
+
daemon
|
|
819
|
+
.command("status")
|
|
820
|
+
.description("Show whether the daemon service is installed/loaded")
|
|
821
|
+
.option("--json", "Print machine-readable JSON")
|
|
822
|
+
.action((opts) => wrap(() => runDaemonStatus(opts))());
|
|
823
|
+
daemon
|
|
824
|
+
.command("doctor")
|
|
825
|
+
.description("Report daemon running/MQTT/spool/held/dead-letter/outbox/flush health")
|
|
826
|
+
.option("--json", "Print machine-readable JSON")
|
|
827
|
+
.action((opts) => wrap(() => runDaemonDoctorCmd(opts))());
|
|
828
|
+
}
|
|
829
|
+
async function runDaemonRun() {
|
|
830
|
+
const handle = await runMeshDaemon();
|
|
831
|
+
// Stay alive until signal handlers call stop + exit.
|
|
832
|
+
await new Promise(() => {
|
|
833
|
+
void handle;
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
async function runDaemonInstall(opts) {
|
|
837
|
+
const result = installDaemonService({
|
|
838
|
+
platform: detectPlatform(),
|
|
839
|
+
paths: buildInstallPaths({ systemUser: opts.systemUser }),
|
|
840
|
+
systemUser: opts.systemUser,
|
|
841
|
+
});
|
|
842
|
+
if (opts.json) {
|
|
843
|
+
console.log(JSON.stringify({
|
|
844
|
+
ok: true,
|
|
845
|
+
action: result.action,
|
|
846
|
+
platform: result.platform,
|
|
847
|
+
dest: result.dest,
|
|
848
|
+
message: result.message,
|
|
849
|
+
}, null, 2));
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
console.log(result.message);
|
|
853
|
+
}
|
|
854
|
+
async function runDaemonUninstall(opts) {
|
|
855
|
+
const result = uninstallDaemonService({
|
|
856
|
+
platform: detectPlatform(),
|
|
857
|
+
paths: buildInstallPaths({}),
|
|
858
|
+
});
|
|
859
|
+
if (opts.json) {
|
|
860
|
+
console.log(JSON.stringify({
|
|
861
|
+
ok: true,
|
|
862
|
+
action: result.action,
|
|
863
|
+
platform: result.platform,
|
|
864
|
+
dest: result.dest,
|
|
865
|
+
message: result.message,
|
|
866
|
+
}, null, 2));
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
console.log(result.message);
|
|
870
|
+
}
|
|
871
|
+
async function runDaemonStatus(opts) {
|
|
872
|
+
const result = daemonServiceStatus({
|
|
873
|
+
platform: detectPlatform(),
|
|
874
|
+
paths: buildInstallPaths({}),
|
|
875
|
+
});
|
|
876
|
+
if (opts.json) {
|
|
877
|
+
console.log(JSON.stringify({
|
|
878
|
+
ok: true,
|
|
879
|
+
action: result.action,
|
|
880
|
+
platform: result.platform,
|
|
881
|
+
running: result.running ?? false,
|
|
882
|
+
dest: result.dest,
|
|
883
|
+
message: result.message,
|
|
884
|
+
}, null, 2));
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
console.log(result.message);
|
|
888
|
+
}
|
|
889
|
+
async function runDaemonDoctorCmd(opts) {
|
|
890
|
+
const report = collectDaemonDoctor();
|
|
891
|
+
if (opts.json) {
|
|
892
|
+
console.log(JSON.stringify({ ok: !report.unhealthy, ...report }, null, 2));
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
for (const line of formatDaemonDoctor(report)) {
|
|
896
|
+
console.log(line);
|
|
897
|
+
}
|
|
898
|
+
if (report.unhealthy)
|
|
899
|
+
process.exitCode = 1;
|
|
172
900
|
}
|
|
173
901
|
//# sourceMappingURL=mesh.js.map
|