agents-inc 0.164.0 → 0.166.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 (191) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +8 -9
  3. package/dist/{chunk-X4SSJZLY.js → chunk-25MPBIK3.js} +12 -261
  4. package/dist/chunk-25MPBIK3.js.map +1 -0
  5. package/dist/{chunk-LT5GXEB2.js → chunk-2VUYFTUB.js} +8 -7
  6. package/dist/chunk-2VUYFTUB.js.map +1 -0
  7. package/dist/chunk-4AAOOAIN.js +49 -0
  8. package/dist/chunk-4AAOOAIN.js.map +1 -0
  9. package/dist/{chunk-ARYNOWKF.js → chunk-4NDBUJHK.js} +8 -8
  10. package/dist/{chunk-S63LXWMC.js → chunk-6L75SQZU.js} +2 -2
  11. package/dist/{chunk-7REKOTKG.js → chunk-6LF4CQBY.js} +3 -3
  12. package/dist/{chunk-K5ITEA3I.js → chunk-7JNJ52CD.js} +34 -17
  13. package/dist/chunk-7JNJ52CD.js.map +1 -0
  14. package/dist/{chunk-XPDVAJDH.js → chunk-A4KDXNOT.js} +3519 -1568
  15. package/dist/chunk-A4KDXNOT.js.map +1 -0
  16. package/dist/{chunk-XHPWTL4I.js → chunk-DAIEQJIF.js} +3 -3
  17. package/dist/{chunk-UITEKY2R.js → chunk-ENGYJCTF.js} +28 -4
  18. package/dist/chunk-ENGYJCTF.js.map +1 -0
  19. package/dist/{chunk-G2JJ5BO5.js → chunk-G4JK7WVO.js} +336 -140
  20. package/dist/chunk-G4JK7WVO.js.map +1 -0
  21. package/dist/{chunk-X37UYIE3.js → chunk-G6UREQ4V.js} +436 -739
  22. package/dist/chunk-G6UREQ4V.js.map +1 -0
  23. package/dist/chunk-GE3WKJ6Q.js +375 -0
  24. package/dist/chunk-GE3WKJ6Q.js.map +1 -0
  25. package/dist/{chunk-RGYDFBCT.js → chunk-HHIYT3PI.js} +5 -5
  26. package/dist/{chunk-2CQNPLVF.js → chunk-HWEKHVLE.js} +63 -42
  27. package/dist/chunk-HWEKHVLE.js.map +1 -0
  28. package/dist/{chunk-XVAQG2CV.js → chunk-JOEA52NR.js} +10 -8
  29. package/dist/chunk-JOEA52NR.js.map +1 -0
  30. package/dist/{chunk-3WVH2CTK.js → chunk-JQRJQ4SS.js} +15 -8
  31. package/dist/chunk-JQRJQ4SS.js.map +1 -0
  32. package/dist/{chunk-DSL66I4N.js → chunk-LFWLEDUG.js} +6 -6
  33. package/dist/{chunk-B3Y3QMWI.js → chunk-MAWNVT2C.js} +4 -4
  34. package/dist/{chunk-Q4IRRVXK.js → chunk-MPTNC32T.js} +3 -3
  35. package/dist/chunk-NBXXFIIW.js +73 -0
  36. package/dist/chunk-NBXXFIIW.js.map +1 -0
  37. package/dist/{chunk-B6FSLXYN.js → chunk-PMJWD27C.js} +3 -3
  38. package/dist/{chunk-XATLUKXK.js → chunk-PNB3524H.js} +6 -6
  39. package/dist/chunk-PXWG7KFO.js +39 -0
  40. package/dist/chunk-PXWG7KFO.js.map +1 -0
  41. package/dist/{chunk-4VL3CEOS.js → chunk-QHDPLLK6.js} +3 -3
  42. package/dist/{chunk-AOTCIUGX.js → chunk-RAHSOJQ2.js} +2 -2
  43. package/dist/{chunk-PHCPUQVR.js → chunk-RZDXDMT6.js} +2 -2
  44. package/dist/{chunk-6MV4JOCL.js → chunk-SAYZWCH6.js} +2 -2
  45. package/dist/{chunk-URXIQE7I.js → chunk-SCOQ34YM.js} +2 -2
  46. package/dist/{chunk-NQXO4LR3.js → chunk-SXVHSOTH.js} +16 -11
  47. package/dist/chunk-SXVHSOTH.js.map +1 -0
  48. package/dist/{chunk-QZX7SYKU.js → chunk-T6UWJAB3.js} +2 -2
  49. package/dist/{chunk-ZVXGW2IC.js → chunk-TODCQFRB.js} +4 -6
  50. package/dist/chunk-TODCQFRB.js.map +1 -0
  51. package/dist/{chunk-YRS3QZP2.js → chunk-TTY6DPJN.js} +3 -3
  52. package/dist/{chunk-QGUP3QJF.js → chunk-TZ2YJJKK.js} +22 -18
  53. package/dist/{chunk-QGUP3QJF.js.map → chunk-TZ2YJJKK.js.map} +1 -1
  54. package/dist/chunk-V3I7VQ7N.js +356 -0
  55. package/dist/chunk-V3I7VQ7N.js.map +1 -0
  56. package/dist/{chunk-AVK2HEKB.js → chunk-V5WXIM2F.js} +2 -6
  57. package/dist/{chunk-AVK2HEKB.js.map → chunk-V5WXIM2F.js.map} +1 -1
  58. package/dist/{chunk-NKGYKOB7.js → chunk-VAPTVR3J.js} +7 -8
  59. package/dist/chunk-VAPTVR3J.js.map +1 -0
  60. package/dist/chunk-VRXX4D6K.js +31 -0
  61. package/dist/chunk-VRXX4D6K.js.map +1 -0
  62. package/dist/{chunk-ABAZMMG4.js → chunk-WVUDLKIO.js} +2 -2
  63. package/dist/{chunk-ECURHW3T.js → chunk-WWIFNPJG.js} +13 -12
  64. package/dist/chunk-WWIFNPJG.js.map +1 -0
  65. package/dist/{chunk-J27W6TXS.js → chunk-XQHZPINL.js} +8 -4
  66. package/dist/chunk-XQHZPINL.js.map +1 -0
  67. package/dist/{chunk-V5GLL2JB.js → chunk-XZEVWPX6.js} +7 -7
  68. package/dist/chunk-Y3VIRVVE.js +932 -0
  69. package/dist/chunk-Y3VIRVVE.js.map +1 -0
  70. package/dist/{chunk-NTF4UXGK.js → chunk-Y47EV74W.js} +73 -30
  71. package/dist/chunk-Y47EV74W.js.map +1 -0
  72. package/dist/chunk-ZR4YPKXQ.js +159 -0
  73. package/dist/chunk-ZR4YPKXQ.js.map +1 -0
  74. package/dist/chunk-ZXKBERQU.js +250 -0
  75. package/dist/chunk-ZXKBERQU.js.map +1 -0
  76. package/dist/commands/build/marketplace.js +17 -17
  77. package/dist/commands/build/marketplace.js.map +1 -1
  78. package/dist/commands/build/plugins.js +11 -9
  79. package/dist/commands/build/plugins.js.map +1 -1
  80. package/dist/commands/compile.js +105 -27
  81. package/dist/commands/compile.js.map +1 -1
  82. package/dist/commands/doctor.js +309 -171
  83. package/dist/commands/doctor.js.map +1 -1
  84. package/dist/commands/edit.js +503 -314
  85. package/dist/commands/edit.js.map +1 -1
  86. package/dist/commands/eject.js +40 -23
  87. package/dist/commands/eject.js.map +1 -1
  88. package/dist/commands/init.js +44 -40
  89. package/dist/commands/list.js +44 -10
  90. package/dist/commands/list.js.map +1 -1
  91. package/dist/commands/new/marketplace.js +13 -10
  92. package/dist/commands/new/marketplace.js.map +1 -1
  93. package/dist/commands/search.js +12 -10
  94. package/dist/commands/search.js.map +1 -1
  95. package/dist/commands/share.js +102 -15
  96. package/dist/commands/share.js.map +1 -1
  97. package/dist/commands/uninstall.js +231 -103
  98. package/dist/commands/uninstall.js.map +1 -1
  99. package/dist/commands/update.js +49 -20
  100. package/dist/commands/update.js.map +1 -1
  101. package/dist/components/common/prompt-confirm.js +1 -1
  102. package/dist/components/common/removal-plan-confirm.js +2 -2
  103. package/dist/components/common/select-list.js +3 -3
  104. package/dist/components/common/spinner.js +6 -3
  105. package/dist/components/wizard/category-grid.js +5 -5
  106. package/dist/components/wizard/checkbox-grid.js +4 -4
  107. package/dist/components/wizard/domain-selection.js +9 -9
  108. package/dist/components/wizard/run-wizard-session.js +29 -29
  109. package/dist/components/wizard/scroll-affordance.js +2 -2
  110. package/dist/components/wizard/skill-agent-summary.js +5 -5
  111. package/dist/components/wizard/source-grid.js +6 -6
  112. package/dist/components/wizard/stack-selection.js +8 -8
  113. package/dist/components/wizard/step-agents.js +8 -8
  114. package/dist/components/wizard/step-build.js +8 -8
  115. package/dist/components/wizard/step-confirm.js +9 -9
  116. package/dist/components/wizard/step-sources.js +9 -9
  117. package/dist/components/wizard/step-stack.js +9 -9
  118. package/dist/components/wizard/summary-panel.js +8 -8
  119. package/dist/components/wizard/toast.js +2 -2
  120. package/dist/components/wizard/wizard-layout.js +12 -12
  121. package/dist/components/wizard/wizard-tabs.js +2 -2
  122. package/dist/components/wizard/wizard.js +27 -27
  123. package/dist/hooks/init.js +69 -43
  124. package/dist/hooks/init.js.map +1 -1
  125. package/dist/load-agent-defs-3C27ENC7.js +14 -0
  126. package/dist/recompile-project-agents-DGNBCU3N.js +18 -0
  127. package/dist/{source-loader-GRBVNRAW.js → source-loader-7KZ3MD5O.js} +6 -4
  128. package/dist/src/agents/_templates/agent-body.liquid +55 -0
  129. package/dist/src/agents/_templates/agent.liquid +1 -55
  130. package/dist/src/agents/meta/agent-summoner/output.md +1 -1
  131. package/dist/src/agents/meta/agent-summoner/playbook.md +15 -7
  132. package/dist/stores/wizard-store.js +4 -4
  133. package/package.json +10 -9
  134. package/src/agents/_templates/agent-body.liquid +55 -0
  135. package/src/agents/_templates/agent.liquid +1 -55
  136. package/src/agents/meta/agent-summoner/output.md +1 -1
  137. package/src/agents/meta/agent-summoner/playbook.md +15 -7
  138. package/src/schemas/project-config.schema.json +1 -1
  139. package/src/schemas/project-source-config.schema.json +1 -1
  140. package/dist/chunk-2CQNPLVF.js.map +0 -1
  141. package/dist/chunk-3WVH2CTK.js.map +0 -1
  142. package/dist/chunk-7ANJQLPJ.js +0 -35
  143. package/dist/chunk-7ANJQLPJ.js.map +0 -1
  144. package/dist/chunk-ECURHW3T.js.map +0 -1
  145. package/dist/chunk-FLW25ORT.js +0 -130
  146. package/dist/chunk-FLW25ORT.js.map +0 -1
  147. package/dist/chunk-G2JJ5BO5.js.map +0 -1
  148. package/dist/chunk-J27W6TXS.js.map +0 -1
  149. package/dist/chunk-JCPANDNR.js +0 -35
  150. package/dist/chunk-JCPANDNR.js.map +0 -1
  151. package/dist/chunk-JIERYE6B.js +0 -480
  152. package/dist/chunk-JIERYE6B.js.map +0 -1
  153. package/dist/chunk-JU4ZGJ7A.js +0 -668
  154. package/dist/chunk-JU4ZGJ7A.js.map +0 -1
  155. package/dist/chunk-K5ITEA3I.js.map +0 -1
  156. package/dist/chunk-LT5GXEB2.js.map +0 -1
  157. package/dist/chunk-NKGYKOB7.js.map +0 -1
  158. package/dist/chunk-NQXO4LR3.js.map +0 -1
  159. package/dist/chunk-NTF4UXGK.js.map +0 -1
  160. package/dist/chunk-UITEKY2R.js.map +0 -1
  161. package/dist/chunk-X37UYIE3.js.map +0 -1
  162. package/dist/chunk-X4SSJZLY.js.map +0 -1
  163. package/dist/chunk-XPDVAJDH.js.map +0 -1
  164. package/dist/chunk-XVAQG2CV.js.map +0 -1
  165. package/dist/chunk-ZBPEFQ4G.js +0 -12
  166. package/dist/chunk-ZBPEFQ4G.js.map +0 -1
  167. package/dist/chunk-ZVXGW2IC.js.map +0 -1
  168. package/dist/load-agent-defs-7IM3GBEQ.js +0 -13
  169. package/dist/recompile-project-agents-VR4NLGOU.js +0 -17
  170. /package/dist/{chunk-ARYNOWKF.js.map → chunk-4NDBUJHK.js.map} +0 -0
  171. /package/dist/{chunk-S63LXWMC.js.map → chunk-6L75SQZU.js.map} +0 -0
  172. /package/dist/{chunk-7REKOTKG.js.map → chunk-6LF4CQBY.js.map} +0 -0
  173. /package/dist/{chunk-XHPWTL4I.js.map → chunk-DAIEQJIF.js.map} +0 -0
  174. /package/dist/{chunk-RGYDFBCT.js.map → chunk-HHIYT3PI.js.map} +0 -0
  175. /package/dist/{chunk-DSL66I4N.js.map → chunk-LFWLEDUG.js.map} +0 -0
  176. /package/dist/{chunk-B3Y3QMWI.js.map → chunk-MAWNVT2C.js.map} +0 -0
  177. /package/dist/{chunk-Q4IRRVXK.js.map → chunk-MPTNC32T.js.map} +0 -0
  178. /package/dist/{chunk-B6FSLXYN.js.map → chunk-PMJWD27C.js.map} +0 -0
  179. /package/dist/{chunk-XATLUKXK.js.map → chunk-PNB3524H.js.map} +0 -0
  180. /package/dist/{chunk-4VL3CEOS.js.map → chunk-QHDPLLK6.js.map} +0 -0
  181. /package/dist/{chunk-AOTCIUGX.js.map → chunk-RAHSOJQ2.js.map} +0 -0
  182. /package/dist/{chunk-PHCPUQVR.js.map → chunk-RZDXDMT6.js.map} +0 -0
  183. /package/dist/{chunk-6MV4JOCL.js.map → chunk-SAYZWCH6.js.map} +0 -0
  184. /package/dist/{chunk-URXIQE7I.js.map → chunk-SCOQ34YM.js.map} +0 -0
  185. /package/dist/{chunk-QZX7SYKU.js.map → chunk-T6UWJAB3.js.map} +0 -0
  186. /package/dist/{chunk-YRS3QZP2.js.map → chunk-TTY6DPJN.js.map} +0 -0
  187. /package/dist/{chunk-ABAZMMG4.js.map → chunk-WVUDLKIO.js.map} +0 -0
  188. /package/dist/{chunk-V5GLL2JB.js.map → chunk-XZEVWPX6.js.map} +0 -0
  189. /package/dist/{load-agent-defs-7IM3GBEQ.js.map → load-agent-defs-3C27ENC7.js.map} +0 -0
  190. /package/dist/{recompile-project-agents-VR4NLGOU.js.map → recompile-project-agents-DGNBCU3N.js.map} +0 -0
  191. /package/dist/{source-loader-GRBVNRAW.js.map → source-loader-7KZ3MD5O.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/commands/new/marketplace.ts","../../../src/cli/lib/marketplace-scaffold.ts"],"sourcesContent":["import { Args } from \"@oclif/core\";\nimport path from \"path\";\n\nimport { BaseCommand } from \"../../base-command.js\";\nimport { CLI_INVOKE_COMMAND, SKILLS_DIR_PATH } from \"../../consts.js\";\nimport { EXIT_CODES } from \"../../lib/exit-codes.js\";\nimport { validateMarketplaceName } from \"../../lib/marketplace-generator.js\";\nimport { exampleSkillId, writeMarketplaceScaffold } from \"../../lib/marketplace-scaffold.js\";\nimport { validateKebabCaseName } from \"../../lib/validate-kebab-name.js\";\nimport { isDirectoryEmpty } from \"../../utils/fs.js\";\n\n/**\n * The refusal over a target directory that already holds something.\n *\n * A scaffold writes a whole marketplace, so writing into an occupied directory\n * would mean merging with — or overwriting — work this command did not author.\n * Both ways out are one step, which is why there is no `--force`: a flag that\n * overwrites an author's own files is the destructive half of a silent fallback.\n */\nfunction occupiedDirectoryError(marketplaceDir: string): string {\n return (\n `${marketplaceDir} already holds files. A scaffold writes a whole marketplace, ` +\n `so it will not write into a directory that already has contents. Choose another ` +\n `name, or empty that directory first.`\n );\n}\n\nexport default class NewMarketplace extends BaseCommand {\n static summary = \"Scaffold a marketplace of your own\";\n\n static description =\n \"Create a directory holding everything the CLI needs to read a marketplace: a package.json \" +\n \"carrying its identity, the three config files, and one example skill already named in the \" +\n \"marketplace's own namespace. It scaffolds only — run 'build plugins' and 'build marketplace' \" +\n \"to publish what you have written.\";\n\n static examples = [\n {\n description: \"Scaffold a marketplace named acme into ./acme\",\n command: \"<%= config.bin %> <%= command.id %> acme\",\n },\n ];\n\n static args = {\n name: Args.string({\n description:\n \"Name of the marketplace (kebab-case). Becomes the directory, the package.json name, \" +\n \"and the prefix every skill id must carry.\",\n required: true,\n }),\n };\n\n async run(): Promise<void> {\n const { args } = await this.parse(NewMarketplace);\n const marketplaceName = args.name;\n const marketplaceDir = path.join(process.cwd(), marketplaceName);\n\n this.refuseUnusableName(marketplaceName);\n await this.refuseOccupiedDirectory(marketplaceDir);\n\n try {\n const written = await writeMarketplaceScaffold(marketplaceDir, marketplaceName);\n this.reportCreated(marketplaceName, marketplaceDir, written);\n this.reportNextSteps(marketplaceName, marketplaceDir);\n } catch (error) {\n this.handleError(error);\n }\n }\n\n /**\n * Both name rules `build marketplace` enforces, enforced here instead — failing\n * at creation beats failing at publish, when the name is already the directory,\n * the package name and the prefix on every id.\n */\n private refuseUnusableName(marketplaceName: string): void {\n const kebabError = validateKebabCaseName(marketplaceName, \"Marketplace\");\n if (kebabError) {\n this.error(kebabError, { exit: EXIT_CODES.INVALID_ARGS });\n }\n\n // The package.json this command is about to write names the marketplace after\n // itself, so the marketplace name IS the package name. That is what keeps the\n // public catalogue's exemption out of reach: it belongs to one npm package, and\n // nobody scaffolds that package from here.\n const reservedError = validateMarketplaceName(marketplaceName, marketplaceName);\n if (reservedError) {\n this.error(reservedError, { exit: EXIT_CODES.ERROR });\n }\n }\n\n private async refuseOccupiedDirectory(marketplaceDir: string): Promise<void> {\n if (await isDirectoryEmpty(marketplaceDir)) return;\n\n this.error(occupiedDirectoryError(marketplaceDir), { exit: EXIT_CODES.ERROR });\n }\n\n private reportCreated(\n marketplaceName: string,\n marketplaceDir: string,\n written: readonly string[],\n ): void {\n this.log(\"\");\n this.logSuccess(`Created marketplace '${marketplaceName}' at ${marketplaceDir}`);\n for (const relPath of written) {\n this.log(` ${relPath}`);\n }\n }\n\n private reportNextSteps(marketplaceName: string, marketplaceDir: string): void {\n const skillDir = path.join(SKILLS_DIR_PATH, exampleSkillId(marketplaceName));\n const steps = [\n `cd ${marketplaceName}`,\n \"Replace the placeholder 'author' in package.json with your own handle — it is what 'build marketplace' records as this marketplace's owner\",\n `Replace ${skillDir}/ with a skill of your own. Every skill id must begin with '${marketplaceName}-'`,\n `${CLI_INVOKE_COMMAND} build plugins`,\n `${CLI_INVOKE_COMMAND} build marketplace`,\n `${CLI_INVOKE_COMMAND} init --marketplace ${marketplaceDir} (from a project)`,\n ];\n\n this.log(\"\");\n this.log(\"Next steps:\");\n for (const [index, step] of steps.entries()) {\n this.log(` ${index + 1}. ${step}`);\n }\n this.log(\"\");\n }\n}\n","import path from \"path\";\n\nimport {\n DEFAULT_VERSION,\n SKILL_CATEGORIES_PATH,\n SKILL_RULES_PATH,\n SKILLS_DIR_PATH,\n STACKS_FILE_PATH,\n STANDARD_FILES,\n} from \"../consts.js\";\nimport { writeFile } from \"../utils/fs.js\";\n\n/** One file of a scaffolded marketplace: where it goes, and what it holds. */\ntype ScaffoldFile = {\n /** Path relative to the marketplace root. */\n relPath: string;\n contents: string;\n};\n\n/**\n * The bare id of the one skill a scaffold ships. The marketplace's own name is\n * prefixed onto it by {@link exampleSkillId} — this half is the same for every\n * marketplace, and on its own it belongs to none of them.\n */\nconst EXAMPLE_SKILL_SUFFIX = \"example-skill\";\n\n/**\n * The domain the scaffolded category sits under.\n *\n * `web` rather than a placeholder of its own: a domain is a member of a generated\n * union, and one invented for the scaffold appears in no wizard domain view and\n * reaches no sub-agent. It is also one of the domains a from-scratch selection\n * opens with, so the example skill is visible without the author choosing anything.\n */\nconst EXAMPLE_DOMAIN = \"web\";\n\n/**\n * The category the scaffold declares and places its example skill in, named by the\n * `<domain>-<name>` convention every category in the public catalogue follows. No\n * built-in category holds this key, so declaring it adds one rather than redefining\n * somebody else's.\n */\nconst EXAMPLE_CATEGORY = `${EXAMPLE_DOMAIN}-example`;\n\n/**\n * The slug the example skill answers to.\n *\n * Deliberately NOT namespaced: the marketplace-namespace rule governs skill ids,\n * and a marketplace's matrix is built from its own skills alone, so nothing in it\n * can collide with this. Prefixing it would settle a question nobody has ruled on.\n */\nconst EXAMPLE_SLUG = \"example-skill\";\n\n/** Where the scaffolded category sorts among the categories a marketplace grows later. */\nconst EXAMPLE_CATEGORY_ORDER = 1;\n\n/** The version a marketplace starts at — pre-release, because nothing is published yet. */\nconst INITIAL_PACKAGE_VERSION = \"0.1.0\";\n\n/** The sub-agent the scaffolded stack hands its skill to. */\nconst EXAMPLE_STACK_AGENT = \"web-developer\";\n\n/**\n * Composes the id of a scaffolded marketplace's example skill.\n *\n * Every skill id a marketplace publishes begins with that marketplace's name, so an\n * author who edits the scaffold rather than starting from a blank directory falls\n * into the namespace instead of having to remember it — `build marketplace` refuses\n * an id that lacks the prefix, and so does the load side.\n */\nexport function exampleSkillId(marketplaceName: string): string {\n return `${marketplaceName}-${EXAMPLE_SKILL_SUFFIX}`;\n}\n\n/**\n * Writes a complete marketplace into `marketplaceDir` and returns every path it\n * wrote, relative to that directory.\n *\n * What it writes is exactly what `docs/guides/creating-a-marketplace.md` promises a\n * marketplace holds — no more. It deliberately writes no `.claude-src/` pair: a\n * marketplace is a repository of skills, and a config manifest there would make\n * `doctor` diagnose an installation that does not exist.\n */\nexport async function writeMarketplaceScaffold(\n marketplaceDir: string,\n marketplaceName: string,\n): Promise<string[]> {\n const files = scaffoldFiles(marketplaceName);\n\n await Promise.all(\n files.map((file) => writeFile(path.join(marketplaceDir, file.relPath), file.contents)),\n );\n\n return files.map((file) => file.relPath);\n}\n\nfunction scaffoldFiles(marketplaceName: string): ScaffoldFile[] {\n const skillId = exampleSkillId(marketplaceName);\n const skillDir = path.join(SKILLS_DIR_PATH, skillId);\n\n return [\n { relPath: STANDARD_FILES.PACKAGE_JSON, contents: packageJson(marketplaceName) },\n { relPath: SKILL_CATEGORIES_PATH, contents: skillCategoriesModule() },\n { relPath: SKILL_RULES_PATH, contents: skillRulesModule() },\n { relPath: STACKS_FILE_PATH, contents: stacksModule(marketplaceName, skillId) },\n { relPath: path.join(skillDir, STANDARD_FILES.SKILL_MD), contents: skillMd(skillId) },\n {\n relPath: path.join(skillDir, STANDARD_FILES.METADATA_YAML),\n contents: metadataYaml(marketplaceName),\n },\n ];\n}\n\n/**\n * A `config/*.ts` module the CLI's loader can read.\n *\n * The loader unwraps a DEFAULT export and treats a module without one as no config\n * at all, so the export form here is load-bearing rather than stylistic.\n */\nfunction configModule(comment: string, data: unknown): string {\n return `${comment}\\nexport default ${JSON.stringify(data, null, 2)};\\n`;\n}\n\n/**\n * `build marketplace` reads a marketplace's whole identity out of package.json and\n * refuses a file missing any of name, version or description.\n *\n * `author` is the field this command cannot know, and it is written anyway —\n * omitting it is not the neutral choice it looks like. The build turns an absent\n * author into `owner: { name: \"\" }`, and the schema the CLI parses a\n * `marketplace.json` back with requires a non-empty owner name, so a scaffold with\n * no author builds a marketplace that then loads as no marketplace at all. The\n * marketplace's own handle is the least-wrong default: it is derived from what the\n * author typed rather than invented, and the next steps say to replace it.\n */\nfunction packageJson(marketplaceName: string): string {\n return `${JSON.stringify(\n {\n name: marketplaceName,\n version: INITIAL_PACKAGE_VERSION,\n description: `Skills curated for ${marketplaceName}'s conventions`,\n author: { name: authorHandle(marketplaceName) },\n },\n null,\n 2,\n )}\\n`;\n}\n\n/** The placeholder handle the marketplace publishes itself and its example skill under. */\nfunction authorHandle(marketplaceName: string): string {\n return `@${marketplaceName}`;\n}\n\nfunction skillCategoriesModule(): string {\n return configModule(\"// The categories this marketplace's skills fall into.\", {\n version: DEFAULT_VERSION,\n categories: {\n [EXAMPLE_CATEGORY]: {\n id: EXAMPLE_CATEGORY,\n displayName: \"Example\",\n description: \"Replace this with a category of your own\",\n domain: EXAMPLE_DOMAIN,\n exclusive: false,\n order: EXAMPLE_CATEGORY_ORDER,\n },\n },\n });\n}\n\n/**\n * A rules file with a version and nothing else.\n *\n * Relationship rules name skills by slug, and that slug reference is the CLI's own\n * generated union — so a marketplace cannot yet name its OWN skills in a rule, and\n * one that tries fails to load. A marketplace works fully without them; what it\n * loses is the wizard's incompatibility hints between its own skills.\n */\nfunction skillRulesModule(): string {\n return configModule(\n [\n \"// Relationships between skills — conflicts, requires, alternatives.\",\n \"//\",\n \"// Rules may only name skills the public catalogue carries; this marketplace's own\",\n \"// skills cannot be named here yet, and a rule that names one will not load. The\",\n \"// file still has to exist, so it ships with a version and no relationships.\",\n ].join(\"\\n\"),\n { version: DEFAULT_VERSION },\n );\n}\n\nfunction stacksModule(marketplaceName: string, skillId: string): string {\n return configModule(\"// The stacks this marketplace offers the wizard.\", {\n stacks: [\n {\n id: `${marketplaceName}-starter`,\n name: `${marketplaceName} starter`,\n description: `Every skill ${marketplaceName} ships`,\n agents: {\n [EXAMPLE_STACK_AGENT]: {\n [EXAMPLE_CATEGORY]: [{ id: skillId }],\n },\n },\n },\n ],\n });\n}\n\n/**\n * The skill's content, headed by the frontmatter the loader reads its machine id\n * out of. That id is also the directory name — `doctor` compares the two — so both\n * come from {@link exampleSkillId} rather than being spelled twice.\n */\nfunction skillMd(skillId: string): string {\n return `---\nname: ${skillId}\ndescription: A placeholder skill — replace it with one of your own\n---\n\n# Example Skill\n\nThis is where a skill's content goes: the conventions, patterns and rules you want\nevery sub-agent carrying it to follow.\n\nReplace this file and the \\`${STANDARD_FILES.METADATA_YAML}\\` beside it, and rename the\ndirectory to the id your skill should publish under.\n`;\n}\n\nfunction metadataYaml(marketplaceName: string): string {\n return [\n \"custom: true\",\n `domain: ${EXAMPLE_DOMAIN}`,\n `author: \"${authorHandle(marketplaceName)}\"`,\n \"displayName: Example Skill\",\n `category: ${EXAMPLE_CATEGORY}`,\n `slug: ${EXAMPLE_SLUG}`,\n 'cliDescription: \"A placeholder skill to replace with your own\"',\n 'usageGuidance: \"Use when you need a worked example of this marketplace\\'s skill shape\"',\n \"\",\n ].join(\"\\n\");\n}\n"],"mappings":"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","names":["path","path"]}
