@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.
- package/.claude-plugin/agents.manifest.json +476 -0
- package/.claude-plugin/plugin.json +42 -0
- package/README.md +44 -2
- package/agents/community/.gitkeep +0 -0
- package/agents/engineering/cto.md +87 -0
- package/agents/engineering/design/ddd-architect.md +95 -0
- package/agents/engineering/discovery/agent-finder.md +214 -0
- package/agents/engineering/discovery/functional-discovery.md +182 -0
- package/agents/engineering/infra/infra-security.md +151 -0
- package/agents/engineering/infra/platform-strategist.md +109 -0
- package/agents/engineering/infra/terraform-architect.md +171 -0
- package/agents/engineering/prompt-engineer.md +37 -0
- package/agents/engineering/research/best-practices-researcher.md +115 -0
- package/agents/engineering/research/framework-docs-researcher.md +93 -0
- package/agents/engineering/research/git-history-analyzer.md +53 -0
- package/agents/engineering/research/learnings-researcher.md +285 -0
- package/agents/engineering/research/repo-research-analyst.md +118 -0
- package/agents/engineering/review/agent-native-reviewer.md +279 -0
- package/agents/engineering/review/architecture-strategist.md +61 -0
- package/agents/engineering/review/code-quality-analyst.md +92 -0
- package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
- package/agents/engineering/review/data-integrity-guardian.md +82 -0
- package/agents/engineering/review/data-migration-expert.md +98 -0
- package/agents/engineering/review/deployment-verification-agent.md +183 -0
- package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
- package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
- package/agents/engineering/review/legacy-code-expert.md +55 -0
- package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
- package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
- package/agents/engineering/review/performance-oracle.md +130 -0
- package/agents/engineering/review/security-sentinel.md +167 -0
- package/agents/engineering/review/semgrep-sast.md +36 -0
- package/agents/engineering/review/test-design-reviewer.md +150 -0
- package/agents/engineering/review/user-impact-reviewer.md +70 -0
- package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
- package/agents/finance/budget-analyst.md +29 -0
- package/agents/finance/cfo.md +44 -0
- package/agents/finance/financial-reporter.md +30 -0
- package/agents/finance/revenue-analyst.md +30 -0
- package/agents/legal/clo.md +115 -0
- package/agents/legal/legal-compliance-auditor.md +110 -0
- package/agents/legal/legal-document-generator.md +134 -0
- package/agents/marketing/analytics-analyst.md +25 -0
- package/agents/marketing/brand-architect.md +181 -0
- package/agents/marketing/cmo.md +76 -0
- package/agents/marketing/conversion-optimizer.md +19 -0
- package/agents/marketing/copywriter.md +20 -0
- package/agents/marketing/fact-checker.md +70 -0
- package/agents/marketing/growth-strategist.md +145 -0
- package/agents/marketing/paid-media-strategist.md +28 -0
- package/agents/marketing/pricing-strategist.md +17 -0
- package/agents/marketing/programmatic-seo-specialist.md +25 -0
- package/agents/marketing/retention-strategist.md +23 -0
- package/agents/marketing/seo-aeo-analyst.md +164 -0
- package/agents/operations/coo.md +50 -0
- package/agents/operations/ops-advisor.md +78 -0
- package/agents/operations/ops-provisioner.md +101 -0
- package/agents/operations/ops-research.md +52 -0
- package/agents/operations/service-automator.md +278 -0
- package/agents/product/business-validator.md +206 -0
- package/agents/product/competitive-intelligence.md +89 -0
- package/agents/product/cpo.md +63 -0
- package/agents/product/design/ux-design-lead.md +209 -0
- package/agents/product/spec-flow-analyzer.md +122 -0
- package/agents/sales/cro.md +44 -0
- package/agents/sales/deal-architect.md +29 -0
- package/agents/sales/outbound-strategist.md +29 -0
- package/agents/sales/pipeline-analyst.md +29 -0
- package/agents/support/cco.md +48 -0
- package/agents/support/community-manager.md +570 -0
- package/agents/support/ticket-triage.md +70 -0
- package/bin/soleur-opencode.js +29 -0
- package/commands/go.md +659 -0
- package/commands/help.md +273 -0
- package/commands/sync.md +977 -0
- package/commands/version.md +103 -0
- package/hooks/antigravity-hooks.json +84 -0
- package/hooks/antigravity-session-start.sh +15 -0
- package/hooks/bare-root-cwd-guard.sh +131 -0
- package/hooks/browser-snapshot-credential-guard.sh +200 -0
- package/hooks/codex-session-start.sh +9 -0
- package/hooks/compaction-state.sh +525 -0
- package/hooks/destructive-command-guard.sh +1387 -0
- package/hooks/devin-session-start.sh +90 -0
- package/hooks/ext-filter-detect.sh +171 -0
- package/hooks/hooks.json +133 -0
- package/hooks/lib/hook-tool-kind.sh +24 -0
- package/hooks/lib/log-rotation.sh +176 -0
- package/hooks/lib/shell-argv.pl +576 -0
- package/hooks/memory-backstop.sh +1114 -0
- package/hooks/operator-stage-approval.sh +391 -0
- package/hooks/stop-hook.sh +367 -0
- package/hooks/unkept-promise-hook.sh +383 -0
- package/hooks/version-banner.sh +127 -0
- package/hooks/welcome-hook.sh +50 -0
- package/install.js +108 -0
- package/lib/agent-registry.ts +154 -0
- package/lib/c4-canonical-cli.mjs +34 -0
- package/lib/c4-canonical.mjs +46 -0
- package/lib/c4-from-components.ts +554 -0
- package/lib/go-routing.ts +81 -0
- package/lib/grok-inspect-contract.ts +311 -0
- package/lib/harness-model-map.ts +101 -0
- package/lib/harness-parity.ts +726 -0
- package/lib/harness-tool-map.ts +180 -0
- package/lib/harness.ts +716 -0
- package/lib/heartbeat-live-reconcile.ts +1153 -0
- package/lib/heartbeat-manifest.ts +342 -0
- package/lib/kb-coverage.ts +252 -0
- package/lib/pr-merge-poll.ts +103 -0
- package/lib/workflow-fidelity.ts +388 -0
- package/opencode/INSTRUCTIONS.md +152 -0
- package/opencode/agents/engineering/cto.md +10 -0
- package/opencode/agents/engineering/design/ddd-architect.md +10 -0
- package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
- package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
- package/opencode/agents/engineering/infra/infra-security.md +10 -0
- package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
- package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
- package/opencode/agents/engineering/prompt-engineer.md +10 -0
- package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
- package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
- package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
- package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
- package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
- package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
- package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
- package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
- package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
- package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
- package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
- package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
- package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
- package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
- package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
- package/opencode/agents/engineering/review/performance-oracle.md +10 -0
- package/opencode/agents/engineering/review/security-sentinel.md +10 -0
- package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
- package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
- package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
- package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
- package/opencode/agents/finance/budget-analyst.md +10 -0
- package/opencode/agents/finance/cfo.md +10 -0
- package/opencode/agents/finance/financial-reporter.md +10 -0
- package/opencode/agents/finance/revenue-analyst.md +10 -0
- package/opencode/agents/legal/clo.md +10 -0
- package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
- package/opencode/agents/legal/legal-document-generator.md +10 -0
- package/opencode/agents/marketing/analytics-analyst.md +10 -0
- package/opencode/agents/marketing/brand-architect.md +10 -0
- package/opencode/agents/marketing/cmo.md +10 -0
- package/opencode/agents/marketing/conversion-optimizer.md +10 -0
- package/opencode/agents/marketing/copywriter.md +10 -0
- package/opencode/agents/marketing/fact-checker.md +10 -0
- package/opencode/agents/marketing/growth-strategist.md +10 -0
- package/opencode/agents/marketing/paid-media-strategist.md +10 -0
- package/opencode/agents/marketing/pricing-strategist.md +10 -0
- package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
- package/opencode/agents/marketing/retention-strategist.md +10 -0
- package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
- package/opencode/agents/operations/coo.md +10 -0
- package/opencode/agents/operations/ops-advisor.md +10 -0
- package/opencode/agents/operations/ops-provisioner.md +10 -0
- package/opencode/agents/operations/ops-research.md +10 -0
- package/opencode/agents/operations/service-automator.md +10 -0
- package/opencode/agents/product/business-validator.md +10 -0
- package/opencode/agents/product/competitive-intelligence.md +10 -0
- package/opencode/agents/product/cpo.md +10 -0
- package/opencode/agents/product/design/ux-design-lead.md +10 -0
- package/opencode/agents/product/spec-flow-analyzer.md +10 -0
- package/opencode/agents/sales/cro.md +10 -0
- package/opencode/agents/sales/deal-architect.md +10 -0
- package/opencode/agents/sales/outbound-strategist.md +10 -0
- package/opencode/agents/sales/pipeline-analyst.md +10 -0
- package/opencode/agents/support/cco.md +10 -0
- package/opencode/agents/support/community-manager.md +10 -0
- package/opencode/agents/support/ticket-triage.md +10 -0
- package/opencode/commands/go.md +10 -0
- package/opencode/commands/help.md +10 -0
- package/opencode/commands/sync.md +10 -0
- package/opencode/commands/version.md +11 -0
- package/opencode/opencode.json +8 -0
- package/opencode/plugin.ts +493 -0
- package/package.json +36 -4
- package/scripts/admin-merge-ready.sh +545 -0
- package/scripts/admin-merge-ready.test.sh +753 -0
- package/scripts/affected-tests.sh +908 -0
- package/scripts/affected-tests.test.sh +1797 -0
- package/scripts/alpha-metrics.sh +78 -0
- package/scripts/alpha-metrics.test.sh +115 -0
- package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
- package/scripts/audit-flag-flip.sh +107 -0
- package/scripts/check-red-on-main.sh +316 -0
- package/scripts/ci-head-verdict.sh +363 -0
- package/scripts/cloud-detect.sh +193 -0
- package/scripts/delegation-verify.sh +657 -0
- package/scripts/delegation-verify.test.sh +1424 -0
- package/scripts/deploy-arm.sh +475 -0
- package/scripts/domain-model-drift.sh +419 -0
- package/scripts/emit-decision.sh +144 -0
- package/scripts/emit-decision.test.sh +144 -0
- package/scripts/ext-filter-divergence-corpus.txt +15 -0
- package/scripts/ext-filter-divergence.sh +189 -0
- package/scripts/generate-c4-from-components.ts +442 -0
- package/scripts/grok-fidelity-gate.sh +68 -0
- package/scripts/grok-pre-push-gate.sh +232 -0
- package/scripts/harness-discovery-smoke.ts +906 -0
- package/scripts/harness-parity-census.ts +92 -0
- package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
- package/scripts/install-opencode-adapter.ts +520 -0
- package/scripts/lib/affected-tests/common.sh +365 -0
- package/scripts/lib/affected-tests/go.sh +157 -0
- package/scripts/lib/affected-tests/js.sh +188 -0
- package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
- package/scripts/lib/affected-tests/python.sh +185 -0
- package/scripts/lib/context-sanitize.sh +49 -0
- package/scripts/lib/domain-model-lib.sh +180 -0
- package/scripts/lib/ext-filter-grammar.tsv +34 -0
- package/scripts/lib/operator-script.sh +1236 -0
- package/scripts/lib/proc.sh +580 -0
- package/scripts/lib/session-state.sh +748 -0
- package/scripts/lib/tmp-classify.sh +843 -0
- package/scripts/lib/wrapper-boundary.sh +47 -0
- package/scripts/lint-ratchet.py +1547 -0
- package/scripts/monitor-pr-checks.sh +441 -0
- package/scripts/pipeline-tally.sh +756 -0
- package/scripts/pipeline-tally.test.sh +740 -0
- package/scripts/plugin-version.sh +528 -0
- package/scripts/precommit-guard.sh +113 -0
- package/scripts/precommit-guard.test.sh +254 -0
- package/scripts/reconcile-live-heartbeats.ts +705 -0
- package/scripts/render-c4-model.sh +217 -0
- package/scripts/resolve-git-root.sh +55 -0
- package/scripts/resolve-regenerable-conflicts.sh +363 -0
- package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
- package/scripts/ruff-requirements.txt +22 -0
- package/scripts/run-in-pid-namespace.sh +98 -0
- package/scripts/run-in-pid-namespace.test.sh +290 -0
- package/scripts/scan-ancestor-signal-helpers.py +556 -0
- package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
- package/scripts/setup-ralph-loop.sh +238 -0
- package/scripts/sync-cursor-name-map.ts +316 -0
- package/scripts/sync-grok-agent-compat.ts +188 -0
- package/scripts/sync-npm-package.ts +178 -0
- package/scripts/sync-opencode-adapter.ts +479 -0
- package/scripts/sync-pr-behind.sh +534 -0
- package/scripts/sync-pr-behind.test.sh +252 -0
- package/scripts/taste-profile-update.sh +180 -0
- package/scripts/taste-profile-update.test.sh +167 -0
- package/scripts/write-budget-marker.sh +23 -0
- package/scripts/write-kb-coverage.ts +113 -0
- package/skills/admin-ip-refresh/SKILL.md +81 -0
- package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
- package/skills/agent-browser/SKILL.md +698 -0
- package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
- package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
- package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
- package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
- package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
- package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
- package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
- package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
- package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
- package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
- package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
- package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
- package/skills/agent-native-architecture/SKILL.md +479 -0
- package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
- package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
- package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
- package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
- package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
- package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
- package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
- package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
- package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
- package/skills/agent-native-architecture/references/product-implications.md +478 -0
- package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
- package/skills/agent-native-architecture/references/self-modification.md +294 -0
- package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
- package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
- package/skills/agent-native-audit/SKILL.md +300 -0
- package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
- package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
- package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
- package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/skills/architecture/SKILL.md +436 -0
- package/skills/architecture/references/adr-template.md +154 -0
- package/skills/architecture/references/likec4-reference.md +136 -0
- package/skills/architecture/references/nfr-reference.md +126 -0
- package/skills/archive-kb/SKILL.md +86 -0
- package/skills/archive-kb/scripts/archive-kb.sh +300 -0
- package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
- package/skills/atdd-developer/SKILL.md +65 -0
- package/skills/brainstorm/SKILL.md +701 -0
- package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
- package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
- package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
- package/skills/brainstorm/references/ui-surface-terms.md +42 -0
- package/skills/brainstorm-techniques/SKILL.md +231 -0
- package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
- package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
- package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
- package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
- package/skills/campaign-calendar/SKILL.md +145 -0
- package/skills/cf-token-scope/SKILL.md +164 -0
- package/skills/cf-token-scope/references/widen-playbook.md +114 -0
- package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
- package/skills/changelog/SKILL.md +142 -0
- package/skills/code-to-prd/SKILL.md +66 -0
- package/skills/code-to-prd/references/banner-template.md +12 -0
- package/skills/code-to-prd/references/prd-template.md +41 -0
- package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
- package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
- package/skills/code-to-prd/test/fixture/.env.example +7 -0
- package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
- package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
- package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
- package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
- package/skills/code-to-prd/test/fixture/package.json +6 -0
- package/skills/cohort-status/SKILL.md +62 -0
- package/skills/community/SKILL.md +210 -0
- package/skills/community/scripts/bsky-community.sh +644 -0
- package/skills/community/scripts/bsky-setup.sh +631 -0
- package/skills/community/scripts/community-router.sh +93 -0
- package/skills/community/scripts/discord-community.sh +487 -0
- package/skills/community/scripts/discord-setup.sh +784 -0
- package/skills/community/scripts/github-community.sh +706 -0
- package/skills/community/scripts/hn-community.sh +233 -0
- package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
- package/skills/community/scripts/linkedin-community.sh +750 -0
- package/skills/community/scripts/linkedin-setup.sh +758 -0
- package/skills/community/scripts/x-community.sh +890 -0
- package/skills/community/scripts/x-setup.sh +713 -0
- package/skills/community/test/community-argv.test.sh +2922 -0
- package/skills/community/test/github-community.test.sh +981 -0
- package/skills/competitive-analysis/SKILL.md +69 -0
- package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
- package/skills/compound/SKILL.md +673 -0
- package/skills/compound/scripts/token-efficiency-report.sh +334 -0
- package/skills/compound/test/phase-16.test.sh +379 -0
- package/skills/compound-capture/SKILL.md +791 -0
- package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
- package/skills/compound-capture/assets/resolution-template.md +116 -0
- package/skills/compound-capture/references/yaml-schema.md +67 -0
- package/skills/compound-capture/schema.yaml +181 -0
- package/skills/constraint-scaffold/SKILL.md +175 -0
- package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
- package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
- package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
- package/skills/constraint-scaffold/references/shared-runner.template +135 -0
- package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
- package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
- package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
- package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
- package/skills/constraint-scaffold/test/generator.test.sh +93 -0
- package/skills/constraint-scaffold/test/parity.test.sh +285 -0
- package/skills/content-writer/SKILL.md +242 -0
- package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
- package/skills/cron-delete/SKILL.md +56 -0
- package/skills/cron-list/SKILL.md +54 -0
- package/skills/deepen-plan/SKILL.md +937 -0
- package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
- package/skills/deploy/SKILL.md +115 -0
- package/skills/deploy/references/hetzner-setup.md +65 -0
- package/skills/deploy/scripts/deploy.sh +37 -0
- package/skills/deploy-docs/SKILL.md +95 -0
- package/skills/dhh-rails-style/SKILL.md +203 -0
- package/skills/dhh-rails-style/references/architecture.md +717 -0
- package/skills/dhh-rails-style/references/controllers.md +323 -0
- package/skills/dhh-rails-style/references/frontend.md +556 -0
- package/skills/dhh-rails-style/references/gems.md +316 -0
- package/skills/dhh-rails-style/references/models.md +388 -0
- package/skills/dhh-rails-style/references/testing.md +354 -0
- package/skills/discord-content/SKILL.md +126 -0
- package/skills/docs-site/SKILL.md +214 -0
- package/skills/drain-labeled-backlog/SKILL.md +203 -0
- package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
- package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
- package/skills/drain-prs/SKILL.md +142 -0
- package/skills/drain-prs/scripts/triage-prs.sh +192 -0
- package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
- package/skills/dspy-ruby/SKILL.md +634 -0
- package/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/skills/dspy-ruby/references/core-concepts.md +268 -0
- package/skills/dspy-ruby/references/optimization.md +623 -0
- package/skills/dspy-ruby/references/providers.md +335 -0
- package/skills/eval-harness/README.md +194 -0
- package/skills/eval-harness/SKILL.md +157 -0
- package/skills/eval-harness/enums/go-routes.json +13 -0
- package/skills/eval-harness/enums/incident-threshold.json +5 -0
- package/skills/eval-harness/enums/lane.json +5 -0
- package/skills/eval-harness/enums/tool-selection.json +20 -0
- package/skills/eval-harness/enums/triage-levels.json +5 -0
- package/skills/eval-harness/gated-skills.json +34 -0
- package/skills/eval-harness/models.generated.json +5 -0
- package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
- package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
- package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
- package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
- package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
- package/skills/eval-harness/prompts/go-baseline.txt +6 -0
- package/skills/eval-harness/prompts/go-skill.txt +21 -0
- package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
- package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
- package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
- package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
- package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
- package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
- package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
- package/skills/eval-harness/prompts/triage-skill.txt +10 -0
- package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
- package/skills/eval-harness/scripts/extract-block.cjs +87 -0
- package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
- package/skills/eval-harness/scripts/gen-models.sh +41 -0
- package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
- package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
- package/skills/eval-harness/scripts/parse-label.cjs +93 -0
- package/skills/eval-harness/scripts/verdict.cjs +132 -0
- package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
- package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
- package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
- package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
- package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
- package/skills/eval-harness/test/eval-gate.test.sh +132 -0
- package/skills/eval-harness/test/extract-block.test.sh +87 -0
- package/skills/eval-harness/test/gate-classification.test.sh +77 -0
- package/skills/eval-harness/test/gen-models.test.sh +75 -0
- package/skills/eval-harness/test/measure-classification.test.sh +74 -0
- package/skills/eval-harness/test/parse-label.test.sh +129 -0
- package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
- package/skills/eval-harness/test/verdict.test.sh +123 -0
- package/skills/every-style-editor/SKILL.md +142 -0
- package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/skills/feature-tweet/SKILL.md +199 -0
- package/skills/feature-video/SKILL.md +412 -0
- package/skills/feature-video/scripts/check_deps.sh +281 -0
- package/skills/file-todos/SKILL.md +267 -0
- package/skills/file-todos/assets/todo-template.md +166 -0
- package/skills/fix-issue/SKILL.md +278 -0
- package/skills/fix-issue/references/agent-authored-exclusion.md +118 -0
- package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -0
- package/skills/flag-bootstrap/SETUP.md +142 -0
- package/skills/flag-create/SKILL.md +145 -0
- package/skills/flag-create/scripts/create.sh +273 -0
- package/skills/flag-delete/SKILL.md +167 -0
- package/skills/flag-delete/scripts/delete.sh +288 -0
- package/skills/flag-list/SKILL.md +99 -0
- package/skills/flag-list/scripts/list.sh +275 -0
- package/skills/flag-set-role/SKILL.md +229 -0
- package/skills/flag-set-role/scripts/flip.sh +774 -0
- package/skills/frontend-anti-slop/SKILL.md +106 -0
- package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
- package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
- package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
- package/skills/frontend-design/SKILL.md +77 -0
- package/skills/gdpr-gate/NOTICE +136 -0
- package/skills/gdpr-gate/SKILL.md +341 -0
- package/skills/gdpr-gate/references/fields.md +160 -0
- package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
- package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
- package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
- package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
- package/skills/gdpr-gate/references/layers/frontend.md +249 -0
- package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
- package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
- package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
- package/skills/gdpr-gate/references/legal-consent.md +140 -0
- package/skills/gdpr-gate/references/non-negotiables.md +60 -0
- package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
- package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
- package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
- package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
- package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
- package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
- package/skills/gemini-imagegen/SKILL.md +283 -0
- package/skills/gemini-imagegen/requirements.txt +5 -0
- package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
- package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
- package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
- package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
- package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
- package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
- package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
- package/skills/git-worktree/SKILL.md +534 -0
- package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
- package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
- package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
- package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
- package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
- package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
- package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
- package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
- package/skills/go/SKILL.md +54 -0
- package/skills/growth/SKILL.md +164 -0
- package/skills/harvest-debt/SKILL.md +78 -0
- package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
- package/skills/heal-skill/SKILL.md +173 -0
- package/skills/help/SKILL.md +46 -0
- package/skills/incident/NOTICE +39 -0
- package/skills/incident/SKILL.md +395 -0
- package/skills/incident/scripts/dry-run.sh +448 -0
- package/skills/incident/scripts/redact-engine.py +412 -0
- package/skills/incident/scripts/redact-sentinel.sh +44 -0
- package/skills/incident/templates/pir.md +139 -0
- package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
- package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
- package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
- package/skills/incident/test/fixtures/positive-corpus.md +87 -0
- package/skills/incident/test/redact-sentinel.test.sh +1420 -0
- package/skills/invoice/SKILL.md +206 -0
- package/skills/kb-glossary/SKILL.md +71 -0
- package/skills/kb-glossary/references/glossary-format.md +111 -0
- package/skills/kb-glossary/references/rejected-request-register.md +115 -0
- package/skills/kb-search/SKILL.md +276 -0
- package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
- package/skills/legal-audit/SKILL.md +161 -0
- package/skills/legal-generate/NOTICE +242 -0
- package/skills/legal-generate/SKILL.md +244 -0
- package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
- package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
- package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
- package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
- package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
- package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
- package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
- package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
- package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
- package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
- package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
- package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
- package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
- package/skills/linear-fetch/SKILL.md +247 -0
- package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
- package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
- package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
- package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
- package/skills/linear-fetch/test/parity.test.sh +112 -0
- package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
- package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
- package/skills/merge-pr/SKILL.md +668 -0
- package/skills/model-launch-review/SKILL.md +255 -0
- package/skills/model-launch-review/scripts/audit-models.sh +501 -0
- package/skills/one-shot/SKILL.md +316 -0
- package/skills/one-shot/references/token-discipline.md +74 -0
- package/skills/operator-bootstrap/SKILL.md +412 -0
- package/skills/operator-bootstrap/template.sh +439 -0
- package/skills/operator-digest/SKILL.md +287 -0
- package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
- package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
- package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
- package/skills/operator-rephrase/SKILL.md +98 -0
- package/skills/pencil-setup/SKILL.md +201 -0
- package/skills/pencil-setup/scripts/check_deps.sh +556 -0
- package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
- package/skills/pencil-setup/scripts/package-lock.json +1172 -0
- package/skills/pencil-setup/scripts/package.json +14 -0
- package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
- package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
- package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
- package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
- package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
- package/skills/plan/SKILL.md +1140 -0
- package/skills/plan/references/differential-migration-testing.md +185 -0
- package/skills/plan/references/plan-community-discovery.md +42 -0
- package/skills/plan/references/plan-founder-check.md +137 -0
- package/skills/plan/references/plan-functional-overlap.md +25 -0
- package/skills/plan/references/plan-host-fanout.md +51 -0
- package/skills/plan/references/plan-issue-templates.md +779 -0
- package/skills/plan/references/plan-network-outage-checklist.md +131 -0
- package/skills/plan/references/plan-scope-check.md +126 -0
- package/skills/plan/references/plan-sharp-edges.md +325 -0
- package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
- package/skills/plan/references/webapp-cwv-observability.md +143 -0
- package/skills/plan-review/SKILL.md +91 -0
- package/skills/plan-review/lib/named-panel.mjs +58 -0
- package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
- package/skills/postmerge/SKILL.md +642 -0
- package/skills/postmerge/references/deploy-status-debugging.md +138 -0
- package/skills/postmerge/references/graceful-degradation.md +20 -0
- package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
- package/skills/preflight/SKILL.md +1553 -0
- package/skills/preflight/references/check-13-founder-check.md +286 -0
- package/skills/preflight/scripts/founder-check.py +1640 -0
- package/skills/preflight/scripts/parse-form-a.awk +107 -0
- package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
- package/skills/product-roadmap/SKILL.md +357 -0
- package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
- package/skills/provision-cloudflare/SKILL.md +67 -0
- package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
- package/skills/provision-doppler/SKILL.md +58 -0
- package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
- package/skills/provision-github/SKILL.md +59 -0
- package/skills/provision-github/scripts/provision-github.sh +300 -0
- package/skills/provision-hetzner/SKILL.md +76 -0
- package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
- package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
- package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
- package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
- package/skills/qa/SKILL.md +263 -0
- package/skills/qa/references/browser-preflight.md +22 -0
- package/skills/questionnaire-generate/SKILL.md +328 -0
- package/skills/questionnaire-generate/references/questionnaire.template +60 -0
- package/skills/rclone/SKILL.md +162 -0
- package/skills/rclone/scripts/check_setup.sh +63 -0
- package/skills/release-announce/SKILL.md +65 -0
- package/skills/release-docs/SKILL.md +92 -0
- package/skills/reproduce-bug/SKILL.md +358 -0
- package/skills/resolve-debt/SKILL.md +85 -0
- package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
- package/skills/resolve-parallel/SKILL.md +45 -0
- package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
- package/skills/resolve-pr-parallel/SKILL.md +68 -0
- package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
- package/skills/resolve-todo-parallel/SKILL.md +57 -0
- package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
- package/skills/review/SKILL.md +1621 -0
- package/skills/review/references/anti-slop-scanner-hook.md +40 -0
- package/skills/review/references/defect-classes-migrations.md +9 -0
- package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
- package/skills/review/references/review-e2e-testing.md +116 -0
- package/skills/review/references/review-todo-structure.md +165 -0
- package/skills/review/references/risk-tier-and-fix-rounds.md +95 -0
- package/skills/review/references/semgrep-custom-rules.yaml +174 -0
- package/skills/review/references/ultrathink-phases.md +98 -0
- package/skills/review/references/wfs.md +6 -0
- package/skills/review/scripts/emit-review-trailer.sh +499 -0
- package/skills/review/scripts/ensure-semgrep.sh +99 -0
- package/skills/review/scripts/fix-round-seats.sh +197 -0
- package/skills/review/test/emit-review-trailer.test.sh +387 -0
- package/skills/review/test/ensure-semgrep.test.sh +111 -0
- package/skills/review/test/fix-round-seats.test.sh +237 -0
- package/skills/review/workflows/README.md +147 -0
- package/skills/review/workflows/review.workflow.js +667 -0
- package/skills/schedule/SKILL.md +796 -0
- package/skills/seo-aeo/SKILL.md +139 -0
- package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
- package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
- package/skills/ship/SKILL.md +3022 -0
- package/skills/ship/references/adr-ordinal-collision.md +47 -0
- package/skills/ship/references/affected-gate-contract.md +115 -0
- package/skills/ship/references/ci-run-diagnosis.md +29 -0
- package/skills/ship/references/ci-workflow-authoring.md +68 -0
- package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
- package/skills/ship/references/cto-ruling-armed-push.md +5 -0
- package/skills/ship/references/decision-challenges-render.md +71 -0
- package/skills/ship/references/detached-battery-verdict.md +13 -0
- package/skills/ship/references/feature-tweet-draft.md +42 -0
- package/skills/ship/references/followthrough-issue-body-template.md +47 -0
- package/skills/ship/references/followthrough-stub-template.sh +92 -0
- package/skills/ship/references/merge-queue-dequeue.md +39 -0
- package/skills/ship/references/operator-holds.md +149 -0
- package/skills/ship/references/pipeline-tally-render.md +43 -0
- package/skills/ship/references/pr-overlap.md +153 -0
- package/skills/ship/references/queue-mode.md +7 -0
- package/skills/ship/references/ready-run-wait.md +36 -0
- package/skills/ship/references/red-on-main-quarantine.md +110 -0
- package/skills/ship/references/register-update-pr-pattern.md +88 -0
- package/skills/ship/references/settle-then-admin-merge.md +130 -0
- package/skills/ship/references/supabase-column-probe.md +13 -0
- package/skills/ship/scripts/auto-close-scan.sh +76 -0
- package/skills/ship/scripts/battery-owed.sh +403 -0
- package/skills/ship/scripts/hold-gate-eval.sh +410 -0
- package/skills/ship/scripts/net-issue-flow.sh +665 -0
- package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
- package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
- package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
- package/skills/skill-creator/SKILL.md +299 -0
- package/skills/skill-creator/references/api-security.md +248 -0
- package/skills/skill-creator/references/authoring-levers.md +110 -0
- package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
- package/skills/skill-creator/references/best-practices.md +431 -0
- package/skills/skill-creator/references/common-patterns.md +639 -0
- package/skills/skill-creator/references/core-principles.md +193 -0
- package/skills/skill-creator/references/executable-code.md +188 -0
- package/skills/skill-creator/references/iteration-and-testing.md +509 -0
- package/skills/skill-creator/references/official-spec.md +193 -0
- package/skills/skill-creator/references/recommended-structure.md +180 -0
- package/skills/skill-creator/references/skill-structure.md +396 -0
- package/skills/skill-creator/references/using-scripts.md +122 -0
- package/skills/skill-creator/references/using-templates.md +120 -0
- package/skills/skill-creator/references/workflows-and-validation.md +541 -0
- package/skills/skill-creator/scripts/init_skill.py +322 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +64 -0
- package/skills/skill-creator/templates/router-skill.md +73 -0
- package/skills/skill-creator/templates/simple-skill.md +34 -0
- package/skills/skill-creator/workflows/add-reference.md +96 -0
- package/skills/skill-creator/workflows/add-script.md +104 -0
- package/skills/skill-creator/workflows/add-template.md +83 -0
- package/skills/skill-creator/workflows/add-workflow.md +128 -0
- package/skills/skill-creator/workflows/audit-skill.md +159 -0
- package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
- package/skills/skill-creator/workflows/create-new-skill.md +207 -0
- package/skills/skill-creator/workflows/get-guidance.md +134 -0
- package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
- package/skills/skill-creator/workflows/verify-skill.md +226 -0
- package/skills/skill-security-scan/SKILL.md +145 -0
- package/skills/skill-security-scan/references/disclaimer.md +21 -0
- package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
- package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
- package/skills/skill-security-scan/references/override-mechanism.md +171 -0
- package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
- package/skills/skill-security-scan/references/regex-patterns.md +136 -0
- package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
- package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
- package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
- package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
- package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
- package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
- package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
- package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
- package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
- package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
- package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
- package/skills/skill-security-scan/scripts/lib.sh +197 -0
- package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
- package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
- package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
- package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
- package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
- package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
- package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
- package/skills/social-distribute/SKILL.md +485 -0
- package/skills/spec-templates/SKILL.md +274 -0
- package/skills/sync/SKILL.md +49 -0
- package/skills/test-browser/SKILL.md +464 -0
- package/skills/test-browser/references/feature-map.md +161 -0
- package/skills/test-fix-loop/SKILL.md +159 -0
- package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
- package/skills/triage/SKILL.md +402 -0
- package/skills/trigger-cron/SKILL.md +136 -0
- package/skills/trigger-cron/scripts/trigger.sh +198 -0
- package/skills/user-set-role/SKILL.md +147 -0
- package/skills/user-set-role/scripts/set-role.sh +218 -0
- package/skills/user-story-writer/SKILL.md +77 -0
- package/skills/ux-audit/SKILL.md +251 -0
- package/skills/ux-audit/references/finding.schema.json +62 -0
- package/skills/ux-audit/references/route-list.yaml +95 -0
- package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
- package/skills/ux-audit/scripts/bot-signin.ts +135 -0
- package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
- package/skills/work/SKILL.md +1479 -0
- package/skills/work/references/end-of-work-resume.md +7 -0
- package/skills/work/references/hook-test-main-cwd.md +24 -0
- package/skills/work/references/key-principles.md +83 -0
- package/skills/work/references/property-testing.md +122 -0
- package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
- package/skills/work/references/work-agent-teams.md +97 -0
- package/skills/work/references/work-lifecycle-parallel.md +147 -0
- package/skills/work/references/work-lockfile-bumps.md +97 -0
- package/skills/work/references/work-scratch-sandboxes.md +212 -0
- package/skills/work/references/work-subagent-fanout.md +61 -0
- package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
- package/skills/xcode-test/SKILL.md +360 -0
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# linkedin-community.sh -- LinkedIn API wrapper for community operations
|
|
3
|
+
#
|
|
4
|
+
# Usage: linkedin-community.sh <command> [args]
|
|
5
|
+
# Commands:
|
|
6
|
+
# post-content --text "<text>" [--author "<urn>"] - Post to LinkedIn (person or company page)
|
|
7
|
+
# fetch-metrics - Fetch aggregate Company Page metrics (org read scopes)
|
|
8
|
+
# fetch-activity - Fetch recent Company Page post metadata (org read scopes)
|
|
9
|
+
#
|
|
10
|
+
# fetch-metrics / fetch-activity history: implemented 2026-06-15. The prior
|
|
11
|
+
# "requires Marketing Developer Platform (MDP) partner approval" premise was
|
|
12
|
+
# incorrect — the org token already carries the read scopes
|
|
13
|
+
# (r_organization_social, rw_organization_admin, r_organization_followers).
|
|
14
|
+
# Collection is aggregate-only: share statistics + a single follower total +
|
|
15
|
+
# recent org-authored post metadata. NO per-member data, NO follower-list
|
|
16
|
+
# extraction, NO demographic facets.
|
|
17
|
+
#
|
|
18
|
+
# Environment variables (required):
|
|
19
|
+
# LINKEDIN_ACCESS_TOKEN - OAuth 2.0 Bearer token for personal posts (60-day TTL)
|
|
20
|
+
# LINKEDIN_ORG_ACCESS_TOKEN - OAuth 2.0 Bearer token for organization/company page
|
|
21
|
+
# operations. Required for fetch-metrics/fetch-activity
|
|
22
|
+
# (org read scopes: r_organization_social,
|
|
23
|
+
# rw_organization_admin) and for org posts
|
|
24
|
+
# (w_organization_social, --author urn:li:organization:*).
|
|
25
|
+
# LINKEDIN_ORG_ID - Numeric organization id. Required for
|
|
26
|
+
# fetch-metrics/fetch-activity (builds the org URN).
|
|
27
|
+
# LINKEDIN_PERSON_URN - Person URN for posting (urn:li:person:{id}), optional if --author provided
|
|
28
|
+
# LINKEDIN_ALLOW_POST - Set to "true" to enable posting (safety guard, default: disabled)
|
|
29
|
+
#
|
|
30
|
+
# Exit codes:
|
|
31
|
+
# 0 - Success
|
|
32
|
+
# 1 - General error
|
|
33
|
+
# 2 - Retryable error (rate limit exhaustion)
|
|
34
|
+
#
|
|
35
|
+
# Output: JSON to stdout
|
|
36
|
+
# Errors: Messages to stderr, exit 1
|
|
37
|
+
|
|
38
|
+
set -euo pipefail
|
|
39
|
+
|
|
40
|
+
# (#7797) Xtrace refusal -- the FIRST thing after `set …`, so nothing above it is
|
|
41
|
+
# traced. This script ships to installed Soleur CLIs: the terminal it runs in
|
|
42
|
+
# belongs to the founder, and `bash -x` would print their live LinkedIn
|
|
43
|
+
# credentials. The `:+x` form tests non-emptiness WITHOUT expanding the value, so
|
|
44
|
+
# the guard cannot leak the thing it is refusing over. CONDITIONAL, and that is
|
|
45
|
+
# measured rather than inherited: every credential this file handles is bound
|
|
46
|
+
# BEFORE the prologue runs (this script's own credential check reads them from
|
|
47
|
+
# the environment, it never acquires one at runtime), so the guard can never be
|
|
48
|
+
# empty while a credential is live -- and a founder tracing with none set keeps
|
|
49
|
+
# full tracing.
|
|
50
|
+
# Refusal goes to STDOUT, not stderr: agent runtimes surface stdout and swallow
|
|
51
|
+
# stderr (constitution.md > Code Style > Always), and a swallowed security refusal
|
|
52
|
+
# leaves the user with a bare `exit 78` and no text at all.
|
|
53
|
+
case "$-" in
|
|
54
|
+
*x*)
|
|
55
|
+
if [ -n "${LINKEDIN_ACCESS_TOKEN:+x}${LINKEDIN_ORG_ACCESS_TOKEN:+x}" ]; then
|
|
56
|
+
printf 'Refusing to run under `bash -x`: LINKEDIN_ACCESS_TOKEN, LINKEDIN_ORG_ACCESS_TOKEN are set, and tracing would print them to your terminal. To trace safely, unset all of them and re-run.\n'
|
|
57
|
+
exit 78
|
|
58
|
+
fi
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
|
|
62
|
+
# (#7873) `--disable` closes ~/.curlrc and `--noproxy '*'` closes the proxy
|
|
63
|
+
# variables, but NEITHER touches the trust store or the TLS session-key log. On an
|
|
64
|
+
# installed CLI the environment belongs to someone who is not us, so
|
|
65
|
+
# a substituted `CURL_CA_BUNDLE` is a clean MITM of the founder's platform
|
|
66
|
+
# token with every other guard fully intact, and `SSLKEYLOGFILE` is passive
|
|
67
|
+
# decryption with no MITM at all. Matches the line in scripts/betterstack-query.sh.
|
|
68
|
+
unset SSLKEYLOGFILE CURL_CA_BUNDLE SSL_CERT_FILE SSL_CERT_DIR CURL_HOME \
|
|
69
|
+
HOSTALIASES LOCALDOMAIN RES_OPTIONS
|
|
70
|
+
|
|
71
|
+
# --- Transport-confinement diagnostics (#7873) --------------------------------
|
|
72
|
+
# Every credentialed `curl` below carries `--disable --noproxy '*'` and discards
|
|
73
|
+
# curl's own stderr (which can carry the URL). A failed request then has four
|
|
74
|
+
# competing causes the founder cannot tell apart: the xtrace refusal, the proxy
|
|
75
|
+
# this script deliberately bypassed, a ~/.curlrc that `--disable` dropped, or an
|
|
76
|
+
# ordinary network failure. So each failure emits ONE structured marker carrying
|
|
77
|
+
# all four discriminators, beside a human line that names the bypass instead of
|
|
78
|
+
# blaming the founder's connectivity.
|
|
79
|
+
SOLEUR_TRANSPORT_SCRIPT="linkedin-community.sh"
|
|
80
|
+
SOLEUR_TRANSPORT_PLATFORM="LinkedIn"
|
|
81
|
+
|
|
82
|
+
# The request helpers below run inside `$(...)`, where fd 1 is the capture pipe.
|
|
83
|
+
# Save the script's real stdout so the marker reaches the founder's terminal (and
|
|
84
|
+
# the always-on PostToolUse Bash extractor) instead of being swallowed into a
|
|
85
|
+
# variable. The marker is on stdout BY DESIGN -- the call sites' `2>/dev/null` is
|
|
86
|
+
# what keeps curl's URL-carrying stderr out of the transcript, and it must not be
|
|
87
|
+
# able to suppress the diagnostic too.
|
|
88
|
+
exec 3>&1
|
|
89
|
+
|
|
90
|
+
# Which proxy variables are non-empty, or `none`.
|
|
91
|
+
proxy_env_names() {
|
|
92
|
+
local names="" n
|
|
93
|
+
for n in HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy; do
|
|
94
|
+
if [[ -n "${!n-}" ]]; then
|
|
95
|
+
names="${names},${n}"
|
|
96
|
+
fi
|
|
97
|
+
done
|
|
98
|
+
if [[ -z "$names" ]]; then
|
|
99
|
+
printf 'none'
|
|
100
|
+
else
|
|
101
|
+
printf '%s' "${names#,}"
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# True when a proxy this script deliberately bypasses is configured. NO_PROXY
|
|
106
|
+
# alone is not one -- it only ever narrows proxying, so it cannot be the cause.
|
|
107
|
+
proxy_bypassed() {
|
|
108
|
+
[[ -n "${HTTP_PROXY:-}${HTTPS_PROXY:-}${ALL_PROXY:-}${http_proxy:-}${https_proxy:-}${all_proxy:-}" ]]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# One line, eight fields, on the saved stdout.
|
|
112
|
+
#
|
|
113
|
+
# Two fields were literals in the first cut and are now MEASURED (#7898 review):
|
|
114
|
+
# `noproxy_applied` was the constant "true", which ASSERTS the property instead of
|
|
115
|
+
# observing it -- dropping --noproxy from a call site left the diagnostic still
|
|
116
|
+
# claiming it was applied. It now reports whether a proxy was actually configured
|
|
117
|
+
# for this request to bypass, which is the fact a reader needs. `refusal` was the
|
|
118
|
+
# constant "none" at every call site, so the cause it exists to discriminate could
|
|
119
|
+
# never appear; it now carries `env-rebind-refused` and `xtrace-credential-bound`.
|
|
120
|
+
#
|
|
121
|
+
# `tls_env_cleared` is new. The prologue unsets CURL_CA_BUNDLE/SSL_CERT_FILE et al,
|
|
122
|
+
# which is correct against an attacker and BREAKS a founder whose corporate CA
|
|
123
|
+
# arrives that way -- curl then exits 60 and, without this field, the event is
|
|
124
|
+
# indistinguishable from an ordinary network failure.
|
|
125
|
+
emit_transport_diag() {
|
|
126
|
+
local curl_exit="$1" refusal="$2" surface="installed-cli" curlrc="false" noproxy="false"
|
|
127
|
+
# The hosted sandbox always sets this (agent-env.ts > AGENT_ENV_OVERRIDES); an
|
|
128
|
+
# installed CLI does not. A label on the event, not a gate on behaviour.
|
|
129
|
+
if [[ -n "${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-}" ]]; then
|
|
130
|
+
surface="in-sandbox"
|
|
131
|
+
fi
|
|
132
|
+
if [[ -f "${HOME:-}/.curlrc" ]]; then
|
|
133
|
+
curlrc="true"
|
|
134
|
+
fi
|
|
135
|
+
if proxy_bypassed; then
|
|
136
|
+
noproxy="true"
|
|
137
|
+
fi
|
|
138
|
+
printf 'SOLEUR_TRANSPORT_DIAG surface=%s script=%s curl_exit=%s refusal=%s proxy_env=%s curlrc_present=%s noproxy_applied=%s tls_env_cleared=%s\n' \
|
|
139
|
+
"$surface" "$SOLEUR_TRANSPORT_SCRIPT" "$curl_exit" "$refusal" \
|
|
140
|
+
"$(proxy_env_names)" "$curlrc" "$noproxy" "true" >&3
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Replaces the bare "Check your network connection and try again." on a
|
|
144
|
+
# credentialed-curl failure. With a proxy set, that line blames the founder for a
|
|
145
|
+
# bypass this script chose.
|
|
146
|
+
report_transport_failure() {
|
|
147
|
+
local curl_exit="${1:-unknown}"
|
|
148
|
+
local detail="${2:-Failed to connect to the ${SOLEUR_TRANSPORT_PLATFORM} API.}"
|
|
149
|
+
emit_transport_diag "$curl_exit" "none"
|
|
150
|
+
echo "Error: ${detail}" >&2
|
|
151
|
+
# Ordered by how specific the evidence is. Each arm names something THIS script
|
|
152
|
+
# did, so the founder is never told to go debug their own network for a choice
|
|
153
|
+
# made here. The bare "check your connection" line is the LAST resort, not the
|
|
154
|
+
# default -- it was the default in the first cut, which meant a founder whose
|
|
155
|
+
# corporate CA or curlrc we had just discarded was blamed for it (#7898 review).
|
|
156
|
+
if [[ "$curl_exit" == "60" || "$curl_exit" == "35" || "$curl_exit" == "77" ]]; then
|
|
157
|
+
# 60 peer-certificate, 35 TLS handshake, 77 CA-bundle unreadable.
|
|
158
|
+
echo "This looks like a TLS trust failure. This script deliberately clears CURL_CA_BUNDLE, SSL_CERT_FILE, SSL_CERT_DIR and SSLKEYLOGFILE before the request, because those variables can redirect or expose a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} credential." >&2
|
|
159
|
+
echo "If your machine needs a corporate CA to reach ${SOLEUR_TRANSPORT_PLATFORM}, that is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
|
|
160
|
+
elif proxy_bypassed; then
|
|
161
|
+
echo "This request deliberately bypasses your proxy ($(proxy_env_names)), because a proxy can redirect a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} token." >&2
|
|
162
|
+
echo "If you need Soleur to reach ${SOLEUR_TRANSPORT_PLATFORM} through your proxy, that is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
|
|
163
|
+
elif [[ -f "${HOME:-}/.curlrc" ]]; then
|
|
164
|
+
echo "You have a ~/.curlrc, and this request deliberately ignores it (--disable), because a curlrc can redirect a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} token. If it configures a proxy or a CA bundle you need, that is why this failed." >&2
|
|
165
|
+
echo "That is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
|
|
166
|
+
else
|
|
167
|
+
echo "Check your network connection and try again." >&2
|
|
168
|
+
fi
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# readonly (#7898 review): `cmd_verify` and friends run `set -a; source "$env_file"; set +a`
|
|
172
|
+
# BELOW this line, so a plain assignment here is rebindable by the repo's .env --
|
|
173
|
+
# which retargets the credentialed request while every transport flag stays intact.
|
|
174
|
+
# That is the "--disable and --noproxy are intact and irrelevant" shape this change
|
|
175
|
+
# exists to close, one layer up. readonly makes the destination non-rebindable.
|
|
176
|
+
readonly LINKEDIN_API="https://api.linkedin.com"
|
|
177
|
+
LINKEDIN_API_VERSION="202602"
|
|
178
|
+
LINKEDIN_POST_MAX_LENGTH=3000
|
|
179
|
+
|
|
180
|
+
# --- Bearer transport (#7843) ---
|
|
181
|
+
# The access token is written into curl's STDIN config channel (`--config -`), never its
|
|
182
|
+
# argument list, where every local user would read it from /proc/<pid>/cmdline. That
|
|
183
|
+
# channel is line-oriented, so a token holding a quote and a newline could append a
|
|
184
|
+
# `url = "..."` directive and make curl issue a second request. The guard refuses
|
|
185
|
+
# anything outside the token alphabet BEFORE the value is formatted into the stream, and
|
|
186
|
+
# never echoes it.
|
|
187
|
+
_bearer_ok() { local LC_ALL=C; case "${1:-}" in ''|*[!A-Za-z0-9._~+/=-]*) return 1 ;; esac; }
|
|
188
|
+
|
|
189
|
+
# Every authenticated request goes through here: ONE place owns the transport flags
|
|
190
|
+
# (`--disable` first, `--noproxy '*'`), the token-shape guard and the header. Returns 120
|
|
191
|
+
# with zero curl calls when the token is unusable; callers map that to a refusal. The
|
|
192
|
+
# token is read as LINKEDIN_ACCESS_TOKEN at call time, so the function-local org-token
|
|
193
|
+
# rebinding in cmd_fetch_metrics / cmd_fetch_activity keeps working.
|
|
194
|
+
linkedin_authed_curl() {
|
|
195
|
+
_bearer_ok "${LINKEDIN_ACCESS_TOKEN:-}" || return 120
|
|
196
|
+
curl --disable --noproxy '*' "$@" --config - \
|
|
197
|
+
< <(printf 'header = "Authorization: Bearer %s"\n' "$LINKEDIN_ACCESS_TOKEN")
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# --- Dependency checks ---
|
|
201
|
+
|
|
202
|
+
require_jq() {
|
|
203
|
+
if ! command -v jq &>/dev/null; then
|
|
204
|
+
echo "Error: jq is required but not installed." >&2
|
|
205
|
+
echo "Install it: https://jqlang.github.io/jq/download/" >&2
|
|
206
|
+
exit 1
|
|
207
|
+
fi
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# --- Credential validation ---
|
|
211
|
+
|
|
212
|
+
require_credentials() {
|
|
213
|
+
if [[ -z "${LINKEDIN_ACCESS_TOKEN:-}" ]]; then
|
|
214
|
+
echo "Error: Missing LinkedIn credentials: LINKEDIN_ACCESS_TOKEN" >&2
|
|
215
|
+
echo "" >&2
|
|
216
|
+
echo "To configure:" >&2
|
|
217
|
+
echo " 1. Run: linkedin-setup.sh generate-token" >&2
|
|
218
|
+
echo " 2. Or set LINKEDIN_ACCESS_TOKEN manually" >&2
|
|
219
|
+
echo " 3. Run: linkedin-setup.sh verify" >&2
|
|
220
|
+
exit 1
|
|
221
|
+
fi
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# Require the organization read credentials for fetch-metrics/fetch-activity.
|
|
225
|
+
# Fails LOUD with `exit 1` (NOT `return 1`): the script runs `set -euo pipefail`,
|
|
226
|
+
# and a `return 1` consumed in a conditional/pipeline does NOT terminate, which
|
|
227
|
+
# would re-open the silent fall-through. Mirrors require_credentials (exit 1),
|
|
228
|
+
# NOT cmd_post_content's return-1 guard.
|
|
229
|
+
#
|
|
230
|
+
# NEVER silent-fallback to the personal LINKEDIN_ACCESS_TOKEN — that token is
|
|
231
|
+
# live in the cron spawn env and lacks the org read scopes; falling back yields
|
|
232
|
+
# an opaque 401/400 instead of a clear "missing org creds" error
|
|
233
|
+
# (learning 2026-04-26-linkedin-org-token-fallback-silent-400.md).
|
|
234
|
+
require_org_credentials() {
|
|
235
|
+
local missing=()
|
|
236
|
+
[[ -n "${LINKEDIN_ORG_ACCESS_TOKEN:-}" ]] || missing+=("LINKEDIN_ORG_ACCESS_TOKEN")
|
|
237
|
+
# Fail-closed numeric check: a non-numeric (or unset) org id must never be
|
|
238
|
+
# interpolated into the request URL. When unset/empty the message names the
|
|
239
|
+
# bare var (matching the existing unset path); when set-but-non-numeric it
|
|
240
|
+
# names the constraint so the operator can fix it.
|
|
241
|
+
if [[ -z "${LINKEDIN_ORG_ID:-}" ]]; then
|
|
242
|
+
missing+=("LINKEDIN_ORG_ID")
|
|
243
|
+
elif [[ ! "${LINKEDIN_ORG_ID}" =~ ^[0-9]+$ ]]; then
|
|
244
|
+
missing+=("LINKEDIN_ORG_ID (must be numeric)")
|
|
245
|
+
fi
|
|
246
|
+
if (( ${#missing[@]} > 0 )); then
|
|
247
|
+
echo "Error: Missing LinkedIn organization credentials: ${missing[*]}" >&2
|
|
248
|
+
echo "fetch-metrics/fetch-activity read the operator's own Company Page" >&2
|
|
249
|
+
echo "aggregate insights and require the org token + org id." >&2
|
|
250
|
+
echo "Set LINKEDIN_ORG_ACCESS_TOKEN (org read scopes) and LINKEDIN_ORG_ID." >&2
|
|
251
|
+
exit 1
|
|
252
|
+
fi
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# --- Response handler ---
|
|
256
|
+
|
|
257
|
+
# Handle HTTP response status codes from LinkedIn API
|
|
258
|
+
# Arguments: http_code body endpoint depth retry_cmd...
|
|
259
|
+
# On 2xx: validates JSON, echoes body to stdout
|
|
260
|
+
# On 429: sleeps and invokes retry_cmd (caller with incremented depth)
|
|
261
|
+
# On error: prints diagnostic to stderr, exits 1 (or 2 for rate limit exhaustion)
|
|
262
|
+
handle_response() {
|
|
263
|
+
local http_code="$1"
|
|
264
|
+
local body="$2"
|
|
265
|
+
local endpoint="$3"
|
|
266
|
+
local depth="$4"
|
|
267
|
+
shift 4
|
|
268
|
+
local -a retry_cmd=("$@")
|
|
269
|
+
|
|
270
|
+
case "$http_code" in
|
|
271
|
+
2[0-9][0-9])
|
|
272
|
+
# LinkedIn POST /rest/posts returns 201 with empty body -- that is valid
|
|
273
|
+
if [[ -n "$body" ]] && ! echo "$body" | jq . >/dev/null 2>&1; then
|
|
274
|
+
echo "Error: LinkedIn API returned malformed JSON for ${endpoint}" >&2
|
|
275
|
+
exit 1
|
|
276
|
+
fi
|
|
277
|
+
echo "$body"
|
|
278
|
+
;;
|
|
279
|
+
401)
|
|
280
|
+
echo "Error: LinkedIn API returned 401 Unauthorized for ${endpoint}." >&2
|
|
281
|
+
echo "Your access token may be expired (60-day TTL)." >&2
|
|
282
|
+
echo "" >&2
|
|
283
|
+
echo "To fix:" >&2
|
|
284
|
+
echo " 1. Run: linkedin-setup.sh validate-credentials" >&2
|
|
285
|
+
echo " 2. If expired, run: linkedin-setup.sh generate-token" >&2
|
|
286
|
+
exit 1
|
|
287
|
+
;;
|
|
288
|
+
403)
|
|
289
|
+
local message
|
|
290
|
+
message=$(echo "$body" | jq -r '.message // .code // "Access denied"' 2>/dev/null || echo "Access denied")
|
|
291
|
+
echo "Error: LinkedIn API returned 403 Forbidden for ${endpoint}: ${message}" >&2
|
|
292
|
+
exit 1
|
|
293
|
+
;;
|
|
294
|
+
429)
|
|
295
|
+
if (( depth >= 3 )); then
|
|
296
|
+
echo "Error: LinkedIn API rate limit exceeded after 3 retries for ${endpoint}." >&2
|
|
297
|
+
exit 2
|
|
298
|
+
fi
|
|
299
|
+
echo "Rate limited. Retrying after 5s (attempt $((depth + 1))/3)..." >&2
|
|
300
|
+
sleep 5
|
|
301
|
+
"${retry_cmd[@]}"
|
|
302
|
+
;;
|
|
303
|
+
*)
|
|
304
|
+
local message
|
|
305
|
+
message=$(echo "$body" | jq -r '.message // .code // "Unknown error"' 2>/dev/null || echo "Unknown error")
|
|
306
|
+
echo "Error: LinkedIn API returned HTTP ${http_code} for ${endpoint}: ${message}" >&2
|
|
307
|
+
exit 1
|
|
308
|
+
;;
|
|
309
|
+
esac
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
# --- GET request helper ---
|
|
313
|
+
|
|
314
|
+
# Make an authenticated GET request to the LinkedIn API
|
|
315
|
+
# Arguments: endpoint [depth] [extra_header]
|
|
316
|
+
# extra_header - optional single "Name: value" header (e.g. the
|
|
317
|
+
# "X-RestLi-Method: FINDER" header the Posts author-finder
|
|
318
|
+
# requires). Forwarded on the 429-retry recursion so a retry
|
|
319
|
+
# never silently drops it.
|
|
320
|
+
# Retries on 429 up to 3 times
|
|
321
|
+
get_request() {
|
|
322
|
+
local endpoint="$1"
|
|
323
|
+
local depth="${2:-0}"
|
|
324
|
+
local extra_header="${3:-}"
|
|
325
|
+
|
|
326
|
+
if (( depth >= 3 )); then
|
|
327
|
+
echo "Error: LinkedIn API rate limit exceeded after 3 retries for ${endpoint}." >&2
|
|
328
|
+
exit 2
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
local url="${LINKEDIN_API}${endpoint}"
|
|
332
|
+
|
|
333
|
+
local -a header_args=()
|
|
334
|
+
if [[ -n "$extra_header" ]]; then
|
|
335
|
+
header_args=(-H "$extra_header")
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
local response http_code body
|
|
339
|
+
local __curl_rc=0
|
|
340
|
+
response=$(linkedin_authed_curl -s -w "\n%{http_code}" \
|
|
341
|
+
-H "X-Restli-Protocol-Version: 2.0.0" \
|
|
342
|
+
-H "LinkedIn-Version: ${LINKEDIN_API_VERSION}" \
|
|
343
|
+
"${header_args[@]}" \
|
|
344
|
+
"$url" 2>/dev/null) || __curl_rc=$?
|
|
345
|
+
if (( __curl_rc == 120 )); then
|
|
346
|
+
echo "Error: LinkedIn access token has an unexpected shape; refusing to send it." >&2
|
|
347
|
+
exit 1
|
|
348
|
+
fi
|
|
349
|
+
if (( __curl_rc != 0 )); then
|
|
350
|
+
report_transport_failure "$__curl_rc" "Failed to connect to LinkedIn API."
|
|
351
|
+
exit 1
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
http_code=$(echo "$response" | tail -1)
|
|
355
|
+
body=$(echo "$response" | sed '$d')
|
|
356
|
+
|
|
357
|
+
handle_response "$http_code" "$body" "$endpoint" "$depth" \
|
|
358
|
+
get_request "$endpoint" "$((depth + 1))" "$extra_header"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# --- POST request helper ---
|
|
362
|
+
|
|
363
|
+
# Make an authenticated POST request to the LinkedIn API
|
|
364
|
+
# Arguments: endpoint json_body [depth]
|
|
365
|
+
# Captures response headers via temp file to extract x-restli-id
|
|
366
|
+
# Only retries on 429 (POST is not idempotent)
|
|
367
|
+
post_request() {
|
|
368
|
+
local endpoint="$1"
|
|
369
|
+
local json_body="$2"
|
|
370
|
+
local depth="${3:-0}"
|
|
371
|
+
|
|
372
|
+
local url="${LINKEDIN_API}${endpoint}"
|
|
373
|
+
|
|
374
|
+
local header_file
|
|
375
|
+
header_file=$(mktemp) || { echo "Error: Failed to create temp file." >&2; exit 1; }
|
|
376
|
+
# shellcheck disable=SC2064
|
|
377
|
+
trap "rm -f '$header_file'" EXIT
|
|
378
|
+
|
|
379
|
+
local response http_code body
|
|
380
|
+
local __curl_rc=0
|
|
381
|
+
response=$(linkedin_authed_curl -s -w "\n%{http_code}" \
|
|
382
|
+
-D "$header_file" \
|
|
383
|
+
-H "X-Restli-Protocol-Version: 2.0.0" \
|
|
384
|
+
-H "LinkedIn-Version: ${LINKEDIN_API_VERSION}" \
|
|
385
|
+
-H "Content-Type: application/json" \
|
|
386
|
+
-X POST -d "$json_body" \
|
|
387
|
+
"$url" 2>/dev/null) || __curl_rc=$?
|
|
388
|
+
if (( __curl_rc == 120 )); then
|
|
389
|
+
rm -f "$header_file"
|
|
390
|
+
echo "Error: LinkedIn access token has an unexpected shape; refusing to send it." >&2
|
|
391
|
+
exit 1
|
|
392
|
+
fi
|
|
393
|
+
if (( __curl_rc != 0 )); then
|
|
394
|
+
rm -f "$header_file"
|
|
395
|
+
report_transport_failure "$__curl_rc" "Failed to connect to LinkedIn API."
|
|
396
|
+
exit 1
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
http_code=$(echo "$response" | tail -1)
|
|
400
|
+
body=$(echo "$response" | sed '$d')
|
|
401
|
+
|
|
402
|
+
# On 429, retry (rate limit returned before post is created)
|
|
403
|
+
if [[ "$http_code" == "429" ]]; then
|
|
404
|
+
rm -f "$header_file"
|
|
405
|
+
handle_response "$http_code" "$body" "$endpoint" "$depth" \
|
|
406
|
+
post_request "$endpoint" "$json_body" "$((depth + 1))"
|
|
407
|
+
return
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
# For non-429 errors on POST, fail immediately (non-idempotent)
|
|
411
|
+
if [[ ! "$http_code" =~ ^2[0-9][0-9]$ ]]; then
|
|
412
|
+
rm -f "$header_file"
|
|
413
|
+
handle_response "$http_code" "$body" "$endpoint" "$depth"
|
|
414
|
+
return
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
# Success: extract x-restli-id from response headers
|
|
418
|
+
local restli_id=""
|
|
419
|
+
if [[ -f "$header_file" ]]; then
|
|
420
|
+
restli_id=$(grep -i '^x-restli-id:' "$header_file" | sed 's/^[^:]*: *//' | tr -d '\r' || true)
|
|
421
|
+
fi
|
|
422
|
+
rm -f "$header_file"
|
|
423
|
+
|
|
424
|
+
# Return JSON with post URN
|
|
425
|
+
if [[ -n "$restli_id" ]]; then
|
|
426
|
+
jq -n --arg id "$restli_id" '{"post_urn": $id}'
|
|
427
|
+
else
|
|
428
|
+
echo '{"post_urn": null}' >&2
|
|
429
|
+
echo "Warning: Post created but x-restli-id header not found in response." >&2
|
|
430
|
+
# Still return valid JSON
|
|
431
|
+
echo '{}'
|
|
432
|
+
fi
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# --- LinkedIn "Little Text Format" handling ---
|
|
436
|
+
#
|
|
437
|
+
# The Posts API commentary field is "Little Text Format": the characters
|
|
438
|
+
# \ | { } [ ] ( ) < > * ~ are reserved, and LinkedIn silently TRUNCATES the
|
|
439
|
+
# post at the first unescaped occurrence (observed 2026-06-10: nine org posts
|
|
440
|
+
# cut mid-sentence at the first "(", which also dropped the trailing article
|
|
441
|
+
# links). '#' and '@' stay unescaped so hashtags and mentions keep their
|
|
442
|
+
# linking behavior; '_' stays unescaped because escaping it corrupts URLs
|
|
443
|
+
# (utm_source=...) and it does not trigger the truncation failure class.
|
|
444
|
+
|
|
445
|
+
sanitize_commentary() {
|
|
446
|
+
# Markdown-source artifacts that must not reach LinkedIn verbatim: strip
|
|
447
|
+
# whole-line HTML comments (e.g. markdownlint directives), unwrap
|
|
448
|
+
# <https://...> autolinks (angle brackets are reserved characters), and
|
|
449
|
+
# drop trailing horizontal rules / blank lines.
|
|
450
|
+
sed -e '/^[[:space:]]*<!--.*-->[[:space:]]*$/d' \
|
|
451
|
+
-e 's/<\(https\{0,1\}:\/\/[^>]*\)>/\1/g' |
|
|
452
|
+
awk '{ lines[++n] = $0 }
|
|
453
|
+
END {
|
|
454
|
+
while (n > 0 && (lines[n] ~ /^[[:space:]]*$/ || lines[n] ~ /^[[:space:]]*-{3,}[[:space:]]*$/)) n--
|
|
455
|
+
for (i = 1; i <= n; i++) print lines[i]
|
|
456
|
+
}'
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
escape_little_text() {
|
|
460
|
+
local t="$1"
|
|
461
|
+
t="${t//\\/\\\\}"
|
|
462
|
+
t="${t//|/\\|}"
|
|
463
|
+
t="${t//\{/\\\{}"
|
|
464
|
+
t="${t//\}/\\\}}"
|
|
465
|
+
t="${t//\[/\\[}"
|
|
466
|
+
t="${t//\]/\\]}"
|
|
467
|
+
t="${t//(/\\(}"
|
|
468
|
+
t="${t//)/\\)}"
|
|
469
|
+
t="${t//</\\<}"
|
|
470
|
+
t="${t//>/\\>}"
|
|
471
|
+
t="${t//\*/\\*}"
|
|
472
|
+
t="${t//\~/\\~}"
|
|
473
|
+
printf '%s' "$t"
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
# --- Commands ---
|
|
477
|
+
|
|
478
|
+
cmd_post_content() {
|
|
479
|
+
# Guard: require explicit opt-in to post.
|
|
480
|
+
if [[ "${LINKEDIN_ALLOW_POST:-}" != "true" ]]; then
|
|
481
|
+
echo "Error: LINKEDIN_ALLOW_POST is not set to 'true'." >&2
|
|
482
|
+
echo "Set LINKEDIN_ALLOW_POST=true to enable posting." >&2
|
|
483
|
+
return 1
|
|
484
|
+
fi
|
|
485
|
+
|
|
486
|
+
local text=""
|
|
487
|
+
local author_override=""
|
|
488
|
+
|
|
489
|
+
while [[ $# -gt 0 ]]; do
|
|
490
|
+
case "$1" in
|
|
491
|
+
--text)
|
|
492
|
+
text="${2:-}"
|
|
493
|
+
if [[ -z "$text" ]]; then
|
|
494
|
+
echo "Error: --text requires a non-empty value." >&2
|
|
495
|
+
exit 1
|
|
496
|
+
fi
|
|
497
|
+
shift 2
|
|
498
|
+
;;
|
|
499
|
+
--author)
|
|
500
|
+
author_override="${2:-}"
|
|
501
|
+
if [[ -z "$author_override" ]]; then
|
|
502
|
+
echo "Error: --author requires a non-empty value." >&2
|
|
503
|
+
exit 1
|
|
504
|
+
fi
|
|
505
|
+
shift 2
|
|
506
|
+
;;
|
|
507
|
+
*)
|
|
508
|
+
echo "Error: Unknown option '$1'" >&2
|
|
509
|
+
echo "Usage: linkedin-community.sh post-content --text \"<text>\" [--author \"<urn>\"]" >&2
|
|
510
|
+
exit 1
|
|
511
|
+
;;
|
|
512
|
+
esac
|
|
513
|
+
done
|
|
514
|
+
|
|
515
|
+
if [[ -z "$text" ]]; then
|
|
516
|
+
echo "Error: --text is required." >&2
|
|
517
|
+
echo "Usage: linkedin-community.sh post-content --text \"<text>\"" >&2
|
|
518
|
+
exit 1
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
# Strip markdown-source artifacts before the length check (the limit
|
|
522
|
+
# applies to visible characters; Little Text escape backslashes added
|
|
523
|
+
# below do not count toward it).
|
|
524
|
+
text=$(printf '%s\n' "$text" | sanitize_commentary)
|
|
525
|
+
|
|
526
|
+
if (( ${#text} > LINKEDIN_POST_MAX_LENGTH )); then
|
|
527
|
+
echo "Error: Post text is ${#text} characters, exceeds LinkedIn's ${LINKEDIN_POST_MAX_LENGTH}-character limit." >&2
|
|
528
|
+
exit 1
|
|
529
|
+
fi
|
|
530
|
+
|
|
531
|
+
local escaped_text
|
|
532
|
+
escaped_text=$(escape_little_text "$text")
|
|
533
|
+
|
|
534
|
+
# Build request body (--author overrides default LINKEDIN_PERSON_URN)
|
|
535
|
+
local author="${author_override:-${LINKEDIN_PERSON_URN:-}}"
|
|
536
|
+
if [[ -z "$author" ]]; then
|
|
537
|
+
echo "Error: No author specified. Provide --author or set LINKEDIN_PERSON_URN." >&2
|
|
538
|
+
exit 1
|
|
539
|
+
fi
|
|
540
|
+
|
|
541
|
+
# Normalize bare person IDs: LinkedIn API requires full URN (urn:li:person:<id>).
|
|
542
|
+
# LINKEDIN_PERSON_URN is often stored as just the ID portion without the prefix.
|
|
543
|
+
if [[ "$author" != urn:* ]]; then
|
|
544
|
+
author="urn:li:person:${author}"
|
|
545
|
+
fi
|
|
546
|
+
|
|
547
|
+
# Organization posts require w_organization_social scope -- use LINKEDIN_ORG_ACCESS_TOKEN.
|
|
548
|
+
if [[ "$author" == urn:li:organization:* ]]; then
|
|
549
|
+
if [[ -z "${LINKEDIN_ORG_ACCESS_TOKEN:-}" ]]; then
|
|
550
|
+
echo "Error: LINKEDIN_ORG_ACCESS_TOKEN is required for organization posts (w_organization_social scope)." >&2
|
|
551
|
+
echo "Set LINKEDIN_ORG_ACCESS_TOKEN to a token with w_organization_social scope." >&2
|
|
552
|
+
exit 1
|
|
553
|
+
fi
|
|
554
|
+
LINKEDIN_ACCESS_TOKEN="$LINKEDIN_ORG_ACCESS_TOKEN"
|
|
555
|
+
fi
|
|
556
|
+
|
|
557
|
+
local json_body
|
|
558
|
+
json_body=$(jq -n \
|
|
559
|
+
--arg author "$author" \
|
|
560
|
+
--arg text "$escaped_text" \
|
|
561
|
+
'{
|
|
562
|
+
author: $author,
|
|
563
|
+
commentary: $text,
|
|
564
|
+
visibility: "PUBLIC",
|
|
565
|
+
distribution: {
|
|
566
|
+
feedDistribution: "MAIN_FEED",
|
|
567
|
+
targetEntities: [],
|
|
568
|
+
thirdPartyDistributionChannels: []
|
|
569
|
+
},
|
|
570
|
+
lifecycleState: "PUBLISHED",
|
|
571
|
+
isReshareDisabledByAuthor: false
|
|
572
|
+
}')
|
|
573
|
+
|
|
574
|
+
local result
|
|
575
|
+
result=$(post_request "/rest/posts" "$json_body")
|
|
576
|
+
|
|
577
|
+
echo "$result"
|
|
578
|
+
echo "Post created successfully." >&2
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
# Fetch aggregate Company Page metrics: lifetime share statistics + a single
|
|
582
|
+
# follower total. Aggregate-only — no demographic facets, no per-member data.
|
|
583
|
+
cmd_fetch_metrics() {
|
|
584
|
+
# Defense-in-depth: re-check org creds as the FIRST line (dispatch also checks).
|
|
585
|
+
# The personal LINKEDIN_ACCESS_TOKEN is live in the spawn env, so a direct
|
|
586
|
+
# call to this function must not be able to fall through to it.
|
|
587
|
+
require_org_credentials
|
|
588
|
+
|
|
589
|
+
# Scope the org token function-locally so get_request's Bearer header uses it
|
|
590
|
+
# WITHOUT leaking into the personal path (safer than cmd_post_content's global
|
|
591
|
+
# mutation at the org-post branch).
|
|
592
|
+
local LINKEDIN_ACCESS_TOKEN="$LINKEDIN_ORG_ACCESS_TOKEN"
|
|
593
|
+
|
|
594
|
+
local org_urn="urn%3Ali%3Aorganization%3A${LINKEDIN_ORG_ID}"
|
|
595
|
+
|
|
596
|
+
# --- Aggregate share statistics (lifetime). ---
|
|
597
|
+
local share_body
|
|
598
|
+
share_body=$(get_request \
|
|
599
|
+
"/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=${org_urn}")
|
|
600
|
+
|
|
601
|
+
# Shape validation BEFORE any `// 0` fallbacks (silent-failure HIGH-1):
|
|
602
|
+
# handle_response validated only JSON parseability, not shape. An empty
|
|
603
|
+
# `.elements` (e.g. token lacks ADMINISTRATOR role) or a wrong shape must NOT
|
|
604
|
+
# render as fake zeros — emit an explicit error and exit 1.
|
|
605
|
+
if ! echo "$share_body" | jq -e '(.elements[0].totalShareStatistics | type) == "object"' >/dev/null 2>&1; then
|
|
606
|
+
echo "Error: organizationalEntityShareStatistics returned no usable totalShareStatistics for org ${LINKEDIN_ORG_ID}." >&2
|
|
607
|
+
echo "The org token may lack the ADMINISTRATOR role, or the response shape changed." >&2
|
|
608
|
+
exit 1
|
|
609
|
+
fi
|
|
610
|
+
|
|
611
|
+
local share_statistics
|
|
612
|
+
share_statistics=$(echo "$share_body" | jq '
|
|
613
|
+
.elements[0].totalShareStatistics as $s |
|
|
614
|
+
{
|
|
615
|
+
impressions: ($s.impressionCount // 0),
|
|
616
|
+
unique_impressions: ($s.uniqueImpressionsCount // 0),
|
|
617
|
+
clicks: ($s.clickCount // 0),
|
|
618
|
+
likes: ($s.likeCount // 0),
|
|
619
|
+
comments: ($s.commentCount // 0),
|
|
620
|
+
shares: ($s.shareCount // 0),
|
|
621
|
+
engagement: ($s.engagement // 0)
|
|
622
|
+
}')
|
|
623
|
+
|
|
624
|
+
# --- Single follower total via networkSizes. ---
|
|
625
|
+
# Partial-failure policy (silent-failure HIGH-2): the networkSizes call must
|
|
626
|
+
# not abort the (more important) share-stats result. Run it in a subshell whose
|
|
627
|
+
# non-zero exit is tolerated; on failure total_followers degrades to null with
|
|
628
|
+
# a stderr warning rather than get_request's default `exit`.
|
|
629
|
+
local total_followers="null"
|
|
630
|
+
local network_body
|
|
631
|
+
if network_body=$(get_request \
|
|
632
|
+
"/rest/networkSizes/${org_urn}?edgeType=COMPANY_FOLLOWED_BY_MEMBER" 2>/dev/null); then
|
|
633
|
+
local size
|
|
634
|
+
size=$(echo "$network_body" | jq -r '.firstDegreeSize // empty' 2>/dev/null || true)
|
|
635
|
+
if [[ "$size" =~ ^[0-9]+$ ]]; then
|
|
636
|
+
# Only a clean non-negative integer is trusted: it is injected via
|
|
637
|
+
# `--argjson total_followers` into the final emit, where a non-numeric
|
|
638
|
+
# value would crash jq under set -e and defeat the degrade-to-null
|
|
639
|
+
# contract. Anything else degrades to null (same path as a failed fetch).
|
|
640
|
+
total_followers="$size"
|
|
641
|
+
elif [[ -n "$size" ]]; then
|
|
642
|
+
echo "Warning: networkSizes returned non-numeric firstDegreeSize ('${size}') for org ${LINKEDIN_ORG_ID}; total_followers=null." >&2
|
|
643
|
+
else
|
|
644
|
+
echo "Warning: networkSizes returned no firstDegreeSize for org ${LINKEDIN_ORG_ID}; total_followers=null." >&2
|
|
645
|
+
fi
|
|
646
|
+
else
|
|
647
|
+
echo "Warning: networkSizes call failed for org ${LINKEDIN_ORG_ID}; total_followers=null." >&2
|
|
648
|
+
fi
|
|
649
|
+
|
|
650
|
+
jq -n \
|
|
651
|
+
--arg org_id "$LINKEDIN_ORG_ID" \
|
|
652
|
+
--argjson total_followers "$total_followers" \
|
|
653
|
+
--argjson share_statistics "$share_statistics" \
|
|
654
|
+
'{
|
|
655
|
+
org_id: $org_id,
|
|
656
|
+
total_followers: $total_followers,
|
|
657
|
+
share_statistics: $share_statistics
|
|
658
|
+
}'
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
# Fetch recent Company Page post metadata via the Posts author-finder.
|
|
662
|
+
# Aggregate-only — post metadata, no commenter/liker identities.
|
|
663
|
+
cmd_fetch_activity() {
|
|
664
|
+
require_org_credentials
|
|
665
|
+
|
|
666
|
+
local LINKEDIN_ACCESS_TOKEN="$LINKEDIN_ORG_ACCESS_TOKEN"
|
|
667
|
+
|
|
668
|
+
local org_urn="urn%3Ali%3Aorganization%3A${LINKEDIN_ORG_ID}"
|
|
669
|
+
|
|
670
|
+
# The Posts author-finder requires the X-RestLi-Method: FINDER header, threaded
|
|
671
|
+
# through get_request's optional 3rd arg (forwarded on the 429 recursion).
|
|
672
|
+
local posts_body
|
|
673
|
+
posts_body=$(get_request \
|
|
674
|
+
"/rest/posts?author=${org_urn}&q=author&count=10&sortBy=LAST_MODIFIED" \
|
|
675
|
+
0 \
|
|
676
|
+
"X-RestLi-Method: FINDER")
|
|
677
|
+
|
|
678
|
+
# Shape validation BEFORE the `.elements[]` iteration: mirrors
|
|
679
|
+
# cmd_fetch_metrics's guard. A missing/null `.elements` (e.g. token lacks the
|
|
680
|
+
# required role, or the response shape changed) makes `.elements[]` crash jq
|
|
681
|
+
# ("Cannot iterate over null", exit 5) under set -e — emit an explicit error
|
|
682
|
+
# and exit 1 instead. A present-but-empty `elements: []` still succeeds and
|
|
683
|
+
# yields `{"posts": []}`.
|
|
684
|
+
if ! echo "$posts_body" | jq -e '(.elements | type) == "array"' >/dev/null 2>&1; then
|
|
685
|
+
echo "Error: posts author-finder returned no usable elements array for org ${LINKEDIN_ORG_ID}." >&2
|
|
686
|
+
echo "The org token may lack the required role, or the response shape changed." >&2
|
|
687
|
+
exit 1
|
|
688
|
+
fi
|
|
689
|
+
|
|
690
|
+
# commentary is operator-authored Page-public post text. If a post @mentions a
|
|
691
|
+
# member it flows to the digest, but the LIA's no-@mention posting TOM bounds
|
|
692
|
+
# this — no inbound filter needed. Emit metadata only; no author/commenter/
|
|
693
|
+
# liker identities. `//` fallbacks preserve each post even if a field is absent
|
|
694
|
+
# (learning 2026-03-10-jq-generator-silent-data-loss.md).
|
|
695
|
+
echo "$posts_body" | jq '
|
|
696
|
+
{
|
|
697
|
+
posts: [
|
|
698
|
+
.elements[] | {
|
|
699
|
+
id: .id,
|
|
700
|
+
commentary: (.commentary // null),
|
|
701
|
+
published_at: (.publishedAt // .createdAt // null),
|
|
702
|
+
lifecycle_state: (.lifecycleState // null)
|
|
703
|
+
}
|
|
704
|
+
]
|
|
705
|
+
}'
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
# --- Main ---
|
|
709
|
+
|
|
710
|
+
main() {
|
|
711
|
+
local command="${1:-}"
|
|
712
|
+
shift || true
|
|
713
|
+
|
|
714
|
+
if [[ -z "$command" ]]; then
|
|
715
|
+
echo "Usage: linkedin-community.sh <command> [args]" >&2
|
|
716
|
+
echo "" >&2
|
|
717
|
+
echo "Commands:" >&2
|
|
718
|
+
echo " post-content --text \"<text>\" [--author \"<urn>\"] - Post to LinkedIn" >&2
|
|
719
|
+
echo " fetch-metrics - Fetch aggregate Company Page metrics (org read scopes)" >&2
|
|
720
|
+
echo " fetch-activity - Fetch recent Company Page post metadata (org read scopes)" >&2
|
|
721
|
+
exit 1
|
|
722
|
+
fi
|
|
723
|
+
|
|
724
|
+
require_jq
|
|
725
|
+
|
|
726
|
+
case "$command" in
|
|
727
|
+
fetch-metrics)
|
|
728
|
+
require_org_credentials
|
|
729
|
+
cmd_fetch_metrics
|
|
730
|
+
;;
|
|
731
|
+
fetch-activity)
|
|
732
|
+
require_org_credentials
|
|
733
|
+
cmd_fetch_activity
|
|
734
|
+
;;
|
|
735
|
+
post-content)
|
|
736
|
+
require_credentials
|
|
737
|
+
cmd_post_content "$@"
|
|
738
|
+
;;
|
|
739
|
+
*)
|
|
740
|
+
echo "Error: Unknown command '${command}'" >&2
|
|
741
|
+
echo "Run 'linkedin-community.sh' without arguments for usage." >&2
|
|
742
|
+
exit 1
|
|
743
|
+
;;
|
|
744
|
+
esac
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
# Guard: allow sourcing without executing main (for test harness)
|
|
748
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
749
|
+
main "$@"
|
|
750
|
+
fi
|