nexus-agents 2.174.0 → 2.174.2

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 (126) hide show
  1. package/dist/{child-mcp-config-EQ2NKTK2.js → child-mcp-config-GWB7JLIS.js} +2 -2
  2. package/dist/{chunk-26WHBGN6.js → chunk-2CB6QMRX.js} +35 -35
  3. package/dist/{chunk-TZS5IKWW.js → chunk-3P7SMHBU.js} +3 -3
  4. package/dist/{chunk-YXLJNG7E.js → chunk-4PTAG65V.js} +2 -2
  5. package/dist/{chunk-MM6UYUIC.js → chunk-5QVJCBM4.js} +3 -3
  6. package/dist/{chunk-LSMFXWRN.js → chunk-6RRAQFSX.js} +2 -2
  7. package/dist/{chunk-YVVUKVZV.js → chunk-ASJTVA7W.js} +5 -5
  8. package/dist/{chunk-OPZQQWVW.js → chunk-DGR27REV.js} +4 -170
  9. package/dist/chunk-DGR27REV.js.map +1 -0
  10. package/dist/{chunk-KOK66W2C.js → chunk-DJZGCMNU.js} +2 -2
  11. package/dist/{chunk-TFU5J53T.js → chunk-E35RUETV.js} +2 -2
  12. package/dist/{chunk-NDNJ3TBM.js → chunk-EDWON3X5.js} +4 -4
  13. package/dist/{chunk-KNOIZAYR.js → chunk-H5KLPUAL.js} +2 -2
  14. package/dist/{chunk-KKFTLDNO.js → chunk-HUYP2XXH.js} +2 -2
  15. package/dist/{chunk-P7BKE3OH.js → chunk-IVINLVGP.js} +2 -2
  16. package/dist/{chunk-AJHA7DSS.js → chunk-JSIX5L3C.js} +2 -2
  17. package/dist/{chunk-TOH2HSHH.js → chunk-JVBI4WKP.js} +35 -5
  18. package/dist/{chunk-TOH2HSHH.js.map → chunk-JVBI4WKP.js.map} +1 -1
  19. package/dist/{chunk-ZLQLBSSQ.js → chunk-K6NXLBHG.js} +22 -9
  20. package/dist/{chunk-ZLQLBSSQ.js.map → chunk-K6NXLBHG.js.map} +1 -1
  21. package/dist/{chunk-NCGPKRRY.js → chunk-KAL22DYO.js} +2 -2
  22. package/dist/{chunk-IJMNDXPZ.js → chunk-KU2HPCJG.js} +4 -4
  23. package/dist/{chunk-22NGPM4P.js → chunk-KWKGX7LC.js} +5 -5
  24. package/dist/{chunk-AOOZ6IZB.js → chunk-KYF4YAZO.js} +3 -3
  25. package/dist/{chunk-4NYZ2VTW.js → chunk-MRRKFMYJ.js} +4 -4
  26. package/dist/{chunk-VAD2FMNC.js → chunk-OCTNNEXT.js} +36 -11
  27. package/dist/chunk-OCTNNEXT.js.map +1 -0
  28. package/dist/{chunk-HZB3LIBF.js → chunk-OGR5Q6QO.js} +5 -5
  29. package/dist/chunk-OM2VVQKB.js +172 -0
  30. package/dist/chunk-OM2VVQKB.js.map +1 -0
  31. package/dist/{chunk-ZMM3HJ2E.js → chunk-PN43VH6Z.js} +7 -7
  32. package/dist/{chunk-CERGIC66.js → chunk-RAIFOYRM.js} +2 -2
  33. package/dist/{chunk-3DI4CEC7.js → chunk-SGIWJYLJ.js} +2 -2
  34. package/dist/{chunk-MLJAC2UH.js → chunk-UB6APBIY.js} +2 -2
  35. package/dist/{chunk-JUUHARON.js → chunk-VSETLO4Q.js} +2 -2
  36. package/dist/{chunk-25ZJ5TCC.js → chunk-Y5JA7XEO.js} +404 -378
  37. package/dist/chunk-Y5JA7XEO.js.map +1 -0
  38. package/dist/{chunk-SDFC2IOJ.js → chunk-YE2XMU3T.js} +3 -3
  39. package/dist/{chunk-V4XEPUCM.js → chunk-YUCJ7VSJ.js} +8 -8
  40. package/dist/{chunk-QPKG7LS7.js → chunk-Z77MIV3C.js} +2 -2
  41. package/dist/{cli-circuit-breaker-OKP72JMQ.js → cli-circuit-breaker-EINPV2CX.js} +3 -3
  42. package/dist/cli.js +41 -40
  43. package/dist/cli.js.map +1 -1
  44. package/dist/{composite-router-BYXWBPQU.js → composite-router-CUJAG7O4.js} +2 -2
  45. package/dist/{consensus-vote-UJRG6CJS.js → consensus-vote-BLCQCWPV.js} +16 -15
  46. package/dist/{context-retriever-TAUTDY3F.js → context-retriever-MTJR3AQM.js} +8 -8
  47. package/dist/{dist-KUG63MRZ.js → dist-GR7PQU7T.js} +563 -482
  48. package/dist/dist-GR7PQU7T.js.map +1 -0
  49. package/dist/{doctor-deep-XKGC3T76.js → doctor-deep-X2KP2G4M.js} +3 -3
  50. package/dist/{expert-bridge-WOKMAGXB.js → expert-bridge-2W2JMFQW.js} +4 -4
  51. package/dist/{factory-ETFUFSGU.js → factory-7UFJ6LXW.js} +9 -8
  52. package/dist/{factory-M4OE3GLA.js → factory-SC5D5P3C.js} +5 -5
  53. package/dist/{improvement-review-CBVDAPCL.js → improvement-review-ZMUJQSX3.js} +5 -5
  54. package/dist/index.d.ts +17 -0
  55. package/dist/index.js +30 -29
  56. package/dist/index.js.map +1 -1
  57. package/dist/{init-opencode-442YJUON.js → init-opencode-I5XSFY34.js} +6 -5
  58. package/dist/{init-opencode-442YJUON.js.map → init-opencode-I5XSFY34.js.map} +1 -1
  59. package/dist/{issue-triage-S7RSXHCS.js → issue-triage-KOIPTNDH.js} +6 -6
  60. package/dist/{pr-reviewer-helpers-UVDDO3T4.js → pr-reviewer-helpers-CZDMP5UJ.js} +4 -4
  61. package/dist/{registry-command-DWOQ2IV4.js → registry-command-5J6RJZMS.js} +2 -2
  62. package/dist/{repo-security-plan-5P23NS3N.js → repo-security-plan-MKSCD567.js} +3 -3
  63. package/dist/{research-helpers-synthesize-5AIKCDA2.js → research-helpers-synthesize-TIT43XF4.js} +4 -4
  64. package/dist/{routing-memory-BOTPCHXJ.js → routing-memory-HAWL7RKO.js} +2 -2
  65. package/dist/{session-memory-KOUNAH5O.js → session-memory-RPSM7Y4Q.js} +3 -3
  66. package/dist/{setup-command-DW3DQJMG.js → setup-command-SORJLYU3.js} +14 -13
  67. package/dist/{setup-config-BBUFGXW2.js → setup-config-YDA4MY4N.js} +3 -3
  68. package/dist/{setup-custom-api-PSVW6DIY.js → setup-custom-api-BZSHB2M5.js} +6 -4
  69. package/dist/{setup-custom-api-PSVW6DIY.js.map → setup-custom-api-BZSHB2M5.js.map} +1 -1
  70. package/dist/{tool-memory-RBACIEGW.js → tool-memory-FCEJMKEH.js} +5 -5
  71. package/dist/{unified-registry-35EIUQ2M.js → unified-registry-JWPD2ES2.js} +10 -9
  72. package/dist/{weather-report-YXGRGPCX.js → weather-report-VNABZRH7.js} +2 -2
  73. package/package.json +8 -8
  74. package/dist/chunk-25ZJ5TCC.js.map +0 -1
  75. package/dist/chunk-OPZQQWVW.js.map +0 -1
  76. package/dist/chunk-VAD2FMNC.js.map +0 -1
  77. package/dist/dist-KUG63MRZ.js.map +0 -1
  78. /package/dist/{child-mcp-config-EQ2NKTK2.js.map → child-mcp-config-GWB7JLIS.js.map} +0 -0
  79. /package/dist/{chunk-26WHBGN6.js.map → chunk-2CB6QMRX.js.map} +0 -0
  80. /package/dist/{chunk-TZS5IKWW.js.map → chunk-3P7SMHBU.js.map} +0 -0
  81. /package/dist/{chunk-YXLJNG7E.js.map → chunk-4PTAG65V.js.map} +0 -0
  82. /package/dist/{chunk-MM6UYUIC.js.map → chunk-5QVJCBM4.js.map} +0 -0
  83. /package/dist/{chunk-LSMFXWRN.js.map → chunk-6RRAQFSX.js.map} +0 -0
  84. /package/dist/{chunk-YVVUKVZV.js.map → chunk-ASJTVA7W.js.map} +0 -0
  85. /package/dist/{chunk-KOK66W2C.js.map → chunk-DJZGCMNU.js.map} +0 -0
  86. /package/dist/{chunk-TFU5J53T.js.map → chunk-E35RUETV.js.map} +0 -0
  87. /package/dist/{chunk-NDNJ3TBM.js.map → chunk-EDWON3X5.js.map} +0 -0
  88. /package/dist/{chunk-KNOIZAYR.js.map → chunk-H5KLPUAL.js.map} +0 -0
  89. /package/dist/{chunk-KKFTLDNO.js.map → chunk-HUYP2XXH.js.map} +0 -0
  90. /package/dist/{chunk-P7BKE3OH.js.map → chunk-IVINLVGP.js.map} +0 -0
  91. /package/dist/{chunk-AJHA7DSS.js.map → chunk-JSIX5L3C.js.map} +0 -0
  92. /package/dist/{chunk-NCGPKRRY.js.map → chunk-KAL22DYO.js.map} +0 -0
  93. /package/dist/{chunk-IJMNDXPZ.js.map → chunk-KU2HPCJG.js.map} +0 -0
  94. /package/dist/{chunk-22NGPM4P.js.map → chunk-KWKGX7LC.js.map} +0 -0
  95. /package/dist/{chunk-AOOZ6IZB.js.map → chunk-KYF4YAZO.js.map} +0 -0
  96. /package/dist/{chunk-4NYZ2VTW.js.map → chunk-MRRKFMYJ.js.map} +0 -0
  97. /package/dist/{chunk-HZB3LIBF.js.map → chunk-OGR5Q6QO.js.map} +0 -0
  98. /package/dist/{chunk-ZMM3HJ2E.js.map → chunk-PN43VH6Z.js.map} +0 -0
  99. /package/dist/{chunk-CERGIC66.js.map → chunk-RAIFOYRM.js.map} +0 -0
  100. /package/dist/{chunk-3DI4CEC7.js.map → chunk-SGIWJYLJ.js.map} +0 -0
  101. /package/dist/{chunk-MLJAC2UH.js.map → chunk-UB6APBIY.js.map} +0 -0
  102. /package/dist/{chunk-JUUHARON.js.map → chunk-VSETLO4Q.js.map} +0 -0
  103. /package/dist/{chunk-SDFC2IOJ.js.map → chunk-YE2XMU3T.js.map} +0 -0
  104. /package/dist/{chunk-V4XEPUCM.js.map → chunk-YUCJ7VSJ.js.map} +0 -0
  105. /package/dist/{chunk-QPKG7LS7.js.map → chunk-Z77MIV3C.js.map} +0 -0
  106. /package/dist/{cli-circuit-breaker-OKP72JMQ.js.map → cli-circuit-breaker-EINPV2CX.js.map} +0 -0
  107. /package/dist/{composite-router-BYXWBPQU.js.map → composite-router-CUJAG7O4.js.map} +0 -0
  108. /package/dist/{consensus-vote-UJRG6CJS.js.map → consensus-vote-BLCQCWPV.js.map} +0 -0
  109. /package/dist/{context-retriever-TAUTDY3F.js.map → context-retriever-MTJR3AQM.js.map} +0 -0
  110. /package/dist/{doctor-deep-XKGC3T76.js.map → doctor-deep-X2KP2G4M.js.map} +0 -0
  111. /package/dist/{expert-bridge-WOKMAGXB.js.map → expert-bridge-2W2JMFQW.js.map} +0 -0
  112. /package/dist/{factory-ETFUFSGU.js.map → factory-7UFJ6LXW.js.map} +0 -0
  113. /package/dist/{factory-M4OE3GLA.js.map → factory-SC5D5P3C.js.map} +0 -0
  114. /package/dist/{improvement-review-CBVDAPCL.js.map → improvement-review-ZMUJQSX3.js.map} +0 -0
  115. /package/dist/{issue-triage-S7RSXHCS.js.map → issue-triage-KOIPTNDH.js.map} +0 -0
  116. /package/dist/{pr-reviewer-helpers-UVDDO3T4.js.map → pr-reviewer-helpers-CZDMP5UJ.js.map} +0 -0
  117. /package/dist/{registry-command-DWOQ2IV4.js.map → registry-command-5J6RJZMS.js.map} +0 -0
  118. /package/dist/{repo-security-plan-5P23NS3N.js.map → repo-security-plan-MKSCD567.js.map} +0 -0
  119. /package/dist/{research-helpers-synthesize-5AIKCDA2.js.map → research-helpers-synthesize-TIT43XF4.js.map} +0 -0
  120. /package/dist/{routing-memory-BOTPCHXJ.js.map → routing-memory-HAWL7RKO.js.map} +0 -0
  121. /package/dist/{session-memory-KOUNAH5O.js.map → session-memory-RPSM7Y4Q.js.map} +0 -0
  122. /package/dist/{setup-command-DW3DQJMG.js.map → setup-command-SORJLYU3.js.map} +0 -0
  123. /package/dist/{setup-config-BBUFGXW2.js.map → setup-config-YDA4MY4N.js.map} +0 -0
  124. /package/dist/{tool-memory-RBACIEGW.js.map → tool-memory-FCEJMKEH.js.map} +0 -0
  125. /package/dist/{unified-registry-35EIUQ2M.js.map → unified-registry-JWPD2ES2.js.map} +0 -0
  126. /package/dist/{weather-report-YXGRGPCX.js.map → weather-report-VNABZRH7.js.map} +0 -0
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  discoverModels,
3
3
  readOpencodeGateway