1
+ {"version":3,"sources":["../../../src/cli/commands/new/marketplace.ts","../../../src/cli/lib/marketplace-scaffold.ts"],"sourcesContent":["import { Args } from \"@oclif/core\";\nimport path from \"path\";\n\nimport { BaseCommand } from \"../../base-command.js\";\nimport { CLI_INVOKE_COMMAND, SKILLS_DIR_PATH } from \"../../consts.js\";\nimport { EXIT_CODES } from \"../../lib/exit-codes.js\";\nimport { validateMarketplaceName } from \"../../lib/marketplace-generator.js\";\nimport { exampleSkillId, writeMarketplaceScaffold } from \"../../lib/marketplace-scaffold.js\";\nimport { validateKebabCaseName } from \"../../lib/validate-kebab-name.js\";\nimport { isDirectoryEmpty } from \"../../utils/fs.js\";\n\n/**\n * How a reserved name is replaced here. The name is this command's argument, and nothing it could\n * be read from exists yet — no package.json, and no `--name` — so another argument is the way out.\n */\nconst RESERVED_NAME_WAY_OUT = `run '${CLI_INVOKE_COMMAND} new marketplace <other-name>'`;\n\n/**\n * The refusal over a target directory that already holds something.\n *\n * A scaffold writes a whole marketplace, so writing into an occupied directory\n * would mean merging with — or overwriting — work this command did not author.\n * Both ways out are one step, which is why there is no `--force`: a flag that\n * overwrites an author's own files is the destructive half of a silent fallback.\n */\nfunction occupiedDirectoryError(marketplaceDir: string): string {\n return (\n `${marketplaceDir} already holds files. A scaffold writes a whole marketplace, ` +\n `so it will not write into a directory that already has contents. Choose another ` +\n `name, or empty that directory first.`\n );\n}\n\nexport default class NewMarketplace extends BaseCommand {\n static summary = \"Scaffold a marketplace of your own\";\n\n static description =\n \"Create a directory holding everything the CLI needs to read a marketplace: a package.json \" +\n \"carrying its identity, the three config files, and one example skill already named in the \" +\n \"marketplace's own namespace. It scaffolds only — run 'build plugins' and 'build marketplace' \" +\n \"to publish what you have written.\";\n\n static examples = [\n {\n description: \"Scaffold a marketplace named acme into ./acme\",\n command: \"<%= config.bin %> <%= command.id %> acme\",\n },\n ];\n\n static args = {\n name: Args.string({\n description:\n \"Name of the marketplace (kebab-case). Becomes the directory, the package.json name, \" +\n \"and the prefix every skill id must carry.\",\n required: true,\n }),\n };\n\n async run(): Promise<void> {\n const { args } = await this.parse(NewMarketplace);\n const marketplaceName = args.name;\n const marketplaceDir = path.join(process.cwd(), marketplaceName);\n\n this.refuseUnusableName(marketplaceName);\n await this.refuseOccupiedDirectory(marketplaceDir);\n\n try {\n const written = await writeMarketplaceScaffold(marketplaceDir, marketplaceName);\n this.reportCreated(marketplaceName, marketplaceDir, written);\n this.reportNextSteps(marketplaceName, marketplaceDir);\n } catch (error) {\n this.handleError(error);\n }\n }\n\n /**\n * Both name rules `build marketplace` enforces, enforced here instead — failing\n * at creation beats failing at publish, when the name is already the directory,\n * the package name and the prefix on every id.\n */\n private refuseUnusableName(marketplaceName: string): void {\n const kebabError = validateKebabCaseName(marketplaceName, \"Marketplace\");\n if (kebabError) {\n this.error(kebabError, { exit: EXIT_CODES.INVALID_ARGS });\n }\n\n // The package.json this command is about to write names the marketplace after\n // itself, so the marketplace name IS the package name. That is what keeps the\n // public catalogue's exemption out of reach: it belongs to one npm package, and\n // nobody scaffolds that package from here.\n const reservedError = validateMarketplaceName(\n marketplaceName,\n marketplaceName,\n RESERVED_NAME_WAY_OUT,\n );\n if (reservedError) {\n this.error(reservedError, { exit: EXIT_CODES.ERROR });\n }\n }\n\n private async refuseOccupiedDirectory(marketplaceDir: string): Promise<void> {\n if (await isDirectoryEmpty(marketplaceDir)) return;\n\n this.error(occupiedDirectoryError(marketplaceDir), { exit: EXIT_CODES.ERROR });\n }\n\n private reportCreated(\n marketplaceName: string,\n marketplaceDir: string,\n written: readonly string[],\n ): void {\n this.log(\"\");\n this.logSuccess(`Created marketplace '${marketplaceName}' at ${marketplaceDir}`);\n for (const relPath of written) {\n this.log(` ${relPath}`);\n }\n }\n\n private reportNextSteps(marketplaceName: string, marketplaceDir: string): void {\n const skillDir = path.join(SKILLS_DIR_PATH, exampleSkillId(marketplaceName));\n const steps = [\n `cd ${marketplaceName}`,\n \"Replace the placeholder 'author' in package.json with your own handle — it is what 'build marketplace' records as this marketplace's owner\",\n `Replace ${skillDir}/ with a skill of your own. Every skill id must begin with '${marketplaceName}-'`,\n `${CLI_INVOKE_COMMAND} build plugins`,\n `${CLI_INVOKE_COMMAND} build marketplace`,\n `${CLI_INVOKE_COMMAND} init --marketplace ${marketplaceDir} (from a project)`,\n ];\n\n this.log(\"\");\n this.log(\"Next steps:\");\n for (const [index, step] of steps.entries()) {\n this.log(` ${index + 1}. ${step}`);\n }\n this.log(\"\");\n }\n}\n","import path from \"path\";\n\nimport {\n DEFAULT_VERSION,\n SKILL_CATEGORIES_PATH,\n SKILL_RULES_PATH,\n SKILLS_DIR_PATH,\n STACKS_FILE_PATH,\n STANDARD_FILES,\n} from \"../consts.js\";\nimport { writeFile } from \"../utils/fs.js\";\n\n/** One file of a scaffolded marketplace: where it goes, and what it holds. */\ntype ScaffoldFile = {\n /** Path relative to the marketplace root. */\n relPath: string;\n contents: string;\n};\n\n/**\n * The bare id of the one skill a scaffold ships. The marketplace's own name is\n * prefixed onto it by {@link exampleSkillId} — this half is the same for every\n * marketplace, and on its own it belongs to none of them.\n */\nconst EXAMPLE_SKILL_SUFFIX = \"example-skill\";\n\n/**\n * The domain the scaffolded category sits under.\n *\n * `web` rather than a placeholder of its own: a domain is a member of a generated\n * union, and one invented for the scaffold appears in no wizard domain view and\n * reaches no sub-agent. It is also one of the domains a from-scratch selection\n * opens with, so the example skill is visible without the author choosing anything.\n */\nconst EXAMPLE_DOMAIN = \"web\";\n\n/**\n * The category the scaffold declares and places its example skill in, named by the\n * `<domain>-<name>` convention every category in the public catalogue follows. No\n * built-in category holds this key, so declaring it adds one rather than redefining\n * somebody else's.\n */\nconst EXAMPLE_CATEGORY = `${EXAMPLE_DOMAIN}-example`;\n\n/**\n * The slug the example skill answers to.\n *\n * Deliberately NOT namespaced: the marketplace-namespace rule governs skill ids,\n * and a marketplace's matrix is built from its own skills alone, so nothing in it\n * can collide with this. Prefixing it would settle a question nobody has ruled on.\n */\nconst EXAMPLE_SLUG = \"example-skill\";\n\n/** Where the scaffolded category sorts among the categories a marketplace grows later. */\nconst EXAMPLE_CATEGORY_ORDER = 1;\n\n/** The version a marketplace starts at — pre-release, because nothing is published yet. */\nconst INITIAL_PACKAGE_VERSION = \"0.1.0\";\n\n/** The sub-agent the scaffolded stack hands its skill to. */\nconst EXAMPLE_STACK_AGENT = \"web-developer\";\n\n/**\n * Composes the id of a scaffolded marketplace's example skill.\n *\n * Every skill id a marketplace publishes begins with that marketplace's name, so an\n * author who edits the scaffold rather than starting from a blank directory falls\n * into the namespace instead of having to remember it — `build marketplace` refuses\n * an id that lacks the prefix, and so does the load side.\n */\nexport function exampleSkillId(marketplaceName: string): string {\n return `${marketplaceName}-${EXAMPLE_SKILL_SUFFIX}`;\n}\n\n/**\n * Writes a complete marketplace into `marketplaceDir` and returns every path it\n * wrote, relative to that directory.\n *\n * What it writes is exactly what `docs/guides/creating-a-marketplace.md` promises a\n * marketplace holds — no more. It deliberately writes no config pair: a\n * marketplace is a repository of skills, and a config manifest there would make\n * `doctor` diagnose an installation that does not exist.\n */\nexport async function writeMarketplaceScaffold(\n marketplaceDir: string,\n marketplaceName: string,\n): Promise<string[]> {\n const files = scaffoldFiles(marketplaceName);\n\n await Promise.all(\n files.map((file) => writeFile(path.join(marketplaceDir, file.relPath), file.contents)),\n );\n\n return files.map((file) => file.relPath);\n}\n\nfunction scaffoldFiles(marketplaceName: string): ScaffoldFile[] {\n const skillId = exampleSkillId(marketplaceName);\n const skillDir = path.join(SKILLS_DIR_PATH, skillId);\n\n return [\n { relPath: STANDARD_FILES.PACKAGE_JSON, contents: packageJson(marketplaceName) },\n { relPath: SKILL_CATEGORIES_PATH, contents: skillCategoriesModule() },\n { relPath: SKILL_RULES_PATH, contents: skillRulesModule() },\n { relPath: STACKS_FILE_PATH, contents: stacksModule(marketplaceName, skillId) },\n { relPath: path.join(skillDir, STANDARD_FILES.SKILL_MD), contents: skillMd(skillId) },\n {\n relPath: path.join(skillDir, STANDARD_FILES.METADATA_YAML),\n contents: metadataYaml(marketplaceName),\n },\n ];\n}\n\n/**\n * A `config/*.ts` module the CLI's loader can read.\n *\n * The loader unwraps a DEFAULT export and treats a module without one as no config\n * at all, so the export form here is load-bearing rather than stylistic.\n */\nfunction configModule(comment: string, data: unknown): string {\n return `${comment}\\nexport default ${JSON.stringify(data, null, 2)};\\n`;\n}\n\n/**\n * `build marketplace` reads a marketplace's whole identity out of package.json and\n * refuses a file missing any of name, version or description.\n *\n * `author` is the field this command cannot know, and it is written anyway —\n * omitting it is not the neutral choice it looks like. The build turns an absent\n * author into `owner: { name: \"\" }`, and the schema the CLI parses a\n * `marketplace.json` back with requires a non-empty owner name, so a scaffold with\n * no author builds a marketplace that then loads as no marketplace at all. The\n * marketplace's own handle is the least-wrong default: it is derived from what the\n * author typed rather than invented, and the next steps say to replace it.\n */\nfunction packageJson(marketplaceName: string): string {\n return `${JSON.stringify(\n {\n name: marketplaceName,\n version: INITIAL_PACKAGE_VERSION,\n description: `Skills curated for ${marketplaceName}'s conventions`,\n author: { name: authorHandle(marketplaceName) },\n },\n null,\n 2,\n )}\\n`;\n}\n\n/** The placeholder handle the marketplace publishes itself and its example skill under. */\nfunction authorHandle(marketplaceName: string): string {\n return `@${marketplaceName}`;\n}\n\nfunction skillCategoriesModule(): string {\n return configModule(\"// The categories this marketplace's skills fall into.\", {\n version: DEFAULT_VERSION,\n categories: {\n [EXAMPLE_CATEGORY]: {\n id: EXAMPLE_CATEGORY,\n displayName: \"Example\",\n description: \"Replace this with a category of your own\",\n domain: EXAMPLE_DOMAIN,\n exclusive: false,\n order: EXAMPLE_CATEGORY_ORDER,\n },\n },\n });\n}\n\n/**\n * A rules file with a version and nothing else.\n *\n * Relationship rules name skills by slug, and that slug reference is the CLI's own\n * generated union — so a marketplace cannot yet name its OWN skills in a rule, and\n * one that tries fails to load. A marketplace works fully without them; what it\n * loses is the wizard's incompatibility hints between its own skills.\n */\nfunction skillRulesModule(): string {\n return configModule(\n [\n \"// Relationships between skills — conflicts, requires, alternatives.\",\n \"//\",\n \"// Rules may only name skills the public catalogue carries; this marketplace's own\",\n \"// skills cannot be named here yet, and a rule that names one will not load. The\",\n \"// file still has to exist, so it ships with a version and no relationships.\",\n ].join(\"\\n\"),\n { version: DEFAULT_VERSION },\n );\n}\n\nfunction stacksModule(marketplaceName: string, skillId: string): string {\n return configModule(\"// The stacks this marketplace offers the wizard.\", {\n stacks: [\n {\n id: `${marketplaceName}-starter`,\n name: `${marketplaceName} starter`,\n description: `Every skill ${marketplaceName} ships`,\n agents: {\n [EXAMPLE_STACK_AGENT]: {\n [EXAMPLE_CATEGORY]: [{ id: skillId }],\n },\n },\n },\n ],\n });\n}\n\n/**\n * The skill's content, headed by the frontmatter the loader reads its machine id\n * out of. That id is also the directory name — `doctor` compares the two — so both\n * come from {@link exampleSkillId} rather than being spelled twice.\n */\nfunction skillMd(skillId: string): string {\n return `---\nname: ${skillId}\ndescription: A placeholder skill — replace it with one of your own\n---\n\n# Example Skill\n\nThis is where a skill's content goes: the conventions, patterns and rules you want\nevery sub-agent carrying it to follow.\n\nReplace this file and the \\`${STANDARD_FILES.METADATA_YAML}\\` beside it, and rename the\ndirectory to the id your skill should publish under.\n`;\n}\n\nfunction metadataYaml(marketplaceName: string): string {\n return [\n \"custom: true\",\n `domain: ${EXAMPLE_DOMAIN}`,\n `author: \"${authorHandle(marketplaceName)}\"`,\n \"displayName: Example Skill\",\n `category: ${EXAMPLE_CATEGORY}`,\n `slug: ${EXAMPLE_SLUG}`,\n 'cliDescription: \"A placeholder skill to replace with your own\"',\n 'usageGuidance: \"Use when you need a worked example of this marketplace\\'s skill shape\"',\n \"\",\n ].join(\"\\n\");\n}\n"],"mappings":"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","names":["path","path"]}
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadSource
4
- } from "../chunk-X37UYIE3.js";
5
- import "../chunk-2CQNPLVF.js";
6
- import "../chunk-B6FSLXYN.js";
7
- import "../chunk-JU4ZGJ7A.js";
4
+ } from "../chunk-G6UREQ4V.js";
5
+ import "../chunk-HWEKHVLE.js";
6
+ import "../chunk-PMJWD27C.js";
7
+ import "../chunk-ZR4YPKXQ.js";
8
+ import "../chunk-GE3WKJ6Q.js";
8
9
  import {
9
10
  BaseCommand
10
- } from "../chunk-JIERYE6B.js";
11
- import "../chunk-NKGYKOB7.js";
12
- import "../chunk-PHCPUQVR.js";
11
+ } from "../chunk-Y3VIRVVE.js";
12
+ import "../chunk-VAPTVR3J.js";
13
+ import "../chunk-RZDXDMT6.js";
13
14
  import {
14
15
  STATUS_MESSAGES,
15
16
  stripTerminalControls,
16
17
  truncateText
17
- } from "../chunk-XPDVAJDH.js";
18
+ } from "../chunk-A4KDXNOT.js";
18
19
  import {
19
20
  typedValues
20
- } from "../chunk-AVK2HEKB.js";
21
- import "../chunk-UITEKY2R.js";
21
+ } from "../chunk-V5WXIM2F.js";
22
+ import "../chunk-ENGYJCTF.js";
22
23
  import "../chunk-OI2H6M5I.js";
