@north-light/crouter 0.3.164 → 0.3.166

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/build-root.d.ts +6 -6
  2. package/dist/build-root.js +12 -13
  3. package/dist/builtin-memory/00-runtime-base.md +3 -4
  4. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  5. package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
  6. package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
  7. package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
  8. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
  9. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
  10. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
  11. package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
  12. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
  13. package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
  14. package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
  15. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
  16. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
  17. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
  18. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
  20. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
  21. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
  22. package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
  23. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
  24. package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
  26. package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
  27. package/dist/builtin-memory/advisor/council.md +40 -0
  28. package/dist/builtin-memory/design.md +8 -11
  29. package/dist/builtin-memory/development.md +6 -9
  30. package/dist/builtin-memory/internal/INDEX.md +4 -5
  31. package/dist/builtin-memory/internal/agent-shaping.md +5 -7
  32. package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
  33. package/dist/builtin-memory/internal/marketplaces.md +13 -14
  34. package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
  35. package/dist/builtin-memory/internal/plugins.md +61 -45
  36. package/dist/builtin-memory/planning.md +4 -7
  37. package/dist/builtin-memory/spec.md +9 -12
  38. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
  39. package/dist/cli.js +1 -1
  40. package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
  41. package/dist/clients/attach/__tests__/context-message.test.js +62 -22
  42. package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
  43. package/dist/clients/attach/render/chat-view.d.ts +20 -20
  44. package/dist/clients/attach/render/chat-view.js +40 -54
  45. package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
  46. package/dist/clients/attach/render/condensed-history.js +60 -0
  47. package/dist/clients/attach/render/context-message.d.ts +1 -1
  48. package/dist/clients/attach/render/context-message.js +18 -41
  49. package/dist/clients/attach/session/bindings.d.ts +5 -3
  50. package/dist/clients/attach/session/bindings.js +7 -13
  51. package/dist/clients/attach/session/keys.d.ts +2 -0
  52. package/dist/clients/attach/session/keys.js +36 -37
  53. package/dist/clients/attach/session/profile-files.d.ts +8 -0
  54. package/dist/clients/attach/session/profile-files.js +157 -0
  55. package/dist/clients/attach/viewer.js +528 -529
  56. package/dist/commands/node-context.js +3 -2
  57. package/dist/commands/node.js +2 -2
  58. package/dist/commands/pkg/plugin-inspect.js +6 -7
  59. package/dist/commands/pkg/plugin-manage.d.ts +1 -1
  60. package/dist/commands/pkg/plugin-manage.js +131 -19
  61. package/dist/commands/pkg/plugin.js +2 -2
  62. package/dist/commands/pkg.js +6 -11
  63. package/dist/commands/profile/env.js +3 -3
  64. package/dist/commands/sys/config.js +17 -76
  65. package/dist/commands/sys/doctor.js +5 -91
  66. package/dist/commands/sys/setup-wizard.d.ts +9 -11
  67. package/dist/commands/sys/setup-wizard.js +47 -81
  68. package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
  69. package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
  70. package/dist/core/__tests__/command-plugins.test.js +75 -16
  71. package/dist/core/__tests__/review-model-floor.test.js +2 -2
  72. package/dist/core/__tests__/tmux-surface.test.js +10 -1
  73. package/dist/core/command-manifests/manifest.d.ts +24 -0
  74. package/dist/core/{configured-clis → command-manifests}/manifest.js +28 -25
  75. package/dist/core/command-manifests/registry.d.ts +1 -2
  76. package/dist/core/command-manifests/registry.js +2 -2
  77. package/dist/core/command-manifests/schema.d.ts +11 -13
  78. package/dist/core/command-manifests/schema.js +53 -192
  79. package/dist/core/command-plugins/compose.d.ts +0 -6
  80. package/dist/core/command-plugins/compose.js +32 -75
  81. package/dist/core/command-plugins/discovery.d.ts +25 -58
  82. package/dist/core/command-plugins/discovery.js +171 -261
  83. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  84. package/dist/core/command-plugins/endpoint.js +48 -0
  85. package/dist/core/command-plugins/store.d.ts +16 -0
  86. package/dist/core/command-plugins/store.js +64 -0
  87. package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
  88. package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
  89. package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
  90. package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
  91. package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
  92. package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
  93. package/dist/core/command.d.ts +8 -9
  94. package/dist/core/command.js +6 -9
  95. package/dist/core/config.js +6 -10
  96. package/dist/core/env-name.d.ts +6 -0
  97. package/dist/core/env-name.js +9 -0
  98. package/dist/core/io.d.ts +1 -1
  99. package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
  100. package/dist/core/keybindings/attach-control.d.ts +37 -0
  101. package/dist/core/keybindings/attach-control.js +38 -0
  102. package/dist/core/keybindings/catalog.d.ts +5 -4
  103. package/dist/core/keybindings/catalog.js +16 -8
  104. package/dist/core/keybindings/index.d.ts +1 -0
  105. package/dist/core/keybindings/index.js +1 -0
  106. package/dist/core/keybindings/types.d.ts +1 -1
  107. package/dist/core/preview-registry.js +41 -74
  108. package/dist/core/runtime/bearings.d.ts +2 -5
  109. package/dist/core/runtime/bearings.js +2 -5
  110. package/dist/core/runtime/broker.js +3 -2
  111. package/dist/core/runtime/front-door.d.ts +1 -1
  112. package/dist/core/runtime/front-door.js +2 -2
  113. package/dist/core/runtime/kickoff.d.ts +3 -3
  114. package/dist/core/runtime/kickoff.js +4 -3
  115. package/dist/core/runtime/lifecycle.js +2 -2
  116. package/dist/core/runtime/situational-context.d.ts +1 -1
  117. package/dist/core/runtime/situational-context.js +1 -1
  118. package/dist/core/runtime/spawn.js +9 -9
  119. package/dist/core/runtime/tmux.js +29 -2
  120. package/dist/core/scope.d.ts +0 -5
  121. package/dist/core/scope.js +0 -10
  122. package/dist/core/user-settings.d.ts +193 -0
  123. package/dist/core/user-settings.js +252 -0
  124. package/dist/index.d.ts +4 -0
  125. package/dist/index.js +3 -0
  126. package/dist/pi-extensions/canvas-stophook.js +3 -2
  127. package/dist/prompts/review.js +4 -2
  128. package/dist/shared/generated-context.d.ts +34 -0
  129. package/dist/shared/generated-context.js +98 -0
  130. package/dist/types.d.ts +23 -16
  131. package/dist/types.js +3 -14
  132. package/dist/web-client/assets/index-BgLGlZ3D.css +2 -0
  133. package/dist/web-client/assets/{index-NIuSCOHM.js → index-CmoNqcCv.js} +19 -19
  134. package/dist/web-client/index.html +2 -2
  135. package/dist/web-client/sw.js +1 -1
  136. package/docs/public-api.md +1 -0
  137. package/package.json +1 -1
  138. package/runtime.lock.json +2 -2
  139. package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
  140. package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
  141. package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
  142. package/dist/builtin-memory/internal/workflow-codification.md +0 -82
  143. package/dist/builtin-memory/product.md +0 -80
  144. package/dist/clients/attach/render/frozen-history.js +0 -100
  145. package/dist/commands/pkg/cli-inspect.d.ts +0 -17
  146. package/dist/commands/pkg/cli-inspect.js +0 -190
  147. package/dist/commands/pkg/cli-manage.d.ts +0 -3
  148. package/dist/commands/pkg/cli-manage.js +0 -206
  149. package/dist/commands/pkg/cli.d.ts +0 -1
  150. package/dist/commands/pkg/cli.js +0 -14
  151. package/dist/core/configured-clis/cache.d.ts +0 -16
  152. package/dist/core/configured-clis/cache.js +0 -57
  153. package/dist/core/configured-clis/compose.d.ts +0 -14
  154. package/dist/core/configured-clis/compose.js +0 -60
  155. package/dist/core/configured-clis/discovery.d.ts +0 -47
  156. package/dist/core/configured-clis/discovery.js +0 -173
  157. package/dist/core/configured-clis/manifest.d.ts +0 -24
  158. package/dist/core/configured-clis/registration.d.ts +0 -40
  159. package/dist/core/configured-clis/registration.js +0 -201
  160. package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
