@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,890 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# x-community.sh -- X/Twitter API v2 wrapper for community operations
|
|
3
|
+
#
|
|
4
|
+
# Usage: x-community.sh <command> [args]
|
|
5
|
+
# Commands:
|
|
6
|
+
# fetch-metrics - Fetch account metrics (followers, following, tweets)
|
|
7
|
+
# fetch-mentions [--max-results N] [--since-id ID] - Fetch recent mentions of the authenticated user
|
|
8
|
+
# fetch-timeline [--max N] - Fetch recent tweets (paid API)
|
|
9
|
+
# fetch-user-timeline <user_id> [--max N] - Fetch another user's recent tweets
|
|
10
|
+
# post-tweet <text> [--reply-to ID] - Post a tweet (optionally as a reply)
|
|
11
|
+
#
|
|
12
|
+
# Environment variables (required):
|
|
13
|
+
# X_API_KEY - API key (consumer key)
|
|
14
|
+
# X_API_SECRET - API secret (consumer secret)
|
|
15
|
+
# X_ACCESS_TOKEN - Access token
|
|
16
|
+
# X_ACCESS_TOKEN_SECRET - Access token secret
|
|
17
|
+
# X_ALLOW_POST - Set to "true" to enable posting (defense-in-depth guard)
|
|
18
|
+
#
|
|
19
|
+
# Exit codes:
|
|
20
|
+
# 0 - Success
|
|
21
|
+
# 1 - General error
|
|
22
|
+
# 2 - Retryable error (rate limit)
|
|
23
|
+
#
|
|
24
|
+
# Output: JSON to stdout
|
|
25
|
+
# Errors: Messages to stderr, exit 1
|
|
26
|
+
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
# (#7797) Xtrace refusal -- the FIRST thing after `set …`, so nothing above it is
|
|
30
|
+
# traced. This script ships to installed Soleur CLIs: the terminal it runs in
|
|
31
|
+
# belongs to the founder, and `bash -x` would print their live X
|
|
32
|
+
# credentials. The `:+x` form tests non-emptiness WITHOUT expanding the value, so
|
|
33
|
+
# the guard cannot leak the thing it is refusing over. CONDITIONAL, and that is
|
|
34
|
+
# measured rather than inherited: every credential this file handles is bound
|
|
35
|
+
# BEFORE the prologue runs (this script's own credential check reads them from
|
|
36
|
+
# the environment, it never acquires one at runtime), so the guard can never be
|
|
37
|
+
# empty while a credential is live -- and a founder tracing with none set keeps
|
|
38
|
+
# full tracing.
|
|
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
|
+
if [ -n "${X_ACCESS_TOKEN:+x}${X_ACCESS_TOKEN_SECRET:+x}${X_API_KEY:+x}${X_API_SECRET:+x}" ]; then
|
|
45
|
+
printf 'Refusing to run under `bash -x`: X_ACCESS_TOKEN, X_ACCESS_TOKEN_SECRET, X_API_KEY, X_API_SECRET are set, and tracing would print them to your terminal. To trace safely, unset all of them and re-run.\n'
|
|
46
|
+
exit 78
|
|
47
|
+
fi
|
|
48
|
+
;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# (#7873) `--disable` closes ~/.curlrc and `--noproxy '*'` closes the proxy
|
|
52
|
+
# variables, but NEITHER touches the trust store or the TLS session-key log. On an
|
|
53
|
+
# installed CLI the environment belongs to someone who is not us, so
|
|
54
|
+
# a substituted `CURL_CA_BUNDLE` is a clean MITM of the founder's platform
|
|
55
|
+
# token with every other guard fully intact, and `SSLKEYLOGFILE` is passive
|
|
56
|
+
# decryption with no MITM at all. Matches the line in scripts/betterstack-query.sh.
|
|
57
|
+
unset SSLKEYLOGFILE CURL_CA_BUNDLE SSL_CERT_FILE SSL_CERT_DIR CURL_HOME \
|
|
58
|
+
HOSTALIASES LOCALDOMAIN RES_OPTIONS
|
|
59
|
+
|
|
60
|
+
# --- Transport-confinement diagnostics (#7873) --------------------------------
|
|
61
|
+
# Every credentialed `curl` below carries `--disable --noproxy '*'` and discards
|
|
62
|
+
# curl's own stderr (which can carry the URL). A failed request then has four
|
|
63
|
+
# competing causes the founder cannot tell apart: the xtrace refusal, the proxy
|
|
64
|
+
# this script deliberately bypassed, a ~/.curlrc that `--disable` dropped, or an
|
|
65
|
+
# ordinary network failure. So each failure emits ONE structured marker carrying
|
|
66
|
+
# all four discriminators, beside a human line that names the bypass instead of
|
|
67
|
+
# blaming the founder's connectivity.
|
|
68
|
+
SOLEUR_TRANSPORT_SCRIPT="x-community.sh"
|
|
69
|
+
SOLEUR_TRANSPORT_PLATFORM="X"
|
|
70
|
+
|
|
71
|
+
# The request helpers below run inside `$(...)`, where fd 1 is the capture pipe.
|
|
72
|
+
# Save the script's real stdout so the marker reaches the founder's terminal (and
|
|
73
|
+
# the always-on PostToolUse Bash extractor) instead of being swallowed into a
|
|
74
|
+
# variable. The marker is on stdout BY DESIGN -- the call sites' `2>/dev/null` is
|
|
75
|
+
# what keeps curl's URL-carrying stderr out of the transcript, and it must not be
|
|
76
|
+
# able to suppress the diagnostic too.
|
|
77
|
+
exec 3>&1
|
|
78
|
+
|
|
79
|
+
# Which proxy variables are non-empty, or `none`.
|
|
80
|
+
proxy_env_names() {
|
|
81
|
+
local names="" n
|
|
82
|
+
for n in HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy; do
|
|
83
|
+
if [[ -n "${!n-}" ]]; then
|
|
84
|
+
names="${names},${n}"
|
|
85
|
+
fi
|
|
86
|
+
done
|
|
87
|
+
if [[ -z "$names" ]]; then
|
|
88
|
+
printf 'none'
|
|
89
|
+
else
|
|
90
|
+
printf '%s' "${names#,}"
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# True when a proxy this script deliberately bypasses is configured. NO_PROXY
|
|
95
|
+
# alone is not one -- it only ever narrows proxying, so it cannot be the cause.
|
|
96
|
+
proxy_bypassed() {
|
|
97
|
+
[[ -n "${HTTP_PROXY:-}${HTTPS_PROXY:-}${ALL_PROXY:-}${http_proxy:-}${https_proxy:-}${all_proxy:-}" ]]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# One line, eight fields, on the saved stdout.
|
|
101
|
+
#
|
|
102
|
+
# Two fields were literals in the first cut and are now MEASURED (#7898 review):
|
|
103
|
+
# `noproxy_applied` was the constant "true", which ASSERTS the property instead of
|
|
104
|
+
# observing it -- dropping --noproxy from a call site left the diagnostic still
|
|
105
|
+
# claiming it was applied. It now reports whether a proxy was actually configured
|
|
106
|
+
# for this request to bypass, which is the fact a reader needs. `refusal` was the
|
|
107
|
+
# constant "none" at every call site, so the cause it exists to discriminate could
|
|
108
|
+
# never appear; it now carries `env-rebind-refused` and `xtrace-credential-bound`.
|
|
109
|
+
#
|
|
110
|
+
# `tls_env_cleared` is new. The prologue unsets CURL_CA_BUNDLE/SSL_CERT_FILE et al,
|
|
111
|
+
# which is correct against an attacker and BREAKS a founder whose corporate CA
|
|
112
|
+
# arrives that way -- curl then exits 60 and, without this field, the event is
|
|
113
|
+
# indistinguishable from an ordinary network failure.
|
|
114
|
+
emit_transport_diag() {
|
|
115
|
+
local curl_exit="$1" refusal="$2" surface="installed-cli" curlrc="false" noproxy="false"
|
|
116
|
+
# The hosted sandbox always sets this (agent-env.ts > AGENT_ENV_OVERRIDES); an
|
|
117
|
+
# installed CLI does not. A label on the event, not a gate on behaviour.
|
|
118
|
+
if [[ -n "${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-}" ]]; then
|
|
119
|
+
surface="in-sandbox"
|
|
120
|
+
fi
|
|
121
|
+
if [[ -f "${HOME:-}/.curlrc" ]]; then
|
|
122
|
+
curlrc="true"
|
|
123
|
+
fi
|
|
124
|
+
if proxy_bypassed; then
|
|
125
|
+
noproxy="true"
|
|
126
|
+
fi
|
|
127
|
+
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' \
|
|
128
|
+
"$surface" "$SOLEUR_TRANSPORT_SCRIPT" "$curl_exit" "$refusal" \
|
|
129
|
+
"$(proxy_env_names)" "$curlrc" "$noproxy" "true" >&3
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Replaces the bare "Check your network connection and try again." on a
|
|
133
|
+
# credentialed-curl failure. With a proxy set, that line blames the founder for a
|
|
134
|
+
# bypass this script chose.
|
|
135
|
+
report_transport_failure() {
|
|
136
|
+
local curl_exit="${1:-unknown}"
|
|
137
|
+
local detail="${2:-Failed to connect to the ${SOLEUR_TRANSPORT_PLATFORM} API.}"
|
|
138
|
+
emit_transport_diag "$curl_exit" "none"
|
|
139
|
+
echo "Error: ${detail}" >&2
|
|
140
|
+
# Ordered by how specific the evidence is. Each arm names something THIS script
|
|
141
|
+
# did, so the founder is never told to go debug their own network for a choice
|
|
142
|
+
# made here. The bare "check your connection" line is the LAST resort, not the
|
|
143
|
+
# default -- it was the default in the first cut, which meant a founder whose
|
|
144
|
+
# corporate CA or curlrc we had just discarded was blamed for it (#7898 review).
|
|
145
|
+
if [[ "$curl_exit" == "60" || "$curl_exit" == "35" || "$curl_exit" == "77" ]]; then
|
|
146
|
+
# 60 peer-certificate, 35 TLS handshake, 77 CA-bundle unreadable.
|
|
147
|
+
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
|
|
148
|
+
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
|
|
149
|
+
elif proxy_bypassed; then
|
|
150
|
+
echo "This request deliberately bypasses your proxy ($(proxy_env_names)), because a proxy can redirect a request carrying your ${SOLEUR_TRANSPORT_PLATFORM} token." >&2
|
|
151
|
+
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
|
|
152
|
+
elif [[ -f "${HOME:-}/.curlrc" ]]; then
|
|
153
|
+
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
|
|
154
|
+
echo "That is not currently supported -- please open an issue at https://github.com/jikig-ai/soleur/issues." >&2
|
|
155
|
+
else
|
|
156
|
+
echo "Check your network connection and try again." >&2
|
|
157
|
+
fi
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# readonly (#7898 review): `cmd_verify` and friends run `set -a; source "$env_file"; set +a`
|
|
161
|
+
# BELOW this line, so a plain assignment here is rebindable by the repo's .env --
|
|
162
|
+
# which retargets the credentialed request while every transport flag stays intact.
|
|
163
|
+
# That is the "--disable and --noproxy are intact and irrelevant" shape this change
|
|
164
|
+
# exists to close, one layer up. readonly makes the destination non-rebindable.
|
|
165
|
+
readonly X_API="https://api.x.com"
|
|
166
|
+
|
|
167
|
+
# (#9597) OAuth 1.0a request signing (HMAC-SHA1) without the key on any process's argument list.
|
|
168
|
+
# The helper library is sourced LAZILY, inside oauth_sign at each signature, not here: it
|
|
169
|
+
# refuses `bash -x` unconditionally at load time (it names its key indirectly, so it has no
|
|
170
|
+
# literal credential name to test), and loading it up here would make `bash -x x-community.sh`
|
|
171
|
+
# with no credentials set exit 78 instead of printing usage -- the case this file's conditional
|
|
172
|
+
# prologue above promises to keep working.
|
|
173
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
174
|
+
|
|
175
|
+
# --- Dependency checks ---
|
|
176
|
+
|
|
177
|
+
require_openssl() {
|
|
178
|
+
if ! command -v openssl &>/dev/null; then
|
|
179
|
+
echo "Error: openssl is required for OAuth 1.0a signing but not found." >&2
|
|
180
|
+
echo "Install it via your package manager (e.g., apt install openssl)." >&2
|
|
181
|
+
exit 1
|
|
182
|
+
fi
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
require_jq() {
|
|
186
|
+
if ! command -v jq &>/dev/null; then
|
|
187
|
+
echo "Error: jq is required but not installed." >&2
|
|
188
|
+
echo "Install it: https://jqlang.github.io/jq/download/" >&2
|
|
189
|
+
exit 1
|
|
190
|
+
fi
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# --- Credential validation ---
|
|
194
|
+
|
|
195
|
+
require_credentials() {
|
|
196
|
+
local missing=()
|
|
197
|
+
|
|
198
|
+
if [[ -z "${X_API_KEY:-}" ]]; then
|
|
199
|
+
missing+=("X_API_KEY")
|
|
200
|
+
fi
|
|
201
|
+
if [[ -z "${X_API_SECRET:-}" ]]; then
|
|
202
|
+
missing+=("X_API_SECRET")
|
|
203
|
+
fi
|
|
204
|
+
if [[ -z "${X_ACCESS_TOKEN:-}" ]]; then
|
|
205
|
+
missing+=("X_ACCESS_TOKEN")
|
|
206
|
+
fi
|
|
207
|
+
if [[ -z "${X_ACCESS_TOKEN_SECRET:-}" ]]; then
|
|
208
|
+
missing+=("X_ACCESS_TOKEN_SECRET")
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
212
|
+
echo "Error: Missing X API credentials: ${missing[*]}" >&2
|
|
213
|
+
echo "" >&2
|
|
214
|
+
echo "To configure:" >&2
|
|
215
|
+
echo " 1. Go to https://developer.x.com/en/portal/dashboard" >&2
|
|
216
|
+
echo " 2. Create or select a project and app" >&2
|
|
217
|
+
echo " 3. Generate API Key, API Secret, Access Token, and Access Token Secret" >&2
|
|
218
|
+
echo " 4. Export them as environment variables, without typing a value into the command (its text is kept in the transcript and the shell history): in a terminal, read -rs X_API_KEY; export X_API_KEY, and the same for each of the other three" >&2
|
|
219
|
+
exit 1
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# (#9597) The OAuth 1.0a Authorization header carries the signature and the access
|
|
224
|
+
# token, so it rides curl's stdin config channel (`--config -`), never its argument
|
|
225
|
+
# list (readable by every local user in /proc/<pid>/cmdline). The channel is
|
|
226
|
+
# line-oriented: a CR/LF or other control character in the header would end the
|
|
227
|
+
# `header = "..."` directive and let the following bytes act as a new one, so a header
|
|
228
|
+
# holding one is refused BEFORE it is formatted into the stream. The header's own
|
|
229
|
+
# quotes (`oauth_token="..."`) are legitimate and are escaped by `_cfg_q`. The consumer
|
|
230
|
+
# secret and token secret never reach curl at all: they only key the HMAC in oauth_sign.
|
|
231
|
+
_hdr_ok() { local LC_ALL=C; case "${1:-}" in ''|*[[:cntrl:]]*) return 1 ;; esac; }
|
|
232
|
+
_cfg_q() { local v="${1-}"; v=${v//\\/\\\\}; v=${v//\"/\\\"}; printf '%s' "$v"; }
|
|
233
|
+
|
|
234
|
+
# --- OAuth 1.0a signing ---
|
|
235
|
+
|
|
236
|
+
# URL-encode a string per RFC 3986 (RFC 5849 3.6 for OAuth). Byte-wise: under LC_ALL=C each byte
|
|
237
|
+
# is one character, so a multi-byte UTF-8 character encodes as its bytes (%C3%A9), not as a
|
|
238
|
+
# code point (the `printf '%02X' "'é"` form gives E9 in a UTF-8 locale, which is not the encoding).
|
|
239
|
+
urlencode() {
|
|
240
|
+
local LC_ALL=C
|
|
241
|
+
local string="$1"
|
|
242
|
+
local encoded=""
|
|
243
|
+
local i c o
|
|
244
|
+
for (( i = 0; i < ${#string}; i++ )); do
|
|
245
|
+
c="${string:i:1}"
|
|
246
|
+
case "$c" in
|
|
247
|
+
[A-Za-z0-9._~-]) encoded+="$c" ;;
|
|
248
|
+
*)
|
|
249
|
+
o=$(printf '%02X' "'$c")
|
|
250
|
+
encoded+="%${o}"
|
|
251
|
+
;;
|
|
252
|
+
esac
|
|
253
|
+
done
|
|
254
|
+
echo "$encoded"
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Generate OAuth 1.0a signature for a request
|
|
258
|
+
# Arguments: method url [param_key=param_value ...]
|
|
259
|
+
oauth_sign() {
|
|
260
|
+
local method="$1"
|
|
261
|
+
local url="$2"
|
|
262
|
+
shift 2
|
|
263
|
+
|
|
264
|
+
local oauth_nonce
|
|
265
|
+
oauth_nonce=$(openssl rand -hex 16)
|
|
266
|
+
local oauth_timestamp
|
|
267
|
+
oauth_timestamp=$(date +%s)
|
|
268
|
+
|
|
269
|
+
# Collect all parameters (OAuth + request params)
|
|
270
|
+
local -a params=()
|
|
271
|
+
params+=("oauth_consumer_key=$(urlencode "${X_API_KEY}")")
|
|
272
|
+
params+=("oauth_nonce=$(urlencode "${oauth_nonce}")")
|
|
273
|
+
params+=("oauth_signature_method=HMAC-SHA1")
|
|
274
|
+
params+=("oauth_timestamp=${oauth_timestamp}")
|
|
275
|
+
params+=("oauth_token=$(urlencode "${X_ACCESS_TOKEN}")")
|
|
276
|
+
params+=("oauth_version=1.0")
|
|
277
|
+
|
|
278
|
+
# Add request parameters
|
|
279
|
+
local param
|
|
280
|
+
for param in "$@"; do
|
|
281
|
+
local key="${param%%=*}"
|
|
282
|
+
local val="${param#*=}"
|
|
283
|
+
params+=("$(urlencode "$key")=$(urlencode "$val")")
|
|
284
|
+
done
|
|
285
|
+
|
|
286
|
+
# Sort parameters lexicographically
|
|
287
|
+
local sorted_params
|
|
288
|
+
sorted_params=$(printf '%s\n' "${params[@]}" | sort)
|
|
289
|
+
|
|
290
|
+
# Build parameter string
|
|
291
|
+
local param_string=""
|
|
292
|
+
while IFS= read -r line; do
|
|
293
|
+
if [[ -n "$param_string" ]]; then
|
|
294
|
+
param_string+="&"
|
|
295
|
+
fi
|
|
296
|
+
param_string+="$line"
|
|
297
|
+
done <<< "$sorted_params"
|
|
298
|
+
|
|
299
|
+
# Build signature base string
|
|
300
|
+
local base_string="${method}&$(urlencode "$url")&$(urlencode "$param_string")"
|
|
301
|
+
|
|
302
|
+
# Build signing key (read BY NAME inside the HMAC function below)
|
|
303
|
+
# shellcheck disable=SC2034
|
|
304
|
+
local signing_key="$(urlencode "${X_API_SECRET}")&$(urlencode "${X_ACCESS_TOKEN_SECRET}")"
|
|
305
|
+
|
|
306
|
+
# Generate HMAC-SHA1 signature (#9597). The signing key is handed to the function by NAME and
|
|
307
|
+
# lives only in bash builtins and pipes: it is on no process's argument list. A failed
|
|
308
|
+
# computation yields no signature and the request is never sent unsigned.
|
|
309
|
+
# oauth_sign runs inside `$(...)`, so the load's stdout (the library's xtrace refusal text) goes
|
|
310
|
+
# to the saved real stdout (fd 3), where the founder sees it, not into the captured header.
|
|
311
|
+
# The load happens at EACH signature, not once: this function body is a command-substitution
|
|
312
|
+
# subshell, so a function defined here dies with it and nothing is cached for the next call (the
|
|
313
|
+
# library is one small file). Any function of this name inherited from the environment (an
|
|
314
|
+
# exported bash function) is dropped first, so an inherited function named hmac_sha1_b64 cannot
|
|
315
|
+
# stand in for the library's. That is all this closes: the key stays readable by any other
|
|
316
|
+
# exported function (one named od, tr, openssl, base64 or cat still receives it on its stdin),
|
|
317
|
+
# which is the same exposure as the exported secret variables themselves.
|
|
318
|
+
unset -f hmac_sha1_b64 2>/dev/null || true
|
|
319
|
+
source "$SCRIPT_DIR/lib/hmac-sha1-b64.sh" >&3 || {
|
|
320
|
+
echo "Error: could not load the OAuth signing helper; refusing to send an unsigned request." >&2
|
|
321
|
+
return 1
|
|
322
|
+
}
|
|
323
|
+
local signature
|
|
324
|
+
signature=$(printf '%s' "$base_string" | hmac_sha1_b64 signing_key) || signature=""
|
|
325
|
+
if [[ -z "$signature" ]]; then
|
|
326
|
+
echo "Error: could not compute the OAuth signature; refusing to send an unsigned request." >&2
|
|
327
|
+
return 1
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
# Build Authorization header
|
|
331
|
+
local auth_header="OAuth "
|
|
332
|
+
auth_header+="oauth_consumer_key=\"$(urlencode "${X_API_KEY}")\", "
|
|
333
|
+
auth_header+="oauth_nonce=\"$(urlencode "${oauth_nonce}")\", "
|
|
334
|
+
auth_header+="oauth_signature=\"$(urlencode "$signature")\", "
|
|
335
|
+
auth_header+="oauth_signature_method=\"HMAC-SHA1\", "
|
|
336
|
+
auth_header+="oauth_timestamp=\"${oauth_timestamp}\", "
|
|
337
|
+
auth_header+="oauth_token=\"$(urlencode "${X_ACCESS_TOKEN}")\", "
|
|
338
|
+
auth_header+="oauth_version=\"1.0\""
|
|
339
|
+
|
|
340
|
+
echo "$auth_header"
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
# --- Response handler ---
|
|
344
|
+
|
|
345
|
+
# Handle HTTP response status codes from X API
|
|
346
|
+
# Arguments: http_code body endpoint depth retry_cmd...
|
|
347
|
+
# On 2xx: validates JSON, echoes body to stdout
|
|
348
|
+
# On 429: sleeps and invokes retry_cmd (caller with incremented depth)
|
|
349
|
+
# On error: prints diagnostic to stderr, exits 1 (or 2 for rate limit exhaustion)
|
|
350
|
+
handle_response() {
|
|
351
|
+
local http_code="$1"
|
|
352
|
+
local body="$2"
|
|
353
|
+
local endpoint="$3"
|
|
354
|
+
local depth="$4"
|
|
355
|
+
shift 4
|
|
356
|
+
local -a retry_cmd=("$@")
|
|
357
|
+
|
|
358
|
+
case "$http_code" in
|
|
359
|
+
2[0-9][0-9])
|
|
360
|
+
if ! echo "$body" | jq . >/dev/null 2>&1; then
|
|
361
|
+
echo "Error: X API returned malformed JSON for ${endpoint}" >&2
|
|
362
|
+
exit 1
|
|
363
|
+
fi
|
|
364
|
+
echo "$body"
|
|
365
|
+
;;
|
|
366
|
+
401)
|
|
367
|
+
echo "Error: X API returned 401 Unauthorized for ${endpoint}." >&2
|
|
368
|
+
echo "Your credentials may be expired or invalid." >&2
|
|
369
|
+
echo "" >&2
|
|
370
|
+
echo "To fix:" >&2
|
|
371
|
+
echo " 1. Go to https://developer.x.com/en/portal/dashboard" >&2
|
|
372
|
+
echo " 2. Regenerate your Access Token and Secret" >&2
|
|
373
|
+
echo " 3. Update environment variables" >&2
|
|
374
|
+
exit 1
|
|
375
|
+
;;
|
|
376
|
+
403)
|
|
377
|
+
local reason
|
|
378
|
+
reason=$(echo "$body" | jq -r '.reason // "unknown"' 2>/dev/null || echo "unknown")
|
|
379
|
+
echo "Error: X API returned 403 Forbidden for ${endpoint}." >&2
|
|
380
|
+
if [[ "$reason" == "client-not-enrolled" ]]; then
|
|
381
|
+
# CONTRACT: community-manager agent matches on this string for 403 fallback detection.
|
|
382
|
+
# Changing this message requires updating agents/support/community-manager.md Capability 4 Step 1b.
|
|
383
|
+
echo "This endpoint requires paid API access." >&2
|
|
384
|
+
echo "Visit https://developer.x.com to purchase credits." >&2
|
|
385
|
+
elif [[ "$reason" == "official-client-forbidden" ]]; then
|
|
386
|
+
echo "Your app may lack the required permissions." >&2
|
|
387
|
+
else
|
|
388
|
+
echo "Your app may lack the required permissions or your account may be suspended." >&2
|
|
389
|
+
fi
|
|
390
|
+
exit 1
|
|
391
|
+
;;
|
|
392
|
+
429)
|
|
393
|
+
local retry_after
|
|
394
|
+
retry_after=$(echo "$body" | jq -r '.retry_after // 5' 2>/dev/null || echo "5")
|
|
395
|
+
# Clamp retry_after to sane range [1, 60]
|
|
396
|
+
# Use printf to truncate float to integer for arithmetic comparison
|
|
397
|
+
# (sleep accepts floats natively, but bash (( )) does not)
|
|
398
|
+
local retry_int
|
|
399
|
+
retry_int=$(printf '%.0f' "$retry_after" 2>/dev/null || echo "5")
|
|
400
|
+
if (( retry_int > 60 )); then
|
|
401
|
+
retry_after=60
|
|
402
|
+
elif (( retry_int < 1 )); then
|
|
403
|
+
retry_after=1
|
|
404
|
+
fi
|
|
405
|
+
echo "Rate limited. Retrying after ${retry_after}s (attempt $((depth + 1))/3)..." >&2
|
|
406
|
+
sleep "$retry_after"
|
|
407
|
+
"${retry_cmd[@]}"
|
|
408
|
+
;;
|
|
409
|
+
*)
|
|
410
|
+
local message
|
|
411
|
+
message=$(echo "$body" | jq -r '.detail // .title // "Unknown error"' 2>/dev/null || echo "Unknown error")
|
|
412
|
+
echo "Error: X API returned HTTP ${http_code} for ${endpoint}: ${message}" >&2
|
|
413
|
+
exit 1
|
|
414
|
+
;;
|
|
415
|
+
esac
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
# --- POST request helper ---
|
|
419
|
+
|
|
420
|
+
# Make an authenticated POST request to the X API
|
|
421
|
+
# Arguments: endpoint [json_body] [depth]
|
|
422
|
+
# Retries on 429 up to 3 times
|
|
423
|
+
post_request() {
|
|
424
|
+
local endpoint="$1"
|
|
425
|
+
local json_body="${2:-}"
|
|
426
|
+
local depth="${3:-0}"
|
|
427
|
+
|
|
428
|
+
if (( depth >= 3 )); then
|
|
429
|
+
echo "Error: X API rate limit exceeded after 3 retries for ${endpoint}." >&2
|
|
430
|
+
exit 2
|
|
431
|
+
fi
|
|
432
|
+
|
|
433
|
+
local url="${X_API}${endpoint}"
|
|
434
|
+
local auth_header
|
|
435
|
+
# `|| exit 1`: a failed signature (it has already said why) stops here. These helpers run inside
|
|
436
|
+
# `$(...)`, where errexit does not apply, so without it the empty header would fall through to the
|
|
437
|
+
# shape check below and print a second, misleading "unexpected shape" line.
|
|
438
|
+
auth_header=$(oauth_sign "POST" "$url") || exit 1
|
|
439
|
+
|
|
440
|
+
local -a curl_args=(
|
|
441
|
+
-s -w "\n%{http_code}"
|
|
442
|
+
--config -
|
|
443
|
+
-H "Content-Type: application/json"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
if [[ -n "$json_body" ]]; then
|
|
447
|
+
curl_args+=(-X POST -d "$json_body")
|
|
448
|
+
fi
|
|
449
|
+
|
|
450
|
+
if ! _hdr_ok "$auth_header"; then
|
|
451
|
+
echo "Error: the OAuth Authorization header has an unexpected shape; refusing to send it." >&2
|
|
452
|
+
exit 1
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
local response http_code body
|
|
456
|
+
# Suppress stderr to prevent credential leakage. The signed header rides curl's stdin
|
|
457
|
+
# config channel, never its argument list.
|
|
458
|
+
local __curl_rc=0
|
|
459
|
+
response=$(curl --disable --noproxy '*' "${curl_args[@]}" "$url" 2>/dev/null \
|
|
460
|
+
< <(printf 'header = "Authorization: %s"\n' "$(_cfg_q "$auth_header")")) || __curl_rc=$?
|
|
461
|
+
if (( __curl_rc != 0 )); then
|
|
462
|
+
report_transport_failure "$__curl_rc" "Failed to connect to X API."
|
|
463
|
+
exit 1
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
http_code=$(echo "$response" | tail -1)
|
|
467
|
+
body=$(echo "$response" | sed '$d')
|
|
468
|
+
|
|
469
|
+
handle_response "$http_code" "$body" "$endpoint" "$depth" \
|
|
470
|
+
post_request "$endpoint" "$json_body" "$((depth + 1))"
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
# --- GET request helper ---
|
|
474
|
+
|
|
475
|
+
# Make an authenticated GET request to the X API with query params
|
|
476
|
+
# Arguments: endpoint query_params [depth]
|
|
477
|
+
# Query params are included in the OAuth signature
|
|
478
|
+
# Retries on 429 up to 3 times
|
|
479
|
+
get_request() {
|
|
480
|
+
local endpoint="$1"
|
|
481
|
+
local query_params="${2:-}"
|
|
482
|
+
local depth="${3:-0}"
|
|
483
|
+
|
|
484
|
+
if (( depth >= 3 )); then
|
|
485
|
+
echo "Error: X API rate limit exceeded after 3 retries for ${endpoint}." >&2
|
|
486
|
+
exit 2
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
local url="${X_API}${endpoint}"
|
|
490
|
+
|
|
491
|
+
# Build OAuth signature with query params included
|
|
492
|
+
local auth_header
|
|
493
|
+
if [[ -n "$query_params" ]]; then
|
|
494
|
+
# Split query_params on & and pass as varargs to oauth_sign
|
|
495
|
+
local -a param_args=()
|
|
496
|
+
local param
|
|
497
|
+
while IFS= read -r param; do
|
|
498
|
+
[[ -n "$param" ]] && param_args+=("$param")
|
|
499
|
+
done <<< "${query_params//&/$'\n'}"
|
|
500
|
+
auth_header=$(oauth_sign "GET" "$url" "${param_args[@]}") || exit 1
|
|
501
|
+
else
|
|
502
|
+
auth_header=$(oauth_sign "GET" "$url") || exit 1
|
|
503
|
+
fi
|
|
504
|
+
|
|
505
|
+
# Build request URL with query string
|
|
506
|
+
local request_url="$url"
|
|
507
|
+
if [[ -n "$query_params" ]]; then
|
|
508
|
+
request_url="${url}?${query_params}"
|
|
509
|
+
fi
|
|
510
|
+
|
|
511
|
+
local response http_code body
|
|
512
|
+
if ! _hdr_ok "$auth_header"; then
|
|
513
|
+
echo "Error: the OAuth Authorization header has an unexpected shape; refusing to send it." >&2
|
|
514
|
+
exit 1
|
|
515
|
+
fi
|
|
516
|
+
local __curl_rc=0
|
|
517
|
+
# The signed header rides curl's stdin config channel, never its argument list.
|
|
518
|
+
response=$(curl --disable --noproxy '*' -s -w "\n%{http_code}" --config - \
|
|
519
|
+
"$request_url" 2>/dev/null \
|
|
520
|
+
< <(printf 'header = "Authorization: %s"\n' "$(_cfg_q "$auth_header")")) || __curl_rc=$?
|
|
521
|
+
if (( __curl_rc != 0 )); then
|
|
522
|
+
report_transport_failure "$__curl_rc" "Failed to connect to X API."
|
|
523
|
+
exit 1
|
|
524
|
+
fi
|
|
525
|
+
|
|
526
|
+
http_code=$(echo "$response" | tail -1)
|
|
527
|
+
body=$(echo "$response" | sed '$d')
|
|
528
|
+
|
|
529
|
+
handle_response "$http_code" "$body" "$endpoint" "$depth" \
|
|
530
|
+
get_request "$endpoint" "$query_params" "$((depth + 1))"
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
# Resolve the authenticated user's numeric ID
|
|
534
|
+
resolve_user_id() {
|
|
535
|
+
local response
|
|
536
|
+
response=$(get_request "/2/users/me" "")
|
|
537
|
+
|
|
538
|
+
local user_id
|
|
539
|
+
user_id=$(echo "$response" | jq -r '.data.id' 2>/dev/null || echo "")
|
|
540
|
+
|
|
541
|
+
if [[ ! "$user_id" =~ ^[0-9]+$ ]]; then
|
|
542
|
+
echo "Error: Failed to resolve user ID from /2/users/me." >&2
|
|
543
|
+
echo "Response did not contain a valid numeric ID." >&2
|
|
544
|
+
exit 1
|
|
545
|
+
fi
|
|
546
|
+
|
|
547
|
+
echo "$user_id"
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
# --- Shared helpers ---
|
|
551
|
+
|
|
552
|
+
# Fetch tweets for a given user ID and return the data array.
|
|
553
|
+
# Arguments: user_id max_results
|
|
554
|
+
_fetch_tweets_for_user() {
|
|
555
|
+
local user_id="$1"
|
|
556
|
+
local max_results="$2"
|
|
557
|
+
local query_params="tweet.fields=created_at,public_metrics,text&max_results=${max_results}"
|
|
558
|
+
local body
|
|
559
|
+
body=$(get_request "/2/users/${user_id}/tweets" "$query_params")
|
|
560
|
+
echo "$body" | jq '.data // []'
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
# --- Anomaly detection ---
|
|
564
|
+
|
|
565
|
+
# Detect anomalous public_metrics patterns and emit warnings to stderr.
|
|
566
|
+
# Shell boolean convention: returns 0 (true) if anomaly exists, 1 (false) if normal.
|
|
567
|
+
# Arguments: metrics_json (JSON object with followers_count, following_count, tweet_count)
|
|
568
|
+
_has_metrics_anomaly() {
|
|
569
|
+
local metrics_json="$1"
|
|
570
|
+
|
|
571
|
+
local followers following tweets
|
|
572
|
+
read -r followers following tweets < <(
|
|
573
|
+
echo "$metrics_json" | jq -r '[.followers_count // 0, .following_count // 0, .tweet_count // 0] | @tsv'
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Fail safe on non-numeric jq output (malformed API response)
|
|
577
|
+
if [[ ! "$followers" =~ ^[0-9]+$ ]] || [[ ! "$following" =~ ^[0-9]+$ ]] || [[ ! "$tweets" =~ ^[0-9]+$ ]]; then
|
|
578
|
+
return 1
|
|
579
|
+
fi
|
|
580
|
+
|
|
581
|
+
# Genuinely new/empty account: all zeros is not anomalous
|
|
582
|
+
if (( followers == 0 && following == 0 && tweets == 0 )); then
|
|
583
|
+
return 1
|
|
584
|
+
fi
|
|
585
|
+
|
|
586
|
+
# All-zeros degradation: followers=0, following=0, but tweets>0
|
|
587
|
+
if (( followers == 0 && following == 0 && tweets > 0 )); then
|
|
588
|
+
echo "Warning: X API returned all-zero social metrics for account with ${tweets} tweets. Possible API degradation." | head -c 200 >&2
|
|
589
|
+
return 0
|
|
590
|
+
fi
|
|
591
|
+
|
|
592
|
+
# Active account with zero followers: followers=0 but tweets>0 and following>0
|
|
593
|
+
if (( followers == 0 && tweets > 0 && following > 0 )); then
|
|
594
|
+
echo "Warning: X API returned 0 followers for active account (${tweets} tweets, ${following} following). This may indicate unfollows, spam cleanup, or API degradation." | head -c 200 >&2
|
|
595
|
+
return 0
|
|
596
|
+
fi
|
|
597
|
+
|
|
598
|
+
return 1
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
# --- Commands ---
|
|
602
|
+
|
|
603
|
+
cmd_fetch_metrics() {
|
|
604
|
+
local body
|
|
605
|
+
body=$(get_request "/2/users/me" "user.fields=public_metrics,description,created_at")
|
|
606
|
+
|
|
607
|
+
local output metrics_json
|
|
608
|
+
output=$(echo "$body" | jq '{
|
|
609
|
+
username: .data.username,
|
|
610
|
+
name: .data.name,
|
|
611
|
+
description: .data.description,
|
|
612
|
+
created_at: .data.created_at,
|
|
613
|
+
metrics: .data.public_metrics
|
|
614
|
+
}')
|
|
615
|
+
metrics_json=$(echo "$output" | jq '.metrics // {}')
|
|
616
|
+
|
|
617
|
+
# Run anomaly check (warnings go to stderr, stdout unchanged)
|
|
618
|
+
_has_metrics_anomaly "$metrics_json" || true
|
|
619
|
+
|
|
620
|
+
echo "$output"
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
cmd_fetch_mentions() {
|
|
624
|
+
local max_results=10
|
|
625
|
+
local since_id=""
|
|
626
|
+
|
|
627
|
+
# Parse optional arguments
|
|
628
|
+
while [[ $# -gt 0 ]]; do
|
|
629
|
+
case "${1:-}" in
|
|
630
|
+
--max-results)
|
|
631
|
+
local mr_val="${2:-}"
|
|
632
|
+
if [[ -z "$mr_val" ]]; then
|
|
633
|
+
echo "Error: --max-results requires a numeric value." >&2
|
|
634
|
+
exit 1
|
|
635
|
+
fi
|
|
636
|
+
if ! [[ "$mr_val" =~ ^[0-9]+$ ]]; then
|
|
637
|
+
echo "Error: --max-results must be a numeric value." >&2
|
|
638
|
+
exit 1
|
|
639
|
+
fi
|
|
640
|
+
if (( mr_val < 5 || mr_val > 100 )); then
|
|
641
|
+
echo "Error: --max-results must be between 5 and 100." >&2
|
|
642
|
+
exit 1
|
|
643
|
+
fi
|
|
644
|
+
max_results="$mr_val"
|
|
645
|
+
shift 2
|
|
646
|
+
;;
|
|
647
|
+
--since-id)
|
|
648
|
+
local si_val="${2:-}"
|
|
649
|
+
if [[ -z "$si_val" ]]; then
|
|
650
|
+
echo "Error: --since-id requires a numeric value." >&2
|
|
651
|
+
exit 1
|
|
652
|
+
fi
|
|
653
|
+
if ! [[ "$si_val" =~ ^[0-9]+$ ]]; then
|
|
654
|
+
echo "Error: --since-id must be a numeric value." >&2
|
|
655
|
+
exit 1
|
|
656
|
+
fi
|
|
657
|
+
since_id="$si_val"
|
|
658
|
+
shift 2
|
|
659
|
+
;;
|
|
660
|
+
*)
|
|
661
|
+
echo "Error: Unknown option." >&2
|
|
662
|
+
echo "Usage: x-community.sh fetch-mentions [--max-results N] [--since-id ID]" >&2
|
|
663
|
+
exit 1
|
|
664
|
+
;;
|
|
665
|
+
esac
|
|
666
|
+
done
|
|
667
|
+
|
|
668
|
+
# Resolve authenticated user ID
|
|
669
|
+
local user_id
|
|
670
|
+
user_id=$(resolve_user_id)
|
|
671
|
+
|
|
672
|
+
# Build query parameters
|
|
673
|
+
local query_params="max_results=${max_results}&tweet.fields=author_id,created_at,conversation_id,referenced_tweets&expansions=author_id&user.fields=username,name,profile_image_url,public_metrics"
|
|
674
|
+
if [[ -n "$since_id" ]]; then
|
|
675
|
+
query_params="${query_params}&since_id=${since_id}"
|
|
676
|
+
fi
|
|
677
|
+
|
|
678
|
+
# Use shared GET helper with retry and enhanced error handling
|
|
679
|
+
local body
|
|
680
|
+
body=$(get_request "/2/users/${user_id}/mentions" "$query_params")
|
|
681
|
+
|
|
682
|
+
# Handle empty data (no mentions)
|
|
683
|
+
local data_count
|
|
684
|
+
data_count=$(echo "$body" | jq '.data | length // 0' 2>/dev/null || echo "0")
|
|
685
|
+
if [[ "$data_count" == "0" ]] || [[ "$data_count" == "null" ]]; then
|
|
686
|
+
echo '{"mentions":[],"meta":{"newest_id":null,"result_count":0}}'
|
|
687
|
+
return 0
|
|
688
|
+
fi
|
|
689
|
+
|
|
690
|
+
# Transform: join includes.users to data by author_id
|
|
691
|
+
# Use INDEX to build a lookup map so tweets without a matching user
|
|
692
|
+
# are preserved with "unknown" fallbacks (not silently dropped)
|
|
693
|
+
echo "$body" | jq '
|
|
694
|
+
((.includes.users // []) | INDEX(.id)) as $users |
|
|
695
|
+
{
|
|
696
|
+
mentions: [
|
|
697
|
+
.data[] |
|
|
698
|
+
($users[.author_id] // {}) as $user |
|
|
699
|
+
{
|
|
700
|
+
id: .id,
|
|
701
|
+
text: .text,
|
|
702
|
+
author_id: .author_id,
|
|
703
|
+
author_username: ($user.username // "unknown"),
|
|
704
|
+
author_name: ($user.name // "unknown"),
|
|
705
|
+
author_profile_image_url: ($user.profile_image_url // null),
|
|
706
|
+
author_followers_count: ($user.public_metrics.followers_count // 0),
|
|
707
|
+
created_at: .created_at,
|
|
708
|
+
conversation_id: .conversation_id,
|
|
709
|
+
referenced_tweets: (.referenced_tweets // null)
|
|
710
|
+
}
|
|
711
|
+
],
|
|
712
|
+
meta: {
|
|
713
|
+
newest_id: (.meta.newest_id // null),
|
|
714
|
+
result_count: (.meta.result_count // 0)
|
|
715
|
+
}
|
|
716
|
+
}'
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
cmd_fetch_timeline() {
|
|
720
|
+
local max_results=10
|
|
721
|
+
|
|
722
|
+
while [[ $# -gt 0 ]]; do
|
|
723
|
+
case "$1" in
|
|
724
|
+
--max)
|
|
725
|
+
max_results="${2:?--max requires a number}"
|
|
726
|
+
shift 2
|
|
727
|
+
;;
|
|
728
|
+
*)
|
|
729
|
+
echo "Error: Unknown option." >&2
|
|
730
|
+
exit 1
|
|
731
|
+
;;
|
|
732
|
+
esac
|
|
733
|
+
done
|
|
734
|
+
|
|
735
|
+
# Validate --max is a positive integer (prevents query param injection)
|
|
736
|
+
if [[ ! "$max_results" =~ ^[0-9]+$ ]]; then
|
|
737
|
+
echo "Error: --max must be a positive integer." >&2
|
|
738
|
+
exit 1
|
|
739
|
+
fi
|
|
740
|
+
|
|
741
|
+
# Clamp max_results to API range 5-100
|
|
742
|
+
if (( max_results < 5 )); then
|
|
743
|
+
echo "Warning: --max ${max_results} is below API minimum of 5, clamping to 5." >&2
|
|
744
|
+
max_results=5
|
|
745
|
+
elif (( max_results > 100 )); then
|
|
746
|
+
echo "Warning: --max ${max_results} is above API maximum of 100, clamping to 100." >&2
|
|
747
|
+
max_results=100
|
|
748
|
+
fi
|
|
749
|
+
|
|
750
|
+
local user_id
|
|
751
|
+
user_id=$(resolve_user_id)
|
|
752
|
+
|
|
753
|
+
_fetch_tweets_for_user "$user_id" "$max_results"
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
cmd_fetch_user_timeline() {
|
|
757
|
+
local user_id="${1:-}"
|
|
758
|
+
shift || true
|
|
759
|
+
|
|
760
|
+
if [[ -z "$user_id" ]]; then
|
|
761
|
+
echo "Error: fetch-user-timeline requires a user_id argument." >&2
|
|
762
|
+
echo "Usage: x-community.sh fetch-user-timeline <user_id> [--max N]" >&2
|
|
763
|
+
exit 1
|
|
764
|
+
fi
|
|
765
|
+
|
|
766
|
+
# Validate user_id is a positive integer (prevents path traversal)
|
|
767
|
+
if [[ ! "$user_id" =~ ^[0-9]+$ ]]; then
|
|
768
|
+
echo "Error: user_id must be a positive integer." >&2
|
|
769
|
+
exit 1
|
|
770
|
+
fi
|
|
771
|
+
|
|
772
|
+
local max_results=5
|
|
773
|
+
|
|
774
|
+
while [[ $# -gt 0 ]]; do
|
|
775
|
+
case "$1" in
|
|
776
|
+
--max)
|
|
777
|
+
max_results="${2:?--max requires a number}"
|
|
778
|
+
shift 2
|
|
779
|
+
;;
|
|
780
|
+
*)
|
|
781
|
+
echo "Error: Unknown option." >&2
|
|
782
|
+
exit 1
|
|
783
|
+
;;
|
|
784
|
+
esac
|
|
785
|
+
done
|
|
786
|
+
|
|
787
|
+
# Validate --max is a positive integer
|
|
788
|
+
if [[ ! "$max_results" =~ ^[0-9]+$ ]]; then
|
|
789
|
+
echo "Error: --max must be a positive integer." >&2
|
|
790
|
+
exit 1
|
|
791
|
+
fi
|
|
792
|
+
|
|
793
|
+
# Clamp max_results to API range 5-100
|
|
794
|
+
if (( max_results < 5 )); then
|
|
795
|
+
echo "Warning: --max ${max_results} is below API minimum of 5, clamping to 5." >&2
|
|
796
|
+
max_results=5
|
|
797
|
+
elif (( max_results > 100 )); then
|
|
798
|
+
echo "Warning: --max ${max_results} is above API maximum of 100, clamping to 100." >&2
|
|
799
|
+
max_results=100
|
|
800
|
+
fi
|
|
801
|
+
|
|
802
|
+
_fetch_tweets_for_user "$user_id" "$max_results"
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
cmd_post_tweet() {
|
|
806
|
+
# Guard: require explicit opt-in to post.
|
|
807
|
+
if [[ "${X_ALLOW_POST:-}" != "true" ]]; then
|
|
808
|
+
echo "Error: X_ALLOW_POST is not set to 'true'." >&2
|
|
809
|
+
echo "Set X_ALLOW_POST=true to enable posting." >&2
|
|
810
|
+
return 1
|
|
811
|
+
fi
|
|
812
|
+
|
|
813
|
+
local text="${1:?Usage: x-community.sh post-tweet <text> [--reply-to TWEET_ID]}"
|
|
814
|
+
shift
|
|
815
|
+
|
|
816
|
+
local reply_to=""
|
|
817
|
+
while [[ $# -gt 0 ]]; do
|
|
818
|
+
case "$1" in
|
|
819
|
+
--reply-to)
|
|
820
|
+
reply_to="${2:?--reply-to requires a tweet ID}"
|
|
821
|
+
shift 2
|
|
822
|
+
;;
|
|
823
|
+
*)
|
|
824
|
+
echo "Error: Unknown option." >&2
|
|
825
|
+
exit 1
|
|
826
|
+
;;
|
|
827
|
+
esac
|
|
828
|
+
done
|
|
829
|
+
|
|
830
|
+
# Build JSON body
|
|
831
|
+
local json_body
|
|
832
|
+
if [[ -n "$reply_to" ]]; then
|
|
833
|
+
json_body=$(jq -n --arg text "$text" --arg reply "$reply_to" \
|
|
834
|
+
'{text: $text, reply: {in_reply_to_tweet_id: $reply}}')
|
|
835
|
+
else
|
|
836
|
+
json_body=$(jq -n --arg text "$text" '{text: $text}')
|
|
837
|
+
fi
|
|
838
|
+
|
|
839
|
+
local result
|
|
840
|
+
result=$(post_request "/2/tweets" "$json_body")
|
|
841
|
+
|
|
842
|
+
# Output the created tweet
|
|
843
|
+
echo "$result" | jq '{
|
|
844
|
+
id: .data.id,
|
|
845
|
+
text: .data.text
|
|
846
|
+
}'
|
|
847
|
+
|
|
848
|
+
echo "Tweet posted successfully." >&2
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
# --- Main ---
|
|
852
|
+
|
|
853
|
+
main() {
|
|
854
|
+
local command="${1:-}"
|
|
855
|
+
shift || true
|
|
856
|
+
|
|
857
|
+
if [[ -z "$command" ]]; then
|
|
858
|
+
echo "Usage: x-community.sh <command> [args]" >&2
|
|
859
|
+
echo "" >&2
|
|
860
|
+
echo "Commands:" >&2
|
|
861
|
+
echo " fetch-metrics - Fetch account metrics" >&2
|
|
862
|
+
echo " fetch-mentions [--max-results N] [--since-id ID] - Fetch recent mentions" >&2
|
|
863
|
+
echo " fetch-timeline [--max N] - Fetch recent tweets (paid API)" >&2
|
|
864
|
+
echo " fetch-user-timeline <user_id> [--max N] - Fetch another user's tweets" >&2
|
|
865
|
+
echo " post-tweet <text> [--reply-to ID] - Post a tweet" >&2
|
|
866
|
+
exit 1
|
|
867
|
+
fi
|
|
868
|
+
|
|
869
|
+
require_jq
|
|
870
|
+
require_openssl
|
|
871
|
+
require_credentials
|
|
872
|
+
|
|
873
|
+
case "$command" in
|
|
874
|
+
fetch-metrics) cmd_fetch_metrics ;;
|
|
875
|
+
fetch-mentions) cmd_fetch_mentions "$@" ;;
|
|
876
|
+
fetch-timeline) cmd_fetch_timeline "$@" ;;
|
|
877
|
+
fetch-user-timeline) cmd_fetch_user_timeline "$@" ;;
|
|
878
|
+
post-tweet) cmd_post_tweet "$@" ;;
|
|
879
|
+
*)
|
|
880
|
+
echo "Error: Unknown command." >&2
|
|
881
|
+
echo "Run 'x-community.sh' without arguments for usage." >&2
|
|
882
|
+
exit 1
|
|
883
|
+
;;
|
|
884
|
+
esac
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
# Guard: allow sourcing without executing main (for test harness)
|
|
888
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
889
|
+
main "$@"
|
|
890
|
+
fi
|