@pugi/cli 0.1.0-beta.99 → 1.0.0-alpha.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 (448) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -191
  3. package/bin/pugi +8 -0
  4. package/package.json +15 -71
  5. package/postinstall.mjs +31 -0
  6. package/CHANGELOG.md +0 -132
  7. package/THIRD_PARTY_NOTICES.md +0 -40
  8. package/assets/pugi-mascot.ansi +0 -16
  9. package/assets/pugi-prozr2-mascot.ansi +0 -9
  10. package/bin/run.js +0 -34
  11. package/dist/commands/deploy.js +0 -439
  12. package/dist/commands/flatten.js +0 -191
  13. package/dist/commands/jobs-watch.js +0 -201
  14. package/dist/commands/jobs.js +0 -260
  15. package/dist/commands/retro.js +0 -210
  16. package/dist/commands/smoke.js +0 -133
  17. package/dist/core/agent-progress/cleanup.js +0 -134
  18. package/dist/core/agent-progress/schema.js +0 -144
  19. package/dist/core/agent-progress/writer.js +0 -101
  20. package/dist/core/agents/adaptive-router.js +0 -330
  21. package/dist/core/agents/loader.js +0 -104
  22. package/dist/core/agents/query-decomposer.js +0 -297
  23. package/dist/core/agents/registry.js +0 -69
  24. package/dist/core/approvals/shortcut-resolver.js +0 -98
  25. package/dist/core/artifact-chain/dispatcher.js +0 -148
  26. package/dist/core/artifact-chain/exporter.js +0 -164
  27. package/dist/core/artifact-chain/state.js +0 -243
  28. package/dist/core/artifact-chain/steps.js +0 -169
  29. package/dist/core/ask-user/question.js +0 -92
  30. package/dist/core/audit/audit-trail.js +0 -275
  31. package/dist/core/auth/ensure-authenticated.js +0 -129
  32. package/dist/core/auth/env-provider.js +0 -238
  33. package/dist/core/auto-open-browser.js +0 -128
  34. package/dist/core/auto-update/channels.js +0 -122
  35. package/dist/core/auto-update/checker.js +0 -241
  36. package/dist/core/auto-update/state.js +0 -235
  37. package/dist/core/bare-mode/index.js +0 -107
  38. package/dist/core/bash/redirect.js +0 -281
  39. package/dist/core/bash-classifier.js +0 -1397
  40. package/dist/core/checkpoint/resumer.js +0 -149
  41. package/dist/core/checkpoint/rewinder.js +0 -291
  42. package/dist/core/checkpoints/shadow-git.js +0 -670
  43. package/dist/core/citations/parser.js +0 -109
  44. package/dist/core/classifier/yolo-classifier.js +0 -88
  45. package/dist/core/clipboard.js +0 -70
  46. package/dist/core/codegraph/decision-store.js +0 -248
  47. package/dist/core/codegraph/detect-repo.js +0 -459
  48. package/dist/core/codegraph/install.js +0 -134
  49. package/dist/core/codegraph/offer-hook.js +0 -220
  50. package/dist/core/compact/auto-trigger.js +0 -96
  51. package/dist/core/compact/buffer-rewriter.js +0 -115
  52. package/dist/core/compact/summarizer.js +0 -208
  53. package/dist/core/compact/token-counter.js +0 -108
  54. package/dist/core/consensus/anvil-fanout.js +0 -276
  55. package/dist/core/consensus/diff-capture.js +0 -491
  56. package/dist/core/consensus/rubric.js +0 -233
  57. package/dist/core/context/builder.js +0 -114
  58. package/dist/core/context/compaction-events.js +0 -99
  59. package/dist/core/context/compaction.js +0 -602
  60. package/dist/core/context/index.js +0 -28
  61. package/dist/core/context/invariants.js +0 -250
  62. package/dist/core/context/markdown-loader.js +0 -288
  63. package/dist/core/context/markdown-traverse.js +0 -255
  64. package/dist/core/context/pugiignore.js +0 -316
  65. package/dist/core/context/repo-skeleton.js +0 -533
  66. package/dist/core/context/tool-eviction.js +0 -55
  67. package/dist/core/context/watcher.js +0 -342
  68. package/dist/core/context/working-set.js +0 -165
  69. package/dist/core/coordinator/agent-tools.js +0 -77
  70. package/dist/core/coordinator/agent-toolset.js +0 -65
  71. package/dist/core/coordinator/fsm.js +0 -73
  72. package/dist/core/coordinator/mode-fsm.js +0 -70
  73. package/dist/core/cost/rate-card.js +0 -129
  74. package/dist/core/cost/tracker.js +0 -221
  75. package/dist/core/credentials.js +0 -355
  76. package/dist/core/cron/scheduler.js +0 -138
  77. package/dist/core/denial-tracking/index.js +0 -8
  78. package/dist/core/denial-tracking/state.js +0 -264
  79. package/dist/core/diagnostics/probe-runner.js +0 -93
  80. package/dist/core/diagnostics/probes/api.js +0 -46
  81. package/dist/core/diagnostics/probes/auth.js +0 -93
  82. package/dist/core/diagnostics/probes/bare-mode.js +0 -42
  83. package/dist/core/diagnostics/probes/cli-version.js +0 -127
  84. package/dist/core/diagnostics/probes/config.js +0 -72
  85. package/dist/core/diagnostics/probes/denial-tracking.js +0 -57
  86. package/dist/core/diagnostics/probes/disk.js +0 -81
  87. package/dist/core/diagnostics/probes/engine-live.js +0 -46
  88. package/dist/core/diagnostics/probes/git.js +0 -65
  89. package/dist/core/diagnostics/probes/hooks.js +0 -118
  90. package/dist/core/diagnostics/probes/mcp.js +0 -75
  91. package/dist/core/diagnostics/probes/node.js +0 -59
  92. package/dist/core/diagnostics/probes/pnpm.js +0 -36
  93. package/dist/core/diagnostics/probes/pugi-md.js +0 -89
  94. package/dist/core/diagnostics/probes/sandbox.js +0 -72
  95. package/dist/core/diagnostics/probes/session.js +0 -74
  96. package/dist/core/diagnostics/probes/status-snapshot.js +0 -488
  97. package/dist/core/diagnostics/probes/workspace.js +0 -63
  98. package/dist/core/diagnostics/types.js +0 -70
  99. package/dist/core/dispatch/cache-cleanup.js +0 -197
  100. package/dist/core/dispatch/cache-handoff.js +0 -295
  101. package/dist/core/edits/apply-patch-layer-e.js +0 -189
  102. package/dist/core/edits/dispatch.js +0 -511
  103. package/dist/core/edits/format-detector.js +0 -260
  104. package/dist/core/edits/format-matrix.js +0 -26
  105. package/dist/core/edits/fuzzy-ladder.js +0 -650
  106. package/dist/core/edits/index.js +0 -19
  107. package/dist/core/edits/journal.js +0 -199
  108. package/dist/core/edits/layer-a-apply.js +0 -217
  109. package/dist/core/edits/layer-a-fuzzy-apply.js +0 -198
  110. package/dist/core/edits/layer-b-apply.js +0 -211
  111. package/dist/core/edits/layer-c-apply.js +0 -160
  112. package/dist/core/edits/layer-d-ast.js +0 -572
  113. package/dist/core/edits/marker-parser.js +0 -401
  114. package/dist/core/edits/security-gate.js +0 -223
  115. package/dist/core/edits/verify-hook.js +0 -273
  116. package/dist/core/edits/worktree.js +0 -322
  117. package/dist/core/engine/adapter-runner.js +0 -8
  118. package/dist/core/engine/anvil-client.js +0 -344
  119. package/dist/core/engine/auto-compact.js +0 -179
  120. package/dist/core/engine/budgets.js +0 -195
  121. package/dist/core/engine/context-prefix.js +0 -155
  122. package/dist/core/engine/index.js +0 -12
  123. package/dist/core/engine/intensity.js +0 -163
  124. package/dist/core/engine/intent.js +0 -260
  125. package/dist/core/engine/native-pugi.js +0 -1616
  126. package/dist/core/engine/noop.js +0 -27
  127. package/dist/core/engine/prompts.js +0 -236
  128. package/dist/core/engine/strip-internal-fields.js +0 -124
  129. package/dist/core/engine/tool-bridge.js +0 -2173
  130. package/dist/core/engine/verification-patterns.js +0 -195
  131. package/dist/core/evaluation/golden-dataset.js +0 -293
  132. package/dist/core/feedback/queue.js +0 -177
  133. package/dist/core/feedback/submitter.js +0 -145
  134. package/dist/core/file-cache.js +0 -141
  135. package/dist/core/flatten/flatten-repo.js +0 -439
  136. package/dist/core/format/osc8-link.js +0 -28
  137. package/dist/core/hook-chains.js +0 -392
  138. package/dist/core/hooks/citation-verify-hook.js +0 -138
  139. package/dist/core/hooks/citation-verify.js +0 -112
  140. package/dist/core/hooks/events.js +0 -46
  141. package/dist/core/hooks/index.js +0 -15
  142. package/dist/core/hooks/registry.js +0 -216
  143. package/dist/core/hooks/runner.js +0 -236
  144. package/dist/core/hooks/v2/event-emitter.js +0 -115
  145. package/dist/core/hooks/v2/executor.js +0 -282
  146. package/dist/core/hooks/v2/index.js +0 -25
  147. package/dist/core/hooks/v2/lifecycle.js +0 -104
  148. package/dist/core/hooks/v2/loader.js +0 -216
  149. package/dist/core/hooks/v2/matcher.js +0 -125
  150. package/dist/core/hooks/v2/trust.js +0 -143
  151. package/dist/core/hooks/v2/types.js +0 -86
  152. package/dist/core/hooks/worktree-events.js +0 -158
  153. package/dist/core/hooks.js +0 -415
  154. package/dist/core/image/renderer.js +0 -71
  155. package/dist/core/index-store.js +0 -260
  156. package/dist/core/init/detector.js +0 -582
  157. package/dist/core/init/template-renderer.js +0 -242
  158. package/dist/core/jobs/registry.js +0 -462
  159. package/dist/core/ledger/results-tsv.js +0 -142
  160. package/dist/core/log-discipline/stdout-redirect.js +0 -51
  161. package/dist/core/lsp/cache.js +0 -105
  162. package/dist/core/lsp/client.js +0 -1229
  163. package/dist/core/lsp/language-detect.js +0 -66
  164. package/dist/core/lsp/post-edit-diagnostics.js +0 -171
  165. package/dist/core/lsp/server-detect.js +0 -173
  166. package/dist/core/lsp/symbol-cache.js +0 -162
  167. package/dist/core/lsp/symbol-tools.js +0 -664
  168. package/dist/core/mcp/client.js +0 -385
  169. package/dist/core/mcp/http-server.js +0 -553
  170. package/dist/core/mcp/orchestrator-config.js +0 -192
  171. package/dist/core/mcp/orchestrator-tools.js +0 -806
  172. package/dist/core/mcp/permission.js +0 -190
  173. package/dist/core/mcp/registry.js +0 -193
  174. package/dist/core/mcp/server-tools.js +0 -219
  175. package/dist/core/mcp/server.js +0 -397
  176. package/dist/core/mcp/trust.js +0 -91
  177. package/dist/core/memory/dual-write.js +0 -416
  178. package/dist/core/memory/passive-extract.js +0 -130
  179. package/dist/core/memory/phase1-kinds.js +0 -20
  180. package/dist/core/memory/secret-scanner.js +0 -304
  181. package/dist/core/memory-sync/queue.js +0 -170
  182. package/dist/core/metrics/extract.js +0 -113
  183. package/dist/core/modes/roo-modes.js +0 -68
  184. package/dist/core/onboarding/ensure-initialized.js +0 -133
  185. package/dist/core/onboarding/marker.js +0 -111
  186. package/dist/core/onboarding/telemetry-state.js +0 -108
  187. package/dist/core/output-style/presets.js +0 -176
  188. package/dist/core/output-style/state.js +0 -185
  189. package/dist/core/path-security.js +0 -345
  190. package/dist/core/permission.js +0 -369
  191. package/dist/core/permissions/auto-classifier.js +0 -124
  192. package/dist/core/permissions/bash-parser.js +0 -371
  193. package/dist/core/permissions/circuit-breaker.js +0 -83
  194. package/dist/core/permissions/constrained-edit.js +0 -91
  195. package/dist/core/permissions/gate.js +0 -278
  196. package/dist/core/permissions/index.js +0 -20
  197. package/dist/core/permissions/mode.js +0 -174
  198. package/dist/core/permissions/network-egress.js +0 -137
  199. package/dist/core/permissions/state.js +0 -241
  200. package/dist/core/permissions/tool-class.js +0 -107
  201. package/dist/core/plan-mode/ui-state.js +0 -51
  202. package/dist/core/plans/plan-artifact.js +0 -721
  203. package/dist/core/policy-limits/etag-store.js +0 -122
  204. package/dist/core/prd-check/parser.js +0 -215
  205. package/dist/core/prd-check/reporter.js +0 -127
  206. package/dist/core/prd-check/session-review.js +0 -557
  207. package/dist/core/prd-check/verifiers.js +0 -223
  208. package/dist/core/prompt-cache/client-cache.js +0 -99
  209. package/dist/core/prompts/assembly.js +0 -29
  210. package/dist/core/prompts/registry.js +0 -364
  211. package/dist/core/pugi-gitignore.js +0 -52
  212. package/dist/core/pugi-md/cc-compat-rules.js +0 -735
  213. package/dist/core/pugi-md/context-injector.js +0 -76
  214. package/dist/core/pugi-md/walk-up.js +0 -207
  215. package/dist/core/python/uv-installer.js +0 -270
  216. package/dist/core/python/uv-resolver.js +0 -83
  217. package/dist/core/rate-limit/narrator.js +0 -146
  218. package/dist/core/recipes/cli-types.js +0 -20
  219. package/dist/core/recipes/loader.js +0 -103
  220. package/dist/core/recipes/runner.js +0 -345
  221. package/dist/core/recipes/schema.js +0 -587
  222. package/dist/core/release-notes/parser.js +0 -241
  223. package/dist/core/release-notes/state.js +0 -116
  224. package/dist/core/repl/ask.js +0 -512
  225. package/dist/core/repl/cancellation.js +0 -98
  226. package/dist/core/repl/cap-warning.js +0 -91
  227. package/dist/core/repl/clipboard-read.js +0 -174
  228. package/dist/core/repl/dispatch-fsm.js +0 -220
  229. package/dist/core/repl/engine-bridge.js +0 -303
  230. package/dist/core/repl/history-search.js +0 -175
  231. package/dist/core/repl/history.js +0 -182
  232. package/dist/core/repl/kill-ring.js +0 -138
  233. package/dist/core/repl/model-pricing.js +0 -135
  234. package/dist/core/repl/privacy-banner.js +0 -71
  235. package/dist/core/repl/session.js +0 -4962
  236. package/dist/core/repl/slash-commands.js +0 -747
  237. package/dist/core/repl/store/index.js +0 -12
  238. package/dist/core/repl/store/jsonl-log.js +0 -321
  239. package/dist/core/repl/store/lockfile.js +0 -155
  240. package/dist/core/repl/store/session-store.js +0 -821
  241. package/dist/core/repl/store/types.js +0 -44
  242. package/dist/core/repl/store/uuid-v7.js +0 -68
  243. package/dist/core/repl/tool-route.js +0 -382
  244. package/dist/core/repl/workspace-context.js +0 -206
  245. package/dist/core/repo-map/build.js +0 -125
  246. package/dist/core/repo-map/cache.js +0 -185
  247. package/dist/core/repo-map/extractor.js +0 -254
  248. package/dist/core/repo-map/formatter.js +0 -145
  249. package/dist/core/repo-map/page-rank.js +0 -105
  250. package/dist/core/repo-map/scanner.js +0 -211
  251. package/dist/core/retro/git-collector.js +0 -251
  252. package/dist/core/retro/health-card.js +0 -25
  253. package/dist/core/retro/metrics.js +0 -342
  254. package/dist/core/retro/narrative.js +0 -249
  255. package/dist/core/retro/plane-collector.js +0 -274
  256. package/dist/core/retro/pr-issue-link.js +0 -65
  257. package/dist/core/retro/types.js +0 -16
  258. package/dist/core/retry-budget/budget.js +0 -284
  259. package/dist/core/retry-budget/index.js +0 -5
  260. package/dist/core/retry-budget/retry-cap.js +0 -74
  261. package/dist/core/routing/lead-worker.js +0 -43
  262. package/dist/core/routing/pre-flight-estimator.js +0 -108
  263. package/dist/core/runs/run-tree.js +0 -103
  264. package/dist/core/sandboxing/adapter.js +0 -29
  265. package/dist/core/sandboxing/index.js +0 -49
  266. package/dist/core/sandboxing/none.js +0 -19
  267. package/dist/core/sandboxing/seatbelt.js +0 -183
  268. package/dist/core/security/injection-scanner.js +0 -367
  269. package/dist/core/security/output-filter.js +0 -418
  270. package/dist/core/session/env-file.js +0 -105
  271. package/dist/core/session/section-budgets.js +0 -140
  272. package/dist/core/session.js +0 -377
  273. package/dist/core/settings.js +0 -400
  274. package/dist/core/share/formatter.js +0 -271
  275. package/dist/core/share/redactor.js +0 -221
  276. package/dist/core/share/uploader.js +0 -267
  277. package/dist/core/skills/defaults.js +0 -457
  278. package/dist/core/skills/loader.js +0 -454
  279. package/dist/core/skills/sources.js +0 -480
  280. package/dist/core/skills/trust.js +0 -172
  281. package/dist/core/smoke/headless-driver.js +0 -174
  282. package/dist/core/smoke/orchestrator.js +0 -194
  283. package/dist/core/smoke/runner.js +0 -238
  284. package/dist/core/smoke/scenario-parser.js +0 -316
  285. package/dist/core/statusline.js +0 -99
  286. package/dist/core/subagents/dispatcher-real.js +0 -600
  287. package/dist/core/subagents/dispatcher.js +0 -352
  288. package/dist/core/subagents/index.js +0 -39
  289. package/dist/core/subagents/isolation-matrix.js +0 -213
  290. package/dist/core/subagents/spawn.js +0 -101
  291. package/dist/core/telemetry/emitter.js +0 -229
  292. package/dist/core/telemetry/queue.js +0 -251
  293. package/dist/core/theme/context.js +0 -91
  294. package/dist/core/theme/presets.js +0 -228
  295. package/dist/core/theme/state.js +0 -181
  296. package/dist/core/todos/invariant.js +0 -10
  297. package/dist/core/todos/state.js +0 -177
  298. package/dist/core/tool-schema/compressor.js +0 -89
  299. package/dist/core/transport/version-interceptor.js +0 -166
  300. package/dist/core/trust.js +0 -109
  301. package/dist/core/tui/thinking-block.js +0 -64
  302. package/dist/core/vim/keymap.js +0 -288
  303. package/dist/core/vim/state.js +0 -92
  304. package/dist/core/watch-markers/marker-watcher.js +0 -133
  305. package/dist/core/worktree/include-parser.js +0 -249
  306. package/dist/core/worktree-manager/cleanup.js +0 -123
  307. package/dist/core/worktree-manager/manager.js +0 -303
  308. package/dist/index.js +0 -44
  309. package/dist/runtime/bootstrap.js +0 -190
  310. package/dist/runtime/cli.js +0 -8121
  311. package/dist/runtime/commands/agents.js +0 -385
  312. package/dist/runtime/commands/budget.js +0 -192
  313. package/dist/runtime/commands/cancel.js +0 -231
  314. package/dist/runtime/commands/chain.js +0 -489
  315. package/dist/runtime/commands/codegraph-status.js +0 -227
  316. package/dist/runtime/commands/compact.js +0 -297
  317. package/dist/runtime/commands/config.js +0 -595
  318. package/dist/runtime/commands/cost.js +0 -199
  319. package/dist/runtime/commands/delegate.js +0 -312
  320. package/dist/runtime/commands/dispatch.js +0 -126
  321. package/dist/runtime/commands/doctor.js +0 -579
  322. package/dist/runtime/commands/feedback.js +0 -184
  323. package/dist/runtime/commands/hooks.js +0 -187
  324. package/dist/runtime/commands/init.js +0 -254
  325. package/dist/runtime/commands/lsp.js +0 -368
  326. package/dist/runtime/commands/mcp.js +0 -935
  327. package/dist/runtime/commands/memory.js +0 -582
  328. package/dist/runtime/commands/model.js +0 -237
  329. package/dist/runtime/commands/onboarding.js +0 -275
  330. package/dist/runtime/commands/patch.js +0 -128
  331. package/dist/runtime/commands/permissions.js +0 -112
  332. package/dist/runtime/commands/plan.js +0 -143
  333. package/dist/runtime/commands/prd-check.js +0 -285
  334. package/dist/runtime/commands/privacy.js +0 -107
  335. package/dist/runtime/commands/recipe.js +0 -325
  336. package/dist/runtime/commands/redo-blob-store.js +0 -92
  337. package/dist/runtime/commands/redo.js +0 -361
  338. package/dist/runtime/commands/release-notes.js +0 -229
  339. package/dist/runtime/commands/repo-map.js +0 -95
  340. package/dist/runtime/commands/report.js +0 -299
  341. package/dist/runtime/commands/resume.js +0 -118
  342. package/dist/runtime/commands/review-consensus.js +0 -414
  343. package/dist/runtime/commands/rewind.js +0 -333
  344. package/dist/runtime/commands/roster.js +0 -117
  345. package/dist/runtime/commands/sessions.js +0 -163
  346. package/dist/runtime/commands/share.js +0 -316
  347. package/dist/runtime/commands/skills.js +0 -401
  348. package/dist/runtime/commands/status.js +0 -186
  349. package/dist/runtime/commands/stickers.js +0 -82
  350. package/dist/runtime/commands/style.js +0 -194
  351. package/dist/runtime/commands/theme.js +0 -196
  352. package/dist/runtime/commands/undo.js +0 -361
  353. package/dist/runtime/commands/update.js +0 -289
  354. package/dist/runtime/commands/vim.js +0 -140
  355. package/dist/runtime/commands/worktree.js +0 -177
  356. package/dist/runtime/commands/worktrees.js +0 -155
  357. package/dist/runtime/deprecation-warning.js +0 -69
  358. package/dist/runtime/engine-exit-code.js +0 -50
  359. package/dist/runtime/headless-repl.js +0 -195
  360. package/dist/runtime/headless.js +0 -548
  361. package/dist/runtime/load-hooks-or-exit.js +0 -71
  362. package/dist/runtime/plan-decompose.js +0 -531
  363. package/dist/runtime/sigint-guard.js +0 -272
  364. package/dist/runtime/stream-renderer.js +0 -195
  365. package/dist/runtime/update-check.js +0 -294
  366. package/dist/runtime/version.js +0 -65
  367. package/dist/runtime/worktree-bootstrap.js +0 -579
  368. package/dist/skills/bundled/batch.js +0 -617
  369. package/dist/skills/bundled/index.js +0 -45
  370. package/dist/skills/bundled/loop.js +0 -358
  371. package/dist/skills/bundled/remember.js +0 -383
  372. package/dist/skills/bundled/simplify.js +0 -289
  373. package/dist/skills/bundled/skillify.js +0 -373
  374. package/dist/skills/bundled/stuck.js +0 -558
  375. package/dist/skills/bundled/verify.js +0 -439
  376. package/dist/testing/vcr.js +0 -486
  377. package/dist/tools/agent-tool.js +0 -229
  378. package/dist/tools/apply-patch.js +0 -556
  379. package/dist/tools/ask-user-question.js +0 -337
  380. package/dist/tools/ask-user.js +0 -115
  381. package/dist/tools/bash.js +0 -1238
  382. package/dist/tools/brief.js +0 -224
  383. package/dist/tools/cron.js +0 -433
  384. package/dist/tools/enter-worktree.js +0 -250
  385. package/dist/tools/exit-worktree.js +0 -147
  386. package/dist/tools/file-tools.js +0 -553
  387. package/dist/tools/http-request.js +0 -336
  388. package/dist/tools/lsp-tools.js +0 -565
  389. package/dist/tools/mcp-tool.js +0 -260
  390. package/dist/tools/multi-edit.js +0 -361
  391. package/dist/tools/powershell.js +0 -268
  392. package/dist/tools/registry.js +0 -166
  393. package/dist/tools/server-tools.js +0 -892
  394. package/dist/tools/skill-tool.js +0 -96
  395. package/dist/tools/sleep.js +0 -99
  396. package/dist/tools/synthetic-output.js +0 -133
  397. package/dist/tools/tasks.js +0 -208
  398. package/dist/tools/todo-write.js +0 -184
  399. package/dist/tools/verify-plan-execution.js +0 -295
  400. package/dist/tools/web-fetch-injection-scanner.js +0 -207
  401. package/dist/tools/web-fetch.js +0 -720
  402. package/dist/tools/web-search.js +0 -458
  403. package/dist/tui/agent-progress-card.js +0 -111
  404. package/dist/tui/agent-tree-pane.js +0 -9
  405. package/dist/tui/agent-tree.js +0 -87
  406. package/dist/tui/ask-cli.js +0 -52
  407. package/dist/tui/ask-modal.js +0 -211
  408. package/dist/tui/ask-user-question-chips.js +0 -315
  409. package/dist/tui/ask-user-question-prompt.js +0 -203
  410. package/dist/tui/compact-banner.js +0 -81
  411. package/dist/tui/conversation-pane.js +0 -164
  412. package/dist/tui/cost-table.js +0 -111
  413. package/dist/tui/device-flow.js +0 -142
  414. package/dist/tui/doctor-table.js +0 -46
  415. package/dist/tui/feedback-prompt.js +0 -156
  416. package/dist/tui/input-box.js +0 -732
  417. package/dist/tui/login-picker.js +0 -69
  418. package/dist/tui/markdown-render.js +0 -266
  419. package/dist/tui/multi-file-diff-approval.js +0 -375
  420. package/dist/tui/onboarding-wizard.js +0 -240
  421. package/dist/tui/permissions-picker.js +0 -86
  422. package/dist/tui/render.js +0 -160
  423. package/dist/tui/repl-render.js +0 -770
  424. package/dist/tui/repl-splash-art.js +0 -64
  425. package/dist/tui/repl-splash-mascot.js +0 -154
  426. package/dist/tui/repl-splash.js +0 -117
  427. package/dist/tui/repl.js +0 -378
  428. package/dist/tui/slash-palette.js +0 -106
  429. package/dist/tui/splash-data.js +0 -61
  430. package/dist/tui/splash.js +0 -31
  431. package/dist/tui/status-bar.js +0 -209
  432. package/dist/tui/status-table.js +0 -7
  433. package/dist/tui/stickers-art.js +0 -136
  434. package/dist/tui/style-table.js +0 -28
  435. package/dist/tui/theme-table.js +0 -29
  436. package/dist/tui/thinking-spinner.js +0 -123
  437. package/dist/tui/tool-stream-pane.js +0 -140
  438. package/dist/tui/update-banner.js +0 -33
  439. package/dist/tui/vim-input.js +0 -267
  440. package/dist/tui/welcome-banner.js +0 -107
  441. package/dist/tui/welcome-data.js +0 -293
  442. package/dist/tui/workspace-context.js +0 -105
  443. package/docs/examples/codegraph.mcp.json +0 -10
  444. package/test/scenarios/codegen-create-file.scenario.txt +0 -13
  445. package/test/scenarios/compact-force.scenario.txt +0 -12
  446. package/test/scenarios/identity.scenario.txt +0 -11
  447. package/test/scenarios/persona-handoff.scenario.txt +0 -12
  448. package/test/scenarios/walkback.scenario.txt +0 -12
