codeply-cli 0.1.1 → 0.3.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/THIRD_PARTY_NOTICES.md +63 -0
- package/bin/codeply.js +562 -259
- package/lib/agent.mjs +1721 -116
- package/lib/ai.js +1107 -184
- package/lib/apply-limit.js +33 -41
- package/lib/apply-patch.js +217 -0
- package/lib/arity-table.json +138 -0
- package/lib/arity.js +70 -0
- package/lib/auth.js +118 -112
- package/lib/commands.js +103 -0
- package/lib/config.js +392 -98
- package/lib/design-library/artifact-template.html +500 -0
- package/lib/design-library/assemble-artifact.mjs +21 -0
- package/lib/design-library/build.mjs +226 -0
- package/lib/design-library/cleanup.mjs +45 -0
- package/lib/design-library/index.json +22380 -0
- package/lib/design-library/make-table-data.mjs +39 -0
- package/lib/design-library/query.mjs +104 -0
- package/lib/diagnostics.js +192 -0
- package/lib/edit-engine.js +171 -14
- package/lib/github-agent.mjs +290 -0
- package/lib/history.js +75 -0
- package/lib/mcp.js +258 -0
- package/lib/native-tools.mjs +189 -0
- package/lib/oauth-connectors.js +478 -0
- package/lib/permissions.js +109 -0
- package/lib/plugins.js +353 -0
- package/lib/render-md.mjs +617 -617
- package/lib/server.mjs +457 -0
- package/lib/session-db.js +220 -0
- package/lib/share.js +168 -0
- package/lib/skills.js +416 -0
- package/lib/snapshot.js +146 -0
- package/lib/subagents.js +164 -0
- package/lib/terminal-hints.js +154 -0
- package/lib/textarea.mjs +258 -258
- package/lib/tools.mjs +1709 -73
- package/lib/tui.js +7 -7
- package/lib/tui.mjs +1086 -899
- package/lib/typecheck-worker.js +147 -0
- package/lib/web-tools.js +158 -0
- package/package.json +5 -3
- package/skills/SOURCE.md +58 -0
- package/skills/accessibility/SKILL.md +147 -0
- package/skills/agent-architecture-audit/SKILL.md +257 -0
- package/skills/agent-eval/SKILL.md +147 -0
- package/skills/agent-harness-construction/SKILL.md +74 -0
- package/skills/agent-introspection-debugging/SKILL.md +154 -0
- package/skills/agent-payment-x402/SKILL.md +225 -0
- package/skills/agent-self-evaluation/SKILL.md +182 -0
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
- package/skills/agent-sort/SKILL.md +216 -0
- package/skills/agentic-engineering/SKILL.md +64 -0
- package/skills/agentic-os/SKILL.md +388 -0
- package/skills/ai-first-engineering/SKILL.md +52 -0
- package/skills/ai-regression-testing/SKILL.md +386 -0
- package/skills/android-clean-architecture/SKILL.md +340 -0
- package/skills/angular-developer/SKILL.md +155 -0
- package/skills/angular-developer/references/angular-animations.md +160 -0
- package/skills/angular-developer/references/angular-aria.md +410 -0
- package/skills/angular-developer/references/cli.md +86 -0
- package/skills/angular-developer/references/component-harnesses.md +59 -0
- package/skills/angular-developer/references/component-styling.md +91 -0
- package/skills/angular-developer/references/components.md +117 -0
- package/skills/angular-developer/references/creating-services.md +97 -0
- package/skills/angular-developer/references/data-resolvers.md +69 -0
- package/skills/angular-developer/references/define-routes.md +67 -0
- package/skills/angular-developer/references/defining-providers.md +72 -0
- package/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/skills/angular-developer/references/e2e-testing.md +56 -0
- package/skills/angular-developer/references/effects.md +83 -0
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/skills/angular-developer/references/host-elements.md +80 -0
- package/skills/angular-developer/references/injection-context.md +63 -0
- package/skills/angular-developer/references/inputs.md +101 -0
- package/skills/angular-developer/references/linked-signal.md +59 -0
- package/skills/angular-developer/references/loading-strategies.md +61 -0
- package/skills/angular-developer/references/mcp.md +108 -0
- package/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/skills/angular-developer/references/outputs.md +86 -0
- package/skills/angular-developer/references/reactive-forms.md +122 -0
- package/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/skills/angular-developer/references/resource.md +77 -0
- package/skills/angular-developer/references/route-animations.md +56 -0
- package/skills/angular-developer/references/route-guards.md +52 -0
- package/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/skills/angular-developer/references/router-testing.md +87 -0
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/skills/angular-developer/references/signal-forms.md +795 -0
- package/skills/angular-developer/references/signals-overview.md +94 -0
- package/skills/angular-developer/references/tailwind-css.md +69 -0
- package/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/skills/angular-developer/references/testing-fundamentals.md +65 -0
- package/skills/api-connector-builder/SKILL.md +121 -0
- package/skills/api-design/SKILL.md +524 -0
- package/skills/architecture-decision-records/SKILL.md +180 -0
- package/skills/article-writing/SKILL.md +80 -0
- package/skills/automation-audit-ops/SKILL.md +143 -0
- package/skills/autonomous-agent-harness/SKILL.md +274 -0
- package/skills/autonomous-loops/SKILL.md +611 -0
- package/skills/backend-patterns/SKILL.md +562 -0
- package/skills/benchmark/SKILL.md +95 -0
- package/skills/benchmark-methodology/SKILL.md +191 -0
- package/skills/benchmark-optimization-loop/SKILL.md +71 -0
- package/skills/blender-motion-state-inspection/SKILL.md +165 -0
- package/skills/blueprint/SKILL.md +106 -0
- package/skills/brand-discovery/SKILL.md +145 -0
- package/skills/brand-discovery/references/10_purpose-why.md +40 -0
- package/skills/brand-discovery/references/20_positioning.md +44 -0
- package/skills/brand-discovery/references/30_audience-niche.md +52 -0
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
- package/skills/brand-discovery/references/50_voice-tone.md +59 -0
- package/skills/brand-discovery/references/60_narrative-story.md +50 -0
- package/skills/brand-discovery/references/70_founder-tension.md +49 -0
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
- package/skills/brand-voice/SKILL.md +98 -0
- package/skills/brand-voice/references/voice-profile-schema.md +55 -0
- package/skills/browser-qa/SKILL.md +105 -0
- package/skills/bun-runtime/SKILL.md +85 -0
- package/skills/canary-watch/SKILL.md +108 -0
- package/skills/carrier-relationship-management/SKILL.md +212 -0
- package/skills/cisco-ios-patterns/SKILL.md +164 -0
- package/skills/ck/SKILL.md +148 -0
- package/skills/claude-devfleet/SKILL.md +112 -0
- package/skills/click-path-audit/SKILL.md +245 -0
- package/skills/clickhouse-io/SKILL.md +445 -0
- package/skills/code-tour/SKILL.md +254 -0
- package/skills/codebase-onboarding/SKILL.md +234 -0
- package/skills/codehealth-mcp/SKILL.md +167 -0
- package/skills/coding-standards/SKILL.md +551 -0
- package/skills/competitive-platform-analysis/SKILL.md +214 -0
- package/skills/competitive-report-structure/SKILL.md +162 -0
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
- package/skills/config-gc/SKILL.md +120 -0
- package/skills/configure-ecc/SKILL.md +385 -0
- package/skills/connections-optimizer/SKILL.md +190 -0
- package/skills/content-engine/SKILL.md +132 -0
- package/skills/content-hash-cache-pattern/SKILL.md +162 -0
- package/skills/context-budget/SKILL.md +136 -0
- package/skills/continuous-agent-loop/SKILL.md +46 -0
- package/skills/continuous-learning/SKILL.md +132 -0
- package/skills/continuous-learning-v2/SKILL.md +361 -0
- package/skills/contract-first/SKILL.md +287 -0
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
- package/skills/cost-tracking/SKILL.md +97 -0
- package/skills/council/SKILL.md +204 -0
- package/skills/cpp-coding-standards/SKILL.md +724 -0
- package/skills/cpp-testing/SKILL.md +325 -0
- package/skills/crosspost/SKILL.md +112 -0
- package/skills/csharp-testing/SKILL.md +322 -0
- package/skills/customer-billing-ops/SKILL.md +141 -0
- package/skills/customs-trade-compliance/SKILL.md +263 -0
- package/skills/dart-flutter-patterns/SKILL.md +564 -0
- package/skills/dashboard-builder/SKILL.md +109 -0
- package/skills/data-scraper-agent/SKILL.md +765 -0
- package/skills/data-throughput-accelerator/SKILL.md +74 -0
- package/skills/database-migrations/SKILL.md +430 -0
- package/skills/deep-research/SKILL.md +160 -0
- package/skills/defi-amm-security/SKILL.md +167 -0
- package/skills/delivery-gate/SKILL.md +126 -0
- package/skills/deployment-patterns/SKILL.md +428 -0
- package/skills/design-system/SKILL.md +83 -0
- package/skills/django-celery/SKILL.md +458 -0
- package/skills/django-patterns/SKILL.md +735 -0
- package/skills/django-security/SKILL.md +644 -0
- package/skills/django-tdd/SKILL.md +730 -0
- package/skills/django-verification/SKILL.md +470 -0
- package/skills/dmux-workflows/SKILL.md +192 -0
- package/skills/docker-patterns/SKILL.md +365 -0
- package/skills/documentation-lookup/SKILL.md +91 -0
- package/skills/dotnet-patterns/SKILL.md +322 -0
- package/skills/dynamic-workflow-mode/SKILL.md +124 -0
- package/skills/e2e-testing/SKILL.md +327 -0
- package/skills/ecc-guide/SKILL.md +190 -0
- package/skills/ecc-recipes/SKILL.md +149 -0
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
- package/skills/email-ops/SKILL.md +122 -0
- package/skills/energy-procurement/SKILL.md +228 -0
- package/skills/enterprise-agent-ops/SKILL.md +51 -0
- package/skills/error-handling/SKILL.md +377 -0
- package/skills/eval-harness/SKILL.md +271 -0
- package/skills/evm-token-decimals/SKILL.md +131 -0
- package/skills/exa-search/SKILL.md +108 -0
- package/skills/fal-ai-media/SKILL.md +289 -0
- package/skills/fastapi-patterns/SKILL.md +514 -0
- package/skills/finance-billing-ops/SKILL.md +128 -0
- package/skills/flox-environments/SKILL.md +497 -0
- package/skills/flutter-dart-code-review/SKILL.md +436 -0
- package/skills/foundation-models-on-device/SKILL.md +243 -0
- package/skills/frontend-a11y/SKILL.md +446 -0
- package/skills/frontend-design-direction/SKILL.md +93 -0
- package/skills/frontend-patterns/SKILL.md +657 -0
- package/skills/frontend-slides/SKILL.md +185 -0
- package/skills/fsharp-testing/SKILL.md +281 -0
- package/skills/gan-style-harness/SKILL.md +279 -0
- package/skills/gateguard/SKILL.md +133 -0
- package/skills/generating-python-installer/SKILL.md +820 -0
- package/skills/git-workflow/SKILL.md +716 -0
- package/skills/github-ops/SKILL.md +145 -0
- package/skills/golang-patterns/SKILL.md +676 -0
- package/skills/golang-testing/SKILL.md +721 -0
- package/skills/google-workspace-ops/SKILL.md +96 -0
- package/skills/growth-log/SKILL.md +128 -0
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
- package/skills/healthcare-emr-patterns/SKILL.md +160 -0
- package/skills/healthcare-eval-harness/SKILL.md +208 -0
- package/skills/healthcare-phi-compliance/SKILL.md +146 -0
- package/skills/hermes-imports/SKILL.md +89 -0
- package/skills/hexagonal-architecture/SKILL.md +277 -0
- package/skills/hipaa-compliance/SKILL.md +79 -0
- package/skills/homelab-network-readiness/SKILL.md +170 -0
- package/skills/homelab-network-setup/SKILL.md +130 -0
- package/skills/homelab-pihole-dns/SKILL.md +275 -0
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
- package/skills/hookify-rules/SKILL.md +128 -0
- package/skills/inherit-legacy-style/SKILL.md +157 -0
- package/skills/intent-driven-development/SKILL.md +360 -0
- package/skills/inventory-demand-planning/SKILL.md +247 -0
- package/skills/investor-materials/SKILL.md +97 -0
- package/skills/investor-outreach/SKILL.md +92 -0
- package/skills/ios-icon-gen/SKILL.md +158 -0
- package/skills/iterative-retrieval/SKILL.md +212 -0
- package/skills/ito-basket-compare/SKILL.md +64 -0
- package/skills/ito-compute/SKILL.md +129 -0
- package/skills/ito-data-atlas-agent/SKILL.md +64 -0
- package/skills/ito-market-intelligence/SKILL.md +61 -0
- package/skills/ito-trade-planner/SKILL.md +68 -0
- package/skills/java-coding-standards/SKILL.md +384 -0
- package/skills/jira-integration/SKILL.md +303 -0
- package/skills/jpa-patterns/SKILL.md +152 -0
- package/skills/knowledge-ops/SKILL.md +155 -0
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
- package/skills/kotlin-patterns/SKILL.md +712 -0
- package/skills/kotlin-testing/SKILL.md +825 -0
- package/skills/kubernetes-patterns/SKILL.md +756 -0
- package/skills/laravel-patterns/SKILL.md +416 -0
- package/skills/laravel-plugin-discovery/SKILL.md +230 -0
- package/skills/laravel-security/SKILL.md +948 -0
- package/skills/laravel-tdd/SKILL.md +675 -0
- package/skills/laravel-verification/SKILL.md +180 -0
- package/skills/latency-critical-systems/SKILL.md +75 -0
- package/skills/lead-intelligence/SKILL.md +322 -0
- package/skills/liquid-glass-design/SKILL.md +279 -0
- package/skills/llm-trading-agent-security/SKILL.md +147 -0
- package/skills/logistics-exception-management/SKILL.md +222 -0
- package/skills/loop-design-check/SKILL.md +143 -0
- package/skills/mailtrap-email-integration/SKILL.md +77 -0
- package/skills/make-interfaces-feel-better/SKILL.md +152 -0
- package/skills/manim-video/SKILL.md +90 -0
- package/skills/market-research/SKILL.md +76 -0
- package/skills/marketing-campaign/SKILL.md +114 -0
- package/skills/mcp-server-patterns/SKILL.md +70 -0
- package/skills/messages-ops/SKILL.md +105 -0
- package/skills/ml-adoption-playbook/SKILL.md +57 -0
- package/skills/mle-workflow/SKILL.md +348 -0
- package/skills/motion-advanced/SKILL.md +596 -0
- package/skills/motion-foundations/SKILL.md +299 -0
- package/skills/motion-patterns/SKILL.md +434 -0
- package/skills/motion-ui/SKILL.md +576 -0
- package/skills/mysql-patterns/SKILL.md +413 -0
- package/skills/nanoclaw-repl/SKILL.md +34 -0
- package/skills/nestjs-patterns/SKILL.md +231 -0
- package/skills/netmiko-ssh-automation/SKILL.md +174 -0
- package/skills/network-bgp-diagnostics/SKILL.md +168 -0
- package/skills/network-config-validation/SKILL.md +211 -0
- package/skills/network-interface-health/SKILL.md +153 -0
- package/skills/nextjs-turbopack/SKILL.md +58 -0
- package/skills/nodejs-keccak256/SKILL.md +103 -0
- package/skills/nutrient-document-processing/SKILL.md +168 -0
- package/skills/nuxt4-patterns/SKILL.md +101 -0
- package/skills/openclaw-persona-forge/SKILL.md +289 -0
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
- package/skills/openclaw-persona-forge/references/output-template.md +166 -0
- package/skills/opensource-pipeline/SKILL.md +256 -0
- package/skills/orch-add-feature/SKILL.md +45 -0
- package/skills/orch-build-mvp/SKILL.md +49 -0
- package/skills/orch-change-feature/SKILL.md +43 -0
- package/skills/orch-fix-defect/SKILL.md +43 -0
- package/skills/orch-pipeline/SKILL.md +121 -0
- package/skills/orch-refine-code/SKILL.md +44 -0
- package/skills/parallel-execution-optimizer/SKILL.md +74 -0
- package/skills/perl-patterns/SKILL.md +505 -0
- package/skills/perl-security/SKILL.md +504 -0
- package/skills/perl-testing/SKILL.md +476 -0
- package/skills/plan-canvas/SKILL.md +153 -0
- package/skills/plan-orchestrate/SKILL.md +263 -0
- package/skills/plankton-code-quality/SKILL.md +237 -0
- package/skills/postgres-patterns/SKILL.md +148 -0
- package/skills/prediction-market-oracle-research/SKILL.md +64 -0
- package/skills/prediction-market-risk-review/SKILL.md +61 -0
- package/skills/premium-web-design/SKILL.md +154 -0
- package/skills/prisma-patterns/SKILL.md +401 -0
- package/skills/product-capability/SKILL.md +142 -0
- package/skills/product-lens/SKILL.md +93 -0
- package/skills/production-audit/SKILL.md +207 -0
- package/skills/production-scheduling/SKILL.md +238 -0
- package/skills/project-flow-ops/SKILL.md +112 -0
- package/skills/prompt-optimizer/SKILL.md +398 -0
- package/skills/python-patterns/SKILL.md +751 -0
- package/skills/python-testing/SKILL.md +817 -0
- package/skills/pytorch-patterns/SKILL.md +397 -0
- package/skills/quality-nonconformance/SKILL.md +260 -0
- package/skills/quarkus-patterns/SKILL.md +723 -0
- package/skills/quarkus-security/SKILL.md +468 -0
- package/skills/quarkus-tdd/SKILL.md +812 -0
- package/skills/quarkus-verification/SKILL.md +480 -0
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
- package/skills/react-native-patterns/SKILL.md +326 -0
- package/skills/react-patterns/SKILL.md +342 -0
- package/skills/react-performance/SKILL.md +575 -0
- package/skills/react-testing/SKILL.md +424 -0
- package/skills/recsys-pipeline-architect/SKILL.md +115 -0
- package/skills/recursive-decision-ledger/SKILL.md +81 -0
- package/skills/redis-patterns/SKILL.md +404 -0
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
- package/skills/remotion-video-creation/SKILL.md +43 -0
- package/skills/repo-scan/SKILL.md +79 -0
- package/skills/research-ops/SKILL.md +113 -0
- package/skills/returns-reverse-logistics/SKILL.md +240 -0
- package/skills/rules-distill/SKILL.md +265 -0
- package/skills/rust-patterns/SKILL.md +500 -0
- package/skills/rust-testing/SKILL.md +501 -0
- package/skills/safety-guard/SKILL.md +76 -0
- package/skills/santa-method/SKILL.md +307 -0
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
- package/skills/scientific-db-uspto-database/SKILL.md +178 -0
- package/skills/scientific-pkg-gget/SKILL.md +167 -0
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
- package/skills/search-first/SKILL.md +183 -0
- package/skills/security-bounty-hunter/SKILL.md +100 -0
- package/skills/security-review/SKILL.md +504 -0
- package/skills/security-scan/SKILL.md +166 -0
- package/skills/seo/SKILL.md +155 -0
- package/skills/skill-comply/SKILL.md +59 -0
- package/skills/skill-scout/SKILL.md +141 -0
- package/skills/skill-stocktake/SKILL.md +195 -0
- package/skills/social-graph-ranker/SKILL.md +155 -0
- package/skills/social-publisher/SKILL.md +130 -0
- package/skills/springboot-patterns/SKILL.md +315 -0
- package/skills/springboot-security/SKILL.md +273 -0
- package/skills/springboot-tdd/SKILL.md +159 -0
- package/skills/springboot-verification/SKILL.md +232 -0
- package/skills/strategic-compact/SKILL.md +142 -0
- package/skills/swift-actor-persistence/SKILL.md +144 -0
- package/skills/swift-concurrency-6-2/SKILL.md +216 -0
- package/skills/swift-protocol-di-testing/SKILL.md +191 -0
- package/skills/swiftui-patterns/SKILL.md +259 -0
- package/skills/taste/SKILL.md +264 -0
- package/skills/taste/references/genre-taxonomy.md +87 -0
- package/skills/tdd-workflow/SKILL.md +583 -0
- package/skills/team-agent-orchestration/SKILL.md +111 -0
- package/skills/team-builder/SKILL.md +169 -0
- package/skills/terminal-ops/SKILL.md +110 -0
- package/skills/tinystruct-patterns/SKILL.md +279 -0
- package/skills/tinystruct-patterns/references/architecture.md +90 -0
- package/skills/tinystruct-patterns/references/data-handling.md +60 -0
- package/skills/tinystruct-patterns/references/database.md +99 -0
- package/skills/tinystruct-patterns/references/routing.md +64 -0
- package/skills/tinystruct-patterns/references/system-usage.md +97 -0
- package/skills/tinystruct-patterns/references/testing.md +72 -0
- package/skills/token-budget-advisor/SKILL.md +134 -0
- package/skills/ui-demo/SKILL.md +466 -0
- package/skills/ui-to-vue/SKILL.md +135 -0
- package/skills/uncloud/SKILL.md +344 -0
- package/skills/unified-memory/SKILL.md +170 -0
- package/skills/unified-notifications-ops/SKILL.md +188 -0
- package/skills/verification-loop/SKILL.md +129 -0
- package/skills/video-editing/SKILL.md +311 -0
- package/skills/videodb/SKILL.md +375 -0
- package/skills/visa-doc-translate/SKILL.md +117 -0
- package/skills/vite-patterns/SKILL.md +450 -0
- package/skills/vue-patterns/SKILL.md +471 -0
- package/skills/windows-desktop-e2e/SKILL.md +888 -0
- package/skills/workspace-surface-audit/SKILL.md +126 -0
- package/skills/x-api/SKILL.md +235 -0
package/lib/apply-limit.js
CHANGED
|
@@ -1,41 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Codeply CLI
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
await supabase.from('apply_history').insert({ user_id: session.user.id, file_path: filePath || '' });
|
|
36
|
-
} catch (e) {
|
|
37
|
-
console.warn('[apply-limit] record failed:', e.message);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
module.exports = { DAILY_APPLY_LIMIT, checkApplyLimit, recordApplyEvent };
|
|
1
|
+
/**
|
|
2
|
+
* Codeply CLI - apply accounting
|
|
3
|
+
*
|
|
4
|
+
* There are no paid plans or daily apply caps any more: checkApplyLimit()
|
|
5
|
+
* always allows the write. recordApplyEvent() still logs a successful write
|
|
6
|
+
* to the shared apply_history table (counts only, never file content) so the
|
|
7
|
+
* admin dashboard keeps its usage numbers. It is fire-and-forget - a slow or
|
|
8
|
+
* failing insert never holds up the agent.
|
|
9
|
+
*/
|
|
10
|
+
const { getClient, getSession } = require('./auth');
|
|
11
|
+
|
|
12
|
+
/** Kept for API compatibility with older callers. Always allowed. */
|
|
13
|
+
async function checkApplyLimit() {
|
|
14
|
+
return { allowed: true, count: 0, limit: Infinity };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function recordApplyEvent(filePath, linesAdded = 0, linesRemoved = 0) {
|
|
18
|
+
try {
|
|
19
|
+
const session = await getSession();
|
|
20
|
+
if (!session) return;
|
|
21
|
+
const { error } = await getClient().from('apply_history').insert({
|
|
22
|
+
user_id: session.user.id,
|
|
23
|
+
file_path: filePath || '',
|
|
24
|
+
lines_added: linesAdded || 0,
|
|
25
|
+
lines_removed: linesRemoved || 0,
|
|
26
|
+
});
|
|
27
|
+
if (error) console.warn('[apply-limit] record failed:', error.message);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.warn('[apply-limit] record failed:', e.message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { checkApplyLimit, recordApplyEvent };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "apply_patch" format: several file changes (add, update, delete, move) in
|
|
3
|
+
* one block, as opencode's and Codex's apply_patch tools accept.
|
|
4
|
+
*
|
|
5
|
+
* *** Begin Patch
|
|
6
|
+
* *** Add File: src/new.js
|
|
7
|
+
* +line one
|
|
8
|
+
* *** Update File: src/app.js
|
|
9
|
+
* *** Move to: src/main.js (optional)
|
|
10
|
+
* @@ function start() (optional anchor line)
|
|
11
|
+
* unchanged context
|
|
12
|
+
* -old line
|
|
13
|
+
* +new line
|
|
14
|
+
* *** Delete File: old.txt
|
|
15
|
+
* *** End Patch
|
|
16
|
+
*
|
|
17
|
+
* parsePatch() and planPatch() are pure (no disk writes) so a caller can show
|
|
18
|
+
* the user the whole change, ask, and only then write it.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function normalize(s) {
|
|
22
|
+
return s.replace(/[‐-―−]/g, '-').replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/ /g, ' ');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @returns {{hunks: object[]}|{error: string}} */
|
|
26
|
+
function parsePatch(text) {
|
|
27
|
+
let lines = String(text || '').replace(/\r\n/g, '\n').split('\n');
|
|
28
|
+
// Models sometimes wrap the patch in a heredoc or a code fence.
|
|
29
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
30
|
+
if (lines.length && /^(?:apply_patch\s*)?<<-?\s*['"]?\w+['"]?\s*$/.test(lines[0].trim())) lines.shift();
|
|
31
|
+
if (lines.length && /^```/.test(lines[0].trim())) lines.shift();
|
|
32
|
+
const begin = lines.findIndex((l) => l.trim() === '*** Begin Patch');
|
|
33
|
+
if (begin === -1) return { error: 'The patch must start with "*** Begin Patch" on its own line.' };
|
|
34
|
+
lines = lines.slice(begin + 1);
|
|
35
|
+
let end = lines.findIndex((l) => l.trim() === '*** End Patch');
|
|
36
|
+
if (end === -1) end = lines.length; // a missing footer is forgivable, the hunks are still complete lines
|
|
37
|
+
lines = lines.slice(0, end);
|
|
38
|
+
|
|
39
|
+
const hunks = [];
|
|
40
|
+
let i = 0;
|
|
41
|
+
const isHeader = (l) => /^\*\*\* (Add File|Update File|Delete File|Move to|End of File):?/.test(l);
|
|
42
|
+
while (i < lines.length) {
|
|
43
|
+
const line = lines[i];
|
|
44
|
+
if (!line.trim()) { i++; continue; }
|
|
45
|
+
let m;
|
|
46
|
+
if ((m = /^\*\*\* Add File:\s*(.+)$/.exec(line))) {
|
|
47
|
+
const body = [];
|
|
48
|
+
i++;
|
|
49
|
+
while (i < lines.length && !isHeader(lines[i]) ) {
|
|
50
|
+
if (lines[i].startsWith('+')) body.push(lines[i].slice(1));
|
|
51
|
+
else if (lines[i] === '') body.push('');
|
|
52
|
+
else return { error: `In "Add File: ${m[1].trim()}", every line must start with "+" (got: ${JSON.stringify(lines[i].slice(0, 60))}).` };
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
while (body.length > 1 && body[body.length - 1] === '') body.pop();
|
|
56
|
+
hunks.push({ type: 'add', path: m[1].trim(), contents: body.join('\n') + (body.length ? '\n' : '') });
|
|
57
|
+
} else if ((m = /^\*\*\* Delete File:\s*(.+)$/.exec(line))) {
|
|
58
|
+
hunks.push({ type: 'delete', path: m[1].trim() });
|
|
59
|
+
i++;
|
|
60
|
+
} else if ((m = /^\*\*\* Update File:\s*(.+)$/.exec(line))) {
|
|
61
|
+
const hunk = { type: 'update', path: m[1].trim(), movePath: null, chunks: [] };
|
|
62
|
+
i++;
|
|
63
|
+
if (i < lines.length && (m = /^\*\*\* Move to:\s*(.+)$/.exec(lines[i]))) { hunk.movePath = m[1].trim(); i++; }
|
|
64
|
+
let cur = null;
|
|
65
|
+
const flush = () => { if (cur && (cur.old.length || cur.new.length)) hunk.chunks.push(cur); cur = null; };
|
|
66
|
+
while (i < lines.length && !/^\*\*\* (Add File|Update File|Delete File)/.test(lines[i])) {
|
|
67
|
+
const l = lines[i];
|
|
68
|
+
if (l.startsWith('@@')) {
|
|
69
|
+
flush();
|
|
70
|
+
cur = { ctx: l.slice(2).trim(), old: [], new: [], eof: false };
|
|
71
|
+
} else if (l.trim() === '*** End of File') {
|
|
72
|
+
if (cur) cur.eof = true;
|
|
73
|
+
} else {
|
|
74
|
+
if (!cur) cur = { ctx: '', old: [], new: [], eof: false };
|
|
75
|
+
if (l.startsWith('+')) cur.new.push(l.slice(1));
|
|
76
|
+
else if (l.startsWith('-')) cur.old.push(l.slice(1));
|
|
77
|
+
else if (l.startsWith(' ')) { cur.old.push(l.slice(1)); cur.new.push(l.slice(1)); }
|
|
78
|
+
else if (l === '') { cur.old.push(''); cur.new.push(''); }
|
|
79
|
+
else return { error: `In "Update File: ${hunk.path}", a change line must start with " ", "-" or "+" (got: ${JSON.stringify(l.slice(0, 60))}).` };
|
|
80
|
+
}
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
flush();
|
|
84
|
+
if (!hunk.chunks.length && !hunk.movePath) return { error: `"Update File: ${hunk.path}" has no changes.` };
|
|
85
|
+
hunks.push(hunk);
|
|
86
|
+
} else {
|
|
87
|
+
return { error: `Unexpected line in the patch: ${JSON.stringify(line.slice(0, 80))}. Each section starts with "*** Add File:", "*** Update File:" or "*** Delete File:".` };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!hunks.length) return { error: 'The patch has no changes.' };
|
|
91
|
+
return { hunks };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Find `needle` (array of lines) in `hay` at or after `from`, loosening whitespace step by step. */
|
|
95
|
+
function seek(hay, needle, from, eof) {
|
|
96
|
+
if (!needle.length) return from;
|
|
97
|
+
const passes = [
|
|
98
|
+
(s) => s,
|
|
99
|
+
(s) => s.replace(/\s+$/, ''),
|
|
100
|
+
(s) => s.trim(),
|
|
101
|
+
(s) => normalize(s).trim(),
|
|
102
|
+
];
|
|
103
|
+
for (const f of passes) {
|
|
104
|
+
const start = eof ? Math.max(from, hay.length - needle.length) : from;
|
|
105
|
+
for (let a = start; a + needle.length <= hay.length; a++) {
|
|
106
|
+
let ok = true;
|
|
107
|
+
for (let b = 0; b < needle.length; b++) if (f(hay[a + b]) !== f(needle[b])) { ok = false; break; }
|
|
108
|
+
if (ok) return a;
|
|
109
|
+
}
|
|
110
|
+
if (eof) {
|
|
111
|
+
// The end-of-file marker is a hint, not a promise: fall back to searching everywhere.
|
|
112
|
+
for (let a = from; a + needle.length <= hay.length; a++) {
|
|
113
|
+
let ok = true;
|
|
114
|
+
for (let b = 0; b < needle.length; b++) if (f(hay[a + b]) !== f(needle[b])) { ok = false; break; }
|
|
115
|
+
if (ok) return a;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return -1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function applyChunks(original, chunks, label) {
|
|
123
|
+
const crlf = /\r\n/.test(original);
|
|
124
|
+
const text = original.replace(/\r\n/g, '\n');
|
|
125
|
+
const hay = text.split('\n');
|
|
126
|
+
const trailingNewline = hay.length > 1 && hay[hay.length - 1] === '';
|
|
127
|
+
if (trailingNewline) hay.pop();
|
|
128
|
+
|
|
129
|
+
const replacements = [];
|
|
130
|
+
let cursor = 0;
|
|
131
|
+
for (const c of chunks) {
|
|
132
|
+
if (c.ctx) {
|
|
133
|
+
const at = seek(hay, [c.ctx], cursor, false);
|
|
134
|
+
if (at === -1) return { error: `${label}: could not find the anchor line "${c.ctx}". Copy it exactly from the file.` };
|
|
135
|
+
cursor = at + 1;
|
|
136
|
+
}
|
|
137
|
+
if (!c.old.length) {
|
|
138
|
+
// Pure insertion: at the end of the file, unless an anchor put us elsewhere.
|
|
139
|
+
const at = c.ctx ? cursor : hay.length;
|
|
140
|
+
replacements.push([at, 0, c.new]);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
let old = c.old;
|
|
144
|
+
let neu = c.new;
|
|
145
|
+
let at = seek(hay, old, cursor, c.eof);
|
|
146
|
+
// A trailing blank line in the chunk often stands for the end of the file.
|
|
147
|
+
if (at === -1 && old[old.length - 1] === '') {
|
|
148
|
+
old = old.slice(0, -1);
|
|
149
|
+
neu = neu[neu.length - 1] === '' ? neu.slice(0, -1) : neu;
|
|
150
|
+
at = seek(hay, old, cursor, c.eof);
|
|
151
|
+
}
|
|
152
|
+
if (at === -1) {
|
|
153
|
+
return { error: `${label}: could not find these lines to replace:\n${c.old.slice(0, 6).map((l) => ` ${l}`).join('\n')}\nRead the file again and copy the lines exactly.` };
|
|
154
|
+
}
|
|
155
|
+
replacements.push([at, old.length, neu]);
|
|
156
|
+
cursor = at + old.length;
|
|
157
|
+
}
|
|
158
|
+
replacements.sort((a, b) => a[0] - b[0]);
|
|
159
|
+
const out = hay.slice();
|
|
160
|
+
for (let k = replacements.length - 1; k >= 0; k--) {
|
|
161
|
+
const [at, len, neu] = replacements[k];
|
|
162
|
+
out.splice(at, len, ...neu);
|
|
163
|
+
}
|
|
164
|
+
let result = out.join('\n') + (trailingNewline || !original ? '\n' : '');
|
|
165
|
+
if (crlf) result = result.replace(/\n/g, '\r\n');
|
|
166
|
+
return { content: result };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Work out what a patch would do, without touching the disk.
|
|
171
|
+
* @param {string} patchText
|
|
172
|
+
* @param {(path:string)=>string|null} readFile current content of a project path, null if it doesn't exist
|
|
173
|
+
* @returns {{changes: Array<{kind:'add'|'update'|'delete'|'move', path:string, newPath?:string, before:string, after:string, added:number, removed:number}>}|{error:string}}
|
|
174
|
+
*/
|
|
175
|
+
function planPatch(patchText, readFile) {
|
|
176
|
+
const parsed = parsePatch(patchText);
|
|
177
|
+
if (parsed.error) return { error: parsed.error };
|
|
178
|
+
const changes = [];
|
|
179
|
+
const virtual = new Map(); // paths changed earlier in this same patch
|
|
180
|
+
const current = (p) => (virtual.has(p) ? virtual.get(p) : readFile(p));
|
|
181
|
+
for (const h of parsed.hunks) {
|
|
182
|
+
if (h.type === 'add') {
|
|
183
|
+
const existing = current(h.path);
|
|
184
|
+
if (existing != null && existing !== '') return { error: `Cannot add ${h.path}: it already exists. Use "*** Update File:" to change it.` };
|
|
185
|
+
changes.push({ kind: 'add', path: h.path, before: '', after: h.contents, added: h.contents.split('\n').length - 1, removed: 0 });
|
|
186
|
+
virtual.set(h.path, h.contents);
|
|
187
|
+
} else if (h.type === 'delete') {
|
|
188
|
+
const existing = current(h.path);
|
|
189
|
+
if (existing == null) return { error: `Cannot delete ${h.path}: no such file.` };
|
|
190
|
+
changes.push({ kind: 'delete', path: h.path, before: existing, after: '', added: 0, removed: existing.split('\n').length });
|
|
191
|
+
virtual.set(h.path, null);
|
|
192
|
+
} else {
|
|
193
|
+
const existing = current(h.path);
|
|
194
|
+
if (existing == null) return { error: `Cannot update ${h.path}: no such file.` };
|
|
195
|
+
let after = existing;
|
|
196
|
+
if (h.chunks.length) {
|
|
197
|
+
const r = applyChunks(existing, h.chunks, h.path);
|
|
198
|
+
if (r.error) return { error: r.error };
|
|
199
|
+
after = r.content;
|
|
200
|
+
}
|
|
201
|
+
const added = h.chunks.reduce((n, c) => n + c.new.length, 0);
|
|
202
|
+
const removed = h.chunks.reduce((n, c) => n + c.old.length, 0);
|
|
203
|
+
if (h.movePath) {
|
|
204
|
+
if (current(h.movePath) != null) return { error: `Cannot move ${h.path} to ${h.movePath}: that file already exists.` };
|
|
205
|
+
changes.push({ kind: 'move', path: h.path, newPath: h.movePath, before: existing, after, added, removed });
|
|
206
|
+
virtual.set(h.path, null);
|
|
207
|
+
virtual.set(h.movePath, after);
|
|
208
|
+
} else {
|
|
209
|
+
changes.push({ kind: 'update', path: h.path, before: existing, after, added, removed });
|
|
210
|
+
virtual.set(h.path, after);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { changes };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { parsePatch, planPatch };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cat": 1,
|
|
3
|
+
"cd": 1,
|
|
4
|
+
"chmod": 1,
|
|
5
|
+
"chown": 1,
|
|
6
|
+
"cp": 1,
|
|
7
|
+
"echo": 1,
|
|
8
|
+
"env": 1,
|
|
9
|
+
"export": 1,
|
|
10
|
+
"grep": 1,
|
|
11
|
+
"kill": 1,
|
|
12
|
+
"killall": 1,
|
|
13
|
+
"ln": 1,
|
|
14
|
+
"ls": 1,
|
|
15
|
+
"mkdir": 1,
|
|
16
|
+
"mv": 1,
|
|
17
|
+
"ps": 1,
|
|
18
|
+
"pwd": 1,
|
|
19
|
+
"rm": 1,
|
|
20
|
+
"rmdir": 1,
|
|
21
|
+
"sleep": 1,
|
|
22
|
+
"source": 1,
|
|
23
|
+
"tail": 1,
|
|
24
|
+
"touch": 1,
|
|
25
|
+
"unset": 1,
|
|
26
|
+
"which": 1,
|
|
27
|
+
"aws": 3,
|
|
28
|
+
"az": 3,
|
|
29
|
+
"bazel": 2,
|
|
30
|
+
"brew": 2,
|
|
31
|
+
"bun": 2,
|
|
32
|
+
"bun run": 3,
|
|
33
|
+
"bun x": 3,
|
|
34
|
+
"cargo": 2,
|
|
35
|
+
"cargo add": 3,
|
|
36
|
+
"cargo run": 3,
|
|
37
|
+
"cdk": 2,
|
|
38
|
+
"cf": 2,
|
|
39
|
+
"cmake": 2,
|
|
40
|
+
"composer": 2,
|
|
41
|
+
"consul": 2,
|
|
42
|
+
"consul kv": 3,
|
|
43
|
+
"crictl": 2,
|
|
44
|
+
"deno": 2,
|
|
45
|
+
"deno task": 3,
|
|
46
|
+
"doctl": 3,
|
|
47
|
+
"docker": 2,
|
|
48
|
+
"docker builder": 3,
|
|
49
|
+
"docker compose": 3,
|
|
50
|
+
"docker container": 3,
|
|
51
|
+
"docker image": 3,
|
|
52
|
+
"docker network": 3,
|
|
53
|
+
"docker volume": 3,
|
|
54
|
+
"eksctl": 2,
|
|
55
|
+
"eksctl create": 3,
|
|
56
|
+
"firebase": 2,
|
|
57
|
+
"flyctl": 2,
|
|
58
|
+
"gcloud": 3,
|
|
59
|
+
"gh": 3,
|
|
60
|
+
"git": 2,
|
|
61
|
+
"git config": 3,
|
|
62
|
+
"git remote": 3,
|
|
63
|
+
"git stash": 3,
|
|
64
|
+
"go": 2,
|
|
65
|
+
"gradle": 2,
|
|
66
|
+
"helm": 2,
|
|
67
|
+
"heroku": 2,
|
|
68
|
+
"hugo": 2,
|
|
69
|
+
"ip": 2,
|
|
70
|
+
"ip addr": 3,
|
|
71
|
+
"ip link": 3,
|
|
72
|
+
"ip netns": 3,
|
|
73
|
+
"ip route": 3,
|
|
74
|
+
"kind": 2,
|
|
75
|
+
"kind create": 3,
|
|
76
|
+
"kubectl": 2,
|
|
77
|
+
"kubectl kustomize": 3,
|
|
78
|
+
"kubectl rollout": 3,
|
|
79
|
+
"kustomize": 2,
|
|
80
|
+
"make": 2,
|
|
81
|
+
"mc": 2,
|
|
82
|
+
"mc admin": 3,
|
|
83
|
+
"minikube": 2,
|
|
84
|
+
"mongosh": 2,
|
|
85
|
+
"mysql": 2,
|
|
86
|
+
"mvn": 2,
|
|
87
|
+
"ng": 2,
|
|
88
|
+
"npm": 2,
|
|
89
|
+
"npm exec": 3,
|
|
90
|
+
"npm init": 3,
|
|
91
|
+
"npm run": 3,
|
|
92
|
+
"npm view": 3,
|
|
93
|
+
"nvm": 2,
|
|
94
|
+
"nx": 2,
|
|
95
|
+
"openssl": 2,
|
|
96
|
+
"openssl req": 3,
|
|
97
|
+
"openssl x509": 3,
|
|
98
|
+
"pip": 2,
|
|
99
|
+
"pipenv": 2,
|
|
100
|
+
"pnpm": 2,
|
|
101
|
+
"pnpm dlx": 3,
|
|
102
|
+
"pnpm exec": 3,
|
|
103
|
+
"pnpm run": 3,
|
|
104
|
+
"poetry": 2,
|
|
105
|
+
"podman": 2,
|
|
106
|
+
"podman container": 3,
|
|
107
|
+
"podman image": 3,
|
|
108
|
+
"psql": 2,
|
|
109
|
+
"pulumi": 2,
|
|
110
|
+
"pulumi stack": 3,
|
|
111
|
+
"pyenv": 2,
|
|
112
|
+
"python": 2,
|
|
113
|
+
"rake": 2,
|
|
114
|
+
"rbenv": 2,
|
|
115
|
+
"redis-cli": 2,
|
|
116
|
+
"rustup": 2,
|
|
117
|
+
"serverless": 2,
|
|
118
|
+
"sfdx": 3,
|
|
119
|
+
"skaffold": 2,
|
|
120
|
+
"sls": 2,
|
|
121
|
+
"sst": 2,
|
|
122
|
+
"swift": 2,
|
|
123
|
+
"systemctl": 2,
|
|
124
|
+
"terraform": 2,
|
|
125
|
+
"terraform workspace": 3,
|
|
126
|
+
"tmux": 2,
|
|
127
|
+
"turbo": 2,
|
|
128
|
+
"ufw": 2,
|
|
129
|
+
"vault": 2,
|
|
130
|
+
"vault auth": 3,
|
|
131
|
+
"vault kv": 3,
|
|
132
|
+
"vercel": 2,
|
|
133
|
+
"volta": 2,
|
|
134
|
+
"wp": 2,
|
|
135
|
+
"yarn": 2,
|
|
136
|
+
"yarn dlx": 3,
|
|
137
|
+
"yarn run": 3
|
|
138
|
+
}
|
package/lib/arity.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many words name a shell command, so "always allow" can be scoped to
|
|
3
|
+
* `npm run dev` or `git checkout` instead of every command there is.
|
|
4
|
+
*
|
|
5
|
+
* The ARITY table and the prefix() rule are from opencode
|
|
6
|
+
* (packages/opencode/src/permission/arity.ts), MIT License,
|
|
7
|
+
* Copyright (c) 2025 opencode - https://github.com/sst/opencode
|
|
8
|
+
* Command splitting and tokenizing below are Codeply's own.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const ARITY = require('./arity-table.json');
|
|
12
|
+
|
|
13
|
+
// "Always allow node" would allow every script there is. For interpreters the
|
|
14
|
+
// script (or module) is part of the name: `node server.js`, `python app.py`.
|
|
15
|
+
const INTERPRETERS = new Set(['node', 'python', 'python3', 'py', 'deno', 'bun', 'ruby', 'php', 'perl', 'bash', 'sh', 'zsh', 'powershell', 'pwsh', 'cmd', 'tsx', 'ts-node']);
|
|
16
|
+
|
|
17
|
+
/** Longest listed prefix wins; unlisted commands are named by their first word. */
|
|
18
|
+
function prefix(tokens) {
|
|
19
|
+
if (tokens.length && INTERPRETERS.has(tokens[0]) && !ARITY[tokens.slice(0, 2).join(' ')]) return tokens.slice(0, 2);
|
|
20
|
+
for (let len = tokens.length; len > 0; len--) {
|
|
21
|
+
const arity = ARITY[tokens.slice(0, len).join(' ')];
|
|
22
|
+
if (arity !== undefined) return tokens.slice(0, arity);
|
|
23
|
+
}
|
|
24
|
+
return tokens.slice(0, 1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Split a command line into its separate commands (&&, ||, ;, |, newlines). Quotes are respected. */
|
|
28
|
+
function splitCommands(line) {
|
|
29
|
+
const out = [];
|
|
30
|
+
let cur = '';
|
|
31
|
+
let quote = null;
|
|
32
|
+
for (let i = 0; i < line.length; i++) {
|
|
33
|
+
const c = line[i];
|
|
34
|
+
const n = line[i + 1];
|
|
35
|
+
if (quote) { cur += c; if (c === quote) quote = null; continue; }
|
|
36
|
+
if (c === '"' || c === "'") { quote = c; cur += c; continue; }
|
|
37
|
+
if ((c === '&' && n === '&') || (c === '|' && n === '|')) { out.push(cur); cur = ''; i++; continue; }
|
|
38
|
+
if (c === ';' || c === '|' || c === '\n' || c === '&') { out.push(cur); cur = ''; continue; }
|
|
39
|
+
cur += c;
|
|
40
|
+
}
|
|
41
|
+
out.push(cur);
|
|
42
|
+
return out.map((s) => s.trim()).filter(Boolean);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Words of one command, without leading VAR=value assignments or flags. */
|
|
46
|
+
function tokenize(cmd) {
|
|
47
|
+
const words = cmd.match(/"[^"]*"|'[^']*'|\S+/g) || [];
|
|
48
|
+
const clean = words.map((w) => w.replace(/^["']|["']$/g, ''));
|
|
49
|
+
let i = 0;
|
|
50
|
+
while (i < clean.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(clean[i])) i++;
|
|
51
|
+
const rest = clean.slice(i).filter((w) => !w.startsWith('-'));
|
|
52
|
+
// A path to a program is still that program: C:\tools\git.exe -> git
|
|
53
|
+
if (rest.length) rest[0] = rest[0].split(/[\\/]/).pop().replace(/\.(exe|cmd|bat|ps1)$/i, '').toLowerCase();
|
|
54
|
+
return rest;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The permission patterns a command line needs, one per command in it:
|
|
59
|
+
* 'npm run dev && git status' -> ['npm run dev', 'git status']
|
|
60
|
+
* Subshells, backticks and redirections into files return null: too dynamic
|
|
61
|
+
* (or too destructive) to approve by name alone.
|
|
62
|
+
*/
|
|
63
|
+
function commandPatterns(line) {
|
|
64
|
+
const text = String(line || '');
|
|
65
|
+
if (/\$\(|`|>/.test(text)) return null;
|
|
66
|
+
const pats = splitCommands(text).map((c) => prefix(tokenize(c)).join(' ')).filter(Boolean);
|
|
67
|
+
return pats.length ? [...new Set(pats)] : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { commandPatterns, prefix, tokenize, splitCommands };
|