23
24
 
24
25
  // src/cli/commands/search.ts
@@ -48,6 +49,7 @@ var Search = class _Search extends BaseCommand {
48
49
  static flags = {};
49
50
  async run() {
50
51
  const { args } = await this.parse(_Search);
52
+ await this.ensureConfigReadable(process.cwd());
51
53
  await this.runSearch(args.query);
52
54
  }
53
55
  async runSearch(query) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/commands/search.ts"],"sourcesContent":["import { Args } from \"@oclif/core\";\nimport { printTable } from \"@oclif/table\";\nimport { sortBy } from \"remeda\";\n\nimport { BaseCommand } from \"../base-command.js\";\nimport { loadSource } from \"../lib/operations/index.js\";\nimport type { ResolvedSkill } from \"../types/index.js\";\nimport { STATUS_MESSAGES } from \"../utils/messages.js\";\nimport { stripTerminalControls, truncateText } from \"../utils/string.js\";\nimport { typedValues } from \"../utils/typed-object.js\";\n\nconst MAX_DESCRIPTION_WIDTH = 50;\n\n/**\n * One row of the results table. Every column is rendered for its cell, so all five are plain\n * strings — including the two that used to be carried through as the skill's own `SkillId` and\n * `CategoryPath`.\n *\n * They stopped being the skill's own values when they started being sanitised, and the type says\n * so rather than casting the sanitised string back into a union it is no longer known to be in.\n * Nothing downstream of this looks a row up by id: it is five strings and a table.\n */\ntype ResultRow = {\n id: string;\n name: string;\n source: string;\n category: string;\n description: string;\n};\n\nexport default class Search extends BaseCommand {\n static summary = \"Search the catalog of available skills\";\n static description =\n \"Read-only catalog browse. Searches the marketplace this installation reads from, \" +\n \"plus the local skills already on disk, by id, displayName, slug, description, or \" +\n \"category. Use `edit` to install what it finds.\";\n\n static examples = [\n {\n description: \"Search for React skills\",\n command: \"<%= config.bin %> search react\",\n },\n {\n description: \"Search by any keyword (matches id, name, description, category)\",\n command: \"<%= config.bin %> search state\",\n },\n ];\n\n static args = {\n query: Args.string({\n description: \"Search query (matches id, displayName, slug, description, category)\",\n required: true,\n }),\n };\n\n static flags = {};\n\n async run(): Promise<void> {\n const { args } = await this.parse(Search);\n await this.runSearch(args.query);\n }\n\n private async runSearch(query: string): Promise<void> {\n try {\n this.log(STATUS_MESSAGES.LOADING_SKILLS);\n\n const allSkills = await loadSearchableSkills();\n\n const results = sortBy(\n allSkills.filter((skill) => matchesQuery(skill, query)),\n (r) => r.displayName.toLowerCase(),\n );\n\n this.log(\"\");\n if (results.length === 0) {\n this.warn(`No skills found matching \"${query}\"`);\n return;\n }\n\n this.logInfo(\n `Found ${results.length} skill${results.length === 1 ? \"\" : \"s\"} matching \"${query}\"`,\n );\n this.log(\"\");\n\n printTable({\n data: results.map(toResultRow),\n columns: [\n { key: \"id\", name: \"ID\" },\n { key: \"name\", name: \"Name\" },\n { key: \"source\", name: \"Origin\" },\n { key: \"category\", name: \"Category\" },\n { key: \"description\", name: \"Description\" },\n ],\n headerOptions: { bold: true },\n });\n this.log(\"\");\n } catch (error) {\n this.handleError(error);\n }\n }\n}\n\n/**\n * Every skill the catalog can offer: the marketplace matrix, which `loadSource` has already\n * merged the on-disk local skills into. There is no second source to reach for — the\n * registered-extras array this used to fan out over was withdrawn with the marketplace axis\n * itself, so the read is one load and no network beyond it.\n */\nasync function loadSearchableSkills(): Promise<ResolvedSkill[]> {\n const { sourceResult } = await loadSource({ projectDir: process.cwd() });\n\n return typedValues(sourceResult.matrix.skills);\n}\n\n/**\n * The machine id and the display name are different answers and get a column each — `edit`\n * takes the id, a human reads the name.\n *\n * Every one of the five is a string a catalogue author wrote, and this is where they stop being\n * that and become cells. `--marketplace` is a supported input, so a stranger's repository reaches\n * this table by the product's advertised route: an unsanitised `displayName` carrying an\n * erase-line and a carriage return repaints the row, and `@oclif/table` sizes the column on the\n * escape bytes as well, so the border moves too. Only `description` carries a budget, because\n * only `description` is prose; the other four are identifiers and are shown whole.\n */\nfunction toResultRow(skill: ResolvedSkill): ResultRow {\n return {\n id: stripTerminalControls(skill.id),\n name: stripTerminalControls(skill.displayName),\n source: stripTerminalControls(activeSourceName(skill)),\n category: stripTerminalControls(skill.category),\n description: truncateText(skill.description, MAX_DESCRIPTION_WIDTH),\n };\n}\n\n/**\n * Where this installation reads the skill from: the local copy when one is on disk, otherwise\n * the marketplace the load resolved. Both names are written by the tagging pass\n * (`loadSkillsFromAllSources`), which `loadSource` always runs — a skill that reached the table\n * untagged would have no origin to report, so say so rather than name one it might not have.\n */\nfunction activeSourceName(skill: ResolvedSkill): string {\n const { activeSource } = skill;\n if (!activeSource) {\n throw new Error(\n `Skill \"${skill.id}\" was loaded with no origin, so where it comes from is unknown`,\n );\n }\n return activeSource.name;\n}\n\nfunction matchesQuery(skill: ResolvedSkill, query: string): boolean {\n const lowerQuery = query.toLowerCase();\n return [skill.id, skill.displayName, skill.slug, skill.description, skill.category].some(\n (field) => field.toLowerCase().includes(lowerQuery),\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/commands/search.ts"],"sourcesContent":["import { Args } from \"@oclif/core\";\nimport { printTable } from \"@oclif/table\";\nimport { sortBy } from \"remeda\";\n\nimport { BaseCommand } from \"../base-command.js\";\nimport { loadSource } from \"../lib/operations/index.js\";\nimport type { ResolvedSkill } from \"../types/index.js\";\nimport { STATUS_MESSAGES } from \"../utils/messages.js\";\nimport { stripTerminalControls, truncateText } from \"../utils/string.js\";\nimport { typedValues } from \"../utils/typed-object.js\";\n\nconst MAX_DESCRIPTION_WIDTH = 50;\n\n/**\n * One row of the results table. Every column is rendered for its cell, so all five are plain\n * strings — including the two that used to be carried through as the skill's own `SkillId` and\n * `CategoryPath`.\n *\n * They stopped being the skill's own values when they started being sanitised, and the type says\n * so rather than casting the sanitised string back into a union it is no longer known to be in.\n * Nothing downstream of this looks a row up by id: it is five strings and a table.\n */\ntype ResultRow = {\n id: string;\n name: string;\n source: string;\n category: string;\n description: string;\n};\n\nexport default class Search extends BaseCommand {\n static summary = \"Search the catalog of available skills\";\n static description =\n \"Read-only catalog browse. Searches the marketplace this installation reads from, \" +\n \"plus the local skills already on disk, by id, displayName, slug, description, or \" +\n \"category. Use `edit` to install what it finds.\";\n\n static examples = [\n {\n description: \"Search for React skills\",\n command: \"<%= config.bin %> search react\",\n },\n {\n description: \"Search by any keyword (matches id, name, description, category)\",\n command: \"<%= config.bin %> search state\",\n },\n ];\n\n static args = {\n query: Args.string({\n description: \"Search query (matches id, displayName, slug, description, category)\",\n required: true,\n }),\n };\n\n static flags = {};\n\n async run(): Promise<void> {\n const { args } = await this.parse(Search);\n // The marketplace searched is the one the configs name, so a config nobody can read is not\n // read past to whichever marketplace the next rung would answer.\n await this.ensureConfigReadable(process.cwd());\n await this.runSearch(args.query);\n }\n\n private async runSearch(query: string): Promise<void> {\n try {\n this.log(STATUS_MESSAGES.LOADING_SKILLS);\n\n const allSkills = await loadSearchableSkills();\n\n const results = sortBy(\n allSkills.filter((skill) => matchesQuery(skill, query)),\n (r) => r.displayName.toLowerCase(),\n );\n\n this.log(\"\");\n if (results.length === 0) {\n this.warn(`No skills found matching \"${query}\"`);\n return;\n }\n\n this.logInfo(\n `Found ${results.length} skill${results.length === 1 ? \"\" : \"s\"} matching \"${query}\"`,\n );\n this.log(\"\");\n\n printTable({\n data: results.map(toResultRow),\n columns: [\n { key: \"id\", name: \"ID\" },\n { key: \"name\", name: \"Name\" },\n { key: \"source\", name: \"Origin\" },\n { key: \"category\", name: \"Category\" },\n { key: \"description\", name: \"Description\" },\n ],\n headerOptions: { bold: true },\n });\n this.log(\"\");\n } catch (error) {\n this.handleError(error);\n }\n }\n}\n\n/**\n * Every skill the catalog can offer: the marketplace matrix, which `loadSource` has already\n * merged the on-disk local skills into. There is no second source to reach for — the\n * registered-extras array this used to fan out over was withdrawn with the marketplace axis\n * itself, so the read is one load and no network beyond it.\n */\nasync function loadSearchableSkills(): Promise<ResolvedSkill[]> {\n const { sourceResult } = await loadSource({ projectDir: process.cwd() });\n\n return typedValues(sourceResult.matrix.skills);\n}\n\n/**\n * The machine id and the display name are different answers and get a column each — `edit`\n * takes the id, a human reads the name.\n *\n * Every one of the five is a string a catalogue author wrote, and this is where they stop being\n * that and become cells. `--marketplace` is a supported input, so a stranger's repository reaches\n * this table by the product's advertised route: an unsanitised `displayName` carrying an\n * erase-line and a carriage return repaints the row, and `@oclif/table` sizes the column on the\n * escape bytes as well, so the border moves too. Only `description` carries a budget, because\n * only `description` is prose; the other four are identifiers and are shown whole.\n */\nfunction toResultRow(skill: ResolvedSkill): ResultRow {\n return {\n id: stripTerminalControls(skill.id),\n name: stripTerminalControls(skill.displayName),\n source: stripTerminalControls(activeSourceName(skill)),\n category: stripTerminalControls(skill.category),\n description: truncateText(skill.description, MAX_DESCRIPTION_WIDTH),\n };\n}\n\n/**\n * Where this installation reads the skill from: the local copy when one is on disk, otherwise\n * the marketplace the load resolved. Both names are written by the tagging pass\n * (`loadSkillsFromAllSources`), which `loadSource` always runs — a skill that reached the table\n * untagged would have no origin to report, so say so rather than name one it might not have.\n */\nfunction activeSourceName(skill: ResolvedSkill): string {\n const { activeSource } = skill;\n if (!activeSource) {\n throw new Error(\n `Skill \"${skill.id}\" was loaded with no origin, so where it comes from is unknown`,\n );\n }\n return activeSource.name;\n}\n\nfunction matchesQuery(skill: ResolvedSkill, query: string): boolean {\n const lowerQuery = query.toLowerCase();\n return [skill.id, skill.displayName, skill.slug, skill.description, skill.category].some(\n (field) => field.toLowerCase().includes(lowerQuery),\n );\n}\n"],"mappings":"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","names":[]}
