@wrenai/genbi 0.0.1

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 (286) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +141 -0
  3. package/bin/genbi.mjs +31 -0
  4. package/dist/assets/echarts-LTFC-ysd.js +40 -0
  5. package/dist/assets/index-Bo--QMUN.js +441 -0
  6. package/dist/assets/index-Xs4q7pNo.css +1 -0
  7. package/dist/index.html +14 -0
  8. package/dist/wrenai_logo.png +0 -0
  9. package/dist/wrenai_logo_white.png +0 -0
  10. package/dist-server/harness/auth/detect.d.ts +18 -0
  11. package/dist-server/harness/auth/detect.js +37 -0
  12. package/dist-server/harness/auth/index.d.ts +4 -0
  13. package/dist-server/harness/auth/index.js +2 -0
  14. package/dist-server/harness/auth/probe.d.ts +18 -0
  15. package/dist-server/harness/auth/probe.js +52 -0
  16. package/dist-server/harness/auth/types.d.ts +42 -0
  17. package/dist-server/harness/auth/types.js +9 -0
  18. package/dist-server/harness/bundle/loader.d.ts +32 -0
  19. package/dist-server/harness/bundle/loader.js +43 -0
  20. package/dist-server/harness/bundle/schema.d.ts +199 -0
  21. package/dist-server/harness/bundle/schema.js +127 -0
  22. package/dist-server/harness/bundle/version.d.ts +10 -0
  23. package/dist-server/harness/bundle/version.js +43 -0
  24. package/dist-server/harness/capability/gate.d.ts +6 -0
  25. package/dist-server/harness/capability/gate.js +29 -0
  26. package/dist-server/harness/capability/registry.d.ts +6 -0
  27. package/dist-server/harness/capability/registry.js +17 -0
  28. package/dist-server/harness/cli-args.d.ts +123 -0
  29. package/dist-server/harness/cli-args.js +247 -0
  30. package/dist-server/harness/cli.d.ts +2 -0
  31. package/dist-server/harness/cli.js +151 -0
  32. package/dist-server/harness/compile/cache.d.ts +23 -0
  33. package/dist-server/harness/compile/cache.js +109 -0
  34. package/dist-server/harness/compile/compose-profile.d.ts +34 -0
  35. package/dist-server/harness/compile/compose-profile.js +95 -0
  36. package/dist-server/harness/compile/errors.d.ts +22 -0
  37. package/dist-server/harness/compile/errors.js +41 -0
  38. package/dist-server/harness/compile/fingerprint.d.ts +22 -0
  39. package/dist-server/harness/compile/fingerprint.js +84 -0
  40. package/dist-server/harness/compile/index.d.ts +8 -0
  41. package/dist-server/harness/compile/index.js +7 -0
  42. package/dist-server/harness/compile/pipeline.d.ts +66 -0
  43. package/dist-server/harness/compile/pipeline.js +176 -0
  44. package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
  45. package/dist-server/harness/compile/resolve-binary.js +133 -0
  46. package/dist-server/harness/compile/types.d.ts +107 -0
  47. package/dist-server/harness/compile/types.js +1 -0
  48. package/dist-server/harness/compile/warble-identity.d.ts +12 -0
  49. package/dist-server/harness/compile/warble-identity.js +33 -0
  50. package/dist-server/harness/compliance/gate.d.ts +35 -0
  51. package/dist-server/harness/compliance/gate.js +48 -0
  52. package/dist-server/harness/compliance/index.d.ts +2 -0
  53. package/dist-server/harness/compliance/index.js +1 -0
  54. package/dist-server/harness/compliance/types.d.ts +16 -0
  55. package/dist-server/harness/compliance/types.js +1 -0
  56. package/dist-server/harness/events/emitter.d.ts +34 -0
  57. package/dist-server/harness/events/emitter.js +25 -0
  58. package/dist-server/harness/events/index.d.ts +4 -0
  59. package/dist-server/harness/events/index.js +2 -0
  60. package/dist-server/harness/events/sse.d.ts +20 -0
  61. package/dist-server/harness/events/sse.js +48 -0
  62. package/dist-server/harness/events/types.d.ts +131 -0
  63. package/dist-server/harness/events/types.js +1 -0
  64. package/dist-server/harness/exec/errors.d.ts +16 -0
  65. package/dist-server/harness/exec/errors.js +29 -0
  66. package/dist-server/harness/exec/index.d.ts +5 -0
  67. package/dist-server/harness/exec/index.js +2 -0
  68. package/dist-server/harness/exec/local.d.ts +32 -0
  69. package/dist-server/harness/exec/local.js +134 -0
  70. package/dist-server/harness/exec/policy.d.ts +12 -0
  71. package/dist-server/harness/exec/policy.js +1 -0
  72. package/dist-server/harness/exec/types.d.ts +63 -0
  73. package/dist-server/harness/exec/types.js +1 -0
  74. package/dist-server/harness/guardrails/index.d.ts +2 -0
  75. package/dist-server/harness/guardrails/index.js +1 -0
  76. package/dist-server/harness/guardrails/policy.d.ts +45 -0
  77. package/dist-server/harness/guardrails/policy.js +57 -0
  78. package/dist-server/harness/index.d.ts +37 -0
  79. package/dist-server/harness/index.js +19 -0
  80. package/dist-server/harness/loop/errors.d.ts +53 -0
  81. package/dist-server/harness/loop/errors.js +72 -0
  82. package/dist-server/harness/loop/executor.d.ts +123 -0
  83. package/dist-server/harness/loop/executor.js +380 -0
  84. package/dist-server/harness/loop/guard.d.ts +16 -0
  85. package/dist-server/harness/loop/guard.js +1 -0
  86. package/dist-server/harness/loop/index.d.ts +5 -0
  87. package/dist-server/harness/loop/index.js +3 -0
  88. package/dist-server/harness/native-producer/index.d.ts +109 -0
  89. package/dist-server/harness/native-producer/index.js +302 -0
  90. package/dist-server/harness/npm-package-resolve.d.ts +28 -0
  91. package/dist-server/harness/npm-package-resolve.js +47 -0
  92. package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
  93. package/dist-server/harness/providers/adapters/anthropic.js +6 -0
  94. package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
  95. package/dist-server/harness/providers/adapters/mock.js +5 -0
  96. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
  97. package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
  98. package/dist-server/harness/providers/binding.d.ts +23 -0
  99. package/dist-server/harness/providers/binding.js +13 -0
  100. package/dist-server/harness/providers/errors.d.ts +6 -0
  101. package/dist-server/harness/providers/errors.js +12 -0
  102. package/dist-server/harness/providers/index.d.ts +18 -0
  103. package/dist-server/harness/providers/index.js +22 -0
  104. package/dist-server/harness/providers/registry.d.ts +16 -0
  105. package/dist-server/harness/providers/registry.js +24 -0
  106. package/dist-server/harness/render/envelope.d.ts +145 -0
  107. package/dist-server/harness/render/envelope.js +428 -0
  108. package/dist-server/harness/render/errors.d.ts +33 -0
  109. package/dist-server/harness/render/errors.js +46 -0
  110. package/dist-server/harness/render/index.d.ts +5 -0
  111. package/dist-server/harness/render/index.js +3 -0
  112. package/dist-server/harness/render/validate.d.ts +13 -0
  113. package/dist-server/harness/render/validate.js +88 -0
  114. package/dist-server/harness/route/adapter-spec.d.ts +40 -0
  115. package/dist-server/harness/route/adapter-spec.js +61 -0
  116. package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
  117. package/dist-server/harness/route/agent-sdk-cli.js +86 -0
  118. package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
  119. package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
  120. package/dist-server/harness/route/artifact-content.d.ts +53 -0
  121. package/dist-server/harness/route/artifact-content.js +83 -0
  122. package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
  123. package/dist-server/harness/route/chat-event-mapper.js +164 -0
  124. package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
  125. package/dist-server/harness/route/codex-ask-events.js +395 -0
  126. package/dist-server/harness/route/codex-ask.d.ts +15 -0
  127. package/dist-server/harness/route/codex-ask.js +242 -0
  128. package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
  129. package/dist-server/harness/route/codex-local-cli.js +78 -0
  130. package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
  131. package/dist-server/harness/route/codex-local-manifest.js +180 -0
  132. package/dist-server/harness/route/describe.d.ts +54 -0
  133. package/dist-server/harness/route/describe.js +84 -0
  134. package/dist-server/harness/route/dispatched.d.ts +138 -0
  135. package/dist-server/harness/route/dispatched.js +339 -0
  136. package/dist-server/harness/route/in-process.d.ts +43 -0
  137. package/dist-server/harness/route/in-process.js +106 -0
  138. package/dist-server/harness/route/index.d.ts +19 -0
  139. package/dist-server/harness/route/index.js +12 -0
  140. package/dist-server/harness/route/profile-source.d.ts +37 -0
  141. package/dist-server/harness/route/profile-source.js +76 -0
  142. package/dist-server/harness/route/route.d.ts +33 -0
  143. package/dist-server/harness/route/route.js +114 -0
  144. package/dist-server/harness/route/tier-binding.d.ts +36 -0
  145. package/dist-server/harness/route/tier-binding.js +63 -0
  146. package/dist-server/harness/route/types.d.ts +286 -0
  147. package/dist-server/harness/route/types.js +1 -0
  148. package/dist-server/harness/session/errors.d.ts +4 -0
  149. package/dist-server/harness/session/errors.js +7 -0
  150. package/dist-server/harness/session/gate.d.ts +11 -0
  151. package/dist-server/harness/session/gate.js +13 -0
  152. package/dist-server/harness/session/index.d.ts +5 -0
  153. package/dist-server/harness/session/index.js +3 -0
  154. package/dist-server/harness/session/run.d.ts +60 -0
  155. package/dist-server/harness/session/run.js +333 -0
  156. package/dist-server/harness/session/types.d.ts +29 -0
  157. package/dist-server/harness/session/types.js +1 -0
  158. package/dist-server/harness/setup/codex-events.d.ts +12 -0
  159. package/dist-server/harness/setup/codex-events.js +108 -0
  160. package/dist-server/harness/setup/runner.d.ts +497 -0
  161. package/dist-server/harness/setup/runner.js +1314 -0
  162. package/dist-server/harness/tools/errors.d.ts +107 -0
  163. package/dist-server/harness/tools/errors.js +141 -0
  164. package/dist-server/harness/tools/index.d.ts +13 -0
  165. package/dist-server/harness/tools/index.js +8 -0
  166. package/dist-server/harness/tools/mcp.d.ts +26 -0
  167. package/dist-server/harness/tools/mcp.js +15 -0
  168. package/dist-server/harness/tools/native.d.ts +179 -0
  169. package/dist-server/harness/tools/native.js +322 -0
  170. package/dist-server/harness/tools/parse.d.ts +14 -0
  171. package/dist-server/harness/tools/parse.js +18 -0
  172. package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
  173. package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
  174. package/dist-server/harness/tools/resolve.d.ts +51 -0
  175. package/dist-server/harness/tools/resolve.js +79 -0
  176. package/dist-server/harness/tools/setup-native.d.ts +84 -0
  177. package/dist-server/harness/tools/setup-native.js +353 -0
  178. package/dist-server/harness/tools/wiring.d.ts +10 -0
  179. package/dist-server/harness/tools/wiring.js +16 -0
  180. package/dist-server/providers/setup.provider.yaml +31 -0
  181. package/dist-server/providers/wren.provider.yaml +33 -0
  182. package/dist-server/server/adopt.d.ts +87 -0
  183. package/dist-server/server/adopt.js +263 -0
  184. package/dist-server/server/app.d.ts +6 -0
  185. package/dist-server/server/app.js +2521 -0
  186. package/dist-server/server/auth-choice.d.ts +16 -0
  187. package/dist-server/server/auth-choice.js +36 -0
  188. package/dist-server/server/bin.d.ts +2 -0
  189. package/dist-server/server/bin.js +601 -0
  190. package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
  191. package/dist-server/server/bootstrap-project-binding.js +71 -0
  192. package/dist-server/server/clarify.d.ts +10 -0
  193. package/dist-server/server/clarify.js +17 -0
  194. package/dist-server/server/codex-setup-mcp.d.ts +2 -0
  195. package/dist-server/server/codex-setup-mcp.js +147 -0
  196. package/dist-server/server/compose.d.ts +89 -0
  197. package/dist-server/server/compose.js +271 -0
  198. package/dist-server/server/conn-config.d.ts +30 -0
  199. package/dist-server/server/conn-config.js +253 -0
  200. package/dist-server/server/context-files.d.ts +12 -0
  201. package/dist-server/server/context-files.js +117 -0
  202. package/dist-server/server/context-map.d.ts +30 -0
  203. package/dist-server/server/context-map.js +95 -0
  204. package/dist-server/server/context-source.d.ts +68 -0
  205. package/dist-server/server/context-source.js +85 -0
  206. package/dist-server/server/db.d.ts +862 -0
  207. package/dist-server/server/db.js +1839 -0
  208. package/dist-server/server/enrichment-runner.d.ts +184 -0
  209. package/dist-server/server/enrichment-runner.js +213 -0
  210. package/dist-server/server/enrichment-verify.d.ts +73 -0
  211. package/dist-server/server/enrichment-verify.js +290 -0
  212. package/dist-server/server/enrichment.d.ts +232 -0
  213. package/dist-server/server/enrichment.js +199 -0
  214. package/dist-server/server/env-detect.d.ts +8 -0
  215. package/dist-server/server/env-detect.js +17 -0
  216. package/dist-server/server/env-field-hints.d.ts +46 -0
  217. package/dist-server/server/env-field-hints.js +48 -0
  218. package/dist-server/server/fold.d.ts +133 -0
  219. package/dist-server/server/fold.js +407 -0
  220. package/dist-server/server/harness-profile-sources.d.ts +3 -0
  221. package/dist-server/server/harness-profile-sources.js +24 -0
  222. package/dist-server/server/harness.d.ts +19 -0
  223. package/dist-server/server/harness.js +439 -0
  224. package/dist-server/server/impact.d.ts +18 -0
  225. package/dist-server/server/impact.js +85 -0
  226. package/dist-server/server/interactive-terminal.d.ts +175 -0
  227. package/dist-server/server/interactive-terminal.js +336 -0
  228. package/dist-server/server/native-artifacts.d.ts +1004 -0
  229. package/dist-server/server/native-artifacts.js +564 -0
  230. package/dist-server/server/native-dispatch-registry.d.ts +42 -0
  231. package/dist-server/server/native-dispatch-registry.js +45 -0
  232. package/dist-server/server/native-producer-fence.d.ts +14 -0
  233. package/dist-server/server/native-producer-fence.js +48 -0
  234. package/dist-server/server/native-session-resume.d.ts +6 -0
  235. package/dist-server/server/native-session-resume.js +103 -0
  236. package/dist-server/server/native-session-workspace.d.ts +40 -0
  237. package/dist-server/server/native-session-workspace.js +175 -0
  238. package/dist-server/server/native-sessions.d.ts +374 -0
  239. package/dist-server/server/native-sessions.js +1517 -0
  240. package/dist-server/server/native-wren-runtime.d.ts +20 -0
  241. package/dist-server/server/native-wren-runtime.js +259 -0
  242. package/dist-server/server/node-pty-host.d.ts +31 -0
  243. package/dist-server/server/node-pty-host.js +100 -0
  244. package/dist-server/server/route-intent.d.ts +40 -0
  245. package/dist-server/server/route-intent.js +23 -0
  246. package/dist-server/server/runtime-binding.d.ts +52 -0
  247. package/dist-server/server/runtime-binding.js +258 -0
  248. package/dist-server/server/source-catalog.d.ts +84 -0
  249. package/dist-server/server/source-catalog.js +221 -0
  250. package/dist-server/server/source-driver.d.ts +35 -0
  251. package/dist-server/server/source-driver.js +124 -0
  252. package/dist-server/server/spa.d.ts +2 -0
  253. package/dist-server/server/spa.js +51 -0
  254. package/dist-server/server/subscription-model-catalog.d.ts +27 -0
  255. package/dist-server/server/subscription-model-catalog.js +203 -0
  256. package/dist-server/server/turn.d.ts +249 -0
  257. package/dist-server/server/turn.js +1074 -0
  258. package/dist-server/server/wire-types.d.ts +799 -0
  259. package/dist-server/server/wire-types.js +1 -0
  260. package/dist-server/server/wren-home.d.ts +19 -0
  261. package/dist-server/server/wren-home.js +33 -0
  262. package/dist-server/server/wren-profiles.d.ts +43 -0
  263. package/dist-server/server/wren-profiles.js +123 -0
  264. package/package.json +93 -0
  265. package/profiles/genbi-default/context/binding.yml +9 -0
  266. package/profiles/genbi-default/ir.golden.json +506 -0
  267. package/profiles/genbi-default/profile.yml +16 -0
  268. package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
  269. package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
  270. package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
  271. package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
  272. package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
  273. package/profiles/genbi-enrich-context/context/binding.yml +4 -0
  274. package/profiles/genbi-enrich-context/ir.golden.json +302 -0
  275. package/profiles/genbi-enrich-context/profile.yml +15 -0
  276. package/profiles/genbi-monitor/context/binding.yml +7 -0
  277. package/profiles/genbi-monitor/ir.golden.json +188 -0
  278. package/profiles/genbi-monitor/profile.yml +27 -0
  279. package/profiles/genbi-setup/components/build_context/component.yml +44 -0
  280. package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
  281. package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
  282. package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
  283. package/profiles/genbi-setup/context/binding.yml +8 -0
  284. package/profiles/genbi-setup/ir.golden.json +140 -0
  285. package/profiles/genbi-setup/profile.yml +18 -0
  286. package/profiles/genbi-setup/raw/schema.json +5 -0
