@soleur/opencode 0.0.0-stage → 3.354.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (832) hide show
  1. package/.claude-plugin/agents.manifest.json +476 -0
  2. package/.claude-plugin/plugin.json +42 -0
  3. package/README.md +44 -2
  4. package/agents/community/.gitkeep +0 -0
  5. package/agents/engineering/cto.md +87 -0
  6. package/agents/engineering/design/ddd-architect.md +95 -0
  7. package/agents/engineering/discovery/agent-finder.md +214 -0
  8. package/agents/engineering/discovery/functional-discovery.md +182 -0
  9. package/agents/engineering/infra/infra-security.md +151 -0
  10. package/agents/engineering/infra/platform-strategist.md +109 -0
  11. package/agents/engineering/infra/terraform-architect.md +171 -0
  12. package/agents/engineering/prompt-engineer.md +37 -0
  13. package/agents/engineering/research/best-practices-researcher.md +115 -0
  14. package/agents/engineering/research/framework-docs-researcher.md +93 -0
  15. package/agents/engineering/research/git-history-analyzer.md +53 -0
  16. package/agents/engineering/research/learnings-researcher.md +285 -0
  17. package/agents/engineering/research/repo-research-analyst.md +118 -0
  18. package/agents/engineering/review/agent-native-reviewer.md +279 -0
  19. package/agents/engineering/review/architecture-strategist.md +61 -0
  20. package/agents/engineering/review/code-quality-analyst.md +92 -0
  21. package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
  22. package/agents/engineering/review/data-integrity-guardian.md +82 -0
  23. package/agents/engineering/review/data-migration-expert.md +98 -0
  24. package/agents/engineering/review/deployment-verification-agent.md +183 -0
  25. package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
  26. package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
  27. package/agents/engineering/review/legacy-code-expert.md +55 -0
  28. package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
  29. package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
  30. package/agents/engineering/review/performance-oracle.md +130 -0
  31. package/agents/engineering/review/security-sentinel.md +167 -0
  32. package/agents/engineering/review/semgrep-sast.md +36 -0
  33. package/agents/engineering/review/test-design-reviewer.md +150 -0
  34. package/agents/engineering/review/user-impact-reviewer.md +70 -0
  35. package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
  36. package/agents/finance/budget-analyst.md +29 -0
  37. package/agents/finance/cfo.md +44 -0
  38. package/agents/finance/financial-reporter.md +30 -0
  39. package/agents/finance/revenue-analyst.md +30 -0
  40. package/agents/legal/clo.md +115 -0
  41. package/agents/legal/legal-compliance-auditor.md +110 -0
  42. package/agents/legal/legal-document-generator.md +134 -0
  43. package/agents/marketing/analytics-analyst.md +25 -0
  44. package/agents/marketing/brand-architect.md +181 -0
  45. package/agents/marketing/cmo.md +76 -0
  46. package/agents/marketing/conversion-optimizer.md +19 -0
  47. package/agents/marketing/copywriter.md +20 -0
  48. package/agents/marketing/fact-checker.md +70 -0
  49. package/agents/marketing/growth-strategist.md +145 -0
  50. package/agents/marketing/paid-media-strategist.md +28 -0
  51. package/agents/marketing/pricing-strategist.md +17 -0
  52. package/agents/marketing/programmatic-seo-specialist.md +25 -0
  53. package/agents/marketing/retention-strategist.md +23 -0
  54. package/agents/marketing/seo-aeo-analyst.md +164 -0
  55. package/agents/operations/coo.md +50 -0
  56. package/agents/operations/ops-advisor.md +78 -0
  57. package/agents/operations/ops-provisioner.md +101 -0
  58. package/agents/operations/ops-research.md +52 -0
  59. package/agents/operations/service-automator.md +278 -0
  60. package/agents/product/business-validator.md +206 -0
  61. package/agents/product/competitive-intelligence.md +89 -0
  62. package/agents/product/cpo.md +63 -0
  63. package/agents/product/design/ux-design-lead.md +209 -0
  64. package/agents/product/spec-flow-analyzer.md +122 -0
  65. package/agents/sales/cro.md +44 -0
  66. package/agents/sales/deal-architect.md +29 -0
  67. package/agents/sales/outbound-strategist.md +29 -0
  68. package/agents/sales/pipeline-analyst.md +29 -0
  69. package/agents/support/cco.md +48 -0
  70. package/agents/support/community-manager.md +570 -0
  71. package/agents/support/ticket-triage.md +70 -0
  72. package/bin/soleur-opencode.js +29 -0
  73. package/commands/go.md +660 -0
  74. package/commands/help.md +273 -0
  75. package/commands/sync.md +977 -0
  76. package/commands/version.md +103 -0
  77. package/hooks/antigravity-hooks.json +84 -0
  78. package/hooks/antigravity-session-start.sh +15 -0
  79. package/hooks/bare-root-cwd-guard.sh +131 -0
  80. package/hooks/browser-snapshot-credential-guard.sh +200 -0
  81. package/hooks/codex-session-start.sh +9 -0
  82. package/hooks/compaction-state.sh +525 -0
  83. package/hooks/destructive-command-guard.sh +1387 -0
  84. package/hooks/devin-session-start.sh +90 -0
  85. package/hooks/ext-filter-detect.sh +171 -0
  86. package/hooks/hooks.json +133 -0
  87. package/hooks/lib/hook-tool-kind.sh +24 -0
  88. package/hooks/lib/log-rotation.sh +176 -0
  89. package/hooks/lib/shell-argv.pl +576 -0
  90. package/hooks/memory-backstop.sh +1114 -0
  91. package/hooks/operator-stage-approval.sh +391 -0
  92. package/hooks/stop-hook.sh +367 -0
  93. package/hooks/unkept-promise-hook.sh +383 -0
  94. package/hooks/version-banner.sh +127 -0
  95. package/hooks/welcome-hook.sh +50 -0
  96. package/install.js +29 -0
  97. package/lib/agent-registry.ts +154 -0
  98. package/lib/c4-canonical-cli.mjs +34 -0
  99. package/lib/c4-canonical.mjs +46 -0
  100. package/lib/c4-from-components.ts +554 -0
  101. package/lib/go-routing.ts +81 -0
  102. package/lib/grok-inspect-contract.ts +311 -0
  103. package/lib/harness-model-map.ts +101 -0
  104. package/lib/harness-parity.ts +726 -0
  105. package/lib/harness-tool-map.ts +180 -0
  106. package/lib/harness.ts +716 -0
  107. package/lib/heartbeat-live-reconcile.ts +1153 -0
  108. package/lib/heartbeat-manifest.ts +361 -0
  109. package/lib/kb-coverage.ts +252 -0
  110. package/lib/pr-merge-poll.ts +103 -0
  111. package/lib/workflow-fidelity.ts +388 -0
  112. package/opencode/INSTRUCTIONS.md +171 -0
  113. package/opencode/agents/engineering/cto.md +10 -0
  114. package/opencode/agents/engineering/design/ddd-architect.md +10 -0
  115. package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
  116. package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
  117. package/opencode/agents/engineering/infra/infra-security.md +10 -0
  118. package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
  119. package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
  120. package/opencode/agents/engineering/prompt-engineer.md +10 -0
  121. package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
  122. package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
  123. package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
  124. package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
  125. package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
  126. package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
  127. package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
  128. package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
  129. package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
  130. package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
  131. package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
  132. package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
  133. package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
  134. package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
  135. package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
  136. package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
  137. package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
  138. package/opencode/agents/engineering/review/performance-oracle.md +10 -0
  139. package/opencode/agents/engineering/review/security-sentinel.md +10 -0
  140. package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
  141. package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
  142. package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
  143. package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
  144. package/opencode/agents/finance/budget-analyst.md +10 -0
  145. package/opencode/agents/finance/cfo.md +10 -0
  146. package/opencode/agents/finance/financial-reporter.md +10 -0
  147. package/opencode/agents/finance/revenue-analyst.md +10 -0
  148. package/opencode/agents/legal/clo.md +10 -0
  149. package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
  150. package/opencode/agents/legal/legal-document-generator.md +10 -0
  151. package/opencode/agents/marketing/analytics-analyst.md +10 -0
  152. package/opencode/agents/marketing/brand-architect.md +10 -0
  153. package/opencode/agents/marketing/cmo.md +10 -0
  154. package/opencode/agents/marketing/conversion-optimizer.md +10 -0
  155. package/opencode/agents/marketing/copywriter.md +10 -0
  156. package/opencode/agents/marketing/fact-checker.md +10 -0
  157. package/opencode/agents/marketing/growth-strategist.md +10 -0
  158. package/opencode/agents/marketing/paid-media-strategist.md +10 -0
  159. package/opencode/agents/marketing/pricing-strategist.md +10 -0
  160. package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
  161. package/opencode/agents/marketing/retention-strategist.md +10 -0
  162. package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
  163. package/opencode/agents/operations/coo.md +10 -0
  164. package/opencode/agents/operations/ops-advisor.md +10 -0
  165. package/opencode/agents/operations/ops-provisioner.md +10 -0
  166. package/opencode/agents/operations/ops-research.md +10 -0
  167. package/opencode/agents/operations/service-automator.md +10 -0
  168. package/opencode/agents/product/business-validator.md +10 -0
  169. package/opencode/agents/product/competitive-intelligence.md +10 -0
  170. package/opencode/agents/product/cpo.md +10 -0
  171. package/opencode/agents/product/design/ux-design-lead.md +10 -0
  172. package/opencode/agents/product/spec-flow-analyzer.md +10 -0
  173. package/opencode/agents/sales/cro.md +10 -0
  174. package/opencode/agents/sales/deal-architect.md +10 -0
  175. package/opencode/agents/sales/outbound-strategist.md +10 -0
  176. package/opencode/agents/sales/pipeline-analyst.md +10 -0
  177. package/opencode/agents/support/cco.md +10 -0
  178. package/opencode/agents/support/community-manager.md +10 -0
  179. package/opencode/agents/support/ticket-triage.md +10 -0
  180. package/opencode/commands/go.md +10 -0
  181. package/opencode/commands/help.md +10 -0
  182. package/opencode/commands/sync.md +10 -0
  183. package/opencode/commands/version.md +11 -0
  184. package/opencode/opencode.json +8 -0
  185. package/opencode/plugin.ts +818 -0
  186. package/package.json +36 -4
  187. package/scripts/admin-merge-ready.sh +545 -0
  188. package/scripts/admin-merge-ready.test.sh +753 -0
  189. package/scripts/affected-tests.sh +908 -0
  190. package/scripts/affected-tests.test.sh +1797 -0
  191. package/scripts/alpha-metrics.sh +78 -0
  192. package/scripts/alpha-metrics.test.sh +115 -0
  193. package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
  194. package/scripts/audit-flag-flip.sh +107 -0
  195. package/scripts/check-red-on-main.sh +316 -0
  196. package/scripts/ci-head-verdict.sh +411 -0
  197. package/scripts/cloud-detect.sh +193 -0
  198. package/scripts/delegation-verify.sh +657 -0
  199. package/scripts/delegation-verify.test.sh +1424 -0
  200. package/scripts/deploy-arm.sh +475 -0
  201. package/scripts/domain-model-drift.sh +419 -0
  202. package/scripts/emit-decision.sh +144 -0
  203. package/scripts/emit-decision.test.sh +144 -0
  204. package/scripts/ext-filter-divergence-corpus.txt +15 -0
  205. package/scripts/ext-filter-divergence.sh +189 -0
  206. package/scripts/generate-c4-from-components.ts +442 -0
  207. package/scripts/grok-fidelity-gate.sh +68 -0
  208. package/scripts/grok-pre-push-gate.sh +232 -0
  209. package/scripts/harness-discovery-smoke.ts +906 -0
  210. package/scripts/harness-parity-census.ts +92 -0
  211. package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
  212. package/scripts/install-opencode-adapter.ts +509 -0
  213. package/scripts/lib/affected-tests/common.sh +365 -0
  214. package/scripts/lib/affected-tests/go.sh +157 -0
  215. package/scripts/lib/affected-tests/js.sh +188 -0
  216. package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
  217. package/scripts/lib/affected-tests/python.sh +185 -0
  218. package/scripts/lib/context-sanitize.sh +49 -0
  219. package/scripts/lib/domain-model-lib.sh +180 -0
  220. package/scripts/lib/ext-filter-grammar.tsv +34 -0
  221. package/scripts/lib/operator-script.sh +1236 -0
  222. package/scripts/lib/proc.sh +580 -0
  223. package/scripts/lib/session-state.sh +748 -0
  224. package/scripts/lib/tmp-classify.sh +843 -0
  225. package/scripts/lib/wrapper-boundary.sh +47 -0
  226. package/scripts/lint-ratchet.py +1547 -0
  227. package/scripts/monitor-pr-checks.sh +441 -0
  228. package/scripts/pipeline-tally.sh +756 -0
  229. package/scripts/pipeline-tally.test.sh +740 -0
  230. package/scripts/plugin-version.sh +528 -0
  231. package/scripts/precommit-guard.sh +115 -0
  232. package/scripts/precommit-guard.test.sh +254 -0
  233. package/scripts/reconcile-live-heartbeats.ts +705 -0
  234. package/scripts/render-c4-model.sh +217 -0
  235. package/scripts/resolve-git-root.sh +55 -0
  236. package/scripts/resolve-regenerable-conflicts.sh +363 -0
  237. package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
  238. package/scripts/ruff-requirements.txt +22 -0
  239. package/scripts/run-in-pid-namespace.sh +98 -0
  240. package/scripts/run-in-pid-namespace.test.sh +290 -0
  241. package/scripts/scan-ancestor-signal-helpers.py +556 -0
  242. package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
  243. package/scripts/setup-ralph-loop.sh +238 -0
  244. package/scripts/sync-cursor-name-map.ts +316 -0
  245. package/scripts/sync-grok-agent-compat.ts +188 -0
  246. package/scripts/sync-npm-package.ts +165 -0
  247. package/scripts/sync-opencode-adapter.ts +498 -0
  248. package/scripts/sync-pr-behind.sh +647 -0
  249. package/scripts/sync-pr-behind.test.sh +252 -0
  250. package/scripts/taste-profile-update.sh +180 -0
  251. package/scripts/taste-profile-update.test.sh +167 -0
  252. package/scripts/write-budget-marker.sh +23 -0
  253. package/scripts/write-kb-coverage.ts +113 -0
  254. package/skills/admin-ip-refresh/SKILL.md +81 -0
  255. package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
  256. package/skills/agent-browser/SKILL.md +698 -0
  257. package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
  258. package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
  259. package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
  260. package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
  261. package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
  262. package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
  263. package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
  264. package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
  265. package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
  266. package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
  267. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
  268. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
  269. package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
  270. package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
  271. package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
  272. package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
  273. package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
  274. package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
  275. package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
  276. package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
  277. package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
  278. package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
  279. package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
  280. package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
  281. package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
  282. package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
  283. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
  284. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
  285. package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
  286. package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
  287. package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
  288. package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
  289. package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
  290. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
  291. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
  292. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
  293. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
  294. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
  295. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
  296. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
  297. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
  298. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
  299. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
  300. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
  301. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
  302. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
  303. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
  304. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
  305. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
  306. package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
  307. package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
  308. package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
  309. package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
  310. package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
  311. package/skills/agent-native-architecture/SKILL.md +479 -0
  312. package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
  313. package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
  314. package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
  315. package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
  316. package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
  317. package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
  318. package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
  319. package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
  320. package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
  321. package/skills/agent-native-architecture/references/product-implications.md +478 -0
  322. package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
  323. package/skills/agent-native-architecture/references/self-modification.md +294 -0
  324. package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
  325. package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
  326. package/skills/agent-native-audit/SKILL.md +300 -0
  327. package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
  328. package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
  329. package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  330. package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  331. package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  332. package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
  333. package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  334. package/skills/architecture/SKILL.md +436 -0
  335. package/skills/architecture/references/adr-template.md +154 -0
  336. package/skills/architecture/references/likec4-reference.md +136 -0
  337. package/skills/architecture/references/nfr-reference.md +126 -0
  338. package/skills/archive-kb/SKILL.md +86 -0
  339. package/skills/archive-kb/scripts/archive-kb.sh +300 -0
  340. package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
  341. package/skills/atdd-developer/SKILL.md +65 -0
  342. package/skills/brainstorm/SKILL.md +701 -0
  343. package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
  344. package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
  345. package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
  346. package/skills/brainstorm/references/ui-surface-terms.md +42 -0
  347. package/skills/brainstorm-techniques/SKILL.md +231 -0
  348. package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
  349. package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
  350. package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
  351. package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
  352. package/skills/campaign-calendar/SKILL.md +145 -0
  353. package/skills/cf-token-scope/SKILL.md +164 -0
  354. package/skills/cf-token-scope/references/widen-playbook.md +114 -0
  355. package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
  356. package/skills/changelog/SKILL.md +142 -0
  357. package/skills/code-to-prd/SKILL.md +66 -0
  358. package/skills/code-to-prd/references/banner-template.md +12 -0
  359. package/skills/code-to-prd/references/prd-template.md +41 -0
  360. package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
  361. package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
  362. package/skills/code-to-prd/test/fixture/.env.example +7 -0
  363. package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
  364. package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
  365. package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
  366. package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
  367. package/skills/code-to-prd/test/fixture/package.json +6 -0
  368. package/skills/cohort-status/SKILL.md +62 -0
  369. package/skills/community/SKILL.md +210 -0
  370. package/skills/community/scripts/bsky-community.sh +644 -0
  371. package/skills/community/scripts/bsky-setup.sh +631 -0
  372. package/skills/community/scripts/community-router.sh +93 -0
  373. package/skills/community/scripts/discord-community.sh +487 -0
  374. package/skills/community/scripts/discord-setup.sh +784 -0
  375. package/skills/community/scripts/github-community.sh +706 -0
  376. package/skills/community/scripts/hn-community.sh +233 -0
  377. package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
  378. package/skills/community/scripts/linkedin-community.sh +750 -0
  379. package/skills/community/scripts/linkedin-setup.sh +758 -0
  380. package/skills/community/scripts/x-community.sh +890 -0
  381. package/skills/community/scripts/x-setup.sh +713 -0
  382. package/skills/community/test/community-argv.test.sh +2922 -0
  383. package/skills/community/test/github-community.test.sh +981 -0
  384. package/skills/competitive-analysis/SKILL.md +69 -0
  385. package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
  386. package/skills/compound/SKILL.md +673 -0
  387. package/skills/compound/scripts/token-efficiency-report.sh +334 -0
  388. package/skills/compound/test/phase-16.test.sh +379 -0
  389. package/skills/compound-capture/SKILL.md +791 -0
  390. package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
  391. package/skills/compound-capture/assets/resolution-template.md +116 -0
  392. package/skills/compound-capture/references/yaml-schema.md +67 -0
  393. package/skills/compound-capture/schema.yaml +181 -0
  394. package/skills/constraint-scaffold/SKILL.md +175 -0
  395. package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
  396. package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
  397. package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
  398. package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
  399. package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
  400. package/skills/constraint-scaffold/references/shared-runner.template +135 -0
  401. package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
  402. package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
  403. package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
  404. package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
  405. package/skills/constraint-scaffold/test/generator.test.sh +93 -0
  406. package/skills/constraint-scaffold/test/parity.test.sh +285 -0
  407. package/skills/content-writer/SKILL.md +242 -0
  408. package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
  409. package/skills/cron-delete/SKILL.md +56 -0
  410. package/skills/cron-list/SKILL.md +54 -0
  411. package/skills/deepen-plan/SKILL.md +937 -0
  412. package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
  413. package/skills/deploy/SKILL.md +115 -0
  414. package/skills/deploy/references/hetzner-setup.md +67 -0
  415. package/skills/deploy/scripts/deploy.sh +37 -0
  416. package/skills/deploy-docs/SKILL.md +95 -0
  417. package/skills/dhh-rails-style/SKILL.md +203 -0
  418. package/skills/dhh-rails-style/references/architecture.md +717 -0
  419. package/skills/dhh-rails-style/references/controllers.md +323 -0
  420. package/skills/dhh-rails-style/references/frontend.md +556 -0
  421. package/skills/dhh-rails-style/references/gems.md +316 -0
  422. package/skills/dhh-rails-style/references/models.md +388 -0
  423. package/skills/dhh-rails-style/references/testing.md +354 -0
  424. package/skills/discord-content/SKILL.md +126 -0
  425. package/skills/docs-site/SKILL.md +214 -0
  426. package/skills/drain-labeled-backlog/SKILL.md +203 -0
  427. package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
  428. package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
  429. package/skills/drain-prs/SKILL.md +143 -0
  430. package/skills/drain-prs/scripts/triage-prs.sh +192 -0
  431. package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
  432. package/skills/dspy-ruby/SKILL.md +634 -0
  433. package/skills/dspy-ruby/assets/config-template.rb +359 -0
  434. package/skills/dspy-ruby/assets/module-template.rb +326 -0
  435. package/skills/dspy-ruby/assets/signature-template.rb +143 -0
  436. package/skills/dspy-ruby/references/core-concepts.md +268 -0
  437. package/skills/dspy-ruby/references/optimization.md +623 -0
  438. package/skills/dspy-ruby/references/providers.md +335 -0
  439. package/skills/eval-harness/README.md +194 -0
  440. package/skills/eval-harness/SKILL.md +157 -0
  441. package/skills/eval-harness/enums/go-routes.json +13 -0
  442. package/skills/eval-harness/enums/incident-threshold.json +5 -0
  443. package/skills/eval-harness/enums/lane.json +5 -0
  444. package/skills/eval-harness/enums/tool-selection.json +20 -0
  445. package/skills/eval-harness/enums/triage-levels.json +5 -0
  446. package/skills/eval-harness/gated-skills.json +34 -0
  447. package/skills/eval-harness/models.generated.json +5 -0
  448. package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
  449. package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
  450. package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
  451. package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
  452. package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
  453. package/skills/eval-harness/prompts/go-baseline.txt +6 -0
  454. package/skills/eval-harness/prompts/go-skill.txt +21 -0
  455. package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
  456. package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
  457. package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
  458. package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
  459. package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
  460. package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
  461. package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
  462. package/skills/eval-harness/prompts/triage-skill.txt +10 -0
  463. package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
  464. package/skills/eval-harness/scripts/extract-block.cjs +87 -0
  465. package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
  466. package/skills/eval-harness/scripts/gen-models.sh +41 -0
  467. package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
  468. package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
  469. package/skills/eval-harness/scripts/parse-label.cjs +93 -0
  470. package/skills/eval-harness/scripts/verdict.cjs +132 -0
  471. package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
  472. package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
  473. package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
  474. package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
  475. package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
  476. package/skills/eval-harness/test/eval-gate.test.sh +132 -0
  477. package/skills/eval-harness/test/extract-block.test.sh +87 -0
  478. package/skills/eval-harness/test/gate-classification.test.sh +77 -0
  479. package/skills/eval-harness/test/gen-models.test.sh +75 -0
  480. package/skills/eval-harness/test/measure-classification.test.sh +74 -0
  481. package/skills/eval-harness/test/parse-label.test.sh +129 -0
  482. package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
  483. package/skills/eval-harness/test/verdict.test.sh +123 -0
  484. package/skills/every-style-editor/SKILL.md +142 -0
  485. package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  486. package/skills/feature-tweet/SKILL.md +199 -0
  487. package/skills/feature-video/SKILL.md +412 -0
  488. package/skills/feature-video/scripts/check_deps.sh +281 -0
  489. package/skills/file-todos/SKILL.md +267 -0
  490. package/skills/file-todos/assets/todo-template.md +166 -0
  491. package/skills/fix-issue/SKILL.md +278 -0
  492. package/skills/fix-issue/references/agent-authored-exclusion.md +120 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +97 -0
  494. package/skills/flag-bootstrap/SETUP.md +142 -0
  495. package/skills/flag-create/SKILL.md +145 -0
  496. package/skills/flag-create/scripts/create.sh +273 -0
  497. package/skills/flag-delete/SKILL.md +167 -0
  498. package/skills/flag-delete/scripts/delete.sh +288 -0
  499. package/skills/flag-list/SKILL.md +99 -0
  500. package/skills/flag-list/scripts/list.sh +275 -0
  501. package/skills/flag-set-role/SKILL.md +229 -0
  502. package/skills/flag-set-role/scripts/flip.sh +774 -0
  503. package/skills/frontend-anti-slop/SKILL.md +106 -0
  504. package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
  505. package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
  506. package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
  507. package/skills/frontend-design/SKILL.md +77 -0
  508. package/skills/gdpr-gate/NOTICE +136 -0
  509. package/skills/gdpr-gate/SKILL.md +341 -0
  510. package/skills/gdpr-gate/references/fields.md +160 -0
  511. package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
  512. package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
  513. package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
  514. package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
  515. package/skills/gdpr-gate/references/layers/frontend.md +249 -0
  516. package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
  517. package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
  518. package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
  519. package/skills/gdpr-gate/references/legal-consent.md +140 -0
  520. package/skills/gdpr-gate/references/non-negotiables.md +60 -0
  521. package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
  522. package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
  523. package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
  524. package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
  525. package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
  526. package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
  527. package/skills/gemini-imagegen/SKILL.md +283 -0
  528. package/skills/gemini-imagegen/requirements.txt +5 -0
  529. package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
  530. package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
  531. package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
  532. package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
  533. package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
  534. package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
  535. package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
  536. package/skills/git-worktree/SKILL.md +534 -0
  537. package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
  538. package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
  539. package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
  540. package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
  541. package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
  542. package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
  543. package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
  544. package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
  545. package/skills/go/SKILL.md +54 -0
  546. package/skills/growth/SKILL.md +164 -0
  547. package/skills/harvest-debt/SKILL.md +78 -0
  548. package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
  549. package/skills/heal-skill/SKILL.md +173 -0
  550. package/skills/help/SKILL.md +46 -0
  551. package/skills/incident/NOTICE +39 -0
  552. package/skills/incident/SKILL.md +395 -0
  553. package/skills/incident/scripts/dry-run.sh +448 -0
  554. package/skills/incident/scripts/redact-engine.py +412 -0
  555. package/skills/incident/scripts/redact-sentinel.sh +44 -0
  556. package/skills/incident/templates/pir.md +139 -0
  557. package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
  558. package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
  559. package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
  560. package/skills/incident/test/fixtures/positive-corpus.md +87 -0
  561. package/skills/incident/test/redact-sentinel.test.sh +1420 -0
  562. package/skills/invoice/SKILL.md +206 -0
  563. package/skills/kb-glossary/SKILL.md +71 -0
  564. package/skills/kb-glossary/references/glossary-format.md +111 -0
  565. package/skills/kb-glossary/references/rejected-request-register.md +115 -0
  566. package/skills/kb-search/SKILL.md +276 -0
  567. package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
  568. package/skills/legal-audit/SKILL.md +161 -0
  569. package/skills/legal-generate/NOTICE +242 -0
  570. package/skills/legal-generate/SKILL.md +244 -0
  571. package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
  572. package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
  573. package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
  574. package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
  575. package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
  576. package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
  577. package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
  578. package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
  579. package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
  580. package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
  581. package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
  582. package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
  583. package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
  584. package/skills/linear-fetch/SKILL.md +247 -0
  585. package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
  586. package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
  587. package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
  588. package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
  589. package/skills/linear-fetch/test/parity.test.sh +112 -0
  590. package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
  591. package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
  592. package/skills/merge-pr/SKILL.md +670 -0
  593. package/skills/model-launch-review/SKILL.md +255 -0
  594. package/skills/model-launch-review/scripts/audit-models.sh +501 -0
  595. package/skills/one-shot/SKILL.md +316 -0
  596. package/skills/one-shot/references/token-discipline.md +74 -0
  597. package/skills/operator-bootstrap/SKILL.md +412 -0
  598. package/skills/operator-bootstrap/template.sh +439 -0
  599. package/skills/operator-digest/SKILL.md +287 -0
  600. package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
  601. package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
  602. package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
  603. package/skills/operator-rephrase/SKILL.md +98 -0
  604. package/skills/pencil-setup/SKILL.md +201 -0
  605. package/skills/pencil-setup/scripts/check_deps.sh +556 -0
  606. package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
  607. package/skills/pencil-setup/scripts/package-lock.json +1172 -0
  608. package/skills/pencil-setup/scripts/package.json +14 -0
  609. package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
  610. package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
  611. package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
  612. package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
  613. package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
  614. package/skills/plan/SKILL.md +1140 -0
  615. package/skills/plan/references/differential-migration-testing.md +185 -0
  616. package/skills/plan/references/plan-community-discovery.md +42 -0
  617. package/skills/plan/references/plan-founder-check.md +137 -0
  618. package/skills/plan/references/plan-functional-overlap.md +25 -0
  619. package/skills/plan/references/plan-host-fanout.md +51 -0
  620. package/skills/plan/references/plan-issue-templates.md +779 -0
  621. package/skills/plan/references/plan-network-outage-checklist.md +131 -0
  622. package/skills/plan/references/plan-scope-check.md +126 -0
  623. package/skills/plan/references/plan-sharp-edges.md +331 -0
  624. package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
  625. package/skills/plan/references/webapp-cwv-observability.md +143 -0
  626. package/skills/plan-review/SKILL.md +91 -0
  627. package/skills/plan-review/lib/named-panel.mjs +58 -0
  628. package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
  629. package/skills/postmerge/SKILL.md +642 -0
  630. package/skills/postmerge/references/deploy-status-debugging.md +141 -0
  631. package/skills/postmerge/references/graceful-degradation.md +20 -0
  632. package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
  633. package/skills/preflight/SKILL.md +1553 -0
  634. package/skills/preflight/references/check-13-founder-check.md +286 -0
  635. package/skills/preflight/scripts/founder-check.py +1640 -0
  636. package/skills/preflight/scripts/parse-form-a.awk +107 -0
  637. package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
  638. package/skills/product-roadmap/SKILL.md +357 -0
  639. package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
  640. package/skills/provision-cloudflare/SKILL.md +67 -0
  641. package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
  642. package/skills/provision-doppler/SKILL.md +58 -0
  643. package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
  644. package/skills/provision-github/SKILL.md +59 -0
  645. package/skills/provision-github/scripts/provision-github.sh +300 -0
  646. package/skills/provision-hetzner/SKILL.md +76 -0
  647. package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
  648. package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
  649. package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
  650. package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
  651. package/skills/qa/SKILL.md +263 -0
  652. package/skills/qa/references/browser-preflight.md +22 -0
  653. package/skills/questionnaire-generate/SKILL.md +328 -0
  654. package/skills/questionnaire-generate/references/questionnaire.template +60 -0
  655. package/skills/rclone/SKILL.md +162 -0
  656. package/skills/rclone/scripts/check_setup.sh +63 -0
  657. package/skills/release-announce/SKILL.md +65 -0
  658. package/skills/release-docs/SKILL.md +92 -0
  659. package/skills/reproduce-bug/SKILL.md +358 -0
  660. package/skills/resolve-debt/SKILL.md +85 -0
  661. package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
  662. package/skills/resolve-parallel/SKILL.md +45 -0
  663. package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
  664. package/skills/resolve-pr-parallel/SKILL.md +68 -0
  665. package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
  666. package/skills/resolve-todo-parallel/SKILL.md +57 -0
  667. package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
  668. package/skills/review/SKILL.md +1620 -0
  669. package/skills/review/references/anti-slop-scanner-hook.md +40 -0
  670. package/skills/review/references/defect-classes-migrations.md +9 -0
  671. package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
  672. package/skills/review/references/review-e2e-testing.md +116 -0
  673. package/skills/review/references/review-evidence-comment.md +36 -0
  674. package/skills/review/references/review-todo-structure.md +165 -0
  675. package/skills/review/references/risk-tier-and-fix-rounds.md +97 -0
  676. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  677. package/skills/review/references/ultrathink-phases.md +98 -0
  678. package/skills/review/references/wfs.md +6 -0
  679. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  680. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  681. package/skills/review/scripts/fix-round-seats.sh +197 -0
  682. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  683. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  684. package/skills/review/test/fix-round-seats.test.sh +237 -0
  685. package/skills/review/workflows/README.md +147 -0
  686. package/skills/review/workflows/review.workflow.js +667 -0
  687. package/skills/schedule/SKILL.md +796 -0
  688. package/skills/seo-aeo/SKILL.md +139 -0
  689. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  690. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  691. package/skills/ship/SKILL.md +3023 -0
  692. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  693. package/skills/ship/references/affected-gate-contract.md +115 -0
  694. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  695. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  696. package/skills/ship/references/cleanup-merged-archival.md +39 -0
  697. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  698. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  699. package/skills/ship/references/decision-challenges-render.md +71 -0
  700. package/skills/ship/references/delegated-act-authoring-form.md +7 -0
  701. package/skills/ship/references/detached-battery-verdict.md +13 -0
  702. package/skills/ship/references/epic-close-sweep.md +62 -0
  703. package/skills/ship/references/feature-tweet-draft.md +42 -0
  704. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  705. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  706. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  707. package/skills/ship/references/operator-holds.md +179 -0
  708. package/skills/ship/references/pipeline-tally-render.md +43 -0
  709. package/skills/ship/references/pr-overlap.md +153 -0
  710. package/skills/ship/references/queue-mode.md +7 -0
  711. package/skills/ship/references/ready-run-wait.md +36 -0
  712. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  713. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  714. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  715. package/skills/ship/references/supabase-column-probe.md +13 -0
  716. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  717. package/skills/ship/scripts/battery-owed.sh +403 -0
  718. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  719. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  720. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  721. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  722. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  723. package/skills/skill-creator/SKILL.md +299 -0
  724. package/skills/skill-creator/references/api-security.md +248 -0
  725. package/skills/skill-creator/references/authoring-levers.md +110 -0
  726. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  727. package/skills/skill-creator/references/best-practices.md +431 -0
  728. package/skills/skill-creator/references/common-patterns.md +639 -0
  729. package/skills/skill-creator/references/core-principles.md +193 -0
  730. package/skills/skill-creator/references/executable-code.md +188 -0
  731. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  732. package/skills/skill-creator/references/official-spec.md +193 -0
  733. package/skills/skill-creator/references/recommended-structure.md +180 -0
  734. package/skills/skill-creator/references/skill-structure.md +396 -0
  735. package/skills/skill-creator/references/using-scripts.md +122 -0
  736. package/skills/skill-creator/references/using-templates.md +120 -0
  737. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  738. package/skills/skill-creator/scripts/init_skill.py +322 -0
  739. package/skills/skill-creator/scripts/package_skill.py +110 -0
  740. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  741. package/skills/skill-creator/templates/router-skill.md +73 -0
  742. package/skills/skill-creator/templates/simple-skill.md +34 -0
  743. package/skills/skill-creator/workflows/add-reference.md +96 -0
  744. package/skills/skill-creator/workflows/add-script.md +104 -0
  745. package/skills/skill-creator/workflows/add-template.md +83 -0
  746. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  747. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  748. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  749. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  750. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  751. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  752. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  753. package/skills/skill-security-scan/SKILL.md +145 -0
  754. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  755. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  756. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  757. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  758. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  759. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  760. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  761. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  762. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  763. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  764. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  765. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  766. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  767. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  768. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  769. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  770. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  771. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  772. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  773. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  774. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  775. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  776. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  777. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  778. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  779. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  780. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  781. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  782. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  783. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  784. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  785. package/skills/social-distribute/SKILL.md +485 -0
  786. package/skills/spec-templates/SKILL.md +274 -0
  787. package/skills/sync/SKILL.md +49 -0
  788. package/skills/test-browser/SKILL.md +464 -0
  789. package/skills/test-browser/references/feature-map.md +161 -0
  790. package/skills/test-fix-loop/SKILL.md +159 -0
  791. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  792. package/skills/triage/SKILL.md +402 -0
  793. package/skills/trigger-cron/SKILL.md +136 -0
  794. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  795. package/skills/user-set-role/SKILL.md +147 -0
  796. package/skills/user-set-role/scripts/set-role.sh +218 -0
  797. package/skills/user-story-writer/SKILL.md +77 -0
  798. package/skills/ux-audit/SKILL.md +251 -0
  799. package/skills/ux-audit/references/finding.schema.json +62 -0
  800. package/skills/ux-audit/references/route-list.yaml +95 -0
  801. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  802. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  803. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  804. package/skills/verification-scaffold/SKILL.md +73 -0
  805. package/skills/verification-scaffold/references/gitleaks.toml.template +13 -0
  806. package/skills/verification-scaffold/references/guardrail-audit.md +156 -0
  807. package/skills/verification-scaffold/references/guards/block-stash-hook.sh.template +72 -0
  808. package/skills/verification-scaffold/references/guards/commit-on-main-hook.sh.template +82 -0
  809. package/skills/verification-scaffold/references/guards/orphan-test-check.sh.template +102 -0
  810. package/skills/verification-scaffold/references/guards/precommit-guard.sh.template +115 -0
  811. package/skills/verification-scaffold/references/guards/secret-scan-hook.sh.template +176 -0
  812. package/skills/verification-scaffold/references/guards/verification-gates.yml.template +49 -0
  813. package/skills/verification-scaffold/references/guards/wrapper-boundary.sh.template +47 -0
  814. package/skills/verification-scaffold/references/guards.tsv +21 -0
  815. package/skills/verification-scaffold/references/verification-readme.template +55 -0
  816. package/skills/verification-scaffold/scripts/verification-scaffold.sh +785 -0
  817. package/skills/verification-scaffold/test/bite-proof.test.sh +159 -0
  818. package/skills/verification-scaffold/test/generator.test.sh +319 -0
  819. package/skills/verification-scaffold/test/manifest.test.sh +144 -0
  820. package/skills/work/SKILL.md +1480 -0
  821. package/skills/work/references/end-of-work-resume.md +7 -0
  822. package/skills/work/references/hook-test-main-cwd.md +24 -0
  823. package/skills/work/references/key-principles.md +83 -0
  824. package/skills/work/references/property-testing.md +122 -0
  825. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  826. package/skills/work/references/work-agent-teams.md +97 -0
  827. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  828. package/skills/work/references/work-lockfile-bumps.md +97 -0
  829. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  830. package/skills/work/references/work-subagent-fanout.md +61 -0
  831. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  832. package/skills/xcode-test/SKILL.md +360 -0
