@north-light/crouter 0.3.163 → 0.3.165

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 (167) 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 +5 -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/memory-loading.md +45 -0
  35. package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
  36. package/dist/builtin-memory/internal/plugins.md +61 -45
  37. package/dist/builtin-memory/planning.md +4 -7
  38. package/dist/builtin-memory/spec.md +9 -12
  39. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
  40. package/dist/cli.js +1 -1
  41. package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
  42. package/dist/clients/attach/__tests__/context-message.test.js +31 -20
  43. package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
  44. package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
  45. package/dist/clients/attach/__tests__/edit-diff.test.js +21 -20
  46. package/dist/clients/attach/render/chat-view.d.ts +20 -20
  47. package/dist/clients/attach/render/chat-view.js +88 -89
  48. package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
  49. package/dist/clients/attach/render/condensed-history.js +60 -0
  50. package/dist/clients/attach/render/context-message.js +9 -14
  51. package/dist/clients/attach/render/crtr-output.d.ts +2 -1
  52. package/dist/clients/attach/render/crtr-output.js +4 -4
  53. package/dist/clients/attach/render/edit-diff.d.ts +21 -6
  54. package/dist/clients/attach/render/edit-diff.js +90 -91
  55. package/dist/clients/attach/render/tool-calls.d.ts +14 -18
  56. package/dist/clients/attach/render/tool-calls.js +66 -41
  57. package/dist/clients/attach/session/bindings.d.ts +5 -3
  58. package/dist/clients/attach/session/bindings.js +7 -13
  59. package/dist/clients/attach/session/keys.d.ts +2 -0
  60. package/dist/clients/attach/session/keys.js +36 -37
  61. package/dist/clients/attach/session/profile-files.d.ts +8 -0
  62. package/dist/clients/attach/session/profile-files.js +157 -0
  63. package/dist/clients/attach/viewer.js +530 -528
  64. package/dist/commands/memory/lint.js +1 -1
  65. package/dist/commands/memory/write.js +1 -1
  66. package/dist/commands/node.js +2 -2
  67. package/dist/commands/pkg/plugin-inspect.js +6 -7
  68. package/dist/commands/pkg/plugin-manage.d.ts +1 -1
  69. package/dist/commands/pkg/plugin-manage.js +131 -19
  70. package/dist/commands/pkg/plugin.js +2 -2
  71. package/dist/commands/pkg.js +6 -11
  72. package/dist/commands/profile/env.js +3 -3
  73. package/dist/commands/sys/config.js +17 -76
  74. package/dist/commands/sys/doctor.js +5 -91
  75. package/dist/commands/sys/setup-wizard.d.ts +9 -11
  76. package/dist/commands/sys/setup-wizard.js +47 -81
  77. package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
  78. package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
  79. package/dist/core/__tests__/command-plugins.test.js +36 -16
  80. package/dist/core/__tests__/review-model-floor.test.js +2 -2
  81. package/dist/core/__tests__/tmux-surface.test.js +10 -1
  82. package/dist/core/command-manifests/manifest.d.ts +24 -0
  83. package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
  84. package/dist/core/command-manifests/registry.d.ts +1 -2
  85. package/dist/core/command-manifests/registry.js +2 -2
  86. package/dist/core/command-manifests/schema.d.ts +11 -13
  87. package/dist/core/command-manifests/schema.js +53 -192
  88. package/dist/core/command-plugins/compose.d.ts +0 -6
  89. package/dist/core/command-plugins/compose.js +28 -75
  90. package/dist/core/command-plugins/discovery.d.ts +25 -58
  91. package/dist/core/command-plugins/discovery.js +152 -259
  92. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  93. package/dist/core/command-plugins/endpoint.js +48 -0
  94. package/dist/core/command-plugins/store.d.ts +16 -0
  95. package/dist/core/command-plugins/store.js +64 -0
  96. package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
  97. package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
  98. package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
  99. package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
  100. package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
  101. package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
  102. package/dist/core/command.d.ts +8 -9
  103. package/dist/core/command.js +6 -9
  104. package/dist/core/config.js +6 -10
  105. package/dist/core/env-name.d.ts +6 -0
  106. package/dist/core/env-name.js +9 -0
  107. package/dist/core/io.d.ts +1 -1
  108. package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
  109. package/dist/core/keybindings/attach-control.d.ts +37 -0
  110. package/dist/core/keybindings/attach-control.js +38 -0
  111. package/dist/core/keybindings/catalog.d.ts +5 -4
  112. package/dist/core/keybindings/catalog.js +16 -8
  113. package/dist/core/keybindings/index.d.ts +1 -0
  114. package/dist/core/keybindings/index.js +1 -0
  115. package/dist/core/keybindings/types.d.ts +1 -1
  116. package/dist/core/preview-registry.js +41 -74
  117. package/dist/core/runtime/bearings.d.ts +2 -5
  118. package/dist/core/runtime/bearings.js +2 -5
  119. package/dist/core/runtime/front-door.d.ts +1 -1
  120. package/dist/core/runtime/front-door.js +2 -2
  121. package/dist/core/runtime/kickoff.d.ts +3 -3
  122. package/dist/core/runtime/kickoff.js +4 -3
  123. package/dist/core/runtime/situational-context.d.ts +1 -1
  124. package/dist/core/runtime/situational-context.js +1 -1
  125. package/dist/core/runtime/spawn.js +9 -9
  126. package/dist/core/runtime/tmux.js +29 -2
  127. package/dist/core/scope.d.ts +0 -5
  128. package/dist/core/scope.js +0 -10
  129. package/dist/core/user-settings.d.ts +193 -0
  130. package/dist/core/user-settings.js +252 -0
  131. package/dist/index.d.ts +4 -0
  132. package/dist/index.js +3 -0
  133. package/dist/pi-extensions/canvas-stophook.js +3 -2
  134. package/dist/pi-extensions/canvas-tool-guide.js +4 -1
  135. package/dist/shared/generated-context.d.ts +34 -0
  136. package/dist/shared/generated-context.js +98 -0
  137. package/dist/types.d.ts +23 -16
  138. package/dist/types.js +3 -14
  139. package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
  140. package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
  141. package/dist/web-client/index.html +2 -2
  142. package/dist/web-client/sw.js +1 -1
  143. package/docs/public-api.md +1 -0
  144. package/package.json +2 -2
  145. package/runtime.lock.json +6 -6
  146. package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
  147. package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
  148. package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
  149. package/dist/builtin-memory/internal/workflow-codification.md +0 -82
  150. package/dist/builtin-memory/product.md +0 -80
  151. package/dist/clients/attach/render/frozen-history.js +0 -100
  152. package/dist/commands/pkg/cli-inspect.d.ts +0 -17
  153. package/dist/commands/pkg/cli-inspect.js +0 -190
  154. package/dist/commands/pkg/cli-manage.d.ts +0 -3
  155. package/dist/commands/pkg/cli-manage.js +0 -206
  156. package/dist/commands/pkg/cli.d.ts +0 -1
  157. package/dist/commands/pkg/cli.js +0 -14
  158. package/dist/core/configured-clis/cache.d.ts +0 -16
  159. package/dist/core/configured-clis/cache.js +0 -57
  160. package/dist/core/configured-clis/compose.d.ts +0 -14
  161. package/dist/core/configured-clis/compose.js +0 -60
  162. package/dist/core/configured-clis/discovery.d.ts +0 -47
  163. package/dist/core/configured-clis/discovery.js +0 -173
  164. package/dist/core/configured-clis/manifest.d.ts +0 -24
  165. package/dist/core/configured-clis/registration.d.ts +0 -40
  166. package/dist/core/configured-clis/registration.js +0 -201
  167. package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