@@ -0,0 +1,10 @@
1
+ import { type Bundle } from "./schema.js";
2
+ export declare class BundleCompatError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export interface HarnessSupport {
6
+ readonly irVersion: string;
7
+ readonly bundleVersions: readonly string[];
8
+ }
9
+ export declare const HARNESS_SUPPORT: HarnessSupport;
10
+ export declare function assertCompat(bundle: Bundle, support?: HarnessSupport): void;
@@ -0,0 +1,43 @@
1
+ import { bundleFormatVersion } from "./schema.js";
2
+ export class BundleCompatError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "BundleCompatError";
6
+ }
7
+ }
8
+ export const HARNESS_SUPPORT = {
9
+ irVersion: "0.6",
10
+ bundleVersions: ["0.1"],
11
+ };
12
+ function parseVersion(version) {
13
+ const parts = version.split(".").map(Number);
14
+ if (parts.some((part) => Number.isNaN(part))) {
15
+ throw new BundleCompatError(`malformed version string: "${version}"`);
16
+ }
17
+ return parts;
18
+ }
19
+ function compareVersions(a, b) {
20
+ const partsA = parseVersion(a);
21
+ const partsB = parseVersion(b);
22
+ const length = Math.max(partsA.length, partsB.length);
23
+ for (let index = 0; index < length; index += 1) {
24
+ const diff = (partsA[index] ?? 0) - (partsB[index] ?? 0);
25
+ if (diff !== 0)
26
+ return diff;
27
+ }
28
+ return 0;
29
+ }
30
+ export function assertCompat(bundle, support = HARNESS_SUPPORT) {
31
+ const formatVersion = bundleFormatVersion(bundle);
32
+ if (!support.bundleVersions.includes(formatVersion)) {
33
+ throw new BundleCompatError(`unsupported bundle format version "${formatVersion}"; ` +
34
+ `harness supports: ${support.bundleVersions.join(", ")}`);
35
+ }
36
+ const { min_ir_version: min, max_ir_version: max } = bundle.compat;
37
+ const withinMin = compareVersions(support.irVersion, min) >= 0;
38
+ const withinMax = compareVersions(support.irVersion, max) <= 0;
39
+ if (!withinMin || !withinMax) {
40
+ throw new BundleCompatError(`bundle compat window [${min}, ${max}] does not include harness IR ` +
41
+ `version "${support.irVersion}"`);
42
+ }
43
+ }
@@ -0,0 +1,6 @@
1
+ import type { Bundle } from "../bundle/schema.js";
2
+ import type { CapabilityRegistry } from "./registry.js";
3
+ export declare class CapabilityGateError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ export declare function assertCapabilities(bundle: Bundle, registry: CapabilityRegistry): void;
@@ -0,0 +1,29 @@
1
+ const GATED_CRITICALITIES = new Set(["required", "safety-critical"]);
2
+ export class CapabilityGateError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "CapabilityGateError";
6
+ }
7
+ }
8
+ export function assertCapabilities(bundle, registry) {
9
+ const missing = [];
10
+ for (const agent of bundle.agents) {
11
+ for (const capability of agent.capabilities) {
12
+ if (!GATED_CRITICALITIES.has(capability.criticality))
13
+ continue;
14
+ if (registry.provides(capability.capability))
15
+ continue;
16
+ missing.push({
17
+ agentId: agent.id,
18
+ capability: capability.capability,
19
+ criticality: capability.criticality,
20
+ });
21
+ }
22
+ }
23
+ if (missing.length > 0) {
24
+ const detail = missing
25
+ .map((entry) => ` - agent "${entry.agentId}": ${entry.capability} (${entry.criticality})`)
26
+ .join("\n");
27
+ throw new CapabilityGateError(`missing required capabilities:\n${detail}`);
28
+ }
29
+ }
@@ -0,0 +1,6 @@
1
+ export interface CapabilityRegistry {
2
+ provides(capability: string): boolean;
3
+ }
4
+ export declare const DEFAULT_CAPABILITIES: readonly string[];
5
+ export declare function createRegistry(capabilities: readonly string[]): CapabilityRegistry;
6
+ export declare function createDefaultCapabilityRegistry(): CapabilityRegistry;
@@ -0,0 +1,17 @@
1
+ export const DEFAULT_CAPABILITIES = [
2
+ "llm:cheap",
3
+ "llm:strong",
4
+ "llm:per_step_tier",
5
+ "sql_execution:read_only",
6
+ "semantic_introspection",
7
+ "genbi_build",
8
+ "render_contract",
9
+ "artifact_write",
10
+ ];
11
+ export function createRegistry(capabilities) {
12
+ const set = new Set(capabilities);
13
+ return { provides: (capability) => set.has(capability) };
14
+ }
15
+ export function createDefaultCapabilityRegistry() {
16
+ return createRegistry(DEFAULT_CAPABILITIES);
17
+ }
@@ -0,0 +1,123 @@
1
+ import type { AuthChoice, LoginProbe } from "./auth/index.js";
2
+ import type { Deployment } from "./compliance/index.js";
3
+ import type { AdapterSpec } from "./providers/index.js";
4
+ import type { RouteResult } from "./route/types.js";
5
+ /**
6
+ * Parsed CLI flags after normalization. Kept separate from `node:util`'s
7
+ * `parseArgs` inferred type so these pure helpers can be unit-tested with
8
+ * plain objects, and so `exactOptionalPropertyTypes` is satisfied (optional
9
+ * fields are omitted, never set to `undefined`).
10
+ */
11
+ export interface CliFlags {
12
+ readonly project?: string;
13
+ readonly profile?: string;
14
+ readonly mode?: string;
15
+ readonly provider?: string;
16
+ readonly adapter?: string;
17
+ readonly apiKey?: string;
18
+ readonly model?: string;
19
+ readonly endpoint?: string;
20
+ readonly warbleBin?: string;
21
+ readonly agentSdkBin?: string;
22
+ readonly out?: string;
23
+ readonly deployment?: string;
24
+ /** Hybrid mode, in-process: repeatable `--tier-adapter <tier>=<mode>[:<field>=<value>,...]` entries. */
25
+ readonly tierAdapters?: readonly string[];
26
+ /** Hybrid mode, dispatched: `--models-config <path>`, forwarded verbatim to `warble-agent-sdk chat`. */
27
+ readonly modelsConfig?: string;
28
+ /** Dispatched only: `--chat-timeout-ms <n>`, overrides `spawnChat`'s default 10-minute hang guard. See `DispatchedOptions.chatTimeoutMs`. */
29
+ readonly chatTimeoutMs?: number;
30
+ }
31
+ /** Thrown for user-facing CLI misuse (bad/missing flags), surfaced as a top-level `error:` line. */
32
+ export declare class CliUsageError extends Error {
33
+ constructor(message: string);
34
+ }
35
+ /** Validates the required `--project` and positional question, rejecting empty/whitespace-only values. */
36
+ export declare function validateRequiredInputs(project: string | undefined, question: string): string;
37
+ /**
38
+ * Resolves `--deployment`, defaulting to `"personal"` and rejecting any
39
+ * value other than `"personal"`/`"hosted"`. Kept separate from
40
+ * `enforceCompliance` so the CLI's flag validation (bad flag value) and the
41
+ * compliance gate's policy decision (which flag values imply what) stay two
42
+ * independently testable pure functions, matching `validateRequiredInputs`.
43
+ */
44
+ export declare function resolveDeployment(flags: CliFlags): Deployment;
45
+ /**
46
+ * Parses `--chat-timeout-ms`'s raw string value into a positive integer,
47
+ * rejecting anything else (blank, non-numeric, zero/negative, non-integer)
48
+ * as a CLI usage error rather than silently passing `NaN`/0 down to
49
+ * `spawnChat`'s `setTimeout`. Returns `undefined` when the flag was omitted.
50
+ */
51
+ export declare function parseChatTimeoutMs(raw: string | undefined): number | undefined;
52
+ /**
53
+ * Parses `WREN_HARNESS_SETUP_MAX_TURNS` (the agentic setup wizard's per-turn
54
+ * agent-loop budget, forwarded as `warble-agent-sdk chat --max-turns`) into a
55
+ * positive integer, rejecting anything else rather than silently passing
56
+ * `NaN`/0 down. Returns `undefined` when unset, so the setup runner's own
57
+ * default applies. The heavy step is `build_context` (generating an MDL takes
58
+ * many read/introspect/write/validate tool calls), which routinely exceeds the
59
+ * dispatcher's default 40-turn cap and fails with `error_max_turns`.
60
+ */
61
+ export declare function parseSetupMaxTurns(raw: string | undefined): number | undefined;
62
+ /**
63
+ * Default auth-selection policy when `--mode` is omitted: prefer an
64
+ * explicitly-configured `api-key` (`--adapter`) or `local` (`--endpoint`)
65
+ * choice — since the operator clearly meant one of those — over
66
+ * auto-detection; otherwise enumerate options via `detectAndPick(probe)` and
67
+ * pick a detected logged-in subscription CLI (claude, then codex); otherwise
68
+ * fail loudly rather than silently guess a mode with no supporting evidence.
69
+ * The `probe` is injected so the subscription-detection branch is testable
70
+ * offline; `main()` passes `createDefaultLoginProbe()`.
71
+ */
72
+ export declare function resolveAuthChoice(flags: CliFlags, probe: LoginProbe): Promise<AuthChoice>;
73
+ /** Builds an `AuthChoice` for an explicit `--mode`, validating the flags each mode requires. */
74
+ export declare function buildExplicitAuthChoice(mode: string, flags: CliFlags): AuthChoice;
75
+ /** Exit code contract for the packaged `bin`: 0 = answered, 1 = error (unhandled throw), 2 = refusal. */
76
+ export declare const EXIT_OK = 0;
77
+ export declare const EXIT_ERROR = 1;
78
+ export declare const EXIT_REFUSAL = 2;
79
+ /**
80
+ * A `RouteResult` with `kind: "refusal"` (in-process only — the agent
81
+ * declined to answer because a `locked` `gated_check` guardrail wasn't
82
+ * satisfied) is a completed *run*, not a crash, but it is also not an
83
+ * answer; a shell/pipeline consumer of the packaged `bin` piping the CLI's
84
+ * stdout needs a way to tell "ran but refused" (`EXIT_REFUSAL`) apart from
85
+ * both "answered" (`EXIT_OK`) and "failed to run" (`EXIT_ERROR`, the
86
+ * top-level `main().catch()` in `cli.ts`). Dispatched has no refusal state of
87
+ * its own to map here — `runDispatchedDefault` throws (in `dispatched.ts`) on
88
+ * an empty final answer rather than returning a refusal-shaped result, so
89
+ * that already lands on `EXIT_ERROR` via the same top-level catch.
90
+ */
91
+ export declare function determineExitCode(result: RouteResult): number;
92
+ /** api-key config from `--api-key`/`--model`; `undefined` when neither is set (so the adapter's own env lookup applies). */
93
+ export declare function buildApiKeyConfig(flags: CliFlags): Record<string, unknown> | undefined;
94
+ /** gateway config from `--endpoint`/`--model`/`--api-key`; `undefined` when none are set (so `deriveAdapterSpec` loud-fails). */
95
+ export declare function buildGatewayConfig(flags: CliFlags): Record<string, unknown> | undefined;
96
+ /** One parsed `--tier-adapter` entry, before it's turned into an `AdapterSpec`. */
97
+ interface ParsedTierAdapterFlag {
98
+ readonly tier: string;
99
+ readonly mode: string;
100
+ readonly fields: Pick<CliFlags, "adapter" | "apiKey" | "model" | "endpoint">;
101
+ }
102
+ /**
103
+ * Parses one hybrid-mode `--tier-adapter <tier>=<mode>[:<field>=<value>,...]`
104
+ * entry — e.g. `cheap=local:endpoint=http://localhost:11434/v1,model=llama3.1`
105
+ * or `strong=api-key:adapter=anthropic,model=claude-opus-4-6`. `mode` is
106
+ * restricted to `api-key`/`local`/`gateway` — subscription has no adapter of
107
+ * its own (dispatched is the whole-run back-end, never a per-tier one), so
108
+ * naming it here is a loud-fail rather than a silent no-op.
109
+ */
110
+ export declare function parseTierAdapterFlag(raw: string): ParsedTierAdapterFlag;
111
+ /**
112
+ * Builds the hybrid-mode per-tier `AdapterSpec` map from every `--tier-adapter`
113
+ * flag the CLI collected. Each entry is parsed with {@link parseTierAdapterFlag},
114
+ * turned into an `AuthChoice` by reusing {@link buildExplicitAuthChoice} (so a
115
+ * per-tier `api-key`/`local`/`gateway` entry validates exactly like the
116
+ * top-level `--mode` flag does — e.g. `api-key` still requires `adapter=`),
117
+ * and then resolved to an `AdapterSpec` via `deriveAdapterSpec`. Coverage
118
+ * against the compiled agent's actual tiers (missing/unknown tier names) is
119
+ * `buildHybridTierBinding`'s job at run time — this function only turns argv
120
+ * into a map, without knowing what tiers the agent uses.
121
+ */
122
+ export declare function buildTierBindingFromFlags(raw: readonly string[]): Record<string, AdapterSpec>;
123
+ export {};
@@ -0,0 +1,247 @@
1
+ import { detectAndPick } from "./auth/index.js";
2
+ import { deriveAdapterSpec } from "./route/adapter-spec.js";
3
+ /** Thrown for user-facing CLI misuse (bad/missing flags), surfaced as a top-level `error:` line. */
4
+ export class CliUsageError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "CliUsageError";
8
+ }
9
+ }
10
+ function isBlank(value) {
11
+ return value.trim().length === 0;
12
+ }
13
+ /** Validates the required `--project` and positional question, rejecting empty/whitespace-only values. */
14
+ export function validateRequiredInputs(project, question) {
15
+ if (project === undefined || isBlank(project)) {
16
+ throw new CliUsageError("--project <dir> is required and must not be empty");
17
+ }
18
+ if (isBlank(question)) {
19
+ throw new CliUsageError("a non-empty question is required");
20
+ }
21
+ return project;
22
+ }
23
+ /**
24
+ * Resolves `--deployment`, defaulting to `"personal"` and rejecting any
25
+ * value other than `"personal"`/`"hosted"`. Kept separate from
26
+ * `enforceCompliance` so the CLI's flag validation (bad flag value) and the
27
+ * compliance gate's policy decision (which flag values imply what) stay two
28
+ * independently testable pure functions, matching `validateRequiredInputs`.
29
+ */
30
+ export function resolveDeployment(flags) {
31
+ const value = flags.deployment ?? "personal";
32
+ if (value !== "personal" && value !== "hosted") {
33
+ throw new CliUsageError(`--deployment must be "personal" or "hosted", got "${value}"`);
34
+ }
35
+ return value;
36
+ }
37
+ /**
38
+ * Parses `--chat-timeout-ms`'s raw string value into a positive integer,
39
+ * rejecting anything else (blank, non-numeric, zero/negative, non-integer)
40
+ * as a CLI usage error rather than silently passing `NaN`/0 down to
41
+ * `spawnChat`'s `setTimeout`. Returns `undefined` when the flag was omitted.
42
+ */
43
+ export function parseChatTimeoutMs(raw) {
44
+ if (raw === undefined)
45
+ return undefined;
46
+ const value = Number(raw);
47
+ if (!Number.isInteger(value) || value <= 0) {
48
+ throw new CliUsageError(`--chat-timeout-ms must be a positive integer (milliseconds), got "${raw}"`);
49
+ }
50
+ return value;
51
+ }
52
+ /**
53
+ * Parses `WREN_HARNESS_SETUP_MAX_TURNS` (the agentic setup wizard's per-turn
54
+ * agent-loop budget, forwarded as `warble-agent-sdk chat --max-turns`) into a
55
+ * positive integer, rejecting anything else rather than silently passing
56
+ * `NaN`/0 down. Returns `undefined` when unset, so the setup runner's own
57
+ * default applies. The heavy step is `build_context` (generating an MDL takes
58
+ * many read/introspect/write/validate tool calls), which routinely exceeds the
59
+ * dispatcher's default 40-turn cap and fails with `error_max_turns`.
60
+ */
61
+ export function parseSetupMaxTurns(raw) {
62
+ if (raw === undefined)
63
+ return undefined;
64
+ // `/^\d+$/` before Number() so hex/float/whitespace-padded spellings
65
+ // ("0x10", "1e3", " 40 ") are rejected rather than silently coerced.
66
+ const value = /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : NaN;
67
+ if (!Number.isInteger(value) || value <= 0) {
68
+ throw new CliUsageError(`WREN_HARNESS_SETUP_MAX_TURNS must be a positive integer, got "${raw}"`);
69
+ }
70
+ return value;
71
+ }
72
+ /**
73
+ * Default auth-selection policy when `--mode` is omitted: prefer an
74
+ * explicitly-configured `api-key` (`--adapter`) or `local` (`--endpoint`)
75
+ * choice — since the operator clearly meant one of those — over
76
+ * auto-detection; otherwise enumerate options via `detectAndPick(probe)` and
77
+ * pick a detected logged-in subscription CLI (claude, then codex); otherwise
78
+ * fail loudly rather than silently guess a mode with no supporting evidence.
79
+ * The `probe` is injected so the subscription-detection branch is testable
80
+ * offline; `main()` passes `createDefaultLoginProbe()`.
81
+ */
82
+ export async function resolveAuthChoice(flags, probe) {
83
+ if (flags.mode !== undefined) {
84
+ return buildExplicitAuthChoice(flags.mode, flags);
85
+ }
86
+ if (flags.adapter !== undefined) {
87
+ const config = buildApiKeyConfig(flags);
88
+ return { mode: "api-key", adapter: flags.adapter, ...(config !== undefined ? { config } : {}) };
89
+ }
90
+ if (flags.endpoint !== undefined) {
91
+ return { mode: "local", endpoint: flags.endpoint };
92
+ }
93
+ // `--api-key`/`--model` without `--adapter` and without an explicit
94
+ // `--mode` used to fall straight through to subscription auto-detection
95
+ // below — silently discarding credentials the operator clearly configured
96
+ // for a *different* mode and answering via whatever subscription CLI
97
+ // happens to be logged in instead. Loud-fail instead of guessing an
98
+ // adapter or silently picking subscription.
99
+ if (flags.apiKey !== undefined || flags.model !== undefined) {
100
+ throw new CliUsageError("--api-key/--model given without --adapter <name> and without an explicit --mode; " +
101
+ "pass --adapter <name> (to use api-key mode) or set --mode explicitly — refusing to " +
102
+ "silently fall back to subscription auto-detection with credentials configured for a " +
103
+ "different mode");
104
+ }
105
+ const options = await detectAndPick(probe);
106
+ const subscription = options.find((option) => option.mode === "subscription");
107
+ if (subscription !== undefined) {
108
+ return { mode: "subscription", provider: subscription.provider };
109
+ }
110
+ throw new CliUsageError("no --mode given, no explicit --adapter/--endpoint configured, and no subscription CLI " +
111
+ "(claude or codex) detected as logged in; pass --mode explicitly " +
112
+ "(subscription|api-key|local|gateway)");
113
+ }
114
+ /** Builds an `AuthChoice` for an explicit `--mode`, validating the flags each mode requires. */
115
+ export function buildExplicitAuthChoice(mode, flags) {
116
+ switch (mode) {
117
+ case "subscription": {
118
+ const provider = flags.provider ?? "claude";
119
+ if (provider !== "claude" && provider !== "codex") {
120
+ throw new CliUsageError(`--provider must be "claude" or "codex", got "${provider}"`);
121
+ }
122
+ return { mode: "subscription", provider };
123
+ }
124
+ case "api-key": {
125
+ if (flags.adapter === undefined) {
126
+ throw new CliUsageError("--mode api-key requires --adapter <name>");
127
+ }
128
+ const config = buildApiKeyConfig(flags);
129
+ return { mode: "api-key", adapter: flags.adapter, ...(config !== undefined ? { config } : {}) };
130
+ }
131
+ case "local":
132
+ return { mode: "local", ...(flags.endpoint !== undefined ? { endpoint: flags.endpoint } : {}) };
133
+ case "gateway": {
134
+ const config = buildGatewayConfig(flags);
135
+ return { mode: "gateway", ...(config !== undefined ? { config } : {}) };
136
+ }
137
+ default:
138
+ throw new CliUsageError(`--mode must be one of subscription|api-key|local|gateway, got "${mode}"`);
139
+ }
140
+ }
141
+ /** Exit code contract for the packaged `bin`: 0 = answered, 1 = error (unhandled throw), 2 = refusal. */
142
+ export const EXIT_OK = 0;
143
+ export const EXIT_ERROR = 1;
144
+ export const EXIT_REFUSAL = 2;
145
+ /**
146
+ * A `RouteResult` with `kind: "refusal"` (in-process only — the agent
147
+ * declined to answer because a `locked` `gated_check` guardrail wasn't
148
+ * satisfied) is a completed *run*, not a crash, but it is also not an
149
+ * answer; a shell/pipeline consumer of the packaged `bin` piping the CLI's
150
+ * stdout needs a way to tell "ran but refused" (`EXIT_REFUSAL`) apart from
151
+ * both "answered" (`EXIT_OK`) and "failed to run" (`EXIT_ERROR`, the
152
+ * top-level `main().catch()` in `cli.ts`). Dispatched has no refusal state of
153
+ * its own to map here — `runDispatchedDefault` throws (in `dispatched.ts`) on
154
+ * an empty final answer rather than returning a refusal-shaped result, so
155
+ * that already lands on `EXIT_ERROR` via the same top-level catch.
156
+ */
157
+ export function determineExitCode(result) {
158
+ return "kind" in result && result.kind === "refusal" ? EXIT_REFUSAL : EXIT_OK;
159
+ }
160
+ /** api-key config from `--api-key`/`--model`; `undefined` when neither is set (so the adapter's own env lookup applies). */
161
+ export function buildApiKeyConfig(flags) {
162
+ const config = {};
163
+ if (flags.apiKey !== undefined)
164
+ config["apiKey"] = flags.apiKey;
165
+ if (flags.model !== undefined)
166
+ config["model"] = flags.model;
167
+ return Object.keys(config).length > 0 ? config : undefined;
168
+ }
169
+ /** gateway config from `--endpoint`/`--model`/`--api-key`; `undefined` when none are set (so `deriveAdapterSpec` loud-fails). */
170
+ export function buildGatewayConfig(flags) {
171
+ const config = {};
172
+ if (flags.endpoint !== undefined)
173
+ config["baseURL"] = flags.endpoint;
174
+ if (flags.model !== undefined)
175
+ config["model"] = flags.model;
176
+ if (flags.apiKey !== undefined)
177
+ config["apiKey"] = flags.apiKey;
178
+ return Object.keys(config).length > 0 ? config : undefined;
179
+ }
180
+ const TIER_ADAPTER_ALLOWED_MODES = new Set(["api-key", "local", "gateway"]);
181
+ const TIER_ADAPTER_ALLOWED_FIELDS = new Set(["adapter", "apiKey", "model", "endpoint"]);
182
+ /**
183
+ * Parses one hybrid-mode `--tier-adapter <tier>=<mode>[:<field>=<value>,...]`
184
+ * entry — e.g. `cheap=local:endpoint=http://localhost:11434/v1,model=llama3.1`
185
+ * or `strong=api-key:adapter=anthropic,model=claude-opus-4-6`. `mode` is
186
+ * restricted to `api-key`/`local`/`gateway` — subscription has no adapter of
187
+ * its own (dispatched is the whole-run back-end, never a per-tier one), so
188
+ * naming it here is a loud-fail rather than a silent no-op.
189
+ */
190
+ export function parseTierAdapterFlag(raw) {
191
+ const tierEnd = raw.indexOf("=");
192
+ if (tierEnd <= 0) {
193
+ throw new CliUsageError(`--tier-adapter must be "<tier>=<mode>[:<field>=<value>,...]", got "${raw}"`);
194
+ }
195
+ const tier = raw.slice(0, tierEnd);
196
+ const rest = raw.slice(tierEnd + 1);
197
+ const fieldsStart = rest.indexOf(":");
198
+ const mode = fieldsStart === -1 ? rest : rest.slice(0, fieldsStart);
199
+ const fieldsRaw = fieldsStart === -1 ? "" : rest.slice(fieldsStart + 1);
200
+ if (!TIER_ADAPTER_ALLOWED_MODES.has(mode)) {
201
+ throw new CliUsageError(`--tier-adapter "${raw}": mode must be one of api-key|local|gateway (a hybrid tier has no ` +
202
+ `subscription adapter of its own), got "${mode}"`);
203
+ }
204
+ const fields = {};
205
+ if (fieldsRaw.length > 0) {
206
+ for (const pair of fieldsRaw.split(",")) {
207
+ const pairEq = pair.indexOf("=");
208
+ if (pairEq <= 0) {
209
+ throw new CliUsageError(`--tier-adapter "${raw}": malformed field "${pair}", expected key=value`);
210
+ }
211
+ const key = pair.slice(0, pairEq);
212
+ if (!TIER_ADAPTER_ALLOWED_FIELDS.has(key)) {
213
+ throw new CliUsageError(`--tier-adapter "${raw}": unknown field "${key}" (allowed: adapter, apiKey, model, endpoint)`);
214
+ }
215
+ fields[key] = pair.slice(pairEq + 1);
216
+ }
217
+ }
218
+ return { tier, mode, fields };
219
+ }
220
+ /**
221
+ * Builds the hybrid-mode per-tier `AdapterSpec` map from every `--tier-adapter`
222
+ * flag the CLI collected. Each entry is parsed with {@link parseTierAdapterFlag},
223
+ * turned into an `AuthChoice` by reusing {@link buildExplicitAuthChoice} (so a
224
+ * per-tier `api-key`/`local`/`gateway` entry validates exactly like the
225
+ * top-level `--mode` flag does — e.g. `api-key` still requires `adapter=`),
226
+ * and then resolved to an `AdapterSpec` via `deriveAdapterSpec`. Coverage
227
+ * against the compiled agent's actual tiers (missing/unknown tier names) is
228
+ * `buildHybridTierBinding`'s job at run time — this function only turns argv
229
+ * into a map, without knowing what tiers the agent uses.
230
+ */
231
+ export function buildTierBindingFromFlags(raw) {
232
+ const tiers = {};
233
+ for (const entry of raw) {
234
+ const { tier, mode, fields } = parseTierAdapterFlag(entry);
235
+ if (tier in tiers) {
236
+ throw new CliUsageError(`--tier-adapter names tier "${tier}" more than once`);
237
+ }
238
+ try {
239
+ const authChoice = buildExplicitAuthChoice(mode, fields); // `mode` was already restricted to api-key|local|gateway above, so this is never "subscription".
240
+ tiers[tier] = deriveAdapterSpec(authChoice, fields.model !== undefined ? { model: fields.model } : {});
241
+ }
242
+ catch (error) {
243
+ throw new CliUsageError(`--tier-adapter "${entry}": ${error instanceof Error ? error.message : String(error)}`);
244
+ }
245
+ }
246
+ return tiers;
247
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { createDefaultLoginProbe } from "./auth/index.js";
4
+ import { buildTierBindingFromFlags, determineExitCode, parseChatTimeoutMs, resolveAuthChoice, resolveDeployment, validateRequiredInputs, } from "./cli-args.js";
5
+ import { enforceCompliance } from "./compliance/index.js";
6
+ import { resolveDefaultProfileSource, route } from "./route/index.js";
7
+ const USAGE = `Usage: wren-harness <question> --project <dir> [options]
8
+
9
+ Options:
10
+ --project <dir> User's wren project directory (required)
11
+ --profile <dir> Warble profile source directory (default: this package's profiles/genbi-default)
12
+ --mode <mode> subscription | api-key | local | gateway (default: see policy below)
13
+ --provider <name> subscription mode: claude | codex (default: claude)
14
+ --adapter <name> api-key mode: provider registry adapter id (e.g. anthropic)
15
+ --api-key <key> api-key mode: API key (omit to rely on the adapter's own env var lookup)
16
+ --model <name> api-key/local mode: model name
17
+ --endpoint <url> local/gateway mode: endpoint URL
18
+ --warble-bin <path> Explicit warble binary (default: the installed @warble/cli, then PATH)
19
+ --agent-sdk-bin <path> Explicit warble-agent-sdk CLI (subscription mode only)
20
+ --out <dir> subscription mode: run output directory
21
+ --deployment <ctx> personal | hosted (default: personal). "hosted" means multi-tenant,
22
+ shared, or always-on-server use; subscription mode is rejected when
23
+ --deployment hosted (use api-key or gateway instead).
24
+ --tier-adapter <spec> Hybrid mode, in-process only (api-key/local/gateway): repeatable
25
+ "<tier>=<mode>[:<field>=<value>,...]" entry binding one bundle tier to
26
+ its own adapter, e.g. --tier-adapter cheap=local:endpoint=http://localhost:11434/v1
27
+ --tier-adapter strong=api-key:adapter=anthropic,model=claude-opus-4-6
28
+ Every tier the compiled agent uses must be bound exactly once (loud-fail
29
+ on missing/unknown tier names); when given, replaces the uniform
30
+ --mode/--adapter/--model binding for tier resolution.
31
+ --models-config <path> Hybrid mode, dispatched only (subscription): path to warble-agent-sdk's own
32
+ per-tier YAML config, forwarded verbatim as --models-config <path> to
33
+ "warble-agent-sdk chat". Known limitation: warble's render stage still
34
+ wall-hits on non-"render: none" components under a non-Anthropic tier.
35
+ --chat-timeout-ms <n> dispatched only (subscription): overrides the default 10-minute hang guard
36
+ on a "warble-agent-sdk chat" invocation. Raise this for a legitimately
37
+ slower cold-start turn (e.g. right after onboarding a project) rather
38
+ than the turn being killed and reported as a timeout.
39
+ --help Show this help
40
+
41
+ Default auth-selection policy (when --mode is omitted), in order:
42
+ 1. --adapter given -> api-key, using --adapter/--api-key/--model
43
+ 2. --endpoint given -> local, using --endpoint/--model
44
+ 3. otherwise, a detected logged-in subscription CLI (claude, then codex) -> subscription
45
+ 4. otherwise -> error: pass --mode explicitly
46
+
47
+ Compliance: subscription mode uses your personal provider subscription. It is allowed for
48
+ --deployment personal (the default), printing a ToS warning to stderr; it is rejected outright for
49
+ --deployment hosted, since sharing a personal subscription behind a multi-tenant/always-on server is
50
+ account sharing. This harness never embeds a proxy relay for subscription auth.
51
+
52
+ Exit codes:
53
+ 0 answered — result JSON on stdout is a normal answer.
54
+ 1 error — an unhandled exception (bad flags, compile/spawn failure, compliance rejection, etc).
55
+ 2 refusal — the agent declined to answer (a locked guardrail wasn't satisfied); result JSON on
56
+ stdout is still the refusal envelope, only the exit code differs from a normal answer.
57
+ `;
58
+ async function main() {
59
+ const { values, positionals } = parseArgs({
60
+ args: process.argv.slice(2),
61
+ allowPositionals: true,
62
+ options: {
63
+ project: { type: "string" },
64
+ profile: { type: "string" },
65
+ mode: { type: "string" },
66
+ provider: { type: "string" },
67
+ adapter: { type: "string" },
68
+ "api-key": { type: "string" },
69
+ model: { type: "string" },
70
+ endpoint: { type: "string" },
71
+ "warble-bin": { type: "string" },
72
+ "agent-sdk-bin": { type: "string" },
73
+ out: { type: "string" },
74
+ deployment: { type: "string" },
75
+ "tier-adapter": { type: "string", multiple: true },
76
+ "models-config": { type: "string" },
77
+ "chat-timeout-ms": { type: "string" },
78
+ help: { type: "boolean" },
79
+ },
80
+ });
81
+ if (values.help === true) {
82
+ process.stdout.write(USAGE);
83
+ return;
84
+ }
85
+ if (positionals.length === 0) {
86
+ process.stderr.write(`error: a question is required\n\n${USAGE}`);
87
+ process.exitCode = 1;
88
+ return;
89
+ }
90
+ const chatTimeoutMs = parseChatTimeoutMs(values["chat-timeout-ms"]);
91
+ const flags = {
92
+ ...(values.project !== undefined ? { project: values.project } : {}),
93
+ ...(values.profile !== undefined ? { profile: values.profile } : {}),
94
+ ...(values.mode !== undefined ? { mode: values.mode } : {}),
95
+ ...(values.provider !== undefined ? { provider: values.provider } : {}),
96
+ ...(values.adapter !== undefined ? { adapter: values.adapter } : {}),
97
+ ...(values["api-key"] !== undefined ? { apiKey: values["api-key"] } : {}),
98
+ ...(values.model !== undefined ? { model: values.model } : {}),
99
+ ...(values.endpoint !== undefined ? { endpoint: values.endpoint } : {}),
100
+ ...(values["warble-bin"] !== undefined ? { warbleBin: values["warble-bin"] } : {}),
101
+ ...(values["agent-sdk-bin"] !== undefined ? { agentSdkBin: values["agent-sdk-bin"] } : {}),
102
+ ...(values.out !== undefined ? { out: values.out } : {}),
103
+ ...(values.deployment !== undefined ? { deployment: values.deployment } : {}),
104
+ ...(values["tier-adapter"] !== undefined ? { tierAdapters: values["tier-adapter"] } : {}),
105
+ ...(values["models-config"] !== undefined ? { modelsConfig: values["models-config"] } : {}),
106
+ ...(chatTimeoutMs !== undefined ? { chatTimeoutMs } : {}),
107
+ };
108
+ const question = positionals.join(" ");
109
+ const project = validateRequiredInputs(flags.project, question);
110
+ const profileSource = flags.profile ?? resolveDefaultProfileSource();
111
+ const authChoice = await resolveAuthChoice(flags, createDefaultLoginProbe());
112
+ const deployment = resolveDeployment(flags);
113
+ // Compliance gate: run (and surface any ToS warning) before the back-end runs.
114
+ // A rejection (subscription + hosted) throws ComplianceError, caught below like any
115
+ // other error. route() re-runs the same gate as a structural backstop for any other
116
+ // caller of route() that doesn't go through this CLI.
117
+ const { warnings } = enforceCompliance(authChoice, { deployment });
118
+ for (const warning of warnings) {
119
+ process.stderr.write(`warning: ${warning}\n`);
120
+ }
121
+ // Hybrid mode: --tier-adapter (in-process) and --models-config (dispatched) are each
122
+ // meaningful for exactly one back-end; route() itself loud-fails (a plain
123
+ // Error, caught like any other error below) if the wrong one is supplied
124
+ // for the resolved authChoice.mode, so no separate CLI-level guard is
125
+ // duplicated here.
126
+ const tierBinding = flags.tierAdapters !== undefined ? buildTierBindingFromFlags(flags.tierAdapters) : undefined;
127
+ const result = await route({
128
+ authChoice,
129
+ profileSource,
130
+ userProject: project,
131
+ question,
132
+ deployment,
133
+ ...(flags.model !== undefined ? { model: flags.model } : {}),
134
+ ...(flags.warbleBin !== undefined ? { warbleBin: flags.warbleBin } : {}),
135
+ ...(flags.agentSdkBin !== undefined ? { agentSdkBin: flags.agentSdkBin } : {}),
136
+ ...(flags.out !== undefined ? { outDir: flags.out } : {}),
137
+ ...(tierBinding !== undefined ? { tierBinding } : {}),
138
+ ...(flags.modelsConfig !== undefined ? { modelsConfig: flags.modelsConfig } : {}),
139
+ ...(flags.chatTimeoutMs !== undefined ? { chatTimeoutMs: flags.chatTimeoutMs } : {}),
140
+ });
141
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
142
+ // A refusal is printed exactly as before — only the exit code
143
+ // changes, so callers that already parse stdout JSON are unaffected;
144
+ // callers that only check the exit code now see EXIT_REFUSAL (2), not a
145
+ // false-success 0. See `determineExitCode`'s doc comment for the contract.
146
+ process.exitCode = determineExitCode(result);
147
+ }
148
+ main().catch((error) => {
149
+ process.stderr.write(`error: ${error instanceof Error ? error.message : String(error)}\n`);
150
+ process.exitCode = 1;
151
+ });