@indigoai-us/hq-cli 5.300.0 → 5.301.0
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 +20 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +9 -0
- package/dist/command-catalog.generated.d.ts +28 -2
- package/dist/command-catalog.generated.js +34 -2
- package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
- package/dist/commands/__fixtures__/access-vault.js +1 -1
- package/dist/commands/access.d.ts +2 -1
- package/dist/commands/access.js +58 -49
- package/dist/commands/cloud-provision.js +51 -2
- package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
- package/dist/commands/core-company-index-scope-flag.js +6 -0
- package/dist/commands/core-job-native-utils.d.ts +6 -0
- package/dist/commands/core-job-native-utils.js +80 -0
- package/dist/commands/core-job-native.d.ts +2 -4
- package/dist/commands/core-job-native.js +6 -10
- package/dist/commands/core-job-notify-native.d.ts +36 -0
- package/dist/commands/core-job-notify-native.js +359 -0
- package/dist/commands/core-job-remediate-native.d.ts +26 -0
- package/dist/commands/core-job-remediate-native.js +374 -0
- package/dist/commands/core.js +26 -8
- package/dist/commands/files.js +31 -14
- package/dist/commands/integrations-api.d.ts +6 -6
- package/dist/commands/integrations-api.js +4 -13
- package/dist/commands/integrations-manage.js +1 -4
- package/dist/commands/lanes-message.js +10 -2
- package/dist/commands/lanes-shared.d.ts +2 -0
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +1 -0
- package/dist/commands/meetings.js +35 -16
- package/dist/commands/safe-extract.d.ts +4 -4
- package/dist/commands/safe-extract.js +6 -7
- package/dist/lib/core-utils/token-usage-report.js +17 -3
- package/dist/lib/index-render/companies.d.ts +4 -1
- package/dist/lib/index-render/companies.js +66 -3
- package/dist/lib/index-render/index.d.ts +1 -0
- package/dist/lib/index-render/index.js +2 -1
- package/dist/lib/index-render/shared.d.ts +1 -1
- package/dist/lib/index-render/shared.js +4 -2
- package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +2 -2
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +2 -2
- package/dist/utils/self-update.js +51 -4
- package/dist/utils/staged-npm-install.d.ts +16 -0
- package/dist/utils/staged-npm-install.js +264 -0
- package/dist/utils/update-command-supervisor.cjs +53 -1
- package/dist/utils/version-gate.d.ts +8 -0
- package/dist/utils/version-gate.js +82 -15
- package/package.json +2 -2
- package/dist/lib/install-path-compatibility-flag.d.ts +0 -9
- package/dist/lib/install-path-compatibility-flag.js +0 -12
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export { emitLaneDecisionEvents };
|
|
|
31
31
|
export interface LanesCommandDeps {
|
|
32
32
|
/** Test seam for failures after a lane provider worker is started. */
|
|
33
33
|
onLaneWorkerStarted?: (laneId: string, command: "message" | "resume") => void;
|
|
34
|
+
/** Test seam for failures after a message is durably queued. */
|
|
35
|
+
onLaneMessageQueued?: (laneId: string, path: "admission" | "account_move") => void;
|
|
34
36
|
hqRoot?: () => string;
|
|
35
37
|
/** Start HQ-root walk here instead of process.cwd(). Tests only. */
|
|
36
38
|
cwd?: () => string;
|
package/dist/commands/lanes.js
CHANGED
|
@@ -7838,6 +7838,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7838
7838
|
});
|
|
7839
7839
|
const messageHandlerDeps = {
|
|
7840
7840
|
onLaneWorkerStarted: deps.onLaneWorkerStarted,
|
|
7841
|
+
onLaneMessageQueued: deps.onLaneMessageQueued,
|
|
7841
7842
|
archiveResumeMessage,
|
|
7842
7843
|
assertLaneRunsHereForAdmission,
|
|
7843
7844
|
assertLaneRunsHereForOperatorMutation,
|
|
@@ -3,6 +3,25 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
4
4
|
import { vaultApiFetch, getCompanyUid, resolveCallerPersonUid, } from "../utils/vault-api.js";
|
|
5
5
|
import { isPlanGateError, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
|
|
6
|
+
// Two minutes gives a large transcript time to arrive over a slow connection,
|
|
7
|
+
// while keeping the interactive command from waiting indefinitely.
|
|
8
|
+
const MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
9
|
+
async function downloadMeetingDocument(url, readBody) {
|
|
10
|
+
const signal = AbortSignal.timeout(MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS);
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(url, { signal });
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
return { response, ok: false };
|
|
15
|
+
return { response, ok: true, body: await readBody(response) };
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if ((signal.aborted && signal.reason instanceof DOMException && signal.reason.name === "TimeoutError") ||
|
|
19
|
+
(err instanceof DOMException && err.name === "TimeoutError")) {
|
|
20
|
+
throw new Error(`Meeting document download timed out after ${MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS / 1000} seconds. Check your network connection and try again.`);
|
|
21
|
+
}
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
6
25
|
function formatDuration(seconds) {
|
|
7
26
|
const h = Math.floor(seconds / 3600);
|
|
8
27
|
const m = Math.floor((seconds % 3600) / 60);
|
|
@@ -495,12 +514,12 @@ export function registerMeetingsCommand(program) {
|
|
|
495
514
|
console.error(chalk.red("No document available for this meeting."));
|
|
496
515
|
process.exit(1);
|
|
497
516
|
}
|
|
498
|
-
const
|
|
499
|
-
if (!
|
|
500
|
-
console.error(chalk.red(`Failed to download meeting document (${
|
|
517
|
+
const download = await downloadMeetingDocument(documentUrl, (response) => response.text());
|
|
518
|
+
if (!download.ok) {
|
|
519
|
+
console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
|
|
501
520
|
process.exit(1);
|
|
502
521
|
}
|
|
503
|
-
const markdown =
|
|
522
|
+
const markdown = download.body;
|
|
504
523
|
if (meetings.opts().json) {
|
|
505
524
|
console.log(JSON.stringify({ meetingId: detail.meetingId, sourceShape: "markdown", markdown }, null, 2));
|
|
506
525
|
return;
|
|
@@ -513,12 +532,12 @@ export function registerMeetingsCommand(program) {
|
|
|
513
532
|
console.error(chalk.red("No document URL available for this meeting."));
|
|
514
533
|
process.exit(1);
|
|
515
534
|
}
|
|
516
|
-
const
|
|
517
|
-
if (!
|
|
518
|
-
console.error(chalk.red(`Failed to download meeting document (${
|
|
535
|
+
const download = await downloadMeetingDocument(detail.documentUrl, (response) => response.json());
|
|
536
|
+
if (!download.ok) {
|
|
537
|
+
console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
|
|
519
538
|
process.exit(1);
|
|
520
539
|
}
|
|
521
|
-
const doc =
|
|
540
|
+
const doc = download.body;
|
|
522
541
|
if (meetings.opts().json) {
|
|
523
542
|
console.log(JSON.stringify(doc.transcript, null, 2));
|
|
524
543
|
return;
|
|
@@ -573,12 +592,12 @@ export function registerMeetingsCommand(program) {
|
|
|
573
592
|
console.log(chalk.yellow("No notes available for this meeting."));
|
|
574
593
|
return;
|
|
575
594
|
}
|
|
576
|
-
const
|
|
577
|
-
if (!
|
|
578
|
-
console.error(chalk.red(`Failed to download meeting document (${
|
|
595
|
+
const download = await downloadMeetingDocument(documentUrl, (response) => response.text());
|
|
596
|
+
if (!download.ok) {
|
|
597
|
+
console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
|
|
579
598
|
process.exit(1);
|
|
580
599
|
}
|
|
581
|
-
const markdown =
|
|
600
|
+
const markdown = download.body;
|
|
582
601
|
if (meetings.opts().json) {
|
|
583
602
|
console.log(JSON.stringify({ meetingId: detail.meetingId, sourceShape: "markdown", markdown }, null, 2));
|
|
584
603
|
return;
|
|
@@ -591,12 +610,12 @@ export function registerMeetingsCommand(program) {
|
|
|
591
610
|
console.error(chalk.red("No document URL available for this meeting."));
|
|
592
611
|
process.exit(1);
|
|
593
612
|
}
|
|
594
|
-
const
|
|
595
|
-
if (!
|
|
596
|
-
console.error(chalk.red(`Failed to download meeting document (${
|
|
613
|
+
const download = await downloadMeetingDocument(detail.documentUrl, (response) => response.json());
|
|
614
|
+
if (!download.ok) {
|
|
615
|
+
console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
|
|
597
616
|
process.exit(1);
|
|
598
617
|
}
|
|
599
|
-
const doc =
|
|
618
|
+
const doc = download.body;
|
|
600
619
|
if (meetings.opts().json) {
|
|
601
620
|
console.log(JSON.stringify(doc.notes, null, 2));
|
|
602
621
|
return;
|
|
@@ -111,10 +111,10 @@ export declare function assertLinkTargetContained(packDir: string, entryAbs: str
|
|
|
111
111
|
/**
|
|
112
112
|
* List a gzip-compressed tarball's entries via `tar -tvf` (no extraction).
|
|
113
113
|
* argv form, never a shell string, keeps hostile filenames out of shell syntax.
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
114
|
+
* Windows drive paths use a basename relative to the archive directory by
|
|
115
|
+
* default. GNU tar cannot parse `C:\\...` as remote `host:path`, and Windows'
|
|
116
|
+
* bundled bsdtar accepts the same relative argv without GNU-only `--force-local`.
|
|
117
|
+
* Callers can still explicitly opt out with `useRelativeWindowsDrivePath: false`.
|
|
118
118
|
*/
|
|
119
119
|
export declare function listTarball(tarballPath: string, opts?: Pick<SafeExtractOptions, 'platform' | 'useRelativeWindowsDrivePath' | 'execFileSync'>): TarEntry[];
|
|
120
120
|
/**
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
import { execFileSync } from 'child_process';
|
|
36
36
|
import * as fs from 'fs';
|
|
37
37
|
import * as path from 'path';
|
|
38
|
-
import { installPathCompatibilityEnabled } from '../lib/install-path-compatibility-flag.js';
|
|
39
38
|
export const DEFAULT_CAPS = {
|
|
40
39
|
// A content pack is docs/skills/policies/small scripts. 256 MiB is already
|
|
41
40
|
// generous; anything larger is almost certainly a bomb or a mistake.
|
|
@@ -218,14 +217,14 @@ function tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath)
|
|
|
218
217
|
/**
|
|
219
218
|
* List a gzip-compressed tarball's entries via `tar -tvf` (no extraction).
|
|
220
219
|
* argv form, never a shell string, keeps hostile filenames out of shell syntax.
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
220
|
+
* Windows drive paths use a basename relative to the archive directory by
|
|
221
|
+
* default. GNU tar cannot parse `C:\\...` as remote `host:path`, and Windows'
|
|
222
|
+
* bundled bsdtar accepts the same relative argv without GNU-only `--force-local`.
|
|
223
|
+
* Callers can still explicitly opt out with `useRelativeWindowsDrivePath: false`.
|
|
225
224
|
*/
|
|
226
225
|
export function listTarball(tarballPath, opts = {}) {
|
|
227
226
|
const platform = opts.platform ?? process.platform;
|
|
228
|
-
const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ??
|
|
227
|
+
const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ?? true;
|
|
229
228
|
const location = tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath);
|
|
230
229
|
const out = (opts.execFileSync ?? execFileSync)('tar', ['-tvf', location.archiveArg], {
|
|
231
230
|
encoding: 'utf-8',
|
|
@@ -330,7 +329,7 @@ export function safeExtractTarball(tarballPath, finalDir, opts = {}) {
|
|
|
330
329
|
}
|
|
331
330
|
// Resolve once so listing and extraction use the same archive location.
|
|
332
331
|
const platform = opts.platform ?? process.platform;
|
|
333
|
-
const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ??
|
|
332
|
+
const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ?? true;
|
|
334
333
|
const exec = opts.execFileSync ?? execFileSync;
|
|
335
334
|
const location = tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath);
|
|
336
335
|
// 1. Pre-flight against headers (no bytes written yet).
|
|
@@ -120,7 +120,7 @@ function comparison(projectDirs, before, after, minHours, json, stdout) {
|
|
|
120
120
|
/** Print token usage totals from Claude session JSONL files. */
|
|
121
121
|
export function tokenUsageReport(args = [], options = {}) {
|
|
122
122
|
const { stdout, stderr } = ioFor(options);
|
|
123
|
-
let lastDays = 7, since = "", json = false, before = "", after = "", minHours = 0;
|
|
123
|
+
let lastDays = 7, since = "", json = false, before = "", after = "", minHours = 0, projectDir = options.projectDir, projectDirSeen = false;
|
|
124
124
|
for (let index = 0; index < args.length;) {
|
|
125
125
|
const arg = args[index];
|
|
126
126
|
if (arg === "--last") {
|
|
@@ -149,8 +149,22 @@ export function tokenUsageReport(args = [], options = {}) {
|
|
|
149
149
|
minHours = Number.parseFloat(args[index + 1] ?? "");
|
|
150
150
|
index += 2;
|
|
151
151
|
}
|
|
152
|
+
else if (arg === "--project-dir") {
|
|
153
|
+
const value = args[index + 1];
|
|
154
|
+
if (!value || value.startsWith("--")) {
|
|
155
|
+
line(stderr, "--project-dir requires a path");
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
if (projectDirSeen) {
|
|
159
|
+
line(stderr, "--project-dir may be specified only once");
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
projectDir = value;
|
|
163
|
+
projectDirSeen = true;
|
|
164
|
+
index += 2;
|
|
165
|
+
}
|
|
152
166
|
else if (arg === "-h" || arg === "--help") {
|
|
153
|
-
stdout("token-usage-report.sh — Daily effective-token totals + top burners from Claude Code session JSONLs.\nUsage:\n scripts/token-usage-report.sh # last 7 days, table\n scripts/token-usage-report.sh --last 14 # last 14 days\n scripts/token-usage-report.sh --since 2026-05-01\n scripts/token-usage-report.sh --json # machine-readable\nEffective tokens weighting (matches weekly rate-limit volume):\n input + 5×output + 1.25×cache_creation + 0.1×cache_read\n--- Compare-windows mode (US-011) ---\nFormat: --before YYYY-MM-DD:YYYY-MM-DD --after YYYY-MM-DD:YYYY-MM-DD\n");
|
|
167
|
+
stdout("token-usage-report.sh — Daily effective-token totals + top burners from Claude Code session JSONLs.\nUsage:\n scripts/token-usage-report.sh # last 7 days, table\n scripts/token-usage-report.sh --last 14 # last 14 days\n scripts/token-usage-report.sh --since 2026-05-01\n scripts/token-usage-report.sh --json # machine-readable\n scripts/token-usage-report.sh --project-dir <path> # use one Claude activity folder\nEffective tokens weighting (matches weekly rate-limit volume):\n input + 5×output + 1.25×cache_creation + 0.1×cache_read\n--- Compare-windows mode (US-011) ---\nFormat: --before YYYY-MM-DD:YYYY-MM-DD --after YYYY-MM-DD:YYYY-MM-DD\n");
|
|
154
168
|
return 0;
|
|
155
169
|
}
|
|
156
170
|
else {
|
|
@@ -158,7 +172,7 @@ export function tokenUsageReport(args = [], options = {}) {
|
|
|
158
172
|
return 1;
|
|
159
173
|
}
|
|
160
174
|
}
|
|
161
|
-
const projectDirs = resolveProjectDirs(options).filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
175
|
+
const projectDirs = resolveProjectDirs({ ...options, projectDir }).filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
162
176
|
if (before && after) {
|
|
163
177
|
comparison(projectDirs, before, after, minHours, json, stdout);
|
|
164
178
|
return 0;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { RenderContext, RenderResult } from "./shared.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class CompanyIndexUserError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function renderCompanies(context: RenderContext, args?: string[]): RenderResult;
|
|
3
6
|
//# sourceMappingURL=companies.d.ts.map
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { basename, countImmediate, date, exists, immediateEntries, isHidden, log, projectStatus, readJson, sanitize, titleize, truncate, write } from "./shared.js";
|
|
4
|
+
export class CompanyIndexUserError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "CompanyIndexUserError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
4
10
|
function description(root, company, name) {
|
|
5
11
|
const base = `companies/${company}/${name}`;
|
|
6
12
|
const nonHidden = (child) => !isHidden(child);
|
|
@@ -17,9 +23,66 @@ function description(root, company, name) {
|
|
|
17
23
|
default: return "—";
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
|
-
export function renderCompanies(context) {
|
|
26
|
+
export function renderCompanies(context, args = []) {
|
|
27
|
+
if (args.some((arg) => arg === "--help" || arg === "-h")) {
|
|
28
|
+
(context.output ?? ((message) => process.stdout.write(message)))("Usage: hq core rebuild-index companies [--company <slug>]\n\n" +
|
|
29
|
+
"Regenerate company root INDEX.md files. Without --company, all companies are regenerated.\n" +
|
|
30
|
+
"--company <slug> regenerates only that company's index.\n" +
|
|
31
|
+
"Company-scoped writes require the cli.rebuild-index-company-scope flag.\n");
|
|
32
|
+
return { written: [] };
|
|
33
|
+
}
|
|
34
|
+
let selectedCompany;
|
|
35
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
36
|
+
const arg = args[index];
|
|
37
|
+
if (arg === "--company") {
|
|
38
|
+
if (selectedCompany !== undefined)
|
|
39
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company may be supplied once");
|
|
40
|
+
const value = args[index + 1];
|
|
41
|
+
if (!value || value.startsWith("--"))
|
|
42
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company requires a company slug");
|
|
43
|
+
selectedCompany = value;
|
|
44
|
+
index += 1;
|
|
45
|
+
}
|
|
46
|
+
else if (arg.startsWith("--company=")) {
|
|
47
|
+
if (selectedCompany !== undefined)
|
|
48
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company may be supplied once");
|
|
49
|
+
selectedCompany = arg.slice("--company=".length);
|
|
50
|
+
if (!selectedCompany)
|
|
51
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company requires a company slug");
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
throw new CompanyIndexUserError(`rebuild-index companies: unknown option ${arg}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
21
57
|
const written = [];
|
|
22
|
-
|
|
58
|
+
let scopedDirectories;
|
|
59
|
+
if (selectedCompany === undefined) {
|
|
60
|
+
scopedDirectories = immediateEntries(context.root, "companies", "dir");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (selectedCompany === "." || selectedCompany === ".." ||
|
|
64
|
+
selectedCompany.includes("/") || selectedCompany.includes("\\") ||
|
|
65
|
+
path.basename(selectedCompany) !== selectedCompany ||
|
|
66
|
+
selectedCompany.startsWith("_") || isHidden(selectedCompany)) {
|
|
67
|
+
throw new CompanyIndexUserError(`rebuild-index companies: invalid company slug "${selectedCompany}"`);
|
|
68
|
+
}
|
|
69
|
+
const companiesRoot = path.resolve(context.root, "companies");
|
|
70
|
+
const selectedDirectory = path.resolve(companiesRoot, selectedCompany);
|
|
71
|
+
let selectedStat;
|
|
72
|
+
try {
|
|
73
|
+
selectedStat = fs.lstatSync(selectedDirectory);
|
|
74
|
+
}
|
|
75
|
+
catch (cause) {
|
|
76
|
+
if (cause.code !== "ENOENT")
|
|
77
|
+
throw cause;
|
|
78
|
+
throw new CompanyIndexUserError(`rebuild-index companies: no company directory named "${selectedCompany}"`);
|
|
79
|
+
}
|
|
80
|
+
if (!selectedStat.isDirectory()) {
|
|
81
|
+
throw new CompanyIndexUserError(`rebuild-index companies: no company directory named "${selectedCompany}"`);
|
|
82
|
+
}
|
|
83
|
+
scopedDirectories = [selectedDirectory];
|
|
84
|
+
}
|
|
85
|
+
for (const directory of scopedDirectories) {
|
|
23
86
|
const company = basename(directory);
|
|
24
87
|
if (company.startsWith("_") || isHidden(company))
|
|
25
88
|
continue;
|
|
@@ -40,7 +103,7 @@ export function renderCompanies(context) {
|
|
|
40
103
|
const parsed = readJson(prd);
|
|
41
104
|
let text = typeof parsed?.description === "string" ? parsed.description : "";
|
|
42
105
|
text = truncate(sanitize(text), 110) || "—";
|
|
43
|
-
lines.push(`| \`${project}\` | ${projectStatus(context.root, project, prd, "active")} | ${text} |`);
|
|
106
|
+
lines.push(`| \`${project}\` | ${projectStatus(context.root, project, prd, "active", prd)} | ${text} |`);
|
|
44
107
|
}
|
|
45
108
|
}
|
|
46
109
|
else
|
|
@@ -2,4 +2,5 @@ import { RenderContext, RenderResult } from "./shared.js";
|
|
|
2
2
|
export type IndexTarget = "all" | "companies" | "company-knowledge" | "orchestrator" | "projects" | "public-knowledge" | "reports" | "social-drafts" | "threads" | "workers";
|
|
3
3
|
export declare function renderIndexTarget(target: IndexTarget, context: RenderContext, args?: string[]): RenderResult;
|
|
4
4
|
export { atomicWrite, comparePath, immediateEntries, type RenderContext, type RenderResult } from "./shared.js";
|
|
5
|
+
export { CompanyIndexUserError } from "./companies.js";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -10,7 +10,7 @@ import { renderWorkers } from "./workers.js";
|
|
|
10
10
|
const allOrder = ["threads", "orchestrator", "companies", "projects", "company-knowledge", "public-knowledge", "workers", "reports", "social-drafts"];
|
|
11
11
|
export function renderIndexTarget(target, context, args = []) {
|
|
12
12
|
switch (target) {
|
|
13
|
-
case "companies": return renderCompanies(context);
|
|
13
|
+
case "companies": return renderCompanies(context, args);
|
|
14
14
|
case "company-knowledge": return renderCompanyKnowledge(context);
|
|
15
15
|
case "orchestrator": return renderOrchestrator(context);
|
|
16
16
|
case "projects": return renderProjects(context);
|
|
@@ -40,4 +40,5 @@ export function renderIndexTarget(target, context, args = []) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
export { atomicWrite, comparePath, immediateEntries } from "./shared.js";
|
|
43
|
+
export { CompanyIndexUserError } from "./companies.js";
|
|
43
44
|
//# sourceMappingURL=index.js.map
|
|
@@ -39,7 +39,7 @@ export declare function hasUnclosedKeepMarker(content: string): boolean;
|
|
|
39
39
|
export declare function looksAutoGenerated(content: string): boolean;
|
|
40
40
|
export declare function generatedStamp(content: string): string | undefined;
|
|
41
41
|
export declare function log(context: RenderContext, message: string): void;
|
|
42
|
-
export declare function projectStatus(root: string, project: string, prdPath: string, fallback: string): string;
|
|
42
|
+
export declare function projectStatus(root: string, project: string, prdPath: string, fallback: string, expectedStatePrdPath?: string): string;
|
|
43
43
|
export declare function basename(file: string): string;
|
|
44
44
|
export declare function isHidden(name: string): boolean;
|
|
45
45
|
export declare function mtime(file: string): number;
|
|
@@ -134,9 +134,11 @@ export function generatedStamp(content) {
|
|
|
134
134
|
return content.match(/^<!-- hq-generated-sha256: ([a-f0-9]{64}) -->/m)?.[1];
|
|
135
135
|
}
|
|
136
136
|
export function log(context, message) { context.log?.(message); }
|
|
137
|
-
export function projectStatus(root, project, prdPath, fallback) {
|
|
137
|
+
export function projectStatus(root, project, prdPath, fallback, expectedStatePrdPath) {
|
|
138
138
|
const state = readJson(at(root, `workspace/orchestrator/${project}/state.json`));
|
|
139
|
-
|
|
139
|
+
const stateMatchesPrd = expectedStatePrdPath === undefined || (typeof state?.prd_path === "string" &&
|
|
140
|
+
path.resolve(root, state.prd_path) === path.resolve(root, expectedStatePrdPath));
|
|
141
|
+
if (state && stateMatchesPrd) {
|
|
140
142
|
const raw = typeof state.status === "string" ? state.status : "active";
|
|
141
143
|
if (["completed", "complete"].includes(raw))
|
|
142
144
|
return "complete";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MqttClientEventCallbacks } from "mqtt";
|
|
2
|
+
/** Event callbacks shared by the daemon's two MQTT client seams. */
|
|
3
|
+
export type MeshMqttEventMap = Pick<MqttClientEventCallbacks, "connect" | "error" | "close" | "message">;
|
|
4
|
+
export interface MeshMqttEventClient {
|
|
5
|
+
on<Event extends keyof MeshMqttEventMap>(event: Event, handler: MeshMqttEventMap[Event]): unknown;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=mqtt-events.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @ts-expect-error unsupported event names are not part of mqtt's event map
|
|
2
|
+
presence.on("conect", () => { });
|
|
3
|
+
// @ts-expect-error MQTT message handlers receive a string topic
|
|
4
|
+
presence.on("message", (topic) => void topic);
|
|
5
|
+
// @ts-expect-error unsupported event names are not part of mqtt's event map
|
|
6
|
+
workCache.on("conect", () => { });
|
|
7
|
+
// @ts-expect-error MQTT message handlers receive a string topic
|
|
8
|
+
workCache.on("message", (topic) => void topic);
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=mqtt-events.test-d.js.map
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - Server credential refusals use a long retry (default 10m ±20%)
|
|
13
13
|
*/
|
|
14
14
|
import { type IClientOptions, type MqttClient } from "mqtt";
|
|
15
|
+
import type { MeshMqttEventClient } from "./mqtt-events.js";
|
|
15
16
|
import { type Contract3Bundle, type CredentialsFetcher, type TimerHost } from "./credentials.js";
|
|
16
17
|
export type MqttConnectionState = "idle" | "connecting" | "connected" | "reconnecting" | "closed";
|
|
17
18
|
/**
|
|
@@ -49,8 +50,7 @@ export interface PresencePayload {
|
|
|
49
50
|
at: string;
|
|
50
51
|
}
|
|
51
52
|
/** Structural mqtt client surface used by PresenceClient (test seam). */
|
|
52
|
-
export interface MeshMqttClientLike {
|
|
53
|
-
on(event: string, handler: (...args: any[]) => void): unknown;
|
|
53
|
+
export interface MeshMqttClientLike extends MeshMqttEventClient {
|
|
54
54
|
publish(topic: string, message: string | Buffer, opts: {
|
|
55
55
|
qos: 0 | 1 | 2;
|
|
56
56
|
retain?: boolean;
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Owns live-cache.json writes. Presence publish stays on PresenceClient.
|
|
4
4
|
*/
|
|
5
5
|
import type { IClientOptions } from "mqtt";
|
|
6
|
+
import type { MeshMqttEventClient } from "./mqtt-events.js";
|
|
6
7
|
import { type PersonalCredentialsFetcher } from "./credentials.js";
|
|
7
|
-
export interface WorkCacheMqtt {
|
|
8
|
-
on(event: string, handler: (...args: any[]) => void): unknown;
|
|
8
|
+
export interface WorkCacheMqtt extends MeshMqttEventClient {
|
|
9
9
|
subscribe(topic: string, opts: {
|
|
10
10
|
qos: 0 | 1;
|
|
11
11
|
}, cb?: (err?: Error | null) => void): unknown;
|
|
@@ -748,7 +748,7 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
748
748
|
console.error(chalk.dim(` Or repair the install: hq rescue`));
|
|
749
749
|
}
|
|
750
750
|
console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
|
|
751
|
-
await captureSelfUpdateOutcome("update-failed", current, latestValid, install.manager, result
|
|
751
|
+
await captureSelfUpdateOutcome("update-failed", current, latestValid, install.manager, result, env, flavor.noun === "rescue");
|
|
752
752
|
return { action: "update-failed", latest };
|
|
753
753
|
}
|
|
754
754
|
if (installSnapshot)
|
|
@@ -801,10 +801,52 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
801
801
|
console.error(chalk.dim(`✓ hq-cli updated to ${latest}.`));
|
|
802
802
|
return { action: "reexec", reexecStatus: status, latest };
|
|
803
803
|
}
|
|
804
|
-
const SELF_UPDATE_ERROR_CLASSES = new Set([
|
|
805
|
-
|
|
804
|
+
const SELF_UPDATE_ERROR_CLASSES = new Set([
|
|
805
|
+
"MODULE_NOT_FOUND",
|
|
806
|
+
"ENOENT",
|
|
807
|
+
"EACCES",
|
|
808
|
+
"EEXIST",
|
|
809
|
+
"E404",
|
|
810
|
+
"EAI_AGAIN",
|
|
811
|
+
"ECONNREFUSED",
|
|
812
|
+
"ECONNRESET",
|
|
813
|
+
"ENOTFOUND",
|
|
814
|
+
"EPERM",
|
|
815
|
+
"ETARGET",
|
|
816
|
+
"ETIMEDOUT",
|
|
817
|
+
]);
|
|
818
|
+
function selfUpdateErrorReason(code, failureKind) {
|
|
819
|
+
switch (code) {
|
|
820
|
+
case "MODULE_NOT_FOUND":
|
|
821
|
+
return "module_missing";
|
|
822
|
+
case "ENOENT":
|
|
823
|
+
return "program_not_found";
|
|
824
|
+
case "EACCES":
|
|
825
|
+
case "EPERM":
|
|
826
|
+
return "permission_denied";
|
|
827
|
+
case "EEXIST":
|
|
828
|
+
return "file_collision";
|
|
829
|
+
case "E404":
|
|
830
|
+
return "npm_package_not_found";
|
|
831
|
+
case "ETARGET":
|
|
832
|
+
return "npm_target_unavailable";
|
|
833
|
+
case "EAI_AGAIN":
|
|
834
|
+
case "ECONNREFUSED":
|
|
835
|
+
case "ECONNRESET":
|
|
836
|
+
case "ENOTFOUND":
|
|
837
|
+
return "registry_network_error";
|
|
838
|
+
case "ETIMEDOUT":
|
|
839
|
+
return "timed_out";
|
|
840
|
+
default:
|
|
841
|
+
if (failureKind === "package_manager_exit")
|
|
842
|
+
return "package_manager_exit";
|
|
843
|
+
return code ? "other_error" : "unknown";
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
async function captureSelfUpdateOutcome(action, fromVersion, targetVersion, installFlavor, failure, env, flushBeforeExit) {
|
|
806
847
|
if (isCliTelemetryDisabled(env))
|
|
807
848
|
return;
|
|
849
|
+
const code = failure?.diagnosticCode ?? failure?.code;
|
|
808
850
|
const error = new Error(action === "update-failed" ? "Self-update failed" : "Self-update ineffective");
|
|
809
851
|
// This event needs no stack: frames would expose the machine's install path.
|
|
810
852
|
error.stack = undefined;
|
|
@@ -814,7 +856,12 @@ async function captureSelfUpdateOutcome(action, fromVersion, targetVersion, inst
|
|
|
814
856
|
from_version: fromVersion,
|
|
815
857
|
target_version: targetVersion,
|
|
816
858
|
install_flavor: installFlavor,
|
|
817
|
-
error_class: code && SELF_UPDATE_ERROR_CLASSES.has(code)
|
|
859
|
+
error_class: code && SELF_UPDATE_ERROR_CLASSES.has(code)
|
|
860
|
+
? code
|
|
861
|
+
: failure?.failureKind === "package_manager_exit"
|
|
862
|
+
? "package_manager_exit"
|
|
863
|
+
: "other",
|
|
864
|
+
error_reason: selfUpdateErrorReason(code, failure?.failureKind),
|
|
818
865
|
},
|
|
819
866
|
});
|
|
820
867
|
if (flushBeforeExit) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type StagedInstallResult = {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
detail?: string;
|
|
4
|
+
code?: string;
|
|
5
|
+
};
|
|
6
|
+
export type StagedInstallRunner = (command: string, args: string[], env?: NodeJS.ProcessEnv) => StagedInstallResult;
|
|
7
|
+
/**
|
|
8
|
+
* Build hq-cli in a space-free temporary npm prefix, then replace the package
|
|
9
|
+
* and its executable links under the real prefix only after npm succeeds.
|
|
10
|
+
* Existing files are held beside their destinations until activation completes,
|
|
11
|
+
* so failed installs and failed activation restore the prior working tree.
|
|
12
|
+
*/
|
|
13
|
+
export declare function installNpmPackageStaged(prefix: string, packageSpec: string, runner: StagedInstallRunner, platform?: NodeJS.Platform, activate?: typeof activateStagedNpmPackage): StagedInstallResult;
|
|
14
|
+
/** Atomically replace a package already built under a temporary npm prefix. */
|
|
15
|
+
export declare function activateStagedNpmPackage(prefix: string, stagePrefix: string, platform?: NodeJS.Platform): StagedInstallResult;
|
|
16
|
+
//# sourceMappingURL=staged-npm-install.d.ts.map
|