@@ -1,190 +0,0 @@
1
- import { defineLeaf } from '../../core/command.js';
2
- import { notFound } from '../../core/errors.js';
3
- import { paginate } from '../../core/pagination.js';
4
- import { resolveScopeArg } from '../../core/scope.js';
5
- import { SUBTREE_NAMES } from '../../build-root.js';
6
- import { listConfiguredCliRegistrations, resolveConfiguredCli, } from '../../core/configured-clis/registration.js';
7
- import { isCacheMetadataValid, readConfiguredCliCacheManifest } from '../../core/configured-clis/cache.js';
8
- import { buildExternalCommandSnapshot } from '../../core/configured-clis/discovery.js';
9
- // ---------------------------------------------------------------------------
10
- // Shared inspection helpers (zero network — read stored manifest bytes only)
11
- // ---------------------------------------------------------------------------
12
- /** Whether a registration's STORED manifest reads as present. Mirrors the
13
- * discovery classifier exactly (§3.3): a missing/metadata-mismatched/unreadable
14
- * manifest, or one whose bytes do not parse as JSON, reads as `absent`; a
15
- * parseable manifest — even one that later fails §4 validation — is `stored`.
16
- * Never fetches. */
17
- export function configuredCliManifestStored(registration) {
18
- if (!isCacheMetadataValid(registration))
19
- return false;
20
- const bytes = readConfiguredCliCacheManifest(registration);
21
- if (bytes === null)
22
- return false;
23
- try {
24
- JSON.parse(Buffer.from(bytes).toString('utf8'));
25
- return true;
26
- }
27
- catch {
28
- return false; // unparseable → reads as absent
29
- }
30
- }
31
- /** The stored-manifest state string for a registration. */
32
- export function configuredCliManifestState(registration) {
33
- return configuredCliManifestStored(registration) ? 'stored' : 'absent';
34
- }
35
- /** The core top-level subtree names — the closed set a remote may never mount
36
- * onto or shadow. Passed to the unified snapshot as `reservedCoreNames`. */
37
- function reservedCoreNames() {
38
- return new Set(SUBTREE_NAMES);
39
- }
40
- /** Every full command path (space-joined tokens) in a contributed node's
41
- * subtree, starting at the node itself — the top-level mount path plus every
42
- * descendant. e.g. `["device", "device reboot", "device status"]`. */
43
- function enumerateCommandPaths(node, prefix) {
44
- const here = [...prefix, node.name];
45
- const out = [here.join(' ')];
46
- if (node.kind === 'branch') {
47
- for (const child of node.children)
48
- out.push(...enumerateCommandPaths(child, here));
49
- }
50
- return out;
51
- }
52
- /** The accepted top-level command names a registration currently contributes,
53
- * read from THE unified collision-resolved snapshot (a collided command is
54
- * dropped and appears only in issues). */
55
- function contributedTopLevel(snapshot, name) {
56
- return snapshot.contributions
57
- .filter((c) => c.contributor.kind === 'configured-cli' && c.contributor.name === name)
58
- .map((c) => c.node.name)
59
- .sort((a, b) => a.localeCompare(b));
60
- }
61
- /** Serialize a single configured-CLI issue to the received/expected/next shape. */
62
- function issueOut(issue) {
63
- return {
64
- code: issue.code,
65
- ...(issue.path !== undefined ? { path: issue.path } : {}),
66
- message: issue.message,
67
- received: issue.received,
68
- expected: issue.expected,
69
- next: issue.next,
70
- };
71
- }
72
- // ---------------------------------------------------------------------------
73
- // list
74
- // ---------------------------------------------------------------------------
75
- export const cliList = defineLeaf({
76
- name: 'list',
77
- description: 'list registered configured CLIs',
78
- whenToUse: 'enumerating configured-CLI registrations across scopes with endpoint, scope, stored-manifest state, and the top-level command(s) each currently contributes',
79
- help: {
80
- name: 'pkg cli list',
81
- summary: 'list registered configured CLIs (zero network)',
82
- params: [
83
- { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project', 'all'], required: false, constraint: 'Default: all.' },
84
- { kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: 'Default 50, max 200.' },
85
- { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: 'Opaque token from next_cursor. Omit on first call.' },
86
- ],
87
- output: [
88
- { name: 'items', type: 'object[]', required: true, constraint: 'Each: {name, endpoint, scope, manifest_state, top_level_commands}. manifest_state is "stored" or "absent". top_level_commands are the accepted top-level command names from the unified snapshot (empty when absent, invalid, or collided). Sorted by scope (project first) then name ascending.' },
89
- { name: 'next_cursor', type: 'string | null', required: true, constraint: 'null means no more items.' },
90
- { name: 'total', type: 'integer | null', required: true, constraint: 'Exact count.' },
91
- ],
92
- outputKind: 'object',
93
- effects: ['None. Read-only. Reads stored manifests only — never fetches, never revalidates.'],
94
- },
95
- run: async (input) => {
96
- const scopeInput = input['scope'];
97
- const limitRaw = input['limit'];
98
- const limit = limitRaw !== undefined ? Math.min(Math.max(1, limitRaw), 200) : 50;
99
- const cursor = input['cursor'];
100
- let scopeFilter;
101
- if (scopeInput !== undefined) {
102
- const resolved = resolveScopeArg(scopeInput);
103
- if (resolved !== 'all')
104
- scopeFilter = resolved;
105
- }
106
- const snapshot = buildExternalCommandSnapshot(reservedCoreNames());
107
- const all = listConfiguredCliRegistrations()
108
- .filter((r) => scopeFilter === undefined || r.scope === scopeFilter)
109
- .sort((a, b) => {
110
- if (a.scope === 'project' && b.scope !== 'project')
111
- return -1;
112
- if (a.scope !== 'project' && b.scope === 'project')
113
- return 1;
114
- return a.name.localeCompare(b.name);
115
- });
116
- const result = paginate(all, { limit, cursor: cursor !== undefined ? cursor : undefined }, { defaultLimit: 50, maxLimit: 200, keyOf: (r) => `${r.scope}:${r.name}`, total: 'count' });
117
- return {
118
- items: result.items.map((r) => ({
119
- name: r.name,
120
- endpoint: r.endpoint,
121
- scope: r.scope,
122
- manifest_state: configuredCliManifestState(r),
123
- top_level_commands: contributedTopLevel(snapshot, r.name),
124
- })),
125
- next_cursor: result.next_cursor,
126
- total: result.total,
127
- };
128
- },
129
- });
130
- // ---------------------------------------------------------------------------
131
- // show
132
- // ---------------------------------------------------------------------------
133
- /** Build the show/refresh report body for one registration from the unified
134
- * snapshot: its stored-manifest state, every command it currently contributes
135
- * (each with its full mount path and subtree), and every validation/collision
136
- * issue attributed to it. Zero network. */
137
- export function configuredCliReport(registration, snapshot) {
138
- const commands = snapshot.contributions
139
- .filter((c) => c.contributor.kind === 'configured-cli' && c.contributor.name === registration.name)
140
- .map((c) => ({
141
- command: c.node.name,
142
- paths: enumerateCommandPaths(c.node, []),
143
- }))
144
- .sort((a, b) => a.command.localeCompare(b.command));
145
- const issues = snapshot.issues
146
- .filter((i) => i.contributor.kind === 'configured-cli' && i.contributor.name === registration.name)
147
- .map(issueOut);
148
- return {
149
- name: registration.name,
150
- scope: registration.scope,
151
- endpoint: registration.endpoint,
152
- manifest_state: configuredCliManifestState(registration),
153
- commands,
154
- issues,
155
- };
156
- }
157
- export const cliShow = defineLeaf({
158
- name: 'show',
159
- description: 'inventory one configured CLI',
160
- whenToUse: 'reading one configured-CLI registration in detail: resolved endpoint, whether a manifest is stored, every command it currently contributes with full mount path and subtree, and every current validation/collision issue',
161
- help: {
162
- name: 'pkg cli show',
163
- summary: 'inventory one configured CLI from its stored manifest (zero network)',
164
- params: [
165
- { kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Registration name.' },
166
- { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution. Default: the effective registration (project outranks user).' },
167
- ],
168
- output: [
169
- { name: 'name', type: 'string', required: true, constraint: 'Registration name.' },
170
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the registration lives in.' },
171
- { name: 'endpoint', type: 'string', required: true, constraint: 'Resolved manifest endpoint URL.' },
172
- { name: 'manifest_state', type: 'string', required: true, constraint: '"stored" or "absent". Absent is a valid inventory state (register-time fetch failed), not an error.' },
173
- { name: 'commands', type: 'object[]', required: true, constraint: 'Every accepted command this CLI contributes. Each: {command (top-level mount name), paths (every full command path in its subtree, space-joined)}. Empty when the manifest is absent, invalid, or collided.' },
174
- { name: 'issues', type: 'object[]', required: true, constraint: 'Every current validation/collision issue for this CLI, from the unified snapshot. Each: {code, path?, message, received, expected, next}.' },
175
- ],
176
- outputKind: 'object',
177
- effects: ['None. Read-only. Serves the stored manifest — never fetches, never revalidates.'],
178
- },
179
- run: async (input) => {
180
- const name = input['name'];
181
- const scopeInput = input['scope'];
182
- const scope = scopeInput !== undefined ? resolveScopeArg(scopeInput) : undefined;
183
- const registration = resolveConfiguredCli(name, scope);
184
- if (registration === null) {
185
- throw notFound(`configured CLI not found: ${name}`);
186
- }
187
- const snapshot = buildExternalCommandSnapshot(reservedCoreNames());
188
- return configuredCliReport(registration, snapshot);
189
- },
190
- });
@@ -1,3 +0,0 @@
1
- export declare const cliRegister: import("../../core/command.js").LeafDef;
2
- export declare const cliRemove: import("../../core/command.js").LeafDef;
3
- export declare const cliRefresh: import("../../core/command.js").LeafDef;
@@ -1,206 +0,0 @@
1
- import { defineLeaf } from '../../core/command.js';
2
- import { notFound, usage } from '../../core/errors.js';
3
- import { resolveScopeArg, requireScopeRoot, ensureProjectScopeRoot, } from '../../core/scope.js';
4
- import { updateConfig, ensureScopeInitialized, readConfig } from '../../core/config.js';
5
- import { SUBTREE_NAMES } from '../../build-root.js';
6
- import { resolveConfiguredCli, normalizeEndpoint, validateRegistrationName, validateEndpoint, validateEnvVarName, } from '../../core/configured-clis/registration.js';
7
- import { removeConfiguredCliCache } from '../../core/configured-clis/cache.js';
8
- import { registerFetchConfiguredCli, refreshConfiguredCli } from '../../core/configured-clis/fetch.js';
9
- import { buildExternalCommandSnapshot } from '../../core/configured-clis/discovery.js';
10
- import { configuredCliManifestState, configuredCliReport } from './cli-inspect.js';
11
- /** Resolve the target scope for `register`. Spec default is **user** (register
12
- * declares a remote surface; it does not install a local artifact). Only
13
- * user/project are valid. */
14
- function resolveRegisterScope(scopeInput) {
15
- if (scopeInput === undefined)
16
- return 'user';
17
- const resolved = resolveScopeArg(scopeInput);
18
- if (resolved === 'all' || resolved === 'builtin') {
19
- throw usage('scope must be "user" or "project"');
20
- }
21
- return resolved;
22
- }
23
- export const cliRegister = defineLeaf({
24
- name: 'register',
25
- description: 'register a configured CLI and load its manifest',
26
- whenToUse: 'declaring a configured CLI — a remote command surface defined by a manifest endpoint and executed as declarative HTTP. Records {name, endpoint, auth-env} to scope config and fetches-and-stores the manifest. Re-run on every guest boot to reload the manifest: a byte-identical re-register is an idempotent record but still re-fetches. To change an existing endpoint/auth-env, `remove` first.',
27
- help: {
28
- name: 'pkg cli register',
29
- summary: 'register a configured CLI and fetch its manifest',
30
- params: [
31
- { kind: 'flag', name: 'name', type: 'string', required: true, constraint: 'Registration name (kebab-case, unique among this scope\'s configured-CLI registrations). A local handle — NOT a command name; the top-level command(s) a CLI contributes are named by its manifest mounts.' },
32
- { kind: 'flag', name: 'endpoint', type: 'string', required: true, constraint: 'Absolute https:// URL crtr GETs to fetch the manifest (http:// permitted for loopback hosts only — local dev). Its origin is the default REST base for leaf calls. No credentials or fragment.' },
33
- { kind: 'flag', name: 'auth-env', type: 'string', required: false, constraint: 'Name of an env var holding a bearer token (e.g. NL_CORE_TOKEN). crtr reads process.env[NAME] at fetch/invocation time and sends Authorization: Bearer. Omit for an unauthenticated endpoint. The credential is never stored.' },
34
- { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: user.' },
35
- ],
36
- output: [
37
- { name: 'name', type: 'string', required: true, constraint: 'Registration name.' },
38
- { name: 'endpoint', type: 'string', required: true, constraint: 'Normalized manifest endpoint URL as stored.' },
39
- { name: 'auth_env', type: 'string', required: false, constraint: 'Configured auth env var name. Absent when unauthenticated.' },
40
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the registration was written to.' },
41
- { name: 'manifest_state', type: 'string', required: true, constraint: '"stored" (fetch succeeded, or a prior manifest was preserved) or "absent" (fetch failed and nothing was stored).' },
42
- { name: 'unchanged', type: 'boolean', required: false, constraint: 'True when the record already existed identically (idempotent re-register). The manifest was still re-fetched.' },
43
- ],
44
- outputKind: 'object',
45
- effects: [
46
- 'Writes configuredClis.<name> to the scope config.json (atomic, lock-guarded), then performs exactly one authenticated GET of the endpoint and stores the returned bytes as the manifest (replacing any prior one on success).',
47
- 'A byte-identical re-register (same endpoint + auth-env) skips the config write but still re-fetches and replaces the stored manifest — the guest-boot reload path.',
48
- 'A fetch failure (network / non-2xx) preserves any prior stored manifest and exits success with a stderr diagnostic suggesting `pkg cli refresh <name>`. A named-but-unset auth env, a differing endpoint/auth-env for an existing name, or an invalid name/endpoint fails before writing or fetching.',
49
- ],
50
- },
51
- run: async (input) => {
52
- const name = input['name']?.trim();
53
- const endpointRaw = input['endpoint'];
54
- const authEnvVar = input['authEnv'];
55
- const scopeInput = input['scope'];
56
- // ---- Validate inputs (a failure writes nothing and fetches nothing) ----
57
- const nameCheck = validateRegistrationName(name);
58
- if (!nameCheck.isValid) {
59
- throw usage(`invalid --name: ${nameCheck.errors.join('; ')}`);
60
- }
61
- const endpointCheck = validateEndpoint(endpointRaw);
62
- if (!endpointCheck.isValid) {
63
- throw usage(`invalid --endpoint: ${endpointCheck.errors.join('; ')}`);
64
- }
65
- if (authEnvVar !== undefined) {
66
- const envCheck = validateEnvVarName(authEnvVar);
67
- if (!envCheck.isValid) {
68
- throw usage(`invalid --auth-env: ${envCheck.errors.join('; ')}`);
69
- }
70
- }
71
- const endpoint = normalizeEndpoint(endpointRaw);
72
- const scope = resolveRegisterScope(scopeInput);
73
- if (scope === 'project')
74
- ensureProjectScopeRoot();
75
- ensureScopeInitialized(scope, requireScopeRoot(scope));
76
- // ---- Resolve any existing same-scope registration (idempotent / conflict) ----
77
- const existing = readConfig(scope).configuredClis[name];
78
- let unchanged = false;
79
- if (existing !== undefined) {
80
- const sameEndpoint = normalizeEndpoint(existing.endpoint) === endpoint;
81
- const sameAuth = (existing.authEnvVar ?? undefined) === (authEnvVar ?? undefined);
82
- if (sameEndpoint && sameAuth) {
83
- unchanged = true; // idempotent record — still re-fetch below
84
- }
85
- else {
86
- throw usage(`configured CLI "${name}" already registered in ${scope} scope with ` +
87
- `endpoint=${existing.endpoint}` +
88
- `${existing.authEnvVar !== undefined ? ` auth-env=${existing.authEnvVar}` : ' (no auth-env)'}. ` +
89
- `Run \`crtr pkg cli remove ${name} --scope ${scope}\` first to change it.`);
90
- }
91
- }
92
- // ---- Auth-env precondition: a named-but-unset var fails usage before any
93
- // fetch AND before any config write (a usage error mutates nothing). ----
94
- if (authEnvVar !== undefined && !process.env[authEnvVar]) {
95
- throw usage(`auth env var "${authEnvVar}" is required but not set or empty — export it before registering. ` +
96
- `crtr never prompts, mints, or refreshes a token.`);
97
- }
98
- // ---- Record the registration (skip the write when byte-identical) ----
99
- if (!unchanged) {
100
- updateConfig(scope, (cfg) => {
101
- cfg.configuredClis[name] = {
102
- endpoint,
103
- ...(authEnvVar !== undefined ? { authEnvVar } : {}),
104
- };
105
- });
106
- }
107
- const registration = resolveConfiguredCli(name, scope);
108
- // ---- One authenticated fetch-and-store (register always fetches) ----
109
- const result = await registerFetchConfiguredCli(registration);
110
- if (result.status !== 200) {
111
- if (result.status === 'auth_env_missing') {
112
- // Precondition re-check above should have caught this; fail loud.
113
- throw usage(result.message);
114
- }
115
- // Network / non-2xx: registration stands, prior manifest preserved,
116
- // exit success with a one-line stderr diagnostic + refresh remedy.
117
- process.stderr.write(`crtr: configured CLI "${registration.name}": ${result.message}\n`);
118
- process.stderr.write(`next: run \`crtr pkg cli refresh ${registration.name}\` once the endpoint is reachable\n`);
119
- }
120
- return {
121
- name: registration.name,
122
- endpoint: registration.endpoint,
123
- ...(registration.authEnvVar !== undefined ? { auth_env: registration.authEnvVar } : {}),
124
- scope: registration.scope,
125
- manifest_state: configuredCliManifestState(registration),
126
- ...(unchanged ? { unchanged: true } : {}),
127
- };
128
- },
129
- });
130
- export const cliRemove = defineLeaf({
131
- name: 'remove',
132
- description: 'remove a configured CLI registration and its stored manifest',
133
- whenToUse: 'deregistering a configured CLI: deletes its config entry and its stored manifest directory. Takes effect on the next invocation with no restart',
134
- help: {
135
- name: 'pkg cli remove',
136
- summary: 'remove a configured CLI registration and its stored manifest',
137
- params: [
138
- { kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Registration name to remove.' },
139
- { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution. Default: the effective registration (project outranks user).' },
140
- ],
141
- output: [
142
- { name: 'removed', type: 'boolean', required: true, constraint: 'Always true (a miss throws not_found).' },
143
- { name: 'name', type: 'string', required: true, constraint: 'Registration name removed.' },
144
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the registration was removed from.' },
145
- ],
146
- outputKind: 'object',
147
- effects: ['Deletes configuredClis.<name> from the scope config.json, then removes <scopeRoot>/configured-clis/<name>/. Config is authoritative — removed first.'],
148
- },
149
- run: async (input) => {
150
- const name = input['name'];
151
- const scopeInput = input['scope'];
152
- const scope = scopeInput !== undefined ? resolveScopeArg(scopeInput) : undefined;
153
- const registration = resolveConfiguredCli(name, scope);
154
- if (registration === null) {
155
- throw notFound(`configured CLI not found: ${name}`);
156
- }
157
- // Config is authoritative: delete the entry first so a crash between steps
158
- // can only leave an inert orphan store, never an active registration that
159
- // reported removed.
160
- updateConfig(registration.scope, (cfg) => {
161
- delete cfg.configuredClis[name];
162
- });
163
- removeConfiguredCliCache(registration);
164
- return { removed: true, name: registration.name, scope: registration.scope };
165
- },
166
- });
167
- export const cliRefresh = defineLeaf({
168
- name: 'refresh',
169
- description: 'refetch and replace a configured CLI manifest',
170
- whenToUse: 'pulling a backend manifest change into an already-stored CLI: unconditionally refetches and replaces the stored manifest, then reports the accepted command surface and issues. The only way to update a stored manifest short of a re-register',
171
- help: {
172
- name: 'pkg cli refresh',
173
- summary: 'refetch and replace a configured CLI manifest, report the surface',
174
- params: [
175
- { kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Registration name to refresh.' },
176
- { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution. Default: the effective registration (project outranks user).' },
177
- ],
178
- output: [
179
- { name: 'name', type: 'string', required: true, constraint: 'Registration name.' },
180
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the registration lives in.' },
181
- { name: 'endpoint', type: 'string', required: true, constraint: 'Resolved manifest endpoint URL.' },
182
- { name: 'manifest_state', type: 'string', required: true, constraint: '"stored" after a successful refresh.' },
183
- { name: 'commands', type: 'object[]', required: true, constraint: 'Every accepted command after the refresh. Each: {command, paths}. Empty when the new manifest is invalid or collided.' },
184
- { name: 'issues', type: 'object[]', required: true, constraint: 'Every current validation/collision issue. Each: {code, path?, message, received, expected, next}.' },
185
- ],
186
- outputKind: 'object',
187
- effects: [
188
- 'Performs one unconditional authenticated GET of the endpoint and replaces the stored manifest with the returned bytes on success.',
189
- 'On failure (network / non-2xx / named-but-unset auth env) it fails loud and KEEPS the previously stored manifest — the command still composes from it.',
190
- ],
191
- },
192
- run: async (input) => {
193
- const name = input['name'];
194
- const scopeInput = input['scope'];
195
- const scope = scopeInput !== undefined ? resolveScopeArg(scopeInput) : undefined;
196
- const registration = resolveConfiguredCli(name, scope);
197
- if (registration === null) {
198
- throw notFound(`configured CLI not found: ${name}`);
199
- }
200
- // Unconditional fetch-and-replace. Throws on failure (auth→usage,
201
- // unreachable→network, protocol→general), preserving the old manifest.
202
- await refreshConfiguredCli(registration);
203
- const snapshot = buildExternalCommandSnapshot(new Set(SUBTREE_NAMES));
204
- return configuredCliReport(registration, snapshot);
205
- },
206
- });
@@ -1 +0,0 @@
1
- export declare const cliBranch: import("../../core/command.js").BranchDef;
@@ -1,14 +0,0 @@
1
- import { defineBranch } from '../../core/command.js';
2
- import { cliRegister, cliRemove, cliRefresh } from './cli-manage.js';
3
- import { cliList, cliShow } from './cli-inspect.js';
4
- export const cliBranch = defineBranch({
5
- name: 'cli',
6
- description: 'register and manage configured CLIs',
7
- whenToUse: 'working with configured CLIs: a command surface whose tree is defined by a remote manifest and executed as declarative HTTP (definition + HTTP, never remote code). Register one against a manifest endpoint, remove, list, show, or refresh it. Use `pkg plugin` for local plugins that ship an executable, and `pkg market` for marketplace sources.',
8
- help: {
9
- name: 'pkg cli',
10
- summary: 'configured-CLI lifecycle and inspection',
11
- model: 'A configured CLI is the CLI analogue of an MCP client: crtr fetches a manifest (an extension of the command-plugin schema) from a remote endpoint, stores it locally, renders native -h help from it, and runs each leaf as a declarative REST call. No remote code — the trust surface is definition + HTTP. Registration records {name, endpoint, auth-env NAME} in scope config and stores the fetched manifest; the credential itself is never stored. The stored manifest is authoritative with zero freshness checks — it reloads only on a re-register or an explicit refresh.',
12
- },
13
- children: [cliRegister, cliRemove, cliList, cliShow, cliRefresh],
14
- });
@@ -1,16 +0,0 @@
1
- import type { ConfiguredCliRegistration } from './registration.js';
2
- /** Minimal metadata stored with a cached manifest. */
3
- export interface CacheMetadata {
4
- /** The endpoint this manifest was fetched from; a mismatch invalidates the cache. */
5
- endpoint: string;
6
- }
7
- /** Read the cached manifest bytes if present. Returns null if absent or unreadable. */
8
- export declare function readConfiguredCliCacheManifest(registration: ConfiguredCliRegistration): Uint8Array | null;
9
- /** Write a manifest to cache atomically. Replaces any prior manifest and stores minimal metadata. */
10
- export declare function writeConfiguredCliCacheManifest(registration: ConfiguredCliRegistration, rawManifest: Uint8Array): void;
11
- /** Check if cache metadata exists and matches the current endpoint.
12
- * Returns true if both manifest and metadata exist with matching endpoint;
13
- * false if absent, unreadable, or endpoint mismatch. */
14
- export declare function isCacheMetadataValid(registration: ConfiguredCliRegistration): boolean;
15
- /** Remove the cache directory for a registration. */
16
- export declare function removeConfiguredCliCache(registration: ConfiguredCliRegistration): void;
@@ -1,57 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { atomicWriteJson, atomicWriteText, ensureDir, pathExists, readJsonIfExists, removePath, } from '../fs-utils.js';
3
- import { configuredCliCacheDir, configuredCliCacheMetaPath, configuredCliManifestPath } from './registration.js';
4
- /** Read the cached manifest bytes if present. Returns null if absent or unreadable. */
5
- export function readConfiguredCliCacheManifest(registration) {
6
- const manifestPath = configuredCliManifestPath(registration);
7
- if (!pathExists(manifestPath)) {
8
- return null;
9
- }
10
- try {
11
- return readFileSync(manifestPath);
12
- }
13
- catch {
14
- return null;
15
- }
16
- }
17
- /** Write a manifest to cache atomically. Replaces any prior manifest and stores minimal metadata. */
18
- export function writeConfiguredCliCacheManifest(registration, rawManifest) {
19
- const cacheDir = configuredCliCacheDir(registration);
20
- const manifestPath = configuredCliManifestPath(registration);
21
- const metaPath = configuredCliCacheMetaPath(registration);
22
- ensureDir(cacheDir);
23
- // Write manifest (exact bytes preserved).
24
- atomicWriteText(manifestPath, rawManifest);
25
- // Write minimal metadata (endpoint only).
26
- const meta = {
27
- endpoint: registration.endpoint,
28
- };
29
- atomicWriteJson(metaPath, meta);
30
- }
31
- /** Check if cache metadata exists and matches the current endpoint.
32
- * Returns true if both manifest and metadata exist with matching endpoint;
33
- * false if absent, unreadable, or endpoint mismatch. */
34
- export function isCacheMetadataValid(registration) {
35
- const manifestPath = configuredCliManifestPath(registration);
36
- const metaPath = configuredCliCacheMetaPath(registration);
37
- if (!pathExists(manifestPath) || !pathExists(metaPath)) {
38
- return false;
39
- }
40
- try {
41
- const meta = readJsonIfExists(metaPath);
42
- if (!meta) {
43
- return false;
44
- }
45
- return meta.endpoint === registration.endpoint;
46
- }
47
- catch {
48
- return false;
49
- }
50
- }
51
- /** Remove the cache directory for a registration. */
52
- export function removeConfiguredCliCache(registration) {
53
- const cacheDir = configuredCliCacheDir(registration);
54
- if (pathExists(cacheDir)) {
55
- removePath(cacheDir);
56
- }
57
- }
@@ -1,14 +0,0 @@
1
- import type { CommandContribution } from '../command-manifests/registry.js';
2
- import type { ValidatedConfiguredCliManifest } from './manifest.js';
3
- import type { ConfiguredCliRegistration } from './registration.js';
4
- /**
5
- * Adapt one registration + its validated manifest into transport-neutral
6
- * contributions — one per top-level root the manifest declares. Each leaf's
7
- * `run` closure captures the registration (name/endpoint/authEnvVar), the
8
- * manifest-level base/timeout overrides, and the leaf's own `rest` mapping,
9
- * then calls `invokeConfiguredLeaf`. The bearer token is never captured here —
10
- * the invoker reads it from the environment at call time. `outputKind` is
11
- * derived from `rest.streaming` (the single source of truth per §4.3): a
12
- * streaming leaf renders as `jsonl`, a unary leaf as `object`.
13
- */
14
- export declare function adaptConfiguredContributions(registration: ConfiguredCliRegistration, manifest: ValidatedConfiguredCliManifest): CommandContribution[];
@@ -1,60 +0,0 @@
1
- // Configured-CLI contribution adapter — the transport binding for a validated,
2
- // stored configured-CLI manifest. It turns each of a manifest's top-level roots
3
- // into a transport-neutral `CommandContribution` whose `adaptLeaf` closes over
4
- // the registration + manifest metadata and dispatches every leaf through the
5
- // generic HTTP invoker (D1). This is the configured-CLI analogue of
6
- // `adaptPluginContributions` (command-plugins/compose.ts): both feed the same
7
- // unified `resolveCommandRegistry` collision snapshot and the same
8
- // `composeExternalSubtrees` tree composer, so a configured CLI is
9
- // indistinguishable from a native command at the `-h` and dispatch surface —
10
- // only the execution seam differs.
11
- //
12
- // This module performs ZERO network and ZERO freshness work: it consumes an
13
- // already-validated manifest (from discovery.ts, read from the stored bytes)
14
- // and produces contributions. The HTTP request happens only later, on explicit
15
- // leaf invocation, inside the invoker.
16
- import { invokeConfiguredLeaf } from './invoker.js';
17
- /**
18
- * Adapt one registration + its validated manifest into transport-neutral
19
- * contributions — one per top-level root the manifest declares. Each leaf's
20
- * `run` closure captures the registration (name/endpoint/authEnvVar), the
21
- * manifest-level base/timeout overrides, and the leaf's own `rest` mapping,
22
- * then calls `invokeConfiguredLeaf`. The bearer token is never captured here —
23
- * the invoker reads it from the environment at call time. `outputKind` is
24
- * derived from `rest.streaming` (the single source of truth per §4.3): a
25
- * streaming leaf renders as `jsonl`, a unary leaf as `object`.
26
- */
27
- export function adaptConfiguredContributions(registration, manifest) {
28
- return manifest.roots.map((root) => ({
29
- contributor: {
30
- kind: 'configured-cli',
31
- name: registration.name,
32
- scope: registration.scope,
33
- },
34
- // A `DeclBranch<ConfiguredCliDeclLeaf>` widens structurally to the
35
- // registry's `DeclBranch<DeclLeafBase>` (the leaf dialect only adds `rest`).
36
- node: root,
37
- adaptLeaf: (leaf, commandPath) => {
38
- // Every leaf reaching this adapter came from a validated configured
39
- // manifest, so it carries the `rest` mapping.
40
- const configuredLeaf = leaf;
41
- const streaming = configuredLeaf.rest.streaming === true;
42
- return {
43
- outputKind: streaming ? 'jsonl' : 'object',
44
- run: (input, context) => invokeConfiguredLeaf({
45
- registration: {
46
- name: registration.name,
47
- endpoint: registration.endpoint,
48
- ...(registration.authEnvVar !== undefined ? { authEnvVar: registration.authEnvVar } : {}),
49
- },
50
- ...(manifest.baseUrl !== undefined ? { baseUrl: manifest.baseUrl } : {}),
51
- ...(manifest.timeouts !== undefined ? { timeouts: manifest.timeouts } : {}),
52
- params: configuredLeaf.params,
53
- rest: configuredLeaf.rest,
54
- output: configuredLeaf.output,
55
- commandPath: Array.from(commandPath),
56
- }, input, context),
57
- };
58
- },
59
- }));
60
- }
@@ -1,47 +0,0 @@
1
- import type { CommandContribution, CommandRegistrySnapshot, ContributorIssue } from '../command-manifests/registry.js';
2
- /** Raw configured candidates before cross-contributor collision resolution:
3
- * the contributions from every registration with a valid stored manifest, the
4
- * validation issues from every registration whose stored manifest is present
5
- * but invalid, and the names of registrations whose store reads as absent (the
6
- * hydration-eligible set, for a caller deciding whether a miss should fetch). */
7
- export interface ConfiguredCliDiscovery {
8
- candidates: CommandContribution[];
9
- issues: ContributorIssue[];
10
- /** Effective registrations with no readable stored manifest (§5.1 hydration). */
11
- absentStoreNames: string[];
12
- }
13
- /** Discover configured-CLI candidates from stored manifests only — zero network.
14
- * Each effective registration (project over user) contributes its valid roots,
15
- * reports its invalid-manifest issues, or is noted as absent-store. */
16
- export declare function discoverConfiguredCliCandidates(reservedCoreNames: ReadonlySet<string>): ConfiguredCliDiscovery;
17
- /** Build THE unified external-command snapshot: every effective command plugin
18
- * and every effective configured CLI, resolved through ONE collision pass, with
19
- * every issue (plugin-structural, configured-manifest-invalid, and cross-
20
- * contributor collision) merged into one list. Performs zero network I/O — all
21
- * manifests read from disk. This exact result is consumed by dispatch/root
22
- * composition, `pkg cli show`/`pkg plugin show`, and `sys doctor`, so their
23
- * accepted-command and issue views can never diverge. `reservedCoreNames` are
24
- * the core top-level subtree names — core always wins. */
25
- export declare function buildExternalCommandSnapshot(reservedCoreNames: ReadonlySet<string>): CommandRegistrySnapshot;
26
- /** Outcome of a miss-triggered absent-store hydration pass. */
27
- export interface AbsentStoreHydration {
28
- /** True if at least one absent-store registration now has freshly stored
29
- * bytes — the signal for the caller to rebuild the tree and re-resolve once. */
30
- hydratedAny: boolean;
31
- /** One concise stderr line per failed hydration (naming the registration, its
32
- * fetch error, and the refresh remedy). No NETWORK precedence: a failure here
33
- * never changes the caller's eventual `unknown_path`/USAGE outcome. */
34
- diagnostics: string[];
35
- }
36
- /** Hydrate EVERY effective absent-store registration exactly once (§5.1). For
37
- * each registration with no readable stored manifest, perform one authenticated
38
- * `GET endpoint`, storing the exact bytes on success. A fetch failure (network,
39
- * non-2xx, or named-but-empty auth env) leaves the store absent and records a
40
- * one-line diagnostic — it never throws and never earns NETWORK precedence over
41
- * the caller's `unknown_path`/USAGE classification. Registrations that already
42
- * hold a stored manifest (valid OR merely schema-invalid) are skipped, so with
43
- * every manifest stored this performs zero network I/O.
44
- *
45
- * Intended caller: dispatch (E2), on an unknown-first-token miss, followed by a
46
- * single tree rebuild + re-resolve when `hydratedAny` is true. */
47
- export declare function hydrateAbsentConfiguredClis(): Promise<AbsentStoreHydration>;