4
- } from "./chunk-TOH2HSHH.js";
5
- import "./chunk-KNOIZAYR.js";
6
- import "./chunk-LSMFXWRN.js";
4
+ } from "./chunk-JVBI4WKP.js";
5
+ import "./chunk-H5KLPUAL.js";
6
+ import "./chunk-6RRAQFSX.js";
7
+ import "./chunk-OM2VVQKB.js";
7
8
  import {
8
9
  createLogger
9
- } from "./chunk-25ZJ5TCC.js";
10
+ } from "./chunk-Y5JA7XEO.js";
10
11
  import "./chunk-AXTD4TFK.js";
11
12
  import "./chunk-DHVMSIT5.js";
12
13
  import "./chunk-ZPWHCABL.js";
@@ -175,4 +176,4 @@ export {
175
176
  runInitOpencode,
176
177
  runOpencodeValidate
177
178
  };
178
- //# sourceMappingURL=init-opencode-442YJUON.js.map
179
+ //# sourceMappingURL=init-opencode-I5XSFY34.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli/init-opencode.ts"],"sourcesContent":["/**\n * `nexus-agents init --opencode <path>` setup helper (#2504, child 4 of #2500).\n *\n * Injects the nexus-agents MCP block + recommended environment into an\n * existing `opencode.json` without overwriting other operator-set keys.\n * Pairs with the existing `init --portable --mcp-config` (which targets\n * Claude Code's `.mcp.json`). Different harness, same merge-not-overwrite\n * pattern.\n *\n * Behaviour:\n * - File exists → merge `mcp.nexus-agents` into the existing JSON.\n * Preserve every other key the operator has set (provider config,\n * model lists, theme, …). Idempotent: re-running produces the same\n * final file.\n * - File missing → write a minimal template with the nexus-agents MCP\n * block + a stubbed `providers.openai-compat` shell the operator\n * fills in (placeholder baseURL + `{env:WORKSPACE_PROXY_KEY}`).\n * - `--dry-run` → print the diff (proposed vs existing) without writing.\n * - `--validate` → after merge (or alongside dry-run), probe\n * `providers.openai-compat.options.baseURL/v1/models` with the\n * resolved apiKey, exit non-zero if unreachable.\n *\n * @module cli/init-opencode\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { createLogger } from '../core/index.js';\nimport { readOpencodeGateway } from '../config/opencode-bridge.js';\nimport { discoverModels } from '../adapters/openai-compat-adapter.js';\n\nconst logger = createLogger({ component: 'init-opencode' });\n\nexport interface InitOpencodeOptions {\n /** Absolute or relative path to opencode.json. */\n readonly path: string;\n /** Path to the nexus-agents CLI binary that the MCP block will spawn. */\n readonly cliPath: string;\n /** Sandbox flavor written to NEXUS_SANDBOX in the MCP environment. */\n readonly sandboxFlavor?: string;\n /** Print the diff without writing. */\n readonly dryRun?: boolean;\n}\n\nexport interface InitOpencodeResult {\n readonly path: string;\n readonly action: 'created' | 'updated' | 'unchanged' | 'dry-run';\n readonly diff: string;\n}\n\ninterface OpencodeMcpBlock {\n type: 'local';\n command: string[];\n enabled: boolean;\n environment: Record<string, string>;\n}\n\ninterface OpencodeFile {\n $schema?: string;\n providers?: Record<string, unknown>;\n mcp?: Record<string, unknown>;\n permission?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Conservative default OpenCode `permission` block (#2658). OpenCode\n * evaluates a glob map with **last-matching-rule-winning**, so the broad\n * `\"*\"` rule is listed first and the hard `deny` patterns last.\n *\n * - `edit` — everything `ask`s; secrets, keys, and `.git/` are hard-denied\n * regardless of operator confirmation. The deny patterns come after\n * `\"*\"` so last-match-wins makes them stick.\n * - `bash` — `ask` unconditionally. Shell is the highest-risk surface.\n * - `skill` — `allow`. Skills are trusted, in-repo, CI-validated content.\n *\n * Emitted only when the operator has not already set their own\n * `permission` block — never overwrites operator config.\n */\nexport function buildDefaultPermissionBlock(): Record<string, unknown> {\n return {\n edit: {\n '*': 'ask',\n '**/.env': 'deny',\n '**/.env.*': 'deny',\n '**/*.pem': 'deny',\n '**/*.key': 'deny',\n '**/id_rsa*': 'deny',\n '**/secrets/**': 'deny',\n '**/.git/**': 'deny',\n },\n bash: 'ask',\n skill: 'allow',\n };\n}\n\n/**\n * Build the canonical nexus-agents MCP block. Mirrors what\n * `Dockerfile.sandbox` writes today plus the env vars from #2501 + #2503.\n */\nexport function buildNexusMcpBlock(opts: {\n readonly cliPath: string;\n readonly sandboxFlavor?: string;\n readonly opencodeConfigPath: string;\n}): OpencodeMcpBlock {\n const env: Record<string, string> = {\n NEXUS_DATA_DIR: '{env:NEXUS_DATA_DIR}',\n NEXUS_OPENCODE_CONFIG: opts.opencodeConfigPath,\n };\n if (opts.sandboxFlavor !== undefined && opts.sandboxFlavor !== '') {\n env['NEXUS_SANDBOX'] = opts.sandboxFlavor;\n }\n return {\n type: 'local',\n command: ['node', opts.cliPath, '--mode=server'],\n enabled: true,\n environment: env,\n };\n}\n\n/**\n * Execute the merge. Pure with respect to `opts.dryRun: true` — no fs\n * writes. Returns the final-or-proposed JSON + a diff string suitable\n * for printing.\n */\nexport function runInitOpencode(opts: InitOpencodeOptions): InitOpencodeResult {\n const existing = readExisting(opts.path);\n const merged = mergeNexusBlock(existing, opts);\n const before = existing === null ? '' : `${JSON.stringify(existing, null, 2)}\\n`;\n const after = `${JSON.stringify(merged, null, 2)}\\n`;\n const diff = simpleDiff(before, after);\n\n if (opts.dryRun === true) {\n return { path: opts.path, action: 'dry-run', diff };\n }\n\n if (existing !== null && before === after) {\n return { path: opts.path, action: 'unchanged', diff };\n }\n\n writeFileSync(opts.path, after, 'utf8');\n return { path: opts.path, action: existing === null ? 'created' : 'updated', diff };\n}\n\nfunction readExisting(path: string): OpencodeFile | null {\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, 'utf8');\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== 'object' || parsed === null) {\n throw new Error('opencode.json root must be an object');\n }\n return parsed as OpencodeFile;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse opencode.json at ${path}: ${msg}`);\n }\n}\n\nfunction mergeNexusBlock(existing: OpencodeFile | null, opts: InitOpencodeOptions): OpencodeFile {\n const block = buildNexusMcpBlock({\n cliPath: opts.cliPath,\n ...(opts.sandboxFlavor !== undefined && { sandboxFlavor: opts.sandboxFlavor }),\n opencodeConfigPath: opts.path,\n });\n\n if (existing === null) {\n return {\n $schema: 'https://opencode.ai/config.json',\n providers: { 'openai-compat': stubOpenaiCompatProvider() },\n permission: buildDefaultPermissionBlock(),\n mcp: { 'nexus-agents': block },\n };\n }\n\n // Idempotent merge: preserve user-customised fields if they exist on the\n // current MCP block (e.g. `enabled: false`). Update anything else.\n const existingMcp: Record<string, unknown> = existing.mcp ?? {};\n const rawNexus = existingMcp['nexus-agents'];\n const existingNexus: Partial<OpencodeMcpBlock> =\n typeof rawNexus === 'object' && rawNexus !== null ? rawNexus : {};\n const mergedBlock: OpencodeMcpBlock = {\n type: 'local',\n command: block.command,\n enabled: existingNexus.enabled ?? block.enabled,\n environment: { ...block.environment, ...(existingNexus.environment ?? {}) },\n };\n\n // Add the default permission block only when the operator has not set\n // their own — never overwrite an existing `permission` key.\n const permission = existing.permission ?? buildDefaultPermissionBlock();\n\n return {\n ...existing,\n permission,\n mcp: { ...existingMcp, 'nexus-agents': mergedBlock },\n };\n}\n\n/**\n * Stub for `providers.openai-compat`. Operators replace the placeholder\n * baseURL + key with their workspace-proxy values. Empty by design — we\n * never overwrite an existing provider block (see `mergeNexusBlock`).\n */\nfunction stubOpenaiCompatProvider(): Record<string, unknown> {\n return {\n npm: '@ai-sdk/openai-compatible',\n options: {\n baseURL: '<replace with workspace proxy URL>',\n apiKey: '{env:WORKSPACE_PROXY_KEY}',\n },\n };\n}\n\n/**\n * Cheap line-based diff. Three-line context, +/- prefixes. Good enough\n * for an operator preview; not a unified-diff replacement.\n */\nfunction simpleDiff(before: string, after: string): string {\n if (before === after) return '(no changes)';\n const beforeLines = before.split('\\n');\n const afterLines = after.split('\\n');\n const out: string[] = [];\n const max = Math.max(beforeLines.length, afterLines.length);\n for (let i = 0; i < max; i += 1) {\n const b = beforeLines[i];\n const a = afterLines[i];\n if (b === a) {\n out.push(` ${b ?? ''}`);\n } else {\n if (b !== undefined) out.push(`- ${b}`);\n if (a !== undefined) out.push(`+ ${a}`);\n }\n }\n return out.join('\\n');\n}\n\nexport function ensureOpencodeDirExists(path: string): void {\n const dir = dirname(path);\n if (!existsSync(dir)) {\n throw new Error(\n `Parent directory does not exist: ${dir}. Create it first or pass a path under an existing dir.`\n );\n }\n logger.debug('opencode.json target directory exists', { dir });\n}\n\n/**\n * --validate flow for `init --opencode` (follow-up to #2504).\n *\n * After the merge step writes the file, optionally probe the gateway it\n * points at. Reuses the same opencode-bridge loader (#2503) that the\n * runtime uses, so what we validate is exactly what the server will see\n * at boot.\n *\n * Returns:\n * - `{ ok: true, models }` when the gateway is reachable and returns ≥1 model\n * - `{ ok: false, reason }` for every failure path (config missing,\n * probe failed, zero models)\n *\n * Caller (cli-commands-handlers) maps to exit codes + stderr / stdout.\n * The API key never reaches the returned object — only model IDs +\n * baseURL appear in the success payload.\n */\nexport interface OpencodeValidateResult {\n readonly ok: boolean;\n readonly baseURL?: string;\n readonly models?: readonly string[];\n readonly reason?: string;\n}\n\nexport async function runOpencodeValidate(opencodePath: string): Promise<OpencodeValidateResult> {\n const config = readOpencodeGateway(opencodePath);\n if (config === null) {\n return {\n ok: false,\n reason:\n 'opencode.json does not resolve a usable gateway. Check that providers.openai-compat.options.{baseURL, apiKey} are set and that any {env:VAR} interpolation references are exported.',\n };\n }\n const result = await discoverModels({ baseUrl: config.baseURL, apiKey: config.apiKey });\n if (!result.ok) {\n return { ok: false, baseURL: config.baseURL, reason: result.error.message };\n }\n if (result.value.length === 0) {\n return {\n ok: false,\n baseURL: config.baseURL,\n reason: 'gateway returned 0 models. Check upstream provider quotas / list filters.',\n };\n }\n return {\n ok: true,\n baseURL: config.baseURL,\n models: result.value.map((m) => m.id),\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli/init-opencode.ts"],"sourcesContent":["/**\n * `nexus-agents init --opencode <path>` setup helper (#2504, child 4 of #2500).\n *\n * Injects the nexus-agents MCP block + recommended environment into an\n * existing `opencode.json` without overwriting other operator-set keys.\n * Pairs with the existing `init --portable --mcp-config` (which targets\n * Claude Code's `.mcp.json`). Different harness, same merge-not-overwrite\n * pattern.\n *\n * Behaviour:\n * - File exists → merge `mcp.nexus-agents` into the existing JSON.\n * Preserve every other key the operator has set (provider config,\n * model lists, theme, …). Idempotent: re-running produces the same\n * final file.\n * - File missing → write a minimal template with the nexus-agents MCP\n * block + a stubbed `providers.openai-compat` shell the operator\n * fills in (placeholder baseURL + `{env:WORKSPACE_PROXY_KEY}`).\n * - `--dry-run` → print the diff (proposed vs existing) without writing.\n * - `--validate` → after merge (or alongside dry-run), probe\n * `providers.openai-compat.options.baseURL/v1/models` with the\n * resolved apiKey, exit non-zero if unreachable.\n *\n * @module cli/init-opencode\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { createLogger } from '../core/index.js';\nimport { readOpencodeGateway } from '../config/opencode-bridge.js';\nimport { discoverModels } from '../adapters/openai-compat-adapter.js';\n\nconst logger = createLogger({ component: 'init-opencode' });\n\nexport interface InitOpencodeOptions {\n /** Absolute or relative path to opencode.json. */\n readonly path: string;\n /** Path to the nexus-agents CLI binary that the MCP block will spawn. */\n readonly cliPath: string;\n /** Sandbox flavor written to NEXUS_SANDBOX in the MCP environment. */\n readonly sandboxFlavor?: string;\n /** Print the diff without writing. */\n readonly dryRun?: boolean;\n}\n\nexport interface InitOpencodeResult {\n readonly path: string;\n readonly action: 'created' | 'updated' | 'unchanged' | 'dry-run';\n readonly diff: string;\n}\n\ninterface OpencodeMcpBlock {\n type: 'local';\n command: string[];\n enabled: boolean;\n environment: Record<string, string>;\n}\n\ninterface OpencodeFile {\n $schema?: string;\n providers?: Record<string, unknown>;\n mcp?: Record<string, unknown>;\n permission?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Conservative default OpenCode `permission` block (#2658). OpenCode\n * evaluates a glob map with **last-matching-rule-winning**, so the broad\n * `\"*\"` rule is listed first and the hard `deny` patterns last.\n *\n * - `edit` — everything `ask`s; secrets, keys, and `.git/` are hard-denied\n * regardless of operator confirmation. The deny patterns come after\n * `\"*\"` so last-match-wins makes them stick.\n * - `bash` — `ask` unconditionally. Shell is the highest-risk surface.\n * - `skill` — `allow`. Skills are trusted, in-repo, CI-validated content.\n *\n * Emitted only when the operator has not already set their own\n * `permission` block — never overwrites operator config.\n */\nexport function buildDefaultPermissionBlock(): Record<string, unknown> {\n return {\n edit: {\n '*': 'ask',\n '**/.env': 'deny',\n '**/.env.*': 'deny',\n '**/*.pem': 'deny',\n '**/*.key': 'deny',\n '**/id_rsa*': 'deny',\n '**/secrets/**': 'deny',\n '**/.git/**': 'deny',\n },\n bash: 'ask',\n skill: 'allow',\n };\n}\n\n/**\n * Build the canonical nexus-agents MCP block. Mirrors what\n * `Dockerfile.sandbox` writes today plus the env vars from #2501 + #2503.\n */\nexport function buildNexusMcpBlock(opts: {\n readonly cliPath: string;\n readonly sandboxFlavor?: string;\n readonly opencodeConfigPath: string;\n}): OpencodeMcpBlock {\n const env: Record<string, string> = {\n NEXUS_DATA_DIR: '{env:NEXUS_DATA_DIR}',\n NEXUS_OPENCODE_CONFIG: opts.opencodeConfigPath,\n };\n if (opts.sandboxFlavor !== undefined && opts.sandboxFlavor !== '') {\n env['NEXUS_SANDBOX'] = opts.sandboxFlavor;\n }\n return {\n type: 'local',\n command: ['node', opts.cliPath, '--mode=server'],\n enabled: true,\n environment: env,\n };\n}\n\n/**\n * Execute the merge. Pure with respect to `opts.dryRun: true` — no fs\n * writes. Returns the final-or-proposed JSON + a diff string suitable\n * for printing.\n */\nexport function runInitOpencode(opts: InitOpencodeOptions): InitOpencodeResult {\n const existing = readExisting(opts.path);\n const merged = mergeNexusBlock(existing, opts);\n const before = existing === null ? '' : `${JSON.stringify(existing, null, 2)}\\n`;\n const after = `${JSON.stringify(merged, null, 2)}\\n`;\n const diff = simpleDiff(before, after);\n\n if (opts.dryRun === true) {\n return { path: opts.path, action: 'dry-run', diff };\n }\n\n if (existing !== null && before === after) {\n return { path: opts.path, action: 'unchanged', diff };\n }\n\n writeFileSync(opts.path, after, 'utf8');\n return { path: opts.path, action: existing === null ? 'created' : 'updated', diff };\n}\n\nfunction readExisting(path: string): OpencodeFile | null {\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, 'utf8');\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== 'object' || parsed === null) {\n throw new Error('opencode.json root must be an object');\n }\n return parsed as OpencodeFile;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse opencode.json at ${path}: ${msg}`);\n }\n}\n\nfunction mergeNexusBlock(existing: OpencodeFile | null, opts: InitOpencodeOptions): OpencodeFile {\n const block = buildNexusMcpBlock({\n cliPath: opts.cliPath,\n ...(opts.sandboxFlavor !== undefined && { sandboxFlavor: opts.sandboxFlavor }),\n opencodeConfigPath: opts.path,\n });\n\n if (existing === null) {\n return {\n $schema: 'https://opencode.ai/config.json',\n providers: { 'openai-compat': stubOpenaiCompatProvider() },\n permission: buildDefaultPermissionBlock(),\n mcp: { 'nexus-agents': block },\n };\n }\n\n // Idempotent merge: preserve user-customised fields if they exist on the\n // current MCP block (e.g. `enabled: false`). Update anything else.\n const existingMcp: Record<string, unknown> = existing.mcp ?? {};\n const rawNexus = existingMcp['nexus-agents'];\n const existingNexus: Partial<OpencodeMcpBlock> =\n typeof rawNexus === 'object' && rawNexus !== null ? rawNexus : {};\n const mergedBlock: OpencodeMcpBlock = {\n type: 'local',\n command: block.command,\n enabled: existingNexus.enabled ?? block.enabled,\n environment: { ...block.environment, ...(existingNexus.environment ?? {}) },\n };\n\n // Add the default permission block only when the operator has not set\n // their own — never overwrite an existing `permission` key.\n const permission = existing.permission ?? buildDefaultPermissionBlock();\n\n return {\n ...existing,\n permission,\n mcp: { ...existingMcp, 'nexus-agents': mergedBlock },\n };\n}\n\n/**\n * Stub for `providers.openai-compat`. Operators replace the placeholder\n * baseURL + key with their workspace-proxy values. Empty by design — we\n * never overwrite an existing provider block (see `mergeNexusBlock`).\n */\nfunction stubOpenaiCompatProvider(): Record<string, unknown> {\n return {\n npm: '@ai-sdk/openai-compatible',\n options: {\n baseURL: '<replace with workspace proxy URL>',\n apiKey: '{env:WORKSPACE_PROXY_KEY}',\n },\n };\n}\n\n/**\n * Cheap line-based diff. Three-line context, +/- prefixes. Good enough\n * for an operator preview; not a unified-diff replacement.\n */\nfunction simpleDiff(before: string, after: string): string {\n if (before === after) return '(no changes)';\n const beforeLines = before.split('\\n');\n const afterLines = after.split('\\n');\n const out: string[] = [];\n const max = Math.max(beforeLines.length, afterLines.length);\n for (let i = 0; i < max; i += 1) {\n const b = beforeLines[i];\n const a = afterLines[i];\n if (b === a) {\n out.push(` ${b ?? ''}`);\n } else {\n if (b !== undefined) out.push(`- ${b}`);\n if (a !== undefined) out.push(`+ ${a}`);\n }\n }\n return out.join('\\n');\n}\n\nexport function ensureOpencodeDirExists(path: string): void {\n const dir = dirname(path);\n if (!existsSync(dir)) {\n throw new Error(\n `Parent directory does not exist: ${dir}. Create it first or pass a path under an existing dir.`\n );\n }\n logger.debug('opencode.json target directory exists', { dir });\n}\n\n/**\n * --validate flow for `init --opencode` (follow-up to #2504).\n *\n * After the merge step writes the file, optionally probe the gateway it\n * points at. Reuses the same opencode-bridge loader (#2503) that the\n * runtime uses, so what we validate is exactly what the server will see\n * at boot.\n *\n * Returns:\n * - `{ ok: true, models }` when the gateway is reachable and returns ≥1 model\n * - `{ ok: false, reason }` for every failure path (config missing,\n * probe failed, zero models)\n *\n * Caller (cli-commands-handlers) maps to exit codes + stderr / stdout.\n * The API key never reaches the returned object — only model IDs +\n * baseURL appear in the success payload.\n */\nexport interface OpencodeValidateResult {\n readonly ok: boolean;\n readonly baseURL?: string;\n readonly models?: readonly string[];\n readonly reason?: string;\n}\n\nexport async function runOpencodeValidate(opencodePath: string): Promise<OpencodeValidateResult> {\n const config = readOpencodeGateway(opencodePath);\n if (config === null) {\n return {\n ok: false,\n reason:\n 'opencode.json does not resolve a usable gateway. Check that providers.openai-compat.options.{baseURL, apiKey} are set and that any {env:VAR} interpolation references are exported.',\n };\n }\n const result = await discoverModels({ baseUrl: config.baseURL, apiKey: config.apiKey });\n if (!result.ok) {\n return { ok: false, baseURL: config.baseURL, reason: result.error.message };\n }\n if (result.value.length === 0) {\n return {\n ok: false,\n baseURL: config.baseURL,\n reason: 'gateway returned 0 models. Check upstream provider quotas / list filters.',\n };\n }\n return {\n ok: true,\n baseURL: config.baseURL,\n models: result.value.map((m) => m.id),\n };\n}\n"],"mappings":"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","names":[]}
@@ -2,11 +2,11 @@ import {
2
2
  IssueTriage,
3
3
  createIssueTriage,
4
4
  formatTriageComment
5
- } from "./chunk-22NGPM4P.js";
6
- import "./chunk-TFU5J53T.js";
7
- import "./chunk-NCGPKRRY.js";
8
- import "./chunk-JUUHARON.js";
9
- import "./chunk-25ZJ5TCC.js";
5
+ } from "./chunk-KWKGX7LC.js";
6
+ import "./chunk-E35RUETV.js";
7
+ import "./chunk-KAL22DYO.js";
8
+ import "./chunk-VSETLO4Q.js";
9
+ import "./chunk-Y5JA7XEO.js";
10
10
  import "./chunk-AXTD4TFK.js";
