@indigoai-us/hq-cli 5.129.0 → 5.131.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 +52 -1
- package/contracts/preflight/v1/fixtures.json +87 -0
- package/contracts/preflight/v1/schema.json +58 -0
- package/dist/command-catalog.generated.d.ts +27 -0
- package/dist/command-catalog.generated.js +34 -0
- package/dist/commands/core.js +7 -1
- package/dist/commands/dm.d.ts +2 -0
- package/dist/commands/dm.js +1 -1
- package/dist/commands/files.js +137 -9
- package/dist/commands/mesh.js +46 -12
- package/dist/lib/core-utils/sentry-report.d.ts +27 -7
- package/dist/lib/core-utils/sentry-report.js +428 -28
- package/dist/lib/mesh/api.d.ts +2 -0
- package/dist/lib/mesh/api.js +15 -2
- package/dist/lib/mesh/live/daemon/doctor.d.ts +4 -0
- package/dist/lib/mesh/live/daemon/doctor.js +10 -2
- package/dist/lib/mesh/live/daemon/run.js +3 -0
- package/dist/lib/mesh/live/emit-client.d.ts +2 -0
- package/dist/lib/mesh/live/emit-client.js +15 -8
- package/dist/lib/mesh/live/emit.d.ts +9 -0
- package/dist/lib/mesh/live/emit.js +28 -2
- package/dist/lib/mesh/live/flush.js +9 -3
- package/dist/lib/search-index/index.js +22 -3
- package/dist/lib/work-context/preflight-contract.d.ts +28 -0
- package/dist/lib/work-context/preflight-contract.js +85 -0
- package/dist/main.js +11 -1
- package/dist/utils/cognito-session.d.ts +39 -0
- package/dist/utils/cognito-session.js +119 -3
- package/dist/utils/environmental-error.d.ts +18 -0
- package/dist/utils/environmental-error.js +240 -7
- package/dist/utils/expected-cli-error.d.ts +2 -0
- package/dist/utils/expected-cli-error.js +4 -0
- package/dist/utils/vault-api.d.ts +1 -0
- package/dist/utils/vault-api.js +11 -3
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.131.0] — 2026-09-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `hq files share` can post the share record to a project channel instead of a
|
|
10
|
+
DM: `--channel <name>` names the channel and `--mention <person>` (repeatable)
|
|
11
|
+
tags people in it. Using `--mention` without `--channel` is an error. If you
|
|
12
|
+
or a mentioned person are not members of that channel, access is still
|
|
13
|
+
granted and the record falls back to a DM, with a short note on the CLI.
|
|
14
|
+
- Offline preflight results the CLI can emit are published at
|
|
15
|
+
`contracts/preflight/v1/schema.json` and `fixtures.json` (included in the
|
|
16
|
+
package). `hq mesh contract preflight --print-fixtures` prints the same
|
|
17
|
+
document. A test fails if the committed fixtures drift from the emitter.
|
|
18
|
+
contractVersion stays pinned at 1 until hq-core's vendored copy moves with it.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Expected caller errors from company resolution, membership, and qmd lookups
|
|
23
|
+
no longer create Sentry issues. Windows filesystem failures remain visible
|
|
24
|
+
and now include redacted path context plus a sync-aware diagnostic.
|
|
25
|
+
|
|
26
|
+
- An acknowledged Work Mesh register keeps the company on the session even
|
|
27
|
+
when there is no project, or a project and no task. The next flush posts a
|
|
28
|
+
held event for that session instead of leaving it held for lack of a company.
|
|
29
|
+
Tests cover each acked-register branch.
|
|
30
|
+
|
|
31
|
+
- `hq mesh project set` sends the project version it just read (`If-Match` and
|
|
32
|
+
`expectedVersion`). A 409 prints "The Board changed while you were editing.
|
|
33
|
+
Re-run to pick up the latest." and exits non-zero. `--json` includes
|
|
34
|
+
`VERSION_CONFLICT`.
|
|
35
|
+
|
|
36
|
+
- `hq mesh session` prints each rejected event's server code (and `--json`
|
|
37
|
+
includes `rejections`). A flush dead-letter row for a per-event rejection
|
|
38
|
+
stores that code next to `reason`.
|
|
39
|
+
|
|
40
|
+
- A Cognito refresh that comes back for a different subject is refused and the
|
|
41
|
+
active `cognito-tokens.json` is left unchanged. `hq mesh daemon doctor`
|
|
42
|
+
prints the whoami subject and the subject the daemon is using.
|
|
43
|
+
|
|
44
|
+
## [5.130.0] — 2026-09-22
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Hook-timeout reports remain visible as warning events with stable grouping and
|
|
49
|
+
diagnostic tags. Repeated reports are rate-limited while the next capture
|
|
50
|
+
records the suppressed count.
|
|
51
|
+
|
|
5
52
|
## [5.129.0] — 2026-09-22
|
|
6
53
|
|
|
7
54
|
### Added
|
|
@@ -305,7 +352,11 @@
|
|
|
305
352
|
with its tenant intact, the write reflects the true classification, and no
|
|
306
353
|
`projectId`, `taskId`, or binding episode is fabricated on partial paths.
|
|
307
354
|
Note: a company resolved by slug only (no uid) is still not attributed on
|
|
308
|
-
either path.
|
|
355
|
+
either path. Tests disable each of those two writers in turn
|
|
356
|
+
(`writeAckedRegisterState`, and the post-ack `writeSessionState`) and
|
|
357
|
+
require the other one to still land the company for a company-only ack
|
|
358
|
+
and a company-plus-project ack. The production write itself landed in
|
|
359
|
+
`28aee9e` (#670).
|
|
309
360
|
|
|
310
361
|
- The setup bot no longer posts the same question twice when a turn takes a
|
|
311
362
|
while: a question asked in an interim update is held back if the final
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contractVersion": 1,
|
|
3
|
+
"source": "hq-cli contracts/preflight/v1/fixtures.json",
|
|
4
|
+
"classifications": [
|
|
5
|
+
"unresolved",
|
|
6
|
+
"needs_company",
|
|
7
|
+
"company_conflict",
|
|
8
|
+
"needs_project",
|
|
9
|
+
"needs_task",
|
|
10
|
+
"bound",
|
|
11
|
+
"untracked"
|
|
12
|
+
],
|
|
13
|
+
"fixtures": [
|
|
14
|
+
{
|
|
15
|
+
"contractVersion": 1,
|
|
16
|
+
"kind": "queued",
|
|
17
|
+
"classification": "unresolved",
|
|
18
|
+
"delivery": "queued",
|
|
19
|
+
"lifecycle": "open",
|
|
20
|
+
"sessionId": "sid-preflight-fixture",
|
|
21
|
+
"clientOperationId": "op-preflight-fixture"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"contractVersion": 1,
|
|
25
|
+
"kind": "needs_company",
|
|
26
|
+
"classification": "needs_company",
|
|
27
|
+
"delivery": "clean",
|
|
28
|
+
"lifecycle": "open",
|
|
29
|
+
"sessionId": "sid-preflight-fixture",
|
|
30
|
+
"clientOperationId": "op-preflight-fixture"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"contractVersion": 1,
|
|
34
|
+
"kind": "company_conflict",
|
|
35
|
+
"classification": "company_conflict",
|
|
36
|
+
"delivery": "clean",
|
|
37
|
+
"lifecycle": "open",
|
|
38
|
+
"sessionId": "sid-preflight-fixture",
|
|
39
|
+
"clientOperationId": "op-preflight-fixture"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"contractVersion": 1,
|
|
43
|
+
"kind": "queued",
|
|
44
|
+
"classification": "needs_project",
|
|
45
|
+
"delivery": "queued",
|
|
46
|
+
"lifecycle": "open",
|
|
47
|
+
"sessionId": "sid-preflight-fixture",
|
|
48
|
+
"clientOperationId": "op-preflight-fixture",
|
|
49
|
+
"companySlug": "acme",
|
|
50
|
+
"companyUid": "cmp_acme"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"contractVersion": 1,
|
|
54
|
+
"kind": "queued",
|
|
55
|
+
"classification": "needs_task",
|
|
56
|
+
"delivery": "queued",
|
|
57
|
+
"lifecycle": "open",
|
|
58
|
+
"sessionId": "sid-preflight-fixture",
|
|
59
|
+
"clientOperationId": "op-preflight-fixture",
|
|
60
|
+
"companySlug": "acme",
|
|
61
|
+
"companyUid": "cmp_acme",
|
|
62
|
+
"projectId": "dispatch-proj"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"contractVersion": 1,
|
|
66
|
+
"kind": "queued",
|
|
67
|
+
"classification": "bound",
|
|
68
|
+
"delivery": "queued",
|
|
69
|
+
"lifecycle": "open",
|
|
70
|
+
"sessionId": "sid-preflight-fixture",
|
|
71
|
+
"clientOperationId": "op-preflight-fixture",
|
|
72
|
+
"companySlug": "acme",
|
|
73
|
+
"companyUid": "cmp_acme",
|
|
74
|
+
"projectId": "dispatch-proj",
|
|
75
|
+
"taskId": "US-9"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"contractVersion": 1,
|
|
79
|
+
"kind": "untracked",
|
|
80
|
+
"classification": "untracked",
|
|
81
|
+
"delivery": "clean",
|
|
82
|
+
"lifecycle": "open",
|
|
83
|
+
"sessionId": "sid-preflight-fixture",
|
|
84
|
+
"clientOperationId": "op-preflight-fixture"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://hq.getindigo.ai/schemas/work-context/preflight/v1/schema.json",
|
|
4
|
+
"title": "Offline preflight ContextResult",
|
|
5
|
+
"description": "Shapes hq mesh context reconcile --offline emits. Hook consumers must accept every fixture and reject any classification outside this enum. Bump contractVersion together with hq-core core/hooks/SessionStart/preflight-fixtures.json.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"contractVersion",
|
|
10
|
+
"kind",
|
|
11
|
+
"classification",
|
|
12
|
+
"delivery",
|
|
13
|
+
"lifecycle",
|
|
14
|
+
"sessionId",
|
|
15
|
+
"clientOperationId"
|
|
16
|
+
],
|
|
17
|
+
"properties": {
|
|
18
|
+
"contractVersion": { "type": "integer", "const": 1 },
|
|
19
|
+
"kind": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"enum": [
|
|
22
|
+
"queued",
|
|
23
|
+
"needs_company",
|
|
24
|
+
"company_conflict",
|
|
25
|
+
"untracked",
|
|
26
|
+
"bound",
|
|
27
|
+
"needs_project",
|
|
28
|
+
"needs_task",
|
|
29
|
+
"error"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"classification": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"enum": [
|
|
35
|
+
"unresolved",
|
|
36
|
+
"needs_company",
|
|
37
|
+
"company_conflict",
|
|
38
|
+
"needs_project",
|
|
39
|
+
"needs_task",
|
|
40
|
+
"bound",
|
|
41
|
+
"untracked"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"delivery": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"enum": ["clean", "queued", "acked", "quarantined"]
|
|
47
|
+
},
|
|
48
|
+
"lifecycle": { "type": "string", "enum": ["open", "terminal"] },
|
|
49
|
+
"sessionId": { "type": "string", "minLength": 1 },
|
|
50
|
+
"clientOperationId": { "type": "string", "minLength": 1 },
|
|
51
|
+
"companySlug": { "type": "string", "minLength": 1 },
|
|
52
|
+
"companyUid": { "type": "string", "minLength": 1 },
|
|
53
|
+
"projectId": { "type": "string", "minLength": 1 },
|
|
54
|
+
"taskId": { "type": "string", "minLength": 1 },
|
|
55
|
+
"bindingEpisodeId": { "type": "string", "minLength": 1 },
|
|
56
|
+
"errorCode": { "type": "string", "minLength": 1 }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1677,6 +1677,12 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
1677
1677
|
}, {
|
|
1678
1678
|
readonly flags: "--no-open";
|
|
1679
1679
|
readonly description: "Print the share-session URL but do not launch the browser";
|
|
1680
|
+
}, {
|
|
1681
|
+
readonly flags: "--channel <name>";
|
|
1682
|
+
readonly description: "Post the share record to this named channel instead of a DM";
|
|
1683
|
+
}, {
|
|
1684
|
+
readonly flags: "--mention <person>";
|
|
1685
|
+
readonly description: "Mention a person on the channel share record (repeatable)";
|
|
1680
1686
|
}];
|
|
1681
1687
|
readonly subcommands: readonly [];
|
|
1682
1688
|
}, {
|
|
@@ -5984,6 +5990,27 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5984
5990
|
}];
|
|
5985
5991
|
readonly subcommands: readonly [];
|
|
5986
5992
|
}];
|
|
5993
|
+
}, {
|
|
5994
|
+
readonly name: "contract";
|
|
5995
|
+
readonly description: "Print versioned work-mesh contracts for other repositories";
|
|
5996
|
+
readonly aliases: readonly [];
|
|
5997
|
+
readonly hidden: false;
|
|
5998
|
+
readonly usage: "[options] [command]";
|
|
5999
|
+
readonly arguments: readonly [];
|
|
6000
|
+
readonly options: readonly [];
|
|
6001
|
+
readonly subcommands: readonly [{
|
|
6002
|
+
readonly name: "preflight";
|
|
6003
|
+
readonly description: "Offline preflight ContextResult contract (contracts/preflight/v1)";
|
|
6004
|
+
readonly aliases: readonly [];
|
|
6005
|
+
readonly hidden: false;
|
|
6006
|
+
readonly usage: "[options]";
|
|
6007
|
+
readonly arguments: readonly [];
|
|
6008
|
+
readonly options: readonly [{
|
|
6009
|
+
readonly flags: "--print-fixtures";
|
|
6010
|
+
readonly description: "Write the fixture file JSON to stdout";
|
|
6011
|
+
}];
|
|
6012
|
+
readonly subcommands: readonly [];
|
|
6013
|
+
}];
|
|
5987
6014
|
}, {
|
|
5988
6015
|
readonly name: "session";
|
|
5989
6016
|
readonly description: "Local session-event spool: enqueue (printf-compatible), flush, and live status";
|
|
@@ -2138,6 +2138,14 @@ export const COMMAND_CATALOG = [
|
|
|
2138
2138
|
{
|
|
2139
2139
|
"flags": "--no-open",
|
|
2140
2140
|
"description": "Print the share-session URL but do not launch the browser"
|
|
2141
|
+
},
|
|
2142
|
+
{
|
|
2143
|
+
"flags": "--channel <name>",
|
|
2144
|
+
"description": "Post the share record to this named channel instead of a DM"
|
|
2145
|
+
},
|
|
2146
|
+
{
|
|
2147
|
+
"flags": "--mention <person>",
|
|
2148
|
+
"description": "Mention a person on the channel share record (repeatable)"
|
|
2141
2149
|
}
|
|
2142
2150
|
],
|
|
2143
2151
|
"subcommands": []
|
|
@@ -7725,6 +7733,32 @@ export const COMMAND_CATALOG = [
|
|
|
7725
7733
|
}
|
|
7726
7734
|
]
|
|
7727
7735
|
},
|
|
7736
|
+
{
|
|
7737
|
+
"name": "contract",
|
|
7738
|
+
"description": "Print versioned work-mesh contracts for other repositories",
|
|
7739
|
+
"aliases": [],
|
|
7740
|
+
"hidden": false,
|
|
7741
|
+
"usage": "[options] [command]",
|
|
7742
|
+
"arguments": [],
|
|
7743
|
+
"options": [],
|
|
7744
|
+
"subcommands": [
|
|
7745
|
+
{
|
|
7746
|
+
"name": "preflight",
|
|
7747
|
+
"description": "Offline preflight ContextResult contract (contracts/preflight/v1)",
|
|
7748
|
+
"aliases": [],
|
|
7749
|
+
"hidden": false,
|
|
7750
|
+
"usage": "[options]",
|
|
7751
|
+
"arguments": [],
|
|
7752
|
+
"options": [
|
|
7753
|
+
{
|
|
7754
|
+
"flags": "--print-fixtures",
|
|
7755
|
+
"description": "Write the fixture file JSON to stdout"
|
|
7756
|
+
}
|
|
7757
|
+
],
|
|
7758
|
+
"subcommands": []
|
|
7759
|
+
}
|
|
7760
|
+
]
|
|
7761
|
+
},
|
|
7728
7762
|
{
|
|
7729
7763
|
"name": "session",
|
|
7730
7764
|
"description": "Local session-event spool: enqueue (printf-compatible), flush, and live status",
|
package/dist/commands/core.js
CHANGED
|
@@ -483,7 +483,13 @@ export function registerCoreCommands(program) {
|
|
|
483
483
|
.action(async (options) => {
|
|
484
484
|
const stdin = await readSentryReportStdin(process.stdin);
|
|
485
485
|
const timeoutMs = options.timeoutMs === undefined ? undefined : Number(options.timeoutMs);
|
|
486
|
-
const
|
|
486
|
+
const scope = core.opts();
|
|
487
|
+
const hqRoot = scope.hqRoot ? resolveLiveRoot({ hqRoot: scope.hqRoot }) : undefined;
|
|
488
|
+
const result = await reportSentryEvent(stdin ?? "", {
|
|
489
|
+
dryRun: options.dryRun,
|
|
490
|
+
timeoutMs,
|
|
491
|
+
hqRoot,
|
|
492
|
+
});
|
|
487
493
|
if (result.code !== 0)
|
|
488
494
|
process.exitCode = result.code;
|
|
489
495
|
});
|
package/dist/commands/dm.d.ts
CHANGED
|
@@ -190,6 +190,8 @@ export declare function buildConnectionActionBody(identifier: string, matched: C
|
|
|
190
190
|
} | {
|
|
191
191
|
withEmail: string;
|
|
192
192
|
};
|
|
193
|
+
/** Fetch the caller's channels (GET /v1/notify/channels). */
|
|
194
|
+
export declare function fetchChannels(token: string): Promise<ChannelSummary[]>;
|
|
193
195
|
/** One incoming DM as returned by GET /v1/notify/inbox. */
|
|
194
196
|
export interface DmInboxEvent {
|
|
195
197
|
eventId: string;
|
package/dist/commands/dm.js
CHANGED
|
@@ -489,7 +489,7 @@ async function runGroupSend(recipients, message) {
|
|
|
489
489
|
}
|
|
490
490
|
}
|
|
491
491
|
/** Fetch the caller's channels (GET /v1/notify/channels). */
|
|
492
|
-
async function fetchChannels(token) {
|
|
492
|
+
export async function fetchChannels(token) {
|
|
493
493
|
const res = await vaultApiFetch({ token, path: "/v1/notify/channels" });
|
|
494
494
|
if (!res.ok) {
|
|
495
495
|
const err = (await res.json().catch(() => ({})));
|
package/dist/commands/files.js
CHANGED
|
@@ -8,6 +8,13 @@ import { GROUP_ID_PATTERN, EMAIL_PATTERN, normalizeFilePrefix } from "./_pattern
|
|
|
8
8
|
import { looksLikeCompanyUid } from "../utils/vault-api.js";
|
|
9
9
|
import { AuthError } from "../utils/auth-error.js";
|
|
10
10
|
import { formatAccessDenied, offerAccessLadder, } from "../utils/access-denied-hint.js";
|
|
11
|
+
import { fetchChannels, matchChannelsByName } from "./dm.js";
|
|
12
|
+
import { resolveCompanySlug, resolvePersonWithRosterFallback, } from "./people.js";
|
|
13
|
+
import { findHqRoot } from "../utils/manifest.js";
|
|
14
|
+
const PERSON_UID_PATTERN = /^(prs|agt)_[A-Za-z0-9_-]+$/;
|
|
15
|
+
function collectMentions(value, previous) {
|
|
16
|
+
return [...previous, value];
|
|
17
|
+
}
|
|
11
18
|
// ---------------------------------------------------------------------------
|
|
12
19
|
// Pure helpers (exported for unit tests)
|
|
13
20
|
// ---------------------------------------------------------------------------
|
|
@@ -129,8 +136,23 @@ export function registerFilesCommand(program) {
|
|
|
129
136
|
.option("--full", "Grant access to the ENTIRE vault (the '*' wildcard prefix) — no need to quote a glob. Requires --with; defaults to write permission.")
|
|
130
137
|
.option("--expires <duration>", "Token expiry duration for share-session URL (e.g. 15m, 1h, 24h). Default 15m. Max 24h.")
|
|
131
138
|
.option("--no-open", "Print the share-session URL but do not launch the browser")
|
|
139
|
+
.option("--channel <name>", "Post the share record to this named channel instead of a DM")
|
|
140
|
+
.option("--mention <person>", "Mention a person on the channel share record (repeatable)", collectMentions, [])
|
|
132
141
|
.action(async (paths, opts) => {
|
|
133
142
|
try {
|
|
143
|
+
const mentions = opts.mention ?? [];
|
|
144
|
+
if (mentions.length > 0 && opts.channel === undefined) {
|
|
145
|
+
console.error(chalk.red("--mention requires --channel <name>."));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
const companySlug = files.opts().company;
|
|
149
|
+
const channelTarget = opts.channel !== undefined
|
|
150
|
+
? await resolveShareChannelTarget({
|
|
151
|
+
channelName: opts.channel,
|
|
152
|
+
mentions,
|
|
153
|
+
companySlug,
|
|
154
|
+
})
|
|
155
|
+
: undefined;
|
|
134
156
|
// Full-vault grant: a glob-safe affordance for "give this person the
|
|
135
157
|
// whole vault" so admins never have to quote a `*` (an unquoted glob
|
|
136
158
|
// expands to local filenames and instantly fails the one-prefix
|
|
@@ -150,8 +172,9 @@ export function registerFilesCommand(program) {
|
|
|
150
172
|
prefix: "*",
|
|
151
173
|
principal: opts.with,
|
|
152
174
|
permission: opts.permission ?? "write",
|
|
153
|
-
companySlug
|
|
175
|
+
companySlug,
|
|
154
176
|
fullVault: true,
|
|
177
|
+
...channelTarget,
|
|
155
178
|
});
|
|
156
179
|
return;
|
|
157
180
|
}
|
|
@@ -159,9 +182,9 @@ export function registerFilesCommand(program) {
|
|
|
159
182
|
console.error(chalk.red("usage: hq files share <paths...> [--with <principal>]"));
|
|
160
183
|
process.exit(1);
|
|
161
184
|
}
|
|
162
|
-
// Fork: --with present → existing direct-grant path.
|
|
163
|
-
//
|
|
164
|
-
//
|
|
185
|
+
// Fork: --with present → existing direct-grant path. --channel without
|
|
186
|
+
// --with grants each --mention as a person. No --with and no --channel
|
|
187
|
+
// → browser-launch share-session path.
|
|
165
188
|
if (opts.with !== undefined) {
|
|
166
189
|
if (paths.length !== 1) {
|
|
167
190
|
console.error(chalk.red("Direct grant (--with) takes exactly one prefix. Pass multiple paths only when minting a share-session URL."));
|
|
@@ -171,15 +194,42 @@ export function registerFilesCommand(program) {
|
|
|
171
194
|
prefix: paths[0],
|
|
172
195
|
principal: opts.with,
|
|
173
196
|
permission: opts.permission,
|
|
174
|
-
companySlug
|
|
197
|
+
companySlug,
|
|
198
|
+
...channelTarget,
|
|
175
199
|
});
|
|
176
200
|
return;
|
|
177
201
|
}
|
|
202
|
+
if (channelTarget) {
|
|
203
|
+
if (channelTarget.mentionPersonUids.length === 0) {
|
|
204
|
+
console.error(chalk.red("--channel requires --with <principal> or --mention <person>."));
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
if (paths.length !== 1) {
|
|
208
|
+
console.error(chalk.red("Channel share takes exactly one prefix. Pass multiple paths only when minting a share-session URL."));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
const permission = opts.permission ?? "read";
|
|
212
|
+
for (const [index, uid] of channelTarget.mentionPersonUids.entries()) {
|
|
213
|
+
await runDirectGrant({
|
|
214
|
+
prefix: paths[0],
|
|
215
|
+
principal: uid,
|
|
216
|
+
permission,
|
|
217
|
+
companySlug,
|
|
218
|
+
...(index === 0
|
|
219
|
+
? {
|
|
220
|
+
channelId: channelTarget.channelId,
|
|
221
|
+
mentionPersonUids: channelTarget.mentionPersonUids,
|
|
222
|
+
}
|
|
223
|
+
: {}),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
178
228
|
await runShareSession({
|
|
179
229
|
paths,
|
|
180
230
|
expires: opts.expires,
|
|
181
231
|
launchBrowser: opts.open,
|
|
182
|
-
companySlug
|
|
232
|
+
companySlug,
|
|
183
233
|
});
|
|
184
234
|
}
|
|
185
235
|
catch (err) {
|
|
@@ -511,6 +561,64 @@ export function registerFilesCommand(program) {
|
|
|
511
561
|
// files-browse.ts) onto the same group without re-creating it.
|
|
512
562
|
return files;
|
|
513
563
|
}
|
|
564
|
+
async function resolveShareChannelTarget(params) {
|
|
565
|
+
const token = (await resolveVaultCredential()).token;
|
|
566
|
+
const channels = await fetchChannels(token);
|
|
567
|
+
const matches = matchChannelsByName(channels, params.channelName);
|
|
568
|
+
if (matches.length === 0) {
|
|
569
|
+
console.error(chalk.red(`No channel named '${params.channelName}' — run \`hq channels\` to see your channels.`));
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
if (matches.length > 1) {
|
|
573
|
+
const scopes = matches
|
|
574
|
+
.map((c) => c.scope ?? "unknown")
|
|
575
|
+
.join(", ");
|
|
576
|
+
console.error(chalk.red(`'${params.channelName}' matches ${matches.length} channels (${scopes}) — this is ambiguous.`));
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
const channelId = matches[0].channelId;
|
|
580
|
+
const mentionPersonUids = [];
|
|
581
|
+
for (const mention of params.mentions) {
|
|
582
|
+
mentionPersonUids.push(await resolveMentionPersonUid(mention, params.companySlug));
|
|
583
|
+
}
|
|
584
|
+
return { channelId, mentionPersonUids };
|
|
585
|
+
}
|
|
586
|
+
async function resolveMentionPersonUid(mention, companySlug) {
|
|
587
|
+
const trimmed = mention.trim();
|
|
588
|
+
if (PERSON_UID_PATTERN.test(trimmed))
|
|
589
|
+
return trimmed;
|
|
590
|
+
const hqRoot = findHqRoot();
|
|
591
|
+
const slug = resolveCompanySlug(hqRoot, companySlug);
|
|
592
|
+
const result = await resolvePersonWithRosterFallback({
|
|
593
|
+
hqRoot,
|
|
594
|
+
slug,
|
|
595
|
+
name: trimmed,
|
|
596
|
+
});
|
|
597
|
+
if (result.status === "found" && result.person.personUid) {
|
|
598
|
+
return result.person.personUid;
|
|
599
|
+
}
|
|
600
|
+
if (result.status === "ambiguous") {
|
|
601
|
+
const names = result.matches.map((p) => p.name).join(", ");
|
|
602
|
+
console.error(chalk.red(`'${trimmed}' matches multiple people (${names}) — use a person uid or email.`));
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
const membership = await resolvePersonWithRosterFallback({
|
|
606
|
+
hqRoot,
|
|
607
|
+
slug,
|
|
608
|
+
name: trimmed,
|
|
609
|
+
opts: { membershipOnly: true },
|
|
610
|
+
});
|
|
611
|
+
if (membership.status === "found" && membership.person.personUid) {
|
|
612
|
+
return membership.person.personUid;
|
|
613
|
+
}
|
|
614
|
+
if (membership.status === "ambiguous") {
|
|
615
|
+
const names = membership.matches.map((p) => p.name).join(", ");
|
|
616
|
+
console.error(chalk.red(`'${trimmed}' matches multiple people (${names}) — use a person uid or email.`));
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
console.error(chalk.red(`Could not resolve --mention '${trimmed}' to a person.`));
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
514
622
|
async function runDirectGrant(params) {
|
|
515
623
|
const canonicalPrefix = normalizeCompanyFilePrefix(params.prefix, {
|
|
516
624
|
mutating: true,
|
|
@@ -535,11 +643,18 @@ async function runDirectGrant(params) {
|
|
|
535
643
|
const isAll = principal === "@all";
|
|
536
644
|
const isEmail = !isAll && EMAIL_PATTERN.test(principal);
|
|
537
645
|
const isGroup = !isAll && GROUP_ID_PATTERN.test(principal);
|
|
538
|
-
|
|
539
|
-
|
|
646
|
+
const isPerson = !isAll && !isEmail && !isGroup && PERSON_UID_PATTERN.test(principal);
|
|
647
|
+
if (!isAll && !isEmail && !isGroup && !isPerson) {
|
|
648
|
+
console.error(chalk.red(`Invalid principal '${principal}': must be '@all', an email address, a person uid, or a group id matching grp_<alphanumeric>`));
|
|
540
649
|
process.exit(1);
|
|
541
650
|
}
|
|
542
|
-
const granteeType = isAll
|
|
651
|
+
const granteeType = isAll
|
|
652
|
+
? "company-wide"
|
|
653
|
+
: isEmail
|
|
654
|
+
? "email"
|
|
655
|
+
: isPerson
|
|
656
|
+
? "person"
|
|
657
|
+
: "group";
|
|
543
658
|
const granteeId = isAll
|
|
544
659
|
? ""
|
|
545
660
|
: isEmail
|
|
@@ -558,6 +673,10 @@ async function runDirectGrant(params) {
|
|
|
558
673
|
granteeType,
|
|
559
674
|
granteeId,
|
|
560
675
|
permission: params.permission,
|
|
676
|
+
...(params.channelId ? { channelId: params.channelId } : {}),
|
|
677
|
+
...(params.mentionPersonUids && params.mentionPersonUids.length > 0
|
|
678
|
+
? { mentionPersonUids: params.mentionPersonUids }
|
|
679
|
+
: {}),
|
|
561
680
|
},
|
|
562
681
|
});
|
|
563
682
|
// No ACL row exists yet for this prefix. Auto-create one with this
|
|
@@ -572,6 +691,10 @@ async function runDirectGrant(params) {
|
|
|
572
691
|
body: {
|
|
573
692
|
prefix: canonicalPrefix,
|
|
574
693
|
entries: [{ granteeType, granteeId, permission: params.permission }],
|
|
694
|
+
...(params.channelId ? { channelId: params.channelId } : {}),
|
|
695
|
+
...(params.mentionPersonUids && params.mentionPersonUids.length > 0
|
|
696
|
+
? { mentionPersonUids: params.mentionPersonUids }
|
|
697
|
+
: {}),
|
|
575
698
|
},
|
|
576
699
|
});
|
|
577
700
|
autoCreated = res.ok;
|
|
@@ -610,6 +733,11 @@ async function runDirectGrant(params) {
|
|
|
610
733
|
else {
|
|
611
734
|
console.log(chalk.green(`${verb} ${params.permission} on ${printedPrefix} to ${principalLabel}`));
|
|
612
735
|
}
|
|
736
|
+
if (typeof data.record?.error === "string" &&
|
|
737
|
+
typeof data.record.status === "number" &&
|
|
738
|
+
data.record.status >= 400) {
|
|
739
|
+
console.log(data.record.error);
|
|
740
|
+
}
|
|
613
741
|
}
|
|
614
742
|
async function runShareSession(params) {
|
|
615
743
|
// Validate every path before minting a session; an anchored path must never
|
package/dist/commands/mesh.js
CHANGED
|
@@ -12,6 +12,7 @@ import chalk from "chalk";
|
|
|
12
12
|
import { loadCachedTokens, isMachineIdentity } from "../utils/cognito-session.js";
|
|
13
13
|
import * as readline from "node:readline/promises";
|
|
14
14
|
import { STORY_STATUSES, appendThreadEvent, ensureProjectThread, eventPayload, getProjectView, listActiveMembershipCompanies, listActiveThreads, patchStory, putProjectView, registerMeshProject, resolveActiveMembershipCompany, resolveMeshPrincipalUid, warmMeshConversationCache, } from "../lib/mesh/api.js";
|
|
15
|
+
import { buildPreflightFixtures } from "../lib/work-context/preflight-contract.js";
|
|
15
16
|
import { createCandidatesFetcher, createMigratePoster, createOrganizePoster, createWorkSessionDeliverer, fetchCompanyLive, formatCompanyLiveTable, openMeshTransport, probeMigrationCapabilityForMemberships, requireToken, } from "../lib/mesh/client.js";
|
|
16
17
|
import { clearDefaultCompany, isDefaultCompanyDisabled, readDeviceConfig, recordMigrationCapabilitySnapshot, setRepairHeldWithDefault, setDefaultCompany, } from "../lib/work-context/config.js";
|
|
17
18
|
import { ensureDefaultCompany } from "../lib/work-context/default-company.js";
|
|
@@ -24,7 +25,7 @@ import { createSessionEventsPoster, resolveVaultApiBase, } from "../lib/mesh/liv
|
|
|
24
25
|
const MEMBERSHIP_VALIDATION_TIMEOUT_MS = 5_000;
|
|
25
26
|
import { buildEmitEvidence } from "../lib/mesh/live/emit-evidence.js";
|
|
26
27
|
import { createEmitPoster, buildMeshEmitEvent } from "../lib/mesh/live/emit-client.js";
|
|
27
|
-
import { emitEvents } from "../lib/mesh/live/emit.js";
|
|
28
|
+
import { emitEvents, formatEmitRejectionLines } from "../lib/mesh/live/emit.js";
|
|
28
29
|
import { readEmitRetry, writeEmitRetry } from "../lib/mesh/live/emit-retry.js";
|
|
29
30
|
import { generateUlid } from "../lib/mesh/live/ulid.js";
|
|
30
31
|
import { readLegacyBacklog } from "../lib/mesh/live/emit-replay.js";
|
|
@@ -300,17 +301,31 @@ async function runProjectSet(projectId, opts) {
|
|
|
300
301
|
const { token, company } = await withCompany(opts);
|
|
301
302
|
const current = await getProjectView(token, company.companyUid, projectId);
|
|
302
303
|
const principalUid = await resolveMeshPrincipalUid(token, loadCachedTokens());
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
: source?.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
304
|
+
const PROJECT_CHANGED = "The Board changed while you were editing. Re-run to pick up the latest.";
|
|
305
|
+
let view;
|
|
306
|
+
try {
|
|
307
|
+
view = await putProjectView(token, company.companyUid, projectId, {
|
|
308
|
+
name: opts.name?.trim() || source?.name || current.name,
|
|
309
|
+
...(opts.description !== undefined
|
|
310
|
+
? { description: opts.description }
|
|
311
|
+
: source?.description !== undefined
|
|
312
|
+
? { description: source.description }
|
|
313
|
+
: { description: current.description }),
|
|
314
|
+
stories: source?.stories ?? current.stories,
|
|
315
|
+
repos: explicitRepos ?? source?.repos ?? current.repos,
|
|
316
|
+
...(current.version !== undefined ? { expectedVersion: current.version } : {}),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
const conflict = err;
|
|
321
|
+
if (conflict.status === 409 && conflict.code === "VERSION_CONFLICT") {
|
|
322
|
+
if (opts.json) {
|
|
323
|
+
console.log(JSON.stringify({ ok: false, code: "VERSION_CONFLICT", error: PROJECT_CHANGED }));
|
|
324
|
+
}
|
|
325
|
+
fail(PROJECT_CHANGED);
|
|
326
|
+
}
|
|
327
|
+
throw err;
|
|
328
|
+
}
|
|
314
329
|
if (opts.json) {
|
|
315
330
|
console.log(JSON.stringify({ ok: true, action: "project.set", principalUid, ...view }, null, 2));
|
|
316
331
|
return;
|
|
@@ -1108,6 +1123,9 @@ async function runSessionEnqueue(cliKind, opts) {
|
|
|
1108
1123
|
console.error(`work-mesh: emit ${kind} accepted=${summary.accepted}` +
|
|
1109
1124
|
` unassigned=${summary.unassigned} rejected=${summary.rejected}` +
|
|
1110
1125
|
` retryDepth=${summary.retryDepth}`);
|
|
1126
|
+
for (const line of formatEmitRejectionLines(summary.rejections)) {
|
|
1127
|
+
console.error(line);
|
|
1128
|
+
}
|
|
1111
1129
|
}
|
|
1112
1130
|
async function runMeshMode(action, opts) {
|
|
1113
1131
|
const meshRoot = workMeshHomeRoot();
|
|
@@ -1192,6 +1210,9 @@ async function runMeshEmit(opts) {
|
|
|
1192
1210
|
` unassigned=${summary.unassigned} rejected=${summary.rejected}` +
|
|
1193
1211
|
` retryDepth=${summary.retryDepth}` +
|
|
1194
1212
|
(opts.replayLegacy ? ` (legacy scanned=${scanned} skipped=${skipped})` : ""));
|
|
1213
|
+
for (const line of formatEmitRejectionLines(summary.rejections)) {
|
|
1214
|
+
console.log(line);
|
|
1215
|
+
}
|
|
1195
1216
|
}
|
|
1196
1217
|
async function runSessionFlush(opts) {
|
|
1197
1218
|
const token = await requireToken();
|
|
@@ -1419,6 +1440,19 @@ export function registerMeshCommand(program) {
|
|
|
1419
1440
|
.argument("<sessionId>", "Canonical session id")
|
|
1420
1441
|
.option("--machine", "Write exactly one ContextResult JSON line to stdout")
|
|
1421
1442
|
.action((sessionId, opts) => wrap(() => runContextUntracked(sessionId, opts))());
|
|
1443
|
+
const contract = mesh
|
|
1444
|
+
.command("contract")
|
|
1445
|
+
.description("Print versioned work-mesh contracts for other repositories");
|
|
1446
|
+
contract
|
|
1447
|
+
.command("preflight")
|
|
1448
|
+
.description("Offline preflight ContextResult contract (contracts/preflight/v1)")
|
|
1449
|
+
.option("--print-fixtures", "Write the fixture file JSON to stdout")
|
|
1450
|
+
.action((opts) => wrap(async () => {
|
|
1451
|
+
if (!opts.printFixtures) {
|
|
1452
|
+
throw new Error("Pass --print-fixtures. The stable package path is contracts/preflight/v1/fixtures.json.");
|
|
1453
|
+
}
|
|
1454
|
+
process.stdout.write(`${JSON.stringify(buildPreflightFixtures(), null, 2)}\n`);
|
|
1455
|
+
})());
|
|
1422
1456
|
const session = mesh
|
|
1423
1457
|
.command("session")
|
|
1424
1458
|
.description("Local session-event spool: enqueue (printf-compatible), flush, and live status");
|