@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,758 @@
1
+ #!/usr/bin/env bash
2
+ # linkedin-setup.sh -- LinkedIn API credential setup and validation
3
+ #
4
+ # SECURITY: Credentials MUST be in environment variables.
5
+ # Never pass tokens as CLI arguments (visible in ps/history).
6
+ #
7
+ # Usage: linkedin-setup.sh <command> [args]
8
+ # Commands:
9
+ # validate-credentials [--warn-days N] - Verify token via introspection API
10
+ # generate-token - OAuth authorization code exchange
11
+ # write-env - Write credentials to .env with chmod 600
12
+ # verify - Source .env and run round-trip check
13
+ #
14
+ # Environment variables (required for API commands):
15
+ # LINKEDIN_CLIENT_ID - LinkedIn Developer App client ID
16
+ # LINKEDIN_CLIENT_SECRET - LinkedIn Developer App client secret
17
+ # LINKEDIN_ACCESS_TOKEN - OAuth 2.0 Bearer token (60-day TTL)
18
+ #
19
+ # Exit codes:
20
+ # 0 - Success
21
+ # 1 - General error
22
+ #
23
+ # Output: JSON or plain text to stdout
24
+ # Errors: Messages to stderr, exit 1
25
+
26
+ set -euo pipefail
27
+
28
+ # (#7797) Xtrace refusal -- the FIRST thing after `set …`, so nothing above it is
29
+ # traced. This script ships to installed Soleur CLIs: the terminal it runs in
30
+ # belongs to the founder, and `bash -x` would print their live LinkedIn
31
+ # credentials. UNCONDITIONAL, and that is measured rather than inherited:
32
+ # `cmd_verify` does `set -a; source "$env_file"; set +a`, loading the founder's
33
+ # .env at RUNTIME, AFTER this prologue. A conditional hatch keyed on whether the
34
+ # credential is already set would therefore be empty at guard time and open, and
35
+ # `source` under xtrace echoes EVERY assignment in the file -- so the leak would
36
+ # be the whole .env, not one token. (Written without the literal parameter
37
+ # expansion on purpose: a bare-token grep for it cannot tell this comment from
38
+ # real code, and the 4-vs-11 arm split is asserted by exactly such a grep.)
39
+ # Refusal goes to STDOUT, not stderr: agent runtimes surface stdout and swallow
40
+ # stderr (constitution.md > Code Style > Always), and a swallowed security refusal
41
+ # leaves the user with a bare `exit 78` and no text at all.
42
+ case "$-" in
43
+ *x*)
44
+ printf 'Refusing to run under `bash -x`: this script loads LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET, LINKEDIN_ACCESS_TOKEN from your .env at runtime, and tracing would print them to your terminal. Unsetting them first does not help -- the .env is the source. Re-run without `bash -x`.\n'
45
+ exit 78
46
+ ;;
47
+ esac
48
+
49
+ # (#7873) `--disable` closes ~/.curlrc and `--noproxy '*'` closes the proxy
50
+ # variables, but NEITHER touches the trust store or the TLS session-key log. On an
51
+ # installed CLI the environment belongs to someone who is not us, so
52
+ # a substituted `CURL_CA_BUNDLE` is a clean MITM of the founder's platform
53
+ # token with every other guard fully intact, and `SSLKEYLOGFILE` is passive
54
+ # decryption with no MITM at all. Matches the line in scripts/betterstack-query.sh.
55
+ unset SSLKEYLOGFILE CURL_CA_BUNDLE SSL_CERT_FILE SSL_CERT_DIR CURL_HOME \
56
+ HOSTALIASES LOCALDOMAIN RES_OPTIONS
57
+
58
+ # --- Transport-confinement diagnostics (#7873) --------------------------------
59
+ # Every credentialed `curl` below carries `--disable --noproxy '*'` and discards
60
+ # curl's own stderr (which can carry the URL). A failed request then has four
61
+ # competing causes the founder cannot tell apart: the xtrace refusal, the proxy
62
+ # this script deliberately bypassed, a ~/.curlrc that `--disable` dropped, or an
63
+ # ordinary network failure. So each failure emits ONE structured marker carrying
64
+ # all four discriminators, beside a human line that names the bypass instead of
65
+ # blaming the founder's connectivity.
66
+ SOLEUR_TRANSPORT_SCRIPT="linkedin-setup.sh"
67
+ SOLEUR_TRANSPORT_PLATFORM="LinkedIn"
68
+
69
+ # The request helpers below run inside `$(...)`, where fd 1 is the capture pipe.
70
+ # Save the script's real stdout so the marker reaches the founder's terminal (and
71
+ # the always-on PostToolUse Bash extractor) instead of being swallowed into a
72
+ # variable. The marker is on stdout BY DESIGN -- the call sites' `2>/dev/null` is
73
+ # what keeps curl's URL-carrying stderr out of the transcript, and it must not be
74
+ # able to suppress the diagnostic too.
75
+ exec 3>&1
76
+
77
+ # Which proxy variables are non-empty, or `none`.
78
+ proxy_env_names() {
79
+ local names="" n
80
+ for n in HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy; do
81
+ if [[ -n "${!n-}" ]]; then
82
+ names="${names},${n}"
83
+ fi
84
+ done
85
+ if [[ -z "$names" ]]; then
86
+ printf 'none'
87
+ else
88
+ printf '%s' "${names#,}"
89
+ fi
90
+ }
91
+
92
+ # True when a proxy this script deliberately bypasses is configured. NO_PROXY
93
+ # alone is not one -- it only ever narrows proxying, so it cannot be the cause.
94
+ proxy_bypassed() {
95
+ [[ -n "${HTTP_PROXY:-}${HTTPS_PROXY:-}${ALL_PROXY:-}${http_proxy:-}${https_proxy:-}${all_proxy:-}" ]]
96
+ }
97
+
98
+ # One line, eight fields, on the saved stdout.
99
+ #
100
+ # Two fields were literals in the first cut and are now MEASURED (#7898 review):
101
+ # `noproxy_applied` was the constant "true", which ASSERTS the property instead of
102
+ # observing it -- dropping --noproxy from a call site left the diagnostic still
103
+ # claiming it was applied. It now reports whether a proxy was actually configured
104
+ # for this request to bypass, which is the fact a reader needs. `refusal` was the
105
+ # constant "none" at every call site, so the cause it exists to discriminate could
106
+ # never appear; it now carries `env-rebind-refused` and `xtrace-credential-bound`.
107
+ #
108
+ # `tls_env_cleared` is new. The prologue unsets CURL_CA_BUNDLE/SSL_CERT_FILE et al,
109
+ # which is correct against an attacker and BREAKS a founder whose corporate CA
110
+ # arrives that way -- curl then exits 60 and, without this field, the event is
111
+ # indistinguishable from an ordinary network failure.
112
+ emit_transport_diag() {
113
+ local curl_exit="$1" refusal="$2" surface="installed-cli" curlrc="false" noproxy="false"
114
+ # The hosted sandbox always sets this (agent-env.ts > AGENT_ENV_OVERRIDES); an
115
+ # installed CLI does not. A label on the event, not a gate on behaviour.
116
+ if [[ -n "${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-}" ]]; then
117
+ surface="in-sandbox"
118
+ fi
119
+ if [[ -f "${HOME:-}/.curlrc" ]]; then
120
+ curlrc="true"
121
+ fi
122
+ if proxy_bypassed; then
123
+ noproxy="true"
124
+ fi
125
+ 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' \
126
+ "$surface" "$SOLEUR_TRANSPORT_SCRIPT" "$curl_exit" "$refusal" \
127
+ "$(proxy_env_names)" "$curlrc" "$noproxy" "true" >&3
128
+ }
129
+
130
+ # Replaces the bare "Check your network connection and try again." on a
131
+ # credentialed-curl failure. With a proxy set, that line blames the founder for a
132
+ # bypass this script chose.
133
+ report_transport_failure() {
134
+ local curl_exit="${1:-unknown}"
135
+ local detail="${2:-Failed to connect to the ${SOLEUR_TRANSPORT_PLATFORM} API.}"
136
+ emit_transport_diag "$curl_exit" "none"
137
+ echo "Error: ${detail}" >&2
138
+ # Ordered by how specific the evidence is. Each arm names something THIS script
139
+ # did, so the founder is never told to go debug their own network for a choice
140
+ # made here. The bare "check your connection" line is the LAST resort, not the
141
+ # default -- it was the default in the first cut, which meant a founder whose
142
+ # corporate CA or curlrc we had just discarded was blamed for it (#7898 review).
143
+ if [[ "$curl_exit" == "60" || "$curl_exit" == "35" || "$curl_exit" == "77" ]]; then
144
+ # 60 peer-certificate, 35 TLS handshake, 77 CA-bundle unreadable.
145
+ 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
146
+ 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
147
+ elif proxy_bypassed; then
148
+ echo "This request deliberately bypasses your proxy ($(proxy_env_names)), because a proxy can redirect a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} token." >&2
149
+ 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
150
+ elif [[ -f "${HOME:-}/.curlrc" ]]; then
151
+ 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
152
+ echo "That is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
153
+ else
154
+ echo "Check your network connection and try again." >&2
155
+ fi
156
+ }
157
+
158
+ # readonly (#7898 review): `cmd_verify` and friends run `set -a; source "$env_file"; set +a`
159
+ # BELOW this line, so a plain assignment here is rebindable by the repo's .env --
160
+ # which retargets the credentialed request while every transport flag stays intact.
161
+ # That is the "--disable and --noproxy are intact and irrelevant" shape this change
162
+ # exists to close, one layer up. readonly makes the destination non-rebindable.
163
+ readonly LINKEDIN_OAUTH="https://www.linkedin.com/oauth/v2"
164
+ readonly LINKEDIN_API="https://api.linkedin.com"
165
+ LINKEDIN_DEFAULT_REDIRECT_URI="https://localhost:8080/callback"
166
+
167
+ # (#7843) The access token is parsed out of an API reply, so it is RESPONSE-DERIVED:
168
+ # untrusted bytes. It rides curl's STDIN config channel (`--config -`), never its
169
+ # argument list (readable by every local user in /proc/<pid>/cmdline). That channel is
170
+ # line-oriented, so a token holding a quote and a newline could append a `url = "..."`
171
+ # directive and make curl issue a second request. This guard refuses anything outside the
172
+ # token alphabet BEFORE the value is formatted into the stream, and never echoes it.
173
+ _bearer_ok() { local LC_ALL=C; case "${1:-}" in ''|*[!A-Za-z0-9._~+/=-]*) return 1 ;; esac; }
174
+
175
+ # (#9597) The OAuth client secret and the access token being introspected are form
176
+ # fields, not bearers, so `_bearer_ok`'s alphabet does not fit them. They still must
177
+ # not ride curl's argument list, so they go on the stdin config channel as
178
+ # `data-urlencode = "name=value"` lines. A config string is one line with `\` and `"`
179
+ # as its only escapes, so a value holding a control character (CR/LF would end the
180
+ # directive and let the next bytes act as a new one), a quote or a backslash is refused
181
+ # BEFORE it is formatted into the stream; `_cfg_q` still escapes what reaches it.
182
+ _cfg_ok() { local LC_ALL=C; case "${1:-}" in ''|*[[:cntrl:]]*|*'"'*|*'\'*) return 1 ;; esac; }
183
+ _cfg_q() { local v="${1-}"; v=${v//\\/\\\\}; v=${v//\"/\\\"}; printf '%s' "$v"; }
184
+
185
+ # --- Dependency checks ---
186
+
187
+ require_jq() {
188
+ if ! command -v jq &>/dev/null; then
189
+ echo "Error: jq is required but not installed." >&2
190
+ echo "Install it: https://jqlang.github.io/jq/download/" >&2
191
+ exit 1
192
+ fi
193
+ }
194
+
195
+ # --- Credential validation ---
196
+
197
+ require_client_credentials() {
198
+ local missing=()
199
+
200
+ if [[ -z "${LINKEDIN_CLIENT_ID:-}" ]]; then
201
+ missing+=("LINKEDIN_CLIENT_ID")
202
+ fi
203
+ if [[ -z "${LINKEDIN_CLIENT_SECRET:-}" ]]; then
204
+ missing+=("LINKEDIN_CLIENT_SECRET")
205
+ fi
206
+ if [[ -z "${LINKEDIN_ACCESS_TOKEN:-}" ]]; then
207
+ missing+=("LINKEDIN_ACCESS_TOKEN")
208
+ fi
209
+
210
+ if [[ ${#missing[@]} -gt 0 ]]; then
211
+ echo "Error: Missing LinkedIn credentials: ${missing[*]}" >&2
212
+ echo "" >&2
213
+ echo "To configure:" >&2
214
+ echo " 1. Create an app at https://www.linkedin.com/developers/apps" >&2
215
+ echo " 2. Add 'Sign In with LinkedIn using OpenID Connect' product" >&2
216
+ echo " 3. Add 'Share on LinkedIn' product" >&2
217
+ echo " 4. Export LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET" >&2
218
+ echo " 5. Run: linkedin-setup.sh generate-token" >&2
219
+ exit 1
220
+ fi
221
+ }
222
+
223
+ # --- Commands ---
224
+
225
+ cmd_validate_credentials() {
226
+ require_client_credentials
227
+
228
+ local warn_days=0
229
+
230
+ while [[ $# -gt 0 ]]; do
231
+ case "$1" in
232
+ --warn-days)
233
+ warn_days="${2:-}"
234
+ if [[ -z "$warn_days" ]] || [[ ! "$warn_days" =~ ^[0-9]+$ ]]; then
235
+ echo "Error: --warn-days requires a positive integer." >&2
236
+ exit 1
237
+ fi
238
+ shift 2
239
+ ;;
240
+ *)
241
+ echo "Error: Unknown option '$1'" >&2
242
+ exit 1
243
+ ;;
244
+ esac
245
+ done
246
+
247
+ local response http_code body
248
+ # Token introspection uses client credentials as POST body params (not Bearer)
249
+ if ! _cfg_ok "$LINKEDIN_CLIENT_SECRET" || ! _cfg_ok "$LINKEDIN_ACCESS_TOKEN"; then
250
+ echo "Error: LinkedIn client secret or access token has an unexpected shape; refusing to send it." >&2
251
+ exit 1
252
+ fi
253
+ local __curl_rc=0
254
+ # The secret and the token ride curl's stdin config channel, never its argument list.
255
+ response=$(curl --disable --noproxy '*' -s -w "\n%{http_code}" --config - \
256
+ -X POST \
257
+ --data-urlencode "client_id=${LINKEDIN_CLIENT_ID}" \
258
+ "${LINKEDIN_OAUTH}/introspectToken" 2>/dev/null \
259
+ < <(printf 'data-urlencode = "client_secret=%s"\ndata-urlencode = "token=%s"\n' \
260
+ "$(_cfg_q "$LINKEDIN_CLIENT_SECRET")" "$(_cfg_q "$LINKEDIN_ACCESS_TOKEN")")) || __curl_rc=$?
261
+ if (( __curl_rc != 0 )); then
262
+ report_transport_failure "$__curl_rc" "Failed to connect to LinkedIn OAuth API."
263
+ exit 1
264
+ fi
265
+
266
+ http_code=$(echo "$response" | tail -1)
267
+ body=$(echo "$response" | sed '$d')
268
+
269
+ if [[ ! "$http_code" =~ ^2[0-9][0-9]$ ]]; then
270
+ local message
271
+ message=$(echo "$body" | jq -r '.error_description // .error // "Unknown error"' 2>/dev/null || echo "Unknown error")
272
+ echo "Error: LinkedIn OAuth API returned HTTP ${http_code}: ${message}" >&2
273
+ exit 1
274
+ fi
275
+
276
+ if ! echo "$body" | jq . >/dev/null 2>&1; then
277
+ echo "Error: LinkedIn OAuth API returned malformed JSON." >&2
278
+ exit 1
279
+ fi
280
+
281
+ local active expires_at scope
282
+ active=$(echo "$body" | jq -r '.active // false')
283
+ expires_at=$(echo "$body" | jq -r '.expires_at // 0')
284
+ scope=$(echo "$body" | jq -r '.scope // "unknown"')
285
+
286
+ if [[ "$active" != "true" ]]; then
287
+ echo "Error: LinkedIn access token is expired or invalid." >&2
288
+ echo "" >&2
289
+ echo "To renew:" >&2
290
+ echo " Run: linkedin-setup.sh generate-token" >&2
291
+ exit 1
292
+ fi
293
+
294
+ local now days_remaining
295
+ now=$(date +%s)
296
+ days_remaining=$(( (expires_at - now) / 86400 ))
297
+
298
+ # Output token info as JSON
299
+ jq -n \
300
+ --argjson active true \
301
+ --argjson days_remaining "$days_remaining" \
302
+ --arg scope "$scope" \
303
+ --argjson expires_at "$expires_at" \
304
+ '{
305
+ active: $active,
306
+ days_remaining: $days_remaining,
307
+ scope: $scope,
308
+ expires_at: $expires_at
309
+ }'
310
+
311
+ echo "Token valid. ${days_remaining} days remaining. Scopes: ${scope}" >&2
312
+
313
+ # Warn-days check: exit non-zero if below threshold
314
+ if (( warn_days > 0 && days_remaining < warn_days )); then
315
+ echo "Warning: Token expires in ${days_remaining} days (threshold: ${warn_days})." >&2
316
+ echo "Run: linkedin-setup.sh generate-token" >&2
317
+ exit 1
318
+ fi
319
+ }
320
+
321
+ cmd_generate_token() {
322
+ if [[ -z "${LINKEDIN_CLIENT_ID:-}" ]]; then
323
+ echo "Error: LINKEDIN_CLIENT_ID is required for token generation." >&2
324
+ echo "Export it from your LinkedIn Developer App settings." >&2
325
+ exit 1
326
+ fi
327
+ if [[ -z "${LINKEDIN_CLIENT_SECRET:-}" ]]; then
328
+ echo "Error: LINKEDIN_CLIENT_SECRET is required for token generation." >&2
329
+ echo "Export it from your LinkedIn Developer App settings." >&2
330
+ exit 1
331
+ fi
332
+
333
+ local redirect_uri="${LINKEDIN_REDIRECT_URI:-$LINKEDIN_DEFAULT_REDIRECT_URI}"
334
+ # w_organization_social is REQUIRED to post as a Company Page (author
335
+ # urn:li:organization:<id>). Without it the /rest/posts call fails with
336
+ # HTTP 400 "Organization Or Events permissions must be used when using
337
+ # organization as author" (#4046 — surfaced when a member-only token was
338
+ # rotated in and the Phase-6 test post hit this). The app must have the
339
+ # Community Management API product approved for LinkedIn to grant it.
340
+ local scopes="openid%20profile%20w_member_social%20w_organization_social"
341
+ local auth_url="${LINKEDIN_OAUTH}/authorization?response_type=code&client_id=${LINKEDIN_CLIENT_ID}&redirect_uri=$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${redirect_uri}" 2>/dev/null || echo "${redirect_uri}")&scope=${scopes}"
342
+
343
+ echo "=== LinkedIn OAuth Token Generation ===" >&2
344
+ echo "" >&2
345
+ echo "1. Open this URL in your browser:" >&2
346
+ echo " ${auth_url}" >&2
347
+ echo "" >&2
348
+
349
+ # Try to open the URL automatically
350
+ if command -v xdg-open &>/dev/null; then
351
+ xdg-open "$auth_url" 2>/dev/null || true
352
+ echo " (Opened in browser)" >&2
353
+ elif command -v open &>/dev/null; then
354
+ open "$auth_url" 2>/dev/null || true
355
+ echo " (Opened in browser)" >&2
356
+ fi
357
+
358
+ echo "2. Log in and authorize the application" >&2
359
+ echo "3. Copy the 'code' parameter from the redirect URL" >&2
360
+ echo " (It looks like: https://localhost:8080/callback?code=AQ...)" >&2
361
+ echo "" >&2
362
+
363
+ local auth_code
364
+ read -rp "Paste the authorization code: " auth_code
365
+
366
+ if [[ -z "$auth_code" ]]; then
367
+ echo "Error: Authorization code is required." >&2
368
+ exit 1
369
+ fi
370
+
371
+ echo "" >&2
372
+ echo "Exchanging authorization code for access token..." >&2
373
+
374
+ local response http_code body
375
+ if ! _cfg_ok "$LINKEDIN_CLIENT_SECRET"; then
376
+ echo "Error: LINKEDIN_CLIENT_SECRET has an unexpected shape; refusing to send it." >&2
377
+ exit 1
378
+ fi
379
+ local __curl_rc=0
380
+ # The client secret rides curl's stdin config channel, never its argument list.
381
+ response=$(curl --disable --noproxy '*' -s -w "\n%{http_code}" --config - \
382
+ -X POST \
383
+ --data-urlencode "grant_type=authorization_code" \
384
+ --data-urlencode "code=${auth_code}" \
385
+ --data-urlencode "client_id=${LINKEDIN_CLIENT_ID}" \
386
+ --data-urlencode "redirect_uri=${redirect_uri}" \
387
+ "${LINKEDIN_OAUTH}/accessToken" 2>/dev/null \
388
+ < <(printf 'data-urlencode = "client_secret=%s"\n' "$(_cfg_q "$LINKEDIN_CLIENT_SECRET")")) || __curl_rc=$?
389
+ if (( __curl_rc != 0 )); then
390
+ report_transport_failure "$__curl_rc" "Failed to connect to LinkedIn OAuth API."
391
+ exit 1
392
+ fi
393
+
394
+ http_code=$(echo "$response" | tail -1)
395
+ body=$(echo "$response" | sed '$d')
396
+
397
+ if [[ ! "$http_code" =~ ^2[0-9][0-9]$ ]]; then
398
+ local message
399
+ message=$(echo "$body" | jq -r '.error_description // .error // "Unknown error"' 2>/dev/null || echo "Unknown error")
400
+ echo "Error: Token exchange failed (HTTP ${http_code}): ${message}" >&2
401
+ exit 1
402
+ fi
403
+
404
+ local access_token
405
+ access_token=$(echo "$body" | jq -r '.access_token // empty')
406
+
407
+ if [[ -z "$access_token" ]]; then
408
+ echo "Error: No access_token in response." >&2
409
+ exit 1
410
+ fi
411
+
412
+ if ! _bearer_ok "$access_token"; then
413
+ echo "Error: access_token in the response has an unexpected shape; refusing to use it." >&2
414
+ exit 1
415
+ fi
416
+
417
+ # Resolve person URN
418
+ echo "Resolving person URN..." >&2
419
+ local userinfo_response userinfo_code userinfo_body
420
+ local __curl_rc=0
421
+ # The bearer rides curl's stdin config channel, never its argument list.
422
+ userinfo_response=$(curl --disable --noproxy '*' -s -w "\n%{http_code}" --config - \
423
+ "${LINKEDIN_API}/v2/userinfo" 2>/dev/null \
424
+ < <(printf 'header = "Authorization: Bearer %s"\n' "$access_token")) || __curl_rc=$?
425
+ if (( __curl_rc != 0 )); then
426
+ report_transport_failure "$__curl_rc" "Failed to resolve person URN."
427
+ exit 1
428
+ fi
429
+
430
+ userinfo_code=$(echo "$userinfo_response" | tail -1)
431
+ userinfo_body=$(echo "$userinfo_response" | sed '$d')
432
+
433
+ if [[ ! "$userinfo_code" =~ ^2[0-9][0-9]$ ]]; then
434
+ echo "Error: Failed to fetch user info (HTTP ${userinfo_code})." >&2
435
+ exit 1
436
+ fi
437
+
438
+ local person_id
439
+ person_id=$(echo "$userinfo_body" | jq -r '.sub // empty')
440
+
441
+ if [[ -z "$person_id" ]]; then
442
+ echo "Error: Could not extract person ID from userinfo response." >&2
443
+ exit 1
444
+ fi
445
+
446
+ local person_urn="urn:li:person:${person_id}"
447
+
448
+ # Export for write-env
449
+ export LINKEDIN_ACCESS_TOKEN="$access_token"
450
+ export LINKEDIN_PERSON_URN="$person_urn"
451
+
452
+ cmd_write_env
453
+
454
+ echo "" >&2
455
+ echo "Token generated successfully." >&2
456
+ echo "Person URN: ${person_urn}" >&2
457
+ echo "Run 'linkedin-setup.sh verify' to confirm." >&2
458
+ }
459
+
460
+ # (#9597) write-env value allow-list. `.env` values are written UNQUOTED and later SOURCED
461
+ # (`verify`, the community scripts), so a value holding a command substitution, a backtick, a
462
+ # quote, a semicolon, a space or a newline would execute or split on the next source, and a leading
463
+ # tilde would silently change a stored credential (tilde expansion on source). Validation is an
464
+ # allow-list, fail-closed, and runs for EVERY value BEFORE the first write: a refused value prints
465
+ # the value-free marker plus ONE human line naming only the VARIABLE, writes nothing, exits 1 and
466
+ # leaves any existing .env untouched. A value outside the list that is legitimate is added to .env
467
+ # by hand. The glob runs under LC_ALL=C and is not grep, which is line-oriented and lets a
468
+ # multi-line value through.
469
+ _wenv_class() { # <value>: 0 allowed, 1 outside the allow-list or empty, 2 holds a control character
470
+ local LC_ALL=C
471
+ case "${1-}" in
472
+ '') return 1 ;;
473
+ *[[:cntrl:]]*) return 2 ;;
474
+ *[!A-Za-z0-9._:/@%+=,-]*) return 1 ;;
475
+ esac
476
+ return 0
477
+ }
478
+ _wenv_refuse() { # <reason> <VARIABLE>
479
+ printf 'SOLEUR_CREDENTIAL_REFUSED script=%s reason=%s var=%s phase=write-env\n' "$SOLEUR_TRANSPORT_SCRIPT" "$1" "$2" >&2
480
+ 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."
481
+ exit 1
482
+ }
483
+ # _wenv_validate <VARIABLE>...: every NON-EMPTY named variable passes the allow-list. The required
484
+ # variables are checked non-empty by the caller before this runs; an empty optional one is skipped.
485
+ _wenv_validate() {
486
+ local _wn _wrc
487
+ for _wn in "$@"; do
488
+ [[ -n "${!_wn:-}" ]] || continue
489
+ _wrc=0
490
+ _wenv_class "${!_wn}" || _wrc=$?
491
+ case "$_wrc" in
492
+ 0) ;;
493
+ 2) _wenv_refuse control_char "$_wn" ;;
494
+ *) _wenv_refuse token_shape "$_wn" ;;
495
+ esac
496
+ done
497
+ }
498
+
499
+ # (#10091) write-env commit: ONE helper, byte-identical in the four setup scripts (a drift row pins
500
+ # it, from this comment line through the closing brace of `_wenv_write`). `_wenv_write <env_file>
501
+ # <remove-ERE> <KEY=VALUE>...` replaces the old remove-then-append, which raced (two writers each read
502
+ # the file, each rewrote it, the later mv lost the earlier's keys), was not atomic (the append hit the
503
+ # live file after the mv) and widened a new file before the chmod.
504
+ # The edit is built in a temp file `<.env>.wenv.XXXXXX` beside the RESOLVED .env (same filesystem, so the
505
+ # final `mv -f` is one atomic rename; a namespace of its own, so the sweep below touches only that
506
+ # namespace (a file there that is not a regular file this user owns is left alone), never the operator
507
+ # library's `.env.tmp.*` temps) under `umask 077` (0600 from birth), serialized by
508
+ # ONE mutual-exclusion mechanism on every platform: an owner-pid `mkdir` lock directory
509
+ # (`<.env>.lock.d`) that is held until AFTER that rename. No flock (a flock run and a mkdir run exclude
510
+ # nothing against each other, flock without a timeout blocks forever, and `exec {fd}<` needs bash 4.1
511
+ # while macOS ships 3.2).
512
+ # Stale-lock recovery is serialized as well, by a second `mkdir` mutex `<.env>.lock.d.recover`. A waiter
513
+ # judges a lock stale when its owner pid is dead (or its owner file stayed empty for _WENV_EMPTY_POLLS
514
+ # passes), then tries to take the mutex; a waiter that loses it keeps waiting. The winner RE-READS the
515
+ # owner of the lock UNDER the mutex (the snapshot it judged on is never trusted; the re-read narrows the
516
+ # window to the few instructions between the read and the removal, it does not close it), removes the lock
517
+ # only if the owner is still a dead pid, or if the snapshot AND the re-read (after a short pause) both found
518
+ # no pid, ALWAYS releases the mutex, and goes round the loop again. A lock whose owner turned from a dead pid
519
+ # into nothing has changed hands and is left alone. A recovering waiter never writes while it recovers,
520
+ # nothing is renamed away and put back, and the lock path is never empty while a live holder exists, so two
521
+ # waiters cannot both hold the lock. A SOLEUR_WENV_LOCK_TICKS below 2 never attempts recovery.
522
+ # The wait is ONE bounded loop. Every failed `mkdir` pass either sleeps one tick (0.1 s; 1 s where
523
+ # `sleep` takes no fraction) or fails at once, and every pass counts against SOLEUR_WENV_LOCK_TICKS
524
+ # (digits only, at most _WENV_TICKS_MAX, else _WENV_TICKS_DEFAULT): about 10 s by default with a
525
+ # fractional `sleep`, about 100 s without one. It FAILS CLOSED: no write, a value-free marker, exit 1,
526
+ # .env untouched. A lock path that exists but is not a directory fails at once (env_lock_not_dir). grep
527
+ # exit 1 (nothing left) is normal; exit 2 (an unreadable .env) aborts, because swallowing it would
528
+ # install an empty file over the credentials. A symlinked .env is resolved first (the real target is
529
+ # rewritten, the link survives); a dangling link or a non-regular target is refused.
530
+ # Signals: this helper owns the EXIT, INT, TERM and HUP traps for its duration and restores nothing (no
531
+ # caller installs one today; a caller that needs its own must chain them). On those four it removes its
532
+ # temp and its recovery mutex and releases the lock, and it removes the lock ONLY when the owner file
533
+ # reads exactly this process. Residual, stated: SIGKILL leaves its lock (recovered by the next run
534
+ # through the dead pid), its temp (swept by the next run, under the lock) and, if it died mid-recovery,
535
+ # `<.env>.lock.d.recover`, which the next runs report after the bounded wait ("remove that directory");
536
+ # a recycled pid can keep a dead owner looking live until the wait times out; the owner check uses
537
+ # `kill -0`, so a lock held by another user reads as dead; a holder stalled between its `mkdir` and its
538
+ # owner write for longer than the empty-owner window can be treated as ownerless and recovered.
539
+ _WENV_TICK=0.1 # seconds per wait tick (a `sleep` without fractions falls back to 1 s)
540
+ _WENV_TICKS_DEFAULT=100 # the default bound: about 10 s
541
+ _WENV_TICKS_MAX=1000 # the largest SOLEUR_WENV_LOCK_TICKS accepted: about 100 s
542
+ _WENV_EMPTY_POLLS=30 # passes an ownerless lock directory is waited on before recovery is tried: about 3 s
543
+ _WENV_EMPTY_PAUSE=0.3 # pause between the two owner reads under the recovery mutex when the owner is not a pid
544
+ _WENV_LOCK="" _WENV_TMP="" _WENV_REC=""
545
+ _wenv_unlock() { # idempotent: remove our temp and recovery mutex; release the lock ONLY if its owner file reads exactly this process
546
+ local _wo=""
547
+ if [[ -n "$_WENV_TMP" ]]; then rm -f -- "$_WENV_TMP"; _WENV_TMP=""; fi
548
+ if [[ -n "$_WENV_REC" ]]; then rmdir -- "$_WENV_REC" 2>/dev/null || true; _WENV_REC=""; fi
549
+ if [[ -n "$_WENV_LOCK" ]]; then
550
+ { read -r _wo < "$_WENV_LOCK/owner"; } 2>/dev/null || _wo=""
551
+ if [[ "$_wo" == "$$" ]]; then rm -rf -- "$_WENV_LOCK"; fi
552
+ _WENV_LOCK=""
553
+ fi
554
+ }
555
+ _wenv_fail() { # <reason> <human text>: value-free marker on stderr, one human line on stdout, exit 1
556
+ printf 'SOLEUR_CREDENTIAL_REFUSED script=%s reason=%s var=ENV_FILE phase=write-env\n' "$SOLEUR_TRANSPORT_SCRIPT" "$1" >&2
557
+ echo "Error: $2 Nothing was written and your existing .env is unchanged."
558
+ exit 1
559
+ }
560
+ _wenv_write() {
561
+ local _wf="$1" _wre="$2" _wr _wl _wm _wd _wi=0 _ws=0 _wt _wown _wo2 _wsnap="" _wstale _wgone _wwhy="" _wn _wrc _wum
562
+ shift 2
563
+ if _wr="$(readlink -f -- "$_wf" 2>/dev/null)" && [[ -n "$_wr" ]]; then
564
+ :
565
+ elif [[ -L "$_wf" ]]; then
566
+ _wenv_fail env_unresolvable "The .env link at ${_wf} could not be resolved."
567
+ else
568
+ _wr="$_wf"
569
+ fi
570
+ if [[ -L "$_wf" && ! -e "$_wr" ]]; then
571
+ _wenv_fail env_dangling_link "The .env at ${_wf} is a link to a file that does not exist."
572
+ fi
573
+ if [[ -e "$_wr" && ! -f "$_wr" ]]; then
574
+ _wenv_fail env_not_regular "The .env at ${_wf} is not a regular file."
575
+ fi
576
+ _wl="${_wr}.lock.d"
577
+ _wm="${_wl}.recover"
578
+ _wt="${SOLEUR_WENV_LOCK_TICKS:-$_WENV_TICKS_DEFAULT}"
579
+ if [[ "$_wt" =~ ^[0-9]{1,4}$ ]] && (( 10#$_wt <= _WENV_TICKS_MAX )); then _wt=$((10#$_wt)); else _wt=$_WENV_TICKS_DEFAULT; fi
580
+ _wum="$(umask)"
581
+ umask 077
582
+ trap '_wenv_unlock' EXIT
583
+ trap '_wenv_unlock; exit 130' INT
584
+ trap '_wenv_unlock; exit 143' TERM
585
+ trap '_wenv_unlock; exit 129' HUP
586
+ while :; do
587
+ if mkdir -- "$_wl" 2>/dev/null; then
588
+ _WENV_LOCK="$_wl"
589
+ if ! printf '%s\n' "$$" > "$_wl/owner"; then
590
+ rm -rf -- "$_wl"; _WENV_LOCK=""
591
+ _wenv_fail env_write_failed "The lock at ${_wl} could not be recorded."
592
+ fi
593
+ break
594
+ fi
595
+ if [[ -L "$_wl" || -f "$_wl" ]]; then
596
+ _wenv_fail env_lock_not_dir "The lock path ${_wl} exists but is not a directory; remove it."
597
+ fi
598
+ _wi=$((_wi + 1))
599
+ _wstale=0
600
+ if [[ ! -d "$_wl" ]]; then
601
+ # mkdir failed with the path absent: the lock was released in between, or mkdir fails for another
602
+ # reason (a name too long, no space): a pass that made no progress, so it sleeps below like any other.
603
+ _ws=0; _wwhy=""
604
+ _wd="${_wr%/*}"; [[ "$_wd" != "$_wr" ]] || _wd=.
605
+ [[ -d "$_wd" && -w "$_wd" ]] || _wenv_fail env_dir_unwritable "The directory of ${_wf} does not allow a lock to be created."
606
+ else
607
+ _wown=""
608
+ { read -r _wown < "$_wl/owner"; } 2>/dev/null || _wown=""
609
+ if [[ "$_wown" =~ ^[0-9]+$ ]]; then
610
+ _ws=0; _wsnap=pid
611
+ if [[ "$_wown" == "$$" ]] || ! kill -0 "$_wown" 2>/dev/null; then _wstale=1; _wwhy=stale; else _wwhy=live; fi
612
+ else
613
+ _ws=$((_ws + 1)); _wwhy=""; _wsnap=empty
614
+ if (( _ws > _WENV_EMPTY_POLLS )); then _wstale=1; fi
615
+ fi
616
+ fi
617
+ if (( _wstale == 1 && _wi < _wt )); then
618
+ if mkdir -- "$_wm" 2>/dev/null; then
619
+ _WENV_REC="$_wm"
620
+ _wo2=""
621
+ { read -r _wo2 < "$_wl/owner"; } 2>/dev/null || _wo2=""
622
+ if [[ "$_wsnap" == empty && ! "$_wo2" =~ ^[0-9]+$ ]]; then
623
+ sleep "$_WENV_EMPTY_PAUSE" 2>/dev/null || sleep 1
624
+ { read -r _wo2 < "$_wl/owner"; } 2>/dev/null || _wo2=""
625
+ fi
626
+ _wgone=0
627
+ if [[ "$_wo2" =~ ^[0-9]+$ ]]; then
628
+ if [[ "$_wo2" == "$$" ]] || ! kill -0 "$_wo2" 2>/dev/null; then rm -rf -- "$_wl"; _ws=0; _wgone=1; fi
629
+ elif [[ "$_wsnap" == empty ]]; then
630
+ rm -rf -- "$_wl"; _ws=0; _wgone=1
631
+ fi
632
+ _wenv_unlock
633
+ if (( _wgone == 1 )); then continue; fi
634
+ fi
635
+ fi
636
+ if (( _wi >= _wt )); then
637
+ if [[ "$_wwhy" != live && -d "$_wm" ]]; then _wwhy=recover; fi
638
+ case "$_wwhy" in
639
+ 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." ;;
640
+ 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." ;;
641
+ *) _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." ;;
642
+ esac
643
+ fi
644
+ sleep "$_WENV_TICK" 2>/dev/null || sleep 1
645
+ done
646
+ for _wn in "${_wr}".wenv.??????; do
647
+ if [[ -f "$_wn" && ! -L "$_wn" && -O "$_wn" ]]; then rm -f -- "$_wn"; fi
648
+ done
649
+ _WENV_TMP="$(mktemp "${_wr}.wenv.XXXXXX")" || _wenv_fail env_write_failed "A temp file could not be created beside ${_wf}."
650
+ if [[ -f "$_wr" ]]; then
651
+ _wrc=0
652
+ LC_ALL=C grep -a -v -E "$_wre" "$_wr" > "$_WENV_TMP" || _wrc=$?
653
+ if (( _wrc > 1 )); then
654
+ _wenv_fail env_unreadable "The existing .env at ${_wf} could not be read."
655
+ fi
656
+ fi
657
+ printf '%s\n' "$@" >> "$_WENV_TMP" || _wenv_fail env_write_failed "The new values could not be written beside ${_wf}."
658
+ chmod 600 "$_WENV_TMP" || _wenv_fail env_write_failed "The temp file beside ${_wf} could not be restricted to mode 600."
659
+ mv -f -- "$_WENV_TMP" "$_wr" || _wenv_fail env_write_failed "The new .env could not be moved into place at ${_wf}."
660
+ _WENV_TMP=""
661
+ _wenv_unlock
662
+ trap - EXIT INT TERM HUP
663
+ umask "$_wum"
664
+ }
665
+
666
+ cmd_write_env() {
667
+ local repo_root
668
+ repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
669
+ local env_file="${repo_root}/.env"
670
+
671
+ # Require all vars to be set
672
+ local missing=()
673
+ [[ -z "${LINKEDIN_CLIENT_ID:-}" ]] && missing+=("LINKEDIN_CLIENT_ID")
674
+ [[ -z "${LINKEDIN_CLIENT_SECRET:-}" ]] && missing+=("LINKEDIN_CLIENT_SECRET")
675
+ [[ -z "${LINKEDIN_ACCESS_TOKEN:-}" ]] && missing+=("LINKEDIN_ACCESS_TOKEN")
676
+ [[ -z "${LINKEDIN_PERSON_URN:-}" ]] && missing+=("LINKEDIN_PERSON_URN")
677
+
678
+ if [[ ${#missing[@]} -gt 0 ]]; then
679
+ echo "Error: Missing variables for write-env: ${missing[*]}" >&2
680
+ exit 1
681
+ fi
682
+
683
+ # Every value is checked BEFORE the first write (see the allow-list above).
684
+ _wenv_validate LINKEDIN_CLIENT_ID LINKEDIN_CLIENT_SECRET LINKEDIN_ACCESS_TOKEN LINKEDIN_PERSON_URN
685
+
686
+ # One atomic, serialized, mode-600-from-birth write (see _wenv_write above). The remove-ERE names exactly
687
+ # the four keys written below: LINKEDIN_ORG_ACCESS_TOKEN, LINKEDIN_ORG_ID, LINKEDIN_ALLOW_POST and
688
+ # LINKEDIN_POST_MAX_LENGTH are read by linkedin-community.sh from the same .env and must survive.
689
+ _wenv_write "$env_file" '^(LINKEDIN_CLIENT_ID|LINKEDIN_CLIENT_SECRET|LINKEDIN_ACCESS_TOKEN|LINKEDIN_PERSON_URN)=' \
690
+ "LINKEDIN_CLIENT_ID=${LINKEDIN_CLIENT_ID}" \
691
+ "LINKEDIN_CLIENT_SECRET=${LINKEDIN_CLIENT_SECRET}" \
692
+ "LINKEDIN_ACCESS_TOKEN=${LINKEDIN_ACCESS_TOKEN}" \
693
+ "LINKEDIN_PERSON_URN=${LINKEDIN_PERSON_URN}"
694
+
695
+ echo "Wrote 4 variables to ${env_file} (permissions: 600)" >&2
696
+ }
697
+
698
+ cmd_verify() {
699
+ local repo_root
700
+ repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
701
+ local env_file="${repo_root}/.env"
702
+
703
+ if [[ ! -f "$env_file" ]]; then
704
+ echo "Error: .env file not found at ${env_file}" >&2
705
+ exit 1
706
+ fi
707
+
708
+ # shellcheck disable=SC1090
709
+ set -a
710
+ # The API base above is `readonly` (#7898 review), so if this .env tries to
711
+ # rebind it `source` returns non-zero. Do NOT let `set -e` abort here: that
712
+ # would be a silent death on the credential-loading path. Capture the status,
713
+ # report it, and continue -- the credentials in the same file still load, and
714
+ # the pinned destination keeps its legitimate value.
715
+ _env_rc=0
716
+ source "$env_file" || _env_rc=$?
717
+ set +a
718
+ if (( _env_rc != 0 )); then
719
+ printf 'Note: at least one assignment in %s was refused. A pinned request destination cannot be overridden from .env -- your credentials loaded normally and the request still goes to the real API.\n' "$env_file"
720
+ emit_transport_diag "none" "env-rebind-refused"
721
+ fi
722
+
723
+ cmd_validate_credentials
724
+ }
725
+
726
+ # --- Main ---
727
+
728
+ main() {
729
+ local command="${1:-}"
730
+ shift || true
731
+
732
+ if [[ -z "$command" ]]; then
733
+ echo "Usage: linkedin-setup.sh <command> [args]" >&2
734
+ echo "" >&2
735
+ echo "Commands:" >&2
736
+ echo " validate-credentials [--warn-days N] - Verify token via introspection" >&2
737
+ echo " generate-token - OAuth authorization code exchange" >&2
738
+ echo " write-env - Write credentials to .env" >&2
739
+ echo " verify - Source .env and validate" >&2
740
+ exit 1
741
+ fi
742
+
743
+ require_jq
744
+
745
+ case "$command" in
746
+ validate-credentials) cmd_validate_credentials "$@" ;;
747
+ generate-token) cmd_generate_token ;;
748
+ write-env) cmd_write_env ;;
749
+ verify) cmd_verify ;;
750
+ *)
751
+ echo "Error: Unknown command '${command}'" >&2
752
+ echo "Run 'linkedin-setup.sh' without arguments for usage." >&2
753
+ exit 1
754
+ ;;
755
+ esac
756
+ }
757
+
758
+ main "$@"