11
11
  import "./chunk-DHVMSIT5.js";
12
12
  import "./chunk-ZPWHCABL.js";
@@ -15,4 +15,4 @@ export {
15
15
  createIssueTriage,
16
16
  formatTriageComment
17
17
  };
18
- //# sourceMappingURL=issue-triage-S7RSXHCS.js.map
18
+ //# sourceMappingURL=issue-triage-KOIPTNDH.js.map
@@ -17,10 +17,10 @@ import {
17
17
  parseFindings,
18
18
  parseSeverity,
19
19
  sumFindings
20
- } from "./chunk-SDFC2IOJ.js";
20
+ } from "./chunk-YE2XMU3T.js";
21
21
  import "./chunk-CQ77TF4Y.js";
22
- import "./chunk-NCGPKRRY.js";
23
- import "./chunk-25ZJ5TCC.js";
22
+ import "./chunk-KAL22DYO.js";
23
+ import "./chunk-Y5JA7XEO.js";
24
24
  import "./chunk-AXTD4TFK.js";
25
25
  import "./chunk-DHVMSIT5.js";
26
26
  import "./chunk-ZPWHCABL.js";
@@ -44,4 +44,4 @@ export {
44
44
  parseSeverity,
45
45
  sumFindings
46
46
  };
47
- //# sourceMappingURL=pr-reviewer-helpers-UVDDO3T4.js.map
47
+ //# sourceMappingURL=pr-reviewer-helpers-CZDMP5UJ.js.map
@@ -7,7 +7,7 @@ import {
7
7
  loadGeneratedRegistryEntries,
8
8
  loadUserManifestOverlay,
9
9
  reloadDefaultRegistry
10
- } from "./chunk-25ZJ5TCC.js";
10
+ } from "./chunk-Y5JA7XEO.js";
11
11
  import "./chunk-AXTD4TFK.js";