@@ -0,0 +1,784 @@
1
+ #!/usr/bin/env bash
2
+ # discord-setup.sh -- Discord bot setup API operations
3
+ #
4
+ # SECURITY: Token MUST be in DISCORD_BOT_TOKEN_INPUT env var.
5
+ # Never pass tokens as CLI arguments (visible in ps/history).
6
+ #
7
+ # Usage: discord-setup.sh <command> [args]
8
+ # Commands:
9
+ # validate-token - Verify token via API, output app ID
10
+ # discover-guilds - List guilds as JSON
11
+ # list-channels <guild_id> - List text channels as JSON
12
+ # create-webhook <channel_id> - Create webhook, output webhook URL
13
+ # write-env <guild_id> - Write to .env with chmod 600 (secrets from the environment)
14
+ # verify - Run guild-info check
15
+ #
16
+ # Environment variables:
17
+ # DISCORD_BOT_TOKEN_INPUT - Bot token (required for API commands and write-env)
18
+ # DISCORD_WEBHOOK_URL_INPUT - Webhook URL (required, write-env only; never an argument)
19
+ # DISCORD_RELEASES_WEBHOOK_URL_INPUT - Releases channel webhook (optional, write-env only)
20
+ # DISCORD_BLOG_WEBHOOK_URL_INPUT - Blog channel webhook (optional, write-env only)
21
+ #
22
+ # write-env no longer takes the webhook URL as an argument (it would sit in the process list
23
+ # and the shell history). Set it, and the bot token, in the environment without echoing them. In a
24
+ # terminal:
25
+ # read -rs DISCORD_WEBHOOK_URL_INPUT; export DISCORD_WEBHOOK_URL_INPUT
26
+ # read -rs DISCORD_BOT_TOKEN_INPUT; export DISCORD_BOT_TOKEN_INPUT
27
+ # `read -rs` needs a terminal. With no terminal (an agent runtime, a pipe, a script) keep each
28
+ # value out of the COMMAND TEXT, because the text of a tool call lands in the transcript, in shell
29
+ # history, and (when the runtime wraps it in `bash -c`) on that wrapper's argument list. An inline
30
+ # `VAR=value discord-setup.sh ...` is only free of an argument list in THIS process: the caller's
31
+ # command still carries the value, so it is not offered. The form that works is to read each value
32
+ # from a mode-600 file. Create each file in your own terminal (not through an agent), mode first,
33
+ # and type the value at the prompt: `(umask 077; cat > /path/to/bot-token.txt)`; an existing file
34
+ # keeps its wider mode through a redirect, so chmod 600 it before writing. Then, in one command whose
35
+ # parentheses keep both values out of the shell you stay in:
36
+ # ( read -r DISCORD_WEBHOOK_URL_INPUT < /path/to/webhook-url.txt; read -r DISCORD_BOT_TOKEN_INPUT < /path/to/bot-token.txt; export DISCORD_WEBHOOK_URL_INPUT DISCORD_BOT_TOKEN_INPUT; discord-setup.sh write-env <guild_id> )
37
+ # (`create-webhook` also needs DISCORD_BOT_TOKEN_INPUT in its environment: read it from the bot-token
38
+ # file first, as _print_env_remedy shows.)
39
+ # Residual, stated plainly: the file names are in the command text, the values are not; the values
40
+ # sit in those files until deleted; `create-webhook` prints the webhook URL on stdout (so it is in
41
+ # the transcript once) unless its output is redirected into the file, which is the way to avoid it;
42
+ # and the values end up in .env (mode 600), which is the point of the command.
43
+ # Every value written to .env is checked against an allow-list first (see _wenv_validate).
44
+ #
45
+ # Exit codes:
46
+ # 0 - Success
47
+ # 1 - General error (including a write-env value outside the allow-list: the stderr marker
48
+ # SOLEUR_CREDENTIAL_REFUSED carries `reason=token_shape|control_char var=<NAME> phase=write-env`;
49
+ # or an operational write-env fault: the same marker with `reason=env_* var=ENV_FILE`, to diagnose
50
+ # and retry once. The one human line is on stdout; and a non-numeric guild_id, whose one line is on
51
+ # stderr like every other command's)
52
+ # 2 - Retryable error from the Discord API (e.g., webhook limit on channel). write-env never exits 2:
53
+ # its faults exit 1
54
+ # 64 - Usage error (write-env only: no guild_id, the webhook URL passed as an argument, or
55
+ # DISCORD_WEBHOOK_URL_INPUT or DISCORD_BOT_TOKEN_INPUT missing or empty). The usage text
56
+ # is printed on STDOUT, and so is the remedy for every one of these except the missing guild_id.
57
+ #
58
+ # Output: JSON or plain text to stdout
59
+ # Errors: Messages to stderr, exit 1 (write-env: the usage errors (exit 64), the allow-list line
60
+ # and the confirmation line are on stdout, see above)
61
+
62
+ set -euo pipefail
63
+
64
+ # (#7797) Xtrace refusal -- the FIRST thing after `set …`, so nothing above it is
65
+ # traced. This script ships to installed Soleur CLIs: the terminal it runs in
66
+ # belongs to the founder, and `bash -x` would print their live Discord bot token.
67
+ # The `:+x` form tests non-emptiness WITHOUT expanding the value, so the guard
68
+ # cannot leak the thing it is refusing over. CONDITIONAL, and that is measured
69
+ # rather than inherited: the token is bound BEFORE the prologue runs (it arrives in
70
+ # DISCORD_BOT_TOKEN_INPUT, or DISCORD_BOT_TOKEN for a caller that already holds it),
71
+ # so the guard can never be empty while one is live. The one runtime acquisition is
72
+ # `verify`, which sources the repo's .env -- cmd_verify carries its own refusal for
73
+ # that path. A founder tracing with none set keeps full tracing.
74
+ # Refusal goes to STDOUT, not stderr: agent runtimes surface stdout and swallow
75
+ # stderr (constitution.md > Code Style > Always), and a swallowed security refusal
76
+ # leaves the user with a bare `exit 78` and no text at all.
77
+ case "$-" in
78
+ *x*)
79
+ if [ -n "${DISCORD_BOT_TOKEN_INPUT:+x}${DISCORD_BOT_TOKEN:+x}${DISCORD_WEBHOOK_URL_INPUT:+x}${DISCORD_RELEASES_WEBHOOK_URL_INPUT:+x}${DISCORD_BLOG_WEBHOOK_URL_INPUT:+x}" ]; then
80
+ printf 'Refusing to run under `bash -x`: a Discord bot token or webhook URL is set, and tracing would print it to your terminal. To trace safely, unset them and re-run.\n'
81
+ exit 78
82
+ fi
83
+ ;;
84
+ esac
85
+
86
+ # (#7873) `--disable` closes ~/.curlrc and `--noproxy '*'` closes the proxy
87
+ # variables, but NEITHER touches the trust store or the TLS session-key log. On an
88
+ # installed CLI the environment belongs to someone who is not us, so
89
+ # a substituted `CURL_CA_BUNDLE` is a clean MITM of the founder's platform
90
+ # token with every other guard fully intact, and `SSLKEYLOGFILE` is passive
91
+ # decryption with no MITM at all. Matches the line in scripts/betterstack-query.sh.
92
+ unset SSLKEYLOGFILE CURL_CA_BUNDLE SSL_CERT_FILE SSL_CERT_DIR CURL_HOME \
93
+ HOSTALIASES LOCALDOMAIN RES_OPTIONS
94
+
95
+ # --- Transport-confinement diagnostics (#7873) --------------------------------
96
+ # Every credentialed `curl` below carries `--disable --noproxy '*'` and discards
97
+ # curl's own stderr (which can carry the URL). A failed request then has four
98
+ # competing causes the founder cannot tell apart: the proxy this script
99
+ # deliberately bypassed, a ~/.curlrc that `--disable` dropped, a TLS trust
100
+ # variable it cleared, or an ordinary network failure. So each failure emits ONE
101
+ # structured marker carrying the discriminators, beside a human line that names
102
+ # the bypass instead of blaming the founder's connectivity.
103
+ SOLEUR_TRANSPORT_SCRIPT="discord-setup.sh"
104
+ SOLEUR_TRANSPORT_PLATFORM="Discord"
105
+
106
+ # The request helpers below run inside `$(...)`, where fd 1 is the capture pipe.
107
+ # Save the script's real stdout so the marker reaches the founder's terminal (and
108
+ # the always-on PostToolUse Bash extractor) instead of being swallowed into a
109
+ # variable. The marker is on stdout BY DESIGN -- the call sites' `2>/dev/null` is
110
+ # what keeps curl's URL-carrying stderr out of the transcript, and it must not be
111
+ # able to suppress the diagnostic too.
112
+ exec 3>&1
113
+
114
+ # Which proxy variables are non-empty, or `none`.
115
+ proxy_env_names() {
116
+ local names="" n
117
+ for n in HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy; do
118
+ if [[ -n "${!n-}" ]]; then
119
+ names="${names},${n}"
120
+ fi
121
+ done
122
+ if [[ -z "$names" ]]; then
123
+ printf 'none'
124
+ else
125
+ printf '%s' "${names#,}"
126
+ fi
127
+ }
128
+
129
+ # True when a proxy this script deliberately bypasses is configured. NO_PROXY
130
+ # alone is not one -- it only ever narrows proxying, so it cannot be the cause.
131
+ proxy_bypassed() {
132
+ [[ -n "${HTTP_PROXY:-}${HTTPS_PROXY:-}${ALL_PROXY:-}${http_proxy:-}${https_proxy:-}${all_proxy:-}" ]]
133
+ }
134
+
135
+ # One line, eight fields, on the saved stdout.
136
+ #
137
+ # Two fields were literals in the first cut and are now MEASURED (#7898 review):
138
+ # `noproxy_applied` was the constant "true", which ASSERTS the property instead of
139
+ # observing it -- dropping --noproxy from a call site left the diagnostic still
140
+ # claiming it was applied. It now reports whether a proxy was actually configured
141
+ # for this request to bypass, which is the fact a reader needs. `refusal` was the
142
+ # constant "none" at every call site, so the cause it exists to discriminate could
143
+ # never appear; the field stays for a caller that can refuse for a named cause, and
144
+ # this script has none, so it passes "none".
145
+ #
146
+ # `tls_env_cleared` is new. The prologue unsets CURL_CA_BUNDLE/SSL_CERT_FILE et al,
147
+ # which is correct against an attacker and BREAKS a founder whose corporate CA
148
+ # arrives that way -- curl then exits 60 and, without this field, the event is
149
+ # indistinguishable from an ordinary network failure.
150
+ emit_transport_diag() {
151
+ local curl_exit="$1" refusal="$2" surface="installed-cli" curlrc="false" noproxy="false"
152
+ # The hosted sandbox always sets this (agent-env.ts > AGENT_ENV_OVERRIDES); an
153
+ # installed CLI does not. A label on the event, not a gate on behaviour.
154
+ if [[ -n "${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-}" ]]; then
155
+ surface="in-sandbox"
156
+ fi
157
+ if [[ -f "${HOME:-}/.curlrc" ]]; then
158
+ curlrc="true"
159
+ fi
160
+ if proxy_bypassed; then
161
+ noproxy="true"
162
+ fi
163
+ printf 'SOLEUR_TRANSPORT_DIAG surface=%s script=%s curl_exit=%s refusal=%s proxy_env=%s curlrc_present=%s noproxy_applied=%s tls_env_cleared=%s\n' \
164
+ "$surface" "$SOLEUR_TRANSPORT_SCRIPT" "$curl_exit" "$refusal" \
165
+ "$(proxy_env_names)" "$curlrc" "$noproxy" "true" >&3
166
+ }
167
+
168
+ # Replaces the bare "Check your network connection and try again." on a
169
+ # credentialed-curl failure. With a proxy set, that line blames the founder for a
170
+ # bypass this script chose.
171
+ report_transport_failure() {
172
+ local curl_exit="${1:-unknown}"
173
+ local detail="${2:-Failed to connect to the ${SOLEUR_TRANSPORT_PLATFORM} API.}"
174
+ emit_transport_diag "$curl_exit" "none"
175
+ echo "Error: ${detail}" >&2
176
+ # Ordered by how specific the evidence is. Each arm names something THIS script
177
+ # did, so the founder is never told to go debug their own network for a choice
178
+ # made here. The bare "check your connection" line is the LAST resort, not the
179
+ # default -- it was the default in the first cut, which meant a founder whose
180
+ # corporate CA or curlrc we had just discarded was blamed for it (#7898 review).
181
+ if [[ "$curl_exit" == "60" || "$curl_exit" == "35" || "$curl_exit" == "77" ]]; then
182
+ # 60 peer-certificate, 35 TLS handshake, 77 CA-bundle unreadable.
183
+ echo "This looks like a TLS trust failure. This script deliberately clears CURL_CA_BUNDLE, SSL_CERT_FILE, SSL_CERT_DIR and SSLKEYLOGFILE before the request, because those variables can redirect or expose a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} credential." >&2
184
+ echo "If your machine needs a corporate CA to reach ${SOLEUR_TRANSPORT_PLATFORM}, that is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
185
+ elif proxy_bypassed; then
186
+ echo "This request deliberately bypasses your proxy ($(proxy_env_names)), because a proxy can redirect a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} token." >&2
187
+ echo "If you need Soleur to reach ${SOLEUR_TRANSPORT_PLATFORM} through your proxy, that is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
188
+ elif [[ -f "${HOME:-}/.curlrc" ]]; then
189
+ echo "You have a ~/.curlrc, and this request deliberately ignores it (--disable), because a curlrc can redirect a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} token. If it configures a proxy or a CA bundle you need, that is why this failed." >&2
190
+ echo "That is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
191
+ else
192
+ echo "Check your network connection and try again." >&2
193
+ fi
194
+ }
195
+
196
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
197
+ source "$SCRIPT_DIR/../../../scripts/resolve-git-root.sh"
198
+
199
+ DISCORD_API="https://discord.com/api/v10"
200
+
201
+ # --- Helpers ---
202
+
203
+ require_token() {
204
+ if [[ -z "${DISCORD_BOT_TOKEN_INPUT:-}" ]]; then
205
+ echo "Error: DISCORD_BOT_TOKEN_INPUT env var is not set." >&2
206
+ echo "Pass the token via environment variable, not as a CLI argument." >&2
207
+ exit 1
208
+ fi
209
+ }
210
+
211
+ require_jq() {
212
+ if ! command -v jq &>/dev/null; then
213
+ echo "Error: jq is required but not installed." >&2
214
+ echo "Install it: https://jqlang.github.io/jq/download/" >&2
215
+ exit 1
216
+ fi
217
+ }
218
+
219
+ # (#9597) The Bot token rides curl's STDIN config channel (`--config -`), never its
220
+ # argument list (readable by every local user in /proc/<pid>/cmdline). That channel is
221
+ # line-oriented, so a token holding a quote or a newline could append a `url = "..."`
222
+ # directive and make curl issue a second request. This guard refuses anything outside
223
+ # the base64url.base64url.base64url shape BEFORE the value is formatted into the stream.
224
+ # (The same check discord-community.sh's validate_env runs; the `=~` runs in the C locale
225
+ # so the ranges are ASCII.) The refusal line is fixed and value-free (never the
226
+ # token), goes to stderr, and exits 1; it is not a connect failure.
227
+ _discord_token_ok() {
228
+ local LC_ALL=C t="${1:-}"
229
+ [[ "$t" =~ ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$ ]]
230
+ }
231
+ refuse_token_shape() {
232
+ printf 'SOLEUR_CREDENTIAL_REFUSED script=%s reason=token_shape\n' "$SOLEUR_TRANSPORT_SCRIPT" >&2
233
+ echo "Error: DISCORD_BOT_TOKEN_INPUT is not shaped like a Discord bot token, so nothing was sent. Expected three dot-separated base64url segments (letters, digits, '-' and '_'), with no 'Bot ' prefix, quotes, spaces, or line breaks (including a trailing CR or newline). The value is not shown." >&2
234
+ exit 1
235
+ }
236
+
237
+ # (#9597) write-env value allow-list. `.env` values are written UNQUOTED and later SOURCED
238
+ # (`verify`, the community scripts), so a value holding a command substitution, a backtick, a
239
+ # quote, a semicolon, a space or a newline would execute or split on the next source, and a leading
240
+ # tilde would silently change a stored credential (tilde expansion on source). Validation is an
241
+ # allow-list, fail-closed, and runs for EVERY value BEFORE the first write: a refused value prints
242
+ # the value-free marker plus ONE human line naming only the VARIABLE, writes nothing, exits 1 and
243
+ # leaves any existing .env untouched. A value outside the list that is legitimate is added to .env
244
+ # by hand (a thread-scoped webhook URL with a query string is outside the list). The glob runs
245
+ # under LC_ALL=C and is not grep, which is line-oriented and lets a multi-line value through.
246
+ _wenv_class() { # <value>: 0 allowed, 1 outside the allow-list or empty, 2 holds a control character
247
+ local LC_ALL=C
248
+ case "${1-}" in
249
+ '') return 1 ;;
250
+ *[[:cntrl:]]*) return 2 ;;
251
+ *[!A-Za-z0-9._:/@%+=,-]*) return 1 ;;
252
+ esac
253
+ return 0
254
+ }
255
+ _wenv_refuse() { # <reason> <VARIABLE>
256
+ printf 'SOLEUR_CREDENTIAL_REFUSED script=%s reason=%s var=%s phase=write-env\n' "$SOLEUR_TRANSPORT_SCRIPT" "$1" "$2" >&2
257
+ echo "Error: ${2} holds a character this script does not write to .env (allowed: letters, digits and . _ : / @ % + = , -), so nothing was written and your existing .env is unchanged. The value is not shown. If the value is legitimate, add it to .env by hand."
258
+ exit 1
259
+ }
260
+ # _wenv_validate <VARIABLE>...: every NON-EMPTY named variable passes the allow-list. The required
261
+ # variables are checked non-empty by the caller before this runs; an empty optional one is skipped.
262
+ _wenv_validate() {
263
+ local _wn _wrc
264
+ for _wn in "$@"; do
265
+ [[ -n "${!_wn:-}" ]] || continue
266
+ _wrc=0
267
+ _wenv_class "${!_wn}" || _wrc=$?
268
+ case "$_wrc" in
269
+ 0) ;;
270
+ 2) _wenv_refuse control_char "$_wn" ;;
271
+ *) _wenv_refuse token_shape "$_wn" ;;
272
+ esac
273
+ done
274
+ }
275
+
276
+ # (#10091) write-env commit: ONE helper, byte-identical in the four setup scripts (a drift row pins
277
+ # it, from this comment line through the closing brace of `_wenv_write`). `_wenv_write <env_file>
278
+ # <remove-ERE> <KEY=VALUE>...` replaces the old remove-then-append, which raced (two writers each read
279
+ # the file, each rewrote it, the later mv lost the earlier's keys), was not atomic (the append hit the
280
+ # live file after the mv) and widened a new file before the chmod.
281
+ # The edit is built in a temp file `<.env>.wenv.XXXXXX` beside the RESOLVED .env (same filesystem, so the
282
+ # final `mv -f` is one atomic rename; a namespace of its own, so the sweep below touches only that
283
+ # namespace (a file there that is not a regular file this user owns is left alone), never the operator
284
+ # library's `.env.tmp.*` temps) under `umask 077` (0600 from birth), serialized by
285
+ # ONE mutual-exclusion mechanism on every platform: an owner-pid `mkdir` lock directory
286
+ # (`<.env>.lock.d`) that is held until AFTER that rename. No flock (a flock run and a mkdir run exclude
287
+ # nothing against each other, flock without a timeout blocks forever, and `exec {fd}<` needs bash 4.1
288
+ # while macOS ships 3.2).
289
+ # Stale-lock recovery is serialized as well, by a second `mkdir` mutex `<.env>.lock.d.recover`. A waiter
290
+ # judges a lock stale when its owner pid is dead (or its owner file stayed empty for _WENV_EMPTY_POLLS
291
+ # passes), then tries to take the mutex; a waiter that loses it keeps waiting. The winner RE-READS the
292
+ # owner of the lock UNDER the mutex (the snapshot it judged on is never trusted; the re-read narrows the
293
+ # window to the few instructions between the read and the removal, it does not close it), removes the lock
294
+ # only if the owner is still a dead pid, or if the snapshot AND the re-read (after a short pause) both found
295
+ # no pid, ALWAYS releases the mutex, and goes round the loop again. A lock whose owner turned from a dead pid
296
+ # into nothing has changed hands and is left alone. A recovering waiter never writes while it recovers,
297
+ # nothing is renamed away and put back, and the lock path is never empty while a live holder exists, so two
298
+ # waiters cannot both hold the lock. A SOLEUR_WENV_LOCK_TICKS below 2 never attempts recovery.
299
+ # The wait is ONE bounded loop. Every failed `mkdir` pass either sleeps one tick (0.1 s; 1 s where
300
+ # `sleep` takes no fraction) or fails at once, and every pass counts against SOLEUR_WENV_LOCK_TICKS
301
+ # (digits only, at most _WENV_TICKS_MAX, else _WENV_TICKS_DEFAULT): about 10 s by default with a
302
+ # fractional `sleep`, about 100 s without one. It FAILS CLOSED: no write, a value-free marker, exit 1,
303
+ # .env untouched. A lock path that exists but is not a directory fails at once (env_lock_not_dir). grep
304
+ # exit 1 (nothing left) is normal; exit 2 (an unreadable .env) aborts, because swallowing it would
305
+ # install an empty file over the credentials. A symlinked .env is resolved first (the real target is
306
+ # rewritten, the link survives); a dangling link or a non-regular target is refused.
307
+ # Signals: this helper owns the EXIT, INT, TERM and HUP traps for its duration and restores nothing (no
308
+ # caller installs one today; a caller that needs its own must chain them). On those four it removes its
309
+ # temp and its recovery mutex and releases the lock, and it removes the lock ONLY when the owner file
310
+ # reads exactly this process. Residual, stated: SIGKILL leaves its lock (recovered by the next run
311
+ # through the dead pid), its temp (swept by the next run, under the lock) and, if it died mid-recovery,
312
+ # `<.env>.lock.d.recover`, which the next runs report after the bounded wait ("remove that directory");
313
+ # a recycled pid can keep a dead owner looking live until the wait times out; the owner check uses
314
+ # `kill -0`, so a lock held by another user reads as dead; a holder stalled between its `mkdir` and its
315
+ # owner write for longer than the empty-owner window can be treated as ownerless and recovered.
316
+ _WENV_TICK=0.1 # seconds per wait tick (a `sleep` without fractions falls back to 1 s)
317
+ _WENV_TICKS_DEFAULT=100 # the default bound: about 10 s
318
+ _WENV_TICKS_MAX=1000 # the largest SOLEUR_WENV_LOCK_TICKS accepted: about 100 s
319
+ _WENV_EMPTY_POLLS=30 # passes an ownerless lock directory is waited on before recovery is tried: about 3 s
320
+ _WENV_EMPTY_PAUSE=0.3 # pause between the two owner reads under the recovery mutex when the owner is not a pid
321
+ _WENV_LOCK="" _WENV_TMP="" _WENV_REC=""
322
+ _wenv_unlock() { # idempotent: remove our temp and recovery mutex; release the lock ONLY if its owner file reads exactly this process
323
+ local _wo=""
324
+ if [[ -n "$_WENV_TMP" ]]; then rm -f -- "$_WENV_TMP"; _WENV_TMP=""; fi
325
+ if [[ -n "$_WENV_REC" ]]; then rmdir -- "$_WENV_REC" 2>/dev/null || true; _WENV_REC=""; fi
326
+ if [[ -n "$_WENV_LOCK" ]]; then
327
+ { read -r _wo < "$_WENV_LOCK/owner"; } 2>/dev/null || _wo=""
328
+ if [[ "$_wo" == "$$" ]]; then rm -rf -- "$_WENV_LOCK"; fi
329
+ _WENV_LOCK=""
330
+ fi
331
+ }
332
+ _wenv_fail() { # <reason> <human text>: value-free marker on stderr, one human line on stdout, exit 1
333
+ printf 'SOLEUR_CREDENTIAL_REFUSED script=%s reason=%s var=ENV_FILE phase=write-env\n' "$SOLEUR_TRANSPORT_SCRIPT" "$1" >&2
334
+ echo "Error: $2 Nothing was written and your existing .env is unchanged."
335
+ exit 1
336
+ }
337
+ _wenv_write() {
338
+ local _wf="$1" _wre="$2" _wr _wl _wm _wd _wi=0 _ws=0 _wt _wown _wo2 _wsnap="" _wstale _wgone _wwhy="" _wn _wrc _wum
339
+ shift 2
340
+ if _wr="$(readlink -f -- "$_wf" 2>/dev/null)" && [[ -n "$_wr" ]]; then
341
+ :
342
+ elif [[ -L "$_wf" ]]; then
343
+ _wenv_fail env_unresolvable "The .env link at ${_wf} could not be resolved."
344
+ else
345
+ _wr="$_wf"
346
+ fi
347
+ if [[ -L "$_wf" && ! -e "$_wr" ]]; then
348
+ _wenv_fail env_dangling_link "The .env at ${_wf} is a link to a file that does not exist."
349
+ fi
350
+ if [[ -e "$_wr" && ! -f "$_wr" ]]; then
351
+ _wenv_fail env_not_regular "The .env at ${_wf} is not a regular file."
352
+ fi
353
+ _wl="${_wr}.lock.d"
354
+ _wm="${_wl}.recover"
355
+ _wt="${SOLEUR_WENV_LOCK_TICKS:-$_WENV_TICKS_DEFAULT}"
356
+ if [[ "$_wt" =~ ^[0-9]{1,4}$ ]] && (( 10#$_wt <= _WENV_TICKS_MAX )); then _wt=$((10#$_wt)); else _wt=$_WENV_TICKS_DEFAULT; fi
357
+ _wum="$(umask)"
358
+ umask 077
359
+ trap '_wenv_unlock' EXIT
360
+ trap '_wenv_unlock; exit 130' INT
361
+ trap '_wenv_unlock; exit 143' TERM
362
+ trap '_wenv_unlock; exit 129' HUP
363
+ while :; do
364
+ if mkdir -- "$_wl" 2>/dev/null; then
365
+ _WENV_LOCK="$_wl"
366
+ if ! printf '%s\n' "$$" > "$_wl/owner"; then
367
+ rm -rf -- "$_wl"; _WENV_LOCK=""
368
+ _wenv_fail env_write_failed "The lock at ${_wl} could not be recorded."
369
+ fi
370
+ break
371
+ fi
372
+ if [[ -L "$_wl" || -f "$_wl" ]]; then
373
+ _wenv_fail env_lock_not_dir "The lock path ${_wl} exists but is not a directory; remove it."
374
+ fi
375
+ _wi=$((_wi + 1))
376
+ _wstale=0
377
+ if [[ ! -d "$_wl" ]]; then
378
+ # mkdir failed with the path absent: the lock was released in between, or mkdir fails for another
379
+ # reason (a name too long, no space): a pass that made no progress, so it sleeps below like any other.
380
+ _ws=0; _wwhy=""
381
+ _wd="${_wr%/*}"; [[ "$_wd" != "$_wr" ]] || _wd=.
382
+ [[ -d "$_wd" && -w "$_wd" ]] || _wenv_fail env_dir_unwritable "The directory of ${_wf} does not allow a lock to be created."
383
+ else
384
+ _wown=""
385
+ { read -r _wown < "$_wl/owner"; } 2>/dev/null || _wown=""
386
+ if [[ "$_wown" =~ ^[0-9]+$ ]]; then
387
+ _ws=0; _wsnap=pid
388
+ if [[ "$_wown" == "$$" ]] || ! kill -0 "$_wown" 2>/dev/null; then _wstale=1; _wwhy=stale; else _wwhy=live; fi
389
+ else
390
+ _ws=$((_ws + 1)); _wwhy=""; _wsnap=empty
391
+ if (( _ws > _WENV_EMPTY_POLLS )); then _wstale=1; fi
392
+ fi
393
+ fi
394
+ if (( _wstale == 1 && _wi < _wt )); then
395
+ if mkdir -- "$_wm" 2>/dev/null; then
396
+ _WENV_REC="$_wm"
397
+ _wo2=""
398
+ { read -r _wo2 < "$_wl/owner"; } 2>/dev/null || _wo2=""
399
+ if [[ "$_wsnap" == empty && ! "$_wo2" =~ ^[0-9]+$ ]]; then
400
+ sleep "$_WENV_EMPTY_PAUSE" 2>/dev/null || sleep 1
401
+ { read -r _wo2 < "$_wl/owner"; } 2>/dev/null || _wo2=""
402
+ fi
403
+ _wgone=0
404
+ if [[ "$_wo2" =~ ^[0-9]+$ ]]; then
405
+ if [[ "$_wo2" == "$$" ]] || ! kill -0 "$_wo2" 2>/dev/null; then rm -rf -- "$_wl"; _ws=0; _wgone=1; fi
406
+ elif [[ "$_wsnap" == empty ]]; then
407
+ rm -rf -- "$_wl"; _ws=0; _wgone=1
408
+ fi
409
+ _wenv_unlock
410
+ if (( _wgone == 1 )); then continue; fi
411
+ fi
412
+ fi
413
+ if (( _wi >= _wt )); then
414
+ if [[ "$_wwhy" != live && -d "$_wm" ]]; then _wwhy=recover; fi
415
+ case "$_wwhy" in
416
+ live) _wenv_fail env_lock_timeout "Another write-env run is still holding the lock at ${_wl}; wait for it to finish and run this again. If no other run is active, remove that directory." ;;
417
+ recover) _wenv_fail env_lock_timeout "A lock recovery is in progress or was interrupted (${_wm} exists); waited and gave up. If no other run is active, remove that directory." ;;
418
+ *) _wenv_fail env_lock_timeout "The lock at ${_wl} could not be taken or recovered; waited and gave up. If no other run is active, remove that directory." ;;
419
+ esac
420
+ fi
421
+ sleep "$_WENV_TICK" 2>/dev/null || sleep 1
422
+ done
423
+ for _wn in "${_wr}".wenv.??????; do
424
+ if [[ -f "$_wn" && ! -L "$_wn" && -O "$_wn" ]]; then rm -f -- "$_wn"; fi
425
+ done
426
+ _WENV_TMP="$(mktemp "${_wr}.wenv.XXXXXX")" || _wenv_fail env_write_failed "A temp file could not be created beside ${_wf}."
427
+ if [[ -f "$_wr" ]]; then
428
+ _wrc=0
429
+ LC_ALL=C grep -a -v -E "$_wre" "$_wr" > "$_WENV_TMP" || _wrc=$?
430
+ if (( _wrc > 1 )); then
431
+ _wenv_fail env_unreadable "The existing .env at ${_wf} could not be read."
432
+ fi
433
+ fi
434
+ printf '%s\n' "$@" >> "$_WENV_TMP" || _wenv_fail env_write_failed "The new values could not be written beside ${_wf}."
435
+ chmod 600 "$_WENV_TMP" || _wenv_fail env_write_failed "The temp file beside ${_wf} could not be restricted to mode 600."
436
+ mv -f -- "$_WENV_TMP" "$_wr" || _wenv_fail env_write_failed "The new .env could not be moved into place at ${_wf}."
437
+ _WENV_TMP=""
438
+ _wenv_unlock
439
+ trap - EXIT INT TERM HUP
440
+ umask "$_wum"
441
+ }
442
+
443
+ # Make a Discord API request. Suppresses curl stderr to prevent token leakage
444
+ # in debug output. Returns body on 2xx, handles errors.
445
+ #
446
+ # For create-webhook (POST to /channels/{id}/webhooks), HTTP 400 returns
447
+ # exit code 2 so the SKILL.md orchestrator can retry with a different channel.
448
+ discord_request() {
449
+ local endpoint="$1"
450
+ local method="${2:-GET}"
451
+ local data="${3:-}"
452
+ local depth="${4:-0}"
453
+
454
+ if (( depth >= 3 )); then
455
+ echo "Error: Discord API rate limit exceeded after 3 retries." >&2
456
+ exit 2
457
+ fi
458
+
459
+ # The guard sits IMMEDIATELY before the call it protects.
460
+ _discord_token_ok "${DISCORD_BOT_TOKEN_INPUT}" || refuse_token_shape
461
+
462
+ local response http_code body
463
+ # The bot token is NOT in this array (an array on argv is the same /proc/<pid>/cmdline
464
+ # surface) -- it is fed to `--config -` below. `--disable --noproxy '*'` stay literal on
465
+ # the call, first, where the transport-confinement lint (Rule D) can see them.
466
+ local curl_args=(
467
+ -s -w "\n%{http_code}"
468
+ --config -
469
+ -X "$method"
470
+ -H "Content-Type: application/json"
471
+ )
472
+
473
+ if [[ -n "$data" ]]; then
474
+ curl_args+=(-d "$data")
475
+ fi
476
+
477
+ # Suppress stderr to prevent token leakage in curl debug output
478
+ local __curl_rc=0
479
+ response=$(curl --disable --noproxy '*' "${curl_args[@]}" "${DISCORD_API}${endpoint}" 2>/dev/null \
480
+ < <(printf 'header = "Authorization: Bot %s"\n' "$DISCORD_BOT_TOKEN_INPUT")) || __curl_rc=$?
481
+ if (( __curl_rc != 0 )); then
482
+ report_transport_failure "$__curl_rc" "Failed to connect to Discord API (endpoint: ${endpoint})."
483
+ exit 1
484
+ fi
485
+
486
+ http_code=$(echo "$response" | tail -1)
487
+ body=$(echo "$response" | sed '$d')
488
+
489
+ case "$http_code" in
490
+ 2[0-9][0-9])
491
+ # Validate JSON
492
+ if ! echo "$body" | jq . >/dev/null 2>&1; then
493
+ echo "Error: Discord API returned malformed JSON for ${endpoint}" >&2
494
+ exit 1
495
+ fi
496
+ echo "$body"
497
+ ;;
498
+ 401)
499
+ echo "Error: Invalid or expired bot token." >&2
500
+ exit 1
501
+ ;;
502
+ 400)
503
+ local message
504
+ message=$(echo "$body" | jq -r '.message // "Bad request"' 2>/dev/null || echo "Bad request")
505
+ echo "Error: Discord API returned HTTP 400: ${message}" >&2
506
+ exit 2
507
+ ;;
508
+ 429)
509
+ local retry_after
510
+ retry_after=$(echo "$body" | jq -r '.retry_after // 5' 2>/dev/null || echo "5")
511
+ # Clamp retry_after to sane range [1, 60]
512
+ # Use printf to truncate float to integer for arithmetic comparison
513
+ # (sleep accepts floats natively, but bash (( )) does not)
514
+ local retry_int
515
+ retry_int=$(printf '%.0f' "$retry_after" 2>/dev/null || echo "5")
516
+ if (( retry_int > 60 )); then
517
+ retry_after=60
518
+ elif (( retry_int < 1 )); then
519
+ retry_after=1
520
+ fi
521
+ echo "Rate limited. Retrying after ${retry_after}s (attempt $((depth + 1))/3)..." >&2
522
+ sleep "$retry_after"
523
+ discord_request "$endpoint" "$method" "$data" "$((depth + 1))"
524
+ ;;
525
+ *)
526
+ local message
527
+ message=$(echo "$body" | jq -r '.message // "Unknown error"' 2>/dev/null || echo "Unknown error")
528
+ echo "Error: Discord API returned HTTP ${http_code}: ${message}" >&2
529
+ exit 1
530
+ ;;
531
+ esac
532
+ }
533
+
534
+ # --- Commands ---
535
+
536
+ validate_snowflake_id() {
537
+ local id="$1"
538
+ local label="$2"
539
+ if [[ ! "$id" =~ ^[0-9]+$ ]]; then
540
+ # The value is never echoed: a caller that put a secret (the webhook URL) in this slot would
541
+ # otherwise see it printed.
542
+ echo "Error: ${label} must be numeric. The value is not shown." >&2
543
+ exit 1
544
+ fi
545
+ }
546
+
547
+ cmd_validate_token() {
548
+ require_token
549
+
550
+ # Verify token via /users/@me
551
+ discord_request "/users/@me" > /dev/null
552
+
553
+ # Get application info for app ID
554
+ local app_info
555
+ app_info=$(discord_request "/oauth2/applications/@me")
556
+
557
+ local app_id app_name
558
+ app_id=$(echo "$app_info" | jq -r '.id // empty')
559
+ app_name=$(echo "$app_info" | jq -r '.name // empty')
560
+
561
+ if [[ -z "$app_id" ]]; then
562
+ echo "Error: Could not extract application ID from Discord API response." >&2
563
+ exit 1
564
+ fi
565
+
566
+ # Output app ID on stdout (used by SKILL.md for OAuth2 URL)
567
+ echo "${app_id}"
568
+ echo "Token valid. Application: ${app_name}" >&2
569
+ }
570
+
571
+ cmd_discover_guilds() {
572
+ require_token
573
+
574
+ discord_request "/users/@me/guilds?with_counts=true" | \
575
+ jq '[.[] | {id, name, approximate_member_count}]'
576
+ }
577
+
578
+ cmd_list_channels() {
579
+ local guild_id="${1:?Usage: discord-setup.sh list-channels <guild_id>}"
580
+ validate_snowflake_id "$guild_id" "guild_id"
581
+ require_token
582
+
583
+ discord_request "/guilds/${guild_id}/channels" | \
584
+ jq '[.[] | select(.type == 0) | {id, name, position}] | sort_by(.position)'
585
+ }
586
+
587
+ cmd_create_webhook() {
588
+ local channel_id="${1:?Usage: discord-setup.sh create-webhook <channel_id>}"
589
+ validate_snowflake_id "$channel_id" "channel_id"
590
+ require_token
591
+
592
+ local result
593
+ result=$(discord_request "/channels/${channel_id}/webhooks" "POST" '{"name":"soleur-community"}')
594
+
595
+ local webhook_id webhook_token
596
+ webhook_id=$(echo "$result" | jq -r '.id // empty')
597
+ webhook_token=$(echo "$result" | jq -r '.token // empty')
598
+
599
+ if [[ -z "$webhook_id" ]] || [[ -z "$webhook_token" ]]; then
600
+ echo "Error: Could not extract webhook ID or token from API response." >&2
601
+ exit 1
602
+ fi
603
+
604
+ # Output the full webhook URL
605
+ echo "https://discord.com/api/webhooks/${webhook_id}/${webhook_token}"
606
+ }
607
+
608
+ # The remedy for a write-env usage error (exit 64). Printed on STDOUT: it is a signal the caller
609
+ # must act on and the command has no stdout payload, and agent runtimes surface stdout and swallow
610
+ # stderr. Both required inputs are named (the webhook URL AND the bot token), so following it once
611
+ # does not produce a second exit 64. The interactive form needs a terminal, so when stdin is not one
612
+ # the form that works without it is named too: the values are read from mode-600 files, so they are
613
+ # never in the text of the command (the transcript, shell history, a wrapper's argument list). An
614
+ # inline `VAR=value command` is deliberately NOT offered. Nothing here prints a value, and the
615
+ # placeholders are literal text.
616
+ _print_env_remedy() {
617
+ echo "Pass the webhook URL and the bot token in the environment, without echoing them. In a terminal:"
618
+ echo " read -rs DISCORD_WEBHOOK_URL_INPUT; export DISCORD_WEBHOOK_URL_INPUT"
619
+ echo " read -rs DISCORD_BOT_TOKEN_INPUT; export DISCORD_BOT_TOKEN_INPUT"
620
+ echo " discord-setup.sh write-env <guild_id>"
621
+ if [ ! -t 0 ]; then
622
+ echo "Your stdin is not a terminal, so read -rs has nothing to read. Do not type either value into the command: its text is kept in the transcript and the shell history. Run the terminal form above yourself, or put each value alone on one line of a mode-600 file and read it from there. Create each file in your own terminal, not through an agent, and type the value at the prompt (end it with Ctrl-D), so the value is never in a command's text and the mode is set before it is written:"
623
+ echo " (umask 077; cat > /path/to/webhook-url.txt)"
624
+ echo " (umask 077; cat > /path/to/bot-token.txt)"
625
+ echo "If a file already exists, run chmod 600 on it before writing: a redirect keeps an existing file's wider mode. Then run the script in one command; the parentheses keep both values out of the shell you stay in:"
626
+ echo " ( read -r DISCORD_WEBHOOK_URL_INPUT < /path/to/webhook-url.txt; read -r DISCORD_BOT_TOKEN_INPUT < /path/to/bot-token.txt; export DISCORD_WEBHOOK_URL_INPUT DISCORD_BOT_TOKEN_INPUT; discord-setup.sh write-env <guild_id> )"
627
+ echo "Instead of typing the webhook URL, the webhook URL file can be written without printing the URL, once the bot token file exists (create-webhook needs the bot token in the environment too): ( read -r DISCORD_BOT_TOKEN_INPUT < /path/to/bot-token.txt; export DISCORD_BOT_TOKEN_INPUT; umask 077; discord-setup.sh create-webhook <channel_id> > /path/to/webhook-url.txt ). Delete both files afterwards: the values stay in them until you do."
628
+ fi
629
+ }
630
+
631
+ cmd_write_env() {
632
+ # (#9597) The webhook URL is a write-capable secret, so it never rides this command line (the
633
+ # process list, the shell history). A second positional is refused BEFORE anything else reads
634
+ # it, whether or not the environment variable is also set (the secret is already on this
635
+ # command line). The argument is never echoed. Exit 64 (usage, text on STDOUT) is distinct from
636
+ # the exit 1 of a value outside the .env allow-list, and covers every missing REQUIRED input of
637
+ # this command: the guild_id, the webhook URL and the bot token, so the exit code names the class.
638
+ # Not covered, and exit 1 on purpose: a guild_id that is present but not numeric
639
+ # (validate_snowflake_id, shared with the other commands, one line on stderr, the value not shown).
640
+ if [[ $# -ge 2 ]]; then
641
+ echo "Error: write-env no longer takes the webhook URL as an argument, because an argument is visible in the process list and your shell history."
642
+ _print_env_remedy
643
+ echo "The argument you passed is not shown. It was already on this command line, so rotate that webhook if anyone else can read your process list or shell history."
644
+ exit 64
645
+ fi
646
+ if [[ $# -eq 0 ]]; then
647
+ echo "Usage: discord-setup.sh write-env <guild_id>"
648
+ echo "Error: guild_id is missing. The webhook URL and the bot token go in DISCORD_WEBHOOK_URL_INPUT and DISCORD_BOT_TOKEN_INPUT, never as arguments."
649
+ exit 64
650
+ fi
651
+ local guild_id="$1"
652
+ if [[ -z "${DISCORD_WEBHOOK_URL_INPUT:-}" ]]; then
653
+ echo "Usage: discord-setup.sh write-env <guild_id>"
654
+ echo "Error: DISCORD_WEBHOOK_URL_INPUT is not set (or is empty). Pass the webhook URL in that environment variable, not as an argument."
655
+ _print_env_remedy
656
+ exit 64
657
+ fi
658
+ validate_snowflake_id "$guild_id" "guild_id"
659
+ if [[ -z "${DISCORD_BOT_TOKEN_INPUT:-}" ]]; then
660
+ echo "Usage: discord-setup.sh write-env <guild_id>"
661
+ echo "Error: DISCORD_BOT_TOKEN_INPUT is not set (or is empty). Pass the bot token in that environment variable, not as an argument."
662
+ _print_env_remedy
663
+ exit 64
664
+ fi
665
+ # Every value is checked BEFORE the first write (see the allow-list above).
666
+ _wenv_validate DISCORD_BOT_TOKEN_INPUT DISCORD_WEBHOOK_URL_INPUT DISCORD_RELEASES_WEBHOOK_URL_INPUT DISCORD_BLOG_WEBHOOK_URL_INPUT
667
+
668
+ local repo_root="$GIT_ROOT"
669
+ local env_file="${repo_root}/.env"
670
+
671
+ # The required three, then the optional channel-specific webhooks if provided.
672
+ local -a kv=(
673
+ "DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN_INPUT}"
674
+ "DISCORD_GUILD_ID=${guild_id}"
675
+ "DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL_INPUT}"
676
+ )
677
+ if [[ -n "${DISCORD_RELEASES_WEBHOOK_URL_INPUT:-}" ]]; then
678
+ kv+=("DISCORD_RELEASES_WEBHOOK_URL=${DISCORD_RELEASES_WEBHOOK_URL_INPUT}")
679
+ fi
680
+ if [[ -n "${DISCORD_BLOG_WEBHOOK_URL_INPUT:-}" ]]; then
681
+ kv+=("DISCORD_BLOG_WEBHOOK_URL=${DISCORD_BLOG_WEBHOOK_URL_INPUT}")
682
+ fi
683
+ # One atomic, serialized, mode-600-from-birth write (see _wenv_write above).
684
+ _wenv_write "$env_file" '^(DISCORD_BOT_TOKEN|DISCORD_GUILD_ID|DISCORD_WEBHOOK_URL|DISCORD_RELEASES_WEBHOOK_URL|DISCORD_BLOG_WEBHOOK_URL)=' "${kv[@]}"
685
+ local var_count=${#kv[@]}
686
+
687
+ # The confirmation is on STDOUT like the rest of write-env's human text (agent runtimes surface
688
+ # stdout and swallow stderr, so a success printed on stderr reads as silence).
689
+ echo "Wrote ${var_count} variables to ${env_file} (permissions: 600)"
690
+ }
691
+
692
+ cmd_verify() {
693
+ # Verify .env configuration by running guild-info
694
+ local repo_root="$GIT_ROOT"
695
+ local env_file="${repo_root}/.env"
696
+
697
+ if [[ ! -f "$env_file" ]]; then
698
+ echo "Error: .env file not found at ${env_file}" >&2
699
+ exit 1
700
+ fi
701
+
702
+ # `source` binds DISCORD_BOT_TOKEN at runtime, AFTER the prologue's guard ran, so the
703
+ # prologue cannot see it. Refuse tracing here, before the credential is bound.
704
+ case "$-" in
705
+ *x*)
706
+ printf 'Refusing to run under `bash -x`: sourcing %s would bind a Discord bot token and tracing would print it to your terminal. To trace safely, re-run without -x.\n' "$env_file"
707
+ exit 78
708
+ ;;
709
+ esac
710
+
711
+ # shellcheck disable=SC1090
712
+ set -a
713
+ source "$env_file"
714
+ set +a
715
+
716
+ if [[ -z "${DISCORD_BOT_TOKEN:-}" ]] || [[ -z "${DISCORD_GUILD_ID:-}" ]]; then
717
+ echo "Error: .env is missing DISCORD_BOT_TOKEN or DISCORD_GUILD_ID." >&2
718
+ exit 1
719
+ fi
720
+
721
+ local script_dir
722
+ script_dir="$(cd "$(dirname "$0")" && pwd)"
723
+
724
+ local community_script="${script_dir}/discord-community.sh"
725
+ if [[ ! -x "$community_script" ]]; then
726
+ echo "Error: Required script not found: ${community_script}" >&2
727
+ exit 1
728
+ fi
729
+
730
+ local guild_info
731
+ guild_info=$("${community_script}" guild-info)
732
+
733
+ local name member_count
734
+ name=$(echo "$guild_info" | jq -r '.name // empty')
735
+ member_count=$(echo "$guild_info" | jq -r '.approximate_member_count // empty')
736
+
737
+ if [[ -z "$name" ]]; then
738
+ echo "Error: Could not extract guild info from API response." >&2
739
+ exit 1
740
+ fi
741
+
742
+ echo "${name}"
743
+ echo "${member_count:-0}"
744
+ }
745
+
746
+ # --- Main ---
747
+
748
+ main() {
749
+ local command="${1:-}"
750
+ shift || true
751
+
752
+ if [[ -z "$command" ]]; then
753
+ echo "Usage: discord-setup.sh <command> [args]" >&2
754
+ echo "" >&2
755
+ echo "Commands:" >&2
756
+ echo " validate-token - Verify token via API, output app ID" >&2
757
+ echo " discover-guilds - List guilds as JSON" >&2
758
+ echo " list-channels <guild_id> - List text channels as JSON" >&2
759
+ echo " create-webhook <channel_id> - Create webhook, output webhook URL" >&2
760
+ echo " write-env <guild_id> - Write to .env with chmod 600 (webhook URL in DISCORD_WEBHOOK_URL_INPUT," >&2
761
+ echo " bot token in DISCORD_BOT_TOKEN_INPUT; with no terminal, read each from a" >&2
762
+ echo " mode-600 file, never typed into the command; write-env prints the exact form)" >&2
763
+ echo " verify - Run guild-info check" >&2
764
+ exit 1
765
+ fi
766
+
767
+ require_jq
768
+
769
+ case "$command" in
770
+ validate-token) cmd_validate_token ;;
771
+ discover-guilds) cmd_discover_guilds ;;
772
+ list-channels) cmd_list_channels "$@" ;;
773
+ create-webhook) cmd_create_webhook "$@" ;;
774
+ write-env) cmd_write_env "$@" ;;
775
+ verify) cmd_verify ;;
776
+ *)
777
+ echo "Error: Unknown command '${command}'" >&2
778
+ echo "Run 'discord-setup.sh' without arguments for usage." >&2
779
+ exit 1
780
+ ;;
781
+ esac
782
+ }
783
+
784
+ main "$@"