opensip-cli 0.1.2 → 0.1.3

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 (56) hide show
  1. package/dist/bootstrap/bind-tool-context.d.ts +16 -0
  2. package/dist/bootstrap/bind-tool-context.d.ts.map +1 -0
  3. package/dist/bootstrap/bind-tool-context.js +160 -0
  4. package/dist/bootstrap/bind-tool-context.js.map +1 -0
  5. package/dist/bootstrap/build-per-run-scope.d.ts.map +1 -1
  6. package/dist/bootstrap/build-per-run-scope.js +35 -2
  7. package/dist/bootstrap/build-per-run-scope.js.map +1 -1
  8. package/dist/bootstrap/execute-post-bailout-bootstrap.d.ts +45 -0
  9. package/dist/bootstrap/execute-post-bailout-bootstrap.d.ts.map +1 -0
  10. package/dist/bootstrap/execute-post-bailout-bootstrap.js +108 -0
  11. package/dist/bootstrap/execute-post-bailout-bootstrap.js.map +1 -0
  12. package/dist/bootstrap/plan-pre-action-bootstrap.d.ts +45 -0
  13. package/dist/bootstrap/plan-pre-action-bootstrap.d.ts.map +1 -0
  14. package/dist/bootstrap/plan-pre-action-bootstrap.js +86 -0
  15. package/dist/bootstrap/plan-pre-action-bootstrap.js.map +1 -0
  16. package/dist/bootstrap/pre-action-bootstrap-phases.d.ts +20 -0
  17. package/dist/bootstrap/pre-action-bootstrap-phases.d.ts.map +1 -0
  18. package/dist/bootstrap/pre-action-bootstrap-phases.js +25 -0
  19. package/dist/bootstrap/pre-action-bootstrap-phases.js.map +1 -0
  20. package/dist/bootstrap/pre-action-hook.d.ts +6 -66
  21. package/dist/bootstrap/pre-action-hook.d.ts.map +1 -1
  22. package/dist/bootstrap/pre-action-hook.js +22 -266
  23. package/dist/bootstrap/pre-action-hook.js.map +1 -1
  24. package/dist/bootstrap/pre-action-runtime.d.ts +9 -0
  25. package/dist/bootstrap/pre-action-runtime.d.ts.map +1 -0
  26. package/dist/bootstrap/pre-action-runtime.js +2 -0
  27. package/dist/bootstrap/pre-action-runtime.js.map +1 -0
  28. package/dist/bootstrap/register-tools-bundled.d.ts +28 -0
  29. package/dist/bootstrap/register-tools-bundled.d.ts.map +1 -0
  30. package/dist/bootstrap/register-tools-bundled.js +107 -0
  31. package/dist/bootstrap/register-tools-bundled.js.map +1 -0
  32. package/dist/bootstrap/register-tools-discovery.d.ts +154 -0
  33. package/dist/bootstrap/register-tools-discovery.d.ts.map +1 -0
  34. package/dist/bootstrap/register-tools-discovery.js +385 -0
  35. package/dist/bootstrap/register-tools-discovery.js.map +1 -0
  36. package/dist/bootstrap/register-tools-mount.d.ts +25 -0
  37. package/dist/bootstrap/register-tools-mount.d.ts.map +1 -0
  38. package/dist/bootstrap/register-tools-mount.js +91 -0
  39. package/dist/bootstrap/register-tools-mount.js.map +1 -0
  40. package/dist/bootstrap/register-tools-shared.d.ts +40 -0
  41. package/dist/bootstrap/register-tools-shared.d.ts.map +1 -0
  42. package/dist/bootstrap/register-tools-shared.js +98 -0
  43. package/dist/bootstrap/register-tools-shared.js.map +1 -0
  44. package/dist/bootstrap/register-tools.d.ts +4 -196
  45. package/dist/bootstrap/register-tools.d.ts.map +1 -1
  46. package/dist/bootstrap/register-tools.js +4 -668
  47. package/dist/bootstrap/register-tools.js.map +1 -1
  48. package/dist/commands/mount-command-spec.d.ts +2 -1
  49. package/dist/commands/mount-command-spec.d.ts.map +1 -1
  50. package/dist/commands/mount-command-spec.js +3 -6
  51. package/dist/commands/mount-command-spec.js.map +1 -1
  52. package/dist/env/host-env-specs.d.ts +4 -3
  53. package/dist/env/host-env-specs.d.ts.map +1 -1
  54. package/dist/env/host-env-specs.js +8 -3
  55. package/dist/env/host-env-specs.js.map +1 -1
  56. package/package.json +32 -32
