@team-agent/installer 0.2.10 → 0.3.0

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 (326) hide show
  1. package/Cargo.lock +744 -0
  2. package/Cargo.toml +34 -0
  3. package/crates/team-agent/Cargo.toml +33 -0
  4. package/crates/team-agent/src/cli/adapters.rs +1343 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +554 -0
  6. package/crates/team-agent/src/cli/emit.rs +1077 -0
  7. package/crates/team-agent/src/cli/helpers.rs +88 -0
  8. package/crates/team-agent/src/cli/leader.rs +216 -0
  9. package/crates/team-agent/src/cli/mod.rs +1141 -0
  10. package/crates/team-agent/src/cli/profile.rs +306 -0
  11. package/crates/team-agent/src/cli/send.rs +215 -0
  12. package/crates/team-agent/src/cli/status.rs +179 -0
  13. package/crates/team-agent/src/cli/status_port.rs +502 -0
  14. package/crates/team-agent/src/cli/tests/base.rs +616 -0
  15. package/crates/team-agent/src/cli/tests/compile.rs +96 -0
  16. package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
  21. package/crates/team-agent/src/cli/tests/mod.rs +97 -0
  22. package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
  23. package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
  25. package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
  27. package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
  28. package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
  29. package/crates/team-agent/src/cli/types.rs +605 -0
  30. package/crates/team-agent/src/compiler/tests.rs +701 -0
  31. package/crates/team-agent/src/compiler.rs +489 -0
  32. package/crates/team-agent/src/coordinator/backoff.rs +153 -0
  33. package/crates/team-agent/src/coordinator/health.rs +436 -0
  34. package/crates/team-agent/src/coordinator/mod.rs +80 -0
  35. package/crates/team-agent/src/coordinator/orphan.rs +179 -0
  36. package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
  37. package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
  38. package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
  39. package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
  40. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
  41. package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
  42. package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
  43. package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
  44. package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
  45. package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
  46. package/crates/team-agent/src/coordinator/tick.rs +2032 -0
  47. package/crates/team-agent/src/coordinator/types.rs +584 -0
  48. package/crates/team-agent/src/db/migration.rs +716 -0
  49. package/crates/team-agent/src/db/mod.rs +23 -0
  50. package/crates/team-agent/src/db/schema.rs +378 -0
  51. package/crates/team-agent/src/event_log.rs +375 -0
  52. package/crates/team-agent/src/fake_worker.rs +253 -0
  53. package/crates/team-agent/src/leader/helpers.rs +190 -0
  54. package/crates/team-agent/src/leader/inject.rs +33 -0
  55. package/crates/team-agent/src/leader/lease.rs +1063 -0
  56. package/crates/team-agent/src/leader/mod.rs +99 -0
  57. package/crates/team-agent/src/leader/owner_bind.rs +292 -0
  58. package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
  59. package/crates/team-agent/src/leader/rediscover.rs +1099 -0
  60. package/crates/team-agent/src/leader/start.rs +273 -0
  61. package/crates/team-agent/src/leader/takeover.rs +235 -0
  62. package/crates/team-agent/src/leader/tests/basics.rs +183 -0
  63. package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
  64. package/crates/team-agent/src/leader/tests/identity.rs +206 -0
  65. package/crates/team-agent/src/leader/tests/idle.rs +271 -0
  66. package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
  67. package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
  68. package/crates/team-agent/src/leader/tests/mod.rs +125 -0
  69. package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
  70. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
  71. package/crates/team-agent/src/leader/types.rs +487 -0
  72. package/crates/team-agent/src/lib.rs +85 -0
  73. package/crates/team-agent/src/lifecycle/display.rs +228 -0
  74. package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
  75. package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
  76. package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
  77. package/crates/team-agent/src/lifecycle/mod.rs +62 -0
  78. package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
  79. package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
  80. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
  81. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
  82. package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
  83. package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
  84. package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
  85. package/crates/team-agent/src/lifecycle/restart.rs +76 -0
  86. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
  88. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
  89. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
  90. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
  91. package/crates/team-agent/src/lifecycle/tests.rs +27 -0
  92. package/crates/team-agent/src/lifecycle/types.rs +685 -0
  93. package/crates/team-agent/src/main.rs +41 -0
  94. package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
  95. package/crates/team-agent/src/mcp_server/mod.rs +183 -0
  96. package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
  97. package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
  98. package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
  99. package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
  100. package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
  101. package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
  102. package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
  103. package/crates/team-agent/src/mcp_server/tests.rs +38 -0
  104. package/crates/team-agent/src/mcp_server/tools.rs +603 -0
  105. package/crates/team-agent/src/mcp_server/types.rs +421 -0
  106. package/crates/team-agent/src/mcp_server/wire.rs +388 -0
  107. package/crates/team-agent/src/message_store.rs +767 -0
  108. package/crates/team-agent/src/messaging/activity.rs +433 -0
  109. package/crates/team-agent/src/messaging/delivery.rs +542 -0
  110. package/crates/team-agent/src/messaging/helpers.rs +209 -0
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
  112. package/crates/team-agent/src/messaging/mod.rs +147 -0
  113. package/crates/team-agent/src/messaging/peers.rs +32 -0
  114. package/crates/team-agent/src/messaging/results.rs +537 -0
  115. package/crates/team-agent/src/messaging/scheduler.rs +344 -0
  116. package/crates/team-agent/src/messaging/selftest.rs +100 -0
  117. package/crates/team-agent/src/messaging/send.rs +582 -0
  118. package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
  119. package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
  121. package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
  122. package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
  123. package/crates/team-agent/src/messaging/trust.rs +192 -0
  124. package/crates/team-agent/src/messaging/types.rs +355 -0
  125. package/crates/team-agent/src/messaging/watchers.rs +591 -0
  126. package/crates/team-agent/src/model/enums.rs +311 -0
  127. package/crates/team-agent/src/model/errors.rs +17 -0
  128. package/crates/team-agent/src/model/ids.rs +155 -0
  129. package/crates/team-agent/src/model/mod.rs +22 -0
  130. package/crates/team-agent/src/model/paths.rs +228 -0
  131. package/crates/team-agent/src/model/permissions.rs +567 -0
  132. package/crates/team-agent/src/model/routing.rs +340 -0
  133. package/crates/team-agent/src/model/spec.rs +680 -0
  134. package/crates/team-agent/src/model/task_graph.rs +380 -0
  135. package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
  136. package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
  137. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
  138. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
  139. package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
  140. package/crates/team-agent/src/model/yaml/tests.rs +288 -0
  141. package/crates/team-agent/src/model/yaml.rs +800 -0
  142. package/crates/team-agent/src/packaging/install.rs +305 -0
  143. package/crates/team-agent/src/packaging/migrate.rs +30 -0
  144. package/crates/team-agent/src/packaging/mod.rs +82 -0
  145. package/crates/team-agent/src/packaging/repair.rs +24 -0
  146. package/crates/team-agent/src/packaging/tests.rs +829 -0
  147. package/crates/team-agent/src/packaging/types.rs +369 -0
  148. package/crates/team-agent/src/provider/adapter.rs +801 -0
  149. package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
  150. package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
  151. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
  152. package/crates/team-agent/src/provider/classify.rs +456 -0
  153. package/crates/team-agent/src/provider/faults.rs +136 -0
  154. package/crates/team-agent/src/provider/helpers.rs +41 -0
  155. package/crates/team-agent/src/provider/mod.rs +53 -0
  156. package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
  157. package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
  158. package/crates/team-agent/src/provider/tests/classify.rs +240 -0
  159. package/crates/team-agent/src/provider/tests/faults.rs +120 -0
  160. package/crates/team-agent/src/provider/tests/idle.rs +208 -0
  161. package/crates/team-agent/src/provider/tests/wire.rs +213 -0
  162. package/crates/team-agent/src/provider/tests.rs +31 -0
  163. package/crates/team-agent/src/provider/types.rs +424 -0
  164. package/crates/team-agent/src/state/identity.rs +656 -0
  165. package/crates/team-agent/src/state/mod.rs +58 -0
  166. package/crates/team-agent/src/state/owner_gate.rs +423 -0
  167. package/crates/team-agent/src/state/persist.rs +712 -0
  168. package/crates/team-agent/src/state/projection.rs +657 -0
  169. package/crates/team-agent/src/state/selector.rs +105 -0
  170. package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
  171. package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
  172. package/crates/team-agent/src/tmux_backend.rs +758 -0
  173. package/crates/team-agent/src/transport/test_support.rs +252 -0
  174. package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
  175. package/crates/team-agent/src/transport/tests/mod.rs +199 -0
  176. package/crates/team-agent/src/transport/tests/wire.rs +527 -0
  177. package/crates/team-agent/src/transport.rs +774 -0
  178. package/npm/install.mjs +90 -106
  179. package/package.json +15 -13
  180. package/crates/team-agent-core/Cargo.toml +0 -12
  181. package/crates/team-agent-core/src/lib.rs +0 -332
  182. package/crates/team-agent-core/src/main.rs +0 -152
  183. package/pyproject.toml +0 -18
  184. package/scripts/install.py +0 -88
  185. package/scripts/run_regression_tests.py +0 -83
  186. package/src/team_agent/__init__.py +0 -3
  187. package/src/team_agent/__main__.py +0 -5
  188. package/src/team_agent/_legacy_pane_discovery.py +0 -186
  189. package/src/team_agent/abnormal_track.py +0 -253
  190. package/src/team_agent/approvals/__init__.py +0 -65
  191. package/src/team_agent/approvals/constants.py +0 -6
  192. package/src/team_agent/approvals/parsing.py +0 -176
  193. package/src/team_agent/approvals/runtime_prompts.py +0 -171
  194. package/src/team_agent/approvals/status.py +0 -176
  195. package/src/team_agent/cli/__init__.py +0 -137
  196. package/src/team_agent/cli/commands.py +0 -481
  197. package/src/team_agent/cli/e2e.py +0 -202
  198. package/src/team_agent/cli/helpers.py +0 -226
  199. package/src/team_agent/cli/parser.py +0 -540
  200. package/src/team_agent/compiler.py +0 -334
  201. package/src/team_agent/coordinator/__init__.py +0 -53
  202. package/src/team_agent/coordinator/__main__.py +0 -83
  203. package/src/team_agent/coordinator/lifecycle.py +0 -363
  204. package/src/team_agent/coordinator/metadata.py +0 -61
  205. package/src/team_agent/coordinator/paths.py +0 -17
  206. package/src/team_agent/diagnose/__init__.py +0 -48
  207. package/src/team_agent/diagnose/checks.py +0 -101
  208. package/src/team_agent/diagnose/comms.py +0 -213
  209. package/src/team_agent/diagnose/health.py +0 -241
  210. package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
  211. package/src/team_agent/diagnose/preflight.py +0 -194
  212. package/src/team_agent/diagnose/quick_start.py +0 -324
  213. package/src/team_agent/display/__init__.py +0 -92
  214. package/src/team_agent/display/adaptive.py +0 -511
  215. package/src/team_agent/display/backend.py +0 -46
  216. package/src/team_agent/display/close.py +0 -154
  217. package/src/team_agent/display/ghostty.py +0 -77
  218. package/src/team_agent/display/rebuild.py +0 -102
  219. package/src/team_agent/display/tiling.py +0 -156
  220. package/src/team_agent/display/worker_window.py +0 -114
  221. package/src/team_agent/display/workspace.py +0 -382
  222. package/src/team_agent/errors.py +0 -10
  223. package/src/team_agent/events.py +0 -84
  224. package/src/team_agent/fake_worker.py +0 -80
  225. package/src/team_agent/idle_predicate.py +0 -200
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -111
  228. package/src/team_agent/launch/__init__.py +0 -41
  229. package/src/team_agent/launch/bootstrap.py +0 -85
  230. package/src/team_agent/launch/config.py +0 -106
  231. package/src/team_agent/launch/core.py +0 -301
  232. package/src/team_agent/launch/requirements.py +0 -57
  233. package/src/team_agent/leader/__init__.py +0 -926
  234. package/src/team_agent/leader_binding.py +0 -183
  235. package/src/team_agent/lifecycle/__init__.py +0 -5
  236. package/src/team_agent/lifecycle/agents.py +0 -278
  237. package/src/team_agent/lifecycle/operations.py +0 -411
  238. package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
  239. package/src/team_agent/lifecycle/start.py +0 -363
  240. package/src/team_agent/mcp_server/__init__.py +0 -42
  241. package/src/team_agent/mcp_server/__main__.py +0 -7
  242. package/src/team_agent/mcp_server/contracts.py +0 -148
  243. package/src/team_agent/mcp_server/normalize.py +0 -257
  244. package/src/team_agent/mcp_server/server.py +0 -150
  245. package/src/team_agent/mcp_server/tools.py +0 -352
  246. package/src/team_agent/message_store/__init__.py +0 -23
  247. package/src/team_agent/message_store/agent_health.py +0 -113
  248. package/src/team_agent/message_store/core.py +0 -497
  249. package/src/team_agent/message_store/leader_notification_log.py +0 -198
  250. package/src/team_agent/message_store/result_watchers.py +0 -251
  251. package/src/team_agent/message_store/schema.py +0 -308
  252. package/src/team_agent/message_store/schema_migration.py +0 -448
  253. package/src/team_agent/messaging/__init__.py +0 -1
  254. package/src/team_agent/messaging/activity_detector.py +0 -254
  255. package/src/team_agent/messaging/delivery.py +0 -473
  256. package/src/team_agent/messaging/deps.py +0 -247
  257. package/src/team_agent/messaging/idle_alerts.py +0 -423
  258. package/src/team_agent/messaging/internal_delivery.py +0 -46
  259. package/src/team_agent/messaging/leader.py +0 -497
  260. package/src/team_agent/messaging/leader_api_errors.py +0 -216
  261. package/src/team_agent/messaging/leader_panes.py +0 -673
  262. package/src/team_agent/messaging/owner_bypass.py +0 -29
  263. package/src/team_agent/messaging/result_delivery.py +0 -539
  264. package/src/team_agent/messaging/results.py +0 -447
  265. package/src/team_agent/messaging/scheduler.py +0 -450
  266. package/src/team_agent/messaging/send.py +0 -532
  267. package/src/team_agent/messaging/session_drift.py +0 -94
  268. package/src/team_agent/messaging/tmux_io.py +0 -506
  269. package/src/team_agent/messaging/tmux_prompt.py +0 -338
  270. package/src/team_agent/messaging/trust_auto_answer.py +0 -52
  271. package/src/team_agent/orchestrator/__init__.py +0 -376
  272. package/src/team_agent/orchestrator/plan.py +0 -122
  273. package/src/team_agent/orchestrator/state.py +0 -128
  274. package/src/team_agent/paths.py +0 -45
  275. package/src/team_agent/permissions.py +0 -123
  276. package/src/team_agent/profiles/__init__.py +0 -82
  277. package/src/team_agent/profiles/constants.py +0 -19
  278. package/src/team_agent/profiles/core.py +0 -407
  279. package/src/team_agent/profiles/helpers.py +0 -69
  280. package/src/team_agent/profiles/provider_env.py +0 -188
  281. package/src/team_agent/profiles/smoke.py +0 -201
  282. package/src/team_agent/provider_cli/__init__.py +0 -43
  283. package/src/team_agent/provider_cli/adapter.py +0 -172
  284. package/src/team_agent/provider_cli/base.py +0 -48
  285. package/src/team_agent/provider_cli/claude.py +0 -457
  286. package/src/team_agent/provider_cli/codex.py +0 -336
  287. package/src/team_agent/provider_cli/copilot.py +0 -8
  288. package/src/team_agent/provider_cli/fake.py +0 -39
  289. package/src/team_agent/provider_cli/gemini.py +0 -95
  290. package/src/team_agent/provider_cli/opencode.py +0 -8
  291. package/src/team_agent/provider_cli/prompt.py +0 -62
  292. package/src/team_agent/provider_cli/registry.py +0 -18
  293. package/src/team_agent/provider_cli/unsupported.py +0 -32
  294. package/src/team_agent/provider_state/README.md +0 -78
  295. package/src/team_agent/provider_state/__init__.py +0 -86
  296. package/src/team_agent/provider_state/claude.py +0 -86
  297. package/src/team_agent/provider_state/codex.py +0 -84
  298. package/src/team_agent/provider_state/common.py +0 -207
  299. package/src/team_agent/provider_state/registry.py +0 -118
  300. package/src/team_agent/providers.py +0 -163
  301. package/src/team_agent/quality_gates.py +0 -104
  302. package/src/team_agent/restart/__init__.py +0 -34
  303. package/src/team_agent/restart/orchestration.py +0 -554
  304. package/src/team_agent/restart/selection.py +0 -89
  305. package/src/team_agent/restart/snapshot.py +0 -70
  306. package/src/team_agent/routing.py +0 -84
  307. package/src/team_agent/runtime.py +0 -1239
  308. package/src/team_agent/rust_core.py +0 -327
  309. package/src/team_agent/sessions/__init__.py +0 -25
  310. package/src/team_agent/sessions/capture.py +0 -143
  311. package/src/team_agent/sessions/inventory.py +0 -44
  312. package/src/team_agent/sessions/resume.py +0 -135
  313. package/src/team_agent/simple_yaml.py +0 -236
  314. package/src/team_agent/spec.py +0 -370
  315. package/src/team_agent/state.py +0 -602
  316. package/src/team_agent/status/__init__.py +0 -63
  317. package/src/team_agent/status/approvals.py +0 -52
  318. package/src/team_agent/status/compact.py +0 -158
  319. package/src/team_agent/status/constants.py +0 -18
  320. package/src/team_agent/status/inbox.py +0 -58
  321. package/src/team_agent/status/peek.py +0 -117
  322. package/src/team_agent/status/queries.py +0 -199
  323. package/src/team_agent/task_graph.py +0 -80
  324. package/src/team_agent/terminal.py +0 -57
  325. package/src/team_agent/wake.py +0 -58
  326. package/src/team_agent/watch/__init__.py +0 -145
