@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.
Files changed (257) hide show
  1. package/bin/dsh-tool-mode-credentials.mjs +20 -0
  2. package/bin/dsh-tool-mode.mjs +202 -0
  3. package/catalog/desert-ant-models.json +693 -693
  4. package/client/client.model-tools.js +592 -0
  5. package/client/index.js +1679 -0
  6. package/lib/adapters/index.d.ts.map +1 -1
  7. package/lib/adapters/index.js +4 -4
  8. package/lib/bindings.d.ts.map +1 -1
  9. package/lib/bindings.js +26 -25
  10. package/lib/capability.d.ts.map +1 -1
  11. package/lib/capability.js +17 -0
  12. package/lib/capture-path.d.ts +97 -0
  13. package/lib/capture-path.d.ts.map +1 -0
  14. package/lib/capture-path.js +152 -0
  15. package/lib/diagnostics.d.ts +166 -0
  16. package/lib/diagnostics.d.ts.map +1 -0
  17. package/lib/diagnostics.js +135 -0
  18. package/lib/extension-dependencies.d.ts +197 -0
  19. package/lib/extension-dependencies.d.ts.map +1 -0
  20. package/lib/extension-dependencies.js +369 -0
  21. package/lib/extension-dispatch.d.ts +117 -0
  22. package/lib/extension-dispatch.d.ts.map +1 -0
  23. package/lib/extension-dispatch.js +124 -0
  24. package/lib/extension-registrar.d.ts +100 -0
  25. package/lib/extension-registrar.d.ts.map +1 -0
  26. package/lib/extension-registrar.js +134 -0
  27. package/lib/extensions-api.d.ts +217 -0
  28. package/lib/extensions-api.d.ts.map +1 -0
  29. package/lib/extensions-api.js +17 -0
  30. package/lib/extensions-remote.d.ts +54 -0
  31. package/lib/extensions-remote.d.ts.map +1 -0
  32. package/lib/extensions-remote.js +66 -0
  33. package/lib/extensions-service.d.ts +334 -0
  34. package/lib/extensions-service.d.ts.map +1 -0
  35. package/lib/extensions-service.js +1351 -0
  36. package/lib/harness-llm.d.ts +138 -0
  37. package/lib/harness-llm.d.ts.map +1 -0
  38. package/lib/harness-llm.js +218 -0
  39. package/lib/harness-serving.d.ts +95 -0
  40. package/lib/harness-serving.d.ts.map +1 -0
  41. package/lib/harness-serving.js +157 -0
  42. package/lib/headless.d.ts +97 -0
  43. package/lib/headless.d.ts.map +1 -0
  44. package/lib/headless.js +349 -0
  45. package/lib/http-acquisition.d.ts +27 -0
  46. package/lib/http-acquisition.d.ts.map +1 -0
  47. package/lib/http-acquisition.js +136 -0
  48. package/lib/index.d.ts +250 -7
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +2625 -70
  51. package/lib/invocation-accounting.d.ts +169 -0
  52. package/lib/invocation-accounting.d.ts.map +1 -0
  53. package/lib/invocation-accounting.js +148 -0
  54. package/lib/legacy-migration.d.ts +95 -0
  55. package/lib/legacy-migration.d.ts.map +1 -0
  56. package/lib/legacy-migration.js +429 -0
  57. package/lib/managed-workers.d.ts +151 -0
  58. package/lib/managed-workers.d.ts.map +1 -0
  59. package/lib/managed-workers.js +249 -0
  60. package/lib/mcp/client-grant.d.ts +74 -0
  61. package/lib/mcp/client-grant.d.ts.map +1 -0
  62. package/lib/mcp/client-grant.js +60 -0
  63. package/lib/mcp/credential-cli.d.ts +38 -0
  64. package/lib/mcp/credential-cli.d.ts.map +1 -0
  65. package/lib/mcp/credential-cli.js +148 -0
  66. package/lib/mcp/credential-store.d.ts +143 -0
  67. package/lib/mcp/credential-store.d.ts.map +1 -0
  68. package/lib/mcp/credential-store.js +454 -0
  69. package/lib/mcp/host-binding.d.ts +25 -0
  70. package/lib/mcp/host-binding.d.ts.map +1 -1
  71. package/lib/mcp/host-binding.js +107 -14
  72. package/lib/mcp/http.d.ts +68 -18
  73. package/lib/mcp/http.d.ts.map +1 -1
  74. package/lib/mcp/http.js +390 -62
  75. package/lib/mcp/oauth.d.ts +149 -0
  76. package/lib/mcp/oauth.d.ts.map +1 -0
  77. package/lib/mcp/oauth.js +396 -0
  78. package/lib/mcp/protocol.d.ts +46 -7
  79. package/lib/mcp/protocol.d.ts.map +1 -1
  80. package/lib/mcp/protocol.js +63 -16
  81. package/lib/mcp/rate-limit.d.ts +68 -0
  82. package/lib/mcp/rate-limit.d.ts.map +1 -0
  83. package/lib/mcp/rate-limit.js +114 -0
  84. package/lib/mcp/sdk-v2.d.ts +17 -0
  85. package/lib/mcp/sdk-v2.d.ts.map +1 -0
  86. package/lib/mcp/sdk-v2.js +181 -0
  87. package/lib/mcp/server.d.ts +11 -1
  88. package/lib/mcp/server.d.ts.map +1 -1
  89. package/lib/mcp/server.js +80 -13
  90. package/lib/mcp/stdio.d.ts.map +1 -1
  91. package/lib/mcp/stdio.js +9 -1
  92. package/lib/mcp/subscriptions.d.ts +43 -1
  93. package/lib/mcp/subscriptions.d.ts.map +1 -1
  94. package/lib/mcp/subscriptions.js +90 -9
  95. package/lib/model-qualification.d.ts +192 -0
  96. package/lib/model-qualification.d.ts.map +1 -0
  97. package/lib/model-qualification.js +178 -0
  98. package/lib/planning.d.ts +246 -0
  99. package/lib/planning.d.ts.map +1 -0
  100. package/lib/planning.js +457 -0
  101. package/lib/prepared-assets.d.ts +178 -0
  102. package/lib/prepared-assets.d.ts.map +1 -0
  103. package/lib/prepared-assets.js +286 -0
  104. package/lib/provider-bridge.d.ts +37 -0
  105. package/lib/provider-bridge.d.ts.map +1 -1
  106. package/lib/provider-bridge.js +189 -12
  107. package/lib/ptc-authority.d.ts +140 -0
  108. package/lib/ptc-authority.d.ts.map +1 -0
  109. package/lib/ptc-authority.js +216 -0
  110. package/lib/ptc-runtime.d.ts +156 -0
  111. package/lib/ptc-runtime.d.ts.map +1 -0
  112. package/lib/ptc-runtime.js +191 -0
  113. package/lib/publication.d.ts +2 -1
  114. package/lib/publication.d.ts.map +1 -1
  115. package/lib/publication.js +13 -5
  116. package/lib/receipt.d.ts.map +1 -1
  117. package/lib/receipt.js +8 -0
  118. package/lib/title-proposal.d.ts +194 -0
  119. package/lib/title-proposal.d.ts.map +1 -0
  120. package/lib/title-proposal.js +244 -0
  121. package/lib/tool-mode-run.d.ts +235 -0
  122. package/lib/tool-mode-run.d.ts.map +1 -0
  123. package/lib/tool-mode-run.js +283 -0
  124. package/lib/types.d.ts +3 -1
  125. package/lib/types.d.ts.map +1 -1
  126. package/lib/update-surface.d.ts +79 -0
  127. package/lib/update-surface.d.ts.map +1 -0
  128. package/lib/update-surface.js +224 -0
  129. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
  130. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
  131. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
  132. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
  133. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
  134. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
  135. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
  136. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
  137. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
  138. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -1
  139. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +69 -55
  140. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +4 -1
  141. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -1
  142. package/node_modules/@try-works/tool-mode-contracts/lib/index.js +26 -2
  143. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +28 -2
  144. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -1
  145. package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +93 -10
  146. package/node_modules/@try-works/tool-mode-contracts/package.json +0 -1
  147. package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
  148. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
  149. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
  150. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
  151. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
  152. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
  153. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
  154. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
  155. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
  156. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
  157. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
  158. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
  159. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
  160. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +31 -0
  161. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
  162. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +31 -0
  163. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
  164. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
  165. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +185 -0
  166. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
  167. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
  168. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
  169. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
  170. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
  171. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
  172. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
  173. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
  174. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
  175. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
  176. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
  177. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
  178. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
  179. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
  180. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
  181. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
  182. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
  183. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
  184. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
  185. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
  186. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
  187. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
  188. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
  189. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
  190. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
  191. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
  192. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
  193. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
  194. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
  195. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
  196. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
  197. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
  198. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
  199. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
  200. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
  201. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
  202. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
  203. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
  204. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
  205. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
  206. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
  207. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
  208. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
  209. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
  210. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
  211. package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
  212. package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
  213. package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
  214. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
  215. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
  216. package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
  217. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
  218. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
  219. package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
  220. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
  221. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
  222. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
  223. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
  224. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
  225. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
  226. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
  227. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
  228. package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
  229. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
  230. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
  231. package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
  232. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
  233. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
  234. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
  235. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
  236. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
  237. package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
  238. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
  239. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
  240. package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
  241. package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
  242. package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
  243. package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
  244. package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
  245. package/package.json +43 -15
  246. package/remote/spec.mjs +439 -0
  247. package/remote/typert.host.mjs +14 -0
  248. package/remote/typert.remote-client.mjs +16 -0
  249. package/lib/adapters/tongue.d.ts +0 -31
  250. package/lib/adapters/tongue.d.ts.map +0 -1
  251. package/lib/adapters/tongue.js +0 -93
  252. package/lib/adapters/voz.d.ts +0 -28
  253. package/lib/adapters/voz.d.ts.map +0 -1
  254. package/lib/adapters/voz.js +0 -102
  255. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
  256. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
  257. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * The host's managed-worker registry (EXT-20, the host half).
