productfoo 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (498) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +338 -2
  3. package/bin/foo +3 -0
  4. package/bin/foo.mjs +42 -0
  5. package/commands/bug.md +31 -0
  6. package/commands/discovery.md +28 -0
  7. package/commands/ideate.md +27 -0
  8. package/commands/init.md +28 -0
  9. package/commands/pitch.md +27 -0
  10. package/commands/preview.md +25 -0
  11. package/commands/research.md +31 -0
  12. package/commands/roast.md +27 -0
  13. package/commands/spec.md +30 -0
  14. package/config/pricing.yaml +198 -0
  15. package/config/routing-policy.yaml +171 -0
  16. package/dist-public/src/activeModel.d.ts +83 -0
  17. package/dist-public/src/activeModel.js +331 -0
  18. package/dist-public/src/agentAdapters.d.ts +122 -0
  19. package/dist-public/src/agentAdapters.js +178 -0
  20. package/dist-public/src/agentState.d.ts +43 -0
  21. package/dist-public/src/agentState.js +177 -0
  22. package/dist-public/src/bugCapsule.d.ts +94 -0
  23. package/dist-public/src/bugCapsule.js +227 -0
  24. package/dist-public/src/bugCapture.d.ts +26 -0
  25. package/dist-public/src/bugCapture.js +251 -0
  26. package/dist-public/src/bugOwnerLog.d.ts +93 -0
  27. package/dist-public/src/bugOwnerLog.js +304 -0
  28. package/dist-public/src/bugReportStore.d.ts +48 -0
  29. package/dist-public/src/bugReportStore.js +860 -0
  30. package/dist-public/src/bugSpecContext.d.ts +7 -0
  31. package/dist-public/src/bugSpecContext.js +129 -0
  32. package/dist-public/src/channelFreshness.d.ts +25 -0
  33. package/dist-public/src/channelFreshness.js +194 -0
  34. package/dist-public/src/cli.d.ts +30 -0
  35. package/dist-public/src/cli.js +1667 -0
  36. package/dist-public/src/cliArgs.d.ts +7 -0
  37. package/dist-public/src/cliArgs.js +44 -0
  38. package/dist-public/src/codeCatalog.d.ts +41 -0
  39. package/dist-public/src/codeCatalog.js +197 -0
  40. package/dist-public/src/commandContract.d.ts +84 -0
  41. package/dist-public/src/commandContract.js +390 -0
  42. package/dist-public/src/commandUsage.d.ts +3 -0
  43. package/dist-public/src/commandUsage.js +30 -0
  44. package/dist-public/src/commands/agent.d.ts +60 -0
  45. package/dist-public/src/commands/agent.js +1353 -0
  46. package/dist-public/src/commands/asp.d.ts +8 -0
  47. package/dist-public/src/commands/asp.js +669 -0
  48. package/dist-public/src/commands/evalCmd.d.ts +4 -0
  49. package/dist-public/src/commands/evalCmd.js +405 -0
  50. package/dist-public/src/commands/login.d.ts +25 -0
  51. package/dist-public/src/commands/login.js +94 -0
  52. package/dist-public/src/commands/loop.d.ts +16 -0
  53. package/dist-public/src/commands/loop.js +619 -0
  54. package/dist-public/src/commands/reconcile.d.ts +41 -0
  55. package/dist-public/src/commands/reconcile.js +292 -0
  56. package/dist-public/src/commands/runtime.d.ts +1 -0
  57. package/dist-public/src/commands/runtime.js +36 -0
  58. package/dist-public/src/commands/scanRedact.d.ts +8 -0
  59. package/dist-public/src/commands/scanRedact.js +266 -0
  60. package/dist-public/src/commands/setup.d.ts +28 -0
  61. package/dist-public/src/commands/setup.js +398 -0
  62. package/dist-public/src/commands/shared.d.ts +17 -0
  63. package/dist-public/src/commands/shared.js +147 -0
  64. package/dist-public/src/commands/source.d.ts +1 -0
  65. package/dist-public/src/commands/source.js +134 -0
  66. package/dist-public/src/commands/specCmd.d.ts +24 -0
  67. package/dist-public/src/commands/specCmd.js +794 -0
  68. package/dist-public/src/commands/update.d.ts +61 -0
  69. package/dist-public/src/commands/update.js +244 -0
  70. package/dist-public/src/comprehensionSignal.d.ts +20 -0
  71. package/dist-public/src/comprehensionSignal.js +166 -0
  72. package/dist-public/src/conductor/diversity.d.ts +48 -0
  73. package/dist-public/src/conductor/diversity.js +423 -0
  74. package/dist-public/src/conductor/engine.d.ts +7 -0
  75. package/dist-public/src/conductor/engine.js +295 -0
  76. package/dist-public/src/conductor/families/ideate.d.ts +2 -0
  77. package/dist-public/src/conductor/families/ideate.js +255 -0
  78. package/dist-public/src/conductor/families/interview.d.ts +2 -0
  79. package/dist-public/src/conductor/families/interview.js +39 -0
  80. package/dist-public/src/conductor/families/specSwarm.d.ts +2 -0
  81. package/dist-public/src/conductor/families/specSwarm.js +146 -0
  82. package/dist-public/src/conductor/familyDelivery.d.ts +6 -0
  83. package/dist-public/src/conductor/familyDelivery.js +37 -0
  84. package/dist-public/src/conductor/familyPackArtifact.d.ts +4 -0
  85. package/dist-public/src/conductor/familyPackArtifact.js +260 -0
  86. package/dist-public/src/conductor/ideateDecision.d.ts +6 -0
  87. package/dist-public/src/conductor/ideateDecision.js +237 -0
  88. package/dist-public/src/conductor/pack.d.ts +26 -0
  89. package/dist-public/src/conductor/pack.js +75 -0
  90. package/dist-public/src/conductor/types.d.ts +111 -0
  91. package/dist-public/src/conductor/types.js +7 -0
  92. package/dist-public/src/configPath.d.ts +2 -0
  93. package/dist-public/src/configPath.js +7 -0
  94. package/dist-public/src/discoveryArtifacts.d.ts +54 -0
  95. package/dist-public/src/discoveryArtifacts.js +467 -0
  96. package/dist-public/src/discoveryCommand.d.ts +127 -0
  97. package/dist-public/src/discoveryCommand.js +2148 -0
  98. package/dist-public/src/discoveryConnections.d.ts +339 -0
  99. package/dist-public/src/discoveryConnections.js +613 -0
  100. package/dist-public/src/discoveryDocs.d.ts +129 -0
  101. package/dist-public/src/discoveryDocs.js +577 -0
  102. package/dist-public/src/discoveryEvidence.d.ts +59 -0
  103. package/dist-public/src/discoveryEvidence.js +198 -0
  104. package/dist-public/src/discoveryFireflies.d.ts +25 -0
  105. package/dist-public/src/discoveryFireflies.js +551 -0
  106. package/dist-public/src/discoveryGmail.d.ts +12 -0
  107. package/dist-public/src/discoveryGmail.js +466 -0
  108. package/dist-public/src/discoveryGong.d.ts +12 -0
  109. package/dist-public/src/discoveryGong.js +491 -0
  110. package/dist-public/src/discoveryLearningRouting.d.ts +90 -0
  111. package/dist-public/src/discoveryLearningRouting.js +405 -0
  112. package/dist-public/src/discoveryMatching.d.ts +26 -0
  113. package/dist-public/src/discoveryMatching.js +73 -0
  114. package/dist-public/src/discoveryOutlook.d.ts +12 -0
  115. package/dist-public/src/discoveryOutlook.js +492 -0
  116. package/dist-public/src/discoveryProcess.d.ts +221 -0
  117. package/dist-public/src/discoveryProcess.js +703 -0
  118. package/dist-public/src/discoveryProof.d.ts +143 -0
  119. package/dist-public/src/discoveryProof.js +529 -0
  120. package/dist-public/src/discoveryProviders.d.ts +152 -0
  121. package/dist-public/src/discoveryProviders.js +756 -0
  122. package/dist-public/src/discoveryReadiness.d.ts +219 -0
  123. package/dist-public/src/discoveryReadiness.js +672 -0
  124. package/dist-public/src/discoveryReconcile.d.ts +57 -0
  125. package/dist-public/src/discoveryReconcile.js +424 -0
  126. package/dist-public/src/discoverySlack.d.ts +12 -0
  127. package/dist-public/src/discoverySlack.js +452 -0
  128. package/dist-public/src/discoverySources.d.ts +56 -0
  129. package/dist-public/src/discoverySources.js +322 -0
  130. package/dist-public/src/discoverySync.d.ts +52 -0
  131. package/dist-public/src/discoverySync.js +151 -0
  132. package/dist-public/src/discoverySynthesis.d.ts +57 -0
  133. package/dist-public/src/discoverySynthesis.js +215 -0
  134. package/dist-public/src/discoveryTeams.d.ts +12 -0
  135. package/dist-public/src/discoveryTeams.js +516 -0
  136. package/dist-public/src/discoveryZoom.d.ts +12 -0
  137. package/dist-public/src/discoveryZoom.js +523 -0
  138. package/dist-public/src/elicitationSurface.d.ts +13 -0
  139. package/dist-public/src/elicitationSurface.js +77 -0
  140. package/dist-public/src/errorReport.d.ts +99 -0
  141. package/dist-public/src/errorReport.js +443 -0
  142. package/dist-public/src/errorTaxonomy.d.ts +84 -0
  143. package/dist-public/src/errorTaxonomy.js +189 -0
  144. package/dist-public/src/evalCalibration.d.ts +82 -0
  145. package/dist-public/src/evalCalibration.js +169 -0
  146. package/dist-public/src/evalChecks.d.ts +21 -0
  147. package/dist-public/src/evalChecks.js +295 -0
  148. package/dist-public/src/evalCompile.d.ts +56 -0
  149. package/dist-public/src/evalCompile.js +58 -0
  150. package/dist-public/src/evalImprove.d.ts +60 -0
  151. package/dist-public/src/evalImprove.js +175 -0
  152. package/dist-public/src/evalJudge.d.ts +47 -0
  153. package/dist-public/src/evalJudge.js +143 -0
  154. package/dist-public/src/evalLabels.d.ts +100 -0
  155. package/dist-public/src/evalLabels.js +287 -0
  156. package/dist-public/src/evalRecord.d.ts +68 -0
  157. package/dist-public/src/evalRecord.js +165 -0
  158. package/dist-public/src/evalRoast.d.ts +88 -0
  159. package/dist-public/src/evalRoast.js +249 -0
  160. package/dist-public/src/evidenceIntake.d.ts +66 -0
  161. package/dist-public/src/evidenceIntake.js +356 -0
  162. package/dist-public/src/feedback.d.ts +78 -0
  163. package/dist-public/src/feedback.js +294 -0
  164. package/dist-public/src/firefliesClient.d.ts +106 -0
  165. package/dist-public/src/firefliesClient.js +258 -0
  166. package/dist-public/src/firstMoveTensions.d.ts +10 -0
  167. package/dist-public/src/firstMoveTensions.js +93 -0
  168. package/dist-public/src/gitRun.d.ts +21 -0
  169. package/dist-public/src/gitRun.js +50 -0
  170. package/dist-public/src/githubReconcile.d.ts +94 -0
  171. package/dist-public/src/githubReconcile.js +205 -0
  172. package/dist-public/src/gmailNango.d.ts +131 -0
  173. package/dist-public/src/gmailNango.js +329 -0
  174. package/dist-public/src/gongNango.d.ts +118 -0
  175. package/dist-public/src/gongNango.js +308 -0
  176. package/dist-public/src/grammarGate.d.ts +13 -0
  177. package/dist-public/src/grammarGate.js +72 -0
  178. package/dist-public/src/harnessGates.d.ts +65 -0
  179. package/dist-public/src/harnessGates.js +168 -0
  180. package/dist-public/src/humanSurfaceEval.d.ts +19 -0
  181. package/dist-public/src/humanSurfaceEval.js +156 -0
  182. package/dist-public/src/ideate.d.ts +43 -0
  183. package/dist-public/src/ideate.js +255 -0
  184. package/dist-public/src/ideateContext.d.ts +11 -0
  185. package/dist-public/src/ideateContext.js +83 -0
  186. package/dist-public/src/index.d.ts +2 -0
  187. package/dist-public/src/index.js +4 -0
  188. package/dist-public/src/initCommand.d.ts +25 -0
  189. package/dist-public/src/initCommand.js +558 -0
  190. package/dist-public/src/interviewGate.d.ts +25 -0
  191. package/dist-public/src/interviewGate.js +126 -0
  192. package/dist-public/src/jsonFile.d.ts +2 -0
  193. package/dist-public/src/jsonFile.js +22 -0
  194. package/dist-public/src/loopBudget.d.ts +182 -0
  195. package/dist-public/src/loopBudget.js +494 -0
  196. package/dist-public/src/loopContracts.d.ts +35 -0
  197. package/dist-public/src/loopContracts.js +364 -0
  198. package/dist-public/src/machineState.d.ts +1 -0
  199. package/dist-public/src/machineState.js +9 -0
  200. package/dist-public/src/notionNango.d.ts +93 -0
  201. package/dist-public/src/notionNango.js +279 -0
  202. package/dist-public/src/orchestrationResult.d.ts +11 -0
  203. package/dist-public/src/orchestrationResult.js +73 -0
  204. package/dist-public/src/outlookNango.d.ts +124 -0
  205. package/dist-public/src/outlookNango.js +327 -0
  206. package/dist-public/src/packetCorrections.d.ts +40 -0
  207. package/dist-public/src/packetCorrections.js +172 -0
  208. package/dist-public/src/packetGlossary.d.ts +6 -0
  209. package/dist-public/src/packetGlossary.js +42 -0
  210. package/dist-public/src/packetIndex.d.ts +54 -0
  211. package/dist-public/src/packetIndex.js +744 -0
  212. package/dist-public/src/packetIndexEntry.d.ts +3 -0
  213. package/dist-public/src/packetIndexEntry.js +128 -0
  214. package/dist-public/src/packetIndexServe.d.ts +13 -0
  215. package/dist-public/src/packetIndexServe.js +113 -0
  216. package/dist-public/src/packetIndexWorker.d.ts +7 -0
  217. package/dist-public/src/packetIndexWorker.js +45 -0
  218. package/dist-public/src/packetLifecycle.d.ts +30 -0
  219. package/dist-public/src/packetLifecycle.js +127 -0
  220. package/dist-public/src/packetReconcile.d.ts +73 -0
  221. package/dist-public/src/packetReconcile.js +386 -0
  222. package/dist-public/src/packetStatus.d.ts +125 -0
  223. package/dist-public/src/packetStatus.js +770 -0
  224. package/dist-public/src/packetValidation.d.ts +17 -0
  225. package/dist-public/src/packetValidation.js +333 -0
  226. package/dist-public/src/packetVerify.d.ts +73 -0
  227. package/dist-public/src/packetVerify.js +303 -0
  228. package/dist-public/src/packetWrite.d.ts +52 -0
  229. package/dist-public/src/packetWrite.js +448 -0
  230. package/dist-public/src/pitch.d.ts +38 -0
  231. package/dist-public/src/pitch.js +154 -0
  232. package/dist-public/src/processMemo.d.ts +8 -0
  233. package/dist-public/src/processMemo.js +23 -0
  234. package/dist-public/src/productDetect.d.ts +30 -0
  235. package/dist-public/src/productDetect.js +376 -0
  236. package/dist-public/src/profile.d.ts +62 -0
  237. package/dist-public/src/profile.js +334 -0
  238. package/dist-public/src/questionModel.d.ts +46 -0
  239. package/dist-public/src/questionModel.js +74 -0
  240. package/dist-public/src/questionText.d.ts +7 -0
  241. package/dist-public/src/questionText.js +38 -0
  242. package/dist-public/src/redactConfig.d.ts +2 -0
  243. package/dist-public/src/redactConfig.js +11 -0
  244. package/dist-public/src/redactEngine.d.ts +59 -0
  245. package/dist-public/src/redactEngine.js +509 -0
  246. package/dist-public/src/redactGate.d.ts +63 -0
  247. package/dist-public/src/redactGate.js +209 -0
  248. package/dist-public/src/repoRead.d.ts +29 -0
  249. package/dist-public/src/repoRead.js +160 -0
  250. package/dist-public/src/research.d.ts +242 -0
  251. package/dist-public/src/research.js +1314 -0
  252. package/dist-public/src/researchCompletion.d.ts +34 -0
  253. package/dist-public/src/researchCompletion.js +123 -0
  254. package/dist-public/src/researchExecution.d.ts +47 -0
  255. package/dist-public/src/researchExecution.js +309 -0
  256. package/dist-public/src/researchFormat.d.ts +21 -0
  257. package/dist-public/src/researchFormat.js +77 -0
  258. package/dist-public/src/researchGate.d.ts +86 -0
  259. package/dist-public/src/researchGate.js +1036 -0
  260. package/dist-public/src/researchReceipt.d.ts +76 -0
  261. package/dist-public/src/researchReceipt.js +148 -0
  262. package/dist-public/src/researchRender.d.ts +2 -0
  263. package/dist-public/src/researchRender.js +267 -0
  264. package/dist-public/src/researchSigningKey.d.ts +19 -0
  265. package/dist-public/src/researchSigningKey.js +95 -0
  266. package/dist-public/src/riskClassify.d.ts +42 -0
  267. package/dist-public/src/riskClassify.js +149 -0
  268. package/dist-public/src/routines.d.ts +42 -0
  269. package/dist-public/src/routines.js +269 -0
  270. package/dist-public/src/routingPolicy.d.ts +172 -0
  271. package/dist-public/src/routingPolicy.js +646 -0
  272. package/dist-public/src/secretInput.d.ts +8 -0
  273. package/dist-public/src/secretInput.js +89 -0
  274. package/dist-public/src/selfCheck.d.ts +7 -0
  275. package/dist-public/src/selfCheck.js +473 -0
  276. package/dist-public/src/selfCheckCache.d.ts +29 -0
  277. package/dist-public/src/selfCheckCache.js +147 -0
  278. package/dist-public/src/severityClass.d.ts +15 -0
  279. package/dist-public/src/severityClass.js +22 -0
  280. package/dist-public/src/skillClientPolicy.d.ts +8 -0
  281. package/dist-public/src/skillClientPolicy.js +16 -0
  282. package/dist-public/src/skillLocal.d.ts +98 -0
  283. package/dist-public/src/skillLocal.js +463 -0
  284. package/dist-public/src/skillTreeDigest.d.ts +9 -0
  285. package/dist-public/src/skillTreeDigest.js +64 -0
  286. package/dist-public/src/slackNango.d.ts +62 -0
  287. package/dist-public/src/slackNango.js +208 -0
  288. package/dist-public/src/social/arxiv.d.ts +19 -0
  289. package/dist-public/src/social/arxiv.js +96 -0
  290. package/dist-public/src/social/github.d.ts +2 -0
  291. package/dist-public/src/social/github.js +78 -0
  292. package/dist-public/src/social/hackernews.d.ts +2 -0
  293. package/dist-public/src/social/hackernews.js +67 -0
  294. package/dist-public/src/social/index.d.ts +10 -0
  295. package/dist-public/src/social/index.js +79 -0
  296. package/dist-public/src/social/reddit.d.ts +2 -0
  297. package/dist-public/src/social/reddit.js +125 -0
  298. package/dist-public/src/social/types.d.ts +73 -0
  299. package/dist-public/src/social/types.js +61 -0
  300. package/dist-public/src/social/xml.d.ts +10 -0
  301. package/dist-public/src/social/xml.js +49 -0
  302. package/dist-public/src/social/youtube.d.ts +7 -0
  303. package/dist-public/src/social/youtube.js +217 -0
  304. package/dist-public/src/sourceIngress.d.ts +488 -0
  305. package/dist-public/src/sourceIngress.js +1651 -0
  306. package/dist-public/src/specAfter.d.ts +187 -0
  307. package/dist-public/src/specAfter.js +814 -0
  308. package/dist-public/src/specBudget.d.ts +124 -0
  309. package/dist-public/src/specBudget.js +424 -0
  310. package/dist-public/src/specDecisionSignals.d.ts +38 -0
  311. package/dist-public/src/specDecisionSignals.js +121 -0
  312. package/dist-public/src/specLinkedCommits.d.ts +7 -0
  313. package/dist-public/src/specLinkedCommits.js +107 -0
  314. package/dist-public/src/specSections.d.ts +105 -0
  315. package/dist-public/src/specSections.js +718 -0
  316. package/dist-public/src/statusGlyphs.d.ts +18 -0
  317. package/dist-public/src/statusGlyphs.js +75 -0
  318. package/dist-public/src/swarmCommand.d.ts +10 -0
  319. package/dist-public/src/swarmCommand.js +96 -0
  320. package/dist-public/src/teamsNango.d.ts +114 -0
  321. package/dist-public/src/teamsNango.js +301 -0
  322. package/dist-public/src/tokenStore.d.ts +10 -0
  323. package/dist-public/src/tokenStore.js +77 -0
  324. package/dist-public/src/trustEnvelope.d.ts +78 -0
  325. package/dist-public/src/trustEnvelope.js +392 -0
  326. package/dist-public/src/untrustedText.d.ts +6 -0
  327. package/dist-public/src/untrustedText.js +20 -0
  328. package/dist-public/src/updateNotice.d.ts +41 -0
  329. package/dist-public/src/updateNotice.js +290 -0
  330. package/dist-public/src/vendorYaml.d.ts +1 -0
  331. package/dist-public/src/vendorYaml.js +7374 -0
  332. package/dist-public/src/workflowRunActuals.d.ts +67 -0
  333. package/dist-public/src/workflowRunActuals.js +184 -0
  334. package/dist-public/src/zendeskDiscovery.d.ts +12 -0
  335. package/dist-public/src/zendeskDiscovery.js +513 -0
  336. package/dist-public/src/zendeskNango.d.ts +131 -0
  337. package/dist-public/src/zendeskNango.js +344 -0
  338. package/dist-public/src/zoomNango.d.ts +106 -0
  339. package/dist-public/src/zoomNango.js +351 -0
  340. package/docs/CHANGELOG.md +32 -0
  341. package/docs/THIRD_PARTY_NOTICES.md +36 -0
  342. package/package.json +71 -4
  343. package/schemas/bug-capsule-receipt.schema.json +54 -0
  344. package/schemas/bug-capsule.schema.json +189 -0
  345. package/schemas/discovery-evidence-envelope.schema.json +79 -0
  346. package/schemas/discovery-processing-receipt.schema.json +71 -0
  347. package/schemas/discovery-service-contract.schema.json +189 -0
  348. package/skills/AGENTS.md +32 -0
  349. package/skills/README.md +287 -0
  350. package/skills/bug/SKILL.md +49 -0
  351. package/skills/build-preview-spec/SKILL.md +103 -0
  352. package/skills/capture-external-artifact/SKILL.md +23 -0
  353. package/skills/capture-external-artifact/agents/openai.yaml +4 -0
  354. package/skills/capture-external-artifact/references/extractor-libraries.md +27 -0
  355. package/skills/check-spec-health/SKILL.md +45 -0
  356. package/skills/check-spec-health/agents/openai.yaml +4 -0
  357. package/skills/competitive-positioning/SKILL.md +86 -0
  358. package/skills/competitive-positioning/agents/openai.yaml +4 -0
  359. package/skills/cpo-lens/SKILL.md +398 -0
  360. package/skills/cpo-lens/agents/openai.yaml +4 -0
  361. package/skills/create-prd/SKILL.md +26 -0
  362. package/skills/create-prd/agents/openai.yaml +4 -0
  363. package/skills/customer-discovery/SKILL.md +192 -0
  364. package/skills/design-system/SKILL.md +49 -0
  365. package/skills/design-system/agents/openai.yaml +4 -0
  366. package/skills/discussion-guide/SKILL.md +75 -0
  367. package/skills/discussion-guide/agents/openai.yaml +4 -0
  368. package/skills/firecrawl-research-papers/SKILL.md +96 -0
  369. package/skills/firecrawl-research-papers/agents/openai.yaml +4 -0
  370. package/skills/foo-roast/SKILL.md +325 -0
  371. package/skills/generate-gtm-brief/SKILL.md +41 -0
  372. package/skills/generate-gtm-brief/agents/openai.yaml +4 -0
  373. package/skills/generate-gtm-brief/references/gtm-brief-template.md +56 -0
  374. package/skills/gong-quotes/SKILL.md +43 -0
  375. package/skills/gong-quotes/agents/openai.yaml +4 -0
  376. package/skills/grill-me/SKILL.md +27 -0
  377. package/skills/grill-me/agents/openai.yaml +4 -0
  378. package/skills/ideate/SKILL.md +180 -0
  379. package/skills/ideate/references/family-packs.md +5 -0
  380. package/skills/initiative-tracker/SKILL.md +23 -0
  381. package/skills/initiative-tracker/agents/openai.yaml +4 -0
  382. package/skills/interop-review/SKILL.md +25 -0
  383. package/skills/interop-review/agents/openai.yaml +4 -0
  384. package/skills/interview-me/SKILL.md +404 -0
  385. package/skills/interview-me/agents/openai.yaml +4 -0
  386. package/skills/loop-library-audit/SKILL.md +123 -0
  387. package/skills/pitch/SKILL.md +110 -0
  388. package/skills/pitch/references/opportunity-assessment.md +136 -0
  389. package/skills/pitch/references/prfaq-template.md +54 -0
  390. package/skills/pmm-release-briefing/SKILL.md +27 -0
  391. package/skills/pmm-release-briefing/agents/openai.yaml +4 -0
  392. package/skills/pmm-release-briefing/references/variant-generalized.md +16 -0
  393. package/skills/positioning-messaging/SKILL.md +70 -0
  394. package/skills/positioning-messaging/agents/openai.yaml +4 -0
  395. package/skills/positioning-messaging/references/guest-insights.md +1400 -0
  396. package/skills/preview-swarm/SKILL.md +418 -0
  397. package/skills/preview-swarm/agents/openai.yaml +4 -0
  398. package/skills/preview-swarm/references/html-effectiveness-build-previews.md +168 -0
  399. package/skills/product-copywriting/SKILL.md +127 -0
  400. package/skills/product-copywriting/agents/openai.yaml +4 -0
  401. package/skills/product-copywriting/references/foo-voice.md +26 -0
  402. package/skills/product-shape-interview/SKILL.md +219 -0
  403. package/skills/product-shape-interview/agents/openai.yaml +4 -0
  404. package/skills/ready-for-release/SKILL.md +39 -0
  405. package/skills/ready-for-release/agents/openai.yaml +4 -0
  406. package/skills/redact-transcripts/SKILL.md +23 -0
  407. package/skills/redact-transcripts/agents/openai.yaml +4 -0
  408. package/skills/research-brief/SKILL.md +48 -0
  409. package/skills/research-brief/agents/openai.yaml +4 -0
  410. package/skills/research-refresh/SKILL.md +63 -0
  411. package/skills/research-refresh/references/worked-cases.md +44 -0
  412. package/skills/review-loop/SKILL.md +200 -0
  413. package/skills/review-spec/SKILL.md +32 -0
  414. package/skills/review-spec/agents/openai.yaml +4 -0
  415. package/skills/rfc-from-research/SKILL.md +42 -0
  416. package/skills/rfc-from-research/agents/openai.yaml +4 -0
  417. package/skills/rfc-from-research/references/rfc-pattern.md +69 -0
  418. package/skills/skill-registry.yaml +810 -0
  419. package/skills/spec-cleanup/SKILL.md +103 -0
  420. package/skills/spec-cleanup/agents/openai.yaml +4 -0
  421. package/skills/spec-swarm/SKILL.md +87 -0
  422. package/skills/spec-swarm/agents/openai.yaml +4 -0
  423. package/skills/spec-swarm/template/SKILL.template.md +94 -0
  424. package/skills/spec-swarm/template/personas/accessibility-inclusion.md +40 -0
  425. package/skills/spec-swarm/template/personas/ai-technical-pm.md +69 -0
  426. package/skills/spec-swarm/template/personas/analytics-engineering.md +39 -0
  427. package/skills/spec-swarm/template/personas/applied-ai-eval.md +64 -0
  428. package/skills/spec-swarm/template/personas/business-gm-pm.md +67 -0
  429. package/skills/spec-swarm/template/personas/consumer-craft-pm.md +68 -0
  430. package/skills/spec-swarm/template/personas/content-design.md +40 -0
  431. package/skills/spec-swarm/template/personas/cpo.md +42 -0
  432. package/skills/spec-swarm/template/personas/data-science.md +42 -0
  433. package/skills/spec-swarm/template/personas/developer-experience.md +65 -0
  434. package/skills/spec-swarm/template/personas/dx-product.md +70 -0
  435. package/skills/spec-swarm/template/personas/field.md +43 -0
  436. package/skills/spec-swarm/template/personas/finance-cfo.md +40 -0
  437. package/skills/spec-swarm/template/personas/growth-pm.md +67 -0
  438. package/skills/spec-swarm/template/personas/platform-pm.md +67 -0
  439. package/skills/spec-swarm/template/personas/pmm.md +43 -0
  440. package/skills/spec-swarm/template/personas/principal-product-designer.md +42 -0
  441. package/skills/spec-swarm/template/personas/product-ops-program.md +41 -0
  442. package/skills/spec-swarm/template/personas/qa-release.md +41 -0
  443. package/skills/spec-swarm/template/personas/responsible-ai-safety.md +64 -0
  444. package/skills/spec-swarm/template/personas/security-privacy-legal.md +40 -0
  445. package/skills/spec-swarm/template/personas/strategy-competitive.md +41 -0
  446. package/skills/spec-swarm/template/personas/technical-architect.md +42 -0
  447. package/skills/spec-swarm/template/personas/technical-lead.md +43 -0
  448. package/skills/spec-swarm/template/personas/user-research.md +41 -0
  449. package/skills/spec-swarm-accessibility-inclusion/SKILL.md +95 -0
  450. package/skills/spec-swarm-accessibility-inclusion/agents/openai.yaml +4 -0
  451. package/skills/spec-swarm-ai-technical-pm/SKILL.md +124 -0
  452. package/skills/spec-swarm-analytics-engineering/SKILL.md +94 -0
  453. package/skills/spec-swarm-analytics-engineering/agents/openai.yaml +4 -0
  454. package/skills/spec-swarm-applied-ai-eval/SKILL.md +120 -0
  455. package/skills/spec-swarm-business-gm-pm/SKILL.md +122 -0
  456. package/skills/spec-swarm-consumer-craft-pm/SKILL.md +122 -0
  457. package/skills/spec-swarm-content-design/SKILL.md +95 -0
  458. package/skills/spec-swarm-content-design/agents/openai.yaml +4 -0
  459. package/skills/spec-swarm-cpo/SKILL.md +94 -0
  460. package/skills/spec-swarm-cpo/agents/openai.yaml +4 -0
  461. package/skills/spec-swarm-data-science/SKILL.md +95 -0
  462. package/skills/spec-swarm-data-science/agents/openai.yaml +4 -0
  463. package/skills/spec-swarm-developer-experience/SKILL.md +121 -0
  464. package/skills/spec-swarm-dx-product/SKILL.md +126 -0
  465. package/skills/spec-swarm-field/SKILL.md +96 -0
  466. package/skills/spec-swarm-field/agents/openai.yaml +4 -0
  467. package/skills/spec-swarm-finance-cfo/SKILL.md +95 -0
  468. package/skills/spec-swarm-finance-cfo/agents/openai.yaml +4 -0
  469. package/skills/spec-swarm-growth-pm/SKILL.md +122 -0
  470. package/skills/spec-swarm-platform-pm/SKILL.md +122 -0
  471. package/skills/spec-swarm-pmm/SKILL.md +96 -0
  472. package/skills/spec-swarm-pmm/agents/openai.yaml +4 -0
  473. package/skills/spec-swarm-principal-product-designer/SKILL.md +95 -0
  474. package/skills/spec-swarm-principal-product-designer/agents/openai.yaml +4 -0
  475. package/skills/spec-swarm-product-ops-program/SKILL.md +96 -0
  476. package/skills/spec-swarm-product-ops-program/agents/openai.yaml +4 -0
  477. package/skills/spec-swarm-qa-release/SKILL.md +96 -0
  478. package/skills/spec-swarm-qa-release/agents/openai.yaml +4 -0
  479. package/skills/spec-swarm-responsible-ai-safety/SKILL.md +120 -0
  480. package/skills/spec-swarm-security-privacy-legal/SKILL.md +95 -0
  481. package/skills/spec-swarm-security-privacy-legal/agents/openai.yaml +4 -0
  482. package/skills/spec-swarm-strategy-competitive/SKILL.md +96 -0
  483. package/skills/spec-swarm-strategy-competitive/agents/openai.yaml +4 -0
  484. package/skills/spec-swarm-technical-architect/SKILL.md +95 -0
  485. package/skills/spec-swarm-technical-architect/agents/openai.yaml +4 -0
  486. package/skills/spec-swarm-technical-lead/SKILL.md +96 -0
  487. package/skills/spec-swarm-technical-lead/agents/openai.yaml +4 -0
  488. package/skills/spec-swarm-user-research/SKILL.md +95 -0
  489. package/skills/spec-swarm-user-research/agents/openai.yaml +4 -0
  490. package/skills/strategy-check/SKILL.md +170 -0
  491. package/skills/strategy-check/agents/openai.yaml +4 -0
  492. package/skills/study-digest/SKILL.md +41 -0
  493. package/skills/synthetic-customer/SKILL.md +68 -0
  494. package/skills/update-cli-spec/SKILL.md +69 -0
  495. package/skills/update-cli-spec/agents/openai.yaml +4 -0
  496. package/skills/voc-researcher/SKILL.md +45 -0
  497. package/skills/voc-researcher/agents/openai.yaml +4 -0
  498. package/skills-lock.json +35 -0