@@ -0,0 +1,154 @@
1
+ import { type ToolPluginManifest, type ToolProvenance, type ToolRegistry, type ToolDiscoverySource } from '@opensip-cli/core';
2
+ export interface DiscoveryOptions {
3
+ /**
4
+ * Ordered tool-discovery sources (precedence: first wins on duplicate
5
+ * name). Built by {@link buildToolDiscoverySources} at the composition
6
+ * root; passed in here so this function reads no ambient HOME/cwd state
7
+ * and stays unit-testable with explicit anchors.
8
+ */
9
+ readonly sources: readonly ToolDiscoverySource[];
10
+ }
11
+ /**
12
+ * Build the ordered tool-discovery sources. Order is precedence
13
+ * (first-occurrence-wins on duplicate name):
14
+ *
15
+ * 1. project-local `.runtime/plugins/tool` — `plugin add --project`
16
+ * 2. project tree (walk up from cwd) — plain `npm install @tool`
17
+ * 3. user-global `~/.opensip-cli/plugins/tool` — `plugin add` (default)
18
+ * 4. CLI install dir (walk up) — `npm i -g @tool`
19
+ *
20
+ * A project-local pin therefore shadows a user-global install of the same
21
+ * tool. Project-root resolution is best-effort: an unresolvable context
22
+ * (e.g. running outside any project) simply contributes no `.runtime`
23
+ * source.
24
+ */
25
+ export declare function buildToolDiscoverySources(cwd: string, cliInstallDir: string): ToolDiscoverySource[];
26
+ /**
27
+ * Discover and register third-party tool packages from npm — any
28
+ * `package.json` declaring `opensipTools.kind === 'tool'`. Built-in
29
+ * ids are skipped to avoid double-registration warnings. Discovery spans
30
+ * the supplied sources (the user-global tool host dir, the project tree +
31
+ * its `.runtime` tool host dir, and the CLI install dir — see
32
+ * {@link buildToolDiscoverySources}).
33
+ *
34
+ * Each discovered package runs through the SAME `admitTool` gate the
35
+ * bundled path uses (launch, Phase 3) BEFORE its module is imported:
36
+ * the static `package.json#opensipTools` manifest is read with source
37
+ * `'installed'`, the compatibility gate runs, and only an `admit` verdict
38
+ * proceeds to import + register. An installed tool is best-effort
39
+ * `explicitlyRequested: false`, so an incompatible one `skip`s (logged)
40
+ * rather than failing the whole CLI — a stray incompatible plugin must not
41
+ * take fit/graph/sim down. Admitted tools' `ToolProvenance` is pushed onto
42
+ * the optional `provenance` collector for Phase 4's `plugin list`.
43
+ *
44
+ * @param provenance Optional sink for admitted tools' provenance records.
45
+ */
46
+ export declare function discoverAndRegisterToolPackages(registry: ToolRegistry, opts: DiscoveryOptions, builtInIds: ReadonlySet<string>, provenance?: ToolProvenance[], manifests?: ToolPluginManifest[]): Promise<void>;
47
+ /**
48
+ * The outcome of admitting a tool — the recorded `ToolProvenance` plus the
49
+ * loaded `ToolPluginManifest`. Returned by the authored legs
50
+ * ({@link admitUserGlobalTool} / {@link admitProjectLocalTool}) and the
51
+ * installed leg ({@link admitInstalledTool}) alike. The manifest is returned
52
+ * (not re-read) so the register step can run the drift guard
53
+ * (`assertManifestMatchesTool`) against the imported runtime and seed the
54
+ * per-run capability registry, without a second filesystem read.
55
+ */
56
+ export interface AuthoredAdmission {
57
+ readonly provenance: ToolProvenance;
58
+ readonly manifest: ToolPluginManifest;
59
+ }
60
+ /**
61
+ * Admit (or reject) a single PROJECT-LOCAL authored tool under the
62
+ * deny-by-default trust policy (launch, Phase 3 Task 3.2; wired into
63
+ * production discovery in the launch contract).
64
+ *
65
+ * A project-local tool is authored code under
66
+ * `<project>/opensip-cli/tools/<name>/` declaring its identity via a JSON
67
+ * sidecar (`opensip-tool.manifest.json`). It is read + gated WITHOUT importing
68
+ * its module:
69
+ *
70
+ * 1. `loadToolManifest('project-local', dir)` — identity only, no code run.
71
+ * 2. Trust check — {@link isProjectLocalToolTrusted}. Not allowlisted ⇒
72
+ * throw {@link PluginIncompatibleError} (fail-closed, exit 5) before any
73
+ * import. Allowlisted ⇒ run the shared compatibility tail; an incompatible
74
+ * explicitly-trusted tool is likewise fail-closed.
75
+ *
76
+ * Returns the admitted tool's `{ provenance, manifest }` on success. The trust
77
+ * decision always precedes import (it is the FIRST statement here, ahead of the
78
+ * shared {@link admitAuthoredTool} tail).
79
+ *
80
+ * @throws {PluginIncompatibleError} when the tool has no conformant sidecar
81
+ * manifest, is not allowlisted, or is compatibility-incompatible.
82
+ */
83
+ export declare function admitProjectLocalTool(args: {
84
+ readonly dir: string;
85
+ readonly env?: NodeJS.ProcessEnv;
86
+ }): AuthoredAdmission;
87
+ /**
88
+ * Admit a single USER-GLOBAL authored tool — the trusted-by-default sibling of
89
+ * {@link admitProjectLocalTool}.
90
+ *
91
+ * A user-global tool is an authored sidecar under
92
+ * `~/.opensip-cli/tools/<name>/`. The user deliberately placed it in their
93
+ * own home dir (the `npm i -g` analogue for authored code), so there is **no
94
+ * allowlist gate** — it is trusted-by-default. It still reads the static
95
+ * sidecar and runs `admitTool` BEFORE the module could be imported (the shared
96
+ * {@link admitAuthoredTool} tail), so trust-before-import holds for this leg
97
+ * too: a global tool the user explicitly authored is fail-closed on a
98
+ * missing/incompatible manifest, never a silent skip.
99
+ *
100
+ * @throws {PluginIncompatibleError} when the tool has no conformant sidecar
101
+ * manifest or is compatibility-incompatible.
102
+ */
103
+ export declare function admitUserGlobalTool(args: {
104
+ readonly dir: string;
105
+ }): AuthoredAdmission;
106
+ /**
107
+ * Discover + admit + register AUTHORED Tool sidecars from the two authored
108
+ * roots, then dynamic-import each admitted runtime through the shared
109
+ * `importToolRuntime` seam — the same admit → import → register path the
110
+ * bundled and installed legs travel (ADR-0027; this is the leg that makes the
111
+ * dormant {@link admitProjectLocalTool} live).
112
+ *
113
+ * Two roots, two trust postures:
114
+ * - **global** (`~/.opensip-cli/tools/`) → {@link admitUserGlobalTool},
115
+ * trusted-by-default.
116
+ * - **project** (`<project>/opensip-cli/tools/`) → {@link admitProjectLocalTool},
117
+ * deny-by-default (allowlist via `OPENSIP_CLI_ALLOW_PROJECT_TOOLS`).
118
+ *
119
+ * Global is processed FIRST so a project-authored tool cannot shadow a same-id
120
+ * global one — matching the `~/.opensip-cli/plugins` precedence note in
121
+ * {@link buildToolDiscoverySources} (first-writer-wins via the registry).
122
+ * `builtInIds` are skipped so an authored tool never shadows a bundled one.
123
+ *
124
+ * **Trust-before-import.** For each candidate, the admit step (which EMBEDS the
125
+ * trust decision — deny-by-default inside `admitProjectLocalTool`) runs to
126
+ * completion BEFORE `importToolRuntime`. A non-allowlisted project tool THROWS
127
+ * `PluginIncompatibleError` (exit 5) here, propagated out of the walk: it must
128
+ * fail the run loudly — that is the clone-protection contract.
129
+ *
130
+ * **Error-posture asymmetry (deliberate).** An un-allowlisted *project* tool is
131
+ * fail-closed by policy (clone-risk; the user must opt in). A *global* tool that
132
+ * fails to load is also fail-closed (the user explicitly authored it into
133
+ * `$HOME`). This differs from the *installed* npm leg, where a stray bad plugin
134
+ * skips-with-diagnostic so it can't take fit/graph/sim down — authored tools are
135
+ * first-party-intent, installed tools are ambient.
136
+ *
137
+ * @param registry The per-invocation tool registry to populate.
138
+ * @param opts.projectAuthoredDir `resolveProjectPaths(root).authoredToolsDir`,
139
+ * or `undefined` when there is no resolvable project context.
140
+ * @param opts.globalAuthoredDir `resolveUserPaths().authoredToolsDir`.
141
+ * @param opts.env Environment carrying the project allowlist (default
142
+ * `process.env`); injectable for tests.
143
+ * @param builtInIds Bundled-tool ids to skip on a name collision.
144
+ * @param provenance Sink for admitted authored tools' provenance records.
145
+ * @param manifests Sink for admitted authored tools' manifests (§5.3).
146
+ * @throws {PluginIncompatibleError} for an un-allowlisted project tool, or any
147
+ * authored tool whose sidecar/runtime is missing/incompatible (fail-closed).
148
+ */
149
+ export declare function discoverAndRegisterAuthoredTools(registry: ToolRegistry, opts: {
150
+ readonly projectAuthoredDir?: string;
151
+ readonly globalAuthoredDir: string;
152
+ readonly env?: NodeJS.ProcessEnv;
153
+ }, builtInIds: ReadonlySet<string>, provenance?: ToolProvenance[], manifests?: ToolPluginManifest[]): Promise<void>;
154
+ //# sourceMappingURL=register-tools-discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-tools-discovery.d.ts","sourceRoot":"","sources":["../../src/bootstrap/register-tools-discovery.ts"],"names":[],"mappings":"AAEA,OAAO,EAYL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAM3B,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAClD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,mBAAmB,EAAE,CAqBvB;AA6FD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,gBAAgB,EACtB,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,EAC/B,UAAU,GAAE,cAAc,EAAO,EACjC,SAAS,GAAE,kBAAkB,EAAO,GACnC,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;CACvC;AAgDD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAClC,GAAG,iBAAiB,CAuBpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,iBAAiB,CAGrF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE;IACJ,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAClC,EACD,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,EAC/B,UAAU,GAAE,cAAc,EAAO,EACjC,SAAS,GAAE,kBAAkB,EAAO,GACnC,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
@@ -0,0 +1,385 @@
1
+ // @fitness-ignore-file performance-anti-patterns -- sequential await across discovered tool packages preserves load order for plugin-conflict detection; bounded by installed plugin count
2
+ // @fitness-ignore-file file-length-limit -- discovery admission remains one policy unit after the register-tools split; below the hard limit and slated for a later source-boundary split.
3
+ import { admitTool, assertManifestMatchesTool, discoverAuthoredToolSidecars, discoverToolPackagesFromAnchors, loadToolManifest, logger, PluginIncompatibleError, PROJECT_LOCAL_MANIFEST_FILE, resolveProjectContext, resolveProjectPaths, resolveUserPaths, } from '@opensip-cli/core';
4
+ import { importToolRuntime } from './admit-tool-package.js';
5
+ import { BOOTSTRAP_MODULE } from './register-tools-shared.js';
6
+ import { isProjectLocalToolTrusted } from './tool-trust.js';
7
+ /**
8
+ * Build the ordered tool-discovery sources. Order is precedence
9
+ * (first-occurrence-wins on duplicate name):
10
+ *
11
+ * 1. project-local `.runtime/plugins/tool` — `plugin add --project`
12
+ * 2. project tree (walk up from cwd) — plain `npm install @tool`
13
+ * 3. user-global `~/.opensip-cli/plugins/tool` — `plugin add` (default)
14
+ * 4. CLI install dir (walk up) — `npm i -g @tool`
15
+ *
16
+ * A project-local pin therefore shadows a user-global install of the same
17
+ * tool. Project-root resolution is best-effort: an unresolvable context
18
+ * (e.g. running outside any project) simply contributes no `.runtime`
19
+ * source.
20
+ */
21
+ export function buildToolDiscoverySources(cwd, cliInstallDir) {
22
+ const sources = [];
23
+ try {
24
+ const project = resolveProjectContext({ cwd, cwdExplicit: false });
25
+ if (project.scope === 'project') {
26
+ sources.push({
27
+ dir: resolveProjectPaths(project.projectRoot).pluginsDir('tool'),
28
+ mode: 'scanDir',
29
+ });
30
+ }
31
+ }
32
+ catch {
33
+ // @swallow-ok no resolvable project context (e.g. running outside any
34
+ // project) → contribute no project-local tool source. Best-effort by
35
+ // documented contract; see the JSDoc on buildToolDiscoverySources.
36
+ }
37
+ sources.push({ dir: cwd, mode: 'walkUp' }, { dir: resolveUserPaths().pluginsDir('tool'), mode: 'scanDir' }, { dir: cliInstallDir, mode: 'walkUp' });
38
+ return sources;
39
+ }
40
+ /**
41
+ * Run the admission gate over a discovered INSTALLED tool package
42
+ * before its module is imported. Reads the static
43
+ * `package.json#opensipTools` manifest and runs the shared `admitTool` gate
44
+ * (source `'installed'`, best-effort `explicitlyRequested: false` so an
45
+ * incompatible installed tool skips rather than failing the whole CLI).
46
+ *
47
+ * Returns:
48
+ * - `undefined` — skip this package (no conformant manifest, gate skipped
49
+ * it, or its id collides with a built-in). The reason is logged.
50
+ * - the admission `{ provenance, manifest }` — the manifest is conformant +
51
+ * compatible; the caller continues to import + register, and records the
52
+ * provenance/manifest only AFTER the runtime actually registered (so
53
+ * `plugin list` and the capability registry never see a tool whose import
54
+ * subsequently failed — matching the bundled/authored legs).
55
+ *
56
+ * Public launch: the grace window ended. A discovered `kind:'tool'` package whose
57
+ * manifest is missing/malformed (`loadToolManifest` → undefined) or declares no
58
+ * `apiVersion` (`admitTool` → skip via {@link checkCompatibility}) is no longer
59
+ * admitted off the marker alone — it is rejected with a diagnostic.
60
+ */
61
+ function admitInstalledTool(pkg, builtInIds) {
62
+ const manifest = loadToolManifest('installed', pkg.packageDir);
63
+ if (manifest === undefined) {
64
+ // Launch: a discovered tool with no conformant manifest is no longer admitted
65
+ // off the `kind:'tool'` marker alone (the grace window ended) — skip it.
66
+ process.stderr.write(`opensip: tool package ${pkg.name} has no conformant package.json#opensipTools manifest — skipping\n`);
67
+ logger.warn({
68
+ evt: 'cli.tool.manifest_invalid',
69
+ module: BOOTSTRAP_MODULE,
70
+ name: pkg.name,
71
+ });
72
+ return undefined;
73
+ }
74
+ if (builtInIds.has(manifest.id))
75
+ return undefined; // builtInIds are human ids from manifests (compat)
76
+ const result = admitTool({
77
+ manifest,
78
+ source: 'installed',
79
+ dir: pkg.packageDir,
80
+ packageName: pkg.name,
81
+ // Best-effort: discovery alone can't tell whether THIS run targets this
82
+ // tool's command, so default false → incompatible installed tools skip.
83
+ explicitlyRequested: false,
84
+ });
85
+ if (result.decision !== 'admit')
86
+ return undefined;
87
+ return { provenance: result.provenance, manifest: result.manifest };
88
+ }
89
+ /**
90
+ * Emit the best-effort stderr line + structured warning for a discovered
91
+ * INSTALLED tool whose runtime failed to load. Each `ToolRuntimeLoad` failure
92
+ * reason maps to its own message + event (preserving the admission diagnostics) —
93
+ * an installed tool's load failure skips it, never crashing the CLI.
94
+ */
95
+ function emitInstalledLoadFailure(name, load) {
96
+ if (load.reason === 'no-entry') {
97
+ process.stderr.write(`opensip: tool package ${name} has no resolvable entry point — skipping\n`);
98
+ logger.warn({ evt: 'cli.tool.no_entry', module: BOOTSTRAP_MODULE, name });
99
+ return;
100
+ }
101
+ if (load.reason === 'invalid-shape') {
102
+ process.stderr.write(`opensip: tool package ${name} does not export a valid \`tool\` — skipping\n`);
103
+ logger.warn({
104
+ evt: 'cli.tool.invalid_shape',
105
+ module: BOOTSTRAP_MODULE,
106
+ name,
107
+ });
108
+ return;
109
+ }
110
+ process.stderr.write(`opensip: failed to load tool ${name}: ${load.detail ?? 'import failed'}\n`);
111
+ logger.warn({
112
+ evt: 'cli.tool.load_failed',
113
+ module: BOOTSTRAP_MODULE,
114
+ name,
115
+ error: load.detail,
116
+ });
117
+ }
118
+ /**
119
+ * Discover and register third-party tool packages from npm — any
120
+ * `package.json` declaring `opensipTools.kind === 'tool'`. Built-in
121
+ * ids are skipped to avoid double-registration warnings. Discovery spans
122
+ * the supplied sources (the user-global tool host dir, the project tree +
123
+ * its `.runtime` tool host dir, and the CLI install dir — see
124
+ * {@link buildToolDiscoverySources}).
125
+ *
126
+ * Each discovered package runs through the SAME `admitTool` gate the
127
+ * bundled path uses (launch, Phase 3) BEFORE its module is imported:
128
+ * the static `package.json#opensipTools` manifest is read with source
129
+ * `'installed'`, the compatibility gate runs, and only an `admit` verdict
130
+ * proceeds to import + register. An installed tool is best-effort
131
+ * `explicitlyRequested: false`, so an incompatible one `skip`s (logged)
132
+ * rather than failing the whole CLI — a stray incompatible plugin must not
133
+ * take fit/graph/sim down. Admitted tools' `ToolProvenance` is pushed onto
134
+ * the optional `provenance` collector for Phase 4's `plugin list`.
135
+ *
136
+ * @param provenance Optional sink for admitted tools' provenance records.
137
+ */
138
+ export async function discoverAndRegisterToolPackages(registry, opts, builtInIds, provenance = [], manifests = []) {
139
+ // `builtInIds` is the set of already-registered bundled-tool *human ids* (manifest.id)
140
+ // to skip on a name collision (launch — passed explicitly by the composition root, which
141
+ // derives it from the bundled MANIFESTS it just loaded; compare against runtime
142
+ // metadata.name for the human key).
143
+ const discovered = discoverToolPackagesFromAnchors(opts.sources);
144
+ for (const pkg of discovered) {
145
+ try {
146
+ // Compatibility gate BEFORE import (launch). `undefined` means the
147
+ // gate skipped it (or it's a built-in id); an admission means import +
148
+ // register as before.
149
+ const admission = admitInstalledTool(pkg, builtInIds);
150
+ if (admission === undefined)
151
+ continue;
152
+ // Load the runtime through the SHARED dynamic-import path (launch) — the
153
+ // same `importToolRuntime` the bundled path uses. Resolves the entry
154
+ // from `packageDir` so a tool living in a host dir off the CLI's own
155
+ // module-resolution path still loads. An installed tool is best-effort:
156
+ // any load failure skips-with-diagnostic (it must not take fit/graph/sim
157
+ // down), in contrast to the bundled path's fail-closed.
158
+ const load = await importToolRuntime(pkg.packageDir);
159
+ if (!load.ok) {
160
+ emitInstalledLoadFailure(pkg.name, load);
161
+ continue;
162
+ }
163
+ // builtInIds holds human ids (from bundled manifests); compare against runtime human name
164
+ if (builtInIds.has(load.tool.metadata.name ?? load.tool.metadata.id))
165
+ continue;
166
+ // Drift guard — the SAME manifest⇔runtime identity check the bundled and
167
+ // authored legs run. For an installed tool a mismatch throws into the
168
+ // surrounding catch (skip-with-diagnostic posture), never crashing the CLI.
169
+ assertManifestMatchesTool(admission.manifest, load.tool);
170
+ registry.register(load.tool, { sourcePackage: pkg.name });
171
+ // Record provenance + manifest only now that the tool actually
172
+ // registered — `plugin list` and the per-run capability registry must
173
+ // never include a tool whose runtime failed to load (parity with the
174
+ // bundled/authored legs, which also record after registration).
175
+ provenance.push(admission.provenance);
176
+ manifests.push(admission.manifest);
177
+ }
178
+ catch (error) {
179
+ const msg = error instanceof Error ? error.message : String(error);
180
+ process.stderr.write(`opensip: failed to load tool ${pkg.name}: ${msg}\n`);
181
+ logger.warn({
182
+ evt: 'cli.tool.load_failed',
183
+ module: BOOTSTRAP_MODULE,
184
+ name: pkg.name,
185
+ error: msg,
186
+ });
187
+ }
188
+ }
189
+ }
190
+ /**
191
+ * The shared admission TAIL for both authored sources.
192
+ * When `preloadedManifest` is supplied we use that snapshot (no re-read) so a
193
+ * prior trust decision (project-local) and the compat gate see the identical
194
+ * declaration. This removes the TOCTOU between the trust-bearing read and the
195
+ * gate read for the deny-by-default authored path.
196
+ *
197
+ * @throws {PluginIncompatibleError} when the sidecar is missing/malformed or
198
+ * the tool is compatibility-incompatible.
199
+ */
200
+ function admitAuthoredTool(source, dir, preloadedManifest) {
201
+ // When a preloaded manifest is supplied (project-local trust path), we use
202
+ // that exact snapshot for the compat gate. This eliminates a TOCTOU between
203
+ // the read that decided "this id is allowlisted" and the read that feeds the
204
+ // compatibility check. The later dynamic import of the .mjs still sees
205
+ // whatever is on disk at execution time (inherent for authored code).
206
+ const rawManifest = preloadedManifest ?? loadToolManifest(source, dir);
207
+ if (rawManifest === undefined) {
208
+ throw new PluginIncompatibleError(`${source} tool at '${dir}' has no conformant ${PROJECT_LOCAL_MANIFEST_FILE} sidecar`, { diagnostic: 'manifest missing or malformed' });
209
+ }
210
+ const result = admitTool({
211
+ manifest: rawManifest,
212
+ source,
213
+ dir,
214
+ // An authored tool (placed in the project tree or the user's home dir) was
215
+ // explicitly authored by the user, so an incompatible one fails the run
216
+ // rather than skipping silently.
217
+ explicitlyRequested: true,
218
+ });
219
+ if (result.decision !== 'admit') {
220
+ throw new PluginIncompatibleError(`${source} tool '${rawManifest.id}' is incompatible: ${result.diagnostic ?? 'compatibility gate rejected it'}`, { diagnostic: result.diagnostic });
221
+ }
222
+ return { provenance: result.provenance, manifest: result.manifest };
223
+ }
224
+ /**
225
+ * Admit (or reject) a single PROJECT-LOCAL authored tool under the
226
+ * deny-by-default trust policy (launch, Phase 3 Task 3.2; wired into
227
+ * production discovery in the launch contract).
228
+ *
229
+ * A project-local tool is authored code under
230
+ * `<project>/opensip-cli/tools/<name>/` declaring its identity via a JSON
231
+ * sidecar (`opensip-tool.manifest.json`). It is read + gated WITHOUT importing
232
+ * its module:
233
+ *
234
+ * 1. `loadToolManifest('project-local', dir)` — identity only, no code run.
235
+ * 2. Trust check — {@link isProjectLocalToolTrusted}. Not allowlisted ⇒
236
+ * throw {@link PluginIncompatibleError} (fail-closed, exit 5) before any
237
+ * import. Allowlisted ⇒ run the shared compatibility tail; an incompatible
238
+ * explicitly-trusted tool is likewise fail-closed.
239
+ *
240
+ * Returns the admitted tool's `{ provenance, manifest }` on success. The trust
241
+ * decision always precedes import (it is the FIRST statement here, ahead of the
242
+ * shared {@link admitAuthoredTool} tail).
243
+ *
244
+ * @throws {PluginIncompatibleError} when the tool has no conformant sidecar
245
+ * manifest, is not allowlisted, or is compatibility-incompatible.
246
+ */
247
+ export function admitProjectLocalTool(args) {
248
+ // Trust decision FIRST — deny-by-default, before any compatibility maths
249
+ // and (critically) before the tool's module could ever be imported. The id
250
+ // is read from the sidecar identity, so load the manifest once here for the
251
+ // trust check, then hand the same dir to the shared tail.
252
+ const manifest = loadToolManifest('project-local', args.dir);
253
+ if (manifest === undefined) {
254
+ throw new PluginIncompatibleError(`project-local tool at '${args.dir}' has no conformant ${PROJECT_LOCAL_MANIFEST_FILE} sidecar`, { diagnostic: 'manifest missing or malformed' });
255
+ }
256
+ if (!isProjectLocalToolTrusted(manifest.id, args.env)) {
257
+ throw new PluginIncompatibleError(`project-local tool '${manifest.id}' is not trusted to load (deny-by-default). ` +
258
+ `Allowlist it via OPENSIP_CLI_ALLOW_PROJECT_TOOLS='${manifest.id}' to admit it.`, { diagnostic: 'project-local tool not allowlisted (deny-by-default)' });
259
+ }
260
+ // Pass the manifest we just loaded (and whose id we just trusted) so the
261
+ // compat gate in the tail sees the identical declaration. Closes the
262
+ // read-re-read TOCTOU for the deny-by-default path.
263
+ return admitAuthoredTool('project-local', args.dir, manifest);
264
+ }
265
+ /**
266
+ * Admit a single USER-GLOBAL authored tool — the trusted-by-default sibling of
267
+ * {@link admitProjectLocalTool}.
268
+ *
269
+ * A user-global tool is an authored sidecar under
270
+ * `~/.opensip-cli/tools/<name>/`. The user deliberately placed it in their
271
+ * own home dir (the `npm i -g` analogue for authored code), so there is **no
272
+ * allowlist gate** — it is trusted-by-default. It still reads the static
273
+ * sidecar and runs `admitTool` BEFORE the module could be imported (the shared
274
+ * {@link admitAuthoredTool} tail), so trust-before-import holds for this leg
275
+ * too: a global tool the user explicitly authored is fail-closed on a
276
+ * missing/incompatible manifest, never a silent skip.
277
+ *
278
+ * @throws {PluginIncompatibleError} when the tool has no conformant sidecar
279
+ * manifest or is compatibility-incompatible.
280
+ */
281
+ export function admitUserGlobalTool(args) {
282
+ // No trust gate — `user-global` is trusted-by-shipping-into-$HOME.
283
+ return admitAuthoredTool('user-global', args.dir);
284
+ }
285
+ /**
286
+ * Discover + admit + register AUTHORED Tool sidecars from the two authored
287
+ * roots, then dynamic-import each admitted runtime through the shared
288
+ * `importToolRuntime` seam — the same admit → import → register path the
289
+ * bundled and installed legs travel (ADR-0027; this is the leg that makes the
290
+ * dormant {@link admitProjectLocalTool} live).
291
+ *
292
+ * Two roots, two trust postures:
293
+ * - **global** (`~/.opensip-cli/tools/`) → {@link admitUserGlobalTool},
294
+ * trusted-by-default.
295
+ * - **project** (`<project>/opensip-cli/tools/`) → {@link admitProjectLocalTool},
296
+ * deny-by-default (allowlist via `OPENSIP_CLI_ALLOW_PROJECT_TOOLS`).
297
+ *
298
+ * Global is processed FIRST so a project-authored tool cannot shadow a same-id
299
+ * global one — matching the `~/.opensip-cli/plugins` precedence note in
300
+ * {@link buildToolDiscoverySources} (first-writer-wins via the registry).
301
+ * `builtInIds` are skipped so an authored tool never shadows a bundled one.
302
+ *
303
+ * **Trust-before-import.** For each candidate, the admit step (which EMBEDS the
304
+ * trust decision — deny-by-default inside `admitProjectLocalTool`) runs to
305
+ * completion BEFORE `importToolRuntime`. A non-allowlisted project tool THROWS
306
+ * `PluginIncompatibleError` (exit 5) here, propagated out of the walk: it must
307
+ * fail the run loudly — that is the clone-protection contract.
308
+ *
309
+ * **Error-posture asymmetry (deliberate).** An un-allowlisted *project* tool is
310
+ * fail-closed by policy (clone-risk; the user must opt in). A *global* tool that
311
+ * fails to load is also fail-closed (the user explicitly authored it into
312
+ * `$HOME`). This differs from the *installed* npm leg, where a stray bad plugin
313
+ * skips-with-diagnostic so it can't take fit/graph/sim down — authored tools are
314
+ * first-party-intent, installed tools are ambient.
315
+ *
316
+ * @param registry The per-invocation tool registry to populate.
317
+ * @param opts.projectAuthoredDir `resolveProjectPaths(root).authoredToolsDir`,
318
+ * or `undefined` when there is no resolvable project context.
319
+ * @param opts.globalAuthoredDir `resolveUserPaths().authoredToolsDir`.
320
+ * @param opts.env Environment carrying the project allowlist (default
321
+ * `process.env`); injectable for tests.
322
+ * @param builtInIds Bundled-tool ids to skip on a name collision.
323
+ * @param provenance Sink for admitted authored tools' provenance records.
324
+ * @param manifests Sink for admitted authored tools' manifests (§5.3).
325
+ * @throws {PluginIncompatibleError} for an un-allowlisted project tool, or any
326
+ * authored tool whose sidecar/runtime is missing/incompatible (fail-closed).
327
+ */
328
+ export async function discoverAndRegisterAuthoredTools(registry, opts, builtInIds, provenance = [], manifests = []) {
329
+ // Global FIRST (trusted-by-default), then project (deny-by-default).
330
+ for (const candidate of discoverAuthoredToolSidecars(opts.globalAuthoredDir)) {
331
+ await admitAndRegisterAuthored({
332
+ registry,
333
+ admission: admitUserGlobalTool({ dir: candidate.dir }),
334
+ dir: candidate.dir,
335
+ builtInIds,
336
+ provenance,
337
+ manifests,
338
+ });
339
+ }
340
+ if (opts.projectAuthoredDir !== undefined) {
341
+ for (const candidate of discoverAuthoredToolSidecars(opts.projectAuthoredDir)) {
342
+ // admitProjectLocalTool embeds the deny-by-default trust gate; a
343
+ // non-allowlisted tool THROWS here, BEFORE importToolRuntime below.
344
+ await admitAndRegisterAuthored({
345
+ registry,
346
+ admission: admitProjectLocalTool({ dir: candidate.dir, env: opts.env }),
347
+ dir: candidate.dir,
348
+ builtInIds,
349
+ provenance,
350
+ manifests,
351
+ });
352
+ }
353
+ }
354
+ }
355
+ /**
356
+ * Shared register-step for an already-ADMITTED authored tool: skip a
357
+ * built-in-id collision, dynamic-import the runtime (fail-closed on failure —
358
+ * an authored tool is first-party-intent), run the manifest⇔runtime drift
359
+ * guard, register, and record provenance + manifest. Admission (incl. the trust
360
+ * decision) has already happened by the time this is called — so import here
361
+ * never precedes a trust decision.
362
+ *
363
+ * @throws {PluginIncompatibleError} when the authored tool's runtime fails to
364
+ * load via the plugin path — an authored tool is first-party-intent, so a
365
+ * load failure is fail-closed (surfaced), never silently skipped.
366
+ */
367
+ async function admitAndRegisterAuthored(args) {
368
+ const { registry, admission, dir, builtInIds, provenance, manifests } = args;
369
+ const { provenance: prov, manifest } = admission;
370
+ // Never shadow a bundled tool (defense in depth; the registry also dedupes).
371
+ if (builtInIds.has(prov.id))
372
+ return;
373
+ const load = await importToolRuntime(dir);
374
+ if (!load.ok) {
375
+ const detailSuffix = load.detail ? `: ${load.detail}` : '';
376
+ throw new PluginIncompatibleError(`${prov.source} tool '${prov.id}' failed to load via the plugin path (${load.reason}${detailSuffix})`, { diagnostic: `authored tool runtime load failed: ${load.reason}` });
377
+ }
378
+ // Drift guard: the static sidecar and the runtime tool are two declarations
379
+ // of the same identity — catch a sidecar that fell out of sync.
380
+ assertManifestMatchesTool(manifest, load.tool);
381
+ registry.register(load.tool);
382
+ provenance.push(prov);
383
+ manifests.push(manifest);
384
+ }
385
+ //# sourceMappingURL=register-tools-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-tools-discovery.js","sourceRoot":"","sources":["../../src/bootstrap/register-tools-discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type CliProgram } from '@opensip-cli/contracts';
2
+ import { type ToolCliContext, type ToolRegistry } from '@opensip-cli/core';
3
+ /**
4
+ * Walk the registry and mount each tool's commands onto `program`. This is
5
+ * **step 8** of the tool lifecycle (launch, §5.4) — see
6
+ * {@link runToolLifecycle}.
7
+ *
8
+ * Public launch: there is ONE command surface — the tool's declared `commandSpecs`,
9
+ * mounted by `mountCommandSpec`. `register()` and the raw-Commander `program`
10
+ * handle on the tool context are gone, so the host owns `program` and passes it
11
+ * in here (the tool never touches Commander). A tool with no `commandSpecs` is a
12
+ * mis-declaration: it contributes no commands, surfaced loudly via
13
+ * `cli.tool.no_command_surface`.
14
+ *
15
+ * Failures are isolated per tool — one tool whose spec fails to mount must not
16
+ * take the whole CLI down. The failure is logged + stderr-warned, then we
17
+ * continue with the next tool.
18
+ *
19
+ * @param registry The per-invocation tool registry to walk.
20
+ * @param program The root Commander program (host-owned; the composition root
21
+ * passes it — it is no longer reachable through the tool context, §8).
22
+ * @param ctx The per-invocation handler context (render/emit/scope — no program).
23
+ */
24
+ export declare function mountAllToolCommands(registry: ToolRegistry, program: CliProgram, ctx: ToolCliContext): void;
25
+ //# sourceMappingURL=register-tools-mount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-tools-mount.d.ts","sourceRoot":"","sources":["../../src/bootstrap/register-tools-mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAO9F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,cAAc,GAClB,IAAI,CAqBN"}