@@ -1,935 +0,0 @@
1
- import { execFileSync } from 'node:child_process';
2
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
- import { isAbsolute, resolve } from 'node:path';
5
- import { FileReadCache } from '../../core/file-cache.js';
6
- import { openSession } from '../../core/session.js';
7
- import { loadSettings } from '../../core/settings.js';
8
- import { isAlive } from '../../core/mcp/client.js';
9
- import { loadMcpRegistry, mcpLogPath } from '../../core/mcp/registry.js';
10
- import { listMcpTrust, setMcpTrust } from '../../core/mcp/trust.js';
11
- import { createPugiMcpServer, serveStdio } from '../../core/mcp/server.js';
12
- import { buildPugiMcpTools } from '../../core/mcp/server-tools.js';
13
- import { buildOrchestratorTools } from '../../core/mcp/orchestrator-tools.js';
14
- import { resolveOrchestratorConfig, describeOrchestratorConfig, } from '../../core/mcp/orchestrator-config.js';
15
- import { serveHttp } from '../../core/mcp/http-server.js';
16
- import { resolveActiveCredential, DEFAULT_API_URL } from '../../core/credentials.js';
17
- import { listMcpPermissions, clearMcpPermission, } from '../../core/mcp/permission.js';
18
- export async function runMcpCommand(args, ctx) {
19
- const sub = args[0] ?? 'list';
20
- switch (sub) {
21
- case 'list':
22
- return runMcpList(ctx);
23
- case 'trust':
24
- return runMcpFlip(args.slice(1), ctx, 'trusted');
25
- case 'deny':
26
- return runMcpFlip(args.slice(1), ctx, 'denied');
27
- case 'install':
28
- return runMcpInstall(args.slice(1), ctx);
29
- case 'remove':
30
- case 'uninstall':
31
- return runMcpRemove(args.slice(1), ctx);
32
- case 'doctor':
33
- return runMcpDoctor(args.slice(1), ctx);
34
- case 'logs':
35
- return runMcpLogs(args.slice(1), ctx);
36
- case 'restart':
37
- return runMcpRestart(args.slice(1), ctx);
38
- case 'serve':
39
- return runMcpServe(args.slice(1), ctx);
40
- case 'perms':
41
- return runMcpPerms(args.slice(1), ctx);
42
- case 'help':
43
- case '--help':
44
- case '-h':
45
- ctx.writeOutput({ command: 'mcp', usage: USAGE_LINES }, USAGE_LINES.join('\n'));
46
- return;
47
- default:
48
- throw new Error(`Unknown sub-command "pugi mcp ${sub}". Try one of: list, trust, deny, install, remove, doctor, logs, restart, serve, perms.`);
49
- }
50
- }
51
- const USAGE_LINES = [
52
- 'Usage: pugi mcp <sub-command>',
53
- '',
54
- ' list List declared MCP servers + trust state + surfaced tools',
55
- ' trust <name> Mark a server as trusted (operator-side ledger)',
56
- ' deny <name> Mark a server as denied',
57
- ' install <name> <command...> Add a server to .pugi/mcp.json (workspace scope).',
58
- ' <command> must be an absolute path OR a binary',
59
- ' resolvable via `which` on the operator PATH.',
60
- ' remove <name> Remove a server from .pugi/mcp.json (workspace scope).',
61
- ' Trust ledger entry is preserved — re-install reuses it.',
62
- ' doctor [--connect] Print per-server health (handshake, tool count, last error).',
63
- ' --connect actually spawns the children (slow, ~5s/server).',
64
- ' Without --connect, reports the declared state only.',
65
- ' logs <name> [--tail N] Tail the per-server log file at .pugi/logs/mcp-<name>.log.',
66
- ' Default tail is 40 lines.',
67
- ' restart <name> Bounce a server: tear down the connection, reload the',
68
- ' config, re-handshake. Surfaces the new tool count.',
69
- ' serve [options] Run Pugi as an MCP server',
70
- ' --http :<port> HTTP+SSE transport (default: stdio)',
71
- ' --host <ip> HTTP bind host (default: 127.0.0.1)',
72
- ' --token <bearer> HTTP bearer token (env: PUGI_MCP_TOKEN).',
73
- ' Required for --http unless --print-token is set.',
74
- ' --print-token Auto-generate a random bearer token and print it',
75
- ' to stderr. Opt-in for ad-hoc local testing only.',
76
- ' --read-only Expose read/grep/glob only (default for HTTP).',
77
- ' --allow-write Expose edit/write (default off — explicit opt-in).',
78
- ' --allow-bash Expose the bash tool (default off — explicit opt-in).',
79
- ' --no-bash Deprecated alias (bash is already off by default).',
80
- ' --orchestrator Expose pugi.run / pugi.read / pugi.write /',
81
- ' pugi.dispatch / pugi.publish / pugi.deploy instead of',
82
- ' the engine surface. Designed for external the upstream tool',
83
- ' / Cursor sessions driving fix-publish-test loops.',
84
- ' --orchestrator-bundle Single-flag form (Trust Sprint item 7). Equivalent to',
85
- ' --orchestrator --allow-bash with PUGI_MCP_EXEC_ENABLED=1',
86
- ' and auto-resolves the pugi binary from PATH (or the',
87
- ' local dev binary when invoked from the monorepo).',
88
- ' Also enabled by setting PUGI_MCP_ORCHESTRATOR=1.',
89
- ' Legacy gates still work as deprecated aliases:',
90
- ' PUGI_MCP_EXEC_ENABLED=1 enables pugi.run',
91
- ' PUGI_MCP_PUBLISH_ENABLED=1 enables pugi.publish',
92
- ' PUGI_MCP_DEPLOY_ENABLED=1 enables pugi.deploy',
93
- ' PUGI_MCP_WORKSPACE_ROOT=... overrides cwd for path validation',
94
- ' perms list Show cached per-(server, tool) decisions',
95
- ' perms reset <server>:<tool> Forget one cached decision',
96
- ];
97
- /* ---------- list ------------------------------------------------------- */
98
- async function runMcpList(ctx) {
99
- const registry = await loadMcpRegistry(ctx.workspaceRoot, { connect: false });
100
- const declared = Array.from(registry.servers.values()).map((state) => ({
101
- name: state.name,
102
- command: state.config.command,
103
- args: state.config.args,
104
- trust: state.trust,
105
- surfacedTools: state.surfacedTools.length,
106
- lastError: state.lastError ?? null,
107
- }));
108
- const ledger = await listMcpTrust();
109
- await registry.shutdown();
110
- if (declared.length === 0) {
111
- ctx.writeOutput({ command: 'mcp.list', servers: [], ledger }, 'No MCP servers declared. Add one with `pugi mcp install <name> <command...>`.');
112
- return;
113
- }
114
- ctx.writeOutput({ command: 'mcp.list', servers: declared, ledger }, [
115
- 'MCP servers:',
116
- ...declared.map((server) => ` ${server.name.padEnd(20)} ${server.trust.padEnd(8)} ${server.command} ${server.args.join(' ')}`),
117
- ].join('\n'));
118
- }
119
- /* ---------- trust / deny ---------------------------------------------- */
120
- async function runMcpFlip(args, ctx, state) {
121
- const name = args[0];
122
- if (!name) {
123
- throw new Error(`pugi mcp ${state === 'trusted' ? 'trust' : 'deny'} requires a server name.`);
124
- }
125
- const by = resolveDecidedBy();
126
- await setMcpTrust(name, state, by);
127
- ctx.writeOutput({ command: `mcp.${state === 'trusted' ? 'trust' : 'deny'}`, name, state, decidedBy: by }, state === 'trusted'
128
- ? `MCP server "${name}" is now trusted.`
129
- : `MCP server "${name}" is now denied.`);
130
- }
131
- /* ---------- install --------------------------------------------------- */
132
- async function runMcpInstall(args, ctx) {
133
- const name = args[0];
134
- const command = args[1];
135
- const rest = args.slice(2);
136
- if (!name || !command) {
137
- throw new Error('Usage: pugi mcp install <name> <command> [args...]');
138
- }
139
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
140
- throw new Error(`pugi mcp install: server name "${name}" must be [a-zA-Z0-9_-]+`);
141
- }
142
- // β4 r1 P1 #6 — validate the executable path. Trust ledger gating is
143
- // not enough: a cloned-and-trusted repo could declare a relative
144
- // command (`./malicious-shim.sh`) that resolves at runtime via the
145
- // shell PATH or the workspace cwd. Require an ABSOLUTE path OR a
146
- // `which`-resolvable binary so the operator sees the canonical
147
- // executable before granting trust.
148
- const resolved = resolveExecutablePath(command);
149
- if (!resolved) {
150
- throw new Error(`pugi mcp install: command "${command}" must be an absolute path or a binary on PATH. ` +
151
- `Pass the full path (e.g. /usr/local/bin/node) or install the binary first.`);
152
- }
153
- // Reject shell metacharacters in args — they survive into the spawn
154
- // call as positional args (no shell), but a metachar in the COMMAND
155
- // slot would be a clearer foot-gun and we already validated above.
156
- for (const arg of [command, ...rest]) {
157
- if (containsShellMetachar(arg)) {
158
- throw new Error(`pugi mcp install: argument "${arg}" contains shell metacharacters; pass tokens individually instead of a shell string.`);
159
- }
160
- }
161
- const mcpJsonPath = resolve(ctx.workspaceRoot, '.pugi/mcp.json');
162
- mkdirSync(resolve(ctx.workspaceRoot, '.pugi'), { recursive: true });
163
- let existing = { servers: {} };
164
- if (existsSync(mcpJsonPath)) {
165
- try {
166
- const raw = readFileSync(mcpJsonPath, 'utf8');
167
- if (raw.trim().length > 0) {
168
- const parsed = JSON.parse(raw);
169
- existing = { servers: parsed.servers ?? {} };
170
- }
171
- }
172
- catch (error) {
173
- throw new Error(`pugi mcp install: cannot parse existing .pugi/mcp.json: ${error.message}. ` +
174
- `Fix the file by hand or delete it and re-run.`);
175
- }
176
- }
177
- if (existing.servers[name]) {
178
- throw new Error(`pugi mcp install: server "${name}" already declared. Remove it from .pugi/mcp.json first.`);
179
- }
180
- // β4 r2 P1 #1 — persist the RESOLVED absolute path as `command`, not the
181
- // original input. Before this fix the install path verified `resolved` but
182
- // wrote the operator's literal `command` argument back to .pugi/mcp.json;
183
- // the spawn at connect-time re-walked the PATH via `spawn(command, ...)`,
184
- // which defeated the P1 #6 (β4 r1) hardening — a workspace-cwd shim
185
- // (`./malicious-node`) inserted between install and trust could intercept
186
- // the call because PATH search includes cwd on many shells.
187
- //
188
- // The original input is preserved as `originalCommand` for display / debug
189
- // surfaces (`pugi mcp list`, audit logs) so operators can still see how
190
- // they typed the install call. The runtime spawn path consumes `command`,
191
- // so the absolute path is always what we actually exec.
192
- existing.servers[name] = {
193
- command: resolved,
194
- originalCommand: command,
195
- args: rest,
196
- env: {},
197
- // Workspace declarations start `pending` — trust must be granted
198
- // explicitly via `pugi mcp trust <name>`. This matches the
199
- // server-level trust ledger override semantics in registry.ts.
200
- trust: 'pending',
201
- };
202
- writeFileSync(mcpJsonPath, `${JSON.stringify(existing, null, 2)}\n`, { mode: 0o600 });
203
- // Surface the resolved binary loudly so the operator sees the canonical
204
- // executable before granting trust. β4 r1 P1 #6.
205
- process.stderr.write(`pugi mcp install: starting executable resolves to ${resolved}\n`);
206
- ctx.writeOutput({
207
- command: 'mcp.install',
208
- name,
209
- // `executable` keeps the legacy field name (= what we will actually
210
- // spawn). `originalCommand` is the operator's literal input.
211
- executable: resolved,
212
- originalCommand: command,
213
- executableResolved: resolved,
214
- args: rest,
215
- configPath: mcpJsonPath,
216
- }, [
217
- `Added MCP server "${name}" to ${mcpJsonPath}.`,
218
- `Resolved executable: ${resolved}`,
219
- `It starts as pending. Run \`pugi mcp trust ${name}\` to enable it.`,
220
- ].join('\n'));
221
- }
222
- /**
223
- * Resolve a command to its canonical executable path. Returns null if the
224
- * input is neither an absolute path that exists nor a binary resolvable
225
- * via `which` on the operator PATH.
226
- */
227
- function resolveExecutablePath(command) {
228
- if (isAbsolute(command)) {
229
- return existsSync(command) ? command : null;
230
- }
231
- // Reject relative paths — `./shim.sh` could be a freshly-cloned
232
- // attacker binary in the workspace cwd. Require absolute OR PATH.
233
- if (command.includes('/') || command.includes('\\'))
234
- return null;
235
- try {
236
- // `which` exits 0 with the path on stdout. We pass exactly one
237
- // argument (the binary name we just validated) so no shell metachar
238
- // path is possible.
239
- const out = execFileSync('/usr/bin/which', [command], {
240
- stdio: ['ignore', 'pipe', 'ignore'],
241
- encoding: 'utf8',
242
- timeout: 5000,
243
- }).trim();
244
- return out.length > 0 ? out : null;
245
- }
246
- catch {
247
- return null;
248
- }
249
- }
250
- function containsShellMetachar(value) {
251
- // Reject the obvious shell control characters. We are not building a
252
- // full lexer; the install path passes the args verbatim to spawn (no
253
- // shell), so the goal here is purely surfacing operator surprise — a
254
- // `;` in an arg almost always means the user thought they were typing
255
- // a shell pipeline.
256
- return /[;|&`$<>(){}\n\r]/.test(value);
257
- }
258
- /* ---------- remove ---------------------------------------------------- */
259
- async function runMcpRemove(args, ctx) {
260
- const name = args[0];
261
- if (!name) {
262
- throw new Error('Usage: pugi mcp remove <name>');
263
- }
264
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
265
- throw new Error(`pugi mcp remove: server name "${name}" must be [a-zA-Z0-9_-]+`);
266
- }
267
- const mcpJsonPath = resolve(ctx.workspaceRoot, '.pugi/mcp.json');
268
- if (!existsSync(mcpJsonPath)) {
269
- throw new Error(`pugi mcp remove: no .pugi/mcp.json at ${mcpJsonPath}. Nothing to remove.`);
270
- }
271
- let existing;
272
- try {
273
- const raw = readFileSync(mcpJsonPath, 'utf8');
274
- if (raw.trim().length === 0) {
275
- existing = { servers: {} };
276
- }
277
- else {
278
- const parsed = JSON.parse(raw);
279
- existing = { servers: parsed.servers ?? {} };
280
- }
281
- }
282
- catch (error) {
283
- throw new Error(`pugi mcp remove: cannot parse .pugi/mcp.json: ${error.message}. ` +
284
- `Fix the file by hand or delete it and re-run.`);
285
- }
286
- if (!existing.servers[name]) {
287
- throw new Error(`pugi mcp remove: server "${name}" not declared in ${mcpJsonPath}. ` +
288
- `Run \`pugi mcp list\` to see declared servers.`);
289
- }
290
- // Preserve the trust ledger entry on purpose — a re-install of the same
291
- // server name should land back at its old trust state so the operator
292
- // does not have to re-approve it. To wipe trust as well, run
293
- // `pugi mcp deny <name>` (or edit ~/.pugi/trust-mcp.json by hand).
294
- delete existing.servers[name];
295
- writeFileSync(mcpJsonPath, `${JSON.stringify(existing, null, 2)}\n`, { mode: 0o600 });
296
- ctx.writeOutput({
297
- command: 'mcp.remove',
298
- name,
299
- configPath: mcpJsonPath,
300
- remaining: Object.keys(existing.servers),
301
- }, [
302
- `Removed MCP server "${name}" from ${mcpJsonPath}.`,
303
- `Trust ledger entry preserved — re-install reuses it.`,
304
- `Remaining servers: ${Object.keys(existing.servers).length === 0 ? '(none)' : Object.keys(existing.servers).join(', ')}.`,
305
- ].join('\n'));
306
- }
307
- /* ---------- doctor ---------------------------------------------------- */
308
- async function runMcpDoctor(args, ctx) {
309
- // `--connect` forces the doctor to actually spawn children + handshake.
310
- // Default behaviour is dry-run (config + trust ledger only) so a routine
311
- // `pugi doctor` does not block on a misbehaving server's 5s timeout per
312
- // entry. Operators investigating an outage pass `--connect` explicitly.
313
- const wantsConnect = args.includes('--connect');
314
- const registry = await loadMcpRegistry(ctx.workspaceRoot, { connect: wantsConnect });
315
- const ledger = await listMcpTrust();
316
- const rows = [];
317
- for (const state of registry.servers.values()) {
318
- const conn = state.connection;
319
- let handshake;
320
- if (!wantsConnect) {
321
- handshake = 'not-attempted';
322
- }
323
- else if (state.lastError) {
324
- handshake = 'failed';
325
- }
326
- else if (conn && isAlive(conn)) {
327
- handshake = 'ok';
328
- }
329
- else {
330
- handshake = 'failed';
331
- }
332
- rows.push({
333
- name: state.name,
334
- trust: state.trust,
335
- handshake,
336
- pid: conn?.child.pid ?? null,
337
- tools: state.surfacedTools.length,
338
- uptimeMs: conn ? Date.now() - conn.startedAt : null,
339
- lastError: state.lastError ?? null,
340
- logFile: mcpLogPath(ctx.workspaceRoot, state.name),
341
- });
342
- }
343
- rows.sort((a, b) => a.name.localeCompare(b.name));
344
- await registry.shutdown();
345
- if (rows.length === 0) {
346
- ctx.writeOutput({ command: 'mcp.doctor', rows, ledger, connectAttempted: wantsConnect }, 'No MCP servers declared. Add one with `pugi mcp install <name> <command...>`.');
347
- return;
348
- }
349
- const headerLines = [
350
- `MCP doctor (${wantsConnect ? 'live handshake' : 'declared state only — pass --connect for live probe'}):`,
351
- '',
352
- ` ${'NAME'.padEnd(20)} ${'TRUST'.padEnd(8)} ${'HANDSHAKE'.padEnd(14)} ${'TOOLS'.padEnd(6)} ${'PID'.padEnd(7)} NOTE`,
353
- ];
354
- for (const row of rows) {
355
- const note = row.lastError
356
- ? `error: ${truncate(row.lastError, 60)}`
357
- : row.handshake === 'ok'
358
- ? `uptime ${formatUptime(row.uptimeMs ?? 0)}`
359
- : row.handshake === 'failed'
360
- ? 'see log file'
361
- : '';
362
- headerLines.push(` ${row.name.padEnd(20)} ${row.trust.padEnd(8)} ${row.handshake.padEnd(14)} ${String(row.tools).padEnd(6)} ${String(row.pid ?? '-').padEnd(7)} ${note}`);
363
- }
364
- headerLines.push('', `Log dir: ${resolve(ctx.workspaceRoot, '.pugi/logs')}`);
365
- if (!wantsConnect) {
366
- headerLines.push('Hint: pass --connect to actually spawn the children (slow, ~5s budget/server).');
367
- }
368
- ctx.writeOutput({ command: 'mcp.doctor', rows, ledger, connectAttempted: wantsConnect }, headerLines.join('\n'));
369
- }
370
- function truncate(value, max) {
371
- if (value.length <= max)
372
- return value;
373
- return `${value.slice(0, max - 1)}…`;
374
- }
375
- function formatUptime(ms) {
376
- if (ms < 1000)
377
- return `${ms}ms`;
378
- const sec = Math.floor(ms / 1000);
379
- if (sec < 60)
380
- return `${sec}s`;
381
- const min = Math.floor(sec / 60);
382
- if (min < 60)
383
- return `${min}m${sec % 60}s`;
384
- const hr = Math.floor(min / 60);
385
- return `${hr}h${min % 60}m`;
386
- }
387
- /* ---------- logs ------------------------------------------------------ */
388
- async function runMcpLogs(args, ctx) {
389
- const name = args[0];
390
- if (!name || name.startsWith('--')) {
391
- throw new Error('Usage: pugi mcp logs <name> [--tail N]');
392
- }
393
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
394
- throw new Error(`pugi mcp logs: server name "${name}" must be [a-zA-Z0-9_-]+`);
395
- }
396
- // `--tail N` and `--tail=N` both supported. Default 40 lines — matches
397
- // typical `tail -n 40` muscle memory.
398
- let tail = 40;
399
- for (let i = 1; i < args.length; i += 1) {
400
- const arg = args[i] ?? '';
401
- if (arg === '--tail') {
402
- const next = args[i + 1];
403
- if (!next)
404
- throw new Error('pugi mcp logs: --tail requires a value');
405
- const n = Number.parseInt(next, 10);
406
- if (!Number.isInteger(n) || n <= 0) {
407
- throw new Error(`pugi mcp logs: --tail must be a positive integer (got "${next}")`);
408
- }
409
- tail = n;
410
- i += 1;
411
- }
412
- else if (arg.startsWith('--tail=')) {
413
- const n = Number.parseInt(arg.slice('--tail='.length), 10);
414
- if (!Number.isInteger(n) || n <= 0) {
415
- throw new Error(`pugi mcp logs: --tail must be a positive integer (got "${arg}")`);
416
- }
417
- tail = n;
418
- }
419
- else {
420
- throw new Error(`pugi mcp logs: unknown flag "${arg}"`);
421
- }
422
- }
423
- const path = mcpLogPath(ctx.workspaceRoot, name);
424
- if (!existsSync(path)) {
425
- ctx.writeOutput({ command: 'mcp.logs', name, path, tail, lines: [] }, `No log file at ${path}. The server has not produced stderr output yet (or has never been started).`);
426
- return;
427
- }
428
- let raw;
429
- try {
430
- raw = readFileSync(path, 'utf8');
431
- }
432
- catch (error) {
433
- throw new Error(`pugi mcp logs: cannot read ${path}: ${error.message}.`);
434
- }
435
- const allLines = raw.split('\n');
436
- // `split('\n')` of a trailing-newline file yields an empty last element.
437
- // Drop it so the displayed tail matches `wc -l` expectations.
438
- if (allLines.length > 0 && allLines[allLines.length - 1] === '') {
439
- allLines.pop();
440
- }
441
- const tailed = allLines.slice(Math.max(0, allLines.length - tail));
442
- const sizeBytes = (() => {
443
- try {
444
- return statSync(path).size;
445
- }
446
- catch {
447
- return 0;
448
- }
449
- })();
450
- ctx.writeOutput({
451
- command: 'mcp.logs',
452
- name,
453
- path,
454
- tail,
455
- totalLines: allLines.length,
456
- sizeBytes,
457
- lines: tailed,
458
- }, [
459
- `pugi mcp logs ${name} (${path}, ${sizeBytes} bytes, ${allLines.length} total lines, showing last ${Math.min(tail, allLines.length)}):`,
460
- ...tailed,
461
- ].join('\n'));
462
- }
463
- /* ---------- restart --------------------------------------------------- */
464
- async function runMcpRestart(args, ctx) {
465
- const name = args[0];
466
- if (!name) {
467
- throw new Error('Usage: pugi mcp restart <name>');
468
- }
469
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
470
- throw new Error(`pugi mcp restart: server name "${name}" must be [a-zA-Z0-9_-]+`);
471
- }
472
- // `pugi mcp restart` is stateless from the CLI's perspective — the CLI
473
- // process has no long-lived MCP registry of its own (the REPL owns it
474
- // and tears it down on exit). What we DO here is: load the config,
475
- // refuse if the server is not declared or not trusted, then probe-spawn
476
- // the server with the live handshake. This proves it is reachable +
477
- // surfaces the tool count for the operator. The REPL picks up the
478
- // change on its next `loadMcpRegistry` cycle.
479
- const registry = await loadMcpRegistry(ctx.workspaceRoot, { connect: false });
480
- const state = registry.servers.get(name);
481
- if (!state) {
482
- await registry.shutdown();
483
- throw new Error(`pugi mcp restart: server "${name}" not declared. Run \`pugi mcp list\` to see declared servers.`);
484
- }
485
- if (state.trust !== 'trusted') {
486
- await registry.shutdown();
487
- throw new Error(`pugi mcp restart: server "${name}" trust state is "${state.trust}". ` +
488
- `Run \`pugi mcp trust ${name}\` first.`);
489
- }
490
- await registry.shutdown();
491
- // Re-load WITH connect=true but scoped to a single-server probe via
492
- // handshakeTimeoutMs (5s default keeps the CLI snappy). We use the same
493
- // loadMcpRegistry path so log routing + error capture stay consistent.
494
- const probe = await loadMcpRegistry(ctx.workspaceRoot, { connect: true });
495
- const probed = probe.servers.get(name);
496
- const lastError = probed?.lastError ?? null;
497
- const toolCount = probed?.surfacedTools.length ?? 0;
498
- const pid = probed?.connection?.child.pid ?? null;
499
- await probe.shutdown();
500
- if (lastError) {
501
- ctx.writeOutput({
502
- command: 'mcp.restart',
503
- name,
504
- ok: false,
505
- error: lastError,
506
- logFile: mcpLogPath(ctx.workspaceRoot, name),
507
- }, [
508
- `pugi mcp restart ${name}: FAILED`,
509
- ` error: ${lastError}`,
510
- ` log file: ${mcpLogPath(ctx.workspaceRoot, name)}`,
511
- ].join('\n'));
512
- return;
513
- }
514
- ctx.writeOutput({
515
- command: 'mcp.restart',
516
- name,
517
- ok: true,
518
- pid,
519
- surfacedTools: toolCount,
520
- }, [
521
- `pugi mcp restart ${name}: OK`,
522
- ` pid: ${pid ?? '-'}`,
523
- ` surfaced tools: ${toolCount}`,
524
- ].join('\n'));
525
- }
526
- async function runMcpServe(args, ctx) {
527
- const flags = parseServeFlags(args);
528
- const session = openSession(ctx.workspaceRoot);
529
- const settings = loadSettings(ctx.workspaceRoot);
530
- const toolCtx = {
531
- root: ctx.workspaceRoot,
532
- settings,
533
- session,
534
- readCache: new FileReadCache(),
535
- };
536
- // β4 r1 P1 #2 — surface defaults flip to deny-most-permissive. The
537
- // previous defaults exposed every tool (read/edit/write/bash) on every
538
- // transport; an HTTP listener with a leaked bearer = remote shell. We
539
- // now require explicit opt-in for write + bash. `--read-only` remains
540
- // the explicit "shrink to read/grep/glob" knob.
541
- //
542
- // β4 r2 P1 #2 — `--allow-bash` and `--allow-write` are now INDEPENDENT
543
- // opt-ins. Before this fix `readOnly` collapsed to `true` whenever
544
- // `--allow-write` was omitted, which forced `buildPugiMcpTools` to
545
- // drop the `bash` tool even when the operator had explicitly set
546
- // `--allow-bash`. The `readOnly` flag below now reflects ONLY the
547
- // explicit `--read-only` request; the per-capability gates
548
- // (`bashAllowed` / `writeAllowed`) handle the rest. The permission gate
549
- // below (`buildServePermissionGate`) still refuses bash/edit per-tool
550
- // when the corresponding capability is off, so a misconfigured
551
- // `buildPugiMcpTools` call (advertising `bash` without the gate flag)
552
- // would still be refused at dispatch.
553
- const readOnly = flags.readOnly === true;
554
- // Trust Sprint item 7 — resolve orchestrator config FIRST so it can
555
- // contribute to bash gating downstream. The resolver collapses the
556
- // legacy multi-knob mode (PUGI_MCP_EXEC_ENABLED + --allow-bash +
557
- // PUGI_MCP_PUGI_BIN) into one toggle (PUGI_MCP_ORCHESTRATOR=1 or
558
- // --orchestrator-bundle).
559
- const orchestratorConfig = flags.orchestrator
560
- ? resolveOrchestratorConfig({
561
- env: process.env,
562
- bundleFlag: flags.orchestratorBundle,
563
- bashFlag: flags.bashAllowed,
564
- workspaceRoot: ctx.workspaceRoot,
565
- })
566
- : null;
567
- const writeAllowed = !readOnly && flags.writeAllowed;
568
- // Bundle implies bash. We OR the resolver's decision into the
569
- // bash-gating knob so a customer who set PUGI_MCP_ORCHESTRATOR=1 does
570
- // not also need to pass --allow-bash (item 7 acceptance criterion).
571
- const bashAllowed = !readOnly && (flags.bashAllowed || (orchestratorConfig?.bashAllowed ?? false));
572
- // P1 — when `--orchestrator` is set the surface swaps to the
573
- // CLI-orchestrator family (pugi.run / pugi.read / pugi.write /
574
- // pugi.dispatch / pugi.publish / pugi.deploy). The engine surface is
575
- // intentionally dropped — the two are mutually exclusive on the wire
576
- // to keep tool-name resolution unambiguous on the consumer side.
577
- const tools = flags.orchestrator
578
- ? buildOrchestratorTools(buildOrchestratorContext(ctx.workspaceRoot, orchestratorConfig))
579
- : buildPugiMcpTools(toolCtx, {
580
- bashAllowed,
581
- // Keep the legacy contract: `readOnly` for the tool-builder means
582
- // "do not advertise edit/write tools". Bash advertisement is gated
583
- // by the independent `bashAllowed` knob. So the builder sees
584
- // `readOnly = true` whenever the operator did not opt into write
585
- // explicitly, which preserves the deny-by-default surface for
586
- // edit/write but no longer accidentally suppresses bash.
587
- readOnly: readOnly || !writeAllowed,
588
- });
589
- // β4 r1 P1 #2 — deny-by-default permissionGate. The MCP cache + FSM
590
- // are consulted on every dispatch; allow_always-cached entries pass
591
- // silently, allow_once entries pass and self-clear, deny entries
592
- // refuse, and unset entries refuse with a hint (operator can grant
593
- // out-of-band via `pugi mcp perm grant <tool>` — backlog).
594
- const permissionGate = buildServePermissionGate({
595
- bashAllowed,
596
- writeAllowed,
597
- });
598
- const server = createPugiMcpServer({
599
- tools,
600
- permissionGate,
601
- ...(ctx.signal ? { signal: ctx.signal } : {}),
602
- log: (level, message) => {
603
- // Stdio: keep stderr empty unless explicitly debugging. HTTP:
604
- // route through writeOutput so operators see lifecycle in JSON
605
- // mode. For now we honour stderr only — adding a --debug flag
606
- // is backlog.
607
- if (level === 'error')
608
- process.stderr.write(`pugi-mcp: ${message}\n`);
609
- },
610
- });
611
- if (flags.http) {
612
- // β4 r1 P0 #1 — token resolution. Prefer explicit operator config;
613
- // fall back to env; only auto-generate when `--print-token` is set.
614
- // The auto-generated token NEVER lands in stdout — only stderr.
615
- const envToken = process.env.PUGI_MCP_TOKEN?.trim();
616
- const explicitToken = flags.bearerToken ?? (envToken && envToken.length > 0 ? envToken : null);
617
- if (!explicitToken && !flags.printToken) {
618
- throw new Error('pugi mcp serve --http requires a bearer token. Pass --token <value>, set ' +
619
- 'PUGI_MCP_TOKEN, or add --print-token to auto-generate one (printed to stderr).');
620
- }
621
- const handle = await serveHttp({
622
- server,
623
- port: flags.http.port,
624
- host: flags.http.host,
625
- ...(explicitToken ? { bearerToken: explicitToken } : {}),
626
- ...(ctx.signal ? { signal: ctx.signal } : {}),
627
- log: (_level, message) => {
628
- process.stderr.write(`pugi-mcp-http: ${message}\n`);
629
- },
630
- });
631
- // Bearer token: print to STDERR only if auto-generated (so it never
632
- // lands in CI logs / session journals / Anvil events that capture
633
- // stdout). Operators consuming the JSON envelope must read it from
634
- // their controlling terminal. β4 r1 P0 #1.
635
- if (handle.bearerTokenAutoGenerated) {
636
- process.stderr.write(`pugi-mcp-http: AUTO-GENERATED BEARER TOKEN (use once; --print-token set):\n` +
637
- ` ${handle.bearerToken}\n` +
638
- `pugi-mcp-http: prefer --token / PUGI_MCP_TOKEN for persistent setups.\n`);
639
- }
640
- // Emit the URL + tool surface to stdout (JSON-safe). Bearer token
641
- // is intentionally omitted from the JSON payload — operators who
642
- // need it programmatically supply --token explicitly.
643
- ctx.writeOutput({
644
- command: 'mcp.serve',
645
- transport: 'http',
646
- url: handle.url,
647
- surface: flags.orchestrator ? 'orchestrator' : 'engine',
648
- bearerTokenSource: handle.bearerTokenAutoGenerated
649
- ? 'auto-generated (see stderr)'
650
- : explicitToken === envToken
651
- ? 'env:PUGI_MCP_TOKEN'
652
- : 'flag:--token',
653
- tools: tools.map((t) => t.name),
654
- }, [
655
- `pugi mcp serve (http) — listening at ${handle.url}`,
656
- handle.bearerTokenAutoGenerated
657
- ? `Bearer token: see stderr (auto-generated, --print-token)`
658
- : `Bearer token: configured via ${explicitToken === envToken ? 'PUGI_MCP_TOKEN env' : '--token flag'}`,
659
- `Tools: ${tools.map((t) => t.name).join(', ')}`,
660
- '',
661
- 'Endpoints:',
662
- ' POST /mcp/v1/initialize',
663
- ' POST /mcp/v1/list',
664
- ' POST /mcp/v1/call',
665
- ' POST /mcp/v1/rpc',
666
- ' GET /mcp/v1/events (SSE)',
667
- ' GET /mcp/v1/health (no auth)',
668
- '',
669
- 'Press Ctrl-C to stop.',
670
- ].join('\n'));
671
- // Keep the process alive while the listener is up. The signal +
672
- // SIGINT handlers below force a graceful close.
673
- await new Promise((resolveExit) => {
674
- const onSignal = async () => {
675
- await handle.close();
676
- resolveExit();
677
- };
678
- process.once('SIGINT', () => void onSignal());
679
- process.once('SIGTERM', () => void onSignal());
680
- if (ctx.signal) {
681
- if (ctx.signal.aborted)
682
- void onSignal();
683
- else
684
- ctx.signal.addEventListener('abort', () => void onSignal(), { once: true });
685
- }
686
- });
687
- return;
688
- }
689
- // Stdio transport — default. The handshake + tools/list happen on
690
- // the wire; nothing is printed unless the parent agent sends a
691
- // request that returns a response. Operator sees one info line on
692
- // stderr so they know the server is up.
693
- process.stderr.write(`pugi-mcp (stdio, ${flags.orchestrator ? 'orchestrator' : 'engine'}): ${tools.length} tool(s) — ${tools.map((t) => t.name).join(', ')}\n`);
694
- // Trust Sprint item 7 — when the orchestrator surface is live, surface
695
- // the resolved capability table on stderr so the operator can confirm
696
- // which gates ended up armed without running `pugi doctor` in another
697
- // pane. Each line is prefixed for grep-ability.
698
- if (orchestratorConfig) {
699
- for (const line of describeOrchestratorConfig(orchestratorConfig)) {
700
- process.stderr.write(`pugi-mcp orchestrator: ${line}\n`);
701
- }
702
- }
703
- await serveStdio({
704
- server,
705
- stdin: ctx.stdin ?? process.stdin,
706
- stdout: ctx.stdout ?? process.stdout,
707
- ...(ctx.signal ? { signal: ctx.signal } : {}),
708
- });
709
- }
710
- function parseServeFlags(args) {
711
- const flags = {
712
- http: null,
713
- bearerToken: null,
714
- printToken: false,
715
- readOnly: false,
716
- writeAllowed: false,
717
- bashAllowed: false,
718
- orchestrator: false,
719
- orchestratorBundle: false,
720
- };
721
- for (let i = 0; i < args.length; i += 1) {
722
- const arg = args[i] ?? '';
723
- if (arg === '--http' || arg === '-h') {
724
- const next = args[i + 1];
725
- if (!next)
726
- throw new Error('--http requires a port (e.g. --http :7100 or --http 7100)');
727
- flags.http = parseHttpBinding(next);
728
- i += 1;
729
- }
730
- else if (arg.startsWith('--http=')) {
731
- flags.http = parseHttpBinding(arg.slice('--http='.length));
732
- }
733
- else if (arg === '--host') {
734
- const next = args[i + 1];
735
- if (!next)
736
- throw new Error('--host requires a value');
737
- // Stash on a synthetic http config; binding-port may have been set
738
- // earlier, OR we initialize with port 0 and require --http.
739
- if (!flags.http) {
740
- throw new Error('--host requires --http to be set first');
741
- }
742
- flags.http.host = next;
743
- i += 1;
744
- }
745
- else if (arg === '--token') {
746
- const next = args[i + 1];
747
- if (!next)
748
- throw new Error('--token requires a value');
749
- flags.bearerToken = next;
750
- i += 1;
751
- }
752
- else if (arg.startsWith('--token=')) {
753
- flags.bearerToken = arg.slice('--token='.length);
754
- }
755
- else if (arg === '--print-token') {
756
- flags.printToken = true;
757
- }
758
- else if (arg === '--read-only') {
759
- flags.readOnly = true;
760
- }
761
- else if (arg === '--allow-write') {
762
- flags.writeAllowed = true;
763
- }
764
- else if (arg === '--allow-bash') {
765
- flags.bashAllowed = true;
766
- // bash implies write capability is meaningless (bash runs anything);
767
- // we still keep them orthogonal so an operator can grant bash
768
- // without exposing the structured `edit` / `write` tools to the
769
- // MCP surface (which would let an external agent rewrite files
770
- // without going through the diff escalation pipeline).
771
- }
772
- else if (arg === '--no-bash') {
773
- // Deprecated — bash is already off by default. Kept for back-compat
774
- // so existing operator scripts do not error.
775
- flags.bashAllowed = false;
776
- }
777
- else if (arg === '--orchestrator') {
778
- flags.orchestrator = true;
779
- }
780
- else if (arg === '--orchestrator-bundle') {
781
- // Trust Sprint item 7 — single-flag form. Implies orchestrator
782
- // surface + bash + exec. Mutual upgrade with --orchestrator: if
783
- // both are passed the bundle wins (it is a strict superset).
784
- flags.orchestrator = true;
785
- flags.orchestratorBundle = true;
786
- flags.bashAllowed = true;
787
- }
788
- else if (arg === '--help') {
789
- // Caller renders USAGE_LINES. We surface the same via top-level
790
- // dispatch — nothing to do here, just don't error.
791
- }
792
- else {
793
- throw new Error(`pugi mcp serve: unknown flag "${arg}"`);
794
- }
795
- }
796
- return flags;
797
- }
798
- /**
799
- * Build the permission gate for `pugi mcp serve`. Default policy is
800
- * deny-with-hint. The MCP cache layer at `~/.pugi/mcp-perms.json` is
801
- * consulted for `allow_always` / `deny` decisions; shell-class tools
802
- * (bash) can NEVER hold `allow_always` (enforced by `setMcpPermission`),
803
- * so a cached approval still requires per-call FSM prompt.
804
- *
805
- * Wired here as a thin synchronous policy because the serve path runs
806
- * in a long-lived non-TTY context (HTTP) — interactive prompts would
807
- * block forever. The future TTY-mode interactive prompt lives on top
808
- * of this gate in `pugi mcp serve --interactive` (backlog).
809
- */
810
- function buildServePermissionGate(opts) {
811
- return async (input) => {
812
- const { tool } = input;
813
- // Surface-level gate: even if the cache says allow_always, the
814
- // operator's serve-flag opt-in is the ultimate authority. A
815
- // misconfigured cache entry (legacy approval) cannot override the
816
- // serve-time policy.
817
- if (tool.permission === 'bash' && !opts.bashAllowed)
818
- return false;
819
- if (tool.permission === 'edit' && !opts.writeAllowed)
820
- return false;
821
- // `network` is the permission class used by orchestrator tools
822
- // (pugi.dispatch / pugi.publish / pugi.deploy). The env capability
823
- // gates inside each tool's `execute` body provide the per-family
824
- // kill switch, so the serve-time gate is permissive here. The
825
- // server's overall `permissionGate` is already deny-most-other —
826
- // adding a third boolean knob (`networkAllowed`) would create more
827
- // ways to misconfigure than to protect. P1 .
828
- return true;
829
- };
830
- }
831
- /**
832
- * Build the OrchestratorToolContext for `pugi mcp serve --orchestrator`.
833
- * Reads from process.env + the credentials store. Encapsulated so tests
834
- * never need to mock the resolveActiveCredential path — they call
835
- * `buildOrchestratorTools` directly with a hand-rolled context.
836
- *
837
- * P1 .
838
- */
839
- function buildOrchestratorContext(workspaceRoot, resolved = null) {
840
- const envRoot = process.env.PUGI_MCP_WORKSPACE_ROOT;
841
- const root = envRoot && envRoot.length > 0 ? resolve(envRoot) : workspaceRoot;
842
- const credential = resolveActiveCredential();
843
- // Trust Sprint item 7 — when `resolved` is provided we honour the
844
- // consolidated capability bits. When absent (legacy direct call from
845
- // a test) we fall back to the raw env so existing harnesses keep
846
- // working untouched.
847
- const execEnabled = resolved
848
- ? resolved.execEnabled
849
- : process.env.PUGI_MCP_EXEC_ENABLED === '1';
850
- const publishEnabled = resolved
851
- ? resolved.publishEnabled
852
- : process.env.PUGI_MCP_PUBLISH_ENABLED === '1';
853
- const deployEnabled = resolved
854
- ? resolved.deployEnabled
855
- : process.env.PUGI_MCP_DEPLOY_ENABLED === '1';
856
- const pugiBin = resolved
857
- ? resolved.pugiBin
858
- : (process.env.PUGI_MCP_PUGI_BIN ?? 'pugi');
859
- return {
860
- workspaceRoot: root,
861
- pugiBin,
862
- apiUrl: credential?.apiUrl ?? DEFAULT_API_URL,
863
- apiKey: credential?.apiKey ?? null,
864
- capabilities: {
865
- exec: execEnabled,
866
- publish: publishEnabled,
867
- deploy: deployEnabled,
868
- },
869
- sshAlias: process.env.PUGI_MCP_SSH_ALIAS ?? 'codeforge',
870
- };
871
- }
872
- function parseHttpBinding(input) {
873
- // Accept `:7100`, `7100`, or `host:7100`.
874
- let host = '127.0.0.1';
875
- let portStr = input;
876
- if (input.startsWith(':')) {
877
- portStr = input.slice(1);
878
- }
879
- else if (input.includes(':')) {
880
- const idx = input.lastIndexOf(':');
881
- host = input.slice(0, idx);
882
- portStr = input.slice(idx + 1);
883
- }
884
- const port = Number.parseInt(portStr, 10);
885
- if (!Number.isInteger(port) || port <= 0 || port > 65535) {
886
- throw new Error(`invalid --http port: "${input}" (expected :PORT or HOST:PORT)`);
887
- }
888
- return { host, port };
889
- }
890
- /* ---------- perms ------------------------------------------------------ */
891
- async function runMcpPerms(args, ctx) {
892
- const sub = args[0] ?? 'list';
893
- switch (sub) {
894
- case 'list': {
895
- const entries = listMcpPermissions();
896
- ctx.writeOutput({ command: 'mcp.perms.list', entries }, entries.length === 0
897
- ? 'No cached MCP permission decisions.'
898
- : [
899
- 'MCP permission cache:',
900
- ...entries.map((entry) => ` ${entry.server.padEnd(16)} ${entry.tool.padEnd(20)} ${entry.decision.padEnd(14)} ${entry.decidedAt}`),
901
- ].join('\n'));
902
- return;
903
- }
904
- case 'reset': {
905
- const target = args[1];
906
- if (!target || !target.includes(':')) {
907
- throw new Error('Usage: pugi mcp perms reset <server>:<tool>');
908
- }
909
- const idx = target.indexOf(':');
910
- const server = target.slice(0, idx);
911
- const tool = target.slice(idx + 1);
912
- const removed = clearMcpPermission(server, tool);
913
- ctx.writeOutput({ command: 'mcp.perms.reset', server, tool, removed }, removed
914
- ? `Forgot permission decision for ${server}:${tool}.`
915
- : `No cached decision for ${server}:${tool}.`);
916
- return;
917
- }
918
- default:
919
- throw new Error(`Unknown "pugi mcp perms ${sub}". Try: list, reset.`);
920
- }
921
- }
922
- function resolveDecidedBy() {
923
- return (process.env.PUGI_TRUSTED_BY?.trim() ||
924
- process.env.USER?.trim() ||
925
- process.env.USERNAME?.trim() ||
926
- 'cli');
927
- }
928
- /**
929
- * Resolve the effective user home for diagnostics (e.g. surfacing the
930
- * trust ledger path in `pugi mcp list --verbose`). Mirrors registry.ts.
931
- */
932
- export function pugiHome() {
933
- return process.env.PUGI_HOME ?? resolve(homedir(), '.pugi');
934
- }
935
- //# sourceMappingURL=mcp.js.map