@soleur/opencode 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (812) 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 +659 -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 +108 -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 +342 -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 +152 -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 +493 -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 +363 -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 +520 -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 +113 -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 +178 -0
  247. package/scripts/sync-opencode-adapter.ts +479 -0
  248. package/scripts/sync-pr-behind.sh +534 -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 +65 -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 +142 -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 +118 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -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 +668 -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 +325 -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 +138 -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 +1621 -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-todo-structure.md +165 -0
  674. package/skills/review/references/risk-tier-and-fix-rounds.md +95 -0
  675. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  676. package/skills/review/references/ultrathink-phases.md +98 -0
  677. package/skills/review/references/wfs.md +6 -0
  678. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  679. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  680. package/skills/review/scripts/fix-round-seats.sh +197 -0
  681. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  682. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  683. package/skills/review/test/fix-round-seats.test.sh +237 -0
  684. package/skills/review/workflows/README.md +147 -0
  685. package/skills/review/workflows/review.workflow.js +667 -0
  686. package/skills/schedule/SKILL.md +796 -0
  687. package/skills/seo-aeo/SKILL.md +139 -0
  688. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  689. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  690. package/skills/ship/SKILL.md +3022 -0
  691. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  692. package/skills/ship/references/affected-gate-contract.md +115 -0
  693. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  694. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  695. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  696. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  697. package/skills/ship/references/decision-challenges-render.md +71 -0
  698. package/skills/ship/references/detached-battery-verdict.md +13 -0
  699. package/skills/ship/references/feature-tweet-draft.md +42 -0
  700. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  701. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  702. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  703. package/skills/ship/references/operator-holds.md +149 -0
  704. package/skills/ship/references/pipeline-tally-render.md +43 -0
  705. package/skills/ship/references/pr-overlap.md +153 -0
  706. package/skills/ship/references/queue-mode.md +7 -0
  707. package/skills/ship/references/ready-run-wait.md +36 -0
  708. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  709. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  710. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  711. package/skills/ship/references/supabase-column-probe.md +13 -0
  712. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  713. package/skills/ship/scripts/battery-owed.sh +403 -0
  714. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  715. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  716. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  717. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  718. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  719. package/skills/skill-creator/SKILL.md +299 -0
  720. package/skills/skill-creator/references/api-security.md +248 -0
  721. package/skills/skill-creator/references/authoring-levers.md +110 -0
  722. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  723. package/skills/skill-creator/references/best-practices.md +431 -0
  724. package/skills/skill-creator/references/common-patterns.md +639 -0
  725. package/skills/skill-creator/references/core-principles.md +193 -0
  726. package/skills/skill-creator/references/executable-code.md +188 -0
  727. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  728. package/skills/skill-creator/references/official-spec.md +193 -0
  729. package/skills/skill-creator/references/recommended-structure.md +180 -0
  730. package/skills/skill-creator/references/skill-structure.md +396 -0
  731. package/skills/skill-creator/references/using-scripts.md +122 -0
  732. package/skills/skill-creator/references/using-templates.md +120 -0
  733. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  734. package/skills/skill-creator/scripts/init_skill.py +322 -0
  735. package/skills/skill-creator/scripts/package_skill.py +110 -0
  736. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  737. package/skills/skill-creator/templates/router-skill.md +73 -0
  738. package/skills/skill-creator/templates/simple-skill.md +34 -0
  739. package/skills/skill-creator/workflows/add-reference.md +96 -0
  740. package/skills/skill-creator/workflows/add-script.md +104 -0
  741. package/skills/skill-creator/workflows/add-template.md +83 -0
  742. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  743. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  744. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  745. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  746. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  747. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  748. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  749. package/skills/skill-security-scan/SKILL.md +145 -0
  750. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  751. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  752. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  753. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  754. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  755. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  756. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  757. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  758. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  759. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  760. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  761. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  762. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  763. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  764. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  765. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  766. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  767. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  768. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  769. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  770. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  771. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  772. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  773. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  774. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  775. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  776. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  777. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  778. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  779. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  780. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  781. package/skills/social-distribute/SKILL.md +485 -0
  782. package/skills/spec-templates/SKILL.md +274 -0
  783. package/skills/sync/SKILL.md +49 -0
  784. package/skills/test-browser/SKILL.md +464 -0
  785. package/skills/test-browser/references/feature-map.md +161 -0
  786. package/skills/test-fix-loop/SKILL.md +159 -0
  787. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  788. package/skills/triage/SKILL.md +402 -0
  789. package/skills/trigger-cron/SKILL.md +136 -0
  790. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  791. package/skills/user-set-role/SKILL.md +147 -0
  792. package/skills/user-set-role/scripts/set-role.sh +218 -0
  793. package/skills/user-story-writer/SKILL.md +77 -0
  794. package/skills/ux-audit/SKILL.md +251 -0
  795. package/skills/ux-audit/references/finding.schema.json +62 -0
  796. package/skills/ux-audit/references/route-list.yaml +95 -0
  797. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  798. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  799. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  800. package/skills/work/SKILL.md +1479 -0
  801. package/skills/work/references/end-of-work-resume.md +7 -0
  802. package/skills/work/references/hook-test-main-cwd.md +24 -0
  803. package/skills/work/references/key-principles.md +83 -0
  804. package/skills/work/references/property-testing.md +122 -0
  805. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  806. package/skills/work/references/work-agent-teams.md +97 -0
  807. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  808. package/skills/work/references/work-lockfile-bumps.md +97 -0
  809. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  810. package/skills/work/references/work-subagent-fanout.md +61 -0
  811. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  812. package/skills/xcode-test/SKILL.md +360 -0