12
12
  import {
13
13
  nexusDataPath
@@ -249,4 +249,4 @@ export {
249
249
  isValidRegistrySubcommand,
250
250
  registryCommand
251
251
  };
252
- //# sourceMappingURL=registry-command-DWOQ2IV4.js.map
252
+ //# sourceMappingURL=registry-command-5J6RJZMS.js.map
@@ -3,9 +3,9 @@ import {
3
3
  buildPlanFromAnalysis,
4
4
  generateSecurityPlan,
5
5
  resolveScannerData
6
- } from "./chunk-MLJAC2UH.js";
6
+ } from "./chunk-UB6APBIY.js";
7
7
  import "./chunk-HFOQKCD2.js";
8
- import "./chunk-25ZJ5TCC.js";
8
+ import "./chunk-Y5JA7XEO.js";
9
9
  import "./chunk-AXTD4TFK.js";
10
10
  import "./chunk-DHVMSIT5.js";
11
11
  import "./chunk-ZPWHCABL.js";
@@ -15,4 +15,4 @@ export {
15
15
  generateSecurityPlan,
16
16
  resolveScannerData
17
17
  };
18
- //# sourceMappingURL=repo-security-plan-5P23NS3N.js.map
18
+ //# sourceMappingURL=repo-security-plan-MKSCD567.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  AttributedInsightSchema,
3
3
  synthesizeResearch
