@try-works/dsh-model-as-tool 0.1.3 → 0.3.6
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/bin/dsh-tool-mode-credentials.mjs +20 -0
- package/bin/dsh-tool-mode.mjs +202 -0
- package/catalog/desert-ant-models.json +693 -693
- package/client/client.model-tools.js +592 -0
- package/client/index.js +1679 -0
- package/lib/adapters/index.d.ts.map +1 -1
- package/lib/adapters/index.js +4 -4
- package/lib/bindings.d.ts.map +1 -1
- package/lib/bindings.js +26 -25
- package/lib/capability.d.ts.map +1 -1
- package/lib/capability.js +17 -0
- package/lib/capture-path.d.ts +97 -0
- package/lib/capture-path.d.ts.map +1 -0
- package/lib/capture-path.js +152 -0
- package/lib/diagnostics.d.ts +166 -0
- package/lib/diagnostics.d.ts.map +1 -0
- package/lib/diagnostics.js +135 -0
- package/lib/extension-dependencies.d.ts +197 -0
- package/lib/extension-dependencies.d.ts.map +1 -0
- package/lib/extension-dependencies.js +369 -0
- package/lib/extension-dispatch.d.ts +117 -0
- package/lib/extension-dispatch.d.ts.map +1 -0
- package/lib/extension-dispatch.js +124 -0
- package/lib/extension-registrar.d.ts +100 -0
- package/lib/extension-registrar.d.ts.map +1 -0
- package/lib/extension-registrar.js +134 -0
- package/lib/extensions-api.d.ts +217 -0
- package/lib/extensions-api.d.ts.map +1 -0
- package/lib/extensions-api.js +17 -0
- package/lib/extensions-remote.d.ts +54 -0
- package/lib/extensions-remote.d.ts.map +1 -0
- package/lib/extensions-remote.js +66 -0
- package/lib/extensions-service.d.ts +334 -0
- package/lib/extensions-service.d.ts.map +1 -0
- package/lib/extensions-service.js +1351 -0
- package/lib/harness-llm.d.ts +138 -0
- package/lib/harness-llm.d.ts.map +1 -0
- package/lib/harness-llm.js +218 -0
- package/lib/harness-serving.d.ts +95 -0
- package/lib/harness-serving.d.ts.map +1 -0
- package/lib/harness-serving.js +157 -0
- package/lib/headless.d.ts +97 -0
- package/lib/headless.d.ts.map +1 -0
- package/lib/headless.js +349 -0
- package/lib/http-acquisition.d.ts +27 -0
- package/lib/http-acquisition.d.ts.map +1 -0
- package/lib/http-acquisition.js +136 -0
- package/lib/index.d.ts +250 -7
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2625 -70
- package/lib/invocation-accounting.d.ts +169 -0
- package/lib/invocation-accounting.d.ts.map +1 -0
- package/lib/invocation-accounting.js +148 -0
- package/lib/legacy-migration.d.ts +95 -0
- package/lib/legacy-migration.d.ts.map +1 -0
- package/lib/legacy-migration.js +429 -0
- package/lib/managed-workers.d.ts +151 -0
- package/lib/managed-workers.d.ts.map +1 -0
- package/lib/managed-workers.js +249 -0
- package/lib/mcp/client-grant.d.ts +74 -0
- package/lib/mcp/client-grant.d.ts.map +1 -0
- package/lib/mcp/client-grant.js +60 -0
- package/lib/mcp/credential-cli.d.ts +38 -0
- package/lib/mcp/credential-cli.d.ts.map +1 -0
- package/lib/mcp/credential-cli.js +148 -0
- package/lib/mcp/credential-store.d.ts +143 -0
- package/lib/mcp/credential-store.d.ts.map +1 -0
- package/lib/mcp/credential-store.js +454 -0
- package/lib/mcp/host-binding.d.ts +25 -0
- package/lib/mcp/host-binding.d.ts.map +1 -1
- package/lib/mcp/host-binding.js +107 -14
- package/lib/mcp/http.d.ts +68 -18
- package/lib/mcp/http.d.ts.map +1 -1
- package/lib/mcp/http.js +390 -62
- package/lib/mcp/oauth.d.ts +149 -0
- package/lib/mcp/oauth.d.ts.map +1 -0
- package/lib/mcp/oauth.js +396 -0
- package/lib/mcp/protocol.d.ts +46 -7
- package/lib/mcp/protocol.d.ts.map +1 -1
- package/lib/mcp/protocol.js +63 -16
- package/lib/mcp/rate-limit.d.ts +68 -0
- package/lib/mcp/rate-limit.d.ts.map +1 -0
- package/lib/mcp/rate-limit.js +114 -0
- package/lib/mcp/sdk-v2.d.ts +17 -0
- package/lib/mcp/sdk-v2.d.ts.map +1 -0
- package/lib/mcp/sdk-v2.js +181 -0
- package/lib/mcp/server.d.ts +11 -1
- package/lib/mcp/server.d.ts.map +1 -1
- package/lib/mcp/server.js +80 -13
- package/lib/mcp/stdio.d.ts.map +1 -1
- package/lib/mcp/stdio.js +9 -1
- package/lib/mcp/subscriptions.d.ts +43 -1
- package/lib/mcp/subscriptions.d.ts.map +1 -1
- package/lib/mcp/subscriptions.js +90 -9
- package/lib/model-qualification.d.ts +192 -0
- package/lib/model-qualification.d.ts.map +1 -0
- package/lib/model-qualification.js +178 -0
- package/lib/planning.d.ts +246 -0
- package/lib/planning.d.ts.map +1 -0
- package/lib/planning.js +457 -0
- package/lib/prepared-assets.d.ts +178 -0
- package/lib/prepared-assets.d.ts.map +1 -0
- package/lib/prepared-assets.js +286 -0
- package/lib/provider-bridge.d.ts +37 -0
- package/lib/provider-bridge.d.ts.map +1 -1
- package/lib/provider-bridge.js +189 -12
- package/lib/ptc-authority.d.ts +140 -0
- package/lib/ptc-authority.d.ts.map +1 -0
- package/lib/ptc-authority.js +216 -0
- package/lib/ptc-runtime.d.ts +156 -0
- package/lib/ptc-runtime.d.ts.map +1 -0
- package/lib/ptc-runtime.js +191 -0
- package/lib/publication.d.ts +2 -1
- package/lib/publication.d.ts.map +1 -1
- package/lib/publication.js +13 -5
- package/lib/receipt.d.ts.map +1 -1
- package/lib/receipt.js +8 -0
- package/lib/title-proposal.d.ts +194 -0
- package/lib/title-proposal.d.ts.map +1 -0
- package/lib/title-proposal.js +244 -0
- package/lib/tool-mode-run.d.ts +235 -0
- package/lib/tool-mode-run.d.ts.map +1 -0
- package/lib/tool-mode-run.js +283 -0
- package/lib/types.d.ts +3 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/update-surface.d.ts +79 -0
- package/lib/update-surface.d.ts.map +1 -0
- package/lib/update-surface.js +224 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +69 -55
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +4 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/index.js +26 -2
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +28 -2
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +93 -10
- package/node_modules/@try-works/tool-mode-contracts/package.json +0 -1
- package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +31 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +31 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +185 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
- package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
- package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
- package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
- package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
- package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
- package/package.json +43 -15
- package/remote/spec.mjs +439 -0
- package/remote/typert.host.mjs +14 -0
- package/remote/typert.remote-client.mjs +16 -0
- package/lib/adapters/tongue.d.ts +0 -31
- package/lib/adapters/tongue.d.ts.map +0 -1
- package/lib/adapters/tongue.js +0 -93
- package/lib/adapters/voz.d.ts +0 -28
- package/lib/adapters/voz.d.ts.map +0 -1
- package/lib/adapters/voz.js +0 -102
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operator command family (EXT-31).
|
|
3
|
+
*
|
|
4
|
+
* EXT-31 is explicit that these are REQUIRED NEW FUNCTIONALITY, not existing npm
|
|
5
|
+
* or DSH commands, and that offline edits must hold exclusive store ownership. So
|
|
6
|
+
* every mutating command takes the store lock, and the CLI refuses rather than
|
|
7
|
+
* racing a running host.
|
|
8
|
+
*
|
|
9
|
+
* The entry point takes argv and an output sink instead of reading `process`
|
|
10
|
+
* directly. A CLI that can only be exercised by spawning a child process is a CLI
|
|
11
|
+
* whose failure paths are tested by hand or not at all, and the failure paths here
|
|
12
|
+
* are the security-relevant ones.
|
|
13
|
+
*
|
|
14
|
+
* The management surface is deliberately NOT the inference surface: EXT-31 forbids
|
|
15
|
+
* exposing the management channel on the MCP tool surface by default, and nothing
|
|
16
|
+
* in this module registers a tool.
|
|
17
|
+
*
|
|
18
|
+
* @module @try-works/tool-mode-extension-sdk/cli
|
|
19
|
+
*/
|
|
20
|
+
import { ManagementError } from "./errors.js";
|
|
21
|
+
import { ExtensionStore } from "./install/store.js";
|
|
22
|
+
import { ExtensionLifecycle } from "./install/lifecycle.js";
|
|
23
|
+
import { commitUpdate, rollback } from "./install/update-flow.js";
|
|
24
|
+
import { summarize } from "./state.js";
|
|
25
|
+
/** The default lazy resolver: the recipes CLI beside this SDK, or nothing. */
|
|
26
|
+
async function defaultRecipeCliResolver() {
|
|
27
|
+
try {
|
|
28
|
+
const module = await import('@try-works/tool-mode-recipes/cli');
|
|
29
|
+
return { runRecipeCli: module.runRecipeCli };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The `recipes` namespace: lazy OPTIONAL forwarding to the recipes authoring
|
|
37
|
+
* CLI (RCP-08). The SDK ships no recipe semantics of its own — the same
|
|
38
|
+
* implementation runs here as under the recipes package's own bin.
|
|
39
|
+
* @param argv - the recipe arguments after the `recipes` token.
|
|
40
|
+
* @param io - output sinks.
|
|
41
|
+
* @param dependencies - the injected runner or resolver.
|
|
42
|
+
* @returns the process exit code.
|
|
43
|
+
*/
|
|
44
|
+
async function forwardRecipes(argv, io, dependencies) {
|
|
45
|
+
const runner = dependencies.recipeCliRunner
|
|
46
|
+
?? await (dependencies.resolveRecipeCli ?? defaultRecipeCliResolver)();
|
|
47
|
+
if (runner === undefined) {
|
|
48
|
+
io.err('the recipes authoring CLI is not available beside this SDK: @try-works/tool-mode-recipes is not installed. '
|
|
49
|
+
+ 'Install it, or run its own bin `tool-mode-recipe` directly. This refusal is deliberate: the SDK never '
|
|
50
|
+
+ 'pretends to author a recipe it cannot validate.');
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
// A raw throw from the forwarded runner becomes a refusal here: the SDK
|
|
54
|
+
// namespace never re-throws into the caller's stack.
|
|
55
|
+
try {
|
|
56
|
+
return await runner.runRecipeCli(argv, io);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
io.err(`the recipes CLI failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** The usage text. */
|
|
64
|
+
const USAGE = `dsh-tool-mode — manage Tool Mode extensions
|
|
65
|
+
|
|
66
|
+
extensions list list installed extensions
|
|
67
|
+
extensions inspect <id> show one extension's state
|
|
68
|
+
extensions install <spec> [--integrity sha512-…] install without activating
|
|
69
|
+
extensions activate <id> [--profile p] activate in a profile
|
|
70
|
+
extensions prepare <id> --model <model> prepare a model deployment
|
|
71
|
+
extensions deactivate <id> [--profile p] deactivate in a profile
|
|
72
|
+
extensions uninstall <id> [--deactivate] [--purge-assets]
|
|
73
|
+
extensions update <id> --to <version> commit a staged, approved update
|
|
74
|
+
(offline: pointerOnly; the host
|
|
75
|
+
reconciles admissions)
|
|
76
|
+
extensions rollback <id> [--to <version>] restore the recorded previous
|
|
77
|
+
pointer (pointer-only restore)
|
|
78
|
+
extensions purge-assets <id> refused here: purging belongs to
|
|
79
|
+
the running host's coordinator
|
|
80
|
+
tools enable <tool> [--profile p] enable one tool
|
|
81
|
+
tools disable <tool> [--profile p] disable one tool
|
|
82
|
+
recipes <…> author recipes: forwards to the
|
|
83
|
+
recipes CLI (create / edit-draft /
|
|
84
|
+
test / validate / describe / pack)
|
|
85
|
+
|
|
86
|
+
Global: --store <dir> --profile <name> --json (one JSON document: {command, exit, output, errors})
|
|
87
|
+
|
|
88
|
+
Mutating commands take exclusive ownership of the store. They refuse rather than
|
|
89
|
+
race a running host, and the management surface is never published on the
|
|
90
|
+
inference tool surface.`;
|
|
91
|
+
/**
|
|
92
|
+
* Parse argv into options.
|
|
93
|
+
*
|
|
94
|
+
* Anything unrecognised is REFUSED rather than ignored: a mistyped flag that is
|
|
95
|
+
* silently dropped turns `--deactivate` into a command the operator did not ask
|
|
96
|
+
* for, which on `uninstall` is the difference between previewing and removing.
|
|
97
|
+
* @param argv - arguments after the executable name.
|
|
98
|
+
* @returns the parsed options.
|
|
99
|
+
* @throws ManagementError when a flag is unknown or a value is missing.
|
|
100
|
+
*/
|
|
101
|
+
export function parseArgs(argv) {
|
|
102
|
+
const positional = [];
|
|
103
|
+
const flags = {};
|
|
104
|
+
const known = new Set([
|
|
105
|
+
'store', 'profile', 'integrity', 'model', 'deactivate', 'purge-assets', 'json', 'help', 'to',
|
|
106
|
+
]);
|
|
107
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
108
|
+
const token = argv[index];
|
|
109
|
+
if (!token.startsWith('--')) {
|
|
110
|
+
positional.push(token);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const name = token.slice(2);
|
|
114
|
+
if (!known.has(name)) {
|
|
115
|
+
throw new ManagementError('INVALID_EXTENSION', 'install', `unknown option ${JSON.stringify(token)}`, 'Run `dsh-tool-mode --help` to see the supported options.');
|
|
116
|
+
}
|
|
117
|
+
// The boolean flags take no value; the rest require one.
|
|
118
|
+
if (name === 'deactivate' || name === 'purge-assets' || name === 'json' || name === 'help') {
|
|
119
|
+
flags[name] = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const value = argv[index + 1];
|
|
123
|
+
if (value === undefined || value.startsWith('--')) {
|
|
124
|
+
throw new ManagementError('INVALID_EXTENSION', 'install', `option ${JSON.stringify(token)} needs a value`, `Pass it as ${token} <value>.`);
|
|
125
|
+
}
|
|
126
|
+
flags[name] = value;
|
|
127
|
+
index += 1;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
store: typeof flags.store === 'string' ? flags.store : '.tool-mode-store',
|
|
131
|
+
profile: typeof flags.profile === 'string' ? flags.profile : 'default',
|
|
132
|
+
positional,
|
|
133
|
+
flags,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Run one command.
|
|
138
|
+
*
|
|
139
|
+
* @param argv - arguments after the executable name.
|
|
140
|
+
* @param io - output sinks.
|
|
141
|
+
* @param dependencies - the registrar and installer to act with.
|
|
142
|
+
* @returns the process exit code.
|
|
143
|
+
*/
|
|
144
|
+
export async function runToolModeCli(argv, io, dependencies = {}) {
|
|
145
|
+
// The recipes namespace is forwarded RAW (no SDK-side parsing): the recipes
|
|
146
|
+
// CLI owns its own argv contract, and this file must not duplicate it. The
|
|
147
|
+
// --json wrapper applies to the forwarded namespace too.
|
|
148
|
+
if (argv[0] === 'recipes') {
|
|
149
|
+
const jsonIndex = argv.indexOf('--json');
|
|
150
|
+
if (jsonIndex === -1) {
|
|
151
|
+
return await forwardRecipes(argv.slice(1), io, dependencies);
|
|
152
|
+
}
|
|
153
|
+
const output = [];
|
|
154
|
+
const errors = [];
|
|
155
|
+
const forwarded = [...argv.slice(1, jsonIndex), ...argv.slice(jsonIndex + 1)];
|
|
156
|
+
const code = await forwardRecipes(forwarded, { out: line => output.push(line), err: line => errors.push(line) }, dependencies);
|
|
157
|
+
io.out(JSON.stringify({ command: `recipes ${forwarded.join(' ')}`, exit: code, output, errors }));
|
|
158
|
+
return code;
|
|
159
|
+
}
|
|
160
|
+
let options;
|
|
161
|
+
try {
|
|
162
|
+
options = parseArgs(argv);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
io.err(error instanceof Error ? error.message : String(error));
|
|
166
|
+
return 2;
|
|
167
|
+
}
|
|
168
|
+
if (options.flags.help === true || options.positional.length === 0) {
|
|
169
|
+
io.out(USAGE);
|
|
170
|
+
return options.positional.length === 0 && options.flags.help !== true ? 2 : 0;
|
|
171
|
+
}
|
|
172
|
+
const [group, action] = options.positional;
|
|
173
|
+
const store = new ExtensionStore(options.store);
|
|
174
|
+
const lifecycle = new ExtensionLifecycle({
|
|
175
|
+
store,
|
|
176
|
+
profile: options.profile,
|
|
177
|
+
registrar: dependencies.registrar ?? refusingRegistrar,
|
|
178
|
+
});
|
|
179
|
+
// --json: the whole command's output is captured and emitted as ONE JSON
|
|
180
|
+
// document with the exit code as the normalized status (0 success, 1 named
|
|
181
|
+
// management refusal, 2 usage/IO) — never hanging an unattended caller.
|
|
182
|
+
if (options.flags.json === true) {
|
|
183
|
+
const output = [];
|
|
184
|
+
const errors = [];
|
|
185
|
+
const code = await dispatchCli(`${group} ${action}`, options, store, lifecycle, { out: line => output.push(line), err: line => errors.push(line) }, dependencies);
|
|
186
|
+
io.out(JSON.stringify({ command: `${group} ${action}`, exit: code, output, errors }));
|
|
187
|
+
return code;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
return await dispatchCli(`${group} ${action}`, options, store, lifecycle, io, dependencies);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (error instanceof ManagementError) {
|
|
194
|
+
io.err(`${error.code}: ${error.message}`);
|
|
195
|
+
// Recovery advice is the point of the vocabulary: a code alone tells an
|
|
196
|
+
// operator what failed but not what to do about it.
|
|
197
|
+
io.err(` ${error.recovery}`);
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
io.err(error instanceof Error ? error.message : String(error));
|
|
201
|
+
return 1;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** The command dispatch, shared by the prose and the --json paths. */
|
|
205
|
+
async function dispatchCli(command, options, store, lifecycle, io, dependencies) {
|
|
206
|
+
try {
|
|
207
|
+
switch (command) {
|
|
208
|
+
case 'extensions list':
|
|
209
|
+
return listExtensions(store, lifecycle, io);
|
|
210
|
+
case 'extensions inspect':
|
|
211
|
+
return inspectExtension(store, lifecycle, options, io);
|
|
212
|
+
case 'extensions install':
|
|
213
|
+
return await installExtension(store, options, io, dependencies);
|
|
214
|
+
case 'extensions activate':
|
|
215
|
+
return await activateExtension(store, lifecycle, options, io);
|
|
216
|
+
case 'extensions prepare':
|
|
217
|
+
return prepareModel(lifecycle, options, io);
|
|
218
|
+
case 'extensions deactivate':
|
|
219
|
+
return await deactivateExtension(lifecycle, options, io);
|
|
220
|
+
case 'extensions uninstall':
|
|
221
|
+
return await uninstallExtension(lifecycle, store, options, io);
|
|
222
|
+
case 'extensions update':
|
|
223
|
+
return await updateExtension(store, options, io);
|
|
224
|
+
case 'extensions rollback':
|
|
225
|
+
return await rollbackExtension(store, options, io);
|
|
226
|
+
case 'extensions purge-assets':
|
|
227
|
+
return purgeAssets(options, io);
|
|
228
|
+
case 'tools enable':
|
|
229
|
+
return await setTool(store, lifecycle, options, io, true);
|
|
230
|
+
case 'tools disable':
|
|
231
|
+
return await setTool(store, lifecycle, options, io, false);
|
|
232
|
+
default:
|
|
233
|
+
io.err(`unknown command ${JSON.stringify(command)}`);
|
|
234
|
+
io.err(USAGE);
|
|
235
|
+
return 2;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (error instanceof ManagementError) {
|
|
240
|
+
io.err(`${error.code}: ${error.message}`);
|
|
241
|
+
io.err(` ${error.recovery}`);
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
io.err(error instanceof Error ? error.message : String(error));
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** A registrar that refuses, so activation without a host reports honestly. */
|
|
249
|
+
const refusingRegistrar = {
|
|
250
|
+
publish(contribution) {
|
|
251
|
+
throw new ManagementError('EXTENSION_INACTIVE', 'activate', `this CLI cannot publish ${contribution.name}: no running host is attached`, 'Activate through the running host, or use the host integration; the CLI does not fake publication.');
|
|
252
|
+
},
|
|
253
|
+
withdraw() { },
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* `extensions update <id> --to <v>` (EXT-31).
|
|
257
|
+
*
|
|
258
|
+
* The OFFLINE CLI path: the version must already be staged and approved
|
|
259
|
+
* (stageUpdate/recordApproval); the commit runs pointerOnly — the durable
|
|
260
|
+
* pointer moves and the running host reconciles admissions on its next
|
|
261
|
+
* lifecycle pass (the CLI never fakes a publication, EXT-31's management
|
|
262
|
+
* channel is the host's).
|
|
263
|
+
*/
|
|
264
|
+
async function updateExtension(store, options, io) {
|
|
265
|
+
const id = options.positional[2];
|
|
266
|
+
const version = options.flags.to;
|
|
267
|
+
if (id === undefined) {
|
|
268
|
+
io.err('extensions update needs an extension id');
|
|
269
|
+
return 2;
|
|
270
|
+
}
|
|
271
|
+
if (typeof version !== 'string') {
|
|
272
|
+
io.err('extensions update needs --to <version> (the staged, approved version)');
|
|
273
|
+
return 2;
|
|
274
|
+
}
|
|
275
|
+
const result = await commitUpdate({
|
|
276
|
+
storeDir: store.rootDir,
|
|
277
|
+
profile: options.profile,
|
|
278
|
+
extensionId: id,
|
|
279
|
+
stagedVersion: version,
|
|
280
|
+
pointerOnly: true,
|
|
281
|
+
});
|
|
282
|
+
io.out(`updated ${id}: active ${result.activeVersion}${result.previousVersion === undefined ? '' : ` (was ${result.previousVersion})`}`);
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
/** `extensions rollback <id> [--to <v>]` (EXT-31): pointer restore only. */
|
|
286
|
+
async function rollbackExtension(store, options, io) {
|
|
287
|
+
const id = options.positional[2];
|
|
288
|
+
if (id === undefined) {
|
|
289
|
+
io.err('extensions rollback needs an extension id');
|
|
290
|
+
return 2;
|
|
291
|
+
}
|
|
292
|
+
const requested = options.flags.to;
|
|
293
|
+
const pointers = store.readProfilePointers(options.profile);
|
|
294
|
+
if (pointers.corrupt) {
|
|
295
|
+
io.err(`the profile pointers could not be read: ${pointers.diagnostic ?? 'unknown reason'}`);
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
const previous = pointers.value.previousVersions[id];
|
|
299
|
+
if (requested !== undefined && requested !== previous) {
|
|
300
|
+
io.err(`rollback --to ${String(requested)} does not match the recorded previous version ${previous ?? '(none)'}; `
|
|
301
|
+
+ 'rollback restores the recorded previous pointer, never an arbitrary version');
|
|
302
|
+
return 1;
|
|
303
|
+
}
|
|
304
|
+
const result = await rollback({ storeDir: store.rootDir, profile: options.profile, extensionId: id });
|
|
305
|
+
io.out(`rolled back ${id}: active ${result.activeVersion}${result.hookFailure === undefined ? '' : ` (hook failure reported: ${result.hookFailure})`}`);
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* `extensions purge-assets <id>` (EXT-31).
|
|
310
|
+
*
|
|
311
|
+
* Honest refusal: asset purging is the deployment coordinator's job in the
|
|
312
|
+
* running host — the offline CLI owns no asset registry and never invents one.
|
|
313
|
+
*/
|
|
314
|
+
function purgeAssets(options, io) {
|
|
315
|
+
const id = options.positional[2];
|
|
316
|
+
if (id === undefined) {
|
|
317
|
+
io.err('extensions purge-assets needs an extension id');
|
|
318
|
+
return 2;
|
|
319
|
+
}
|
|
320
|
+
io.err(`asset purging for ${id} requires the running host's deployment coordinator; this offline CLI owns no asset registry and refuses rather than faking a purge`);
|
|
321
|
+
return 1;
|
|
322
|
+
}
|
|
323
|
+
/** `extensions list` */
|
|
324
|
+
function listExtensions(store, lifecycle, io) {
|
|
325
|
+
const lock = store.listInstalled();
|
|
326
|
+
if (lock.corrupt) {
|
|
327
|
+
// Fails closed with diagnostics rather than reporting an empty store, so an
|
|
328
|
+
// operator can tell "nothing installed" from "state unreadable".
|
|
329
|
+
io.err(`the store state could not be read: ${lock.diagnostic ?? 'unknown reason'}`);
|
|
330
|
+
return 1;
|
|
331
|
+
}
|
|
332
|
+
if (lock.value.length === 0) {
|
|
333
|
+
io.out('no extensions installed');
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
for (const record of lock.value) {
|
|
337
|
+
const state = lifecycle.stateOf(record.extensionId);
|
|
338
|
+
const summary = state === undefined ? 'not-configured' : summarize(state);
|
|
339
|
+
io.out(`${record.extensionId}@${record.version}\t${summary}\t${record.installPath}`);
|
|
340
|
+
}
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
/** `extensions inspect <id>` */
|
|
344
|
+
function inspectExtension(store, lifecycle, options, io) {
|
|
345
|
+
const id = options.positional[2];
|
|
346
|
+
if (id === undefined) {
|
|
347
|
+
io.err('extensions inspect needs an extension id');
|
|
348
|
+
return 2;
|
|
349
|
+
}
|
|
350
|
+
const record = store.listInstalled().value.find(candidate => candidate.extensionId === id);
|
|
351
|
+
if (record === undefined) {
|
|
352
|
+
io.err(`${id} is not installed`);
|
|
353
|
+
return 1;
|
|
354
|
+
}
|
|
355
|
+
const integrity = store.verifyUnchanged(record);
|
|
356
|
+
const state = lifecycle.stateOf(id);
|
|
357
|
+
if (options.flags.json === true) {
|
|
358
|
+
io.out(JSON.stringify({ record, state, unchanged: integrity.unchanged }, null, 2));
|
|
359
|
+
return 0;
|
|
360
|
+
}
|
|
361
|
+
io.out(`id ${record.extensionId}`);
|
|
362
|
+
io.out(`version ${record.version}`);
|
|
363
|
+
io.out(`installed ${record.installedAt}`);
|
|
364
|
+
io.out(`source ${record.source.kind}: ${record.source.requested}`);
|
|
365
|
+
io.out(`archive ${record.archiveDigest}`);
|
|
366
|
+
io.out(`content ${integrity.unchanged ? 'unchanged' : `CHANGED — ${integrity.diagnostic ?? ''}`}`);
|
|
367
|
+
io.out(`activation ${state === undefined ? 'not-configured' : summarize(state)}`);
|
|
368
|
+
if (state !== undefined) {
|
|
369
|
+
io.out(`desired ${state.desiredActivation}`);
|
|
370
|
+
io.out(`observed ${state.observedActivation}`);
|
|
371
|
+
io.out(`export ${state.export}`);
|
|
372
|
+
for (const tool of state.tools) {
|
|
373
|
+
io.out(`tool ${tool.toolName} ${tool.enabled ? 'enabled' : 'disabled'}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
/** `extensions install <spec>` */
|
|
379
|
+
async function installExtension(store, options, io, dependencies) {
|
|
380
|
+
const spec = options.positional[2];
|
|
381
|
+
if (spec === undefined) {
|
|
382
|
+
io.err('extensions install needs a source');
|
|
383
|
+
return 2;
|
|
384
|
+
}
|
|
385
|
+
const integrity = typeof options.flags.integrity === 'string' ? options.flags.integrity : undefined;
|
|
386
|
+
if (integrity === undefined) {
|
|
387
|
+
// EXT-12: integrity is not trust, but installing WITHOUT a declared digest
|
|
388
|
+
// means nothing can be verified later. Requiring it is the safer default.
|
|
389
|
+
io.err('extensions install requires --integrity sha512-<verified-base64>');
|
|
390
|
+
io.err(' Ask the publisher for the digest; installing unverified bytes cannot be undone later.');
|
|
391
|
+
return 2;
|
|
392
|
+
}
|
|
393
|
+
if (dependencies.install === undefined) {
|
|
394
|
+
io.err('this CLI has no installer attached; install through the host or a configured source');
|
|
395
|
+
return 1;
|
|
396
|
+
}
|
|
397
|
+
const release = store.acquireExclusive();
|
|
398
|
+
try {
|
|
399
|
+
const id = await dependencies.install(spec, integrity, options.store);
|
|
400
|
+
io.out(`installed ${id} (inactive — installation is not activation)`);
|
|
401
|
+
return 0;
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
release();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/** `extensions activate <id>` */
|
|
408
|
+
async function activateExtension(store, lifecycle, options, io) {
|
|
409
|
+
const id = options.positional[2];
|
|
410
|
+
if (id === undefined) {
|
|
411
|
+
io.err('extensions activate needs an extension id');
|
|
412
|
+
return 2;
|
|
413
|
+
}
|
|
414
|
+
// Activation names an exact installed VERSION, because the same extension may be
|
|
415
|
+
// installed twice and "activate the newest" is a guess about operator intent.
|
|
416
|
+
const installed = store.listInstalled().value.filter(record => record.extensionId === id);
|
|
417
|
+
if (installed.length === 0) {
|
|
418
|
+
io.err(`${id} is not installed`);
|
|
419
|
+
return 1;
|
|
420
|
+
}
|
|
421
|
+
if (installed.length > 1) {
|
|
422
|
+
const versions = installed.map(record => record.version).join(', ');
|
|
423
|
+
io.err(`${id} is installed at ${installed.length} versions (${versions})`);
|
|
424
|
+
io.err(' Uninstall the versions you do not want; activation will not pick one for you.');
|
|
425
|
+
return 1;
|
|
426
|
+
}
|
|
427
|
+
const result = await lifecycle.activate(id, installed[0].version);
|
|
428
|
+
io.out(`activated ${id}@${result.version} in profile ${options.profile} (generation ${result.generation})`);
|
|
429
|
+
for (const diagnostic of result.diagnostics) {
|
|
430
|
+
io.out(` ${diagnostic.code}: ${diagnostic.message}`);
|
|
431
|
+
}
|
|
432
|
+
return 0;
|
|
433
|
+
}
|
|
434
|
+
/** `extensions prepare <id> --model <model>` */
|
|
435
|
+
function prepareModel(lifecycle, options, io) {
|
|
436
|
+
const id = options.positional[2];
|
|
437
|
+
const model = typeof options.flags.model === 'string' ? options.flags.model : undefined;
|
|
438
|
+
if (id === undefined || model === undefined) {
|
|
439
|
+
io.err('extensions prepare needs an extension id and --model <model>');
|
|
440
|
+
return 2;
|
|
441
|
+
}
|
|
442
|
+
const state = lifecycle.stateOf(id);
|
|
443
|
+
if (state === undefined) {
|
|
444
|
+
io.err(`${id} is not configured in profile ${options.profile}`);
|
|
445
|
+
return 1;
|
|
446
|
+
}
|
|
447
|
+
// Preparation is the host's job and the host is not running, so this records the
|
|
448
|
+
// deployment the operator asked for rather than claiming to have fetched it.
|
|
449
|
+
io.out(`preparation requested for ${id} model ${model}`);
|
|
450
|
+
io.out(' preparation runs in the host; this command records the request only.');
|
|
451
|
+
return 0;
|
|
452
|
+
}
|
|
453
|
+
/** `extensions deactivate <id>` */
|
|
454
|
+
async function deactivateExtension(lifecycle, options, io) {
|
|
455
|
+
const id = options.positional[2];
|
|
456
|
+
if (id === undefined) {
|
|
457
|
+
io.err('extensions deactivate needs an extension id');
|
|
458
|
+
return 2;
|
|
459
|
+
}
|
|
460
|
+
const result = await lifecycle.deactivate(id);
|
|
461
|
+
if (result.fullyStopped) {
|
|
462
|
+
io.out(`deactivated ${id} in profile ${options.profile}`);
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
// EXT-27: never report a full stop while an owned worker continues.
|
|
466
|
+
io.err(`deactivated ${id}, but cleanup did not complete: ${result.cleanupFailure ?? 'unknown reason'}`);
|
|
467
|
+
io.err(' The extension is quarantined; retry once its worker has stopped.');
|
|
468
|
+
return 1;
|
|
469
|
+
}
|
|
470
|
+
/** `extensions uninstall <id>` */
|
|
471
|
+
async function uninstallExtension(lifecycle, store, options, io) {
|
|
472
|
+
const id = options.positional[2];
|
|
473
|
+
if (id === undefined) {
|
|
474
|
+
io.err('extensions uninstall needs an extension id');
|
|
475
|
+
return 2;
|
|
476
|
+
}
|
|
477
|
+
const state = lifecycle.stateOf(id);
|
|
478
|
+
const active = state?.observedActivation === 'active' || state?.desiredActivation === 'active';
|
|
479
|
+
if (active && options.flags.deactivate !== true) {
|
|
480
|
+
// The impact preview EXT-28 requires, shown BEFORE refusing so the operator
|
|
481
|
+
// knows exactly what re-running would affect.
|
|
482
|
+
io.err(`${id} is active in profile ${options.profile}`);
|
|
483
|
+
io.err('Impact preview:');
|
|
484
|
+
for (const tool of state?.tools ?? [])
|
|
485
|
+
io.err(` tool ${tool.toolName} (${tool.enabled ? 'enabled' : 'disabled'})`);
|
|
486
|
+
io.err(` profile ${options.profile}`);
|
|
487
|
+
io.err('Re-run with --deactivate to deactivate it first.');
|
|
488
|
+
return 1;
|
|
489
|
+
}
|
|
490
|
+
await store.exclusive(async () => {
|
|
491
|
+
await lifecycle.uninstall(id, { deactivate: options.flags.deactivate === true });
|
|
492
|
+
io.out(`uninstalled ${id}`);
|
|
493
|
+
if (options.flags['purge-assets'] === true) {
|
|
494
|
+
// Assets are removed by a separate, restart-retryable step (EXT-29), so this
|
|
495
|
+
// reports the request rather than implying the bytes are already gone.
|
|
496
|
+
io.out(' asset purge requested; on Windows deletion may complete after restart');
|
|
497
|
+
}
|
|
498
|
+
io.out(' a disabled tombstone is retained so the package cannot silently return');
|
|
499
|
+
});
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
/** `tools enable|disable <tool>` */
|
|
503
|
+
async function setTool(store, lifecycle, options, io, enabled) {
|
|
504
|
+
const toolName = options.positional[2];
|
|
505
|
+
if (toolName === undefined) {
|
|
506
|
+
io.err(`tools ${enabled ? 'enable' : 'disable'} needs a tool name`);
|
|
507
|
+
return 2;
|
|
508
|
+
}
|
|
509
|
+
// The tool name identifies the extension that contributes it, so the operator
|
|
510
|
+
// does not have to remember which package owns which tool.
|
|
511
|
+
const owner = lifecycle.states().find(state => state.tools.some(tool => tool.toolName === toolName));
|
|
512
|
+
if (owner === undefined) {
|
|
513
|
+
io.err(`no extension in profile ${options.profile} contributes a tool named ${toolName}`);
|
|
514
|
+
return 1;
|
|
515
|
+
}
|
|
516
|
+
// A toggle RECONCILES the published surface now, so like every other offline
|
|
517
|
+
// mutation it must hold exclusive store ownership: racing a running host's own
|
|
518
|
+
// registry mutations with none would leave the two processes disagreeing. The
|
|
519
|
+
// reentrant exclusive() is used rather than the raw lock so the lifecycle's own
|
|
520
|
+
// acquisition nests at depth 2 instead of overwriting this command's record.
|
|
521
|
+
try {
|
|
522
|
+
await store.exclusive(async () => {
|
|
523
|
+
await lifecycle.setToolEnabled(owner.extensionId, toolName, enabled);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
io.err(error instanceof Error ? error.message : String(error));
|
|
528
|
+
return 1;
|
|
529
|
+
}
|
|
530
|
+
io.out(`${enabled ? 'enabled' : 'disabled'} ${toolName} in profile ${options.profile}`);
|
|
531
|
+
return 0;
|
|
532
|
+
}
|
|
533
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving a DECLARATIVE implementation's recipe (EXT-13, RCP-01, slice 3).
|
|
3
|
+
*
|
|
4
|
+
* THE CONVENTION THIS ESTABLISHES, and why it has to exist. The pinned manifest schema's
|
|
5
|
+
* `implementations` items permit only `id`, `contractId`, `contractVersion`, `modelId` and
|
|
6
|
+
* `recipeVersion`, with `additionalProperties: false` — so **a manifest cannot name its
|
|
7
|
+
* recipe files**. There is no path field to read.
|
|
8
|
+
*
|
|
9
|
+
* What a manifest declares is a recipe VERSION. The ARTEFACT is therefore located by a
|
|
10
|
+
* convention, and until now no convention existed because nothing had ever consumed a
|
|
11
|
+
* declarative manifest: every package in this repository was `kind: "module"` and reached its
|
|
12
|
+
* behaviour through a module entry.
|
|
13
|
+
*
|
|
14
|
+
* The convention, stated once so it is not re-invented at each call site:
|
|
15
|
+
*
|
|
16
|
+
* <package>/recipes/<implementation-id>.json
|
|
17
|
+
*
|
|
18
|
+
* Chosen because the implementation id is already unique within the package, already matches
|
|
19
|
+
* `^[a-z][a-z0-9-]*$` by schema, and therefore needs no escaping or normalisation to become a
|
|
20
|
+
* file name. The alternative — keying on the contract id — would need path-mapping for the
|
|
21
|
+
* `tool-mode/` namespace and would collide when one contract has several implementations,
|
|
22
|
+
* which is exactly what `implementations` is an ARRAY to allow.
|
|
23
|
+
*
|
|
24
|
+
* @module @try-works/tool-mode-extension-sdk/declarative-recipes
|
|
25
|
+
*/
|
|
26
|
+
/** The directory the convention looks in. */
|
|
27
|
+
export declare const RECIPE_DIRECTORY = "recipes";
|
|
28
|
+
/** A resolved recipe, or the reason it could not be resolved. */
|
|
29
|
+
export type RecipeResolution = {
|
|
30
|
+
readonly ok: true;
|
|
31
|
+
readonly recipe: unknown;
|
|
32
|
+
readonly path: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly ok: false;
|
|
35
|
+
readonly reason: 'missing' | 'unreadable' | 'invalid-json';
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly detail: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Resolve one implementation's recipe by the convention.
|
|
41
|
+
*
|
|
42
|
+
* @param packageDir - the installed package root.
|
|
43
|
+
* @param implementationId - the manifest implementation id, which names the file.
|
|
44
|
+
* @returns the parsed recipe, or an honest failure.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveDeclarativeRecipe(packageDir: string, implementationId: string): RecipeResolution;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve every implementation a declarative manifest declares.
|
|
49
|
+
*
|
|
50
|
+
* All-or-nothing on purpose: a package that could satisfy HALF its declarations would be
|
|
51
|
+
* activated in a state its manifest does not describe, and a caller would have no way to
|
|
52
|
+
* learn which half. Refusing names what is missing instead.
|
|
53
|
+
*
|
|
54
|
+
* @param packageDir - the installed package root.
|
|
55
|
+
* @param implementations - the manifest's declaration list.
|
|
56
|
+
* @returns every resolved recipe, or the first failure.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveDeclarativeImplementations(packageDir: string, implementations: readonly {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly contractId: string;
|
|
61
|
+
readonly recipeVersion: string;
|
|
62
|
+
}[]): {
|
|
63
|
+
readonly ok: true;
|
|
64
|
+
readonly resolved: readonly {
|
|
65
|
+
readonly id: string;
|
|
66
|
+
readonly contractId: string;
|
|
67
|
+
readonly recipeVersion: string;
|
|
68
|
+
readonly recipe: unknown;
|
|
69
|
+
readonly path: string;
|
|
70
|
+
}[];
|
|
71
|
+
} | {
|
|
72
|
+
readonly ok: false;
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly detail: string;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Whether an installed package is declarative, and what it declares.
|
|
78
|
+
*
|
|
79
|
+
* @param packageDir - the installed package root.
|
|
80
|
+
* @returns the declared recipe names when the package is declarative, else undefined.
|
|
81
|
+
*/
|
|
82
|
+
export declare function declaredRecipeNames(packageDir: string): readonly string[] | undefined;
|
|
83
|
+
//# sourceMappingURL=declarative-recipes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declarative-recipes.d.ts","sourceRoot":"","sources":["../src/declarative-recipes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,YAAY,CAAA;AAEzC,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,cAAc,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtI;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,gBAAgB,CA6CvG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,SAAS;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,EAAE,GAC/G;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GACzL;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAcvE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAQrF"}
|