@@ -0,0 +1,750 @@
1
+ #!/usr/bin/env bash
2
+ # linkedin-community.sh -- LinkedIn API wrapper for community operations
3
+ #
4
+ # Usage: linkedin-community.sh <command> [args]
5
+ # Commands:
6
+ # post-content --text "<text>" [--author "<urn>"] - Post to LinkedIn (person or company page)
7
+ # fetch-metrics - Fetch aggregate Company Page metrics (org read scopes)
8
+ # fetch-activity - Fetch recent Company Page post metadata (org read scopes)
9
+ #
10
+ # fetch-metrics / fetch-activity history: implemented 2026-06-15. The prior
11
+ # "requires Marketing Developer Platform (MDP) partner approval" premise was
12
+ # incorrect — the org token already carries the read scopes
13
+ # (r_organization_social, rw_organization_admin, r_organization_followers).
14
+ # Collection is aggregate-only: share statistics + a single follower total +
15
+ # recent org-authored post metadata. NO per-member data, NO follower-list
16
+ # extraction, NO demographic facets.
17
+ #
18
+ # Environment variables (required):
19
+ # LINKEDIN_ACCESS_TOKEN - OAuth 2.0 Bearer token for personal posts (60-day TTL)
20
+ # LINKEDIN_ORG_ACCESS_TOKEN - OAuth 2.0 Bearer token for organization/company page
21
+ # operations. Required for fetch-metrics/fetch-activity
22
+ # (org read scopes: r_organization_social,
23
+ # rw_organization_admin) and for org posts
24
+ # (w_organization_social, --author urn:li:organization:*).
25
+ # LINKEDIN_ORG_ID - Numeric organization id. Required for
26
+ # fetch-metrics/fetch-activity (builds the org URN).
27
+ # LINKEDIN_PERSON_URN - Person URN for posting (urn:li:person:{id}), optional if --author provided
28
+ # LINKEDIN_ALLOW_POST - Set to "true" to enable posting (safety guard, default: disabled)
29
+ #
30
+ # Exit codes:
31
+ # 0 - Success
32
+ # 1 - General error
33
+ # 2 - Retryable error (rate limit exhaustion)
34
+ #
35
+ # Output: JSON to stdout
36
+ # Errors: Messages to stderr, exit 1
37
+
38
+ set -euo pipefail
39
+
40
+ # (#7797) Xtrace refusal -- the FIRST thing after `set …`, so nothing above it is
41
+ # traced. This script ships to installed Soleur CLIs: the terminal it runs in
42
+ # belongs to the founder, and `bash -x` would print their live LinkedIn
43
+ # credentials. The `:+x` form tests non-emptiness WITHOUT expanding the value, so
44
+ # the guard cannot leak the thing it is refusing over. CONDITIONAL, and that is
45
+ # measured rather than inherited: every credential this file handles is bound
46
+ # BEFORE the prologue runs (this script's own credential check reads them from
47
+ # the environment, it never acquires one at runtime), so the guard can never be
48
+ # empty while a credential is live -- and a founder tracing with none set keeps
49
+ # full tracing.
50
+ # Refusal goes to STDOUT, not stderr: agent runtimes surface stdout and swallow
51
+ # stderr (constitution.md > Code Style > Always), and a swallowed security refusal
52
+ # leaves the user with a bare `exit 78` and no text at all.
53
+ case "$-" in
54
+ *x*)
55
+ if [ -n "${LINKEDIN_ACCESS_TOKEN:+x}${LINKEDIN_ORG_ACCESS_TOKEN:+x}" ]; then
56
+ printf 'Refusing to run under `bash -x`: LINKEDIN_ACCESS_TOKEN, LINKEDIN_ORG_ACCESS_TOKEN are set, and tracing would print them to your terminal. To trace safely, unset all of them and re-run.\n'
57
+ exit 78
58
+ fi
59
+ ;;
60
+ esac
61
+
62
+ # (#7873) `--disable` closes ~/.curlrc and `--noproxy '*'` closes the proxy
63
+ # variables, but NEITHER touches the trust store or the TLS session-key log. On an
64
+ # installed CLI the environment belongs to someone who is not us, so
65
+ # a substituted `CURL_CA_BUNDLE` is a clean MITM of the founder's platform
66
+ # token with every other guard fully intact, and `SSLKEYLOGFILE` is passive
67
+ # decryption with no MITM at all. Matches the line in scripts/betterstack-query.sh.
68
+ unset SSLKEYLOGFILE CURL_CA_BUNDLE SSL_CERT_FILE SSL_CERT_DIR CURL_HOME \
69
+ HOSTALIASES LOCALDOMAIN RES_OPTIONS
70
+
71
+ # --- Transport-confinement diagnostics (#7873) --------------------------------
72
+ # Every credentialed `curl` below carries `--disable --noproxy '*'` and discards
73
+ # curl's own stderr (which can carry the URL). A failed request then has four
74
+ # competing causes the founder cannot tell apart: the xtrace refusal, the proxy
75
+ # this script deliberately bypassed, a ~/.curlrc that `--disable` dropped, or an
76
+ # ordinary network failure. So each failure emits ONE structured marker carrying
77
+ # all four discriminators, beside a human line that names the bypass instead of
78
+ # blaming the founder's connectivity.
79
+ SOLEUR_TRANSPORT_SCRIPT="linkedin-community.sh"
80
+ SOLEUR_TRANSPORT_PLATFORM="LinkedIn"
81
+
82
+ # The request helpers below run inside `$(...)`, where fd 1 is the capture pipe.
83
+ # Save the script's real stdout so the marker reaches the founder's terminal (and
84
+ # the always-on PostToolUse Bash extractor) instead of being swallowed into a
85
+ # variable. The marker is on stdout BY DESIGN -- the call sites' `2>/dev/null` is
86
+ # what keeps curl's URL-carrying stderr out of the transcript, and it must not be
87
+ # able to suppress the diagnostic too.
88
+ exec 3>&1
89
+
90
+ # Which proxy variables are non-empty, or `none`.
91
+ proxy_env_names() {
92
+ local names="" n
93
+ for n in HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy; do
94
+ if [[ -n "${!n-}" ]]; then
95
+ names="${names},${n}"
96
+ fi
97
+ done
98
+ if [[ -z "$names" ]]; then
99
+ printf 'none'
100
+ else
101
+ printf '%s' "${names#,}"
102
+ fi
103
+ }
104
+
105
+ # True when a proxy this script deliberately bypasses is configured. NO_PROXY
106
+ # alone is not one -- it only ever narrows proxying, so it cannot be the cause.
107
+ proxy_bypassed() {
108
+ [[ -n "${HTTP_PROXY:-}${HTTPS_PROXY:-}${ALL_PROXY:-}${http_proxy:-}${https_proxy:-}${all_proxy:-}" ]]
109
+ }
110
+
111
+ # One line, eight fields, on the saved stdout.
112
+ #
113
+ # Two fields were literals in the first cut and are now MEASURED (#7898 review):
114
+ # `noproxy_applied` was the constant "true", which ASSERTS the property instead of
115
+ # observing it -- dropping --noproxy from a call site left the diagnostic still
116
+ # claiming it was applied. It now reports whether a proxy was actually configured
117
+ # for this request to bypass, which is the fact a reader needs. `refusal` was the
118
+ # constant "none" at every call site, so the cause it exists to discriminate could
119
+ # never appear; it now carries `env-rebind-refused` and `xtrace-credential-bound`.
120
+ #
121
+ # `tls_env_cleared` is new. The prologue unsets CURL_CA_BUNDLE/SSL_CERT_FILE et al,
122
+ # which is correct against an attacker and BREAKS a founder whose corporate CA
123
+ # arrives that way -- curl then exits 60 and, without this field, the event is
124
+ # indistinguishable from an ordinary network failure.
125
+ emit_transport_diag() {
126
+ local curl_exit="$1" refusal="$2" surface="installed-cli" curlrc="false" noproxy="false"
127
+ # The hosted sandbox always sets this (agent-env.ts > AGENT_ENV_OVERRIDES); an
128
+ # installed CLI does not. A label on the event, not a gate on behaviour.
129
+ if [[ -n "${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-}" ]]; then
130
+ surface="in-sandbox"
131
+ fi
132
+ if [[ -f "${HOME:-}/.curlrc" ]]; then
133
+ curlrc="true"
134
+ fi
135
+ if proxy_bypassed; then
136
+ noproxy="true"
137
+ fi
138
+ 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' \
139
+ "$surface" "$SOLEUR_TRANSPORT_SCRIPT" "$curl_exit" "$refusal" \
140
+ "$(proxy_env_names)" "$curlrc" "$noproxy" "true" >&3
141
+ }
142
+
143
+ # Replaces the bare "Check your network connection and try again." on a
144
+ # credentialed-curl failure. With a proxy set, that line blames the founder for a
145
+ # bypass this script chose.
146
+ report_transport_failure() {
147
+ local curl_exit="${1:-unknown}"
148
+ local detail="${2:-Failed to connect to the ${SOLEUR_TRANSPORT_PLATFORM} API.}"
149
+ emit_transport_diag "$curl_exit" "none"
150
+ echo "Error: ${detail}" >&2
151
+ # Ordered by how specific the evidence is. Each arm names something THIS script
152
+ # did, so the founder is never told to go debug their own network for a choice
153
+ # made here. The bare "check your connection" line is the LAST resort, not the
154
+ # default -- it was the default in the first cut, which meant a founder whose
155
+ # corporate CA or curlrc we had just discarded was blamed for it (#7898 review).
156
+ if [[ "$curl_exit" == "60" || "$curl_exit" == "35" || "$curl_exit" == "77" ]]; then
157
+ # 60 peer-certificate, 35 TLS handshake, 77 CA-bundle unreadable.
158
+ 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
159
+ 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
160
+ elif proxy_bypassed; then
161
+ echo "This request deliberately bypasses your proxy ($(proxy_env_names)), because a proxy can redirect a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} token." >&2
162
+ 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
163
+ elif [[ -f "${HOME:-}/.curlrc" ]]; then
164
+ 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
165
+ echo "That is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
166
+ else
167
+ echo "Check your network connection and try again." >&2
168
+ fi
169
+ }
170
+
171
+ # readonly (#7898 review): `cmd_verify` and friends run `set -a; source "$env_file"; set +a`
172
+ # BELOW this line, so a plain assignment here is rebindable by the repo's .env --
173
+ # which retargets the credentialed request while every transport flag stays intact.
174
+ # That is the "--disable and --noproxy are intact and irrelevant" shape this change
175
+ # exists to close, one layer up. readonly makes the destination non-rebindable.
176
+ readonly LINKEDIN_API="https://api.linkedin.com"
177
+ LINKEDIN_API_VERSION="202602"
178
+ LINKEDIN_POST_MAX_LENGTH=3000
179
+
180
+ # --- Bearer transport (#7843) ---
181
+ # The access token is written into curl's STDIN config channel (`--config -`), never its
182
+ # argument list, where every local user would read it from /proc/<pid>/cmdline. That
183
+ # channel is line-oriented, so a token holding a quote and a newline could append a
184
+ # `url = "..."` directive and make curl issue a second request. The guard refuses
185
+ # anything outside the token alphabet BEFORE the value is formatted into the stream, and
186
+ # never echoes it.
187
+ _bearer_ok() { local LC_ALL=C; case "${1:-}" in ''|*[!A-Za-z0-9._~+/=-]*) return 1 ;; esac; }
188
+
189
+ # Every authenticated request goes through here: ONE place owns the transport flags
190
+ # (`--disable` first, `--noproxy '*'`), the token-shape guard and the header. Returns 120
191
+ # with zero curl calls when the token is unusable; callers map that to a refusal. The
192
+ # token is read as LINKEDIN_ACCESS_TOKEN at call time, so the function-local org-token
193
+ # rebinding in cmd_fetch_metrics / cmd_fetch_activity keeps working.
194
+ linkedin_authed_curl() {
195
+ _bearer_ok "${LINKEDIN_ACCESS_TOKEN:-}" || return 120
196
+ curl --disable --noproxy '*' "$@" --config - \
197
+ < <(printf 'header = "Authorization: Bearer %s"\n' "$LINKEDIN_ACCESS_TOKEN")
198
+ }
199
+
200
+ # --- Dependency checks ---
201
+
202
+ require_jq() {
203
+ if ! command -v jq &>/dev/null; then
204
+ echo "Error: jq is required but not installed." >&2
205
+ echo "Install it: https://jqlang.github.io/jq/download/" >&2
206
+ exit 1
207
+ fi
208
+ }
209
+
210
+ # --- Credential validation ---
211
+
212
+ require_credentials() {
213
+ if [[ -z "${LINKEDIN_ACCESS_TOKEN:-}" ]]; then
214
+ echo "Error: Missing LinkedIn credentials: LINKEDIN_ACCESS_TOKEN" >&2
215
+ echo "" >&2
216
+ echo "To configure:" >&2
217
+ echo " 1. Run: linkedin-setup.sh generate-token" >&2
218
+ echo " 2. Or set LINKEDIN_ACCESS_TOKEN manually" >&2
219
+ echo " 3. Run: linkedin-setup.sh verify" >&2
220
+ exit 1
221
+ fi
222
+ }
223
+
224
+ # Require the organization read credentials for fetch-metrics/fetch-activity.
225
+ # Fails LOUD with `exit 1` (NOT `return 1`): the script runs `set -euo pipefail`,
226
+ # and a `return 1` consumed in a conditional/pipeline does NOT terminate, which
227
+ # would re-open the silent fall-through. Mirrors require_credentials (exit 1),
228
+ # NOT cmd_post_content's return-1 guard.
229
+ #
230
+ # NEVER silent-fallback to the personal LINKEDIN_ACCESS_TOKEN — that token is
231
+ # live in the cron spawn env and lacks the org read scopes; falling back yields
232
+ # an opaque 401/400 instead of a clear "missing org creds" error
233
+ # (learning 2026-04-26-linkedin-org-token-fallback-silent-400.md).
234
+ require_org_credentials() {
235
+ local missing=()
236
+ [[ -n "${LINKEDIN_ORG_ACCESS_TOKEN:-}" ]] || missing+=("LINKEDIN_ORG_ACCESS_TOKEN")
237
+ # Fail-closed numeric check: a non-numeric (or unset) org id must never be
238
+ # interpolated into the request URL. When unset/empty the message names the
239
+ # bare var (matching the existing unset path); when set-but-non-numeric it
240
+ # names the constraint so the operator can fix it.
241
+ if [[ -z "${LINKEDIN_ORG_ID:-}" ]]; then
242
+ missing+=("LINKEDIN_ORG_ID")
243
+ elif [[ ! "${LINKEDIN_ORG_ID}" =~ ^[0-9]+$ ]]; then
244
+ missing+=("LINKEDIN_ORG_ID (must be numeric)")
245
+ fi
246
+ if (( ${#missing[@]} > 0 )); then
247
+ echo "Error: Missing LinkedIn organization credentials: ${missing[*]}" >&2
248
+ echo "fetch-metrics/fetch-activity read the operator's own Company Page" >&2
249
+ echo "aggregate insights and require the org token + org id." >&2
250
+ echo "Set LINKEDIN_ORG_ACCESS_TOKEN (org read scopes) and LINKEDIN_ORG_ID." >&2
251
+ exit 1
252
+ fi
253
+ }
254
+
255
+ # --- Response handler ---
256
+
257
+ # Handle HTTP response status codes from LinkedIn API
258
+ # Arguments: http_code body endpoint depth retry_cmd...
259
+ # On 2xx: validates JSON, echoes body to stdout
260
+ # On 429: sleeps and invokes retry_cmd (caller with incremented depth)
261
+ # On error: prints diagnostic to stderr, exits 1 (or 2 for rate limit exhaustion)
262
+ handle_response() {
263
+ local http_code="$1"
264
+ local body="$2"
265
+ local endpoint="$3"
266
+ local depth="$4"
267
+ shift 4
268
+ local -a retry_cmd=("$@")
269
+
270
+ case "$http_code" in
271
+ 2[0-9][0-9])
272
+ # LinkedIn POST /rest/posts returns 201 with empty body -- that is valid
273
+ if [[ -n "$body" ]] && ! echo "$body" | jq . >/dev/null 2>&1; then
274
+ echo "Error: LinkedIn API returned malformed JSON for ${endpoint}" >&2
275
+ exit 1
276
+ fi
277
+ echo "$body"
278
+ ;;
279
+ 401)
280
+ echo "Error: LinkedIn API returned 401 Unauthorized for ${endpoint}." >&2
281
+ echo "Your access token may be expired (60-day TTL)." >&2
282
+ echo "" >&2
283
+ echo "To fix:" >&2
284
+ echo " 1. Run: linkedin-setup.sh validate-credentials" >&2
285
+ echo " 2. If expired, run: linkedin-setup.sh generate-token" >&2
286
+ exit 1
287
+ ;;
288
+ 403)
289
+ local message
290
+ message=$(echo "$body" | jq -r '.message // .code // "Access denied"' 2>/dev/null || echo "Access denied")
291
+ echo "Error: LinkedIn API returned 403 Forbidden for ${endpoint}: ${message}" >&2
292
+ exit 1
293
+ ;;
294
+ 429)
295
+ if (( depth >= 3 )); then
296
+ echo "Error: LinkedIn API rate limit exceeded after 3 retries for ${endpoint}." >&2
297
+ exit 2
298
+ fi
299
+ echo "Rate limited. Retrying after 5s (attempt $((depth + 1))/3)..." >&2
300
+ sleep 5
301
+ "${retry_cmd[@]}"
302
+ ;;
303
+ *)
304
+ local message
305
+ message=$(echo "$body" | jq -r '.message // .code // "Unknown error"' 2>/dev/null || echo "Unknown error")
306
+ echo "Error: LinkedIn API returned HTTP ${http_code} for ${endpoint}: ${message}" >&2
307
+ exit 1
308
+ ;;
309
+ esac
310
+ }
311
+
312
+ # --- GET request helper ---
313
+
314
+ # Make an authenticated GET request to the LinkedIn API
315
+ # Arguments: endpoint [depth] [extra_header]
316
+ # extra_header - optional single "Name: value" header (e.g. the
317
+ # "X-RestLi-Method: FINDER" header the Posts author-finder
318
+ # requires). Forwarded on the 429-retry recursion so a retry
319
+ # never silently drops it.
320
+ # Retries on 429 up to 3 times
321
+ get_request() {
322
+ local endpoint="$1"
323
+ local depth="${2:-0}"
324
+ local extra_header="${3:-}"
325
+
326
+ if (( depth >= 3 )); then
327
+ echo "Error: LinkedIn API rate limit exceeded after 3 retries for ${endpoint}." >&2
328
+ exit 2
329
+ fi
330
+
331
+ local url="${LINKEDIN_API}${endpoint}"
332
+
333
+ local -a header_args=()
334
+ if [[ -n "$extra_header" ]]; then
335
+ header_args=(-H "$extra_header")
336
+ fi
337
+
338
+ local response http_code body
339
+ local __curl_rc=0
340
+ response=$(linkedin_authed_curl -s -w "\n%{http_code}" \
341
+ -H "X-Restli-Protocol-Version: 2.0.0" \
342
+ -H "LinkedIn-Version: ${LINKEDIN_API_VERSION}" \
343
+ "${header_args[@]}" \
344
+ "$url" 2>/dev/null) || __curl_rc=$?
345
+ if (( __curl_rc == 120 )); then
346
+ echo "Error: LinkedIn access token has an unexpected shape; refusing to send it." >&2
347
+ exit 1
348
+ fi
349
+ if (( __curl_rc != 0 )); then
350
+ report_transport_failure "$__curl_rc" "Failed to connect to LinkedIn API."
351
+ exit 1
352
+ fi
353
+
354
+ http_code=$(echo "$response" | tail -1)
355
+ body=$(echo "$response" | sed '$d')
356
+
357
+ handle_response "$http_code" "$body" "$endpoint" "$depth" \
358
+ get_request "$endpoint" "$((depth + 1))" "$extra_header"
359
+ }
360
+
361
+ # --- POST request helper ---
362
+
363
+ # Make an authenticated POST request to the LinkedIn API
364
+ # Arguments: endpoint json_body [depth]
365
+ # Captures response headers via temp file to extract x-restli-id
366
+ # Only retries on 429 (POST is not idempotent)
367
+ post_request() {
368
+ local endpoint="$1"
369
+ local json_body="$2"
370
+ local depth="${3:-0}"
371
+
372
+ local url="${LINKEDIN_API}${endpoint}"
373
+
374
+ local header_file
375
+ header_file=$(mktemp) || { echo "Error: Failed to create temp file." >&2; exit 1; }
376
+ # shellcheck disable=SC2064
377
+ trap "rm -f '$header_file'" EXIT
378
+
379
+ local response http_code body
380
+ local __curl_rc=0
381
+ response=$(linkedin_authed_curl -s -w "\n%{http_code}" \
382
+ -D "$header_file" \
383
+ -H "X-Restli-Protocol-Version: 2.0.0" \
384
+ -H "LinkedIn-Version: ${LINKEDIN_API_VERSION}" \
385
+ -H "Content-Type: application/json" \
386
+ -X POST -d "$json_body" \
387
+ "$url" 2>/dev/null) || __curl_rc=$?
388
+ if (( __curl_rc == 120 )); then
389
+ rm -f "$header_file"
390
+ echo "Error: LinkedIn access token has an unexpected shape; refusing to send it." >&2
391
+ exit 1
392
+ fi
393
+ if (( __curl_rc != 0 )); then
394
+ rm -f "$header_file"
395
+ report_transport_failure "$__curl_rc" "Failed to connect to LinkedIn API."
396
+ exit 1
397
+ fi
398
+
399
+ http_code=$(echo "$response" | tail -1)
400
+ body=$(echo "$response" | sed '$d')
401
+
402
+ # On 429, retry (rate limit returned before post is created)
403
+ if [[ "$http_code" == "429" ]]; then
404
+ rm -f "$header_file"
405
+ handle_response "$http_code" "$body" "$endpoint" "$depth" \
406
+ post_request "$endpoint" "$json_body" "$((depth + 1))"
407
+ return
408
+ fi
409
+
410
+ # For non-429 errors on POST, fail immediately (non-idempotent)
411
+ if [[ ! "$http_code" =~ ^2[0-9][0-9]$ ]]; then
412
+ rm -f "$header_file"
413
+ handle_response "$http_code" "$body" "$endpoint" "$depth"
414
+ return
415
+ fi
416
+
417
+ # Success: extract x-restli-id from response headers
418
+ local restli_id=""
419
+ if [[ -f "$header_file" ]]; then
420
+ restli_id=$(grep -i '^x-restli-id:' "$header_file" | sed 's/^[^:]*: *//' | tr -d '\r' || true)
421
+ fi
422
+ rm -f "$header_file"
423
+
424
+ # Return JSON with post URN
425
+ if [[ -n "$restli_id" ]]; then
426
+ jq -n --arg id "$restli_id" '{"post_urn": $id}'
427
+ else
428
+ echo '{"post_urn": null}' >&2
429
+ echo "Warning: Post created but x-restli-id header not found in response." >&2
430
+ # Still return valid JSON
431
+ echo '{}'
432
+ fi
433
+ }
434
+
435
+ # --- LinkedIn "Little Text Format" handling ---
436
+ #
437
+ # The Posts API commentary field is "Little Text Format": the characters
438
+ # \ | { } [ ] ( ) < > * ~ are reserved, and LinkedIn silently TRUNCATES the
439
+ # post at the first unescaped occurrence (observed 2026-06-10: nine org posts
440
+ # cut mid-sentence at the first "(", which also dropped the trailing article
441
+ # links). '#' and '@' stay unescaped so hashtags and mentions keep their
442
+ # linking behavior; '_' stays unescaped because escaping it corrupts URLs
443
+ # (utm_source=...) and it does not trigger the truncation failure class.
444
+
445
+ sanitize_commentary() {
446
+ # Markdown-source artifacts that must not reach LinkedIn verbatim: strip
447
+ # whole-line HTML comments (e.g. markdownlint directives), unwrap
448
+ # <https://...> autolinks (angle brackets are reserved characters), and
449
+ # drop trailing horizontal rules / blank lines.
450
+ sed -e '/^[[:space:]]*<!--.*-->[[:space:]]*$/d' \
451
+ -e 's/<\(https\{0,1\}:\/\/[^>]*\)>/\1/g' |
452
+ awk '{ lines[++n] = $0 }
453
+ END {
454
+ while (n > 0 && (lines[n] ~ /^[[:space:]]*$/ || lines[n] ~ /^[[:space:]]*-{3,}[[:space:]]*$/)) n--
455
+ for (i = 1; i <= n; i++) print lines[i]
456
+ }'
457
+ }
458
+
459
+ escape_little_text() {
460
+ local t="$1"
461
+ t="${t//\\/\\\\}"
462
+ t="${t//|/\\|}"
463
+ t="${t//\{/\\\{}"
464
+ t="${t//\}/\\\}}"
465
+ t="${t//\[/\\[}"
466
+ t="${t//\]/\\]}"
467
+ t="${t//(/\\(}"
468
+ t="${t//)/\\)}"
469
+ t="${t//</\\<}"
470
+ t="${t//>/\\>}"
471
+ t="${t//\*/\\*}"
472
+ t="${t//\~/\\~}"
473
+ printf '%s' "$t"
474
+ }
475
+
476
+ # --- Commands ---
477
+
478
+ cmd_post_content() {
479
+ # Guard: require explicit opt-in to post.
480
+ if [[ "${LINKEDIN_ALLOW_POST:-}" != "true" ]]; then
481
+ echo "Error: LINKEDIN_ALLOW_POST is not set to 'true'." >&2
482
+ echo "Set LINKEDIN_ALLOW_POST=true to enable posting." >&2
483
+ return 1
484
+ fi
485
+
486
+ local text=""
487
+ local author_override=""
488
+
489
+ while [[ $# -gt 0 ]]; do
490
+ case "$1" in
491
+ --text)
492
+ text="${2:-}"
493
+ if [[ -z "$text" ]]; then
494
+ echo "Error: --text requires a non-empty value." >&2
495
+ exit 1
496
+ fi
497
+ shift 2
498
+ ;;
499
+ --author)
500
+ author_override="${2:-}"
501
+ if [[ -z "$author_override" ]]; then
502
+ echo "Error: --author requires a non-empty value." >&2
503
+ exit 1
504
+ fi
505
+ shift 2
506
+ ;;
507
+ *)
508
+ echo "Error: Unknown option '$1'" >&2
509
+ echo "Usage: linkedin-community.sh post-content --text \"<text>\" [--author \"<urn>\"]" >&2
510
+ exit 1
511
+ ;;
512
+ esac
513
+ done
514
+
515
+ if [[ -z "$text" ]]; then
516
+ echo "Error: --text is required." >&2
517
+ echo "Usage: linkedin-community.sh post-content --text \"<text>\"" >&2
518
+ exit 1
519
+ fi
520
+
521
+ # Strip markdown-source artifacts before the length check (the limit
522
+ # applies to visible characters; Little Text escape backslashes added
523
+ # below do not count toward it).
524
+ text=$(printf '%s\n' "$text" | sanitize_commentary)
525
+
526
+ if (( ${#text} > LINKEDIN_POST_MAX_LENGTH )); then
527
+ echo "Error: Post text is ${#text} characters, exceeds LinkedIn's ${LINKEDIN_POST_MAX_LENGTH}-character limit." >&2
528
+ exit 1
529
+ fi
530
+
531
+ local escaped_text
532
+ escaped_text=$(escape_little_text "$text")
533
+
534
+ # Build request body (--author overrides default LINKEDIN_PERSON_URN)
535
+ local author="${author_override:-${LINKEDIN_PERSON_URN:-}}"
536
+ if [[ -z "$author" ]]; then
537
+ echo "Error: No author specified. Provide --author or set LINKEDIN_PERSON_URN." >&2
538
+ exit 1
539
+ fi
540
+
541
+ # Normalize bare person IDs: LinkedIn API requires full URN (urn:li:person:<id>).
542
+ # LINKEDIN_PERSON_URN is often stored as just the ID portion without the prefix.
543
+ if [[ "$author" != urn:* ]]; then
544
+ author="urn:li:person:${author}"
545
+ fi
546
+
547
+ # Organization posts require w_organization_social scope -- use LINKEDIN_ORG_ACCESS_TOKEN.
548
+ if [[ "$author" == urn:li:organization:* ]]; then
549
+ if [[ -z "${LINKEDIN_ORG_ACCESS_TOKEN:-}" ]]; then
550
+ echo "Error: LINKEDIN_ORG_ACCESS_TOKEN is required for organization posts (w_organization_social scope)." >&2
551
+ echo "Set LINKEDIN_ORG_ACCESS_TOKEN to a token with w_organization_social scope." >&2
552
+ exit 1
553
+ fi
554
+ LINKEDIN_ACCESS_TOKEN="$LINKEDIN_ORG_ACCESS_TOKEN"
555
+ fi
556
+
557
+ local json_body
558
+ json_body=$(jq -n \
559
+ --arg author "$author" \
560
+ --arg text "$escaped_text" \
561
+ '{
562
+ author: $author,
563
+ commentary: $text,
564
+ visibility: "PUBLIC",
565
+ distribution: {
566
+ feedDistribution: "MAIN_FEED",
567
+ targetEntities: [],
568
+ thirdPartyDistributionChannels: []
569
+ },
570
+ lifecycleState: "PUBLISHED",
571
+ isReshareDisabledByAuthor: false
572
+ }')
573
+
574
+ local result
575
+ result=$(post_request "/rest/posts" "$json_body")
576
+
577
+ echo "$result"
578
+ echo "Post created successfully." >&2
579
+ }
580
+
581
+ # Fetch aggregate Company Page metrics: lifetime share statistics + a single
582
+ # follower total. Aggregate-only — no demographic facets, no per-member data.
583
+ cmd_fetch_metrics() {
584
+ # Defense-in-depth: re-check org creds as the FIRST line (dispatch also checks).
585
+ # The personal LINKEDIN_ACCESS_TOKEN is live in the spawn env, so a direct
586
+ # call to this function must not be able to fall through to it.
587
+ require_org_credentials
588
+
589
+ # Scope the org token function-locally so get_request's Bearer header uses it
590
+ # WITHOUT leaking into the personal path (safer than cmd_post_content's global
591
+ # mutation at the org-post branch).
592
+ local LINKEDIN_ACCESS_TOKEN="$LINKEDIN_ORG_ACCESS_TOKEN"
593
+
594
+ local org_urn="urn%3Ali%3Aorganization%3A${LINKEDIN_ORG_ID}"
595
+
596
+ # --- Aggregate share statistics (lifetime). ---
597
+ local share_body
598
+ share_body=$(get_request \
599
+ "/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=${org_urn}")
600
+
601
+ # Shape validation BEFORE any `// 0` fallbacks (silent-failure HIGH-1):
602
+ # handle_response validated only JSON parseability, not shape. An empty
603
+ # `.elements` (e.g. token lacks ADMINISTRATOR role) or a wrong shape must NOT
604
+ # render as fake zeros — emit an explicit error and exit 1.
605
+ if ! echo "$share_body" | jq -e '(.elements[0].totalShareStatistics | type) == "object"' >/dev/null 2>&1; then
606
+ echo "Error: organizationalEntityShareStatistics returned no usable totalShareStatistics for org ${LINKEDIN_ORG_ID}." >&2
607
+ echo "The org token may lack the ADMINISTRATOR role, or the response shape changed." >&2
608
+ exit 1
609
+ fi
610
+
611
+ local share_statistics
612
+ share_statistics=$(echo "$share_body" | jq '
613
+ .elements[0].totalShareStatistics as $s |
614
+ {
615
+ impressions: ($s.impressionCount // 0),
616
+ unique_impressions: ($s.uniqueImpressionsCount // 0),
617
+ clicks: ($s.clickCount // 0),
618
+ likes: ($s.likeCount // 0),
619
+ comments: ($s.commentCount // 0),
620
+ shares: ($s.shareCount // 0),
621
+ engagement: ($s.engagement // 0)
622
+ }')
623
+
624
+ # --- Single follower total via networkSizes. ---
625
+ # Partial-failure policy (silent-failure HIGH-2): the networkSizes call must
626
+ # not abort the (more important) share-stats result. Run it in a subshell whose
627
+ # non-zero exit is tolerated; on failure total_followers degrades to null with
628
+ # a stderr warning rather than get_request's default `exit`.
629
+ local total_followers="null"
630
+ local network_body
631
+ if network_body=$(get_request \
632
+ "/rest/networkSizes/${org_urn}?edgeType=COMPANY_FOLLOWED_BY_MEMBER" 2>/dev/null); then
633
+ local size
634
+ size=$(echo "$network_body" | jq -r '.firstDegreeSize // empty' 2>/dev/null || true)
635
+ if [[ "$size" =~ ^[0-9]+$ ]]; then
636
+ # Only a clean non-negative integer is trusted: it is injected via
637
+ # `--argjson total_followers` into the final emit, where a non-numeric
638
+ # value would crash jq under set -e and defeat the degrade-to-null
639
+ # contract. Anything else degrades to null (same path as a failed fetch).
640
+ total_followers="$size"
641
+ elif [[ -n "$size" ]]; then
642
+ echo "Warning: networkSizes returned non-numeric firstDegreeSize ('${size}') for org ${LINKEDIN_ORG_ID}; total_followers=null." >&2
643
+ else
644
+ echo "Warning: networkSizes returned no firstDegreeSize for org ${LINKEDIN_ORG_ID}; total_followers=null." >&2
645
+ fi
646
+ else
647
+ echo "Warning: networkSizes call failed for org ${LINKEDIN_ORG_ID}; total_followers=null." >&2
648
+ fi
649
+
650
+ jq -n \
651
+ --arg org_id "$LINKEDIN_ORG_ID" \
652
+ --argjson total_followers "$total_followers" \
653
+ --argjson share_statistics "$share_statistics" \
654
+ '{
655
+ org_id: $org_id,
656
+ total_followers: $total_followers,
657
+ share_statistics: $share_statistics
658
+ }'
659
+ }
660
+
661
+ # Fetch recent Company Page post metadata via the Posts author-finder.
662
+ # Aggregate-only — post metadata, no commenter/liker identities.
663
+ cmd_fetch_activity() {
664
+ require_org_credentials
665
+
666
+ local LINKEDIN_ACCESS_TOKEN="$LINKEDIN_ORG_ACCESS_TOKEN"
667
+
668
+ local org_urn="urn%3Ali%3Aorganization%3A${LINKEDIN_ORG_ID}"
669
+
670
+ # The Posts author-finder requires the X-RestLi-Method: FINDER header, threaded
671
+ # through get_request's optional 3rd arg (forwarded on the 429 recursion).
672
+ local posts_body
673
+ posts_body=$(get_request \
674
+ "/rest/posts?author=${org_urn}&q=author&count=10&sortBy=LAST_MODIFIED" \
675
+ 0 \
676
+ "X-RestLi-Method: FINDER")
677
+
678
+ # Shape validation BEFORE the `.elements[]` iteration: mirrors
679
+ # cmd_fetch_metrics's guard. A missing/null `.elements` (e.g. token lacks the
680
+ # required role, or the response shape changed) makes `.elements[]` crash jq
681
+ # ("Cannot iterate over null", exit 5) under set -e — emit an explicit error
682
+ # and exit 1 instead. A present-but-empty `elements: []` still succeeds and
683
+ # yields `{"posts": []}`.
684
+ if ! echo "$posts_body" | jq -e '(.elements | type) == "array"' >/dev/null 2>&1; then
685
+ echo "Error: posts author-finder returned no usable elements array for org ${LINKEDIN_ORG_ID}." >&2
686
+ echo "The org token may lack the required role, or the response shape changed." >&2
687
+ exit 1
688
+ fi
689
+
690
+ # commentary is operator-authored Page-public post text. If a post @mentions a
691
+ # member it flows to the digest, but the LIA's no-@mention posting TOM bounds
692
+ # this — no inbound filter needed. Emit metadata only; no author/commenter/
693
+ # liker identities. `//` fallbacks preserve each post even if a field is absent
694
+ # (learning 2026-03-10-jq-generator-silent-data-loss.md).
695
+ echo "$posts_body" | jq '
696
+ {
697
+ posts: [
698
+ .elements[] | {
699
+ id: .id,
700
+ commentary: (.commentary // null),
701
+ published_at: (.publishedAt // .createdAt // null),
702
+ lifecycle_state: (.lifecycleState // null)
703
+ }
704
+ ]
705
+ }'
706
+ }
707
+
708
+ # --- Main ---
709
+
710
+ main() {
711
+ local command="${1:-}"
712
+ shift || true
713
+
714
+ if [[ -z "$command" ]]; then
715
+ echo "Usage: linkedin-community.sh <command> [args]" >&2
716
+ echo "" >&2
717
+ echo "Commands:" >&2
718
+ echo " post-content --text \"<text>\" [--author \"<urn>\"] - Post to LinkedIn" >&2
719
+ echo " fetch-metrics - Fetch aggregate Company Page metrics (org read scopes)" >&2
720
+ echo " fetch-activity - Fetch recent Company Page post metadata (org read scopes)" >&2
721
+ exit 1
722
+ fi
723
+
724
+ require_jq
725
+
726
+ case "$command" in
727
+ fetch-metrics)
728
+ require_org_credentials
729
+ cmd_fetch_metrics
730
+ ;;
731
+ fetch-activity)
732
+ require_org_credentials
733
+ cmd_fetch_activity
734
+ ;;
735
+ post-content)
736
+ require_credentials
737
+ cmd_post_content "$@"
738
+ ;;
739
+ *)
740
+ echo "Error: Unknown command '${command}'" >&2
741
+ echo "Run 'linkedin-community.sh' without arguments for usage." >&2
742
+ exit 1
743
+ ;;
744
+ esac
745
+ }
746
+
747
+ # Guard: allow sourcing without executing main (for test harness)
748
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
749
+ main "$@"
750
+ fi