@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.
- package/dist/build-root.d.ts +6 -6
- package/dist/build-root.js +12 -13
- package/dist/builtin-memory/00-runtime-base.md +3 -4
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
- package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
- package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
- package/dist/builtin-memory/advisor/council.md +40 -0
- package/dist/builtin-memory/design.md +8 -11
- package/dist/builtin-memory/development.md +6 -9
- package/dist/builtin-memory/internal/INDEX.md +5 -5
- package/dist/builtin-memory/internal/agent-shaping.md +5 -7
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
- package/dist/builtin-memory/internal/marketplaces.md +13 -14
- package/dist/builtin-memory/internal/memory-loading.md +45 -0
- package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
- package/dist/builtin-memory/internal/plugins.md +61 -45
- package/dist/builtin-memory/planning.md +4 -7
- package/dist/builtin-memory/spec.md +9 -12
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
- package/dist/cli.js +1 -1
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
- package/dist/clients/attach/__tests__/context-message.test.js +31 -20
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
- package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
- package/dist/clients/attach/__tests__/edit-diff.test.js +21 -20
- package/dist/clients/attach/render/chat-view.d.ts +20 -20
- package/dist/clients/attach/render/chat-view.js +88 -89
- package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
- package/dist/clients/attach/render/condensed-history.js +60 -0
- package/dist/clients/attach/render/context-message.js +9 -14
- package/dist/clients/attach/render/crtr-output.d.ts +2 -1
- package/dist/clients/attach/render/crtr-output.js +4 -4
- package/dist/clients/attach/render/edit-diff.d.ts +21 -6
- package/dist/clients/attach/render/edit-diff.js +90 -91
- package/dist/clients/attach/render/tool-calls.d.ts +14 -18
- package/dist/clients/attach/render/tool-calls.js +66 -41
- package/dist/clients/attach/session/bindings.d.ts +5 -3
- package/dist/clients/attach/session/bindings.js +7 -13
- package/dist/clients/attach/session/keys.d.ts +2 -0
- package/dist/clients/attach/session/keys.js +36 -37
- package/dist/clients/attach/session/profile-files.d.ts +8 -0
- package/dist/clients/attach/session/profile-files.js +157 -0
- package/dist/clients/attach/viewer.js +530 -528
- package/dist/commands/memory/lint.js +1 -1
- package/dist/commands/memory/write.js +1 -1
- package/dist/commands/node.js +2 -2
- package/dist/commands/pkg/plugin-inspect.js +6 -7
- package/dist/commands/pkg/plugin-manage.d.ts +1 -1
- package/dist/commands/pkg/plugin-manage.js +131 -19
- package/dist/commands/pkg/plugin.js +2 -2
- package/dist/commands/pkg.js +6 -11
- package/dist/commands/profile/env.js +3 -3
- package/dist/commands/sys/config.js +17 -76
- package/dist/commands/sys/doctor.js +5 -91
- package/dist/commands/sys/setup-wizard.d.ts +9 -11
- package/dist/commands/sys/setup-wizard.js +47 -81
- package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
- package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
- package/dist/core/__tests__/command-plugins.test.js +36 -16
- package/dist/core/__tests__/review-model-floor.test.js +2 -2
- package/dist/core/__tests__/tmux-surface.test.js +10 -1
- package/dist/core/command-manifests/manifest.d.ts +24 -0
- package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
- package/dist/core/command-manifests/registry.d.ts +1 -2
- package/dist/core/command-manifests/registry.js +2 -2
- package/dist/core/command-manifests/schema.d.ts +11 -13
- package/dist/core/command-manifests/schema.js +53 -192
- package/dist/core/command-plugins/compose.d.ts +0 -6
- package/dist/core/command-plugins/compose.js +28 -75
- package/dist/core/command-plugins/discovery.d.ts +25 -58
- package/dist/core/command-plugins/discovery.js +152 -259
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +48 -0
- package/dist/core/command-plugins/store.d.ts +16 -0
- package/dist/core/command-plugins/store.js +64 -0
- package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
- package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
- package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
- package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
- package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
- package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
- package/dist/core/command.d.ts +8 -9
- package/dist/core/command.js +6 -9
- package/dist/core/config.js +6 -10
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/io.d.ts +1 -1
- package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
- package/dist/core/keybindings/attach-control.d.ts +37 -0
- package/dist/core/keybindings/attach-control.js +38 -0
- package/dist/core/keybindings/catalog.d.ts +5 -4
- package/dist/core/keybindings/catalog.js +16 -8
- package/dist/core/keybindings/index.d.ts +1 -0
- package/dist/core/keybindings/index.js +1 -0
- package/dist/core/keybindings/types.d.ts +1 -1
- package/dist/core/preview-registry.js +41 -74
- package/dist/core/runtime/bearings.d.ts +2 -5
- package/dist/core/runtime/bearings.js +2 -5
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +4 -3
- package/dist/core/runtime/situational-context.d.ts +1 -1
- package/dist/core/runtime/situational-context.js +1 -1
- package/dist/core/runtime/spawn.js +9 -9
- package/dist/core/runtime/tmux.js +29 -2
- package/dist/core/scope.d.ts +0 -5
- package/dist/core/scope.js +0 -10
- package/dist/core/user-settings.d.ts +193 -0
- package/dist/core/user-settings.js +252 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/pi-extensions/canvas-stophook.js +3 -2
- package/dist/pi-extensions/canvas-tool-guide.js +4 -1
- package/dist/shared/generated-context.d.ts +34 -0
- package/dist/shared/generated-context.js +98 -0
- package/dist/types.d.ts +23 -16
- package/dist/types.js +3 -14
- package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
- package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/docs/public-api.md +1 -0
- package/package.json +2 -2
- package/runtime.lock.json +6 -6
- package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
- package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
- package/dist/builtin-memory/internal/workflow-codification.md +0 -82
- package/dist/builtin-memory/product.md +0 -80
- package/dist/clients/attach/render/frozen-history.js +0 -100
- package/dist/commands/pkg/cli-inspect.d.ts +0 -17
- package/dist/commands/pkg/cli-inspect.js +0 -190
- package/dist/commands/pkg/cli-manage.d.ts +0 -3
- package/dist/commands/pkg/cli-manage.js +0 -206
- package/dist/commands/pkg/cli.d.ts +0 -1
- package/dist/commands/pkg/cli.js +0 -14
- package/dist/core/configured-clis/cache.d.ts +0 -16
- package/dist/core/configured-clis/cache.js +0 -57
- package/dist/core/configured-clis/compose.d.ts +0 -14
- package/dist/core/configured-clis/compose.js +0 -60
- package/dist/core/configured-clis/discovery.d.ts +0 -47
- package/dist/core/configured-clis/discovery.js +0 -173
- package/dist/core/configured-clis/manifest.d.ts +0 -24
- package/dist/core/configured-clis/registration.d.ts +0 -40
- package/dist/core/configured-clis/registration.js +0 -201
- 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
|
-
}
|