@north-light/crouter 0.3.180 → 0.3.181
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/dist/api/client.d.ts +10 -1
- package/dist/api/client.js +13 -0
- package/dist/api/dto/broker.d.ts +32 -0
- package/dist/api/dto/crons.d.ts +17 -0
- package/dist/api/dto/memory.d.ts +17 -0
- package/dist/api/dto/memory.js +6 -0
- package/dist/api/dto/messages.d.ts +5 -0
- package/dist/api/dto/reviews.d.ts +8 -4
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +4 -0
- package/dist/build-root.d.ts +7 -0
- package/dist/build-root.js +21 -0
- package/dist/builtin-memory/insights/init.md +48 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/__tests__/insights-active-init.test.ts +98 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +7 -50
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +16 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +34 -0
- package/dist/cli.js +1 -2
- package/dist/clients/attach/__tests__/context-message.test.js +5 -2
- package/dist/clients/attach/assets/README.md +7 -0
- package/dist/clients/attach/assets/whip-06.mp3 +0 -0
- package/dist/clients/attach/assets/whip-crack.mp3 +0 -0
- package/dist/clients/attach/assets/whip-snap.mp3 +0 -0
- package/dist/clients/attach/chrome/canvas-panels.d.ts +7 -1
- package/dist/clients/attach/chrome/canvas-panels.js +20 -3
- package/dist/clients/attach/chrome/review-wait.d.ts +6 -0
- package/dist/clients/attach/chrome/review-wait.js +22 -0
- package/dist/clients/attach/chrome/roster.js +23 -2
- package/dist/clients/attach/chrome/widgets.js +1 -1
- package/dist/clients/attach/input/controller.js +4 -3
- package/dist/clients/attach/overlays/mcp.js +3 -1
- package/dist/clients/attach/render/chat-view.js +1 -1
- package/dist/clients/attach/session/whip.d.ts +1 -0
- package/dist/clients/attach/session/whip.js +26 -0
- package/dist/clients/attach/slash/dispatch.js +2 -0
- package/dist/clients/attach/viewer.js +578 -573
- package/dist/clients/inbox/review/document-surface.d.ts +1 -1
- package/dist/clients/inbox/review/document-surface.js +4 -4
- package/dist/clients/inbox/review/launch.js +16 -4
- package/dist/clients/inbox/review/review-client.d.ts +9 -4
- package/dist/clients/inbox/review/review-client.js +3 -0
- package/dist/commands/cron.js +30 -8
- package/dist/commands/human/prompts.d.ts +7 -2
- package/dist/commands/human/prompts.js +15 -10
- package/dist/commands/human.js +1 -2
- package/dist/commands/memory/find.js +11 -8
- package/dist/commands/memory/read.js +111 -11
- package/dist/commands/memory/write.js +1 -1
- package/dist/commands/memory.js +1 -1
- package/dist/commands/pkg/market-manage.d.ts +13 -0
- package/dist/commands/pkg/market-manage.js +39 -33
- package/dist/commands/pkg/plugin-inspect.js +4 -3
- package/dist/commands/pkg/plugin-manage.js +12 -11
- package/dist/commands/surface/node/focus.js +1 -2
- package/dist/commands/sys/doctor.js +4 -4
- package/dist/commands/sys/setup-core.d.ts +14 -7
- package/dist/commands/sys/setup-core.js +66 -11
- package/dist/commands/sys/setup-wizard.js +2 -2
- package/dist/commands/sys/setup.js +1 -1
- package/dist/core/__tests__/cron-held-settlement.test.d.ts +1 -0
- package/dist/core/__tests__/cron-held-settlement.test.js +222 -0
- package/dist/core/__tests__/helpers/harness.js +1 -2
- package/dist/core/__tests__/phase4-review-store.test.js +1 -0
- package/dist/core/__tests__/serial/command-plugins.test.js +88 -1
- package/dist/core/__tests__/session-model.test.js +5 -3
- package/dist/core/bootstrap.d.ts +0 -4
- package/dist/core/bootstrap.js +1 -55
- package/dist/core/canvas/crons.d.ts +54 -2
- package/dist/core/canvas/crons.js +48 -4
- package/dist/core/canvas/db.js +23 -0
- package/dist/core/command-manifests/manifest.d.ts +11 -0
- package/dist/core/command-manifests/manifest.js +45 -4
- package/dist/core/command-manifests/schema.d.ts +1 -1
- package/dist/core/command-plugins/bundle.d.ts +1 -0
- package/dist/core/command-plugins/bundle.js +3 -3
- package/dist/core/command-plugins/discovery.d.ts +5 -2
- package/dist/core/command-plugins/discovery.js +5 -5
- package/dist/core/command-plugins/help-addenda.d.ts +12 -0
- package/dist/core/command-plugins/help-addenda.js +30 -0
- package/dist/core/command.js +25 -2
- package/dist/core/config.js +0 -1
- package/dist/core/human/convention.d.ts +0 -1
- package/dist/core/human/convention.js +0 -6
- package/dist/core/keybindings/inbox.d.ts +6 -8
- package/dist/core/keybindings/inbox.js +6 -15
- package/dist/core/keybindings/index.d.ts +1 -1
- package/dist/core/keybindings/index.js +1 -1
- package/dist/core/memory/doc-link-grammar.js +4 -1
- package/dist/core/memory-resolver.d.ts +28 -4
- package/dist/core/memory-resolver.js +51 -39
- package/dist/core/review/stage.js +1 -0
- package/dist/core/review/store.d.ts +5 -0
- package/dist/core/review/store.js +10 -0
- package/dist/core/review/types.d.ts +4 -0
- package/dist/core/runtime/broker/event-projection.d.ts +8 -1
- package/dist/core/runtime/broker/event-projection.js +25 -1
- package/dist/core/runtime/broker/frame-dispatch.d.ts +2 -0
- package/dist/core/runtime/broker/frame-dispatch.js +50 -8
- package/dist/core/runtime/broker/message-ledger.d.ts +53 -0
- package/dist/core/runtime/broker/message-ledger.js +143 -0
- package/dist/core/runtime/broker/rebind.js +14 -0
- package/dist/core/runtime/broker-protocol.d.ts +46 -1
- package/dist/core/runtime/broker.js +11 -2
- package/dist/core/runtime/interactive-deliver.d.ts +5 -2
- package/dist/core/runtime/interactive-deliver.js +6 -3
- package/dist/core/runtime/shell-expansion.d.ts +32 -0
- package/dist/core/runtime/shell-expansion.js +102 -0
- package/dist/core/session-model/session-state.d.ts +9 -4
- package/dist/core/session-model/session-state.js +5 -1
- package/dist/daemon/api/handlers/broker-ops.js +8 -0
- package/dist/daemon/api/handlers/crons.js +14 -1
- package/dist/daemon/api/handlers/inbox.js +5 -0
- package/dist/daemon/api/handlers/memory.d.ts +2 -0
- package/dist/daemon/api/handlers/memory.js +48 -0
- package/dist/daemon/api/handlers/messages.js +7 -1
- package/dist/daemon/api/handlers/reviews.js +7 -5
- package/dist/daemon/api/map.js +3 -0
- package/dist/daemon/api/server.js +2 -0
- package/dist/daemon/cron-run.js +71 -3
- package/dist/daemon/crtrd.js +3 -0
- package/dist/daemon/reconcilers/pending-review-submit.d.ts +7 -0
- package/dist/daemon/reconcilers/pending-review-submit.js +35 -0
- package/dist/daemon/review/companion.d.ts +8 -0
- package/dist/daemon/review/companion.js +35 -0
- package/dist/daemon/review/deliver.js +2 -1
- package/dist/daemon/review/finish.d.ts +29 -2
- package/dist/daemon/review/finish.js +75 -2
- package/dist/shared/generated-context.d.ts +3 -4
- package/dist/shared/generated-context.js +24 -6
- package/dist/types.d.ts +0 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -37,17 +37,17 @@ export function validateCommandManifest(raw, options) {
|
|
|
37
37
|
};
|
|
38
38
|
// Top-level structure check
|
|
39
39
|
if (!isRecord(raw)) {
|
|
40
|
-
issue('command_manifest_invalid', 'manifest must be an object', typeName(raw), options.transport === 'http' ? '{ schemaVersion, baseUrl?, timeouts?, mounts }' : '{ schemaVersion, mounts }', 'Provide a valid JSON manifest object.');
|
|
40
|
+
issue('command_manifest_invalid', 'manifest must be an object', typeName(raw), options.transport === 'http' ? '{ schemaVersion, baseUrl?, timeouts?, mounts, helpAddenda? }' : '{ schemaVersion, mounts, helpAddenda? }', 'Provide a valid JSON manifest object.');
|
|
41
41
|
return { issues };
|
|
42
42
|
}
|
|
43
43
|
// Check for unknown top-level keys
|
|
44
44
|
const topKeys = Object.keys(raw);
|
|
45
45
|
const allowedTopKeys = options.transport === 'http'
|
|
46
|
-
? new Set(['schemaVersion', 'baseUrl', 'timeouts', 'mounts'])
|
|
47
|
-
: new Set(['schemaVersion', 'mounts']);
|
|
46
|
+
? new Set(['schemaVersion', 'baseUrl', 'timeouts', 'mounts', 'helpAddenda'])
|
|
47
|
+
: new Set(['schemaVersion', 'mounts', 'helpAddenda']);
|
|
48
48
|
const unknownKeys = topKeys.filter((k) => !allowedTopKeys.has(k));
|
|
49
49
|
if (unknownKeys.length > 0) {
|
|
50
|
-
issue('command_manifest_invalid', `unknown top-level keys`, unknownKeys.join(', '), options.transport === 'http' ? 'only: schemaVersion, baseUrl, timeouts, mounts' : 'only: schemaVersion, mounts', 'Remove the unknown keys.');
|
|
50
|
+
issue('command_manifest_invalid', `unknown top-level keys`, unknownKeys.join(', '), options.transport === 'http' ? 'only: schemaVersion, baseUrl, timeouts, mounts, helpAddenda' : 'only: schemaVersion, mounts, helpAddenda', 'Remove the unknown keys.');
|
|
51
51
|
return { issues };
|
|
52
52
|
}
|
|
53
53
|
// Validate schemaVersion
|
|
@@ -83,6 +83,15 @@ export function validateCommandManifest(raw, options) {
|
|
|
83
83
|
return { issues };
|
|
84
84
|
timeouts = t;
|
|
85
85
|
}
|
|
86
|
+
// Validate optional helpAddenda (both transports)
|
|
87
|
+
let helpAddenda;
|
|
88
|
+
if (raw['helpAddenda'] !== undefined) {
|
|
89
|
+
const h = validateHelpAddenda(raw['helpAddenda'], options.coreCommandPaths, issue);
|
|
90
|
+
if (h === null)
|
|
91
|
+
return { issues };
|
|
92
|
+
if (Object.keys(h).length > 0)
|
|
93
|
+
helpAddenda = h;
|
|
94
|
+
}
|
|
86
95
|
// Validate mounts (required, non-empty)
|
|
87
96
|
const mounts = raw['mounts'];
|
|
88
97
|
if (!Array.isArray(mounts)) {
|
|
@@ -112,6 +121,7 @@ export function validateCommandManifest(raw, options) {
|
|
|
112
121
|
schemaVersion: 1,
|
|
113
122
|
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
114
123
|
...(timeouts !== undefined ? { timeouts } : {}),
|
|
124
|
+
...(helpAddenda !== undefined ? { helpAddenda } : {}),
|
|
115
125
|
roots,
|
|
116
126
|
},
|
|
117
127
|
issues: [],
|
|
@@ -146,6 +156,37 @@ function validateTimeouts(raw, issue) {
|
|
|
146
156
|
}
|
|
147
157
|
return out;
|
|
148
158
|
}
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Help addenda validation
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
/** Validate the optional top-level `helpAddenda` map: core command path →
|
|
163
|
+
* addendum text rendered as an attributed block beneath that core command's
|
|
164
|
+
* help. When the caller supplies the core command path set, a key naming no
|
|
165
|
+
* existing core path is rejected outright — a typo fails at the gate, never
|
|
166
|
+
* silently at render. */
|
|
167
|
+
function validateHelpAddenda(raw, coreCommandPaths, issue) {
|
|
168
|
+
if (!isRecord(raw)) {
|
|
169
|
+
issue('command_manifest_invalid', 'helpAddenda must be an object', typeName(raw), 'a map of core command path → addendum text', 'Fix helpAddenda.', 'helpAddenda');
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const out = {};
|
|
173
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
174
|
+
if (key.split(' ').some((t) => !KEBAB.test(t))) {
|
|
175
|
+
issue('command_help_addendum_invalid', 'helpAddenda key must be a space-separated core command path', key, 'kebab tokens separated by single spaces (e.g. "cron" or "cron add")', 'Fix the helpAddenda key.', `helpAddenda.${key}`);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
179
|
+
issue('command_manifest_invalid', 'helpAddenda value must be a non-empty string', typeof value === 'string' ? 'empty string' : typeName(value), 'non-empty addendum text', 'Fix the helpAddenda value.', `helpAddenda.${key}`);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
if (coreCommandPaths !== undefined && !coreCommandPaths.has(key)) {
|
|
183
|
+
issue('command_help_addendum_invalid', 'helpAddenda key names no crtr core command path', key, 'an existing core command path (e.g. "cron", "cron add")', 'Fix the key or remove the addendum.', `helpAddenda.${key}`);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
out[key] = value;
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
149
190
|
function validateMount(raw, index, transport, issue) {
|
|
150
191
|
const path = `mounts[${index}]`;
|
|
151
192
|
if (!isRecord(raw)) {
|
|
@@ -78,7 +78,7 @@ export interface CommandManifestIssue {
|
|
|
78
78
|
expected: string;
|
|
79
79
|
next: string;
|
|
80
80
|
}
|
|
81
|
-
export type CommandIssueCode = 'command_manifest_unreadable' | 'command_manifest_invalid' | 'command_schema_version' | 'command_path_unsafe' | 'command_not_executable' | 'command_parent_invalid' | 'command_node_invalid' | 'command_collision' | 'command_rest_invalid';
|
|
81
|
+
export type CommandIssueCode = 'command_manifest_unreadable' | 'command_manifest_invalid' | 'command_schema_version' | 'command_path_unsafe' | 'command_not_executable' | 'command_parent_invalid' | 'command_node_invalid' | 'command_collision' | 'command_help_addendum_invalid' | 'command_rest_invalid';
|
|
82
82
|
type IssueFn = (code: CommandIssueCode, message: string, received: string, expected: string, next: string, path?: string) => void;
|
|
83
83
|
export type TransportKind = 'exec' | 'http';
|
|
84
84
|
export declare function validateCommandNode(raw: unknown, path: string[], topLevel: boolean, transport: TransportKind, issue: IssueFn): DeclBranch<DeclLeaf> | DeclLeaf | null;
|
|
@@ -26,4 +26,5 @@ export interface PluginBundleValidation {
|
|
|
26
26
|
/** Validate a Core-served plugin directory archive entirely in memory. */
|
|
27
27
|
export declare function validatePluginBundle(archive: Uint8Array, options: {
|
|
28
28
|
reservedCoreNames: ReadonlySet<string>;
|
|
29
|
+
coreCommandPaths?: ReadonlySet<string>;
|
|
29
30
|
}): Promise<PluginBundleValidation>;
|
|
@@ -141,7 +141,7 @@ function parseBundleMetadata(bytes) {
|
|
|
141
141
|
}
|
|
142
142
|
return undefined;
|
|
143
143
|
}
|
|
144
|
-
function parseCommands(bytes, reservedCoreNames) {
|
|
144
|
+
function parseCommands(bytes, reservedCoreNames, coreCommandPaths) {
|
|
145
145
|
let raw;
|
|
146
146
|
try {
|
|
147
147
|
raw = JSON.parse(Buffer.from(bytes).toString('utf8'));
|
|
@@ -151,7 +151,7 @@ function parseCommands(bytes, reservedCoreNames) {
|
|
|
151
151
|
issues: [bundleInvalid('commands.json is not valid JSON', 'invalid JSON', 'a valid command-manifest JSON object', 'Regenerate commands.json.', 'commands.json')],
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
|
-
const validation = validateCommandManifest(raw, { transport: 'http', reservedCoreNames });
|
|
154
|
+
const validation = validateCommandManifest(raw, { transport: 'http', reservedCoreNames, ...(coreCommandPaths !== undefined ? { coreCommandPaths } : {}) });
|
|
155
155
|
return {
|
|
156
156
|
...(validation.manifest !== undefined ? { commands: validation.manifest } : {}),
|
|
157
157
|
issues: validation.issues,
|
|
@@ -178,7 +178,7 @@ export async function validatePluginBundle(archive, options) {
|
|
|
178
178
|
const metadataIssue = parseBundleMetadata(bundle.bytes);
|
|
179
179
|
if (metadataIssue !== undefined)
|
|
180
180
|
return { issues: [metadataIssue] };
|
|
181
|
-
const commandValidation = parseCommands(commands.bytes, options.reservedCoreNames);
|
|
181
|
+
const commandValidation = parseCommands(commands.bytes, options.reservedCoreNames, options.coreCommandPaths);
|
|
182
182
|
if (commandValidation.commands === undefined)
|
|
183
183
|
return { issues: commandValidation.issues };
|
|
184
184
|
return {
|
|
@@ -19,13 +19,16 @@ export interface PluginCommandValidation {
|
|
|
19
19
|
plugin: InstalledPlugin;
|
|
20
20
|
manifestPath: string;
|
|
21
21
|
transport?: PluginTransport;
|
|
22
|
+
/** The validated manifest — present only when validation fully passed.
|
|
23
|
+
* Carries `helpAddenda` for the help-render lookup. */
|
|
24
|
+
manifest?: ValidatedCommandManifest;
|
|
22
25
|
contributions: ValidatedContribution[];
|
|
23
26
|
issues: CommandDiscoveryIssue[];
|
|
24
27
|
}
|
|
25
28
|
export declare function effectiveCommandPlugins(startDir?: string, profileId?: string | null): InstalledPlugin[];
|
|
26
|
-
export declare function validatePluginCommands(plugin: InstalledPlugin, reservedNames?: ReadonlySet<string>): PluginCommandValidation;
|
|
29
|
+
export declare function validatePluginCommands(plugin: InstalledPlugin, reservedNames?: ReadonlySet<string>, coreCommandPaths?: ReadonlySet<string>): PluginCommandValidation;
|
|
27
30
|
export declare function discoverPluginCommandCandidates(startDir?: string, profileId?: string | null): PluginCommandValidation[];
|
|
28
|
-
export declare function validateEffectiveCommandPlugins(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null): PluginCommandValidation[];
|
|
31
|
+
export declare function validateEffectiveCommandPlugins(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null, coreCommandPaths?: ReadonlySet<string>): PluginCommandValidation[];
|
|
29
32
|
export declare function discoverCommandContributions(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null): {
|
|
30
33
|
contributions: ValidatedContribution[];
|
|
31
34
|
issues: CommandDiscoveryIssue[];
|
|
@@ -81,7 +81,7 @@ function validateExecExecutable(plugin, transport, manifest, issues) {
|
|
|
81
81
|
}
|
|
82
82
|
return { kind: 'exec', executable };
|
|
83
83
|
}
|
|
84
|
-
export function validatePluginCommands(plugin, reservedNames = new Set()) {
|
|
84
|
+
export function validatePluginCommands(plugin, reservedNames = new Set(), coreCommandPaths) {
|
|
85
85
|
const issues = [];
|
|
86
86
|
const commands = plugin.manifest.commands;
|
|
87
87
|
const transportRaw = plugin.manifest.transport;
|
|
@@ -120,7 +120,7 @@ export function validatePluginCommands(plugin, reservedNames = new Set()) {
|
|
|
120
120
|
issues.push({ code: 'command_manifest_invalid', plugin: plugin.name, message: 'commands.json is not valid JSON', received: safe, expected: 'a JSON object', next: 'Regenerate commands.json.' });
|
|
121
121
|
return { plugin, manifestPath, transport, contributions: [], issues };
|
|
122
122
|
}
|
|
123
|
-
const validation = validateCommandManifest(raw, { transport: transport.kind, reservedCoreNames: reservedNames });
|
|
123
|
+
const validation = validateCommandManifest(raw, { transport: transport.kind, reservedCoreNames: reservedNames, ...(coreCommandPaths !== undefined ? { coreCommandPaths } : {}) });
|
|
124
124
|
if (validation.manifest === undefined) {
|
|
125
125
|
validation.issues.forEach(issueFor(plugin, issues));
|
|
126
126
|
return { plugin, manifestPath, transport, contributions: [], issues };
|
|
@@ -130,13 +130,13 @@ export function validatePluginCommands(plugin, reservedNames = new Set()) {
|
|
|
130
130
|
: transport;
|
|
131
131
|
if (resolvedTransport === undefined)
|
|
132
132
|
return { plugin, manifestPath, transport, contributions: [], issues };
|
|
133
|
-
return { plugin, manifestPath, transport: resolvedTransport, contributions: validation.manifest.roots.map((node) => ({ plugin, transport: resolvedTransport, node, manifest: validation.manifest })), issues };
|
|
133
|
+
return { plugin, manifestPath, transport: resolvedTransport, manifest: validation.manifest, contributions: validation.manifest.roots.map((node) => ({ plugin, transport: resolvedTransport, node, manifest: validation.manifest })), issues };
|
|
134
134
|
}
|
|
135
135
|
export function discoverPluginCommandCandidates(startDir = process.cwd(), profileId) {
|
|
136
136
|
return effectiveCommandPlugins(startDir, profileId).map((plugin) => validatePluginCommands(plugin));
|
|
137
137
|
}
|
|
138
|
-
export function validateEffectiveCommandPlugins(reservedNames, startDir = process.cwd(), profileId) {
|
|
139
|
-
const validations = effectiveCommandPlugins(startDir, profileId).map((plugin) => validatePluginCommands(plugin, reservedNames));
|
|
138
|
+
export function validateEffectiveCommandPlugins(reservedNames, startDir = process.cwd(), profileId, coreCommandPaths) {
|
|
139
|
+
const validations = effectiveCommandPlugins(startDir, profileId).map((plugin) => validatePluginCommands(plugin, reservedNames, coreCommandPaths));
|
|
140
140
|
const claims = new Map();
|
|
141
141
|
for (const validation of validations)
|
|
142
142
|
for (const contribution of validation.contributions)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface HelpAddendum {
|
|
2
|
+
plugin: string;
|
|
3
|
+
text: string;
|
|
4
|
+
}
|
|
5
|
+
/** Collect every enabled plugin's addendum targeting `commandPath` (the
|
|
6
|
+
* space-joined walked path, e.g. "cron add"), in plugin-name order. `gate`
|
|
7
|
+
* carries the strict-validation inputs the install/bundle gates use; the
|
|
8
|
+
* help renderer already holds both (build-root exports them). */
|
|
9
|
+
export declare function collectHelpAddenda(commandPath: string, gate: {
|
|
10
|
+
reservedCoreNames: ReadonlySet<string>;
|
|
11
|
+
coreCommandPaths: ReadonlySet<string>;
|
|
12
|
+
}, startDir?: string, profileId?: string | null): HelpAddendum[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Plugin help addenda: a command plugin may append attributed product guidance
|
|
2
|
+
// beneath a CORE command's help (`helpAddenda` in its command manifest).
|
|
3
|
+
// Append-only, never replace — a plugin cannot alter substrate contract text.
|
|
4
|
+
//
|
|
5
|
+
// This module is the help-render lookup. It loads only on the help path
|
|
6
|
+
// (dynamic import from runCli) and reads stored manifest bytes only (no
|
|
7
|
+
// network). The caller supplies the SAME inputs the install/bundle gates pass
|
|
8
|
+
// to `validateCommandManifest` — the reserved core names and the full core
|
|
9
|
+
// command path set — so this lookup reproduces the gate's exact verdict: a
|
|
10
|
+
// manifest whose `helpAddenda` was rejected at ingress (reported there as a
|
|
11
|
+
// typed issue, e.g. `command_help_addendum_invalid`) contributes nothing at
|
|
12
|
+
// render either. Ingress report and render can never disagree; nothing
|
|
13
|
+
// disappears here that was not already loudly rejected at the gate.
|
|
14
|
+
import { effectiveCommandPlugins, validatePluginCommands } from './discovery.js';
|
|
15
|
+
/** Collect every enabled plugin's addendum targeting `commandPath` (the
|
|
16
|
+
* space-joined walked path, e.g. "cron add"), in plugin-name order. `gate`
|
|
17
|
+
* carries the strict-validation inputs the install/bundle gates use; the
|
|
18
|
+
* help renderer already holds both (build-root exports them). */
|
|
19
|
+
export function collectHelpAddenda(commandPath, gate, startDir, profileId) {
|
|
20
|
+
if (commandPath.length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const plugin of effectiveCommandPlugins(startDir, profileId)) {
|
|
24
|
+
const text = validatePluginCommands(plugin, gate.reservedCoreNames, gate.coreCommandPaths).manifest?.helpAddenda?.[commandPath];
|
|
25
|
+
if (text !== undefined)
|
|
26
|
+
out.push({ plugin: plugin.name, text });
|
|
27
|
+
}
|
|
28
|
+
out.sort((a, b) => a.plugin.localeCompare(b.plugin));
|
|
29
|
+
return out;
|
|
30
|
+
}
|
package/dist/core/command.js
CHANGED
|
@@ -187,6 +187,29 @@ function renderNode(node) {
|
|
|
187
187
|
return renderBranch(node.help);
|
|
188
188
|
return renderLeafArgv(node.help);
|
|
189
189
|
}
|
|
190
|
+
/** Render a node's help plus any plugin help addenda targeting its walked
|
|
191
|
+
* command path — append-only, attributed blocks a plugin adds beneath core
|
|
192
|
+
* contract text, never inside it. Plugin discovery loads only here (dynamic
|
|
193
|
+
* import on the help path), so the dispatch path's module graph and cold
|
|
194
|
+
* start are unchanged; the lookup reads stored manifest bytes only. The
|
|
195
|
+
* lookup receives the install gate's exact strict-validation inputs (reserved
|
|
196
|
+
* core names + the full core command path set, both from build-root), so a
|
|
197
|
+
* manifest rejected at ingress contributes nothing here either — loading
|
|
198
|
+
* every subtree for that path set is a help-path-only cost. */
|
|
199
|
+
async function renderNodeWithAddenda(node, path) {
|
|
200
|
+
const body = renderNode(node);
|
|
201
|
+
if (path.length === 0)
|
|
202
|
+
return body;
|
|
203
|
+
const [{ collectHelpAddenda }, { SUBTREE_NAMES, coreCommandPaths }] = await Promise.all([
|
|
204
|
+
import('./command-plugins/help-addenda.js'),
|
|
205
|
+
import('../build-root.js'),
|
|
206
|
+
]);
|
|
207
|
+
const addenda = collectHelpAddenda(path.join(' '), {
|
|
208
|
+
reservedCoreNames: new Set(SUBTREE_NAMES),
|
|
209
|
+
coreCommandPaths: await coreCommandPaths(),
|
|
210
|
+
});
|
|
211
|
+
return body + addenda.map((a) => `\n\n<plugin-help plugin="${a.plugin}">\n${a.text}\n</plugin-help>`).join('');
|
|
212
|
+
}
|
|
190
213
|
function helpRequested(remaining) {
|
|
191
214
|
return remaining.some((t) => t === '-h' || t === '--help');
|
|
192
215
|
}
|
|
@@ -599,13 +622,13 @@ export async function runCli(root, argv) {
|
|
|
599
622
|
}
|
|
600
623
|
// Help anywhere in remaining tokens → print node help and exit
|
|
601
624
|
if (helpRequested(remaining)) {
|
|
602
|
-
process.stdout.write(
|
|
625
|
+
process.stdout.write(await renderNodeWithAddenda(node, path) + '\n');
|
|
603
626
|
process.exitCode = ExitCode.SUCCESS;
|
|
604
627
|
return;
|
|
605
628
|
}
|
|
606
629
|
// Bare branch or bare root (no -h, remaining fully consumed) → help surface
|
|
607
630
|
if (node.kind === 'root' || node.kind === 'branch') {
|
|
608
|
-
process.stdout.write(
|
|
631
|
+
process.stdout.write(await renderNodeWithAddenda(node, path) + '\n');
|
|
609
632
|
process.exitCode = ExitCode.SUCCESS;
|
|
610
633
|
return;
|
|
611
634
|
}
|
package/dist/core/config.js
CHANGED
|
@@ -50,7 +50,6 @@ export function readState(scope) {
|
|
|
50
50
|
marketplaces: existing.marketplaces ?? {},
|
|
51
51
|
plugins: existing.plugins ?? {},
|
|
52
52
|
last_self_check: existing.last_self_check,
|
|
53
|
-
bootstrap_done: existing.bootstrap_done,
|
|
54
53
|
activeCanvas: existing.activeCanvas ?? null,
|
|
55
54
|
};
|
|
56
55
|
}
|
|
@@ -10,7 +10,6 @@ export declare function isResolved(dir: string): boolean;
|
|
|
10
10
|
export declare function isClaimed(dir: string): boolean;
|
|
11
11
|
/** Resolve and verify a ticket directory without a registry. */
|
|
12
12
|
export declare function requireTicket(dir: string): string;
|
|
13
|
-
export declare function stampCanvasNode(deck: Deck): void;
|
|
14
13
|
export declare function atomicWriteJson(path: string, value: unknown): void;
|
|
15
14
|
/** Publish one immutable JSON record without replacing an existing winner. */
|
|
16
15
|
export declare function publishJsonExclusive(path: string, value: unknown): boolean;
|
|
@@ -23,12 +23,6 @@ export function requireTicket(dir) {
|
|
|
23
23
|
}
|
|
24
24
|
return canonicalDir;
|
|
25
25
|
}
|
|
26
|
-
export function stampCanvasNode(deck) {
|
|
27
|
-
const id = process.env['CRTR_NODE_ID'];
|
|
28
|
-
if (id === undefined || id.trim() === '' || deck.source?.nodeId)
|
|
29
|
-
return;
|
|
30
|
-
deck.source = { ...(deck.source ?? {}), nodeId: id };
|
|
31
|
-
}
|
|
32
26
|
export function atomicWriteJson(path, value) {
|
|
33
27
|
const tmp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
34
28
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -8,13 +8,11 @@ import type { BindingResolution } from './types.js';
|
|
|
8
8
|
* current state; callers holding a live snapshot (e.g. the attach viewer) pass
|
|
9
9
|
* it so a `/reload` rebind is honored. */
|
|
10
10
|
export declare function inboxShortcut(bindings?: BindingResolution<BindingId>): string | null;
|
|
11
|
-
/**
|
|
11
|
+
/** The shortcut belongs to surfaces that actually own the key — the viewer
|
|
12
|
+
* footer and the settings surface. Agent-facing command output never names it:
|
|
13
|
+
* an agent repeats the gesture back to a human who may be reading on a screen
|
|
14
|
+
* where it means nothing.
|
|
15
|
+
*
|
|
16
|
+
* Closed-inbox footer affordance: "<shortcut> inbox", or the CLI fallback
|
|
12
17
|
* `crtr human list` when the binding is disabled. */
|
|
13
18
|
export declare function inboxOpenHint(bindings?: BindingResolution<BindingId>): string;
|
|
14
|
-
/** Bare token naming the inbox affordance for a parenthetical — the resolved
|
|
15
|
-
* shortcut (e.g. `Alt+I`), or the backtick-quoted CLI form when disabled. */
|
|
16
|
-
export declare function inboxHint(bindings?: BindingResolution<BindingId>): string;
|
|
17
|
-
/** Imperative clause telling the human how to open the inbox — "press
|
|
18
|
-
* <shortcut> in a node viewer (or run `crtr human list` for the queue)", or just
|
|
19
|
-
* the CLI form when the binding is disabled. */
|
|
20
|
-
export declare function inboxOpenInstruction(bindings?: BindingResolution<BindingId>): string;
|
|
@@ -12,23 +12,14 @@ export function inboxShortcut(bindings = resolveUserKeybindings()) {
|
|
|
12
12
|
const gestures = bindings.gestures(INBOX_TOGGLE);
|
|
13
13
|
return gestures.length > 0 ? gestures.map(formatGesture).join(' / ') : null;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/** The shortcut belongs to surfaces that actually own the key — the viewer
|
|
16
|
+
* footer and the settings surface. Agent-facing command output never names it:
|
|
17
|
+
* an agent repeats the gesture back to a human who may be reading on a screen
|
|
18
|
+
* where it means nothing.
|
|
19
|
+
*
|
|
20
|
+
* Closed-inbox footer affordance: "<shortcut> inbox", or the CLI fallback
|
|
16
21
|
* `crtr human list` when the binding is disabled. */
|
|
17
22
|
export function inboxOpenHint(bindings) {
|
|
18
23
|
const shortcut = inboxShortcut(bindings);
|
|
19
24
|
return shortcut !== null ? `${shortcut} inbox` : 'crtr human list';
|
|
20
25
|
}
|
|
21
|
-
/** Bare token naming the inbox affordance for a parenthetical — the resolved
|
|
22
|
-
* shortcut (e.g. `Alt+I`), or the backtick-quoted CLI form when disabled. */
|
|
23
|
-
export function inboxHint(bindings) {
|
|
24
|
-
return inboxShortcut(bindings) ?? '`crtr human list`';
|
|
25
|
-
}
|
|
26
|
-
/** Imperative clause telling the human how to open the inbox — "press
|
|
27
|
-
* <shortcut> in a node viewer (or run `crtr human list` for the queue)", or just
|
|
28
|
-
* the CLI form when the binding is disabled. */
|
|
29
|
-
export function inboxOpenInstruction(bindings) {
|
|
30
|
-
const shortcut = inboxShortcut(bindings);
|
|
31
|
-
return shortcut !== null
|
|
32
|
-
? `press ${shortcut} in a node viewer (or run \`crtr human list\` for the queue)`
|
|
33
|
-
: 'run `crtr human list` for the queue';
|
|
34
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { BINDING_CATALOG, BINDING_IDS, SAFETY_REQUIREMENTS, isAttachPaneBinding, type BindingId, } from './catalog.js';
|
|
2
2
|
export { GestureSyntaxError, KeybindingValidationError, normalizeGesture, normalizeSparseOverrides, normalizeStroke, resolveKeybindings, resolveUserKeybindingSettings, resolveUserKeybindings, splitGestureExpression, type UserKeybindingSettings, } from './resolve.js';
|
|
3
3
|
export { canonicalTerminalStroke, formatBinding, formatGesture, formatStroke, matchesPiTuiInput, matchesTerminalInput, type PiTuiMatcher, type RawTerminalInput, } from './match.js';
|
|
4
|
-
export { inboxOpenHint,
|
|
4
|
+
export { inboxOpenHint, inboxShortcut, } from './inbox.js';
|
|
5
5
|
export { backgroundBashHint, backgroundBashShortcut, } from './background-bash.js';
|
|
6
6
|
export { ATTACH_CONTROL_BINDINGS, decodeAttachControlInput, encodeAttachControlInput, extractAttachControlInput, type AttachBindingId, type AttachMenuBindingId, type ExtractedAttachControlInput, } from './attach-control.js';
|
|
7
7
|
export { KeybindingConcurrentEditError, persistUserKeybindings, type PersistedKeybindings, type PersistKeybindingsOptions, } from './persistence.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { BINDING_CATALOG, BINDING_IDS, SAFETY_REQUIREMENTS, isAttachPaneBinding, } from './catalog.js';
|
|
2
2
|
export { GestureSyntaxError, KeybindingValidationError, normalizeGesture, normalizeSparseOverrides, normalizeStroke, resolveKeybindings, resolveUserKeybindingSettings, resolveUserKeybindings, splitGestureExpression, } from './resolve.js';
|
|
3
3
|
export { canonicalTerminalStroke, formatBinding, formatGesture, formatStroke, matchesPiTuiInput, matchesTerminalInput, } from './match.js';
|
|
4
|
-
export { inboxOpenHint,
|
|
4
|
+
export { inboxOpenHint, inboxShortcut, } from './inbox.js';
|
|
5
5
|
export { backgroundBashHint, backgroundBashShortcut, } from './background-bash.js';
|
|
6
6
|
export { ATTACH_CONTROL_BINDINGS, decodeAttachControlInput, encodeAttachControlInput, extractAttachControlInput, } from './attach-control.js';
|
|
7
7
|
export { KeybindingConcurrentEditError, persistUserKeybindings, } from './persistence.js';
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// doc-link-grammar.ts — the single source of truth for the `[[canonical/name]]`
|
|
2
2
|
// memory-document link grammar. Zero imports, pure string ops, browser-safe —
|
|
3
3
|
// any surface that highlights, resolves, or lints doc links imports THIS module
|
|
4
|
-
// rather than re-implementing the bracket scan or the name shape.
|
|
4
|
+
// rather than re-implementing the bracket scan or the name shape. It ships to
|
|
5
|
+
// external consumers as `@north-light/crouter-api/doc-link-grammar`, so a
|
|
6
|
+
// browser UI rendering a node's transcript scans with the same grammar crtrd
|
|
7
|
+
// validates `/v1/memory/resolve` names with.
|
|
5
8
|
//
|
|
6
9
|
// A doc link is a durable cross-reference written INSIDE a memory document's
|
|
7
10
|
// body, pointing at another memory document by its exact canonical name (the
|
|
@@ -60,6 +60,24 @@ export interface MemoryResolutionOpts {
|
|
|
60
60
|
}
|
|
61
61
|
/** Canonical, unambiguous identifier for a memory document: `<scope>/<name>`. */
|
|
62
62
|
export declare function memoryDocId(doc: MemoryDoc): string;
|
|
63
|
+
/** Whose memory view is being resolved: the workspace dir, profile, and node
|
|
64
|
+
* id that decide which stores exist and in what order. Ambient resolution
|
|
65
|
+
* builds this from the process (`process.cwd()`, `CRTR_PROFILE_ID`,
|
|
66
|
+
* `CRTR_NODE_ID`); a TARGET-ADDRESSED caller passes another node's, which is
|
|
67
|
+
* what one process serving several nodes needs — crtrd resolving a link on
|
|
68
|
+
* behalf of a node, or a warm-spare claim building a node's first-message
|
|
69
|
+
* context. The same name can name different documents for different nodes, so
|
|
70
|
+
* every scope in the chain (node-local, project stack, profile, user, builtin)
|
|
71
|
+
* reads from the target rather than the host process. */
|
|
72
|
+
export interface MemoryTarget {
|
|
73
|
+
/** The workspace dir the project-scope stack walks up from. */
|
|
74
|
+
cwd: string;
|
|
75
|
+
/** The selected profile id, or null when none. */
|
|
76
|
+
profileId: string | null;
|
|
77
|
+
/** The node whose `nodes/<id>/context/memory/` store is the nearest scope,
|
|
78
|
+
* or null outside a node. */
|
|
79
|
+
nodeId: string | null;
|
|
80
|
+
}
|
|
63
81
|
/** All native memory docs for a scope. Project scope is a nearest-first stack of
|
|
64
82
|
* every ancestor `.crouter/memory/` (widened by a selected profile's project
|
|
65
83
|
* stack); profile is the selected profile's own singleton store, resolved
|
|
@@ -70,10 +88,10 @@ export declare function listMemoryDocs(scope: MemoryScope, quiet?: boolean): Mem
|
|
|
70
88
|
* doc's name exactly as `listMemoryDocs` does (path-relative, no extension,
|
|
71
89
|
* slash-separated) then prefixing the plugin name. Builtin has no plugins. */
|
|
72
90
|
export declare function listPluginMemoryDocs(plugin: InstalledPlugin, scope: MemoryScope, quiet?: boolean): MemoryDoc[];
|
|
73
|
-
/** All project-scoped docs visible from an explicit node workspace/profile
|
|
74
|
-
*
|
|
75
|
-
* (
|
|
76
|
-
*
|
|
91
|
+
/** All project-scoped docs visible from an explicit node workspace/profile — the
|
|
92
|
+
* project-only slice of a `MemoryTarget` view, used where only workspace docs
|
|
93
|
+
* are wanted (a workspace-open render). For a full-precedence target-addressed
|
|
94
|
+
* lookup, use `resolveMemoryDocForTarget`. */
|
|
77
95
|
export declare function listProjectMemoryDocs(startDir?: string, profileId?: string | null, quiet?: boolean): MemoryDoc[];
|
|
78
96
|
/** All memory docs across the resolved sources, in precedence order: each
|
|
79
97
|
* ancestor project `.crouter/` from nearest to farthest, then the selected
|
|
@@ -93,3 +111,9 @@ export declare function createMemoryDocSnapshot(): MemoryDocSnapshot;
|
|
|
93
111
|
* names are omitted so callers can preserve their per-document error behavior. */
|
|
94
112
|
export declare function resolveMemoryDocs(names: readonly string[]): Map<string, MemoryDoc>;
|
|
95
113
|
export declare function resolveMemoryDoc(rawName: string, opts?: MemoryResolutionOpts): MemoryDoc;
|
|
114
|
+
/** Resolve a memory document as ANOTHER node would see it — the same precedence
|
|
115
|
+
* chain (node-local > project stack > profile > user > builtin), read from the
|
|
116
|
+
* target's cwd/profile/node rather than the host process's. This is what crtrd
|
|
117
|
+
* resolves a `[[name]]` link through: the daemon's own cwd and env name no
|
|
118
|
+
* node, and the same name can be a different document for two nodes. */
|
|
119
|
+
export declare function resolveMemoryDocForTarget(rawName: string, target: MemoryTarget, opts?: MemoryResolutionOpts): MemoryDoc;
|