4
- } from "./chunk-TZS5IKWW.js";
5
- import "./chunk-KOK66W2C.js";
4
+ } from "./chunk-3P7SMHBU.js";
5
+ import "./chunk-DJZGCMNU.js";
6
6
  import "./chunk-NUBSJGQZ.js";
7
- import "./chunk-25ZJ5TCC.js";
7
+ import "./chunk-Y5JA7XEO.js";
8
8
  import "./chunk-AXTD4TFK.js";
9
9
  import "./chunk-DHVMSIT5.js";
10
10
  import "./chunk-ZPWHCABL.js";
@@ -12,4 +12,4 @@ export {
12
12
  AttributedInsightSchema,
13
13
  synthesizeResearch
14
14
  };
15
- //# sourceMappingURL=research-helpers-synthesize-5AIKCDA2.js.map
15
+ //# sourceMappingURL=research-helpers-synthesize-TIT43XF4.js.map
@@ -2,7 +2,7 @@ import {
2
2
  DEFAULT_ROUTING_MEMORY_CONFIG,
3
3
  RoutingMemory,
4
4
  createRoutingMemory
5
- } from "./chunk-25ZJ5TCC.js";
5
+ } from "./chunk-Y5JA7XEO.js";
6
6
  import "./chunk-AXTD4TFK.js";
7
7
  import "./chunk-DHVMSIT5.js";
8
8
  import "./chunk-ZPWHCABL.js";
@@ -11,4 +11,4 @@ export {
11
11
  RoutingMemory,
12
12
  createRoutingMemory
13
13
  };
14
- //# sourceMappingURL=routing-memory-BOTPCHXJ.js.map
14
+ //# sourceMappingURL=routing-memory-HAWL7RKO.js.map
@@ -6,8 +6,8 @@ import {
6
6
  SessionMemory,
7
7
  SessionMemoryError,
8
8
  createSessionMemory
9
- } from "./chunk-P7BKE3OH.js";
10
- import "./chunk-25ZJ5TCC.js";
9
+ } from "./chunk-IVINLVGP.js";
10
+ import "./chunk-Y5JA7XEO.js";
11
11
  import "./chunk-AXTD4TFK.js";
12
12
  import "./chunk-DHVMSIT5.js";
13
13
  import "./chunk-ZPWHCABL.js";
@@ -20,4 +20,4 @@ export {
20
20
  SessionMemoryError,
21
21
  createSessionMemory
22
22
  };
23
- //# sourceMappingURL=session-memory-KOUNAH5O.js.map
23
+ //# sourceMappingURL=session-memory-RPSM7Y4Q.js.map
@@ -8,22 +8,23 @@ import {
8
8
  runWizard,
9
9
  setupCommand,
10
10
  setupCommandAsync
11
- } from "./chunk-YVVUKVZV.js";
12
- import "./chunk-YXLJNG7E.js";
13
- import "./chunk-V4XEPUCM.js";
14
- import "./chunk-TOH2HSHH.js";
15
- import "./chunk-KNOIZAYR.js";
11
+ } from "./chunk-ASJTVA7W.js";
12
+ import "./chunk-4PTAG65V.js";
13
+ import "./chunk-YUCJ7VSJ.js";
14
+ import "./chunk-JVBI4WKP.js";
15
+ import "./chunk-H5KLPUAL.js";
16
16
  import "./chunk-NUBSJGQZ.js";
17
17
  import "./chunk-6T3EPABN.js";
18
18
  import "./chunk-YPNQPT2F.js";
19
- import "./chunk-ZLQLBSSQ.js";
20
- import "./chunk-LSMFXWRN.js";
21
- import "./chunk-OPZQQWVW.js";
22
- import "./chunk-CERGIC66.js";
19
+ import "./chunk-K6NXLBHG.js";
20
+ import "./chunk-6RRAQFSX.js";
21
+ import "./chunk-DGR27REV.js";
22
+ import "./chunk-OM2VVQKB.js";
23
+ import "./chunk-RAIFOYRM.js";
23
24
  import "./chunk-QZUG667L.js";
24
- import "./chunk-3DI4CEC7.js";
25
- import "./chunk-QPKG7LS7.js";
26
- import "./chunk-25ZJ5TCC.js";
25
+ import "./chunk-SGIWJYLJ.js";
26
+ import "./chunk-Z77MIV3C.js";
27
+ import "./chunk-Y5JA7XEO.js";
27
28
  import "./chunk-AXTD4TFK.js";
28
29
  import "./chunk-DHVMSIT5.js";
29
30
  import "./chunk-ZPWHCABL.js";
@@ -38,4 +39,4 @@ export {
38
39
  setupCommand,
39
40
  setupCommandAsync
40
41
  };
41
- //# sourceMappingURL=setup-command-DW3DQJMG.js.map
42
+ //# sourceMappingURL=setup-command-SORJLYU3.js.map
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  runConfigInitSync
3
- } from "./chunk-YXLJNG7E.js";
4
- import "./chunk-25ZJ5TCC.js";
3
+ } from "./chunk-4PTAG65V.js";
4
+ import "./chunk-Y5JA7XEO.js";
5
5
  import "./chunk-AXTD4TFK.js";
6
6
  import "./chunk-DHVMSIT5.js";
7
7
  import "./chunk-ZPWHCABL.js";
8
8
  export {
9
9
  runConfigInitSync
10
10
  };