3
+ *
4
+ * The SDK ships the mechanism — {@link ManagedExtensionWorker}, a scrubbed-environment
5
+ * child process speaking the bounded invoke protocol — and this module is the host-side
6
+ * LIFETIME discipline: ONE worker process per extension per activation GENERATION,
7
+ * reused across invocations, disposed the moment the generation advances (a
8
+ * re-activation gets a fresh process, the old one never serves another call).
9
+ *
10
+ * What stays OUT of the worker, by construction:
11
+ *
12
+ * - the grant: the host authorizes the caller BEFORE dispatch ever builds a provider,
13
+ * so `providerFor` here runs only for calls that already passed authorization, and
14
+ * the worker receives exactly `{implementationId, input}` — no grant, no caller
15
+ * identity, no configuration secrets;
16
+ * - the schemas: input validation happened host-side before the call, and the returned
17
+ * value is validated against the declared output schema by the same path that
18
+ * validates the in-process result — the worker's payload is treated as untrusted
19
+ * data exactly like an in-process provider's;
20
+ * - receipts and accounting: untouched — they record the call in the same code path.
21
+ *
22
+ * IDENTITY: the worker is keyed by the extension's TRUE MANIFEST ID, never by a path
23
+ * or a package specifier (an install path must not leak into diagnostics or collide
24
+ * with a package name). The SDK child also derives its implementation->model mapping
25
+ * from the owning package.json + the manifest beside the entry, so this registry
26
+ * STAGES a host-owned worker directory — package.json + the manifest + a junction to
27
+ * the entry's directory — when the entry's own package root does not carry them
28
+ * (a staged store copy extracts no manifest). The entry's REAL bytes are the ones the
29
+ * worker executes: the junction resolves to the verified installed copy, never a host
30
+ * rewrite.
31
+ *
32
+ * Every worker failure surfaces as the SDK's ManagementError vocabulary
33
+ * (EXTENSION_FAULT), which the dispatch path converts into a tool error; the host
34
+ * process is never affected.
35
+ *
36
+ * @module @try-works/dsh-model-as-tool/managed-workers
37
+ */
38
+ import { ManagedExtensionWorker } from '@try-works/tool-mode-extension-sdk';
39
+ import { existsSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
40
+ import { basename, dirname, join } from 'node:path';
41
+ import { tmpdir } from 'node:os';
42
+ /**
43
+ * Create the host's managed-worker registry.
44
+ * @returns the registry.
45
+ */
46
+ export function createManagedWorkerRegistry() {
47
+ const holders = new Map();
48
+ /**
49
+ * Resolve the entry the CHILD must run, staging a host-owned owning-package
50
+ * layout when the entry's own package root does not carry one (a staged store
51
+ * copy extracts no package.json/manifest, which the SDK child needs for its
52
+ * implementation->model mapping). The staged dir carries a package.json and the
53
+ * REAL manifest bytes plus a junction to the entry's directory, so the bytes the
54
+ * worker executes are exactly the verified installed copy's.
55
+ */
56
+ const resolveChildEntry = (binding) => {
57
+ // Walk up from the entry looking for the OWNING package: the nearest package.json
58
+ // that actually declares a toolMode.manifest. A store copy extracts neither, so a
59
+ // stray unrelated package.json higher up must NOT be mistaken for the owner (that
60
+ // would hand the child a package whose manifest it would then misread).
61
+ let root = dirname(binding.entryPath);
62
+ let owned = false;
63
+ for (;;) {
64
+ const candidate = join(root, 'package.json');
65
+ if (existsSync(candidate)) {
66
+ try {
67
+ const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
68
+ if (typeof pkg.toolMode?.manifest === 'string') {
69
+ owned = true;
70
+ break;
71
+ }
72
+ }
73
+ catch {
74
+ // Unreadable or malformed: not an owner.
75
+ }
76
+ }
77
+ const parent = dirname(root);
78
+ if (parent === root)
79
+ break;
80
+ root = parent;
81
+ }
82
+ if (owned)
83
+ return { entryPath: binding.entryPath, stagedDir: undefined };
84
+ if (binding.manifestPath === undefined) {
85
+ throw new Error(`the worker for ${binding.extensionId} cannot derive the model mapping: the entry's package root carries no `
86
+ + 'package.json and the host holds no manifest path for it (a staged store copy). Until the SDK lands the '
87
+ + 'host-supplied mapping API, this binding is refused rather than run with a guessed mapping.');
88
+ }
89
+ const stagedDir = join(tmpdir(), `tmw-${binding.extensionId.replace(/[^A-Za-z0-9._-]/g, '_')}-${binding.generation}-${process.pid}`);
90
+ mkdirSync(join(stagedDir, 'lib'), { recursive: true });
91
+ writeFileSync(join(stagedDir, 'package.json'), JSON.stringify({
92
+ name: binding.extensionId,
93
+ version: '0.0.0-worker',
94
+ type: 'module',
95
+ toolMode: { manifest: './tool-mode.extension.json' },
96
+ }));
97
+ // The REAL manifest bytes, so the child's identity and mapping checks run on
98
+ // exactly what the host verified.
99
+ writeFileSync(join(stagedDir, 'tool-mode.extension.json'), readFileSync(binding.manifestPath));
100
+ // A junction (Windows, no elevation) or symlink to the entry's directory — the
101
+ // child's mapping walks UP from the staged entry and finds the staged owner.
102
+ symlinkSync(dirname(binding.entryPath), join(stagedDir, 'lib'), process.platform === 'win32' ? 'junction' : 'dir');
103
+ return { entryPath: join(stagedDir, 'lib', basename(binding.entryPath)), stagedDir };
104
+ };
105
+ const bind = async (binding) => {
106
+ const current = holders.get(binding.extensionId);
107
+ if (current !== undefined && current.generation === binding.generation) {
108
+ return current;
109
+ }
110
+ // EXT-20: a generation change disposes the OLD worker before any new process is
111
+ // created — the previous generation's process never answers this generation's call.
112
+ if (current !== undefined) {
113
+ await current.worker.dispose();
114
+ if (current.stagedDir !== undefined)
115
+ rmSync(current.stagedDir, { recursive: true, force: true });
116
+ holders.delete(binding.extensionId);
117
+ }
118
+ const { entryPath, stagedDir } = resolveChildEntry(binding);
119
+ const holder = {
120
+ generation: binding.generation,
121
+ stagedDir,
122
+ worker: ManagedExtensionWorker.create({
123
+ extensionId: binding.extensionId,
124
+ entryPath,
125
+ generation: binding.generation,
126
+ ...binding.envPassthrough === undefined ? {} : { envPassthrough: binding.envPassthrough },
127
+ }),
128
+ };
129
+ holders.set(binding.extensionId, holder);
130
+ return holder;
131
+ };
132
+ return {
133
+ async providerFor(binding) {
134
+ const holder = await bind(binding);
135
+ return {
136
+ async load(request) {
137
+ // The HOST-VERIFIED prepared assets reach the child through the
138
+ // SDK's load API (host-prepared assets ONLY — nothing else is sent,
139
+ // and the child never auto-loads). An empty map reaches the child as
140
+ // an empty map: the extension's own load refuses a missing group by
141
+ // name rather than the host inventing a verification.
142
+ await holder.worker.load(request.modelId, {
143
+ assets: request.assets,
144
+ signal: request.signal,
145
+ });
146
+ },
147
+ async invoke(request) {
148
+ // The GRANT and the schemas were already enforced host-side; the worker
149
+ // receives the validated input and nothing else. No resolvePath broker is
150
+ // supplied on the dispatch path: an extension tool needing a host-authorized
151
+ // path is refused by the child's own denial rather than given an identity
152
+ // resolver (the protocol's omitted-callback rule).
153
+ return await holder.worker.invoke({
154
+ implementationId: request.implementationId,
155
+ input: request.input,
156
+ ...request.context?.signal === undefined
157
+ ? {}
158
+ : { signal: request.context.signal },
159
+ });
160
+ },
161
+ };
162
+ },
163
+ async adapterFor(binding) {
164
+ const holder = await bind(binding);
165
+ let disposed = false;
166
+ return {
167
+ async inspect(modelId, options) {
168
+ if (disposed)
169
+ throw new Error(`the managed worker for ${binding.extensionId} was disposed`);
170
+ return await holder.worker.inspect(modelId, {
171
+ ...options?.signal === undefined ? {} : { signal: options.signal },
172
+ });
173
+ },
174
+ async load(modelId, options) {
175
+ if (disposed)
176
+ throw new Error(`the managed worker for ${binding.extensionId} was disposed`);
177
+ // Host-prepared assets ONLY — the map the host verified; nothing else is
178
+ // sent to the child, and the child never auto-loads.
179
+ await holder.worker.load(modelId, {
180
+ assets: options.assets,
181
+ ...options.signal === undefined ? {} : { signal: options.signal },
182
+ });
183
+ },
184
+ async unload(modelId, options) {
185
+ if (disposed)
186
+ throw new Error(`the managed worker for ${binding.extensionId} was disposed`);
187
+ await holder.worker.unload(modelId, {
188
+ ...options?.signal === undefined ? {} : { signal: options.signal },
189
+ });
190
+ },
191
+ async invoke(request) {
192
+ if (disposed) {
193
+ throw new Error(`the managed worker for ${binding.extensionId} was disposed`);
194
+ }
195
+ return await holder.worker.invoke({
196
+ implementationId: request.implementationId,
197
+ input: request.input,
198
+ ...request.signal === undefined ? {} : { signal: request.signal },
199
+ ...request.resolvePath === undefined ? {} : { resolvePath: request.resolvePath },
200
+ });
201
+ },
202
+ async dispose() {
203
+ if (disposed)
204
+ return;
205
+ disposed = true;
206
+ await holder.worker.dispose();
207
+ if (holders.get(binding.extensionId) === holder)
208
+ holders.delete(binding.extensionId);
209
+ if (holder.stagedDir !== undefined)
210
+ rmSync(holder.stagedDir, { recursive: true, force: true });
211
+ },
212
+ pid() {
213
+ return holder.worker.pid;
214
+ },
215
+ };
216
+ },
217
+ async dispose(extensionId) {
218
+ const holder = holders.get(extensionId);
219
+ holders.delete(extensionId);
220
+ if (holder !== undefined) {
221
+ await holder.worker.dispose();
222
+ if (holder.stagedDir !== undefined)
223
+ rmSync(holder.stagedDir, { recursive: true, force: true });
224
+ }
225
+ },
226
+ capabilities() {
227
+ // The verified SDK child protocol now serves inspect, load/unload and the
228
+ // invoke resolvePath broker.
229
+ return { inspect: true, load: true };
230
+ },
231
+ bindings() {
232
+ return [...holders.entries()].map(([extensionId, holder]) => ({
233
+ extensionId,
234
+ generation: holder.generation,
235
+ pid: holder.worker.pid,
236
+ }));
237
+ },
238
+ async disposeAll() {
239
+ const live = [...holders.values()];
240
+ holders.clear();
241
+ await Promise.all(live.map(holder => holder.worker.dispose()));
242
+ for (const holder of live) {
243
+ if (holder.stagedDir !== undefined)
244
+ rmSync(holder.stagedDir, { recursive: true, force: true });
245
+ }
246
+ },
247
+ };
248
+ }
249
+ //# sourceMappingURL=managed-workers.js.map
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Per-client external grants (SEC-04, SEC-01 tails, SEC-03).
3
+ *
4
+ * Before this module, every external MCP call dispatched through the HOST'S OWN
5
+ * interactive grant: the surface's `execute` ran `ctx.tools.execute` under the operator's
6
+ * grant, with `callerKind: 'interactive'` — so an external client was the operator as far
7
+ * as authorization and receipts were concerned, and the round-65 review recorded exactly
8
+ * that as a false claim to the contrary. SEC-04 requires an ISOLATED external execution
9
+ * context per client instead.
10
+ *
11
+ * This module is the grant half of that isolation. It derives a grant per AUTHENTICATED
12
+ * client at the point where the identity is already verified — the HTTP layer, after the
13
+ * static-token or OAuth check — and the derived identity travels with the call as CALLER
14
+ * IDENTITY, never as a second dispatch path:
15
+ *
16
+ * - the DEFAULT allowed set is the live published surface (the registered contracts plus
17
+ * `tool_mode_run` — never `model_tools`, which stays host-local);
18
+ * - a per-client override narrows that set, so two clients can hold different grants;
19
+ * - the grant carries the CLIENT'S OWN `grantId` (`external:<clientId>`), recording
20
+ * policy and limits, so a receipt records who called rather than pretending the
21
+ * operator did — and a requested tool list can never extend any of it (BASE-05).
22
+ *
23
+ * @module @try-works/dsh-model-as-tool/mcp/client-grant
24
+ */
25
+ import { type GrantLimits } from '../authority.ts';
26
+ /** The per-client grant policy an operator configures. */
27
+ export interface ClientGrantConfig {
28
+ /**
29
+ * The default allowed-tools set every authenticated client receives.
30
+ *
31
+ * Absent means the LIVE published surface at request time — the surface is the
32
+ * operator's export decision, so deriving the default from it keeps one source of
33
+ * truth. A non-empty list narrows the default for every client.
34
+ */
35
+ readonly allowedTools?: readonly string[];
36
+ /** Whether the client's calls may be recorded; defaults to `receipts` (OBS-02). */
37
+ readonly recordingPolicy?: 'none' | 'receipts';
38
+ /** Resource limits; defaults are {@link DEFAULT_LIMITS}. */
39
+ readonly limits?: Partial<GrantLimits>;
40
+ /** Per-client overrides, keyed by the authenticated client identity. */
41
+ readonly perClient?: Readonly<Record<string, {
42
+ /** The client's own allowed set; absent inherits the default above. */
43
+ readonly allowedTools?: readonly string[];
44
+ readonly recordingPolicy?: 'none' | 'receipts';
45
+ readonly limits?: Partial<GrantLimits>;
46
+ }>>;
47
+ }
48
+ /**
49
+ * The authenticated caller an external request runs as (SEC-04).
50
+ *
51
+ * Pure identity plus the derived grant — nothing executable, nothing caller-supplied.
52
+ */
53
+ export interface ExternalCaller {
54
+ /** The authenticated client identity (static-token `clientId` or OAuth subject). */
55
+ readonly clientId: string;
56
+ /** The client's OWN grant identity, recorded in receipts — never the operator's. */
57
+ readonly grantId: string;
58
+ /** The tool names this client may invoke. */
59
+ readonly allowedTools: readonly string[];
60
+ readonly recordingPolicy: 'none' | 'receipts';
61
+ readonly limits: GrantLimits;
62
+ }
63
+ /**
64
+ * Derive one client's grant at authentication time.
65
+ *
66
+ * The client identity is INPUT — it must already be verified by the transport; nothing
67
+ * here authenticates, so a caller cannot reach this function with a chosen identity.
68
+ * @param clientId - the authenticated client identity.
69
+ * @param config - the operator's per-client grant policy, or undefined for the default.
70
+ * @param surfaceTools - the tool names the host currently publishes.
71
+ * @returns the derived caller identity and grant.
72
+ */
73
+ export declare function resolveClientGrant(clientId: string, config: ClientGrantConfig | undefined, surfaceTools: readonly string[]): ExternalCaller;
74
+ //# sourceMappingURL=client-grant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-grant.d.ts","sourceRoot":"","sources":["../../src/mcp/client-grant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElE,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,mFAAmF;IACnF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAC9C,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IACtC,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;QAC3C,uEAAuE;QACvE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;QACzC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;QAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;KACvC,CAAC,CAAC,CAAA;CACJ;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,UAAU,CAAA;IAC7C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAC7B;AAOD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,iBAAiB,GAAG,SAAS,EACrC,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,cAAc,CAmBhB"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Per-client external grants (SEC-04, SEC-01 tails, SEC-03).
3
+ *
4
+ * Before this module, every external MCP call dispatched through the HOST'S OWN
5
+ * interactive grant: the surface's `execute` ran `ctx.tools.execute` under the operator's
6
+ * grant, with `callerKind: 'interactive'` — so an external client was the operator as far
7
+ * as authorization and receipts were concerned, and the round-65 review recorded exactly
8
+ * that as a false claim to the contrary. SEC-04 requires an ISOLATED external execution
9
+ * context per client instead.
10
+ *
11
+ * This module is the grant half of that isolation. It derives a grant per AUTHENTICATED
12
+ * client at the point where the identity is already verified — the HTTP layer, after the
13
+ * static-token or OAuth check — and the derived identity travels with the call as CALLER
14
+ * IDENTITY, never as a second dispatch path:
15
+ *
16
+ * - the DEFAULT allowed set is the live published surface (the registered contracts plus
17
+ * `tool_mode_run` — never `model_tools`, which stays host-local);
18
+ * - a per-client override narrows that set, so two clients can hold different grants;
19
+ * - the grant carries the CLIENT'S OWN `grantId` (`external:<clientId>`), recording
20
+ * policy and limits, so a receipt records who called rather than pretending the
21
+ * operator did — and a requested tool list can never extend any of it (BASE-05).
22
+ *
23
+ * @module @try-works/dsh-model-as-tool/mcp/client-grant
24
+ */
25
+ import { DEFAULT_LIMITS } from "../authority.js";
26
+ /** Read a valid recording policy, defaulting to the observable one. */
27
+ function recordingPolicyOf(value) {
28
+ return value === 'none' ? 'none' : 'receipts';
29
+ }
30
+ /**
31
+ * Derive one client's grant at authentication time.
32
+ *
33
+ * The client identity is INPUT — it must already be verified by the transport; nothing
34
+ * here authenticates, so a caller cannot reach this function with a chosen identity.
35
+ * @param clientId - the authenticated client identity.
36
+ * @param config - the operator's per-client grant policy, or undefined for the default.
37
+ * @param surfaceTools - the tool names the host currently publishes.
38
+ * @returns the derived caller identity and grant.
39
+ */
40
+ export function resolveClientGrant(clientId, config, surfaceTools) {
41
+ const perClient = config?.perClient?.[clientId];
42
+ const requested = perClient?.allowedTools ?? config?.allowedTools ?? surfaceTools;
43
+ const onSurface = new Set(surfaceTools);
44
+ // H3: the grant is INTERSECTED with the live published surface. An override is a
45
+ // static list, and a typo in it must not silently grant a tool the operator never
46
+ // meant to name — while an override that names a tool the surface does not (yet)
47
+ // publish grants nothing for it rather than a phantom allowance. The DEFAULT
48
+ // (the surface itself) passes through the intersection unchanged.
49
+ const allowedTools = requested.filter(name => onSurface.has(name));
50
+ return {
51
+ clientId,
52
+ // The client's OWN identity: a receipt that says `external:<id>` names the caller
53
+ // without pretending the operator made the call.
54
+ grantId: `external:${clientId}`,
55
+ allowedTools,
56
+ recordingPolicy: recordingPolicyOf(perClient?.recordingPolicy ?? config?.recordingPolicy),
57
+ limits: { ...DEFAULT_LIMITS, ...config?.limits, ...perClient?.limits },
58
+ };
59
+ }
60
+ //# sourceMappingURL=client-grant.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The HUMAN-ONLY credential CLI (SEC-01).
3
+ *
4
+ * The model-visible `model_tools credentials` surface REFUSES generation: a
5
+ * generated secret must never enter a model transcript. This module is the
6
+ * operator's terminal instead — it generates over the durable store and prints
7
+ * the secret EXACTLY ONCE to stdout, with the same stream discipline as the
8
+ * stdio bridge: stdout carries the credential and nothing else; every
9
+ * diagnostic goes to stderr. The secret is never logged, never persisted
10
+ * anywhere beyond the owner-only store document, and cannot be recovered —
11
+ * a lost secret is replaced by revoking and regenerating.
12
+ *
13
+ * Usage:
14
+ * dsh-tool-mode-credentials generate --scheme static-token --client-id <id>
15
+ * dsh-tool-mode-credentials generate --scheme oauth-resource-server --client-id <id> [--scope a,b,c]
16
+ * dsh-tool-mode-credentials list [--store <path>]
17
+ * dsh-tool-mode-credentials revoke --client-id <id> [--store <path>]
18
+ *
19
+ * Exit codes: 0 done; 1 the operation failed (corrupt store, duplicate id,
20
+ * persistence failure); 2 usage.
21
+ *
22
+ * @module @try-works/dsh-model-as-tool/mcp/credential-cli
23
+ */
24
+ /** The two streams the CLI owns, so a test can capture them separately. */
25
+ export interface CliStreams {
26
+ /** The credential output — stdout in production, never anything else. */
27
+ out(line: string): void;
28
+ /** Diagnostics and usage — stderr in production. */
29
+ err(line: string): void;
30
+ }
31
+ /**
32
+ * Run the credential CLI.
33
+ * @param argv - the process arguments, without the executable.
34
+ * @param streams - the two streams (stdout/stderr in production).
35
+ * @returns the process exit code.
36
+ */
37
+ export declare function runCredentialsCli(argv: readonly string[], streams: CliStreams): number;
38
+ //# sourceMappingURL=credential-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-cli.d.ts","sourceRoot":"","sources":["../../src/mcp/credential-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AASH,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,oDAAoD;IACpD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AA0BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CA4FtF"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The HUMAN-ONLY credential CLI (SEC-01).
3
+ *
4
+ * The model-visible `model_tools credentials` surface REFUSES generation: a
5
+ * generated secret must never enter a model transcript. This module is the
6
+ * operator's terminal instead — it generates over the durable store and prints
7
+ * the secret EXACTLY ONCE to stdout, with the same stream discipline as the
8
+ * stdio bridge: stdout carries the credential and nothing else; every
9
+ * diagnostic goes to stderr. The secret is never logged, never persisted
10
+ * anywhere beyond the owner-only store document, and cannot be recovered —
11
+ * a lost secret is replaced by revoking and regenerating.
12
+ *
13
+ * Usage:
14
+ * dsh-tool-mode-credentials generate --scheme static-token --client-id <id>
15
+ * dsh-tool-mode-credentials generate --scheme oauth-resource-server --client-id <id> [--scope a,b,c]
16
+ * dsh-tool-mode-credentials list [--store <path>]
17
+ * dsh-tool-mode-credentials revoke --client-id <id> [--store <path>]
18
+ *
19
+ * Exit codes: 0 done; 1 the operation failed (corrupt store, duplicate id,
20
+ * persistence failure); 2 usage.
21
+ *
22
+ * @module @try-works/dsh-model-as-tool/mcp/credential-cli
23
+ */
24
+ import { createFileCredentialStore, defaultCredentialStoreFile, generateCredential, } from "./credential-store.js";
25
+ const USAGE = [
26
+ 'usage:',
27
+ ' dsh-tool-mode-credentials generate --scheme static-token --client-id <id>',
28
+ ' dsh-tool-mode-credentials generate --scheme oauth-resource-server --client-id <id> [--scope a,b,c]',
29
+ ' dsh-tool-mode-credentials list [--store <path>]',
30
+ ' dsh-tool-mode-credentials revoke --client-id <id> [--store <path>]',
31
+ 'The generated secret is printed ONCE to stdout and is never echoed again;',
32
+ 'a lost secret is replaced by revoking the client and generating a new one.',
33
+ ].join('\n');
34
+ /** Parse `--flag value` pairs; repeated flags keep the last value. */
35
+ function parseArgs(argv) {
36
+ const pairs = new Map();
37
+ for (let i = 0; i < argv.length; i += 1) {
38
+ const token = argv[i];
39
+ if (!token.startsWith('--'))
40
+ throw new Error(`unexpected argument ${JSON.stringify(token)}`);
41
+ const value = argv[i + 1];
42
+ if (value === undefined || value.startsWith('--'))
43
+ throw new Error(`the flag ${token} needs a value`);
44
+ pairs.set(token.slice(2), value);
45
+ i += 1;
46
+ }
47
+ return pairs;
48
+ }
49
+ /**
50
+ * Run the credential CLI.
51
+ * @param argv - the process arguments, without the executable.
52
+ * @param streams - the two streams (stdout/stderr in production).
53
+ * @returns the process exit code.
54
+ */
55
+ export function runCredentialsCli(argv, streams) {
56
+ const command = argv[0];
57
+ let pairs;
58
+ try {
59
+ pairs = parseArgs(argv.slice(1));
60
+ }
61
+ catch (error) {
62
+ streams.err(error instanceof Error ? error.message : String(error));
63
+ streams.err(USAGE);
64
+ return 2;
65
+ }
66
+ const storeFile = pairs.get('store') ?? defaultCredentialStoreFile();
67
+ let store;
68
+ try {
69
+ store = createFileCredentialStore(storeFile);
70
+ }
71
+ catch (error) {
72
+ streams.err(`cannot open the credential store: ${error instanceof Error ? error.message : String(error)}`);
73
+ return 1;
74
+ }
75
+ if (command === 'generate') {
76
+ const scheme = pairs.get('scheme');
77
+ const clientId = pairs.get('client-id');
78
+ if (scheme !== 'static-token' && scheme !== 'oauth-resource-server') {
79
+ streams.err('generate requires --scheme static-token or --scheme oauth-resource-server');
80
+ streams.err(USAGE);
81
+ return 2;
82
+ }
83
+ if (clientId === undefined || clientId.length === 0) {
84
+ streams.err('generate requires --client-id');
85
+ streams.err(USAGE);
86
+ return 2;
87
+ }
88
+ const scope = pairs.get('scope') === undefined
89
+ ? undefined
90
+ : pairs.get('scope').split(',').map(name => name.trim()).filter(name => name.length > 0);
91
+ try {
92
+ const generated = generateCredential(store, {
93
+ clientId,
94
+ scheme: scheme,
95
+ ...scope === undefined ? {} : { scope },
96
+ });
97
+ // THE ONCE-ONLY OUTPUT. Nothing else in this process writes to stdout.
98
+ streams.out('Generated credential (SEC-01).');
99
+ streams.out(`clientId: ${generated.clientId}`);
100
+ streams.out(`scheme: ${generated.scheme}`);
101
+ streams.out(`scope: ${generated.scope.join(' ')}`);
102
+ streams.out(`secret: ${generated.secret}`);
103
+ streams.out('SHOWN ONCE: store the secret now. It is never echoed again and cannot be recovered;');
104
+ streams.out('a lost secret is replaced by revoking this client and generating a new credential.');
105
+ return 0;
106
+ }
107
+ catch (error) {
108
+ streams.err(`generation refused: ${error instanceof Error ? error.message : String(error)}`);
109
+ return 1;
110
+ }
111
+ }
112
+ if (command === 'list') {
113
+ try {
114
+ const entries = store.list();
115
+ streams.out(`${entries.length} live client credential(s):`);
116
+ for (const entry of entries) {
117
+ streams.out(`- ${entry.clientId} (${entry.scheme}, ${entry.createdAt})`);
118
+ }
119
+ return 0;
120
+ }
121
+ catch (error) {
122
+ streams.err(`list failed: ${error instanceof Error ? error.message : String(error)}`);
123
+ return 1;
124
+ }
125
+ }
126
+ if (command === 'revoke') {
127
+ const clientId = pairs.get('client-id');
128
+ if (clientId === undefined || clientId.length === 0) {
129
+ streams.err('revoke requires --client-id');
130
+ streams.err(USAGE);
131
+ return 2;
132
+ }
133
+ try {
134
+ const existed = store.get(clientId) !== undefined;
135
+ store.delete(clientId);
136
+ streams.out(existed ? `revoked ${clientId}` : `no credential for ${clientId}; nothing to revoke`);
137
+ return 0;
138
+ }
139
+ catch (error) {
140
+ streams.err(`revoke refused: ${error instanceof Error ? error.message : String(error)}`);
141
+ return 1;
142
+ }
143
+ }
144
+ streams.err(`unknown command ${JSON.stringify(command ?? '')}`);
145
+ streams.err(USAGE);
146
+ return 2;
147
+ }
148
+ //# sourceMappingURL=credential-cli.js.map