@@ -0,0 +1,1353 @@
1
+ // The `foo agent` family: adapter status/doctor/setup for harness command
2
+ // surfaces, plus the local experience/usage/rating ledger and its telemetry
3
+ // config. The per-invocation ledger/telemetry primitives used by cli.ts live
4
+ // in ../agentState.js (boot-path: keeps this heavy domain out of the static
5
+ // import graph) and are re-exported here for the stable import surface.
6
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs";
7
+ import { createHash } from "node:crypto";
8
+ import { execFileSync } from "node:child_process";
9
+ import { homedir } from "node:os";
10
+ import path from "node:path";
11
+ import { HARNESS_COMMAND_ADAPTERS, resolveHarnessCommandTarget } from "../agentAdapters.js";
12
+ import { FOO_COMMAND_CONTRACT, FOO_COMMAND_MARKER, renderLauncher, showCommandContract } from "../commandContract.js";
13
+ import { redactText } from "../redactEngine.js";
14
+ import { loadRepoAllowlist } from "../redactConfig.js";
15
+ import { readPackageVersion, ROOT } from "./shared.js";
16
+ import { UsageError } from "../errorTaxonomy.js";
17
+ import { readJson } from "../jsonFile.js";
18
+ import { isSourceCheckout } from "../channelFreshness.js";
19
+ import { agentLogPath, appendLedgerEvent, agentTelemetryConfigPath, generateTesterId, nextAgentEventId, readAgentTelemetryConfig, readTelemetryFile, resolveTesterId, writeAgentTelemetryConfig, writeMachineTesterId, } from "../agentState.js";
20
+ export { agentLogPath, autoWireEligibleDomain, nextAgentEventId, readAgentTelemetryConfig, resolveTesterId, } from "../agentState.js";
21
+ // R-010 product-management ratings + R-012 design/workflow ratings, 1-7 scale.
22
+ const RATING_FIELDS = [
23
+ "spec_clarity",
24
+ "intent_fidelity",
25
+ "product_judgment_lift",
26
+ "build_readiness",
27
+ "output_intent_match",
28
+ "spec_to_output_lift",
29
+ "avoidable_rework",
30
+ "trust_to_delegate_next_time",
31
+ "experience_clarity",
32
+ "trust_explainability",
33
+ ];
34
+ const REROUTE_CHOICES = ["spec", "review", "artifact", "none"];
35
+ // productfoo installs exactly these human-facing commands. Every other skill stays
36
+ // internal package payload that these commands route to (see R-014/R-015).
37
+ // Single-sourced from the command contract (R-001): the adapter surface can
38
+ // never drift from what `foo commands list` declares.
39
+ const FOO_COMMANDS = FOO_COMMAND_CONTRACT.map((c) => c.id);
40
+ // Resolve the commands to install for a given harness, respecting exclusions (e.g., bug for third-party bots).
41
+ function commandsForHarness(adapter) {
42
+ if (!adapter.excludedCommands || adapter.excludedCommands.length === 0)
43
+ return FOO_COMMANDS;
44
+ return FOO_COMMANDS.filter((id) => !adapter.excludedCommands.includes(id));
45
+ }
46
+ const FOO_ROUTING_CONTRACT = {
47
+ schema_version: "0.1",
48
+ applies_when: [
49
+ "bin/foo is available in the repo",
50
+ "the user asks product, spec, packet, planning, decision, correction, validation, or drift work",
51
+ ],
52
+ first_actions: ["foo spec", "foo spec start", "foo asp validate", "foo asp reconcile", "foo asp event", "foo asp task"],
53
+ downstream_companions: ["Compound Engineering", "Superpowers", "generic planning tools"],
54
+ // default-agent-routing-v0 T-004 (R-004): productfoo defers implementation planning
55
+ // to downstream companions, but the handoff must carry the packet contract —
56
+ // never a bare prompt. There is deliberately no public `foo plan` in V0.
57
+ planning_handoff: {
58
+ packet_path: "<packet-dir>",
59
+ include: [
60
+ "relevant requirements (spec.md ## Requirements)",
61
+ "relevant tasks and their done_when checks (tasks.yaml)",
62
+ "the validation bar: foo asp validate <packet-dir> must stay green",
63
+ ],
64
+ note: "productfoo-native planning (`foo plan`) is a possible future slice after adapter-first routing is proven; V0 routes planning to companions with this contract.",
65
+ },
66
+ public_commands_added: [],
67
+ requires_hook: false,
68
+ };
69
+ // Some harnesses namespace commands only when they come from a plugin, so the
70
+ // adapter can install them as a tiny auto-loaded plugin named `foo`.
71
+ const FOO_PLUGIN_NAME = "foo";
72
+ function fooPluginManifest(version = "0.1.0") {
73
+ return `${JSON.stringify({
74
+ name: FOO_PLUGIN_NAME,
75
+ version,
76
+ description: "productfoo product-shaping commands: init (start here), research, discovery, pitch, ideate, spec, roast, preview.",
77
+ author: { name: "productfoo" },
78
+ skills: "./skills/",
79
+ interface: {
80
+ displayName: "productfoo",
81
+ shortDescription: "Product shaping, specs, proof, and drift checks",
82
+ longDescription: "productfoo turns product intent into agreed specs, proof, approval boundaries, and aligned implementation work.",
83
+ developerName: "productfoo",
84
+ category: "Coding",
85
+ capabilities: ["Interactive", "Write"],
86
+ },
87
+ }, null, 2)}\n`;
88
+ }
89
+ function pluginManifestForAgent(agent) {
90
+ if (agent.id === "codex")
91
+ return fooPluginManifest(codexPluginVersion());
92
+ return fooPluginManifest("0.1.0");
93
+ }
94
+ export const AGENT_DEFINITIONS = Object.values(HARNESS_COMMAND_ADAPTERS);
95
+ function parseAgentOptions(args) {
96
+ const options = {
97
+ agent: "all",
98
+ dryRun: false,
99
+ format: "text",
100
+ scope: "project",
101
+ yes: false,
102
+ };
103
+ for (let index = 0; index < args.length; index += 1) {
104
+ const arg = args[index];
105
+ if (arg === "--json") {
106
+ options.format = "json";
107
+ continue;
108
+ }
109
+ if (arg === "--markdown") {
110
+ options.format = "markdown";
111
+ continue;
112
+ }
113
+ if (arg === "--dry-run") {
114
+ options.dryRun = true;
115
+ continue;
116
+ }
117
+ if (arg === "--yes") {
118
+ options.yes = true;
119
+ continue;
120
+ }
121
+ if (arg === "--agent") {
122
+ const value = args[index + 1];
123
+ if (!value)
124
+ throw new UsageError("--agent requires a value", "E_BAD_FLAG");
125
+ options.agent = value;
126
+ index += 1;
127
+ continue;
128
+ }
129
+ if (arg === "--scope") {
130
+ const value = args[index + 1];
131
+ if (value !== "project" && value !== "global")
132
+ throw new UsageError("--scope must be project or global", "E_BAD_FLAG");
133
+ options.scope = value;
134
+ index += 1;
135
+ continue;
136
+ }
137
+ throw new UsageError(`unknown agent argument: ${arg}`, "E_BAD_FLAG");
138
+ }
139
+ return options;
140
+ }
141
+ function parseAgentLogOptions(args) {
142
+ const options = {
143
+ includePrompt: false,
144
+ format: "text",
145
+ };
146
+ for (let index = 0; index < args.length; index += 1) {
147
+ const arg = args[index];
148
+ if (arg === "--json") {
149
+ options.format = "json";
150
+ continue;
151
+ }
152
+ if (arg === "--include-prompt") {
153
+ options.includePrompt = true;
154
+ continue;
155
+ }
156
+ if (["--kind", "--summary", "--agent", "--packet", "--command", "--prompt", "--prompt-summary"].includes(arg)) {
157
+ const value = args[index + 1];
158
+ if (!value)
159
+ throw new UsageError(`${arg} requires a value`, "E_BAD_FLAG");
160
+ if (arg === "--kind") {
161
+ if (value !== "failure" && value !== "poor_experience" && value !== "good_experience") {
162
+ throw new UsageError("--kind must be failure, poor_experience, or good_experience", "E_BAD_FLAG");
163
+ }
164
+ options.kind = value;
165
+ }
166
+ if (arg === "--summary")
167
+ options.summary = value;
168
+ if (arg === "--agent")
169
+ options.agent = value;
170
+ if (arg === "--packet")
171
+ options.packet = value;
172
+ if (arg === "--command")
173
+ options.command = value;
174
+ if (arg === "--prompt")
175
+ options.prompt = value;
176
+ if (arg === "--prompt-summary")
177
+ options.promptSummary = value;
178
+ index += 1;
179
+ continue;
180
+ }
181
+ if (arg === "--prompt-mode") {
182
+ const value = args[index + 1];
183
+ if (value !== "metadata" && value !== "summary" && value !== "raw") {
184
+ throw new UsageError("--prompt-mode must be metadata, summary, or raw", "E_BAD_FLAG");
185
+ }
186
+ options.promptMode = value;
187
+ index += 1;
188
+ continue;
189
+ }
190
+ throw new UsageError(`unknown agent log argument: ${arg}`, "E_BAD_FLAG");
191
+ }
192
+ if (!options.kind)
193
+ throw new UsageError("--kind is required", "E_BAD_FLAG");
194
+ if (!options.summary)
195
+ throw new UsageError("--summary is required", "E_BAD_FLAG");
196
+ return options;
197
+ }
198
+ function parseAgentLogsOptions(args) {
199
+ let format = "text";
200
+ let usage = false;
201
+ let ratings = false;
202
+ for (const arg of args) {
203
+ if (arg === "--json") {
204
+ format = "json";
205
+ continue;
206
+ }
207
+ if (arg === "--usage") {
208
+ usage = true;
209
+ continue;
210
+ }
211
+ if (arg === "--ratings") {
212
+ ratings = true;
213
+ continue;
214
+ }
215
+ throw new UsageError(`unknown agent logs argument: ${arg}`, "E_BAD_FLAG");
216
+ }
217
+ return { format, usage, ratings };
218
+ }
219
+ function parseAgentTelemetryOptions(args) {
220
+ let format = "text";
221
+ for (const arg of args) {
222
+ if (arg === "--json") {
223
+ format = "json";
224
+ continue;
225
+ }
226
+ throw new UsageError(`unknown agent telemetry argument: ${arg}`, "E_BAD_FLAG");
227
+ }
228
+ return { format };
229
+ }
230
+ function selectAgents(agent) {
231
+ if (agent === "all")
232
+ return AGENT_DEFINITIONS;
233
+ const selected = AGENT_DEFINITIONS.find((definition) => definition.id === agent);
234
+ if (!selected)
235
+ throw new UsageError(`unknown agent: ${agent}`, "E_BAD_FLAG");
236
+ return [selected];
237
+ }
238
+ function commandSourcePath(id) {
239
+ return path.join(ROOT, "commands", `${id}.md`);
240
+ }
241
+ function isFooManagedCommand(filePath) {
242
+ if (!existsSync(filePath))
243
+ return false;
244
+ if (!lstatSync(filePath).isFile())
245
+ return false;
246
+ return readFileSync(filePath, "utf8").includes(FOO_COMMAND_MARKER);
247
+ }
248
+ // Adapters install into the runtime the caller is using, never the productfoo package
249
+ // directory: the user's project (cwd) for project scope, home for global scope.
250
+ function targetBaseForScope(scope) {
251
+ const releaseGateRoot = process.env.FOO_RELEASE_GATE_ADAPTER_ROOT?.trim();
252
+ if (scope === "global" && releaseGateRoot) {
253
+ if (process.env.FOO_CODEX_PLUGIN_NO_INSTALL !== "1" || !path.isAbsolute(releaseGateRoot)) {
254
+ throw new UsageError("FOO_RELEASE_GATE_ADAPTER_ROOT requires an absolute path and FOO_CODEX_PLUGIN_NO_INSTALL=1", "E_BAD_FLAG");
255
+ }
256
+ return path.resolve(releaseGateRoot);
257
+ }
258
+ return scope === "global" ? homedir() : process.cwd();
259
+ }
260
+ function agentNamespace(agent) {
261
+ return agent.plugin?.name ?? agent.commandNamespace ?? agent.invocationNamespace ?? null;
262
+ }
263
+ // How the user actually types ONE command. A namespace and a filename prefix are
264
+ // independent, and both are part of what gets typed:
265
+ // Claude ns=foo, no prefix -> /foo:spec
266
+ // Codex plugin=foo, no prefix -> /foo:spec
267
+ // OpenCode no ns, prefix foo- -> /foo-spec
268
+ export function typedCommand(agent, command) {
269
+ const ns = agentNamespace(agent);
270
+ const prefix = agent.commandPrefix ?? "";
271
+ return ns ? `/${ns}:${prefix}${command}` : `/${prefix}${command}`;
272
+ }
273
+ export function agentInvocations(agent) {
274
+ return commandsForHarness(agent).map((command) => typedCommand(agent, command));
275
+ }
276
+ function cliPluginInvocations(agent) {
277
+ if (!agent.plugin)
278
+ return [];
279
+ return commandsForHarness(agent).map((command) => `$${agent.plugin.name}:${command}`);
280
+ }
281
+ function pluginManifestPath(base, agent) {
282
+ return agent.plugin ? path.join(base, agent.plugin.dir, agent.plugin.manifestRelPath) : null;
283
+ }
284
+ // productfoo-managed command files a previous setup wrote into the current install
285
+ // directory whose id has since left FOO_COMMANDS (e.g. a demoted `detect`). We
286
+ // own these — the marker proves it — so setup should prune them; leaving them
287
+ // keeps a retired command in the harness menu forever.
288
+ // The on-disk filename for a command: prefix + id + the harness's extension,
289
+ // e.g. `foo-spec.md` (Cursor) or `spec.md` (Claude Code). Codex plugin skills
290
+ // use <id>/SKILL.md via commandFilePath. One place keeps setup/state aligned.
291
+ function commandFileName(prefix, id, extension) {
292
+ return `${prefix}${id}${extension}`;
293
+ }
294
+ function commandFilePath(dir, agent, target, id) {
295
+ return agent.commandLayout === "skills"
296
+ ? path.join(dir, id, `SKILL${target.extension}`)
297
+ : path.join(dir, commandFileName(target.prefix, id, target.extension));
298
+ }
299
+ function renderPluginSkill(id) {
300
+ return renderLauncher(showCommandContract(id)).replace(/^---\n/, `---\nname: ${id}\n`);
301
+ }
302
+ // The exact bytes setup would write for this command today. An installed shim
303
+ // that differs is outdated: it still carries the managed marker, so presence
304
+ // checks pass, but its instructions describe a contract the CLI no longer
305
+ // honors (a repurposed command's old shim advertised a front door that had
306
+ // been removed — issue #520).
307
+ function expectedCommandContent(agent, id) {
308
+ // grok-bot-foo-first-override-v0: Grok launchers include the override flag.
309
+ return agent.commandLayout === "skills"
310
+ ? renderPluginSkill(id)
311
+ : renderLauncher(showCommandContract(id), { grokFooFirst: agent.id === "grok" });
312
+ }
313
+ function codexPluginVersion() {
314
+ const digest = createHash("sha256")
315
+ .update(FOO_COMMANDS.map((id) => renderPluginSkill(id)).join("\n"))
316
+ .digest("hex")
317
+ .slice(0, 12);
318
+ return `${readPackageVersion(ROOT)}+codex.${digest}`;
319
+ }
320
+ function ensurePersonalMarketplace(base, agent) {
321
+ if (!agent.marketplace || !agent.plugin)
322
+ return null;
323
+ const marketplacePath = path.join(base, agent.marketplace.manifestRelPath);
324
+ let marketplace = {};
325
+ if (existsSync(marketplacePath))
326
+ marketplace = readJson(marketplacePath);
327
+ if (marketplace.name && marketplace.name !== agent.marketplace.name) {
328
+ throw new Error(`refusing to rewrite marketplace ${marketplacePath}: expected ${agent.marketplace.name}, found ${marketplace.name}`);
329
+ }
330
+ marketplace.name = agent.marketplace.name;
331
+ marketplace.interface ??= { displayName: "Personal" };
332
+ marketplace.interface.displayName ??= "Personal";
333
+ marketplace.plugins ??= [];
334
+ const expectedPath = `./${agent.plugin.dir}`;
335
+ const existing = marketplace.plugins.find((entry) => entry.name === agent.plugin.name);
336
+ if (existing) {
337
+ const source = existing.source;
338
+ if (source?.source !== "local" || source.path !== expectedPath) {
339
+ throw new Error(`refusing to replace non-productfoo marketplace entry: ${agent.plugin.name}`);
340
+ }
341
+ }
342
+ else {
343
+ marketplace.plugins.push({
344
+ name: agent.plugin.name,
345
+ source: { source: "local", path: expectedPath },
346
+ policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" },
347
+ category: "Productivity",
348
+ });
349
+ }
350
+ mkdirSync(path.dirname(marketplacePath), { recursive: true });
351
+ writeFileSync(marketplacePath, `${JSON.stringify(marketplace, null, 2)}\n`);
352
+ return marketplace.name;
353
+ }
354
+ function detectStaleCommands(base, agent, scope) {
355
+ const target = resolveHarnessCommandTarget(agent, scope);
356
+ if (!target.supported)
357
+ return [];
358
+ const dir = path.join(base, target.dir);
359
+ if (!existsSync(dir) || !lstatSync(dir).isDirectory())
360
+ return [];
361
+ const stale = [];
362
+ for (const entry of readdirSync(dir)) {
363
+ if (!entry.endsWith(target.extension))
364
+ continue;
365
+ let id = entry.slice(0, -target.extension.length);
366
+ if (target.prefix && id.startsWith(target.prefix))
367
+ id = id.slice(target.prefix.length);
368
+ if (FOO_COMMANDS.includes(id))
369
+ continue;
370
+ if (isFooManagedCommand(path.join(dir, entry)))
371
+ stale.push(id);
372
+ }
373
+ return stale.sort();
374
+ }
375
+ // Paths from earlier productfoo adapter versions that this setup should clean up:
376
+ // skill symlink adapters, and flat command directories holding productfoo-managed files.
377
+ function detectLegacyPaths(base, agent) {
378
+ const legacy = [];
379
+ for (const symlinkRel of agent.legacyPaths) {
380
+ const symlinkFull = path.join(base, symlinkRel);
381
+ if (existsSync(symlinkFull) && lstatSync(symlinkFull).isSymbolicLink())
382
+ legacy.push(symlinkRel);
383
+ }
384
+ if (agent.legacyCommandsDir) {
385
+ const flatDir = path.join(base, agent.legacyCommandsDir);
386
+ const prefix = agent.legacyCommandPrefix ?? "";
387
+ const hasFooFiles = FOO_COMMANDS.some((id) => isFooManagedCommand(path.join(flatDir, `${prefix}${id}.md`)));
388
+ if (hasFooFiles)
389
+ legacy.push(agent.legacyCommandsDir);
390
+ }
391
+ // A retired plugin install: productfoo's own manifest still on disk means the old
392
+ // (never-auto-loaded) plugin wrapper needs migrating to the commands dir.
393
+ if (agent.legacyPluginDir) {
394
+ const manifest = path.join(base, agent.legacyPluginDir, ".claude-plugin", "plugin.json");
395
+ if (isFooPluginManifest(manifest))
396
+ legacy.push(agent.legacyPluginDir);
397
+ }
398
+ return legacy;
399
+ }
400
+ export function agentCommandState(base, agent, scope) {
401
+ const target = resolveHarnessCommandTarget(agent, scope);
402
+ // Planned harness (unverified dir / un-emittable format) or global-only harness
403
+ // at project scope: report `unsupported`, never a silent success (D-002).
404
+ if (!target.supported) {
405
+ return { installed: [], conflicts: [], legacy: [], stale: [], outdated: [], manifestReady: true, status: "unsupported", reason: target.reason };
406
+ }
407
+ const harnessCommands = commandsForHarness(agent);
408
+ const dir = path.join(base, target.dir);
409
+ const installed = [];
410
+ const conflicts = [];
411
+ const outdated = [];
412
+ for (const id of harnessCommands) {
413
+ const filePath = commandFilePath(dir, agent, target, id);
414
+ if (!existsSync(filePath))
415
+ continue;
416
+ if (isFooManagedCommand(filePath)) {
417
+ installed.push(id);
418
+ // Presence is not currency: a managed shim whose content no longer
419
+ // matches what setup would write today instructs the agent to run a
420
+ // contract the CLI may have dropped. Content drift is drift.
421
+ if (readFileSync(filePath, "utf8") !== expectedCommandContent(agent, id))
422
+ outdated.push(id);
423
+ }
424
+ else
425
+ conflicts.push(id);
426
+ }
427
+ const legacy = detectLegacyPaths(base, agent);
428
+ const stale = detectStaleCommands(base, agent, scope);
429
+ const manifest = pluginManifestPath(base, agent);
430
+ const manifestReady = manifest ? existsSync(manifest) : true;
431
+ const status = conflicts.length > 0 || legacy.length > 0 || stale.length > 0 || outdated.length > 0
432
+ ? "drift"
433
+ : installed.length === harnessCommands.length && manifestReady
434
+ ? "ready"
435
+ : "missing";
436
+ return { installed, conflicts, legacy, stale, outdated, manifestReady, status };
437
+ }
438
+ export function codexGlobalAdapterStatus(home = homedir()) {
439
+ const agent = AGENT_DEFINITIONS.find((definition) => definition.id === "codex");
440
+ if (!agent)
441
+ return { status: "unsupported", command_path: "unsupported: Codex adapter missing" };
442
+ const target = resolveHarnessCommandTarget(agent, "global");
443
+ if (!target.supported)
444
+ return { status: "unsupported", command_path: `unsupported: ${target.reason}` };
445
+ return {
446
+ status: agentCommandState(home, agent, "global").status,
447
+ command_path: path.join(home, target.dir),
448
+ };
449
+ }
450
+ function renderAgentStatus(options) {
451
+ const base = targetBaseForScope(options.scope);
452
+ const agents = selectAgents(options.agent).map((agent) => {
453
+ const state = agentCommandState(base, agent, options.scope);
454
+ const target = resolveHarnessCommandTarget(agent, options.scope);
455
+ const surfaces = agent.plugin ? { app: agentInvocations(agent), cli: cliPluginInvocations(agent) } : undefined;
456
+ return {
457
+ id: agent.id,
458
+ label: agent.label,
459
+ command_path: target.supported ? target.dir : `unsupported: ${state.reason ?? "no command surface for this scope"}`,
460
+ namespace: agentNamespace(agent),
461
+ invocations: agentInvocations(agent),
462
+ status: state.status,
463
+ installed: state.installed,
464
+ conflicts: state.conflicts,
465
+ legacy: state.legacy,
466
+ stale: state.stale,
467
+ outdated: state.outdated,
468
+ ...(surfaces ? { surfaces } : {}),
469
+ };
470
+ });
471
+ const payload = {
472
+ schema_version: "0.1",
473
+ scope: options.scope,
474
+ base,
475
+ canonical_commands: [...FOO_COMMANDS],
476
+ agents,
477
+ next_actions: agents.some((agent) => agent.status !== "ready")
478
+ ? [`foo agent setup --agent all --scope ${options.scope} --dry-run`]
479
+ : ["foo agent doctor"],
480
+ };
481
+ if (options.format === "json")
482
+ return `${JSON.stringify(payload, null, 2)}\n`;
483
+ const describe = (agent) => {
484
+ const extra = agent.status === "drift"
485
+ ? agent.legacy.length > 0
486
+ ? ` (drift: legacy ${agent.legacy.join(", ")})`
487
+ : agent.stale.length > 0
488
+ ? ` (drift: retired ${agent.stale.join(", ")})`
489
+ : agent.outdated.length > 0
490
+ ? ` (drift: outdated ${agent.outdated.join(", ")} — re-run setup to refresh)`
491
+ : ` (drift: conflicting ${agent.conflicts.join(", ")})`
492
+ : agent.status === "ready"
493
+ ? ` as ${agent.invocations.join(", ")}`
494
+ : "";
495
+ return `${agent.label}: ${agent.status} (${agent.command_path})${extra}`;
496
+ };
497
+ if (options.format === "markdown") {
498
+ return [
499
+ "# productfoo Agent Status",
500
+ "",
501
+ `Canonical commands: ${FOO_COMMANDS.join(", ")}`,
502
+ "",
503
+ ...agents.map((agent) => `- ${describe(agent)}`),
504
+ "",
505
+ ].join("\n");
506
+ }
507
+ return [
508
+ "foo agent status",
509
+ "",
510
+ `Canonical commands: ${FOO_COMMANDS.join(", ")}`,
511
+ ...agents.map(describe),
512
+ "",
513
+ ].join("\n");
514
+ }
515
+ function renderAgentDoctor() {
516
+ const base = targetBaseForScope("project");
517
+ const agents = selectAgents("all").map((agent) => ({ agent, state: agentCommandState(base, agent, "project") }));
518
+ return [
519
+ "foo agent doctor",
520
+ "",
521
+ `Canonical commands: ${FOO_COMMANDS.join(", ")}`,
522
+ `productfoo installs only these ${FOO_COMMANDS.length} commands; all other skills stay internal package payload.`,
523
+ "",
524
+ ...agents.map(({ agent, state }) => {
525
+ const note = state.legacy.length > 0
526
+ ? ` (legacy ${state.legacy.join(", ")} — re-run setup to repair)`
527
+ : state.stale.length > 0
528
+ ? ` (retired ${state.stale.join(", ")} — re-run setup to prune)`
529
+ : state.outdated.length > 0
530
+ ? ` (outdated ${state.outdated.join(", ")} — re-run setup to refresh)`
531
+ : state.status === "ready"
532
+ ? ` as ${agentInvocations(agent).join(", ")}`
533
+ : "";
534
+ return `${agent.label}: ${state.status} (${agent.commandsDir})${note}`;
535
+ }),
536
+ "",
537
+ "Next: foo agent setup --agent all --scope project --dry-run",
538
+ "",
539
+ ].join("\n");
540
+ }
541
+ function agentSetupChanges(base, agents, scope) {
542
+ return agents.map((agent) => {
543
+ const state = agentCommandState(base, agent, scope);
544
+ const target = resolveHarnessCommandTarget(agent, scope);
545
+ const harnessCommands = commandsForHarness(agent);
546
+ if (!target.supported) {
547
+ return {
548
+ action: "unsupported",
549
+ agent: agent.id,
550
+ command_path: `unsupported: ${state.reason ?? "no command surface for this scope"}`,
551
+ namespace: agentNamespace(agent),
552
+ commands: [...harnessCommands],
553
+ install: [],
554
+ install_manifest: false,
555
+ conflicts: [],
556
+ legacy: [],
557
+ stale: [],
558
+ outdated: [],
559
+ unsupported_reason: state.reason,
560
+ };
561
+ }
562
+ // An outdated shim re-installs like a missing one: setup rewrites every
563
+ // command file from the current contract, so refresh needs no extra step —
564
+ // it just must not be reported as "verify-existing" when content drifted.
565
+ const install = harnessCommands.filter((id) => (!state.installed.includes(id) || state.outdated.includes(id)) && !state.conflicts.includes(id));
566
+ const installManifest = Boolean(agent.plugin) && !state.manifestReady;
567
+ const action = state.conflicts.length > 0
568
+ ? "conflict"
569
+ : state.legacy.length > 0 || state.stale.length > 0
570
+ ? "repair-legacy"
571
+ : install.length > 0 || installManifest
572
+ ? "install-commands"
573
+ : "verify-existing";
574
+ return {
575
+ action,
576
+ agent: agent.id,
577
+ command_path: target.dir,
578
+ namespace: agentNamespace(agent),
579
+ commands: [...harnessCommands],
580
+ install: [...install],
581
+ install_manifest: installManifest,
582
+ conflicts: state.conflicts,
583
+ legacy: state.legacy,
584
+ stale: state.stale,
585
+ outdated: state.outdated,
586
+ ...(agent.plugin ? { surfaces: { app: agentInvocations(agent), cli: cliPluginInvocations(agent) } } : {}),
587
+ };
588
+ });
589
+ }
590
+ // True only for a plugin.json productfoo itself wrote (never a user's own plugin).
591
+ function isFooPluginManifest(manifestPath) {
592
+ if (!existsSync(manifestPath) || !lstatSync(manifestPath).isFile())
593
+ return false;
594
+ try {
595
+ return readJson(manifestPath).name === FOO_PLUGIN_NAME;
596
+ }
597
+ catch {
598
+ return false;
599
+ }
600
+ }
601
+ function removeDirIfEmpty(dir) {
602
+ if (existsSync(dir) && lstatSync(dir).isDirectory() && readdirSync(dir).length === 0)
603
+ rmdirSync(dir);
604
+ }
605
+ // Remove a legacy path productfoo created earlier: a skills symlink, the productfoo-managed
606
+ // command files inside an old flat command directory (leaving the user's own),
607
+ // or a retired plugin install (productfoo's manifest + its command files + the dirs
608
+ // they leave empty — anything the user added in there survives).
609
+ function removeLegacyPath(base, rel, prefix = "") {
610
+ const full = path.join(base, rel);
611
+ if (!existsSync(full))
612
+ return;
613
+ if (lstatSync(full).isSymbolicLink()) {
614
+ unlinkSync(full);
615
+ return;
616
+ }
617
+ for (const subdir of [full, path.join(full, "commands")]) {
618
+ for (const id of FOO_COMMANDS) {
619
+ const file = path.join(subdir, `${prefix}${id}.md`);
620
+ if (isFooManagedCommand(file))
621
+ unlinkSync(file);
622
+ }
623
+ }
624
+ const manifest = path.join(full, ".claude-plugin", "plugin.json");
625
+ if (isFooPluginManifest(manifest))
626
+ unlinkSync(manifest);
627
+ for (const dir of [path.join(full, ".claude-plugin"), path.join(full, "commands"), full])
628
+ removeDirIfEmpty(dir);
629
+ }
630
+ // Broken-install check shared by dry-run and apply: a payload without its
631
+ // commands/ (the 0.1.0 foo-dist regression) must fail the DRY RUN too — a
632
+ // dry-run that passes while --yes aborts is a lie about readiness.
633
+ function assertCanonicalCommandSources() {
634
+ for (const id of FOO_COMMANDS) {
635
+ if (!existsSync(commandSourcePath(id))) {
636
+ throw new Error(`canonical command source missing: commands/${id}.md — this foo install is missing its commands/ payload; reinstall foo`);
637
+ }
638
+ }
639
+ }
640
+ function applyAgentSetup(base, agents, scope) {
641
+ assertCanonicalCommandSources();
642
+ if (process.env.FOO_CODEX_PLUGIN_NO_INSTALL !== "1"
643
+ && agents.some((agent) => agent.id === "codex"
644
+ && agent.marketplace
645
+ && resolveHarnessCommandTarget(agent, scope).supported)) {
646
+ try {
647
+ execFileSync("codex", ["--version"], { stdio: "pipe" });
648
+ }
649
+ catch (error) {
650
+ if (error.code === "ENOENT") {
651
+ throw new UsageError("Codex CLI is not installed or not on PATH; install Codex, then rerun `foo agent setup --agent codex --scope global --yes`");
652
+ }
653
+ throw error;
654
+ }
655
+ }
656
+ // Refuse the whole apply if any target name is owned by a non-productfoo file, so a
657
+ // setup run never half-overwrites a user's own commands.
658
+ const blocked = agents
659
+ .map((agent) => ({ agent, state: agentCommandState(base, agent, scope) }))
660
+ .filter(({ state }) => state.conflicts.length > 0);
661
+ if (blocked.length > 0) {
662
+ const detail = blocked
663
+ .map(({ agent, state }) => `${agent.id} (${state.conflicts.join(", ")})`)
664
+ .join("; ");
665
+ throw new Error(`refusing to overwrite non-productfoo command files: ${detail}`);
666
+ }
667
+ for (const agent of agents) {
668
+ const target = resolveHarnessCommandTarget(agent, scope);
669
+ // Planned harness or global-only harness at the wrong scope: write nothing.
670
+ // Honest no-op — never a broken markdown/TOML binding (D-002/D-004).
671
+ if (!target.supported)
672
+ continue;
673
+ const state = agentCommandState(base, agent, scope);
674
+ for (const rel of state.legacy) {
675
+ removeLegacyPath(base, rel, rel === agent.legacyCommandsDir ? agent.legacyCommandPrefix : "");
676
+ }
677
+ // Prune productfoo-managed command files whose id has left FOO_COMMANDS, so a
678
+ // demoted command does not linger in the harness menu after re-setup.
679
+ const dir = path.join(base, target.dir);
680
+ for (const id of state.stale) {
681
+ const file = commandFilePath(dir, agent, target, id);
682
+ if (isFooManagedCommand(file))
683
+ unlinkSync(file);
684
+ }
685
+ const manifest = pluginManifestPath(base, agent);
686
+ if (manifest) {
687
+ mkdirSync(path.dirname(manifest), { recursive: true });
688
+ writeFileSync(manifest, pluginManifestForAgent(agent));
689
+ }
690
+ mkdirSync(dir, { recursive: true });
691
+ const harnessCommands = commandsForHarness(agent);
692
+ for (const id of harnessCommands) {
693
+ // R-004: launchers are disposable artifacts generated from the CLI
694
+ // command contract — rerunning setup recreates them; a metadata change
695
+ // flows into every adapter surface from one source.
696
+ const file = commandFilePath(dir, agent, target, id);
697
+ mkdirSync(path.dirname(file), { recursive: true });
698
+ // grok-bot-foo-first-override-v0: Grok launchers get the productfoo-first override
699
+ // block for spec/ideate/pitch/preview commands to beat generic coordinator
700
+ // instructions (D-001, R-001).
701
+ const content = agent.commandLayout === "skills"
702
+ ? renderPluginSkill(id)
703
+ : renderLauncher(showCommandContract(id), { grokFooFirst: agent.id === "grok" });
704
+ writeFileSync(file, content);
705
+ }
706
+ const marketplaceName = ensurePersonalMarketplace(base, agent);
707
+ if (agent.id === "codex" && marketplaceName && process.env.FOO_CODEX_PLUGIN_NO_INSTALL !== "1") {
708
+ try {
709
+ execFileSync("codex", ["plugin", "add", `${agent.plugin.name}@${marketplaceName}`], { stdio: "pipe" });
710
+ }
711
+ catch (error) {
712
+ if (error.code === "ENOENT") {
713
+ throw new UsageError("Codex CLI disappeared during setup; restore it on PATH, then rerun `foo agent setup --agent codex --scope global --yes`");
714
+ }
715
+ throw error;
716
+ }
717
+ }
718
+ }
719
+ }
720
+ // install-once-wire-all, ask-first (#913, #622): an installed client offers to
721
+ // wire its harness command launchers once, names the agents it will touch, and
722
+ // remembers the answer in ~/.foo/harness-wired.json:
723
+ // - the question defaults to yes (owner decision, #913): Enter sets up the
724
+ // named agents; only an explicit n/no declines.
725
+ // - consent "yes": the agents are wired, and after an upgrade (stamped version
726
+ // differs) those same agents are refreshed without asking again — the
727
+ // person already said yes to exactly them.
728
+ // - consent "no": never asked again; `foo agent setup` is always there.
729
+ // - no stamp and nobody at a terminal (an agent-invoked or piped run): nothing
730
+ // is written and nothing is stamped, so the question waits for a person.
731
+ // Which agents: the profile's harnesses (the first-run survey answer — asked,
732
+ // so it must drive something), else every supported agent. Codex is set up
733
+ // through its own CLI (`foo agent setup --agent codex`), never silently.
734
+ // Guarantees that keep it safe on the hot path:
735
+ // - installs only: a source checkout manages its own harnesses (isSourceCheckout,
736
+ // the same test `foo update` uses — bundled skills no longer read as a checkout).
737
+ // - opt-out: FOO_NO_AUTOWIRE=1.
738
+ // - idempotent + conflict-safe: reuses applyAgentSetup, which only touches
739
+ // productfoo-managed files and refuses to overwrite a user's own command.
740
+ // - best-effort: the caller wraps this in try/catch so it can NEVER break a command.
741
+ const HARNESS_STAMP_REL = path.join(".foo", "harness-wired.json");
742
+ export function harnessStampPath(home) {
743
+ return path.join(home, HARNESS_STAMP_REL);
744
+ }
745
+ /** The agents an offer would touch: the preferred ones productfoo can wire globally,
746
+ * else every supported agent (Codex excluded — it is set up through its CLI). */
747
+ export function autoWireAgents(preferred = []) {
748
+ const eligible = selectAgents("all").filter((agent) => agent.id !== "codex" && resolveHarnessCommandTarget(agent, "global").supported);
749
+ const chosen = preferred.length ? eligible.filter((agent) => preferred.includes(agent.id)) : eligible;
750
+ return chosen;
751
+ }
752
+ export async function maybeAutoWireHarnesses(version, opts = {}) {
753
+ const env = opts.env ?? process.env;
754
+ if ((env.FOO_NO_AUTOWIRE ?? "").trim() === "1")
755
+ return null;
756
+ if (isSourceCheckout(opts.packageRoot ?? ROOT))
757
+ return null;
758
+ const home = opts.home ?? targetBaseForScope("global");
759
+ const stampPath = harnessStampPath(home);
760
+ let stamp = null;
761
+ try {
762
+ stamp = readJson(stampPath);
763
+ }
764
+ catch {
765
+ stamp = null; // no stamp yet (fresh install)
766
+ }
767
+ const writeStamp = (next) => {
768
+ mkdirSync(path.dirname(stampPath), { recursive: true });
769
+ writeFileSync(stampPath, `${JSON.stringify({ ...next, at: new Date().toISOString() }, null, 2)}\n`);
770
+ };
771
+ if (stamp?.consent === "no")
772
+ return null; // they said no; foo agent setup is theirs to run
773
+ if (stamp && stamp.version === version)
774
+ return null; // already wired for this exact version — fast path
775
+ if (stamp) {
776
+ // Upgrade: refresh exactly what was consented to. A stamp written before
777
+ // ask-first existed (no consent field) came from an alpha install that was
778
+ // wired then; keep refreshing its agents rather than asking a returning tester.
779
+ const agents = stamp.agents?.length
780
+ ? selectAgents("all").filter((agent) => stamp.agents.includes(agent.id))
781
+ : autoWireAgents();
782
+ applyAgentSetup(home, agents, "global"); // may throw on a user conflict — caller catches
783
+ const wired = agents.map((agent) => agent.id);
784
+ writeStamp({ version, agents: wired, consent: "yes" });
785
+ return { reason: "upgrade", wired };
786
+ }
787
+ // First run: ask, or wait for someone who can answer.
788
+ if (!opts.ask)
789
+ return null;
790
+ const agents = autoWireAgents(opts.preferred ?? []);
791
+ if (agents.length === 0)
792
+ return null;
793
+ const names = agents.map((agent) => agent.label);
794
+ const yes = await opts.ask(`Set up productfoo's commands in ${names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names.at(-1)}` : names[0]}? [Y/n] `);
795
+ if (!yes) {
796
+ writeStamp({ version, agents: [], consent: "no" });
797
+ return { reason: "declined", wired: [], offered: agents.map((agent) => agent.id) };
798
+ }
799
+ applyAgentSetup(home, agents, "global");
800
+ const wired = agents.map((agent) => agent.id);
801
+ writeStamp({ version, agents: wired, consent: "yes" });
802
+ return { reason: "first-run", wired };
803
+ }
804
+ /** The one line each auto-wire outcome prints (#913). */
805
+ export function autoWireMessage(outcome) {
806
+ if (outcome.reason === "declined") {
807
+ return "Okay — foo won't set up your coding agents. Any time: foo agent setup --agent all --scope global --yes\n";
808
+ }
809
+ return `foo set up its slash-commands in your coding agents (${outcome.wired.join(", ")})${outcome.reason === "upgrade" ? " after the upgrade" : ""} — reopen your agent to see the /foo-* commands. (disable: FOO_NO_AUTOWIRE=1)\n`;
810
+ }
811
+ function renderAgentSetup(args) {
812
+ const options = parseAgentOptions(args);
813
+ assertCanonicalCommandSources();
814
+ const base = targetBaseForScope(options.scope);
815
+ const selectedAgents = selectAgents(options.agent);
816
+ const changes = agentSetupChanges(base, selectedAgents, options.scope);
817
+ const hasConflict = changes.some((change) => change.action === "conflict");
818
+ const payload = {
819
+ schema_version: "0.1",
820
+ status: options.dryRun ? "dry_run" : "applied",
821
+ scope: options.scope,
822
+ base,
823
+ agents: selectedAgents.map((agent) => agent.id),
824
+ commands: [...FOO_COMMANDS],
825
+ routing_contract: FOO_ROUTING_CONTRACT,
826
+ changes,
827
+ human_handoff: {
828
+ reason: "Review command adapter changes before writing files.",
829
+ resume_command: `foo agent setup --agent ${options.agent} --scope ${options.scope} --yes`,
830
+ },
831
+ };
832
+ if (!options.dryRun && !options.yes) {
833
+ throw new UsageError("agent setup writes command files; rerun with --dry-run or --yes", "E_BAD_FLAG");
834
+ }
835
+ if (!options.dryRun)
836
+ applyAgentSetup(base, selectedAgents, options.scope);
837
+ if (options.format === "json")
838
+ return `${JSON.stringify(payload, null, 2)}\n`;
839
+ // Show the commands exactly as the user will type them in that harness, so the
840
+ // setup summary and `agent status` can never disagree about the name.
841
+ const named = (change) => {
842
+ const agent = AGENT_DEFINITIONS.find((candidate) => candidate.id === change.agent);
843
+ if (!agent)
844
+ return change.commands.join(", ");
845
+ return change.commands.map((command) => typedCommand(agent, command).replace(/^\//, "")).join(", ");
846
+ };
847
+ const describe = (change) => {
848
+ if (change.action === "unsupported") {
849
+ return `unsupported: ${change.agent} — ${change.unsupported_reason ?? "no command surface for this scope"}`;
850
+ }
851
+ if (change.action === "conflict") {
852
+ return `conflict: ${change.agent} ${change.conflicts.join(", ")} (not productfoo-managed)`;
853
+ }
854
+ if (change.action === "repair-legacy") {
855
+ const removed = [...change.legacy, ...change.stale.map((id) => `${id}.md`)].join(", ");
856
+ return `repair-legacy: ${change.agent} remove ${removed}, install ${named(change)} -> ${change.command_path}`;
857
+ }
858
+ if (change.action === "verify-existing") {
859
+ return `verify-existing: ${change.agent} ${named(change)} -> ${change.command_path}`;
860
+ }
861
+ return `install-commands: ${change.agent} ${named(change)} -> ${change.command_path}`;
862
+ };
863
+ const lines = [
864
+ options.dryRun ? "foo agent setup dry run" : "foo agent setup applied",
865
+ "",
866
+ ...changes.map(describe),
867
+ "",
868
+ ];
869
+ if (hasConflict && options.dryRun) {
870
+ lines.push("Some agents have conflicting command files; resolve them before applying.", "");
871
+ }
872
+ if (options.dryRun)
873
+ lines.push(`Next: ${payload.human_handoff.resume_command}`, "");
874
+ return lines.join("\n");
875
+ }
876
+ function relativeAgentLogPath() {
877
+ return "~/.foo/agent-events.jsonl";
878
+ }
879
+ function relativeAgentTelemetryConfigPath() {
880
+ return "~/.foo/telemetry.json (tester id, machine-wide) · .foo/telemetry.json (per-repo opt-out)";
881
+ }
882
+ function readAgentEvents(root) {
883
+ const logPath = agentLogPath(root);
884
+ if (!existsSync(logPath))
885
+ return [];
886
+ // A malformed ledger line is skipped, not fatal: the ledger is append-only
887
+ // and one corrupt write must not brick `foo agent logs|rate|telemetry`.
888
+ const events = [];
889
+ for (const line of readFileSync(logPath, "utf8").split("\n")) {
890
+ if (!line.trim())
891
+ continue;
892
+ try {
893
+ events.push(JSON.parse(line));
894
+ }
895
+ catch {
896
+ continue;
897
+ }
898
+ }
899
+ return events;
900
+ }
901
+ // Experience readers must partition by event_type so the per-invocation
902
+ // cli_usage firehose never dilutes the curated experience ledger (R-018).
903
+ function experienceEvents(events) {
904
+ return events.filter((event) => event.event_type === "agent_experience");
905
+ }
906
+ function usageEvents(events) {
907
+ return events.filter((event) => event.event_type === "cli_usage");
908
+ }
909
+ function ratingEvents(events) {
910
+ return events.filter((event) => event.event_type === "rating");
911
+ }
912
+ function agentLogExists(root) {
913
+ return existsSync(agentLogPath(root));
914
+ }
915
+ function agentTelemetryPayload(root) {
916
+ const { config, exists } = readAgentTelemetryConfig(root);
917
+ return {
918
+ schema_version: "0.1",
919
+ enabled: config.enabled,
920
+ tester_id: resolveTesterId(root) ?? null,
921
+ default_state: "enabled",
922
+ config_path: relativeAgentTelemetryConfigPath(),
923
+ config_exists: exists,
924
+ storage: "local",
925
+ network_requests: [],
926
+ disclosure: "Usage logs, profile answers, ratings, and diagnostic reports stay on this machine. Local usage logging is enabled by default and can be disabled. Explicit experience summaries and diagnostic reports can contain sensitive text; inspect them before sharing. Bundled skills load from the installed package. Update checks and provider operations have their own network behavior and settings; your coding agent may use external tools and incur charges.",
927
+ captures: ["command family and version", "exit status and bounded error code", "duration", "packet identifier when present", "local installation identifier", "terminal interactivity", "explicit experience summaries and ratings", "bounded profile picks"],
928
+ excludes: ["raw command arguments in usage events", "error messages and stack traces in usage events", "free-text profile answers in usage events"],
929
+ opt_out_command: "foo agent telemetry disable",
930
+ };
931
+ }
932
+ function summarizeAgentEvents(events) {
933
+ const counts = {
934
+ failure: events.filter((event) => event.kind === "failure").length,
935
+ poor_experience: events.filter((event) => event.kind === "poor_experience").length,
936
+ good_experience: events.filter((event) => event.kind === "good_experience").length,
937
+ };
938
+ const agentIds = [...new Set(events.map((event) => event.source?.agent ?? event.agent).filter(Boolean))].sort();
939
+ const byAgent = Object.fromEntries(agentIds.map((agent) => [
940
+ agent,
941
+ {
942
+ failure: events.filter((event) => (event.source?.agent ?? event.agent) === agent && event.kind === "failure").length,
943
+ poor_experience: events.filter((event) => (event.source?.agent ?? event.agent) === agent && event.kind === "poor_experience").length,
944
+ good_experience: events.filter((event) => (event.source?.agent ?? event.agent) === agent && event.kind === "good_experience").length,
945
+ },
946
+ ]));
947
+ return {
948
+ counts,
949
+ analytics: {
950
+ schema_version: "0.1",
951
+ total_events: events.length,
952
+ by_agent: byAgent,
953
+ },
954
+ };
955
+ }
956
+ // Usage rollup answers "which commands run / fail most" without touching the
957
+ // experience ledger's signal. Groups by command and exit code (R-018).
958
+ function summarizeUsageEvents(events) {
959
+ const byCommand = {};
960
+ for (const event of events) {
961
+ const key = event.command || event.domain || "unknown";
962
+ const slot = (byCommand[key] ??= { runs: 0, failures: 0, by_exit_code: {} });
963
+ slot.runs += 1;
964
+ if (event.exit_code !== 0)
965
+ slot.failures += 1;
966
+ const codeKey = String(event.exit_code);
967
+ slot.by_exit_code[codeKey] = (slot.by_exit_code[codeKey] ?? 0) + 1;
968
+ }
969
+ return {
970
+ schema_version: "0.1",
971
+ total: events.length,
972
+ by_command: byCommand,
973
+ };
974
+ }
975
+ // T-010 (spec-after-capture-v0): the load-bearing pull-through metric. Counts how
976
+ // many `foo spec from` runs were each followed by a `foo asp reconcile` run — the
977
+ // developer advancing into the loop rather than treating the one-shot drift report
978
+ // as done. V0a proxy: a same-log greedy sequential match by timestamp; coarse
979
+ // until V0b writes spec ids that tie a reconcile to the exact spec it reconciles.
980
+ export function specAfterPullThrough(events) {
981
+ const sorted = [...events].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
982
+ let openSpecFrom = 0;
983
+ let specFromRuns = 0;
984
+ let reconcileRuns = 0;
985
+ let pulled = 0;
986
+ for (const event of sorted) {
987
+ if (event.command === "spec from") {
988
+ specFromRuns += 1;
989
+ openSpecFrom += 1;
990
+ }
991
+ else if (event.command === "asp reconcile") {
992
+ reconcileRuns += 1;
993
+ if (openSpecFrom > 0) {
994
+ pulled += 1;
995
+ openSpecFrom -= 1;
996
+ }
997
+ }
998
+ }
999
+ return {
1000
+ spec_from_runs: specFromRuns,
1001
+ reconcile_runs: reconcileRuns,
1002
+ pulled_through: pulled,
1003
+ rate: specFromRuns > 0 ? Math.round((pulled / specFromRuns) * 100) / 100 : null,
1004
+ };
1005
+ }
1006
+ // Averages 1-7 ratings by field and by checkpoint (R-010/R-012, C-002/C-006) and
1007
+ // counts low-rating reroute follow-ups (R-011, C-004). No raw prompt text.
1008
+ function summarizeRatingEvents(events) {
1009
+ const round = (sum, count) => Math.round((sum / count) * 100) / 100;
1010
+ const fieldSum = {};
1011
+ const byCheckpoint = {};
1012
+ for (const event of events) {
1013
+ const slot = (byCheckpoint[event.checkpoint] ??= { count: 0, fields: {} });
1014
+ slot.count += 1;
1015
+ for (const [field, value] of Object.entries(event.ratings)) {
1016
+ (fieldSum[field] ??= { sum: 0, count: 0 });
1017
+ fieldSum[field].sum += value;
1018
+ fieldSum[field].count += 1;
1019
+ (slot.fields[field] ??= { sum: 0, count: 0 });
1020
+ slot.fields[field].sum += value;
1021
+ slot.fields[field].count += 1;
1022
+ }
1023
+ }
1024
+ return {
1025
+ schema_version: "0.1",
1026
+ total: events.length,
1027
+ reroute_required: events.filter((event) => event.reroute.required).length,
1028
+ average_by_field: Object.fromEntries(Object.entries(fieldSum).map(([field, { sum, count }]) => [field, round(sum, count)])),
1029
+ by_checkpoint: Object.fromEntries(Object.entries(byCheckpoint).map(([checkpoint, { count, fields }]) => [
1030
+ checkpoint,
1031
+ {
1032
+ count,
1033
+ average_by_field: Object.fromEntries(Object.entries(fields).map(([field, { sum, count: fieldCount }]) => [field, round(sum, fieldCount)])),
1034
+ },
1035
+ ])),
1036
+ };
1037
+ }
1038
+ function renderAgentLog(root, args) {
1039
+ const options = parseAgentLogOptions(args);
1040
+ const promptMode = options.includePrompt
1041
+ ? "raw"
1042
+ : options.promptMode ?? (options.promptSummary ? "summary" : "metadata");
1043
+ const promptCaptured = promptMode === "raw" && Boolean(options.prompt);
1044
+ // T-004 (R-005): never store a raw secret. Redact the captured prompt before
1045
+ // it lands in the local ledger; the receipt reports what was scrubbed. A
1046
+ // secret-free prompt passes through unchanged (redactions = 0).
1047
+ const redaction = promptCaptured ? redactText(options.prompt, { allowlist: loadRepoAllowlist(root) }) : null;
1048
+ const event = {
1049
+ schema_version: "0.1",
1050
+ event_type: "agent_experience",
1051
+ id: nextAgentEventId(),
1052
+ timestamp: new Date().toISOString(),
1053
+ kind: options.kind,
1054
+ summary: options.summary,
1055
+ agent: options.agent,
1056
+ packet: options.packet,
1057
+ command: options.command,
1058
+ prompt_captured: promptCaptured,
1059
+ ...(redaction ? { prompt: redaction.redactedText } : {}),
1060
+ source: {
1061
+ agent: options.agent,
1062
+ packet: options.packet,
1063
+ command: options.command,
1064
+ },
1065
+ prompt_context: {
1066
+ mode: promptMode,
1067
+ captured: promptCaptured,
1068
+ ...(redaction && redaction.receipt.redactions > 0 ? { redactions: redaction.receipt.redactions } : {}),
1069
+ ...(options.promptSummary ? { summary: options.promptSummary } : {}),
1070
+ },
1071
+ };
1072
+ // One append per event, size-cap rotation (cli-performance-v0 R-004).
1073
+ appendLedgerEvent(agentLogPath(root), `${JSON.stringify(event)}\n`);
1074
+ if (options.format === "json")
1075
+ return `${JSON.stringify(event, null, 2)}\n`;
1076
+ return [
1077
+ "foo agent log recorded",
1078
+ "",
1079
+ `Kind: ${event.kind}`,
1080
+ `Summary: ${event.summary}`,
1081
+ `Prompt captured: ${event.prompt_captured ? "yes" : "no"}`,
1082
+ ...(redaction ? [`Prompt redactions: ${redaction.receipt.redactions} (secret values scrubbed before storage; R-007)`] : []),
1083
+ `Path: ${relativeAgentLogPath()}`,
1084
+ "",
1085
+ ].join("\n");
1086
+ }
1087
+ function parseRateKeyValue(arg, raw) {
1088
+ if (!raw)
1089
+ throw new UsageError(`${arg} requires a <key>=<value>`, "E_BAD_FLAG");
1090
+ const eq = raw.indexOf("=");
1091
+ if (eq <= 0)
1092
+ throw new UsageError(`${arg} expects <key>=<value>, got: ${raw}`, "E_BAD_FLAG");
1093
+ return [raw.slice(0, eq), raw.slice(eq + 1)];
1094
+ }
1095
+ function parseAgentRateOptions(args) {
1096
+ const options = {
1097
+ checkpoint: "",
1098
+ ratings: {},
1099
+ qualitative: {},
1100
+ format: "text",
1101
+ };
1102
+ for (let index = 0; index < args.length; index += 1) {
1103
+ const arg = args[index];
1104
+ if (arg === "--json") {
1105
+ options.format = "json";
1106
+ continue;
1107
+ }
1108
+ if (arg === "--rating") {
1109
+ const [field, value] = parseRateKeyValue(arg, args[index + 1]);
1110
+ if (!RATING_FIELDS.includes(field)) {
1111
+ throw new UsageError(`unknown rating field: ${field} (allowed: ${RATING_FIELDS.join(", ")})`, "E_BAD_FLAG");
1112
+ }
1113
+ if (!/^[1-7]$/.test(value))
1114
+ throw new UsageError(`--rating ${field} must be an integer 1-7, got: ${value}`, "E_BAD_FLAG");
1115
+ options.ratings[field] = Number(value);
1116
+ index += 1;
1117
+ continue;
1118
+ }
1119
+ if (arg === "--note") {
1120
+ const [key, value] = parseRateKeyValue(arg, args[index + 1]);
1121
+ options.qualitative[key] = value;
1122
+ index += 1;
1123
+ continue;
1124
+ }
1125
+ if (["--checkpoint", "--baseline-mode", "--lift-drivers", "--agent", "--packet", "--commit", "--artifact", "--reroute"].includes(arg)) {
1126
+ const value = args[index + 1];
1127
+ if (!value)
1128
+ throw new UsageError(`${arg} requires a value`, "E_BAD_FLAG");
1129
+ if (arg === "--checkpoint")
1130
+ options.checkpoint = value;
1131
+ if (arg === "--baseline-mode")
1132
+ options.baselineMode = value;
1133
+ if (arg === "--lift-drivers")
1134
+ options.liftDrivers = value;
1135
+ if (arg === "--agent")
1136
+ options.agent = value;
1137
+ if (arg === "--packet")
1138
+ options.packet = value;
1139
+ if (arg === "--commit")
1140
+ options.commit = value;
1141
+ if (arg === "--artifact")
1142
+ options.artifact = value;
1143
+ if (arg === "--reroute") {
1144
+ if (!REROUTE_CHOICES.includes(value)) {
1145
+ throw new UsageError(`--reroute must be one of: ${REROUTE_CHOICES.join(", ")}`, "E_BAD_FLAG");
1146
+ }
1147
+ options.reroute = value;
1148
+ }
1149
+ index += 1;
1150
+ continue;
1151
+ }
1152
+ throw new UsageError(`unknown agent rate argument: ${arg}`, "E_BAD_FLAG");
1153
+ }
1154
+ if (!options.checkpoint)
1155
+ throw new UsageError("--checkpoint is required", "E_BAD_FLAG");
1156
+ if (Object.keys(options.ratings).length === 0) {
1157
+ throw new UsageError("at least one --rating <field>=<1-7> is required", "E_BAD_FLAG");
1158
+ }
1159
+ return options;
1160
+ }
1161
+ // `foo agent rate` records product-management (R-010) and design/workflow (R-012)
1162
+ // ratings at a checkpoint. A rating of 3 or lower flags a reroute follow-up but
1163
+ // never restarts a loop automatically — the user records the choice (R-011, C-004).
1164
+ function renderAgentRate(root, args) {
1165
+ const options = parseAgentRateOptions(args);
1166
+ const lowRating = Object.values(options.ratings).some((value) => value <= 3);
1167
+ const event = {
1168
+ schema_version: "0.1",
1169
+ event_type: "rating",
1170
+ id: nextAgentEventId(),
1171
+ timestamp: new Date().toISOString(),
1172
+ checkpoint: options.checkpoint,
1173
+ ratings: options.ratings,
1174
+ qualitative: options.qualitative,
1175
+ ...(options.baselineMode ? { baseline_mode: options.baselineMode } : {}),
1176
+ ...(options.liftDrivers ? { lift_drivers: options.liftDrivers } : {}),
1177
+ ...(options.agent ? { agent: options.agent } : {}),
1178
+ ...(options.packet ? { packet_ref: options.packet } : {}),
1179
+ ...(options.commit ? { commit_ref: options.commit } : {}),
1180
+ ...(options.artifact ? { artifact_ref: options.artifact } : {}),
1181
+ reroute: { required: lowRating, choice: options.reroute ?? null },
1182
+ source: { agent: options.agent, checkpoint: options.checkpoint },
1183
+ };
1184
+ // One append per event, size-cap rotation (cli-performance-v0 R-004).
1185
+ appendLedgerEvent(agentLogPath(root), `${JSON.stringify(event)}\n`);
1186
+ if (options.format === "json")
1187
+ return `${JSON.stringify(event, null, 2)}\n`;
1188
+ const needsRerouteChoice = event.reroute.required && !event.reroute.choice;
1189
+ return [
1190
+ "foo agent rating recorded",
1191
+ "",
1192
+ `Checkpoint: ${event.checkpoint}`,
1193
+ `Ratings: ${Object.entries(event.ratings).map(([field, value]) => `${field}=${value}`).join(", ")}`,
1194
+ `Reroute required: ${event.reroute.required ? "yes" : "no"}${event.reroute.choice ? ` (choice: ${event.reroute.choice})` : ""}`,
1195
+ ...(needsRerouteChoice
1196
+ ? [
1197
+ "",
1198
+ "A rating of 3 or lower was recorded. productfoo will not reroute automatically.",
1199
+ "Record your choice: re-run with --reroute spec|review|artifact|none",
1200
+ ]
1201
+ : []),
1202
+ `Path: ${relativeAgentLogPath()}`,
1203
+ "",
1204
+ ].join("\n");
1205
+ }
1206
+ function renderAgentLogs(root, args) {
1207
+ if (args[0] === "sync")
1208
+ throw new UsageError("Log sync is not available. Logs stay local; use foo agent logs --json to inspect them.", "E_BAD_FLAG");
1209
+ const options = parseAgentLogsOptions(args);
1210
+ const all = readAgentEvents(root);
1211
+ const logExists = agentLogExists(root);
1212
+ if (options.usage) {
1213
+ const usageList = usageEvents(all);
1214
+ const usage = summarizeUsageEvents(usageList);
1215
+ const pullThrough = specAfterPullThrough(usageList);
1216
+ const payload = {
1217
+ schema_version: "0.1",
1218
+ log_path: relativeAgentLogPath(),
1219
+ log_exists: logExists,
1220
+ usage_count: usage.total,
1221
+ by_command: usage.by_command,
1222
+ spec_after_pull_through: pullThrough,
1223
+ };
1224
+ if (options.format === "json")
1225
+ return `${JSON.stringify(payload, null, 2)}\n`;
1226
+ const rows = Object.entries(usage.by_command)
1227
+ .sort((a, b) => b[1].runs - a[1].runs)
1228
+ .map(([command, stat]) => `${command}: ${stat.runs} run(s), ${stat.failures} failure(s)`);
1229
+ const pullThroughLine = pullThrough.spec_from_runs > 0
1230
+ ? [
1231
+ "",
1232
+ `spec-after pull-through: ${pullThrough.pulled_through}/${pullThrough.spec_from_runs} ` +
1233
+ `\`foo spec from\` run(s) advanced into \`foo asp reconcile\` (rate ${pullThrough.rate}).`,
1234
+ ]
1235
+ : [];
1236
+ return [
1237
+ "foo agent usage",
1238
+ "",
1239
+ `Path: ${payload.log_path}`,
1240
+ `Invocations: ${usage.total}`,
1241
+ ...(rows.length ? ["", ...rows] : ["", "No CLI usage events recorded yet."]),
1242
+ ...pullThroughLine,
1243
+ "",
1244
+ ].join("\n");
1245
+ }
1246
+ if (options.ratings) {
1247
+ const summary = summarizeRatingEvents(ratingEvents(all));
1248
+ const payload = {
1249
+ schema_version: "0.1",
1250
+ log_path: relativeAgentLogPath(),
1251
+ log_exists: logExists,
1252
+ rating_count: summary.total,
1253
+ reroute_required: summary.reroute_required,
1254
+ average_by_field: summary.average_by_field,
1255
+ by_checkpoint: summary.by_checkpoint,
1256
+ };
1257
+ if (options.format === "json")
1258
+ return `${JSON.stringify(payload, null, 2)}\n`;
1259
+ const rows = Object.entries(summary.average_by_field).map(([field, avg]) => `${field}: ${avg}`);
1260
+ return [
1261
+ "foo agent ratings",
1262
+ "",
1263
+ `Path: ${payload.log_path}`,
1264
+ `Ratings: ${summary.total} (reroute follow-ups: ${summary.reroute_required})`,
1265
+ ...(rows.length ? ["", "Average by field:", ...rows] : ["", "No ratings recorded yet."]),
1266
+ "",
1267
+ ].join("\n");
1268
+ }
1269
+ const events = experienceEvents(all);
1270
+ const { counts, analytics } = summarizeAgentEvents(events);
1271
+ const payload = {
1272
+ schema_version: "0.1",
1273
+ log_path: relativeAgentLogPath(),
1274
+ log_exists: logExists,
1275
+ counts,
1276
+ analytics,
1277
+ events,
1278
+ };
1279
+ if (options.format === "json")
1280
+ return `${JSON.stringify(payload, null, 2)}\n`;
1281
+ return [
1282
+ "foo agent logs",
1283
+ "",
1284
+ `Path: ${payload.log_path}`,
1285
+ `Failures: ${counts.failure}`,
1286
+ `Poor experiences: ${counts.poor_experience}`,
1287
+ `Good experiences: ${counts.good_experience}`,
1288
+ ...(events.length
1289
+ ? []
1290
+ : [
1291
+ "",
1292
+ "No local agent experience events recorded yet.",
1293
+ 'Start one with: foo agent log --kind good_experience --summary "..." --agent codex',
1294
+ ]),
1295
+ "",
1296
+ ].join("\n");
1297
+ }
1298
+ function renderAgentTelemetry(root, command, args) {
1299
+ if (!command)
1300
+ throw new UsageError("agent telemetry command missing", "E_COMMAND_MISSING");
1301
+ // `set-id <id>` takes a positional; keep it out of the flag parser.
1302
+ const positionals = args.filter((arg) => !arg.startsWith("--"));
1303
+ const options = parseAgentTelemetryOptions(args.filter((arg) => arg.startsWith("--")));
1304
+ const projectCurrent = readTelemetryFile(agentTelemetryConfigPath(root)).config;
1305
+ if (command === "enable")
1306
+ writeAgentTelemetryConfig(root, { ...projectCurrent, enabled: true });
1307
+ else if (command === "disable")
1308
+ writeAgentTelemetryConfig(root, { ...projectCurrent, enabled: false });
1309
+ else if (command === "set-id") {
1310
+ const id = positionals[0]?.trim();
1311
+ if (!id)
1312
+ throw new UsageError("usage: foo agent telemetry set-id <id>", "E_BAD_FLAG");
1313
+ writeMachineTesterId(id);
1314
+ }
1315
+ else if (command === "reset-id") {
1316
+ writeMachineTesterId(generateTesterId());
1317
+ }
1318
+ else if (command !== "status") {
1319
+ throw new UsageError(`unknown agent telemetry command: ${command}`, "E_BAD_FLAG");
1320
+ }
1321
+ const payload = agentTelemetryPayload(root);
1322
+ if (options.format === "json")
1323
+ return `${JSON.stringify(payload, null, 2)}\n`;
1324
+ return [
1325
+ "foo local usage logging", "",
1326
+ `Enabled: ${payload.enabled ? "yes" : "no"}`,
1327
+ `Local installation id: ${payload.tester_id ?? "(none — logging off)"}`,
1328
+ `Config: ${payload.config_path}`,
1329
+ payload.disclosure, "",
1330
+ `Recorded locally: ${payload.captures.join(", ")}`,
1331
+ `Excluded from usage events: ${payload.excludes.join(", ")}`,
1332
+ `Opt out: ${payload.opt_out_command}`, "",
1333
+ ].join("\n");
1334
+ }
1335
+ export function runAgentCommand(root, command, args) {
1336
+ if (!command)
1337
+ throw new UsageError("agent command missing", "E_COMMAND_MISSING");
1338
+ if (command === "status")
1339
+ return renderAgentStatus(parseAgentOptions(args));
1340
+ if (command === "doctor")
1341
+ return renderAgentDoctor();
1342
+ if (command === "setup")
1343
+ return renderAgentSetup(args);
1344
+ if (command === "log")
1345
+ return renderAgentLog(root, args);
1346
+ if (command === "rate")
1347
+ return renderAgentRate(root, args);
1348
+ if (command === "logs")
1349
+ return renderAgentLogs(root, args);
1350
+ if (command === "telemetry")
1351
+ return renderAgentTelemetry(root, args[0], args.slice(1));
1352
+ throw new UsageError(`unknown agent command: ${command}`, "E_BAD_FLAG");
1353
+ }