11
- //# sourceMappingURL=setup-config-BBUFGXW2.js.map
11
+ //# sourceMappingURL=setup-config-YDA4MY4N.js.map
@@ -1,13 +1,15 @@
1
+ import {
2
+ CUSTOM_API_DEFAULT_MODEL
3
+ } from "./chunk-DGR27REV.js";
1
4
  import {
2
5
  CUSTOM_API_ALLOW_PRIVATE_ENV,
3
6
  CUSTOM_API_BASE_URL_ENV,
4
- CUSTOM_API_DEFAULT_MODEL,
5
7
  validateCustomApiBaseUrl
6
- } from "./chunk-OPZQQWVW.js";
8
+ } from "./chunk-OM2VVQKB.js";
7
9
  import {
8
10
  err,
9
11
  ok
10
- } from "./chunk-25ZJ5TCC.js";
12
+ } from "./chunk-Y5JA7XEO.js";
11
13
  import "./chunk-AXTD4TFK.js";
12
14
  import "./chunk-DHVMSIT5.js";
13
15
  import "./chunk-ZPWHCABL.js";
@@ -105,4 +107,4 @@ function buildShellFragment(params) {
105
107
  export {
106
108
  configureCustomApi
107
109
  };
108
- //# sourceMappingURL=setup-custom-api-PSVW6DIY.js.map
110
+ //# sourceMappingURL=setup-custom-api-BZSHB2M5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli/setup-custom-api.ts"],"sourcesContent":["/**\n * Interactive setup for a custom OpenAI-compatible gateway (#2124).\n *\n * Closes the last child of epic #2119. The runtime adapter (#2125) is\n * already live — this module is the ergonomics layer: a single command\n * that validates the URL, probes connectivity, and tells the user\n * exactly what env vars to set for their shell.\n *\n * Writes nothing by default (dry-run is the safe behavior for a CLI\n * that touches shell state). Prints the shell fragment; user copies\n * it into ~/.bashrc / ~/.zshrc / ~/.config/fish/config.fish. Rationale:\n * harness-neutral — we don't know which shell the user runs or where\n * their nexus-agents.yaml lives.\n *\n * @module cli/setup-custom-api\n */\n\nimport { createInterface } from 'node:readline';\nimport { validateCustomApiBaseUrl } from '../adapters/sdk/custom-api-validation.js';\nimport { CUSTOM_API_BASE_URL_ENV, CUSTOM_API_ALLOW_PRIVATE_ENV } from '../adapters/sdk/types.js';\nimport { CUSTOM_API_DEFAULT_MODEL as DEFAULT_MODEL } from '../config/defaults.js';\nimport { ok, err, type Result } from '../core/index.js';\n\n/** Inputs to `configureCustomApi`. */\nexport interface CustomApiSetupInput {\n /** Gateway base URL (required). SSRF guard from adapters/sdk is applied. */\n readonly baseUrl: string;\n /** API key. If absent, `configureCustomApi` will prompt (or fail non-interactively). */\n readonly apiKey?: string;\n /** Model id to default to. Omitted → documented fallback \"gpt-4o\". */\n readonly model?: string;\n /** Skip the TTY prompt for the API key — useful for CI and scripting. */\n readonly nonInteractive?: boolean;\n /** Omit HTTP probe — useful for dry-run / offline verify. */\n readonly skipProbe?: boolean;\n /** Allow private / loopback base URLs (overrides the SSRF guard). */\n readonly allowPrivate?: boolean;\n /** Stream for prompts/output. Defaults to process.stdout / stdin. */\n readonly stream?: { readonly output: NodeJS.WritableStream };\n /**\n * Pluggable HTTP fetcher for tests. Defaults to global fetch.\n * Called as `fetch(url, { headers })`; should return `{ status, body }`.\n */\n readonly fetcher?: HttpFetcher;\n}\n\n/** Minimal HTTP fetcher shape — lets tests inject a mock without network. */\nexport type HttpFetcher = (\n url: string,\n init: { readonly headers: Readonly<Record<string, string>> }\n) => Promise<{ readonly status: number; readonly body: string }>;\n\n/** Outcome of `configureCustomApi`. */\nexport interface CustomApiSetupResult {\n /** Validated base URL (may differ trivially from input — e.g. canonical form). */\n readonly baseUrl: string;\n /** Resolved model id. */\n readonly model: string;\n /** Whether the /v1/models probe succeeded (always true unless skipped). */\n readonly probeSucceeded: boolean;\n /**\n * Shell fragment the user should add to their shell rc so nexus-agents\n * picks up the gateway on subsequent invocations. Already-set env\n * equivalents of these lines are all that's needed at runtime.\n */\n readonly shellFragment: string;\n}\n\n/**\n * Configures a custom OpenAI-compatible gateway.\n *\n * Steps:\n * 1. Validate base URL via the SSRF guard (same as the runtime adapter).\n * 2. Resolve the API key (from input, env, or TTY prompt).\n * 3. Probe `GET {baseUrl}/models` with Bearer auth to confirm connectivity.\n * 4. Emit the shell-fragment the user adds to their shell rc.\n *\n * Returns an `ok` Result on success, or a `ConfigError`-bearing `err`\n * Result on any failure (invalid URL, missing API key in non-interactive,\n * probe failure). Never throws.\n */\nexport async function configureCustomApi(\n input: CustomApiSetupInput\n): Promise<Result<CustomApiSetupResult, Error>> {\n const urlValidation = validateCustomApiBaseUrl(input.baseUrl, {\n ...(input.allowPrivate === true ? { allowPrivate: true } : {}),\n });\n if (!urlValidation.ok) return err(urlValidation.error);\n const baseUrl = urlValidation.value.toString();\n\n const apiKeyResult = await resolveApiKey(input);\n if (!apiKeyResult.ok) return err(apiKeyResult.error);\n const apiKey = apiKeyResult.value;\n\n const probeSucceeded = input.skipProbe === true ? false : await runProbe(baseUrl, apiKey, input);\n if (input.skipProbe !== true && !probeSucceeded) {\n return err(\n new Error(\n `Gateway probe failed: GET ${stripTrailingSlash(baseUrl)}/models did not return 2xx. ` +\n `Check that the URL is the chat-completions base (typically ends with /v1) and the API key has /models read scope.`\n )\n );\n }\n\n const model = input.model ?? DEFAULT_MODEL;\n return ok({\n baseUrl,\n model,\n probeSucceeded,\n shellFragment: buildShellFragment({ baseUrl, apiKey, model, allowPrivate: input.allowPrivate }),\n });\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith('/') ? s.slice(0, -1) : s;\n}\n\n/**\n * Performs the /v1/models probe. Returns true on any 2xx response.\n * Swallows errors into `false` — the caller reports the failure.\n */\nasync function runProbe(\n baseUrl: string,\n apiKey: string,\n input: CustomApiSetupInput\n): Promise<boolean> {\n const url = `${stripTrailingSlash(baseUrl)}/models`;\n const fetcher = input.fetcher ?? defaultFetcher;\n try {\n const res = await fetcher(url, {\n headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json' },\n });\n return res.status >= 200 && res.status < 300;\n } catch {\n return false;\n }\n}\n\nconst defaultFetcher: HttpFetcher = async (url, init) => {\n const res = await fetch(url, { headers: init.headers });\n const body = await res.text();\n return { status: res.status, body };\n};\n\n/**\n * Resolves the API key in priority order: input → env var → TTY prompt\n * (unless non-interactive, in which case the absence is a fail).\n */\nasync function resolveApiKey(input: CustomApiSetupInput): Promise<Result<string, Error>> {\n if (input.apiKey !== undefined && input.apiKey !== '') return ok(input.apiKey);\n const envKey = process.env['NEXUS_CUSTOM_API_KEY'];\n if (envKey !== undefined && envKey !== '') return ok(envKey);\n if (input.nonInteractive === true) {\n return err(\n new Error(\n 'Custom API key required but not provided. Pass --custom-api-key or set NEXUS_CUSTOM_API_KEY.'\n )\n );\n }\n const prompted = await promptForApiKey(input.stream?.output ?? process.stdout);\n if (prompted === '') {\n return err(new Error('No API key entered; aborting.'));\n }\n return ok(prompted);\n}\n\nasync function promptForApiKey(out: NodeJS.WritableStream): Promise<string> {\n out.write('Enter the API key for your custom gateway: ');\n const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });\n try {\n return await new Promise<string>((resolve) => {\n rl.question('', (answer) => {\n resolve(answer.trim());\n });\n });\n } finally {\n rl.close();\n }\n}\n\n/**\n * Formats the shell fragment the user should paste into their shell rc.\n * Designed to be POSIX-portable; fish users can adapt `export` to `set -gx`.\n */\nfunction buildShellFragment(params: {\n readonly baseUrl: string;\n readonly apiKey: string;\n readonly model: string;\n readonly allowPrivate: boolean | undefined;\n}): string {\n const { baseUrl, apiKey, model, allowPrivate } = params;\n const lines: string[] = [\n '# nexus-agents custom-openai gateway (nexus-agents setup --custom-api)',\n `export ${CUSTOM_API_BASE_URL_ENV}=\"${baseUrl}\"`,\n `export NEXUS_CUSTOM_API_KEY=\"${apiKey}\"`,\n `export NEXUS_CUSTOM_MODEL=\"${model}\"`,\n ];\n if (allowPrivate === true) {\n lines.push(`export ${CUSTOM_API_ALLOW_PRIVATE_ENV}=1 # SSRF-guard bypass`);\n }\n return lines.join('\\n') + '\\n';\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli/setup-custom-api.ts"],"sourcesContent":["/**\n * Interactive setup for a custom OpenAI-compatible gateway (#2124).\n *\n * Closes the last child of epic #2119. The runtime adapter (#2125) is\n * already live — this module is the ergonomics layer: a single command\n * that validates the URL, probes connectivity, and tells the user\n * exactly what env vars to set for their shell.\n *\n * Writes nothing by default (dry-run is the safe behavior for a CLI\n * that touches shell state). Prints the shell fragment; user copies\n * it into ~/.bashrc / ~/.zshrc / ~/.config/fish/config.fish. Rationale:\n * harness-neutral — we don't know which shell the user runs or where\n * their nexus-agents.yaml lives.\n *\n * @module cli/setup-custom-api\n */\n\nimport { createInterface } from 'node:readline';\nimport { validateCustomApiBaseUrl } from '../adapters/sdk/custom-api-validation.js';\nimport { CUSTOM_API_BASE_URL_ENV, CUSTOM_API_ALLOW_PRIVATE_ENV } from '../adapters/sdk/types.js';\nimport { CUSTOM_API_DEFAULT_MODEL as DEFAULT_MODEL } from '../config/defaults.js';\nimport { ok, err, type Result } from '../core/index.js';\n\n/** Inputs to `configureCustomApi`. */\nexport interface CustomApiSetupInput {\n /** Gateway base URL (required). SSRF guard from adapters/sdk is applied. */\n readonly baseUrl: string;\n /** API key. If absent, `configureCustomApi` will prompt (or fail non-interactively). */\n readonly apiKey?: string;\n /** Model id to default to. Omitted → documented fallback \"gpt-4o\". */\n readonly model?: string;\n /** Skip the TTY prompt for the API key — useful for CI and scripting. */\n readonly nonInteractive?: boolean;\n /** Omit HTTP probe — useful for dry-run / offline verify. */\n readonly skipProbe?: boolean;\n /** Allow private / loopback base URLs (overrides the SSRF guard). */\n readonly allowPrivate?: boolean;\n /** Stream for prompts/output. Defaults to process.stdout / stdin. */\n readonly stream?: { readonly output: NodeJS.WritableStream };\n /**\n * Pluggable HTTP fetcher for tests. Defaults to global fetch.\n * Called as `fetch(url, { headers })`; should return `{ status, body }`.\n */\n readonly fetcher?: HttpFetcher;\n}\n\n/** Minimal HTTP fetcher shape — lets tests inject a mock without network. */\nexport type HttpFetcher = (\n url: string,\n init: { readonly headers: Readonly<Record<string, string>> }\n) => Promise<{ readonly status: number; readonly body: string }>;\n\n/** Outcome of `configureCustomApi`. */\nexport interface CustomApiSetupResult {\n /** Validated base URL (may differ trivially from input — e.g. canonical form). */\n readonly baseUrl: string;\n /** Resolved model id. */\n readonly model: string;\n /** Whether the /v1/models probe succeeded (always true unless skipped). */\n readonly probeSucceeded: boolean;\n /**\n * Shell fragment the user should add to their shell rc so nexus-agents\n * picks up the gateway on subsequent invocations. Already-set env\n * equivalents of these lines are all that's needed at runtime.\n */\n readonly shellFragment: string;\n}\n\n/**\n * Configures a custom OpenAI-compatible gateway.\n *\n * Steps:\n * 1. Validate base URL via the SSRF guard (same as the runtime adapter).\n * 2. Resolve the API key (from input, env, or TTY prompt).\n * 3. Probe `GET {baseUrl}/models` with Bearer auth to confirm connectivity.\n * 4. Emit the shell-fragment the user adds to their shell rc.\n *\n * Returns an `ok` Result on success, or a `ConfigError`-bearing `err`\n * Result on any failure (invalid URL, missing API key in non-interactive,\n * probe failure). Never throws.\n */\nexport async function configureCustomApi(\n input: CustomApiSetupInput\n): Promise<Result<CustomApiSetupResult, Error>> {\n const urlValidation = validateCustomApiBaseUrl(input.baseUrl, {\n ...(input.allowPrivate === true ? { allowPrivate: true } : {}),\n });\n if (!urlValidation.ok) return err(urlValidation.error);\n const baseUrl = urlValidation.value.toString();\n\n const apiKeyResult = await resolveApiKey(input);\n if (!apiKeyResult.ok) return err(apiKeyResult.error);\n const apiKey = apiKeyResult.value;\n\n const probeSucceeded = input.skipProbe === true ? false : await runProbe(baseUrl, apiKey, input);\n if (input.skipProbe !== true && !probeSucceeded) {\n return err(\n new Error(\n `Gateway probe failed: GET ${stripTrailingSlash(baseUrl)}/models did not return 2xx. ` +\n `Check that the URL is the chat-completions base (typically ends with /v1) and the API key has /models read scope.`\n )\n );\n }\n\n const model = input.model ?? DEFAULT_MODEL;\n return ok({\n baseUrl,\n model,\n probeSucceeded,\n shellFragment: buildShellFragment({ baseUrl, apiKey, model, allowPrivate: input.allowPrivate }),\n });\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith('/') ? s.slice(0, -1) : s;\n}\n\n/**\n * Performs the /v1/models probe. Returns true on any 2xx response.\n * Swallows errors into `false` — the caller reports the failure.\n */\nasync function runProbe(\n baseUrl: string,\n apiKey: string,\n input: CustomApiSetupInput\n): Promise<boolean> {\n const url = `${stripTrailingSlash(baseUrl)}/models`;\n const fetcher = input.fetcher ?? defaultFetcher;\n try {\n const res = await fetcher(url, {\n headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json' },\n });\n return res.status >= 200 && res.status < 300;\n } catch {\n return false;\n }\n}\n\nconst defaultFetcher: HttpFetcher = async (url, init) => {\n const res = await fetch(url, { headers: init.headers });\n const body = await res.text();\n return { status: res.status, body };\n};\n\n/**\n * Resolves the API key in priority order: input → env var → TTY prompt\n * (unless non-interactive, in which case the absence is a fail).\n */\nasync function resolveApiKey(input: CustomApiSetupInput): Promise<Result<string, Error>> {\n if (input.apiKey !== undefined && input.apiKey !== '') return ok(input.apiKey);\n const envKey = process.env['NEXUS_CUSTOM_API_KEY'];\n if (envKey !== undefined && envKey !== '') return ok(envKey);\n if (input.nonInteractive === true) {\n return err(\n new Error(\n 'Custom API key required but not provided. Pass --custom-api-key or set NEXUS_CUSTOM_API_KEY.'\n )\n );\n }\n const prompted = await promptForApiKey(input.stream?.output ?? process.stdout);\n if (prompted === '') {\n return err(new Error('No API key entered; aborting.'));\n }\n return ok(prompted);\n}\n\nasync function promptForApiKey(out: NodeJS.WritableStream): Promise<string> {\n out.write('Enter the API key for your custom gateway: ');\n const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });\n try {\n return await new Promise<string>((resolve) => {\n rl.question('', (answer) => {\n resolve(answer.trim());\n });\n });\n } finally {\n rl.close();\n }\n}\n\n/**\n * Formats the shell fragment the user should paste into their shell rc.\n * Designed to be POSIX-portable; fish users can adapt `export` to `set -gx`.\n */\nfunction buildShellFragment(params: {\n readonly baseUrl: string;\n readonly apiKey: string;\n readonly model: string;\n readonly allowPrivate: boolean | undefined;\n}): string {\n const { baseUrl, apiKey, model, allowPrivate } = params;\n const lines: string[] = [\n '# nexus-agents custom-openai gateway (nexus-agents setup --custom-api)',\n `export ${CUSTOM_API_BASE_URL_ENV}=\"${baseUrl}\"`,\n `export NEXUS_CUSTOM_API_KEY=\"${apiKey}\"`,\n `export NEXUS_CUSTOM_MODEL=\"${model}\"`,\n ];\n if (allowPrivate === true) {\n lines.push(`export ${CUSTOM_API_ALLOW_PRIVATE_ENV}=1 # SSRF-guard bypass`);\n }\n return lines.join('\\n') + '\\n';\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,SAAS,uBAAuB;AAgEhC,eAAsB,mBACpB,OAC8C;AAC9C,QAAM,gBAAgB,yBAAyB,MAAM,SAAS;AAAA,IAC5D,GAAI,MAAM,iBAAiB,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;AAAA,EAC9D,CAAC;AACD,MAAI,CAAC,cAAc,GAAI,QAAO,IAAI,cAAc,KAAK;AACrD,QAAM,UAAU,cAAc,MAAM,SAAS;AAE7C,QAAM,eAAe,MAAM,cAAc,KAAK;AAC9C,MAAI,CAAC,aAAa,GAAI,QAAO,IAAI,aAAa,KAAK;AACnD,QAAM,SAAS,aAAa;AAE5B,QAAM,iBAAiB,MAAM,cAAc,OAAO,QAAQ,MAAM,SAAS,SAAS,QAAQ,KAAK;AAC/F,MAAI,MAAM,cAAc,QAAQ,CAAC,gBAAgB;AAC/C,WAAO;AAAA,MACL,IAAI;AAAA,QACF,6BAA6B,mBAAmB,OAAO,CAAC;AAAA,MAE1D;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM,SAAS;AAC7B,SAAO,GAAG;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,mBAAmB,EAAE,SAAS,QAAQ,OAAO,cAAc,MAAM,aAAa,CAAC;AAAA,EAChG,CAAC;AACH;AAEA,SAAS,mBAAmB,GAAmB;AAC7C,SAAO,EAAE,SAAS,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI;AAC5C;AAMA,eAAe,SACb,SACA,QACA,OACkB;AAClB,QAAM,MAAM,GAAG,mBAAmB,OAAO,CAAC;AAC1C,QAAM,UAAU,MAAM,WAAW;AACjC,MAAI;AACF,UAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,MAC7B,SAAS,EAAE,eAAe,UAAU,MAAM,IAAI,QAAQ,mBAAmB;AAAA,IAC3E,CAAC;AACD,WAAO,IAAI,UAAU,OAAO,IAAI,SAAS;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,iBAA8B,OAAO,KAAK,SAAS;AACvD,QAAM,MAAM,MAAM,MAAM,KAAK,EAAE,SAAS,KAAK,QAAQ,CAAC;AACtD,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,SAAO,EAAE,QAAQ,IAAI,QAAQ,KAAK;AACpC;AAMA,eAAe,cAAc,OAA4D;AACvF,MAAI,MAAM,WAAW,UAAa,MAAM,WAAW,GAAI,QAAO,GAAG,MAAM,MAAM;AAC7E,QAAM,SAAS,QAAQ,IAAI,sBAAsB;AACjD,MAAI,WAAW,UAAa,WAAW,GAAI,QAAO,GAAG,MAAM;AAC3D,MAAI,MAAM,mBAAmB,MAAM;AACjC,WAAO;AAAA,MACL,IAAI;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,QAAM,WAAW,MAAM,gBAAgB,MAAM,QAAQ,UAAU,QAAQ,MAAM;AAC7E,MAAI,aAAa,IAAI;AACnB,WAAO,IAAI,IAAI,MAAM,+BAA+B,CAAC;AAAA,EACvD;AACA,SAAO,GAAG,QAAQ;AACpB;AAEA,eAAe,gBAAgB,KAA6C;AAC1E,MAAI,MAAM,6CAA6C;AACvD,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,QAAQ,UAAU,KAAK,CAAC;AAC3F,MAAI;AACF,WAAO,MAAM,IAAI,QAAgB,CAAC,YAAY;AAC5C,SAAG,SAAS,IAAI,CAAC,WAAW;AAC1B,gBAAQ,OAAO,KAAK,CAAC;AAAA,MACvB,CAAC;AAAA,IACH,CAAC;AAAA,EACH,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;AAMA,SAAS,mBAAmB,QAKjB;AACT,QAAM,EAAE,SAAS,QAAQ,OAAO,aAAa,IAAI;AACjD,QAAM,QAAkB;AAAA,IACtB;AAAA,IACA,UAAU,uBAAuB,KAAK,OAAO;AAAA,IAC7C,gCAAgC,MAAM;AAAA,IACtC,8BAA8B,KAAK;AAAA,EACrC;AACA,MAAI,iBAAiB,MAAM;AACzB,UAAM,KAAK,UAAU,4BAA4B,0BAA0B;AAAA,EAC7E;AACA,SAAO,MAAM,KAAK,IAAI,IAAI;AAC5B;","names":[]}
@@ -3,13 +3,13 @@ import {
3
3
  getToolMemory,
4
4
  reinitializeMemoryBackends,
5
5
  shutdownToolMemory
6
- } from "./chunk-NDNJ3TBM.js";
6
+ } from "./chunk-EDWON3X5.js";
7
7
  import "./chunk-6T3EPABN.js";