@@ -2,25 +2,41 @@
2
2
  import {
3
3
  publishSeedConfig,
4
4
  seedPayloadForInstallation
5
- } from "../chunk-NTF4UXGK.js";
6
- import "../chunk-X4SSJZLY.js";
5
+ } from "../chunk-Y47EV74W.js";
6
+ import {
7
+ unofferablePlacementsFound
8
+ } from "../chunk-PXWG7KFO.js";
9
+ import "../chunk-25MPBIK3.js";
10
+ import "../chunk-GE3WKJ6Q.js";
11
+ import {
12
+ providerFlag,
13
+ providerNamedBy,
14
+ refuseAnAmbiguousInstallation
15
+ } from "../chunk-NBXXFIIW.js";
7
16
  import {
8
17
  BaseCommand,
9
18
  EXIT_CODES
10
- } from "../chunk-JIERYE6B.js";
11
- import "../chunk-NKGYKOB7.js";
12
- import "../chunk-PHCPUQVR.js";
19
+ } from "../chunk-Y3VIRVVE.js";
20
+ import "../chunk-VAPTVR3J.js";
21
+ import "../chunk-RZDXDMT6.js";
13
22
  import {
14
23
  formatZodErrors,
15
24
  getErrorMessage,
25
+ isLocalSource,
26
+ shareRefusesEjectedSkills,
16
27
  sharedConfigDestinations,
28
+ sharedIdNamesAFolder,
29
+ sharedSkillsLeftBehind,
17
30
  stripTerminalControls,
18
- truncateText
19
- } from "../chunk-XPDVAJDH.js";
20
- import "../chunk-AVK2HEKB.js";
31
+ truncateText,
32
+ warn
33
+ } from "../chunk-A4KDXNOT.js";
34
+ import {
35
+ typedEntries
36
+ } from "../chunk-V5WXIM2F.js";
21
37
  import {
22
38
  installableSeedPayloadSchema
23
- } from "../chunk-UITEKY2R.js";
39
+ } from "../chunk-ENGYJCTF.js";
24
40
  import "../chunk-OI2H6M5I.js";