@@ -0,0 +1,1077 @@
1
+ //! cli · emit — `emit`(--json vs 人读 dict 逐键)+ 顶层 `run` 调度(parser.py `main`)+
2
+ //! 人读标量/集合渲染(`human_value` / `json_dumps_like`)。
3
+
4
+ use super::*;
5
+ use std::io::Write as _;
6
+
7
+ /// `emit`(`helpers.py:12-23`):`--json`→`json.dumps(indent=2, ensure_ascii=False, sort_keys=True)`;
8
+ /// 否则 dict 逐键 `key: value`(嵌套 dict/list 内联 compact json,`ensure_ascii=False`)、非 dict 直接 print。
9
+ /// 返回应打印到 stdout 的字符串(bin main 负责实际 println)。
10
+ pub fn emit(output: &CmdOutput, as_json: bool) -> Option<String> {
11
+ match output {
12
+ CmdOutput::None => None,
13
+ CmdOutput::Human(text) => Some(text.clone()),
14
+ CmdOutput::Json(value) if as_json => serde_json::to_string_pretty(&sort_json(value)).ok(),
15
+ CmdOutput::Json(Value::Object(obj)) => {
16
+ let lines: Vec<String> = obj
17
+ .iter()
18
+ .map(|(key, value)| format!("{key}: {}", human_value(value)))
19
+ .collect();
20
+ Some(lines.join("\n"))
21
+ }
22
+ CmdOutput::Json(value) => Some(human_value(value)),
23
+ }
24
+ }
25
+
26
+ /// `main(argv)`(`parser.py:84`):**CLI 唯一进程入口**。codex/claude passthrough 早返回 →
27
+ /// 解析 argv 到 subcommand → 调对应 handler → 异常落盘 + 信封 + `ExitCode::Error` →
28
+ /// `consume_leader_inbox_summary` → `emit` → `result.ok is False ? Error : Ok`。
29
+ /// **行为入口**:契约可端到端跑 argv→(stdout, exit code)。
30
+ pub fn run(argv: &[String], cwd: &Path) -> ExitCode {
31
+ let Some(command) = argv.first().map(String::as_str) else {
32
+ return emit_missing_subcommand_usage();
33
+ };
34
+ if command == "codex" || command == "claude" {
35
+ return cmd_leader_passthrough(command, &argv[1..], cwd)
36
+ .map(emit_result)
37
+ .unwrap_or(ExitCode::Error);
38
+ }
39
+ if matches!(command, "-h" | "--help" | "help") {
40
+ println!("{}", command_help(None));
41
+ return ExitCode::Ok;
42
+ }
43
+ // CR-063/G4: every REGISTERED subcommand's `--help` must short-circuit BEFORE dispatch —
44
+ // before argument validation, leader-pane checks, or runtime-state writes. The prior
45
+ // `command_has_help` whitelist (quick-start/start/stop/...) silently dropped `--help`
46
+ // for add-agent / stop-agent / reset-agent / claim-leader / attach-leader, so those
47
+ // fell into handlers that emitted "missing agent" or "caller_not_leader_shaped" and
48
+ // created `.team/logs/cli-error-*.log` under cwd.
49
+ //
50
+ // The gate stays on KNOWN subcommands so an unknown command still falls through to
51
+ // the argparse-style invalid-choice path (golden parser.py:84; covered by
52
+ // `cli_unknown_command_red` and the `claude_code` divergence guard which would
53
+ // otherwise be silently passthrough-shaped).
54
+ if is_known_subcommand(command)
55
+ && argv.iter().skip(1).any(|arg| matches!(arg.as_str(), "-h" | "--help"))
56
+ {
57
+ println!("{}", command_help(Some(command)));
58
+ return ExitCode::Ok;
59
+ }
60
+ match dispatch(command, &argv[1..], cwd) {
61
+ Ok(exit) => exit,
62
+ Err(error) => emit_cli_error(command, &argv[1..], cwd, &error),
63
+ }
64
+ }
65
+
66
+ /// Print a handler's CmdResult to stdout (emit formats json/human), then surface its exit code.
67
+ /// (parser.py: `print(emit(result, as_json))` then the ok→exit mapping.)
68
+ fn emit_result(r: CmdResult) -> ExitCode {
69
+ if let Some(text) = emit(&r.output, r.as_json) {
70
+ println!("{text}");
71
+ }
72
+ r.exit
73
+ }
74
+
75
+ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliError> {
76
+ match command {
77
+ "init" => cmd_init(&init_args(args, cwd)).map(emit_result),
78
+ "quick-start" => cmd_quick_start(&quick_start_args(args, cwd)?).map(emit_result),
79
+ "compile" => cmd_compile(&compile_args(args, cwd)?).map(emit_result),
80
+ "send" => cmd_send(&send_args(args, cwd)?).map(emit_result),
81
+ "allow-peer-talk" => cmd_allow_peer_talk(&allow_peer_talk_args(args, cwd)?).map(emit_result),
82
+ "status" => cmd_status(&status_args(args, cwd)).map(emit_result),
83
+ "stop" => cmd_shutdown(&shutdown_args(args, cwd)).map(emit_result),
84
+ "shutdown" => cmd_shutdown(&shutdown_args(args, cwd)).map(emit_result),
85
+ "restart" => cmd_restart(&restart_args(args, cwd)).map(emit_result),
86
+ "restart-agent" => cmd_reset_agent(&reset_agent_args(args, cwd)?).map(emit_result),
87
+ "start-agent" => cmd_start_agent(&start_agent_args(args, cwd)?).map(emit_result),
88
+ "stop-agent" => cmd_stop_agent(&stop_agent_args(args, cwd)?).map(emit_result),
89
+ "reset-agent" => cmd_reset_agent(&reset_agent_args(args, cwd)?).map(emit_result),
90
+ "add-agent" => cmd_add_agent(&add_agent_args(args, cwd)?).map(emit_result),
91
+ "fork-agent" => cmd_fork_agent(&fork_agent_args(args, cwd)?).map(emit_result),
92
+ "remove-agent" => cmd_remove_agent(&remove_agent_args(args, cwd)?).map(emit_result),
93
+ "stuck-list" => cmd_stuck_list(&stuck_list_args(args, cwd)).map(emit_result),
94
+ "stuck-cancel" => cmd_stuck_cancel(&stuck_cancel_args(args, cwd)?).map(emit_result),
95
+ "acknowledge-idle" => cmd_acknowledge_idle(&acknowledge_idle_args(args, cwd)).map(emit_result),
96
+ "takeover" => cmd_takeover(&takeover_args(args, cwd)).map(emit_result),
97
+ "claim-leader" => cmd_claim_leader(&claim_leader_args(args, cwd)).map(emit_result),
98
+ "identity" => cmd_identity(&identity_args(args, cwd)).map(emit_result),
99
+ "approvals" => cmd_approvals(&approvals_args(args, cwd)).map(emit_result),
100
+ "inbox" => cmd_inbox(&inbox_args(args, cwd)?).map(emit_result),
101
+ "doctor" => cmd_doctor(&doctor_args(args, cwd)).map(emit_result),
102
+ "watch" => cmd_watch(&watch_args(args, cwd)).map(emit_result),
103
+ "sessions" => cmd_sessions(&sessions_args(args, cwd)).map(emit_result),
104
+ "validate" => cmd_validate(&validate_args(args, cwd)).map(emit_result),
105
+ "profile" => cmd_profile(&profile_args(args, cwd)?).map(emit_result),
106
+ "validate-result" if has_arg(args, "--result") => {
107
+ eprintln!("team-agent: error: unrecognized arguments: --result");
108
+ Ok(ExitCode::Usage)
109
+ }
110
+ "validate-result" => cmd_validate_result(&validate_result_args(args)?).map(emit_result),
111
+ "collect" => cmd_collect(&collect_args(args, cwd)).map(emit_result),
112
+ "settle" => cmd_settle(&settle_args(args, cwd)).map(emit_result),
113
+ "repair-state" => cmd_repair_state(&repair_state_args(args, cwd)?).map(emit_result),
114
+ "diagnose" => cmd_diagnose(&diagnose_args(args, cwd)).map(emit_result),
115
+ "preflight" => cmd_preflight(&preflight_args(args, cwd)).map(emit_result),
116
+ "wait-ready" => cmd_wait_ready(&wait_ready_args(args, cwd)).map(emit_result),
117
+ "e2e" => cmd_e2e(&e2e_args(args, cwd)).map(emit_result),
118
+ "peek" => cmd_peek(&peek_args(args, cwd)?).map(emit_result),
119
+ "coordinator" => run_coordinator(args, cwd),
120
+ _ => Ok(emit_unknown_subcommand_usage(command)),
121
+ }
122
+ }
123
+
124
+ fn emit_missing_subcommand_usage() -> ExitCode {
125
+ emit_usage_error("the following arguments are required: {codex,claude,...,doctor}");
126
+ ExitCode::Usage
127
+ }
128
+
129
+ /// Registered subcommands (the dispatch table) PLUS spec-only verbs that have no
130
+ /// dispatch arm yet but must still respond to `--help` per CR-063/G4 (`attach-leader`).
131
+ /// Used by the `--help` short-circuit gate so unknown commands keep falling through
132
+ /// to the argparse invalid-choice path.
133
+ fn is_known_subcommand(command: &str) -> bool {
134
+ matches!(
135
+ command,
136
+ "init"
137
+ | "quick-start"
138
+ | "compile"
139
+ | "send"
140
+ | "allow-peer-talk"
141
+ | "status"
142
+ | "start"
143
+ | "stop"
144
+ | "shutdown"
145
+ | "restart"
146
+ | "restart-agent"
147
+ | "start-agent"
148
+ | "stop-agent"
149
+ | "reset-agent"
150
+ | "add-agent"
151
+ | "fork-agent"
152
+ | "remove-agent"
153
+ | "purge-agent"
154
+ | "stuck-list"
155
+ | "stuck-cancel"
156
+ | "acknowledge-idle"
157
+ | "takeover"
158
+ | "claim-leader"
159
+ | "attach-leader"
160
+ | "identity"
161
+ | "approvals"
162
+ | "inbox"
163
+ | "doctor"
164
+ | "watch"
165
+ | "sessions"
166
+ | "validate"
167
+ | "profile"
168
+ | "validate-result"
169
+ | "collect"
170
+ | "settle"
171
+ | "repair-state"
172
+ | "diagnose"
173
+ | "preflight"
174
+ | "wait-ready"
175
+ | "e2e"
176
+ | "peek"
177
+ | "coordinator"
178
+ )
179
+ }
180
+
181
+ fn command_help(command: Option<&str>) -> String {
182
+ match command {
183
+ None => "usage: team-agent <command> [options]\n\nCommands: quick-start, status, send, collect, start, stop, shutdown, restart, restart-agent, start-agent, stop-agent, reset-agent, add-agent, remove-agent, purge-agent".to_string(),
184
+ Some("quick-start") => "usage: team-agent quick-start [TEAMDIR] [--name NAME] [--team-id TEAM] [--yes] [--fresh] [--json]".to_string(),
185
+ Some("start") => "usage: team-agent start [TEAMDIR] [--yes] [--fresh] [--json]".to_string(),
186
+ Some("stop") | Some("shutdown") => "usage: team-agent stop [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]".to_string(),
187
+ Some("restart-agent") => "usage: team-agent restart-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--discard-session] [--no-display] [--json]".to_string(),
188
+ Some("purge-agent") => "usage: team-agent purge-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--force] [--json]".to_string(),
189
+ Some("restart") => "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--json]".to_string(),
190
+ Some("status") => "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--summary|--json] [--detail]".to_string(),
191
+ Some("send") => "usage: team-agent send TARGET MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
192
+ Some("collect") => "usage: team-agent collect [--workspace WORKSPACE] [--result-file FILE] [--json]".to_string(),
193
+ Some(other) => format!("usage: team-agent {other} [options]"),
194
+ }
195
+ }
196
+
197
+ fn emit_unknown_subcommand_usage(command: &str) -> ExitCode {
198
+ emit_usage_error(&format!(
199
+ "argument {{codex,claude,...,doctor}}: invalid choice: '{command}' (choose from codex, claude, ..., doctor)"
200
+ ));
201
+ ExitCode::Usage
202
+ }
203
+
204
+ fn emit_usage_error(message: &str) {
205
+ eprintln!("usage: team-agent [-h] {{codex,claude,...,doctor}} ...");
206
+ eprintln!("team-agent: error: {message}");
207
+ }
208
+
209
+ /// `cmd_validate` delegates to runtime validate_file.
210
+ pub fn cmd_validate(args: &ValidateArgs) -> Result<CmdResult, CliError> {
211
+ let spec = resolve_path(&args.spec);
212
+ let value = if spec.is_dir() {
213
+ validate_team_dir(&spec)?
214
+ } else {
215
+ validate_spec_file(&spec)?
216
+ };
217
+ Ok(CmdResult::from_json(value, args.json))
218
+ }
219
+
220
+ fn validate_spec_file(spec_path: &Path) -> Result<Value, CliError> {
221
+ let text = std::fs::read_to_string(spec_path)?;
222
+ let base_dir = spec_path.parent().unwrap_or_else(|| Path::new("."));
223
+ let spec = crate::model::spec::load_and_validate_spec(&text, base_dir)
224
+ .map_err(model_error_to_cli)?;
225
+ let team = spec
226
+ .get("team")
227
+ .and_then(|team| team.get("name"))
228
+ .and_then(crate::model::yaml::Value::as_str)
229
+ .unwrap_or("");
230
+ let workspace = spec
231
+ .get("team")
232
+ .and_then(|team| team.get("workspace"))
233
+ .and_then(crate::model::yaml::Value::as_str)
234
+ .map(PathBuf::from)
235
+ .unwrap_or_else(|| base_dir.to_path_buf());
236
+ let mut obj = Map::new();
237
+ obj.insert("ok".to_string(), Value::Bool(true));
238
+ obj.insert("team".to_string(), Value::String(team.to_string()));
239
+ obj.insert(
240
+ "workspace".to_string(),
241
+ Value::String(workspace.to_string_lossy().to_string()),
242
+ );
243
+ Ok(Value::Object(obj))
244
+ }
245
+
246
+ fn validate_team_dir(team_dir: &Path) -> Result<Value, CliError> {
247
+ let spec = crate::compiler::compile_team(team_dir).map_err(model_error_to_cli)?;
248
+ let team = spec
249
+ .get("team")
250
+ .and_then(|team| team.get("name"))
251
+ .and_then(crate::model::yaml::Value::as_str)
252
+ .unwrap_or("");
253
+ let workspace = spec
254
+ .get("team")
255
+ .and_then(|team| team.get("workspace"))
256
+ .and_then(crate::model::yaml::Value::as_str)
257
+ .map(PathBuf::from)
258
+ .unwrap_or_else(|| team_dir.to_path_buf());
259
+ let agents = spec
260
+ .get("agents")
261
+ .and_then(crate::model::yaml::Value::as_list)
262
+ .map(|agents| {
263
+ agents
264
+ .iter()
265
+ .filter_map(|agent| {
266
+ agent
267
+ .get("id")
268
+ .and_then(crate::model::yaml::Value::as_str)
269
+ .map(|id| Value::String(id.to_string()))
270
+ })
271
+ .collect::<Vec<_>>()
272
+ })
273
+ .unwrap_or_default();
274
+ let mut obj = Map::new();
275
+ obj.insert("ok".to_string(), Value::Bool(true));
276
+ obj.insert("type".to_string(), Value::String("team_dir".to_string()));
277
+ obj.insert(
278
+ "workspace".to_string(),
279
+ Value::String(workspace.to_string_lossy().to_string()),
280
+ );
281
+ obj.insert("team".to_string(), Value::String(team.to_string()));
282
+ obj.insert("agents".to_string(), Value::Array(agents));
283
+ Ok(Value::Object(obj))
284
+ }
285
+
286
+ fn resolve_path(path: &Path) -> PathBuf {
287
+ std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
288
+ }
289
+
290
+ fn model_error_to_cli(error: crate::model::errors::ModelError) -> CliError {
291
+ match error {
292
+ crate::model::errors::ModelError::Validation(message) => CliError::Runtime(message),
293
+ other => CliError::Runtime(other.to_string()),
294
+ }
295
+ }
296
+
297
+ fn emit_cli_error(command: &str, args: &[String], cwd: &Path, error: &CliError) -> ExitCode {
298
+ let parsed = parse_args(args);
299
+ let workspace = workspace(&parsed, cwd);
300
+ let log_path = cli_error_log_path(&workspace);
301
+ if let Some(parent) = log_path.parent() {
302
+ let _ = std::fs::create_dir_all(parent);
303
+ }
304
+ let normalized = normalize_cli_error(error);
305
+ let payload_error = normalized.as_ref().unwrap_or(error);
306
+ let _ = std::fs::write(&log_path, format!("{payload_error}\n"));
307
+ let payload = payload_error.to_payload(&log_path, command);
308
+ if has_arg(args, "--json") {
309
+ if let Ok(value) = serde_json::to_value(payload) {
310
+ println!("{}", python_compact_json(&value));
311
+ }
312
+ } else {
313
+ eprintln!("error: {}", payload.error);
314
+ eprintln!("action: {}", payload.action);
315
+ eprintln!("log: {}", payload.log);
316
+ }
317
+ ExitCode::Error
318
+ }
319
+
320
+ fn normalize_cli_error(error: &CliError) -> Option<CliError> {
321
+ match error {
322
+ CliError::Runtime(message) => Some(CliError::Runtime(
323
+ message
324
+ .strip_prefix("validation error: ")
325
+ .unwrap_or(message)
326
+ .to_string(),
327
+ )),
328
+ _ => None,
329
+ }
330
+ }
331
+
332
+ fn cli_error_log_path(workspace: &Path) -> PathBuf {
333
+ let stamp = chrono::Utc::now().format("%Y%m%d-%H%M%S%.6f");
334
+ workspace
335
+ .join(".team")
336
+ .join("logs")
337
+ .join(format!("cli-error-{stamp}.log"))
338
+ }
339
+
340
+ struct PythonCompactFormatter;
341
+
342
+ impl serde_json::ser::Formatter for PythonCompactFormatter {
343
+ fn begin_array_value<W: ?Sized + std::io::Write>(&mut self, w: &mut W, first: bool) -> std::io::Result<()> {
344
+ if first { Ok(()) } else { w.write_all(b", ") }
345
+ }
346
+
347
+ fn begin_object_key<W: ?Sized + std::io::Write>(&mut self, w: &mut W, first: bool) -> std::io::Result<()> {
348
+ if first { Ok(()) } else { w.write_all(b", ") }
349
+ }
350
+
351
+ fn begin_object_value<W: ?Sized + std::io::Write>(&mut self, w: &mut W) -> std::io::Result<()> {
352
+ w.write_all(b": ")
353
+ }
354
+ }
355
+
356
+ fn python_compact_json(value: &Value) -> String {
357
+ let mut bytes = Vec::new();
358
+ let mut ser = serde_json::Serializer::with_formatter(&mut bytes, PythonCompactFormatter);
359
+ if value.serialize(&mut ser).is_err() {
360
+ return "{}".to_string();
361
+ }
362
+ String::from_utf8(bytes).unwrap_or_else(|_| "{}".to_string())
363
+ }
364
+
365
+ fn has_arg(args: &[String], needle: &str) -> bool {
366
+ args.iter().any(|arg| arg == needle)
367
+ }
368
+
369
+ #[derive(Debug, Default)]
370
+ struct ParsedArgs {
371
+ positionals: Vec<String>,
372
+ workspace: Option<PathBuf>,
373
+ team: Option<String>,
374
+ json: bool,
375
+ yes: bool,
376
+ fresh: bool,
377
+ name: Option<String>,
378
+ team_id: Option<String>,
379
+ targets: Option<String>,
380
+ task: Option<String>,
381
+ sender: Option<String>,
382
+ watch_result: bool,
383
+ requires_ack: bool,
384
+ no_ack: bool,
385
+ no_wait: bool,
386
+ timeout: Option<f64>,
387
+ confirm_human: bool,
388
+ detail: bool,
389
+ summary: bool,
390
+ keep_logs: bool,
391
+ allow_fresh: bool,
392
+ force: bool,
393
+ no_display: bool,
394
+ discard_session: bool,
395
+ role_file: Option<String>,
396
+ as_agent: Option<String>,
397
+ label: Option<String>,
398
+ from_spec: bool,
399
+ confirm: bool,
400
+ alert_type: Option<String>,
401
+ limit: Option<usize>,
402
+ since: Option<String>,
403
+ gate: Option<String>,
404
+ comms: bool,
405
+ fix: bool,
406
+ fix_schema: bool,
407
+ cleanup_orphans: bool,
408
+ once: bool,
409
+ tick_interval: Option<f64>,
410
+ status_value: Option<String>,
411
+ providers: Option<String>,
412
+ allow_raw_screen: bool,
413
+ tail: Option<usize>,
414
+ result_file: Option<PathBuf>,
415
+ file: Option<PathBuf>,
416
+ result: Option<String>,
417
+ real: bool,
418
+ assignee: Option<String>,
419
+ out: Option<PathBuf>,
420
+ auth_mode: Option<String>,
421
+ message_id: Option<String>,
422
+ }
423
+
424
+ fn parse_args(args: &[String]) -> ParsedArgs {
425
+ let mut parsed = ParsedArgs::default();
426
+ let mut i = 0usize;
427
+ while i < args.len() {
428
+ let Some(arg) = args.get(i) else {
429
+ break;
430
+ };
431
+ match arg.as_str() {
432
+ "--workspace" => {
433
+ parsed.workspace = next_arg(args, &mut i).map(PathBuf::from);
434
+ }
435
+ "--team" => parsed.team = next_arg(args, &mut i),
436
+ "--json" => parsed.json = true,
437
+ "--yes" => parsed.yes = true,
438
+ "--fresh" => parsed.fresh = true,
439
+ "--name" => parsed.name = next_arg(args, &mut i),
440
+ "--team-id" => parsed.team_id = next_arg(args, &mut i),
441
+ "--targets" | "--target" | "--to" => parsed.targets = next_arg(args, &mut i),
442
+ "--task" => parsed.task = next_arg(args, &mut i),
443
+ "--sender" => parsed.sender = next_arg(args, &mut i),
444
+ "--watch-result" => parsed.watch_result = true,
445
+ "--requires-ack" => parsed.requires_ack = true,
446
+ "--no-ack" => parsed.no_ack = true,
447
+ "--no-wait" => parsed.no_wait = true,
448
+ "--timeout" => parsed.timeout = next_arg(args, &mut i).and_then(|v| v.parse::<f64>().ok()),
449
+ "--confirm-human" => parsed.confirm_human = true,
450
+ "--detail" => parsed.detail = true,
451
+ "--summary" => parsed.summary = true,
452
+ "--keep-logs" => parsed.keep_logs = true,
453
+ "--allow-fresh" => parsed.allow_fresh = true,
454
+ "--force" => parsed.force = true,
455
+ "--no-display" => parsed.no_display = true,
456
+ "--discard-session" => parsed.discard_session = true,
457
+ "--role-file" => parsed.role_file = next_arg(args, &mut i),
458
+ "--as" => parsed.as_agent = next_arg(args, &mut i),
459
+ "--label" => parsed.label = next_arg(args, &mut i),
460
+ "--from-spec" => parsed.from_spec = true,
461
+ "--confirm" => parsed.confirm = true,
462
+ "--alert-type" => parsed.alert_type = next_arg(args, &mut i),
463
+ "--limit" => parsed.limit = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
464
+ "--since" => parsed.since = next_arg(args, &mut i),
465
+ "--gate" => parsed.gate = next_arg(args, &mut i),
466
+ "--comms" => parsed.comms = true,
467
+ "--fix" => parsed.fix = true,
468
+ "--fix-schema" => parsed.fix_schema = true,
469
+ "--cleanup-orphans" => parsed.cleanup_orphans = true,
470
+ "--once" => parsed.once = true,
471
+ "--tick-interval" => parsed.tick_interval = next_arg(args, &mut i).and_then(|v| v.parse::<f64>().ok()),
472
+ "--status" => parsed.status_value = next_arg(args, &mut i),
473
+ "--providers" => parsed.providers = next_arg(args, &mut i),
474
+ "--allow-raw-screen" => parsed.allow_raw_screen = true,
475
+ "--tail" => parsed.tail = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
476
+ "--result-file" => parsed.result_file = next_arg(args, &mut i).map(PathBuf::from),
477
+ "--file" => parsed.file = next_arg(args, &mut i).map(PathBuf::from),
478
+ "--result" => parsed.result = next_arg(args, &mut i),
479
+ "--real" => parsed.real = true,
480
+ "--assignee" => parsed.assignee = next_arg(args, &mut i),
481
+ "--out" => parsed.out = next_arg(args, &mut i).map(PathBuf::from),
482
+ "--auth-mode" => parsed.auth_mode = next_arg(args, &mut i),
483
+ "--message-id" => parsed.message_id = next_arg(args, &mut i),
484
+ "-h" | "--help" => {}
485
+ other if other.starts_with("--team=") => {
486
+ parsed.team = Some(other.trim_start_matches("--team=").to_string());
487
+ }
488
+ other if other.starts_with('-') => {}
489
+ other => parsed.positionals.push(other.to_string()),
490
+ }
491
+ i = i.saturating_add(1);
492
+ }
493
+ parsed
494
+ }
495
+
496
+ fn next_arg(args: &[String], index: &mut usize) -> Option<String> {
497
+ *index = index.saturating_add(1);
498
+ args.get(*index).cloned()
499
+ }
500
+
501
+ fn workspace(parsed: &ParsedArgs, cwd: &Path) -> PathBuf {
502
+ parsed.workspace.clone().unwrap_or_else(|| cwd.to_path_buf())
503
+ }
504
+
505
+ fn required_pos(parsed: &ParsedArgs, index: usize, name: &str) -> Result<String, CliError> {
506
+ parsed
507
+ .positionals
508
+ .get(index)
509
+ .cloned()
510
+ .ok_or_else(|| CliError::Usage(format!("missing {name}")))
511
+ }
512
+
513
+ fn quick_start_args(args: &[String], cwd: &Path) -> Result<QuickStartArgs, CliError> {
514
+ let parsed = parse_args(args);
515
+ let workspace = workspace(&parsed, cwd);
516
+ let agents_dir = parsed
517
+ .positionals
518
+ .first()
519
+ .map(PathBuf::from)
520
+ .unwrap_or_else(|| workspace.clone());
521
+ let agents_dir = if agents_dir.is_absolute() {
522
+ agents_dir
523
+ } else {
524
+ workspace.join(agents_dir)
525
+ };
526
+ Ok(QuickStartArgs {
527
+ agents_dir,
528
+ name: parsed.name,
529
+ team_id: parsed.team_id.or(parsed.team),
530
+ yes: parsed.yes,
531
+ fresh: parsed.fresh,
532
+ json: parsed.json,
533
+ })
534
+ }
535
+
536
+ fn init_args(args: &[String], cwd: &Path) -> InitArgs {
537
+ let parsed = parse_args(args);
538
+ InitArgs {
539
+ workspace: workspace(&parsed, cwd),
540
+ force: parsed.force,
541
+ json: parsed.json,
542
+ }
543
+ }
544
+
545
+ fn compile_args(args: &[String], cwd: &Path) -> Result<CompileArgs, CliError> {
546
+ let parsed = parse_args(args);
547
+ let team = parsed
548
+ .team
549
+ .as_deref()
550
+ .map(PathBuf::from)
551
+ .ok_or_else(|| CliError::Usage("missing --team".to_string()))?;
552
+ let out = parsed.out.unwrap_or_else(|| PathBuf::from("team.spec.yaml"));
553
+ Ok(CompileArgs {
554
+ team: resolve_cli_path(cwd, &team),
555
+ out: resolve_cli_path(cwd, &out),
556
+ json: parsed.json,
557
+ })
558
+ }
559
+
560
+ fn resolve_cli_path(cwd: &Path, path: &Path) -> PathBuf {
561
+ if path.is_absolute() {
562
+ path.to_path_buf()
563
+ } else {
564
+ cwd.join(path)
565
+ }
566
+ }
567
+
568
+ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
569
+ let parsed = parse_args(args);
570
+ let target = if parsed.targets.is_some() {
571
+ None
572
+ } else {
573
+ parsed.positionals.first().cloned()
574
+ };
575
+ let message_start = usize::from(target.is_some());
576
+ let workspace = workspace(&parsed, cwd);
577
+ Ok(SendArgs {
578
+ target,
579
+ message: parsed.positionals.iter().skip(message_start).cloned().collect(),
580
+ targets: parsed.targets,
581
+ workspace,
582
+ team: parsed.team,
583
+ task: parsed.task,
584
+ sender: parsed.sender.unwrap_or_else(|| "leader".to_string()),
585
+ no_ack: parsed.no_ack && !parsed.requires_ack,
586
+ no_wait: parsed.no_wait,
587
+ watch_result: parsed.watch_result,
588
+ timeout: parsed.timeout.unwrap_or(30.0),
589
+ confirm_human: parsed.confirm_human,
590
+ json: parsed.json,
591
+ message_id: parsed.message_id,
592
+ })
593
+ }
594
+
595
+ fn allow_peer_talk_args(args: &[String], cwd: &Path) -> Result<AllowPeerTalkArgs, CliError> {
596
+ let parsed = parse_args(args);
597
+ Ok(AllowPeerTalkArgs {
598
+ a: required_pos(&parsed, 0, "a")?,
599
+ b: required_pos(&parsed, 1, "b")?,
600
+ workspace: workspace(&parsed, cwd),
601
+ json: parsed.json,
602
+ })
603
+ }
604
+
605
+ fn status_args(args: &[String], cwd: &Path) -> StatusArgs {
606
+ let parsed = parse_args(args);
607
+ StatusArgs {
608
+ agent: parsed.positionals.first().cloned(),
609
+ workspace: workspace(&parsed, cwd),
610
+ detail: parsed.detail,
611
+ summary: parsed.summary,
612
+ json: parsed.json,
613
+ }
614
+ }
615
+
616
+ fn watch_args(args: &[String], cwd: &Path) -> WatchArgs {
617
+ let parsed = parse_args(args);
618
+ WatchArgs {
619
+ workspace: workspace(&parsed, cwd),
620
+ team: parsed.team,
621
+ }
622
+ }
623
+
624
+ fn approvals_args(args: &[String], cwd: &Path) -> ApprovalsArgs {
625
+ let parsed = parse_args(args);
626
+ ApprovalsArgs {
627
+ agent: parsed.positionals.first().cloned(),
628
+ workspace: workspace(&parsed, cwd),
629
+ json: parsed.json,
630
+ }
631
+ }
632
+
633
+ fn inbox_args(args: &[String], cwd: &Path) -> Result<InboxArgs, CliError> {
634
+ let parsed = parse_args(args);
635
+ Ok(InboxArgs {
636
+ agent: required_pos(&parsed, 0, "agent")?,
637
+ workspace: workspace(&parsed, cwd),
638
+ limit: parsed.limit.unwrap_or(20),
639
+ since: parsed.since,
640
+ json: parsed.json,
641
+ })
642
+ }
643
+
644
+ fn takeover_args(args: &[String], cwd: &Path) -> TakeoverArgs {
645
+ let parsed = parse_args(args);
646
+ TakeoverArgs {
647
+ workspace: workspace(&parsed, cwd),
648
+ team: parsed.team,
649
+ confirm: parsed.confirm,
650
+ json: parsed.json,
651
+ }
652
+ }
653
+
654
+ fn claim_leader_args(args: &[String], cwd: &Path) -> ClaimLeaderArgs {
655
+ let parsed = parse_args(args);
656
+ ClaimLeaderArgs {
657
+ workspace: workspace(&parsed, cwd),
658
+ team: parsed.team,
659
+ confirm: parsed.confirm,
660
+ json: parsed.json,
661
+ }
662
+ }
663
+
664
+ fn identity_args(args: &[String], cwd: &Path) -> IdentityArgs {
665
+ let parsed = parse_args(args);
666
+ IdentityArgs {
667
+ workspace: workspace(&parsed, cwd),
668
+ team: parsed.team,
669
+ json: parsed.json,
670
+ }
671
+ }
672
+
673
+ fn shutdown_args(args: &[String], cwd: &Path) -> ShutdownArgs {
674
+ let parsed = parse_args(args);
675
+ ShutdownArgs {
676
+ workspace: workspace(&parsed, cwd),
677
+ team: parsed.team,
678
+ keep_logs: parsed.keep_logs,
679
+ json: parsed.json,
680
+ }
681
+ }
682
+
683
+ fn restart_args(args: &[String], cwd: &Path) -> RestartArgs {
684
+ let parsed = parse_args(args);
685
+ RestartArgs {
686
+ workspace: parsed
687
+ .positionals
688
+ .first()
689
+ .map(PathBuf::from)
690
+ .unwrap_or_else(|| workspace(&parsed, cwd)),
691
+ team: parsed.team,
692
+ allow_fresh: parsed.allow_fresh,
693
+ json: parsed.json,
694
+ }
695
+ }
696
+
697
+ fn start_agent_args(args: &[String], cwd: &Path) -> Result<StartAgentArgs, CliError> {
698
+ let parsed = parse_args(args);
699
+ Ok(StartAgentArgs {
700
+ agent: required_pos(&parsed, 0, "agent")?,
701
+ workspace: workspace(&parsed, cwd),
702
+ team: parsed.team,
703
+ force: parsed.force,
704
+ allow_fresh: parsed.allow_fresh,
705
+ no_display: parsed.no_display,
706
+ json: parsed.json,
707
+ })
708
+ }
709
+
710
+ fn stop_agent_args(args: &[String], cwd: &Path) -> Result<StopAgentArgs, CliError> {
711
+ let parsed = parse_args(args);
712
+ Ok(StopAgentArgs {
713
+ agent: required_pos(&parsed, 0, "agent")?,
714
+ workspace: workspace(&parsed, cwd),
715
+ team: parsed.team,
716
+ json: parsed.json,
717
+ })
718
+ }
719
+
720
+ fn reset_agent_args(args: &[String], cwd: &Path) -> Result<ResetAgentArgs, CliError> {
721
+ let parsed = parse_args(args);
722
+ Ok(ResetAgentArgs {
723
+ agent: required_pos(&parsed, 0, "agent")?,
724
+ workspace: workspace(&parsed, cwd),
725
+ team: parsed.team,
726
+ discard_session: parsed.discard_session,
727
+ no_display: parsed.no_display,
728
+ json: parsed.json,
729
+ })
730
+ }
731
+
732
+ fn add_agent_args(args: &[String], cwd: &Path) -> Result<AddAgentArgs, CliError> {
733
+ let parsed = parse_args(args);
734
+ Ok(AddAgentArgs {
735
+ agent: required_pos(&parsed, 0, "agent")?,
736
+ workspace: workspace(&parsed, cwd),
737
+ team: parsed.team,
738
+ role_file: parsed.role_file.ok_or_else(|| CliError::Usage("missing --role-file".to_string()))?,
739
+ no_display: parsed.no_display,
740
+ json: parsed.json,
741
+ })
742
+ }
743
+
744
+ fn fork_agent_args(args: &[String], cwd: &Path) -> Result<ForkAgentArgs, CliError> {
745
+ let parsed = parse_args(args);
746
+ Ok(ForkAgentArgs {
747
+ source_agent: required_pos(&parsed, 0, "source_agent")?,
748
+ workspace: workspace(&parsed, cwd),
749
+ team: parsed.team,
750
+ as_agent: parsed.as_agent.ok_or_else(|| CliError::Usage("missing --as".to_string()))?,
751
+ label: parsed.label,
752
+ no_display: parsed.no_display,
753
+ json: parsed.json,
754
+ })
755
+ }
756
+
757
+ fn remove_agent_args(args: &[String], cwd: &Path) -> Result<RemoveAgentArgs, CliError> {
758
+ let parsed = parse_args(args);
759
+ Ok(RemoveAgentArgs {
760
+ agent: required_pos(&parsed, 0, "agent")?,
761
+ workspace: workspace(&parsed, cwd),
762
+ team: parsed.team,
763
+ from_spec: parsed.from_spec,
764
+ confirm: parsed.confirm,
765
+ force: parsed.force,
766
+ json: parsed.json,
767
+ })
768
+ }
769
+
770
+ fn stuck_list_args(args: &[String], cwd: &Path) -> StuckListArgs {
771
+ let parsed = parse_args(args);
772
+ StuckListArgs {
773
+ workspace: workspace(&parsed, cwd),
774
+ json: parsed.json,
775
+ }
776
+ }
777
+
778
+ fn stuck_cancel_args(args: &[String], cwd: &Path) -> Result<StuckCancelArgs, CliError> {
779
+ let parsed = parse_args(args);
780
+ Ok(StuckCancelArgs {
781
+ agent: required_pos(&parsed, 0, "agent")?,
782
+ workspace: workspace(&parsed, cwd),
783
+ alert_type: alert_type(parsed.alert_type.as_deref())?,
784
+ json: parsed.json,
785
+ })
786
+ }
787
+
788
+ fn alert_type(raw: Option<&str>) -> Result<Option<AlertType>, CliError> {
789
+ match raw {
790
+ Some("stuck") => Ok(Some(AlertType::Stuck)),
791
+ Some("idle_fallback") => Ok(Some(AlertType::IdleFallback)),
792
+ Some("cross_worker_deadlock") => Ok(Some(AlertType::CrossWorkerDeadlock)),
793
+ Some("all") | None => Ok(None),
794
+ Some(other) => Err(CliError::Usage(format!("invalid --alert-type: {other}"))),
795
+ }
796
+ }
797
+
798
+ fn acknowledge_idle_args(args: &[String], cwd: &Path) -> AcknowledgeIdleArgs {
799
+ let parsed = parse_args(args);
800
+ let workspace = workspace(&parsed, cwd);
801
+ AcknowledgeIdleArgs {
802
+ team: parsed.team,
803
+ workspace,
804
+ json: parsed.json,
805
+ }
806
+ }
807
+
808
+ fn doctor_args(args: &[String], cwd: &Path) -> DoctorArgs {
809
+ let parsed = parse_args(args);
810
+ DoctorArgs {
811
+ spec: parsed.positionals.first().map(PathBuf::from),
812
+ workspace: workspace(&parsed, cwd),
813
+ gate: doctor_gate(parsed.gate.as_deref()),
814
+ comms: parsed.comms,
815
+ team: parsed.team,
816
+ fix: parsed.fix,
817
+ fix_schema: parsed.fix_schema,
818
+ cleanup_orphans: parsed.cleanup_orphans,
819
+ confirm: parsed.confirm,
820
+ json: parsed.json,
821
+ }
822
+ }
823
+
824
+ fn doctor_gate(raw: Option<&str>) -> Option<DoctorGate> {
825
+ match raw {
826
+ Some("orphans") => Some(DoctorGate::Orphans),
827
+ Some("comms") => Some(DoctorGate::Comms),
828
+ _ => None,
829
+ }
830
+ }
831
+
832
+ fn sessions_args(args: &[String], cwd: &Path) -> SessionsArgs {
833
+ let parsed = parse_args(args);
834
+ SessionsArgs {
835
+ workspace: workspace(&parsed, cwd),
836
+ json: parsed.json,
837
+ }
838
+ }
839
+
840
+ fn validate_args(args: &[String], cwd: &Path) -> ValidateArgs {
841
+ let parsed = parse_args(args);
842
+ ValidateArgs {
843
+ spec: parsed
844
+ .positionals
845
+ .first()
846
+ .map(PathBuf::from)
847
+ .unwrap_or_else(|| cwd.join("team.spec.yaml")),
848
+ json: parsed.json,
849
+ }
850
+ }
851
+
852
+ fn profile_args(args: &[String], cwd: &Path) -> Result<ProfileArgs, CliError> {
853
+ let parsed = parse_args(args);
854
+ let workspace = resolve_cli_path(cwd, &workspace(&parsed, cwd));
855
+ Ok(ProfileArgs {
856
+ command: required_pos(&parsed, 0, "profile command")?,
857
+ name: required_pos(&parsed, 1, "profile name")?,
858
+ workspace: resolve_path(&workspace),
859
+ team: parsed.team,
860
+ auth_mode: parsed.auth_mode,
861
+ json: parsed.json,
862
+ })
863
+ }
864
+
865
+ fn validate_result_args(args: &[String]) -> Result<ValidateResultArgs, CliError> {
866
+ let parsed = parse_args(args);
867
+ Ok(ValidateResultArgs {
868
+ envelope: parsed.positionals.first().cloned(),
869
+ file: parsed.file,
870
+ result: parsed.result,
871
+ json: parsed.json,
872
+ })
873
+ }
874
+
875
+ fn collect_args(args: &[String], cwd: &Path) -> CollectArgs {
876
+ let parsed = parse_args(args);
877
+ CollectArgs {
878
+ workspace: workspace(&parsed, cwd),
879
+ result_file: parsed.result_file,
880
+ json: parsed.json,
881
+ }
882
+ }
883
+
884
+ fn settle_args(args: &[String], cwd: &Path) -> SettleArgs {
885
+ let parsed = parse_args(args);
886
+ SettleArgs {
887
+ workspace: workspace(&parsed, cwd),
888
+ json: parsed.json,
889
+ }
890
+ }
891
+
892
+ fn repair_state_args(args: &[String], cwd: &Path) -> Result<RepairStateArgs, CliError> {
893
+ let parsed = parse_args(args);
894
+ Ok(RepairStateArgs {
895
+ workspace: workspace(&parsed, cwd),
896
+ task_id: parsed.task.ok_or_else(|| CliError::Usage("missing --task".to_string()))?,
897
+ assignee: parsed.assignee,
898
+ status: parsed
899
+ .status_value
900
+ .ok_or_else(|| CliError::Usage("missing --status".to_string()))?,
901
+ summary: option_value(args, "--summary").or_else(|| parsed.positionals.first().cloned()),
902
+ json: parsed.json,
903
+ })
904
+ }
905
+
906
+ fn option_value(args: &[String], flag: &str) -> Option<String> {
907
+ let prefix = format!("{flag}=");
908
+ let mut i = 0usize;
909
+ while i < args.len() {
910
+ let arg = args.get(i)?;
911
+ if let Some(value) = arg.strip_prefix(&prefix) {
912
+ return Some(value.to_string());
913
+ }
914
+ if arg == flag {
915
+ return args
916
+ .get(i.saturating_add(1))
917
+ .filter(|value| !value.starts_with('-'))
918
+ .cloned();
919
+ }
920
+ i = i.saturating_add(1);
921
+ }
922
+ None
923
+ }
924
+
925
+ fn diagnose_args(args: &[String], cwd: &Path) -> DiagnoseArgs {
926
+ let parsed = parse_args(args);
927
+ DiagnoseArgs {
928
+ workspace: workspace(&parsed, cwd),
929
+ json: parsed.json,
930
+ }
931
+ }
932
+
933
+ fn preflight_args(args: &[String], cwd: &Path) -> PreflightArgs {
934
+ let parsed = parse_args(args);
935
+ let team = parsed
936
+ .team
937
+ .as_deref()
938
+ .map(PathBuf::from)
939
+ .or_else(|| parsed.positionals.first().map(PathBuf::from))
940
+ .unwrap_or_else(|| cwd.to_path_buf());
941
+ PreflightArgs {
942
+ team,
943
+ json: parsed.json,
944
+ }
945
+ }
946
+
947
+ fn wait_ready_args(args: &[String], cwd: &Path) -> WaitReadyArgs {
948
+ let parsed = parse_args(args);
949
+ WaitReadyArgs {
950
+ workspace: workspace(&parsed, cwd),
951
+ timeout: parsed.timeout.unwrap_or(60.0),
952
+ json: parsed.json,
953
+ }
954
+ }
955
+
956
+ fn e2e_args(args: &[String], cwd: &Path) -> E2eArgs {
957
+ let parsed = parse_args(args);
958
+ let providers = parsed
959
+ .providers
960
+ .as_deref()
961
+ .unwrap_or("fake")
962
+ .split(',')
963
+ .filter_map(|p| {
964
+ let trimmed = p.trim();
965
+ if trimmed.is_empty() {
966
+ None
967
+ } else {
968
+ Some(trimmed.to_string())
969
+ }
970
+ })
971
+ .collect();
972
+ E2eArgs {
973
+ workspace: workspace(&parsed, cwd),
974
+ providers,
975
+ real: parsed.real,
976
+ json: parsed.json,
977
+ }
978
+ }
979
+
980
+ fn peek_args(args: &[String], cwd: &Path) -> Result<PeekArgs, CliError> {
981
+ let parsed = parse_args(args);
982
+ Ok(PeekArgs {
983
+ agent: required_pos(&parsed, 0, "agent")?,
984
+ workspace: workspace(&parsed, cwd),
985
+ tail: parsed.tail.unwrap_or(80),
986
+ allow_raw_screen: parsed.allow_raw_screen,
987
+ json: parsed.json,
988
+ })
989
+ }
990
+ fn run_coordinator(args: &[String], cwd: &Path) -> Result<ExitCode, CliError> {
991
+ let parsed = parse_args(args);
992
+ let workspace = crate::coordinator::WorkspacePath::new(workspace(&parsed, cwd));
993
+ crate::coordinator::run_daemon(crate::coordinator::DaemonArgs {
994
+ workspace,
995
+ once: parsed.once,
996
+ tick_interval_sec: parsed.tick_interval,
997
+ })
998
+ .map(|()| ExitCode::Ok)
999
+ .map_err(|e| CliError::Runtime(e.to_string()))
1000
+ }
1001
+
1002
+ fn human_value(value: &Value) -> String {
1003
+ match value {
1004
+ Value::Null => "None".to_string(),
1005
+ Value::Bool(true) => "True".to_string(),
1006
+ Value::Bool(false) => "False".to_string(),
1007
+ Value::Number(n) => n.to_string(),
1008
+ Value::String(s) => s.clone(),
1009
+ Value::Array(_) | Value::Object(_) => json_dumps_like(value),
1010
+ }
1011
+ }
1012
+
1013
+ fn json_dumps_like(value: &Value) -> String {
1014
+ match value {
1015
+ Value::Null => "null".to_string(),
1016
+ Value::Bool(b) => b.to_string(),
1017
+ Value::Number(n) => n.to_string(),
1018
+ Value::String(s) => match serde_json::to_string(s) {
1019
+ Ok(text) => text,
1020
+ Err(_) => "\"\"".to_string(),
1021
+ },
1022
+ Value::Array(arr) => {
1023
+ let inner = arr.iter().map(json_dumps_like).collect::<Vec<_>>().join(", ");
1024
+ format!("[{inner}]")
1025
+ }
1026
+ Value::Object(obj) => {
1027
+ let inner = obj
1028
+ .iter()
1029
+ .map(|(k, v)| format!("{}: {}", json_dumps_like(&Value::String(k.clone())), json_dumps_like(v)))
1030
+ .collect::<Vec<_>>()
1031
+ .join(", ");
1032
+ format!("{{{inner}}}")
1033
+ }
1034
+ }
1035
+ }
1036
+
1037
+ #[cfg(test)]
1038
+ mod tests {
1039
+ #![allow(clippy::unwrap_used)]
1040
+
1041
+ use super::*;
1042
+
1043
+ fn tmp_workspace() -> PathBuf {
1044
+ use std::sync::atomic::{AtomicU64, Ordering};
1045
+ static CTR: AtomicU64 = AtomicU64::new(0);
1046
+ let dir = std::env::temp_dir().join(format!(
1047
+ "ta-cli-emit-test-{}-{}",
1048
+ std::process::id(),
1049
+ CTR.fetch_add(1, Ordering::Relaxed)
1050
+ ));
1051
+ std::fs::create_dir_all(&dir).unwrap();
1052
+ dir
1053
+ }
1054
+
1055
+ fn cli_argv(items: &[&str]) -> Vec<String> {
1056
+ items.iter().map(|s| (*s).to_string()).collect()
1057
+ }
1058
+
1059
+ #[test]
1060
+ fn ux_quick_start_workspace_resolves_relative_agents_dir_inside_workspace() {
1061
+ let cwd = tmp_workspace();
1062
+ let ws = tmp_workspace();
1063
+ let args = quick_start_args(
1064
+ &cli_argv(&["--workspace", &ws.to_string_lossy(), "agents", "--yes", "--json"]),
1065
+ &cwd,
1066
+ )
1067
+ .unwrap();
1068
+ assert_eq!(
1069
+ args.agents_dir,
1070
+ ws.join("agents"),
1071
+ "quick-start --workspace <ws> agents must resolve the role-doc dir under <ws>, so team-in-team \
1072
+ setup works from any caller cwd"
1073
+ );
1074
+ let _ = std::fs::remove_dir_all(&cwd);
1075
+ let _ = std::fs::remove_dir_all(&ws);
1076
+ }
1077
+ }