8
- import "./chunk-P7BKE3OH.js";
8
+ import "./chunk-IVINLVGP.js";
9
9
  import "./chunk-PINS44XA.js";
10
10
  import "./chunk-KSQSWEFH.js";
11
- import "./chunk-3DI4CEC7.js";
12
- import "./chunk-25ZJ5TCC.js";
11
+ import "./chunk-SGIWJYLJ.js";
12
+ import "./chunk-Y5JA7XEO.js";
13
13
  import "./chunk-AXTD4TFK.js";
14
14
  import "./chunk-DHVMSIT5.js";
15
15
  import "./chunk-ZPWHCABL.js";
@@ -19,4 +19,4 @@ export {
19
19
  reinitializeMemoryBackends,
20
20
  shutdownToolMemory
21
21
  };
22
- //# sourceMappingURL=tool-memory-RBACIEGW.js.map
22
+ //# sourceMappingURL=tool-memory-FCEJMKEH.js.map
@@ -4,15 +4,16 @@ import {
4
4
  getGlobalRegistry,
5
5
  resetGlobalRegistry,
6
6
  withDefaultOnRetirement
7
- } from "./chunk-4NYZ2VTW.js";
8
- import "./chunk-ZLQLBSSQ.js";
9
- import "./chunk-LSMFXWRN.js";
10
- import "./chunk-OPZQQWVW.js";
11
- import "./chunk-CERGIC66.js";
7
+ } from "./chunk-MRRKFMYJ.js";
8
+ import "./chunk-K6NXLBHG.js";
9
+ import "./chunk-6RRAQFSX.js";
10
+ import "./chunk-DGR27REV.js";
11
+ import "./chunk-OM2VVQKB.js";
12
+ import "./chunk-RAIFOYRM.js";
12
13
  import "./chunk-QZUG667L.js";