25
41
 
26
42
  // src/cli/commands/share.ts
@@ -72,6 +88,19 @@ async function readAllOf(stream) {
72
88
  }
73
89
 
74
90
  // src/cli/commands/share.ts
91
+ function caveatsAbout(payload, leftBehind) {
92
+ const { marketplace } = payload;
93
+ return [
94
+ ...isAFolder(marketplace) ? [sharedIdNamesAFolder(marketplace)] : [],
95
+ ...leftBehind.length > 0 ? [sharedSkillsLeftBehind(leftBehind)] : []
96
+ ];
97
+ }
98
+ function isAFolder(marketplace) {
99
+ return marketplace !== void 0 && isLocalSource(marketplace);
100
+ }
101
+ function ejectedSkillsIn(payload) {
102
+ return typedEntries(payload.skills).filter(([, skill]) => skill.install === "eject").map(([id]) => id);
103
+ }
75
104
  var Share = class _Share extends BaseCommand {
76
105
  static summary = "Share this installation as an id anyone can install";
77
106
  static description = "Turns the skills, sub-agents and per-agent curation installed here into a configuration the agentsinc.sh store holds, and prints the id it was given. Install it elsewhere with 'init --from <id>', or open it in the editor. The id is the configuration's own hash, so sharing an unchanged installation returns the id it already had.";
@@ -79,14 +108,15 @@ var Share = class _Share extends BaseCommand {
79
108
  stdin: Flags.boolean({
80
109
  description: "Share a configuration piped in on standard input instead of the one installed here",
81
110
  default: false
82
- })
111
+ }),
112
+ provider: providerFlag()
83
113
  };