@@ -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>;
@@ -1,173 +0,0 @@
1
- // Configured-CLI discovery — the single integration seam that joins effective
2
- // registrations, their STORED manifest bytes, pure §4 validation, and configured
3
- // contribution creation, then folds the result together with command-plugin
4
- // candidates into ONE unified, collision-resolved command snapshot.
5
- //
6
- // This is the serial integration point (E1). Three consumers read the exact same
7
- // final snapshot so their view of accepted commands and issues can never drift:
8
- // - dispatch / root composition (build-root.ts) via `buildExternalCommandSnapshot`
9
- // - `pkg cli show` + `pkg plugin show` (lifecycle inspection)
10
- // - `sys doctor`
11
- //
12
- // Two hard invariants of the amended spec:
13
- // 1. Composition from a stored manifest does ZERO network and ZERO freshness
14
- // work — no TTL, no ETag, no revalidation, no debounce. The bytes on disk
15
- // are authoritative until a `register`/`refresh` replaces them.
16
- // 2. The ONLY network this module can trigger is `hydrateAbsentConfiguredClis`,
17
- // the one-time absent-store hydration on an unknown-first-token miss (§5.1).
18
- // It is exported separately so dispatch (E2) can call it explicitly; nothing
19
- // on the composition path ever fetches.
20
- import { resolveCommandRegistry } from '../command-manifests/registry.js';
21
- import { adaptPluginContributions } from '../command-plugins/compose.js';
22
- import { discoverPluginCommandCandidates } from '../command-plugins/discovery.js';
23
- import { validateConfiguredCliManifest } from './manifest.js';
24
- import { effectiveConfiguredClis } from './registration.js';
25
- import { isCacheMetadataValid, readConfiguredCliCacheManifest, writeConfiguredCliCacheManifest } from './cache.js';
26
- import { fetchConfiguredCliManifest } from './fetch.js';
27
- import { adaptConfiguredContributions } from './compose.js';
28
- function contributorRef(registration) {
29
- return { kind: 'configured-cli', name: registration.name, scope: registration.scope };
30
- }
31
- /** Read + validate a registration's STORED manifest. Never fetches. A missing,
32
- * metadata-mismatched, or unparseable manifest reads as `absent` (§3.3: corrupt
33
- * or incomplete stored state reads as absent, making it hydration-eligible). A
34
- * parseable manifest that fails §4 validation is `invalid` (stored, reports
35
- * issues, composes nothing — NOT hydration-eligible). */
36
- function readStoredManifest(registration, reservedCoreNames) {
37
- if (!isCacheMetadataValid(registration))
38
- return { state: 'absent' };
39
- const bytes = readConfiguredCliCacheManifest(registration);
40
- if (bytes === null)
41
- return { state: 'absent' };
42
- let raw;
43
- try {
44
- raw = JSON.parse(Buffer.from(bytes).toString('utf8'));
45
- }
46
- catch {
47
- return { state: 'absent' }; // unparseable → reads as absent
48
- }
49
- const validation = validateConfiguredCliManifest(raw, { reservedCoreNames });
50
- if (validation.manifest !== undefined)
51
- return { state: 'valid', manifest: validation.manifest };
52
- return {
53
- state: 'invalid',
54
- issues: validation.issues.map((i) => ({ ...i, contributor: contributorRef(registration) })),
55
- };
56
- }
57
- /** Discover configured-CLI candidates from stored manifests only — zero network.
58
- * Each effective registration (project over user) contributes its valid roots,
59
- * reports its invalid-manifest issues, or is noted as absent-store. */
60
- export function discoverConfiguredCliCandidates(reservedCoreNames) {
61
- const candidates = [];
62
- const issues = [];
63
- const absentStoreNames = [];
64
- for (const registration of effectiveConfiguredClis()) {
65
- const stored = readStoredManifest(registration, reservedCoreNames);
66
- if (stored.state === 'absent') {
67
- absentStoreNames.push(registration.name);
68
- }
69
- else if (stored.state === 'invalid') {
70
- issues.push(...stored.issues);
71
- }
72
- else {
73
- candidates.push(...adaptConfiguredContributions(registration, stored.manifest));
74
- }
75
- }
76
- return { candidates, issues, absentStoreNames };
77
- }
78
- // ---------------------------------------------------------------------------
79
- // The one unified snapshot (zero network)
80
- // ---------------------------------------------------------------------------
81
- /** Convert command-plugin candidates + their per-plugin structural issues into
82
- * the transport-neutral shape, WITHOUT the plugin module's own collision pass
83
- * (collisions are resolved once, uniformly, by `resolveCommandRegistry`). */
84
- function pluginCandidates() {
85
- const validations = discoverPluginCommandCandidates();
86
- // Reuse the plugin adapter purely to bind each ValidatedContribution's leaf to
87
- // its executable-spawn `run`; the registry does the collision resolution.
88
- const candidates = adaptPluginContributions(validations.flatMap((v) => v.contributions)).contributions;
89
- const issues = validations.flatMap((v) => v.issues.map((i) => ({
90
- code: i.code,
91
- ...(i.path !== undefined ? { path: i.path } : {}),
92
- message: i.message,
93
- received: i.received,
94
- expected: i.expected,
95
- next: i.next,
96
- // Command plugins are never installed at builtin scope.
97
- contributor: { kind: 'plugin', name: v.plugin.name, scope: v.plugin.scope },
98
- })));
99
- return { candidates, issues };
100
- }
101
- /** Build THE unified external-command snapshot: every effective command plugin
102
- * and every effective configured CLI, resolved through ONE collision pass, with
103
- * every issue (plugin-structural, configured-manifest-invalid, and cross-
104
- * contributor collision) merged into one list. Performs zero network I/O — all
105
- * manifests read from disk. This exact result is consumed by dispatch/root
106
- * composition, `pkg cli show`/`pkg plugin show`, and `sys doctor`, so their
107
- * accepted-command and issue views can never diverge. `reservedCoreNames` are
108
- * the core top-level subtree names — core always wins. */
109
- export function buildExternalCommandSnapshot(reservedCoreNames) {
110
- const plugins = pluginCandidates();
111
- const configured = discoverConfiguredCliCandidates(reservedCoreNames);
112
- const resolved = resolveCommandRegistry([...plugins.candidates, ...configured.candidates], reservedCoreNames);
113
- return {
114
- contributions: resolved.contributions,
115
- issues: [...plugins.issues, ...configured.issues, ...resolved.issues],
116
- };
117
- }
118
- /** Is this registration's stored manifest absent (hydration-eligible)? Mirrors
119
- * `readStoredManifest`'s absent branch without running the full §4 validator —
120
- * a parseable-but-invalid manifest is stored, not absent. */
121
- function isAbsentStore(registration) {
122
- if (!isCacheMetadataValid(registration))
123
- return true;
124
- const bytes = readConfiguredCliCacheManifest(registration);
125
- if (bytes === null)
126
- return true;
127
- try {
128
- JSON.parse(Buffer.from(bytes).toString('utf8'));
129
- return false;
130
- }
131
- catch {
132
- return true; // unparseable → reads as absent
133
- }
134
- }
135
- /** Hydrate EVERY effective absent-store registration exactly once (§5.1). For
136
- * each registration with no readable stored manifest, perform one authenticated
137
- * `GET endpoint`, storing the exact bytes on success. A fetch failure (network,
138
- * non-2xx, or named-but-empty auth env) leaves the store absent and records a
139
- * one-line diagnostic — it never throws and never earns NETWORK precedence over
140
- * the caller's `unknown_path`/USAGE classification. Registrations that already
141
- * hold a stored manifest (valid OR merely schema-invalid) are skipped, so with
142
- * every manifest stored this performs zero network I/O.
143
- *
144
- * Intended caller: dispatch (E2), on an unknown-first-token miss, followed by a
145
- * single tree rebuild + re-resolve when `hydratedAny` is true. */
146
- export async function hydrateAbsentConfiguredClis() {
147
- let hydratedAny = false;
148
- const diagnostics = [];
149
- for (const registration of effectiveConfiguredClis()) {
150
- if (!isAbsentStore(registration))
151
- continue;
152
- // A per-registration fetch OR store failure (network, non-2xx, empty auth
153
- // env, or a filesystem write error) is a FAILED hydration for this one
154
- // registration: record a diagnostic and continue. It must never throw and
155
- // never earn NETWORK precedence over the caller's unknown_path/USAGE
156
- // outcome (§5.1), so the whole attempt — including the atomic store — is
157
- // caught here.
158
- try {
159
- const result = await fetchConfiguredCliManifest(registration);
160
- if (result.status === 200) {
161
- writeConfiguredCliCacheManifest(registration, result.raw);
162
- hydratedAny = true;
163
- }
164
- else {
165
- diagnostics.push(`configured CLI "${registration.name}": ${result.message} — run \`crtr pkg cli refresh ${registration.name}\``);
166
- }
167
- }
168
- catch (error) {
169
- diagnostics.push(`configured CLI "${registration.name}": ${error.message} — run \`crtr pkg cli refresh ${registration.name}\``);
170
- }
171
- }
172
- return { hydratedAny, diagnostics };
173
- }
@@ -1,24 +0,0 @@
1
- import type { ConfiguredCliDeclLeaf, DeclBranch, ManifestTimeouts, CommandManifestIssue } from '../command-manifests/schema.js';
2
- export interface ValidatedConfiguredCliManifest {
3
- schemaVersion: 1;
4
- baseUrl?: string;
5
- timeouts?: ManifestTimeouts;
6
- /** Top-level branches, each ready to mount at parent []. */
7
- roots: DeclBranch<ConfiguredCliDeclLeaf>[];
8
- }
9
- export interface ConfiguredCliManifestValidation {
10
- /** The validated, materialized manifest (present only if all issues are fixed). */
11
- manifest?: ValidatedConfiguredCliManifest;
12
- /** All typed validation issues. Any issue means zero contributions. */
13
- issues: CommandManifestIssue[];
14
- }
15
- /**
16
- * Validates a configured CLI manifest exactly per §4, performing strict atomic
17
- * validation and order-independent forest materialization. Returns either a
18
- * valid manifest with roots ready to mount, or a complete issues list with
19
- * zero contributions.
20
- */
21
- export declare function validateConfiguredCliManifest(raw: unknown, options: {
22
- reservedCoreNames: ReadonlySet<string>;
23
- }): ConfiguredCliManifestValidation;
24
- export { validateConfiguredCliNode } from '../command-manifests/schema.js';
@@ -1,40 +0,0 @@
1
- import type { Scope } from '../../types.js';
2
- /** A registered configured CLI: name, endpoint, optional auth env var, scope and root. */
3
- export interface ConfiguredCliRegistration {
4
- name: string;
5
- endpoint: string;
6
- authEnvVar?: string;
7
- scope: 'project' | 'user';
8
- root: string;
9
- }
10
- /** Validation result for registration inputs. */
11
- export interface RegistrationValidation {
12
- isValid: boolean;
13
- errors: string[];
14
- }
15
- /** Validate a registration name. */
16
- export declare function validateRegistrationName(name: unknown): RegistrationValidation;
17
- /** Validate an environment variable name. */
18
- export declare function validateEnvVarName(name: unknown): RegistrationValidation;
19
- /** Validate an endpoint URL. */
20
- export declare function validateEndpoint(endpoint: unknown): RegistrationValidation;
21
- /** Normalize endpoint URL to a canonical form (remove trailing slash if present). */
22
- export declare function normalizeEndpoint(endpoint: string): string;
23
- /** List every EFFECTIVE configured CLI visible across scopes (project nearest-first,
24
- * then user). Dedupes by name: the first-seen registration for a name wins (nearer
25
- * project root, then user) and any same-name registration in a farther scope is
26
- * omitted entirely — there is no shadowed-copy representation and no `effective`
27
- * field on `ConfiguredCliRegistration`. */
28
- export declare function listConfiguredCliRegistrations(): ConfiguredCliRegistration[];
29
- /** Get deduplicated effective registrations (one per unique name, project wins over user). */
30
- export declare function effectiveConfiguredClis(): ConfiguredCliRegistration[];
31
- /** Resolve a configured CLI registration by name. If scope is omitted, returns the
32
- * effective winner (project > user). If scope is explicit, returns the registration
33
- * in that scope or null if not found. */
34
- export declare function resolveConfiguredCli(name: string, scope?: Scope | 'project' | 'user'): ConfiguredCliRegistration | null;
35
- /** Get the cache directory for a configured CLI registration. */
36
- export declare function configuredCliCacheDir(registration: ConfiguredCliRegistration): string;
37
- /** Get the manifest file path for a configured CLI. */
38
- export declare function configuredCliManifestPath(registration: ConfiguredCliRegistration): string;
39
- /** Get the cache metadata file path for a configured CLI. */
40
- export declare function configuredCliCacheMetaPath(registration: ConfiguredCliRegistration): string;
@@ -1,201 +0,0 @@
1
- import { join } from 'node:path';
2
- import { configuredClisDir, configuredClisDirForRoot, projectScopeRoots, userScopeRoot } from '../scope.js';
3
- import { readConfig, readConfigAtRoot } from '../config.js';
4
- /** Kebab-case identifier: lowercase letter start, alphanumeric + hyphen chars,
5
- * no trailing hyphens. Examples: `demo`, `my-cli`, `azure-devops`. */
6
- function isValidRegistrationName(name) {
7
- return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name);
8
- }
9
- /** Environment variable name: must start with letter or underscore, followed by
10
- * alphanumeric or underscore chars. Examples: `NL_CORE_TOKEN`, `_INTERNAL`. */
11
- function isValidEnvVarName(name) {
12
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
13
- }
14
- /** Loopback hostnames permitted for plain-http endpoints (local dev only).
15
- * Includes `host.docker.internal`, Docker's own DNS alias a container uses to
16
- * reach its host — the container-network equivalent of loopback for the same
17
- * local-dev trust boundary (e.g. a Dockerized guest registering its host
18
- * Core as a configured CLI). */
19
- function isLoopbackHostname(hostname) {
20
- // URL normalizes IPv6 hosts to bracketed form, e.g. `[::1]`.
21
- return (hostname === 'localhost' ||
22
- hostname === '127.0.0.1' ||
23
- hostname === '[::1]' ||
24
- hostname === '::1' ||
25
- hostname === 'host.docker.internal');
26
- }
27
- /** Validate that endpoint is an absolute HTTPS URL (or HTTP on a loopback host)
28
- * with no credentials/fragment. */
29
- function isValidEndpoint(endpoint) {
30
- try {
31
- const url = new URL(endpoint);
32
- // https required; plain http permitted only for loopback hosts (local dev)
33
- if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopbackHostname(url.hostname)))
34
- return false;
35
- if (url.username || url.password || url.hash)
36
- return false;
37
- return true;
38
- }
39
- catch {
40
- return false;
41
- }
42
- }
43
- /** Validate a registration name. */
44
- export function validateRegistrationName(name) {
45
- const errors = [];
46
- if (typeof name !== 'string') {
47
- errors.push('name must be a string');
48
- }
49
- else if (!isValidRegistrationName(name)) {
50
- errors.push('name must be kebab-case (lowercase letter start, alphanumeric + hyphens)');
51
- }
52
- return { isValid: errors.length === 0, errors };
53
- }
54
- /** Validate an environment variable name. */
55
- export function validateEnvVarName(name) {
56
- const errors = [];
57
- if (typeof name !== 'string') {
58
- errors.push('env var name must be a string');
59
- }
60
- else if (!isValidEnvVarName(name)) {
61
- errors.push('env var name must start with letter/underscore, followed by alphanumeric/underscore');
62
- }
63
- return { isValid: errors.length === 0, errors };
64
- }
65
- /** Validate an endpoint URL. */
66
- export function validateEndpoint(endpoint) {
67
- const errors = [];
68
- if (typeof endpoint !== 'string') {
69
- errors.push('endpoint must be a string');
70
- }
71
- else if (!isValidEndpoint(endpoint)) {
72
- errors.push('endpoint must be an absolute https:// URL (http:// permitted for loopback hosts only; no credentials or fragment)');
73
- }
74
- return { isValid: errors.length === 0, errors };
75
- }
76
- /** Normalize endpoint URL to a canonical form (remove trailing slash if present). */
77
- export function normalizeEndpoint(endpoint) {
78
- try {
79
- const url = new URL(endpoint);
80
- // Reconstruct without trailing slash on path
81
- let pathname = url.pathname;
82
- if (pathname.length > 1 && pathname.endsWith('/')) {
83
- pathname = pathname.slice(0, -1);
84
- }
85
- return `${url.protocol}//${url.host}${pathname}${url.search}`;
86
- }
87
- catch {
88
- return endpoint;
89
- }
90
- }
91
- /** List every EFFECTIVE configured CLI visible across scopes (project nearest-first,
92
- * then user). Dedupes by name: the first-seen registration for a name wins (nearer
93
- * project root, then user) and any same-name registration in a farther scope is
94
- * omitted entirely — there is no shadowed-copy representation and no `effective`
95
- * field on `ConfiguredCliRegistration`. */
96
- export function listConfiguredCliRegistrations() {
97
- const out = [];
98
- const seen = new Set();
99
- // Project scopes, nearest-first (projectScopeRoots is already nearest-first).
100
- // Read EACH root's own config directly and address its own cache dir, so a
101
- // nested/profile-widened stack keeps nearest-project precedence (first-seen
102
- // wins) instead of collapsing every root onto the nearest config. `root` is
103
- // the `configured-clis/` cache parent (matching resolveConfiguredCli and
104
- // configuredCliCacheDir), never the bare scope-root.
105
- for (const scopeRootDir of projectScopeRoots()) {
106
- const config = readConfigAtRoot(scopeRootDir);
107
- const root = configuredClisDirForRoot(scopeRootDir);
108
- for (const [name, entry] of Object.entries(config.configuredClis)) {
109
- if (seen.has(name))
110
- continue;
111
- out.push({
112
- name,
113
- endpoint: entry.endpoint,
114
- authEnvVar: entry.authEnvVar,
115
- scope: 'project',
116
- root,
117
- });
118
- seen.add(name);
119
- }
120
- }
121
- // Then user scope (lowest precedence, so add unseen ones).
122
- const userConfig = readConfig('user');
123
- const userRoot = configuredClisDirForRoot(userScopeRoot());
124
- for (const [name, entry] of Object.entries(userConfig.configuredClis)) {
125
- if (seen.has(name))
126
- continue;
127
- out.push({
128
- name,
129
- endpoint: entry.endpoint,
130
- authEnvVar: entry.authEnvVar,
131
- scope: 'user',
132
- root: userRoot,
133
- });
134
- seen.add(name);
135
- }
136
- return out;
137
- }
138
- /** Get deduplicated effective registrations (one per unique name, project wins over user). */
139
- export function effectiveConfiguredClis() {
140
- return listConfiguredCliRegistrations();
141
- }
142
- /** Resolve a configured CLI registration by name. If scope is omitted, returns the
143
- * effective winner (project > user). If scope is explicit, returns the registration
144
- * in that scope or null if not found. */
145
- export function resolveConfiguredCli(name, scope) {
146
- const all = listConfiguredCliRegistrations();
147
- if (scope === undefined) {
148
- // Return effective winner (first in list after precedence ordering)
149
- return all.find((r) => r.name === name) ?? null;
150
- }
151
- if (scope !== 'project' && scope !== 'user') {
152
- return null; // builtin scope is not applicable
153
- }
154
- if (scope === 'project') {
155
- // Walk every visible project root (nearest-first), matching the same
156
- // precedence `listConfiguredCliRegistrations` uses — an explicit
157
- // `--scope project` must find a registration in a farther project root,
158
- // not only the nearest one.
159
- for (const scopeRootDir of projectScopeRoots()) {
160
- const config = readConfigAtRoot(scopeRootDir);
161
- const entry = config.configuredClis[name];
162
- if (!entry)
163
- continue;
164
- return {
165
- name,
166
- endpoint: entry.endpoint,
167
- authEnvVar: entry.authEnvVar,
168
- scope: 'project',
169
- root: configuredClisDirForRoot(scopeRootDir),
170
- };
171
- }
172
- return null;
173
- }
174
- // user scope: single root.
175
- const config = readConfig('user');
176
- const entry = config.configuredClis[name];
177
- if (!entry)
178
- return null;
179
- const root = configuredClisDir('user');
180
- if (!root)
181
- return null;
182
- return {
183
- name,
184
- endpoint: entry.endpoint,
185
- authEnvVar: entry.authEnvVar,
186
- scope: 'user',
187
- root,
188
- };
189
- }
190
- /** Get the cache directory for a configured CLI registration. */
191
- export function configuredCliCacheDir(registration) {
192
- return join(registration.root, registration.name);
193
- }
194
- /** Get the manifest file path for a configured CLI. */
195
- export function configuredCliManifestPath(registration) {
196
- return join(configuredCliCacheDir(registration), 'manifest.json');
197
- }
198
- /** Get the cache metadata file path for a configured CLI. */
199
- export function configuredCliCacheMetaPath(registration) {
200
- return join(configuredCliCacheDir(registration), 'meta.json');
201
- }