13
- import "./chunk-3DI4CEC7.js";
14
- import "./chunk-QPKG7LS7.js";
15
- import "./chunk-25ZJ5TCC.js";
14
+ import "./chunk-SGIWJYLJ.js";
15
+ import "./chunk-Z77MIV3C.js";
16
+ import "./chunk-Y5JA7XEO.js";
16
17
  import "./chunk-AXTD4TFK.js";
17
18
  import "./chunk-DHVMSIT5.js";
18
19
  import "./chunk-ZPWHCABL.js";
@@ -23,4 +24,4 @@ export {
23
24
  resetGlobalRegistry,
24
25
  withDefaultOnRetirement
25
26
  };
26
- //# sourceMappingURL=unified-registry-35EIUQ2M.js.map
27
+ //# sourceMappingURL=unified-registry-JWPD2ES2.js.map
@@ -5,7 +5,7 @@ import {
5
5
  getModelWeatherSummary,
6
6
  queryWithLookback,
7
7
  shouldExplore
8
- } from "./chunk-25ZJ5TCC.js";
8
+ } from "./chunk-Y5JA7XEO.js";
9
9
  import "./chunk-AXTD4TFK.js";
10
10
  import "./chunk-DHVMSIT5.js";
11
11
  import "./chunk-ZPWHCABL.js";
@@ -17,4 +17,4 @@ export {
17
17
  queryWithLookback,
18
18
  shouldExplore
19
19
  };
20
- //# sourceMappingURL=weather-report-YXGRGPCX.js.map
20
+ //# sourceMappingURL=weather-report-VNABZRH7.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-agents",
3
- "version": "2.174.0",
3
+ "version": "2.174.2",
4
4
  "description": "Governance substrate for AI coding agents — adversarial PR review, drift-detected rules, tamper-evident audit, and closed-loop outcome routing for Claude, Codex, Gemini, and OpenCode",
5
5
  "mcpName": "io.github.nexus-substrate/nexus-agents",
6
6
  "license": "MIT",
@@ -60,14 +60,14 @@
60
60
  "node": ">=22.0.0"
61
61
  },
62
62
  "optionalDependencies": {
63
- "@atproto/api": "^0.20.36"
63
+ "@atproto/api": "^0.20.38"
64
64
  },
65
65
  "dependencies": {
66
66
  "@anthropic-ai/sdk": "^0.115.0",
67
67
  "@ast-grep/lang-go": "0.0.6",
68
68
  "@ast-grep/lang-python": "0.0.6",
69
69
  "@ast-grep/napi": "0.45.0",
70
- "@google/genai": "^2.15.0",
70
+ "@google/genai": "^2.16.0",
71
71
  "@modelcontextprotocol/sdk": "^1.30.0",
72
72
  "better-sqlite3": "^12.11.1",
73
73
  "jsonc-parser": "^3.3.1",
@@ -81,13 +81,13 @@
81
81
  "zod": "^4.4.3"
82
82
  },
83
83
  "devDependencies": {
84
- "@ai-sdk/anthropic": "^3.0.104",
85
- "@ai-sdk/google": "^3.0.103",
86
- "@ai-sdk/openai": "^3.0.90",
84
+ "@ai-sdk/anthropic": "^3.0.107",
85
+ "@ai-sdk/google": "^3.0.104",
86
+ "@ai-sdk/openai": "^3.0.91",
87
87
  "@types/better-sqlite3": "^7.6.13",
88
- "@types/semver": "^7.7.1",
88
+ "@types/semver": "^7.8.0",
89
89
  "@vitest/coverage-v8": "4.1.10",
90
- "ai": "^6.0.238",
90
+ "ai": "^6.0.246",
91
91
  "fast-check": "^4.9.0",
92
92
  "tsup": "^8.5.1",
93
93
  "typedoc": "0.28.20",