84
114
  static examples = [
85
115
  "<%= config.bin %> <%= command.id %>",
86
116
  "cat proposal.json | <%= config.bin %> <%= command.id %> --stdin"
87
117
  ];
88
118
  /**
89
- * Read, map, refuse, publish.
119
+ * Read, map, refuse, publish, then say what the id will not do everywhere.
90
120
  *
91
121
  * Everything that can fail locally fails before the POST. The store's free tier allows a
92
122
  * thousand writes a day and reads a hundred times that, so a write is the scarce half — and one
@@ -94,24 +124,79 @@ var Share = class _Share extends BaseCommand {
94
124
  *
95
125
  * The first three steps are `seedPayloadForInstallation`, shared with `edit --ui`: the two
96
126
  * commands mint the same id from the same directory and differ only in what they do with it.
127
+ * The one refusal `edit --ui` does not share is of ejected skills — a share carries plugins
128
+ * only, and the editor is where a user goes to turn ejected skills into plugins.
97
129
  */
98
130
  async run() {
99
131
  const { flags } = await this.parse(_Share);
100
- const payload = flags.stdin ? await this.payloadFromPipe() : await this.payloadFromInstallation();
132
+ if (!flags.stdin) {
133
+ await refuseAnAmbiguousInstallation(
134
+ process.cwd(),
135
+ "share",
136
+ providerNamedBy(flags.provider),
137
+ (message) => this.error(message, { exit: EXIT_CODES.INVALID_ARGS })
138
+ );
139
+ await this.ensureConfigReadable(process.cwd());
140
+ await this.refuseUnofferablePlacements(process.cwd());
141
+ }
142
+ const { payload, caveats } = flags.stdin ? await this.payloadFromPipe() : await this.payloadFromInstallation();
101
143
  const published = await publishSeedConfig(payload);
102
144
  if (!published.ok) {
103
145
  this.error(published.error, { exit: EXIT_CODES.ERROR });
104
146
  }
105
147
  this.reportShared(published.id);
148
+ for (const caveat of caveats) warn(caveat);
149
+ }
150
+ /**
151
+ * Refuses a configuration asking for a mode/scope cell its own host does not offer, before the
152
+ * payload is built and long before the POST.
153
+ *
154
+ * **`share` is a read path, and the roster that names them missed it.** The list this refusal
155
+ * was built against reads "`compile`, `edit`, `update` and `doctor`" and claims to be every
156
+ * command that acts on a configuration it did not just write — while `share` sits beside them
157
+ * reading the same file through `seedPayloadForInstallation`. What it publishes is described as
158
+ * "the skills, sub-agents and per-agent curation installed HERE", and a row the host could not
159
+ * place was never installed here at all, so the id would describe an installation that does not
160
+ * exist. `refuseAnAmbiguousInstallation` above is this finding's sibling and was already asked;
161
+ * the two are the pair `doctor`'s Placements row reports together.
162
+ *
163
+ * A refusal rather than a row, like the other three writers, and for a reason of its own: the
164
+ * store's write is the scarce half, and one spent on an installation nothing on this machine
165
+ * will act on buys a link to a misdescription.
166
+ *
167
+ * `uninstall` reads the same file and is deliberately NOT on this roster: a refusal there would
168
+ * make an unofferable installation unremovable, which is a guard that has swallowed its domain.
169
+ */
170
+ async refuseUnofferablePlacements(cwd) {
171
+ const [finding] = await unofferablePlacementsFound(cwd);
172
+ if (finding !== void 0) this.error(finding, { exit: EXIT_CODES.ERROR });
106
173
  }
107
- /** The installation in this directory, mapped and announced. */
174
+ /** The installation in this directory: mapped, refused if it holds ejected skills, announced. */
108
175
  async payloadFromInstallation() {
109
176
  const prepared = await seedPayloadForInstallation(process.cwd());
110
177
  if (!prepared.ok) {
111
178
  this.error(prepared.error, { exit: EXIT_CODES.ERROR });
112
179
  }
180
+ this.refuseEjectedSkills(prepared.payload);
113
181
  this.log(`Sharing ${prepared.skills} skill(s) across ${prepared.agents} sub-agent(s)...`);
114
- return prepared.payload;
182
+ return {
183
+ payload: prepared.payload,
184
+ caveats: caveatsAbout(prepared.payload, prepared.leftBehind)
185
+ };
186
+ }
187
+ /**
188
+ * Refuses a share while anything it would send is an ejected (Local) skill.
189
+ *
190
+ * An ejected skill is a copy on this machine, which the user may have edited and an
191
+ * editor-added skill always is, so a share carries plugins only — from a project, that covers
192
+ * the ejected skills it inherits from the global installation as well as its own. A skill the
193
+ * user wrote by hand is never in the payload, so it neither travels nor refuses.
194
+ */
195
+ refuseEjectedSkills(payload) {
196
+ const ejected = ejectedSkillsIn(payload);
197
+ if (ejected.length > 0) {
198
+ this.error(shareRefusesEjectedSkills(ejected), { exit: EXIT_CODES.ERROR });
199
+ }
115
200
  }
116
201
  /**
117
202
  * A configuration the CALLER holds, which is why nothing here reads an installation.
@@ -126,6 +211,8 @@ var Share = class _Share extends BaseCommand {
126
211
  * the editor, which reads `?fromId=` and nothing else. Publishing from here rather than from
127
212
  * the producer keeps `SEED_VERSION`, the `AGENTS_INC_API_URL` override and the caller's
128
213
  * user-agent in the one place that owns them.
214
+ *
215
+ * It owes no caveats: those describe an installation, and none was read.
129
216
  */
130
217
  async payloadFromPipe() {
131
218
  if (process.stdin.isTTY) {
@@ -135,7 +222,7 @@ var Share = class _Share extends BaseCommand {
135
222
  if (!read.ok) {
136
223
  this.error(read.error, { exit: EXIT_CODES.ERROR });
137
224
  }
138
- return read.payload;
225
+ return { payload: read.payload, caveats: [] };
139
226
  }
140
227
  /**
141
228
  * Both destinations, because an id nobody can act on is not a share and there are exactly two
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/commands/share.ts","../../src/cli/lib/seed/read-piped-payload.ts","../../src/cli/utils/read-stream.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\n\nimport { BaseCommand } from \"../base-command.js\";\nimport { EXIT_CODES } from \"../lib/exit-codes.js\";\nimport { seedPayloadForInstallation } from \"../lib/seed/installation-payload.js\";\nimport { publishSeedConfig } from \"../lib/seed/publish-seed.js\";\nimport {\n readPipedPayload,\n STDIN_IS_A_TERMINAL,\n type PipedPayload,\n} from \"../lib/seed/read-piped-payload.js\";\nimport { readAllOf } from \"../utils/read-stream.js\";\nimport { sharedConfigDestinations } from \"../utils/messages.js\";\nimport type { SeedPayload } from \"@workspace/matrix/seed\";\n\nexport default class Share extends BaseCommand {\n static summary = \"Share this installation as an id anyone can install\";\n\n static description =\n \"Turns the skills, sub-agents and per-agent curation installed here into a configuration the agentsinc.sh store holds, and prints the id it was given. Install it elsewhere with 'init --from <id>', or open it in the editor. The id is the configuration's own hash, so sharing an unchanged installation returns the id it already had.\";\n\n static flags = {\n stdin: Flags.boolean({\n description:\n \"Share a configuration piped in on standard input instead of the one installed here\",\n default: false,\n }),\n };\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"cat proposal.json | <%= config.bin %> <%= command.id %> --stdin\",\n ];\n\n /**\n * Read, map, refuse, publish.\n *\n * Everything that can fail locally fails before the POST. The store's free tier allows a\n * thousand writes a day and reads a hundred times that, so a write is the scarce half — and one\n * spent on a configuration that cannot be installed buys a dead link.\n *\n * The first three steps are `seedPayloadForInstallation`, shared with `edit --ui`: the two\n * commands mint the same id from the same directory and differ only in what they do with it.\n */\n async run(): Promise<void> {\n const { flags } = await this.parse(Share);\n\n const payload = flags.stdin\n ? await this.payloadFromPipe()\n : await this.payloadFromInstallation();\n\n const published = await publishSeedConfig(payload);\n if (!published.ok) {\n this.error(published.error, { exit: EXIT_CODES.ERROR });\n }\n\n this.reportShared(published.id);\n }\n\n /** The installation in this directory, mapped and announced. */\n private async payloadFromInstallation(): Promise<SeedPayload> {\n const prepared = await seedPayloadForInstallation(process.cwd());\n if (!prepared.ok) {\n this.error(prepared.error, { exit: EXIT_CODES.ERROR });\n }\n\n this.log(`Sharing ${prepared.skills} skill(s) across ${prepared.agents} sub-agent(s)...`);\n\n return prepared.payload;\n }\n\n /**\n * A configuration the CALLER holds, which is why nothing here reads an installation.\n *\n * That is the whole distinction the flag draws, and it is not cosmetic: a bare `share` resolves\n * an installation the way every other command does — this project, then the global one — so\n * without this branch, sharing a piped payload from a directory with nothing in it would\n * publish whatever the machine happens to have installed globally.\n *\n * The producer this exists for is not this CLI. `meta-config-stack-detect` walks a repository\n * and emits a `SeedPayload` it is forbidden to write or apply, and an id is the only door into\n * the editor, which reads `?fromId=` and nothing else. Publishing from here rather than from\n * the producer keeps `SEED_VERSION`, the `AGENTS_INC_API_URL` override and the caller's\n * user-agent in the one place that owns them.\n */\n private async payloadFromPipe(): Promise<SeedPayload> {\n if (process.stdin.isTTY) {\n this.error(STDIN_IS_A_TERMINAL, { exit: EXIT_CODES.ERROR });\n }\n\n const read: PipedPayload = readPipedPayload(await readAllOf(process.stdin));\n if (!read.ok) {\n this.error(read.error, { exit: EXIT_CODES.ERROR });\n }\n\n return read.payload;\n }\n\n /**\n * Both destinations, because an id nobody can act on is not a share and there are exactly two\n * things that read one: this CLI, and the editor the configuration can be reopened in. The\n * lines themselves are `sharedConfigDestinations`, shared with `edit --ui` for the same reason\n * the mint above is.\n */\n private reportShared(id: string): void {\n this.logSuccess(`Shared as ${id}`);\n for (const destination of sharedConfigDestinations(id)) {\n this.log(destination);\n }\n }\n}\n","import { installableSeedPayloadSchema, type SeedPayload } from \"@workspace/matrix/seed\";\n\nimport { formatZodErrors } from \"../schema-validator.js\";\nimport { getErrorMessage } from \"../../utils/errors.js\";\nimport { stripTerminalControls, truncateText } from \"../../utils/string.js\";\n\n/** A payload read from a pipe, or the reason it could not be. */\nexport type PipedPayload =\n | { readonly ok: true; readonly payload: SeedPayload }\n | { readonly ok: false; readonly error: string };\n\nexport const NOTHING_PIPED =\n \"--stdin expects a configuration on standard input and none arrived. Pipe one in, as 'cat proposal.json | agents-inc share --stdin'.\";\n\nexport const STDIN_IS_A_TERMINAL =\n \"--stdin expects a configuration on standard input, but standard input is a terminal. Pipe one in rather than typing it.\";\n\n/**\n * How much of a body to quote back when it is not JSON, so a refusal names what it read.\n *\n * A CHARACTER bound, because what it clips is arbitrary bytes a producer piped by mistake: any\n * cut is as good as any other, and the point is only that the caller recognises what they sent.\n */\nconst EXCERPT = 80;\n\n/**\n * How many problems to name before the rest are counted instead.\n *\n * The scope rule raises one issue per (skill, sub-agent) PAIR rather than one per payload, and\n * each renders at roughly 150 characters — the same sentence every time, with two names changed.\n * A repository scan is 20-40 skills across 3-6 sub-agents, so an unbounded list is 60-240 of them\n * and hundreds of lines: the refusal becomes the wall of text it is complaining about.\n *\n * Four, so the whole message lands near twice the 300-character `EXPLANATION_BUDGET` in\n * `publish-seed.ts`, which bounds the store's account of this same refusal arriving over the\n * wire. Twice, because that one quotes back a remote explanation of a payload already sent, where\n * these are the caller's own pairs and each name is somewhere they have to go and edit — and four\n * of them is enough to show the mistake is a pattern rather than a one-off.\n *\n * A COUNT rather than a `truncateText` over the joined list, which is the one place this file's\n * two bounds differ. A character cut lands mid-sentence, and mid-sentence here means a half-named\n * skill or sub-agent — worse than one left out and counted, because it reads as a name.\n */\nconst PROBLEMS_NAMED = 4;\n\n/**\n * Everything that can fail locally, failing before the caller spends a write.\n *\n * The store's free tier allows a thousand writes a day against a hundred times that in reads, so\n * a write is the scarce half — and one spent on a payload the decoder cannot read buys a dead\n * link. Each of the three refusals below is a different mistake and says which: nothing arrived,\n * what arrived is not JSON, or it is JSON the contract does not accept.\n *\n * `installableSeedPayloadSchema` rather than a shape written here, and that is the point of\n * publishing from the CLI at all. `SEED_VERSION` is a `z.literal`, so a producer that hardcodes\n * the wire format emits payloads the store refuses the day it moves; the schema travels with the\n * code that posts.\n *\n * THE *INSTALLABLE* SCHEMA, AND THE DIFFERENCE IS THE WHOLE BUG THIS CLOSED. The worker validates\n * a POST with `installableSeedPayloadSchema` and a GET with the base `seedPayloadSchema` — write\n * strict, read lenient, deliberately, so links already in the wild stay repairable in the editor.\n * This gate read the BASE schema, so the one rule the two differ by — a project-scoped skill\n * assigned to a sub-agent resting at global, which has nowhere to be written — was the single\n * payload that passed locally and failed at the edge. It arrived as a bare `HTTP 400`.\n *\n * That was not an exotic shape. `agents` is sparse and an absent entry rests on the shared default\n * of `global`, so \"assign a skill and say nothing else\" is already the unwritable pair;\n * `meta-config-stack-detect` emitted it on every run. Reading the same schema the store writes\n * with is what makes this file's promise — everything that can fail locally, failing before the\n * caller spends a write — true rather than nearly true.\n */\nexport function readPipedPayload(body: string): PipedPayload {\n if (body.trim() === \"\") return { ok: false, error: NOTHING_PIPED };\n\n const read = readJson(body);\n if (!read.parsed) return { ok: false, error: notJsonMessage(body, read.reason) };\n\n const result = installableSeedPayloadSchema.safeParse(read.value);\n if (!result.success) {\n return { ok: false, error: refusedByContract(formatZodErrors(result.error)) };\n }\n\n return { ok: true, payload: result.data };\n}\n\n/** Whether a body is JSON at all, separated from whether it is a payload. */\ntype JsonRead =\n | { readonly parsed: true; readonly value: unknown }\n | { readonly parsed: false; readonly reason: string };\n\n/**\n * `JSON.parse` behind a verdict, so the caller reads as three guards rather than as a `let`\n * assigned inside a `try`. The same shape `readYaml` takes in `lib/skills/skill-metadata.ts`,\n * for the same reason: the failure is one of several a reader has to tell apart, and a thrown\n * exception is the one form that cannot sit beside the others.\n */\nfunction readJson(body: string): JsonRead {\n try {\n return { parsed: true, value: JSON.parse(body) };\n } catch (error) {\n return { parsed: false, reason: getErrorMessage(error) };\n }\n}\n\n/**\n * Two quotes of the same untrusted body, and both have to be made inert.\n *\n * The excerpt is the obvious one. The REASON is not, and it is the one that got through: V8 writes\n * `Unexpected token 'h', \"here is...\" is not valid JSON`, quoting the offending input verbatim, so\n * a parser's own account of a failure carries whatever caused it. `truncateText` covers the\n * excerpt; nothing covered the reason, because nothing looked like it came from outside.\n */\nfunction notJsonMessage(body: string, reason: string): string {\n const opening = truncateText(body.trim(), EXCERPT);\n const said = stripTerminalControls(reason);\n return `What arrived on standard input is not JSON: ${said}. It began: ${opening}`;\n}\n\nfunction refusedByContract(problems: string[]): string {\n return `The configuration on standard input is not one this store accepts: ${namedProblems(problems)}`;\n}\n\n/**\n * The first few problems in full, and a count of however many were left out.\n *\n * The count is what keeps a clipped list honest. Without it a caller who fixed the pairs named\n * here and ran again would have no way to tell this from a complete list — so the second refusal\n * would read as a new fault rather than as the rest of the one they were already fixing.\n */\nfunction namedProblems(problems: string[]): string {\n const named = problems.slice(0, PROBLEMS_NAMED).join(\"; \");\n const elided = problems.length - PROBLEMS_NAMED;\n return elided > 0 ? `${named} (and ${elided} more)` : named;\n}\n","import type { Readable } from \"node:stream\";\n\n/**\n * Every chunk of a readable, as text.\n *\n * A stream rather than `readFileSync(\"/dev/stdin\")`: that path does not exist on every platform\n * this CLI runs on, and it reads a pipe the operating system may still be filling. Async\n * iteration is the one form that is correct on all of them.\n */\nexport async function readAllOf(stream: Readable): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));\n }\n\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/commands/share.ts","../../src/cli/lib/seed/read-piped-payload.ts","../../src/cli/utils/read-stream.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\n\nimport { BaseCommand } from \"../base-command.js\";\nimport { isLocalSource } from \"../lib/configuration/config.js\";\nimport { EXIT_CODES } from \"../lib/exit-codes.js\";\nimport { unofferablePlacementsFound } from \"../lib/hosts/configured-placements.js\";\nimport { seedPayloadForInstallation } from \"../lib/seed/installation-payload.js\";\nimport { publishSeedConfig } from \"../lib/seed/publish-seed.js\";\nimport {\n readPipedPayload,\n STDIN_IS_A_TERMINAL,\n type PipedPayload,\n} from \"../lib/seed/read-piped-payload.js\";\nimport {\n providerFlag,\n providerNamedBy,\n refuseAnAmbiguousInstallation,\n} from \"../lib/installation/provider-flag.js\";\nimport { warn } from \"../utils/logger.js\";\nimport { readAllOf } from \"../utils/read-stream.js\";\nimport {\n sharedConfigDestinations,\n sharedIdNamesAFolder,\n sharedSkillsLeftBehind,\n shareRefusesEjectedSkills,\n} from \"../utils/messages.js\";\nimport { typedEntries } from \"../utils/typed-object.js\";\nimport type { SkillId } from \"../types/index.js\";\nimport type { SeedPayload } from \"@workspace/matrix/seed\";\n\n/** A payload to publish, and what is owed about it once it has an id. */\ntype ShareablePayload = { payload: SeedPayload; caveats: string[] };\n\n/**\n * What an id minted from this installation will not do everywhere, said after the id rather than\n * refused: it still installs everything else.\n *\n * A payload names one marketplace, by the ref `--marketplace` took. A folder is a ref only this\n * machine can follow, and a plugin from any other marketplace is skipped wherever the id is\n * installed.\n */\nfunction caveatsAbout(payload: SeedPayload, leftBehind: readonly SkillId[]): string[] {\n const { marketplace } = payload;\n return [\n ...(isAFolder(marketplace) ? [sharedIdNamesAFolder(marketplace)] : []),\n ...(leftBehind.length > 0 ? [sharedSkillsLeftBehind(leftBehind)] : []),\n ];\n}\n\n/** Whether a payload's marketplace ref is a folder on this machine rather than a repository. */\nfunction isAFolder(marketplace: string | undefined): marketplace is string {\n return marketplace !== undefined && isLocalSource(marketplace);\n}\n\n/** The skills a payload asks the receiver to eject — local copies, editor-added ones included. */\nfunction ejectedSkillsIn(payload: SeedPayload): string[] {\n return typedEntries(payload.skills)\n .filter(([, skill]) => skill.install === \"eject\")\n .map(([id]) => id);\n}\n\nexport default class Share extends BaseCommand {\n static summary = \"Share this installation as an id anyone can install\";\n\n static description =\n \"Turns the skills, sub-agents and per-agent curation installed here into a configuration the agentsinc.sh store holds, and prints the id it was given. Install it elsewhere with 'init --from <id>', or open it in the editor. The id is the configuration's own hash, so sharing an unchanged installation returns the id it already had.\";\n\n static flags = {\n stdin: Flags.boolean({\n description:\n \"Share a configuration piped in on standard input instead of the one installed here\",\n default: false,\n }),\n provider: providerFlag(),\n };\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"cat proposal.json | <%= config.bin %> <%= command.id %> --stdin\",\n ];\n\n /**\n * Read, map, refuse, publish, then say what the id will not do everywhere.\n *\n * Everything that can fail locally fails before the POST. The store's free tier allows a\n * thousand writes a day and reads a hundred times that, so a write is the scarce half — and one\n * spent on a configuration that cannot be installed buys a dead link.\n *\n * The first three steps are `seedPayloadForInstallation`, shared with `edit --ui`: the two\n * commands mint the same id from the same directory and differ only in what they do with it.\n * The one refusal `edit --ui` does not share is of ejected skills — a share carries plugins\n * only, and the editor is where a user goes to turn ejected skills into plugins.\n */\n async run(): Promise<void> {\n const { flags } = await this.parse(Share);\n\n // Only the installation path reads a directory. A piped payload is the CALLER's configuration\n // and no folder on this machine decides anything about it, so neither a scope holding two\n // installations nor a placement this host cannot fill is this run's problem — asking anyway\n // would refuse a `--stdin` share for the state of a directory it never opens.\n if (!flags.stdin) {\n await refuseAnAmbiguousInstallation(\n process.cwd(),\n \"share\",\n providerNamedBy(flags.provider),\n (message) => this.error(message, { exit: EXIT_CODES.INVALID_ARGS }),\n );\n await this.ensureConfigReadable(process.cwd());\n await this.refuseUnofferablePlacements(process.cwd());\n }\n\n const { payload, caveats } = flags.stdin\n ? await this.payloadFromPipe()\n : await this.payloadFromInstallation();\n\n const published = await publishSeedConfig(payload);\n if (!published.ok) {\n this.error(published.error, { exit: EXIT_CODES.ERROR });\n }\n\n this.reportShared(published.id);\n // Through `warn()` from `utils/logger.ts` rather than `this.warn`: oclif hard-wraps at the\n // terminal width, and a caveat names a folder path that does not survive being broken.\n for (const caveat of caveats) warn(caveat);\n }\n\n /**\n * Refuses a configuration asking for a mode/scope cell its own host does not offer, before the\n * payload is built and long before the POST.\n *\n * **`share` is a read path, and the roster that names them missed it.** The list this refusal\n * was built against reads \"`compile`, `edit`, `update` and `doctor`\" and claims to be every\n * command that acts on a configuration it did not just write — while `share` sits beside them\n * reading the same file through `seedPayloadForInstallation`. What it publishes is described as\n * \"the skills, sub-agents and per-agent curation installed HERE\", and a row the host could not\n * place was never installed here at all, so the id would describe an installation that does not\n * exist. `refuseAnAmbiguousInstallation` above is this finding's sibling and was already asked;\n * the two are the pair `doctor`'s Placements row reports together.\n *\n * A refusal rather than a row, like the other three writers, and for a reason of its own: the\n * store's write is the scarce half, and one spent on an installation nothing on this machine\n * will act on buys a link to a misdescription.\n *\n * `uninstall` reads the same file and is deliberately NOT on this roster: a refusal there would\n * make an unofferable installation unremovable, which is a guard that has swallowed its domain.\n */\n private async refuseUnofferablePlacements(cwd: string): Promise<void> {\n const [finding] = await unofferablePlacementsFound(cwd);\n if (finding !== undefined) this.error(finding, { exit: EXIT_CODES.ERROR });\n }\n\n /** The installation in this directory: mapped, refused if it holds ejected skills, announced. */\n private async payloadFromInstallation(): Promise<ShareablePayload> {\n const prepared = await seedPayloadForInstallation(process.cwd());\n if (!prepared.ok) {\n this.error(prepared.error, { exit: EXIT_CODES.ERROR });\n }\n\n this.refuseEjectedSkills(prepared.payload);\n this.log(`Sharing ${prepared.skills} skill(s) across ${prepared.agents} sub-agent(s)...`);\n\n return {\n payload: prepared.payload,\n caveats: caveatsAbout(prepared.payload, prepared.leftBehind),\n };\n }\n\n /**\n * Refuses a share while anything it would send is an ejected (Local) skill.\n *\n * An ejected skill is a copy on this machine, which the user may have edited and an\n * editor-added skill always is, so a share carries plugins only — from a project, that covers\n * the ejected skills it inherits from the global installation as well as its own. A skill the\n * user wrote by hand is never in the payload, so it neither travels nor refuses.\n */\n private refuseEjectedSkills(payload: SeedPayload): void {\n const ejected = ejectedSkillsIn(payload);\n if (ejected.length > 0) {\n this.error(shareRefusesEjectedSkills(ejected), { exit: EXIT_CODES.ERROR });\n }\n }\n\n /**\n * A configuration the CALLER holds, which is why nothing here reads an installation.\n *\n * That is the whole distinction the flag draws, and it is not cosmetic: a bare `share` resolves\n * an installation the way every other command does — this project, then the global one — so\n * without this branch, sharing a piped payload from a directory with nothing in it would\n * publish whatever the machine happens to have installed globally.\n *\n * The producer this exists for is not this CLI. `meta-config-stack-detect` walks a repository\n * and emits a `SeedPayload` it is forbidden to write or apply, and an id is the only door into\n * the editor, which reads `?fromId=` and nothing else. Publishing from here rather than from\n * the producer keeps `SEED_VERSION`, the `AGENTS_INC_API_URL` override and the caller's\n * user-agent in the one place that owns them.\n *\n * It owes no caveats: those describe an installation, and none was read.\n */\n private async payloadFromPipe(): Promise<ShareablePayload> {\n if (process.stdin.isTTY) {\n this.error(STDIN_IS_A_TERMINAL, { exit: EXIT_CODES.ERROR });\n }\n\n const read: PipedPayload = readPipedPayload(await readAllOf(process.stdin));\n if (!read.ok) {\n this.error(read.error, { exit: EXIT_CODES.ERROR });\n }\n\n return { payload: read.payload, caveats: [] };\n }\n\n /**\n * Both destinations, because an id nobody can act on is not a share and there are exactly two\n * things that read one: this CLI, and the editor the configuration can be reopened in. The\n * lines themselves are `sharedConfigDestinations`, shared with `edit --ui` for the same reason\n * the mint above is.\n */\n private reportShared(id: string): void {\n this.logSuccess(`Shared as ${id}`);\n for (const destination of sharedConfigDestinations(id)) {\n this.log(destination);\n }\n }\n}\n","import { installableSeedPayloadSchema, type SeedPayload } from \"@workspace/matrix/seed\";\n\nimport { formatZodErrors } from \"../schema-validator.js\";\nimport { getErrorMessage } from \"../../utils/errors.js\";\nimport { stripTerminalControls, truncateText } from \"../../utils/string.js\";\n\n/** A payload read from a pipe, or the reason it could not be. */\nexport type PipedPayload =\n | { readonly ok: true; readonly payload: SeedPayload }\n | { readonly ok: false; readonly error: string };\n\nexport const NOTHING_PIPED =\n \"--stdin expects a configuration on standard input and none arrived. Pipe one in, as 'cat proposal.json | agents-inc share --stdin'.\";\n\nexport const STDIN_IS_A_TERMINAL =\n \"--stdin expects a configuration on standard input, but standard input is a terminal. Pipe one in rather than typing it.\";\n\n/**\n * How much of a body to quote back when it is not JSON, so a refusal names what it read.\n *\n * A CHARACTER bound, because what it clips is arbitrary bytes a producer piped by mistake: any\n * cut is as good as any other, and the point is only that the caller recognises what they sent.\n */\nconst EXCERPT = 80;\n\n/**\n * How many problems to name before the rest are counted instead.\n *\n * The scope rule raises one issue per (skill, sub-agent) PAIR rather than one per payload, and\n * each renders at roughly 150 characters — the same sentence every time, with two names changed.\n * A repository scan is 20-40 skills across 3-6 sub-agents, so an unbounded list is 60-240 of them\n * and hundreds of lines: the refusal becomes the wall of text it is complaining about.\n *\n * Four, so the whole message lands near twice the 300-character `EXPLANATION_BUDGET` in\n * `publish-seed.ts`, which bounds the store's account of this same refusal arriving over the\n * wire. Twice, because that one quotes back a remote explanation of a payload already sent, where\n * these are the caller's own pairs and each name is somewhere they have to go and edit — and four\n * of them is enough to show the mistake is a pattern rather than a one-off.\n *\n * A COUNT rather than a `truncateText` over the joined list, which is the one place this file's\n * two bounds differ. A character cut lands mid-sentence, and mid-sentence here means a half-named\n * skill or sub-agent — worse than one left out and counted, because it reads as a name.\n */\nconst PROBLEMS_NAMED = 4;\n\n/**\n * Everything that can fail locally, failing before the caller spends a write.\n *\n * The store's free tier allows a thousand writes a day against a hundred times that in reads, so\n * a write is the scarce half — and one spent on a payload the decoder cannot read buys a dead\n * link. Each of the three refusals below is a different mistake and says which: nothing arrived,\n * what arrived is not JSON, or it is JSON the contract does not accept.\n *\n * `installableSeedPayloadSchema` rather than a shape written here, and that is the point of\n * publishing from the CLI at all. `SEED_VERSION` is a `z.literal`, so a producer that hardcodes\n * the wire format emits payloads the store refuses the day it moves; the schema travels with the\n * code that posts.\n *\n * THE *INSTALLABLE* SCHEMA, AND THE DIFFERENCE IS THE WHOLE BUG THIS CLOSED. The worker validates\n * a POST with `installableSeedPayloadSchema` and a GET with the base `seedPayloadSchema` — write\n * strict, read lenient, deliberately, so links already in the wild stay repairable in the editor.\n * This gate read the BASE schema, so the one rule the two differ by — a project-scoped skill\n * assigned to a sub-agent resting at global, which has nowhere to be written — was the single\n * payload that passed locally and failed at the edge. It arrived as a bare `HTTP 400`.\n *\n * That was not an exotic shape. `agents` is sparse and an absent entry rests on the shared default\n * of `global`, so \"assign a skill and say nothing else\" is already the unwritable pair;\n * `meta-config-stack-detect` emitted it on every run. Reading the same schema the store writes\n * with is what makes this file's promise — everything that can fail locally, failing before the\n * caller spends a write — true rather than nearly true.\n */\nexport function readPipedPayload(body: string): PipedPayload {\n if (body.trim() === \"\") return { ok: false, error: NOTHING_PIPED };\n\n const read = readJson(body);\n if (!read.parsed) return { ok: false, error: notJsonMessage(body, read.reason) };\n\n const result = installableSeedPayloadSchema.safeParse(read.value);\n if (!result.success) {\n return { ok: false, error: refusedByContract(formatZodErrors(result.error)) };\n }\n\n return { ok: true, payload: result.data };\n}\n\n/** Whether a body is JSON at all, separated from whether it is a payload. */\ntype JsonRead =\n | { readonly parsed: true; readonly value: unknown }\n | { readonly parsed: false; readonly reason: string };\n\n/**\n * `JSON.parse` behind a verdict, so the caller reads as three guards rather than as a `let`\n * assigned inside a `try`. The same shape `readYaml` takes in `lib/skills/skill-metadata.ts`,\n * for the same reason: the failure is one of several a reader has to tell apart, and a thrown\n * exception is the one form that cannot sit beside the others.\n */\nfunction readJson(body: string): JsonRead {\n try {\n return { parsed: true, value: JSON.parse(body) };\n } catch (error) {\n return { parsed: false, reason: getErrorMessage(error) };\n }\n}\n\n/**\n * Two quotes of the same untrusted body, and both have to be made inert.\n *\n * The excerpt is the obvious one. The REASON is not, and it is the one that got through: V8 writes\n * `Unexpected token 'h', \"here is...\" is not valid JSON`, quoting the offending input verbatim, so\n * a parser's own account of a failure carries whatever caused it. `truncateText` covers the\n * excerpt; nothing covered the reason, because nothing looked like it came from outside.\n */\nfunction notJsonMessage(body: string, reason: string): string {\n const opening = truncateText(body.trim(), EXCERPT);\n const said = stripTerminalControls(reason);\n return `What arrived on standard input is not JSON: ${said}. It began: ${opening}`;\n}\n\nfunction refusedByContract(problems: string[]): string {\n return `The configuration on standard input is not one this store accepts: ${namedProblems(problems)}`;\n}\n\n/**\n * The first few problems in full, and a count of however many were left out.\n *\n * The count is what keeps a clipped list honest. Without it a caller who fixed the pairs named\n * here and ran again would have no way to tell this from a complete list — so the second refusal\n * would read as a new fault rather than as the rest of the one they were already fixing.\n */\nfunction namedProblems(problems: string[]): string {\n const named = problems.slice(0, PROBLEMS_NAMED).join(\"; \");\n const elided = problems.length - PROBLEMS_NAMED;\n return elided > 0 ? `${named} (and ${elided} more)` : named;\n}\n","import type { Readable } from \"node:stream\";\n\n/**\n * Every chunk of a readable, as text.\n *\n * A stream rather than `readFileSync(\"/dev/stdin\")`: that path does not exist on every platform\n * this CLI runs on, and it reads a pipe the operating system may still be filling. Async\n * iteration is the one form that is correct on all of them.\n */\nexport async function readAllOf(stream: Readable): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));\n }\n\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n"],"mappings":"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","names":[]}