codeply-cli 0.5.5 → 0.6.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/LICENSE +21 -21
- package/THIRD_PARTY_NOTICES.md +65 -65
- package/bin/codeply.js +8 -3
- package/lib/agent.mjs +2596 -2268
- package/lib/ai.js +135 -27
- package/lib/ai.mjs +6 -6
- package/lib/apply-patch.js +217 -217
- package/lib/arity-table.json +138 -138
- package/lib/arity.js +70 -70
- package/lib/auth.mjs +6 -6
- package/lib/autopilot.js +84 -0
- package/lib/bot-mail.js +46 -0
- package/lib/bots.js +142 -15
- package/lib/brain.js +914 -0
- package/lib/call-assist.js +120 -0
- package/lib/chatgpt.js +282 -282
- package/lib/cloud-browser.mjs +143 -143
- package/lib/cloud.mjs +1396 -1312
- package/lib/commands.js +103 -103
- package/lib/computer.js +136 -0
- package/lib/design-library/assemble-artifact.mjs +21 -21
- package/lib/design-library/cleanup.mjs +45 -45
- package/lib/design-library/index.json +22379 -22379
- package/lib/design-library/make-table-data.mjs +39 -39
- package/lib/design-tools.mjs +173 -0
- package/lib/diagnostics.js +192 -192
- package/lib/dsh-acp.mjs +444 -0
- package/lib/edit-engine.js +364 -364
- package/lib/github-agent.mjs +326 -326
- package/lib/handoff.mjs +245 -0
- package/lib/history.js +78 -75
- package/lib/life-sources.js +319 -0
- package/lib/life-store.js +356 -0
- package/lib/life-triggers.js +286 -0
- package/lib/life.js +487 -0
- package/lib/mail-watch.js +3 -3
- package/lib/mcp.js +258 -258
- package/lib/native-tools.mjs +17 -5
- package/lib/oauth-connectors.js +76 -21
- package/lib/permissions.js +109 -109
- package/lib/plugins.js +356 -353
- package/lib/reach.js +67 -67
- package/lib/render-md.mjs +647 -617
- package/lib/screen.js +77 -0
- package/lib/server.mjs +458 -457
- package/lib/session-db.js +220 -220
- package/lib/share.js +208 -168
- package/lib/skills.js +4 -1
- package/lib/snapshot.js +146 -146
- package/lib/subagents.js +4 -2
- package/lib/telemetry.js +105 -105
- package/lib/terminal-hints.js +154 -154
- package/lib/textarea.mjs +264 -258
- package/lib/tools.mjs +326 -40
- package/lib/tui.mjs +1459 -1086
- package/lib/typecheck-worker.js +147 -147
- package/lib/web-tools.js +209 -209
- package/package.json +1 -1
- package/skills/SOURCE.md +58 -58
- package/skills/accessibility/SKILL.md +147 -147
- package/skills/agent-architecture-audit/SKILL.md +257 -257
- package/skills/agent-eval/SKILL.md +147 -147
- package/skills/agent-harness-construction/SKILL.md +74 -74
- package/skills/agent-introspection-debugging/SKILL.md +154 -154
- package/skills/agent-payment-x402/SKILL.md +225 -225
- package/skills/agent-self-evaluation/SKILL.md +182 -182
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
- package/skills/agent-sort/SKILL.md +216 -216
- package/skills/agentic-engineering/SKILL.md +64 -64
- package/skills/agentic-os/SKILL.md +388 -388
- package/skills/ai-first-engineering/SKILL.md +52 -52
- package/skills/ai-regression-testing/SKILL.md +386 -386
- package/skills/android-clean-architecture/SKILL.md +340 -340
- package/skills/angular-developer/SKILL.md +155 -155
- package/skills/angular-developer/references/angular-animations.md +160 -160
- package/skills/angular-developer/references/angular-aria.md +410 -410
- package/skills/angular-developer/references/cli.md +86 -86
- package/skills/angular-developer/references/component-harnesses.md +59 -59
- package/skills/angular-developer/references/component-styling.md +91 -91
- package/skills/angular-developer/references/components.md +117 -117
- package/skills/angular-developer/references/creating-services.md +97 -97
- package/skills/angular-developer/references/data-resolvers.md +69 -69
- package/skills/angular-developer/references/define-routes.md +67 -67
- package/skills/angular-developer/references/defining-providers.md +72 -72
- package/skills/angular-developer/references/di-fundamentals.md +120 -120
- package/skills/angular-developer/references/e2e-testing.md +56 -56
- package/skills/angular-developer/references/effects.md +83 -83
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
- package/skills/angular-developer/references/host-elements.md +80 -80
- package/skills/angular-developer/references/injection-context.md +63 -63
- package/skills/angular-developer/references/inputs.md +101 -101
- package/skills/angular-developer/references/linked-signal.md +59 -59
- package/skills/angular-developer/references/loading-strategies.md +61 -61
- package/skills/angular-developer/references/mcp.md +108 -108
- package/skills/angular-developer/references/navigate-to-routes.md +69 -69
- package/skills/angular-developer/references/outputs.md +86 -86
- package/skills/angular-developer/references/reactive-forms.md +122 -122
- package/skills/angular-developer/references/rendering-strategies.md +44 -44
- package/skills/angular-developer/references/resource.md +77 -77
- package/skills/angular-developer/references/route-animations.md +56 -56
- package/skills/angular-developer/references/route-guards.md +52 -52
- package/skills/angular-developer/references/router-lifecycle.md +45 -45
- package/skills/angular-developer/references/router-testing.md +87 -87
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
- package/skills/angular-developer/references/signal-forms.md +795 -795
- package/skills/angular-developer/references/signals-overview.md +94 -94
- package/skills/angular-developer/references/tailwind-css.md +69 -69
- package/skills/angular-developer/references/template-driven-forms.md +114 -114
- package/skills/angular-developer/references/testing-fundamentals.md +65 -65
- package/skills/api-connector-builder/SKILL.md +121 -121
- package/skills/api-design/SKILL.md +524 -524
- package/skills/architecture-decision-records/SKILL.md +180 -180
- package/skills/article-writing/SKILL.md +80 -80
- package/skills/automation-audit-ops/SKILL.md +143 -143
- package/skills/autonomous-agent-harness/SKILL.md +274 -274
- package/skills/autonomous-loops/SKILL.md +611 -611
- package/skills/backend-patterns/SKILL.md +562 -562
- package/skills/benchmark/SKILL.md +95 -95
- package/skills/benchmark-methodology/SKILL.md +191 -191
- package/skills/benchmark-optimization-loop/SKILL.md +71 -71
- package/skills/blender-motion-state-inspection/SKILL.md +165 -165
- package/skills/blueprint/SKILL.md +106 -106
- package/skills/brand-discovery/SKILL.md +145 -145
- package/skills/brand-discovery/references/10_purpose-why.md +40 -40
- package/skills/brand-discovery/references/20_positioning.md +44 -44
- package/skills/brand-discovery/references/30_audience-niche.md +52 -52
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
- package/skills/brand-discovery/references/50_voice-tone.md +59 -59
- package/skills/brand-discovery/references/60_narrative-story.md +50 -50
- package/skills/brand-discovery/references/70_founder-tension.md +49 -49
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
- package/skills/brand-voice/SKILL.md +98 -98
- package/skills/brand-voice/references/voice-profile-schema.md +55 -55
- package/skills/browser-qa/SKILL.md +105 -105
- package/skills/bun-runtime/SKILL.md +85 -85
- package/skills/canary-watch/SKILL.md +108 -108
- package/skills/carrier-relationship-management/SKILL.md +212 -212
- package/skills/cisco-ios-patterns/SKILL.md +164 -164
- package/skills/ck/SKILL.md +148 -148
- package/skills/claude-devfleet/SKILL.md +112 -112
- package/skills/click-path-audit/SKILL.md +245 -245
- package/skills/clickhouse-io/SKILL.md +445 -445
- package/skills/code-tour/SKILL.md +254 -254
- package/skills/codebase-onboarding/SKILL.md +234 -234
- package/skills/codehealth-mcp/SKILL.md +167 -167
- package/skills/codeply-taste/LICENSE +21 -0
- package/skills/codeply-taste/SKILL.md +100 -0
- package/skills/codeply-taste/SOURCE.md +10 -0
- package/skills/codeply-taste/assets/base.css +100 -0
- package/skills/codeply-taste/assets/base.js +37 -0
- package/skills/codeply-taste/library/directions.json +1078 -0
- package/skills/codeply-taste/library/heroes.json +894 -0
- package/skills/codeply-taste/library/motions.json +57 -0
- package/skills/codeply-taste/library/palettes.json +1485 -0
- package/skills/codeply-taste/library/photos.json +7240 -0
- package/skills/codeply-taste/library/sections.json +4136 -0
- package/skills/codeply-taste/library/sites.json +7274 -0
- package/skills/codeply-taste/library/type.json +2024 -0
- package/skills/codeply-taste/references/colour.md +141 -0
- package/skills/codeply-taste/references/copy.md +177 -0
- package/skills/codeply-taste/references/critique.md +171 -0
- package/skills/codeply-taste/references/imagery.md +377 -0
- package/skills/codeply-taste/references/layout.md +178 -0
- package/skills/codeply-taste/references/motion.md +292 -0
- package/skills/codeply-taste/references/research.md +306 -0
- package/skills/codeply-taste/references/tells.md +115 -0
- package/skills/codeply-taste/references/type.md +170 -0
- package/skills/codeply-taste/scripts/assets.mjs +865 -0
- package/skills/codeply-taste/scripts/draw.mjs +525 -0
- package/skills/codeply-taste/scripts/inspect.mjs +205 -0
- package/skills/codeply-taste/scripts/lib/cdp.mjs +205 -0
- package/skills/codeply-taste/scripts/lib/probe.mjs +569 -0
- package/skills/codeply-taste/scripts/lib/rules.mjs +137 -0
- package/skills/codeply-taste/scripts/lint.mjs +108 -0
- package/skills/coding-standards/SKILL.md +551 -551
- package/skills/competitive-platform-analysis/SKILL.md +214 -214
- package/skills/competitive-report-structure/SKILL.md +162 -162
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
- package/skills/config-gc/SKILL.md +120 -120
- package/skills/configure-ecc/SKILL.md +385 -385
- package/skills/connections-optimizer/SKILL.md +190 -190
- package/skills/content-engine/SKILL.md +132 -132
- package/skills/content-hash-cache-pattern/SKILL.md +162 -162
- package/skills/context-budget/SKILL.md +136 -136
- package/skills/continuous-agent-loop/SKILL.md +46 -46
- package/skills/continuous-learning/SKILL.md +132 -132
- package/skills/continuous-learning-v2/SKILL.md +361 -361
- package/skills/contract-first/SKILL.md +287 -287
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
- package/skills/cost-tracking/SKILL.md +97 -97
- package/skills/council/SKILL.md +204 -204
- package/skills/cpp-coding-standards/SKILL.md +724 -724
- package/skills/cpp-testing/SKILL.md +325 -325
- package/skills/crosspost/SKILL.md +112 -112
- package/skills/csharp-testing/SKILL.md +322 -322
- package/skills/customer-billing-ops/SKILL.md +141 -141
- package/skills/customs-trade-compliance/SKILL.md +263 -263
- package/skills/dart-flutter-patterns/SKILL.md +564 -564
- package/skills/dashboard-builder/SKILL.md +109 -109
- package/skills/data-scraper-agent/SKILL.md +765 -765
- package/skills/data-throughput-accelerator/SKILL.md +74 -74
- package/skills/database-migrations/SKILL.md +430 -430
- package/skills/deep-research/SKILL.md +160 -160
- package/skills/defi-amm-security/SKILL.md +167 -167
- package/skills/delivery-gate/SKILL.md +126 -126
- package/skills/deployment-patterns/SKILL.md +428 -428
- package/skills/design-system/SKILL.md +83 -83
- package/skills/django-celery/SKILL.md +458 -458
- package/skills/django-patterns/SKILL.md +735 -735
- package/skills/django-security/SKILL.md +644 -644
- package/skills/django-tdd/SKILL.md +730 -730
- package/skills/django-verification/SKILL.md +470 -470
- package/skills/dmux-workflows/SKILL.md +192 -192
- package/skills/docker-patterns/SKILL.md +365 -365
- package/skills/documentation-lookup/SKILL.md +91 -91
- package/skills/dotnet-patterns/SKILL.md +322 -322
- package/skills/dynamic-workflow-mode/SKILL.md +124 -124
- package/skills/e2e-testing/SKILL.md +327 -327
- package/skills/ecc-guide/SKILL.md +190 -190
- package/skills/ecc-recipes/SKILL.md +149 -149
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
- package/skills/email-ops/SKILL.md +122 -122
- package/skills/energy-procurement/SKILL.md +228 -228
- package/skills/enterprise-agent-ops/SKILL.md +51 -51
- package/skills/error-handling/SKILL.md +377 -377
- package/skills/eval-harness/SKILL.md +271 -271
- package/skills/evm-token-decimals/SKILL.md +131 -131
- package/skills/exa-search/SKILL.md +108 -108
- package/skills/fal-ai-media/SKILL.md +289 -289
- package/skills/fastapi-patterns/SKILL.md +514 -514
- package/skills/finance-billing-ops/SKILL.md +128 -128
- package/skills/flox-environments/SKILL.md +497 -497
- package/skills/flutter-dart-code-review/SKILL.md +436 -436
- package/skills/foundation-models-on-device/SKILL.md +243 -243
- package/skills/frontend-a11y/SKILL.md +446 -446
- package/skills/frontend-design-direction/SKILL.md +93 -93
- package/skills/frontend-patterns/SKILL.md +657 -657
- package/skills/frontend-slides/SKILL.md +185 -185
- package/skills/fsharp-testing/SKILL.md +281 -281
- package/skills/gan-style-harness/SKILL.md +279 -279
- package/skills/gateguard/SKILL.md +133 -133
- package/skills/generating-python-installer/SKILL.md +820 -820
- package/skills/git-workflow/SKILL.md +716 -716
- package/skills/github-ops/SKILL.md +145 -145
- package/skills/golang-patterns/SKILL.md +676 -676
- package/skills/golang-testing/SKILL.md +721 -721
- package/skills/google-workspace-ops/SKILL.md +96 -96
- package/skills/growth-log/SKILL.md +128 -128
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
- package/skills/healthcare-emr-patterns/SKILL.md +160 -160
- package/skills/healthcare-eval-harness/SKILL.md +208 -208
- package/skills/healthcare-phi-compliance/SKILL.md +146 -146
- package/skills/hermes-imports/SKILL.md +89 -89
- package/skills/hexagonal-architecture/SKILL.md +277 -277
- package/skills/hipaa-compliance/SKILL.md +79 -79
- package/skills/homelab-network-readiness/SKILL.md +170 -170
- package/skills/homelab-network-setup/SKILL.md +130 -130
- package/skills/homelab-pihole-dns/SKILL.md +275 -275
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
- package/skills/hookify-rules/SKILL.md +128 -128
- package/skills/inherit-legacy-style/SKILL.md +157 -157
- package/skills/intent-driven-development/SKILL.md +360 -360
- package/skills/inventory-demand-planning/SKILL.md +247 -247
- package/skills/investor-materials/SKILL.md +97 -97
- package/skills/investor-outreach/SKILL.md +92 -92
- package/skills/ios-icon-gen/SKILL.md +158 -158
- package/skills/iterative-retrieval/SKILL.md +212 -212
- package/skills/ito-basket-compare/SKILL.md +64 -64
- package/skills/ito-compute/SKILL.md +129 -129
- package/skills/ito-data-atlas-agent/SKILL.md +64 -64
- package/skills/ito-market-intelligence/SKILL.md +61 -61
- package/skills/ito-trade-planner/SKILL.md +68 -68
- package/skills/java-coding-standards/SKILL.md +384 -384
- package/skills/jira-integration/SKILL.md +303 -303
- package/skills/jpa-patterns/SKILL.md +152 -152
- package/skills/knowledge-ops/SKILL.md +155 -155
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
- package/skills/kotlin-patterns/SKILL.md +712 -712
- package/skills/kotlin-testing/SKILL.md +825 -825
- package/skills/kubernetes-patterns/SKILL.md +756 -756
- package/skills/laravel-patterns/SKILL.md +416 -416
- package/skills/laravel-plugin-discovery/SKILL.md +230 -230
- package/skills/laravel-security/SKILL.md +948 -948
- package/skills/laravel-tdd/SKILL.md +675 -675
- package/skills/laravel-verification/SKILL.md +180 -180
- package/skills/latency-critical-systems/SKILL.md +75 -75
- package/skills/lead-intelligence/SKILL.md +322 -322
- package/skills/liquid-glass-design/SKILL.md +279 -279
- package/skills/llm-trading-agent-security/SKILL.md +147 -147
- package/skills/logistics-exception-management/SKILL.md +222 -222
- package/skills/loop-design-check/SKILL.md +143 -143
- package/skills/mailtrap-email-integration/SKILL.md +77 -77
- package/skills/make-interfaces-feel-better/SKILL.md +152 -152
- package/skills/manim-video/SKILL.md +90 -90
- package/skills/market-research/SKILL.md +76 -76
- package/skills/marketing-campaign/SKILL.md +114 -114
- package/skills/mcp-server-patterns/SKILL.md +70 -70
- package/skills/messages-ops/SKILL.md +105 -105
- package/skills/ml-adoption-playbook/SKILL.md +57 -57
- package/skills/mle-workflow/SKILL.md +348 -348
- package/skills/motion-advanced/SKILL.md +596 -596
- package/skills/motion-foundations/SKILL.md +299 -299
- package/skills/motion-patterns/SKILL.md +434 -434
- package/skills/motion-ui/SKILL.md +576 -576
- package/skills/mysql-patterns/SKILL.md +413 -413
- package/skills/nanoclaw-repl/SKILL.md +34 -34
- package/skills/nestjs-patterns/SKILL.md +231 -231
- package/skills/netmiko-ssh-automation/SKILL.md +174 -174
- package/skills/network-bgp-diagnostics/SKILL.md +168 -168
- package/skills/network-config-validation/SKILL.md +211 -211
- package/skills/network-interface-health/SKILL.md +153 -153
- package/skills/nextjs-turbopack/SKILL.md +58 -58
- package/skills/nodejs-keccak256/SKILL.md +103 -103
- package/skills/nutrient-document-processing/SKILL.md +168 -168
- package/skills/nuxt4-patterns/SKILL.md +101 -101
- package/skills/openclaw-persona-forge/SKILL.md +289 -289
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
- package/skills/openclaw-persona-forge/references/output-template.md +166 -166
- package/skills/opensource-pipeline/SKILL.md +256 -256
- package/skills/orch-add-feature/SKILL.md +45 -45
- package/skills/orch-build-mvp/SKILL.md +49 -49
- package/skills/orch-change-feature/SKILL.md +43 -43
- package/skills/orch-fix-defect/SKILL.md +43 -43
- package/skills/orch-pipeline/SKILL.md +121 -121
- package/skills/orch-refine-code/SKILL.md +44 -44
- package/skills/parallel-execution-optimizer/SKILL.md +74 -74
- package/skills/perl-patterns/SKILL.md +505 -505
- package/skills/perl-security/SKILL.md +504 -504
- package/skills/perl-testing/SKILL.md +476 -476
- package/skills/plan-canvas/SKILL.md +153 -153
- package/skills/plan-orchestrate/SKILL.md +263 -263
- package/skills/plankton-code-quality/SKILL.md +237 -237
- package/skills/postgres-patterns/SKILL.md +148 -148
- package/skills/prediction-market-oracle-research/SKILL.md +64 -64
- package/skills/prediction-market-risk-review/SKILL.md +61 -61
- package/skills/premium-web-design/SKILL.md +154 -154
- package/skills/prisma-patterns/SKILL.md +401 -401
- package/skills/product-capability/SKILL.md +142 -142
- package/skills/product-lens/SKILL.md +93 -93
- package/skills/production-audit/SKILL.md +207 -207
- package/skills/production-scheduling/SKILL.md +238 -238
- package/skills/project-flow-ops/SKILL.md +112 -112
- package/skills/prompt-optimizer/SKILL.md +398 -398
- package/skills/python-patterns/SKILL.md +751 -751
- package/skills/python-testing/SKILL.md +817 -817
- package/skills/pytorch-patterns/SKILL.md +397 -397
- package/skills/quality-nonconformance/SKILL.md +260 -260
- package/skills/quarkus-patterns/SKILL.md +723 -723
- package/skills/quarkus-security/SKILL.md +468 -468
- package/skills/quarkus-tdd/SKILL.md +812 -812
- package/skills/quarkus-verification/SKILL.md +480 -480
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
- package/skills/react-native-patterns/SKILL.md +326 -326
- package/skills/react-patterns/SKILL.md +342 -342
- package/skills/react-performance/SKILL.md +575 -575
- package/skills/react-testing/SKILL.md +424 -424
- package/skills/recsys-pipeline-architect/SKILL.md +115 -115
- package/skills/recursive-decision-ledger/SKILL.md +81 -81
- package/skills/redis-patterns/SKILL.md +404 -404
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
- package/skills/remotion-video-creation/SKILL.md +43 -43
- package/skills/repo-scan/SKILL.md +79 -79
- package/skills/research-ops/SKILL.md +113 -113
- package/skills/returns-reverse-logistics/SKILL.md +240 -240
- package/skills/rules-distill/SKILL.md +265 -265
- package/skills/rust-patterns/SKILL.md +500 -500
- package/skills/rust-testing/SKILL.md +501 -501
- package/skills/safety-guard/SKILL.md +76 -76
- package/skills/santa-method/SKILL.md +307 -307
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
- package/skills/scientific-db-uspto-database/SKILL.md +178 -178
- package/skills/scientific-pkg-gget/SKILL.md +167 -167
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
- package/skills/search-first/SKILL.md +183 -183
- package/skills/security-bounty-hunter/SKILL.md +100 -100
- package/skills/security-review/SKILL.md +504 -504
- package/skills/security-scan/SKILL.md +166 -166
- package/skills/seo/SKILL.md +155 -155
- package/skills/skill-comply/SKILL.md +59 -59
- package/skills/skill-scout/SKILL.md +141 -141
- package/skills/skill-stocktake/SKILL.md +195 -195
- package/skills/social-graph-ranker/SKILL.md +155 -155
- package/skills/social-publisher/SKILL.md +130 -130
- package/skills/springboot-patterns/SKILL.md +315 -315
- package/skills/springboot-security/SKILL.md +273 -273
- package/skills/springboot-tdd/SKILL.md +159 -159
- package/skills/springboot-verification/SKILL.md +232 -232
- package/skills/strategic-compact/SKILL.md +142 -142
- package/skills/swift-actor-persistence/SKILL.md +144 -144
- package/skills/swift-concurrency-6-2/SKILL.md +216 -216
- package/skills/swift-protocol-di-testing/SKILL.md +191 -191
- package/skills/swiftui-patterns/SKILL.md +259 -259
- package/skills/taste/SKILL.md +264 -264
- package/skills/taste/references/genre-taxonomy.md +87 -87
- package/skills/tdd-workflow/SKILL.md +583 -583
- package/skills/team-agent-orchestration/SKILL.md +111 -111
- package/skills/team-builder/SKILL.md +169 -169
- package/skills/terminal-ops/SKILL.md +110 -110
- package/skills/tinystruct-patterns/SKILL.md +279 -279
- package/skills/tinystruct-patterns/references/architecture.md +90 -90
- package/skills/tinystruct-patterns/references/data-handling.md +60 -60
- package/skills/tinystruct-patterns/references/database.md +99 -99
- package/skills/tinystruct-patterns/references/routing.md +64 -64
- package/skills/tinystruct-patterns/references/system-usage.md +97 -97
- package/skills/tinystruct-patterns/references/testing.md +72 -72
- package/skills/token-budget-advisor/SKILL.md +134 -134
- package/skills/ui-demo/SKILL.md +466 -466
- package/skills/ui-to-vue/SKILL.md +135 -135
- package/skills/uncloud/SKILL.md +344 -344
- package/skills/unified-memory/SKILL.md +170 -170
- package/skills/unified-notifications-ops/SKILL.md +188 -188
- package/skills/verification-loop/SKILL.md +129 -129
- package/skills/video-editing/SKILL.md +311 -311
- package/skills/videodb/SKILL.md +375 -375
- package/skills/visa-doc-translate/SKILL.md +117 -117
- package/skills/vite-patterns/SKILL.md +450 -450
- package/skills/vue-patterns/SKILL.md +471 -471
- package/skills/windows-desktop-e2e/SKILL.md +888 -888
- package/skills/workspace-surface-audit/SKILL.md +126 -126
- package/skills/x-api/SKILL.md +235 -235
package/lib/oauth-connectors.js
CHANGED
|
@@ -27,14 +27,18 @@ const GMAIL_SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', 'https:/
|
|
|
27
27
|
// channels:join is what lets slackPostMessage auto-join a public channel
|
|
28
28
|
// before posting to it - without it, chat:write alone only covers channels
|
|
29
29
|
// the app has already been manually invited into.
|
|
30
|
-
|
|
30
|
+
// chat:write.customize lets a Crew bot post under its own name; im:history,
|
|
31
|
+
// im:read and app_mentions:read bring the user's messages to their bots
|
|
32
|
+
// (slack-events on Codeply's server).
|
|
33
|
+
const SLACK_SCOPES = 'chat:write,channels:read,channels:history,channels:join,chat:write.customize,im:history,im:read,app_mentions:read,users:read';
|
|
31
34
|
// Separate from the bot scopes above - Slack issues bot and user tokens
|
|
32
35
|
// through entirely different mechanisms in the same OAuth exchange. `scope`
|
|
33
36
|
// requests the bot token (posts as "Codeply Craft APP"); `user_scope`
|
|
34
37
|
// requests a second, independent token that posts as the actual signed-in
|
|
35
38
|
// user. Both come back in one authorize round-trip, but they're genuinely
|
|
36
39
|
// two different tokens with two different scope sets.
|
|
37
|
-
|
|
40
|
+
// im:history: direct messages to the user reach their bots (drafts only; the user sends their own replies).
|
|
41
|
+
const SLACK_USER_SCOPES = 'chat:write,im:history';
|
|
38
42
|
|
|
39
43
|
function buildGmailAuthUrl(clientId, redirectUri) {
|
|
40
44
|
const params = new URLSearchParams({
|
|
@@ -115,6 +119,8 @@ function gmailError(data, status, what, scopeHint) {
|
|
|
115
119
|
else if (status === 400) hint = ' Reconnecting Gmail in Connect Apps usually fixes this.';
|
|
116
120
|
const e = new Error(`${what} failed (HTTP ${status}${reason ? `, ${reason}` : ''}): ${detail || 'no details from Google'}.${hint}`);
|
|
117
121
|
e.status = status;
|
|
122
|
+
// Switched off on Codeply's side: only Codeply can fix it, so the user is never sent to set it up.
|
|
123
|
+
if (status === 403 && /accessNotConfigured|SERVICE_DISABLED|has not been used/i.test(detail + reason)) e.serviceSide = true;
|
|
118
124
|
return e;
|
|
119
125
|
}
|
|
120
126
|
|
|
@@ -131,20 +137,20 @@ function base64UrlEncode(str) {
|
|
|
131
137
|
* RFC 2822 message, base64url as Gmail wants it. A non-ASCII subject is RFC 2047 encoded.
|
|
132
138
|
* inReplyTo / references (Message-ID values) make it a reply in the same thread.
|
|
133
139
|
*/
|
|
134
|
-
function rawEmail({ to, subject, body, inReplyTo, references }) {
|
|
140
|
+
function rawEmail({ to, cc, subject, body, inReplyTo, references }) {
|
|
135
141
|
const s = String(subject || '');
|
|
136
142
|
const subj = /^[\x20-\x7e]*$/.test(s) ? s : `=?UTF-8?B?${Buffer.from(s, 'utf8').toString('base64')}?=`;
|
|
137
143
|
const oneLine = (v) => String(v || '').replace(/[\r\n]+/g, ' ').trim();
|
|
138
144
|
const head = [
|
|
139
|
-
to ? `To: ${oneLine(to)}` : '', `Subject: ${oneLine(subj)}`,
|
|
145
|
+
to ? `To: ${oneLine(to)}` : '', cc ? `Cc: ${oneLine(cc)}` : '', `Subject: ${oneLine(subj)}`,
|
|
140
146
|
inReplyTo ? `In-Reply-To: ${oneLine(inReplyTo)}` : '', references ? `References: ${oneLine(references)}` : '',
|
|
141
147
|
'Content-Type: text/plain; charset="UTF-8"',
|
|
142
148
|
].filter(Boolean).join('\r\n');
|
|
143
149
|
return base64UrlEncode(`${head}\r\n\r\n${body || ''}`);
|
|
144
150
|
}
|
|
145
151
|
|
|
146
|
-
async function gmailSend(accessToken, { to, subject, body }) {
|
|
147
|
-
const raw = rawEmail({ to, subject, body });
|
|
152
|
+
async function gmailSend(accessToken, { to, cc, subject, body }) {
|
|
153
|
+
const raw = rawEmail({ to, cc, subject, body });
|
|
148
154
|
const res = await fetch('https://www.googleapis.com/gmail/v1/users/me/messages/send', {
|
|
149
155
|
method: 'POST',
|
|
150
156
|
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
|
|
@@ -156,8 +162,8 @@ async function gmailSend(accessToken, { to, subject, body }) {
|
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
/** Saves a draft in the user's Gmail (needs gmail.compose). threadId + inReplyTo make it a reply in that thread. */
|
|
159
|
-
async function gmailCreateDraft(accessToken, { to, subject, body, threadId, inReplyTo, references }) {
|
|
160
|
-
const message = { raw: rawEmail({ to, subject, body, inReplyTo, references }) };
|
|
165
|
+
async function gmailCreateDraft(accessToken, { to, cc, subject, body, threadId, inReplyTo, references }) {
|
|
166
|
+
const message = { raw: rawEmail({ to, cc, subject, body, inReplyTo, references }) };
|
|
161
167
|
if (threadId) message.threadId = String(threadId);
|
|
162
168
|
const res = await fetch('https://www.googleapis.com/gmail/v1/users/me/drafts', {
|
|
163
169
|
method: 'POST',
|
|
@@ -189,6 +195,7 @@ function calendarError(data, status, what) {
|
|
|
189
195
|
else if (status === 400) hint = ' Check the start and end times.';
|
|
190
196
|
const e = new Error(`${what} failed (HTTP ${status}${reason ? `, ${reason}` : ''}): ${detail || 'no details from Google'}.${hint}`);
|
|
191
197
|
e.status = status;
|
|
198
|
+
if (status === 403 && /accessNotConfigured|SERVICE_DISABLED|has not been used|is disabled/i.test(text)) e.serviceSide = true;
|
|
192
199
|
return e;
|
|
193
200
|
}
|
|
194
201
|
|
|
@@ -203,9 +210,10 @@ async function calendarListEvents(accessToken, { timeMin, timeMax, maxResults =
|
|
|
203
210
|
return data.items || [];
|
|
204
211
|
}
|
|
205
212
|
|
|
206
|
-
/** Creates an event on the primary calendar. `event` is a Calendar API event resource. */
|
|
213
|
+
/** Creates an event on the primary calendar. `event` is a Calendar API event resource. Guests get an invite. */
|
|
207
214
|
async function calendarInsertEvent(accessToken, event) {
|
|
208
|
-
const
|
|
215
|
+
const invite = Array.isArray(event.attendees) && event.attendees.length ? '?sendUpdates=all' : '';
|
|
216
|
+
const res = await fetch(`${CALENDAR_EVENTS_URL}${invite}`, {
|
|
209
217
|
method: 'POST',
|
|
210
218
|
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
|
|
211
219
|
body: JSON.stringify(event),
|
|
@@ -215,6 +223,40 @@ async function calendarInsertEvent(accessToken, event) {
|
|
|
215
223
|
return data; // { id, htmlLink, ... }
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
/** Moves, renames or changes one event (a partial resource). Guests hear about it. */
|
|
227
|
+
async function calendarPatchEvent(accessToken, id, patch) {
|
|
228
|
+
const res = await fetch(`${CALENDAR_EVENTS_URL}/${encodeURIComponent(id)}?sendUpdates=all`, {
|
|
229
|
+
method: 'PATCH',
|
|
230
|
+
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
|
|
231
|
+
body: JSON.stringify(patch),
|
|
232
|
+
});
|
|
233
|
+
const data = await readJson(res);
|
|
234
|
+
if (!res.ok) throw calendarError(data, res.status, 'Calendar update');
|
|
235
|
+
return data;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Cancels one event. Guests are told. */
|
|
239
|
+
async function calendarDeleteEvent(accessToken, id) {
|
|
240
|
+
const res = await fetch(`${CALENDAR_EVENTS_URL}/${encodeURIComponent(id)}?sendUpdates=all`, { method: 'DELETE', headers: { Authorization: `Bearer ${accessToken}` } });
|
|
241
|
+
if (!res.ok && res.status !== 410) throw calendarError(await readJson(res), res.status, 'Calendar cancel');
|
|
242
|
+
return { ok: true };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** One whole email: headers and the plain-text body (HTML turned into text). */
|
|
246
|
+
async function gmailGetMessage(accessToken, id) {
|
|
247
|
+
const res = await fetch(`https://www.googleapis.com/gmail/v1/users/me/messages/${encodeURIComponent(id)}?format=full`, { headers: { Authorization: `Bearer ${accessToken}` } });
|
|
248
|
+
const msg = await readJson(res);
|
|
249
|
+
if (!res.ok) throw gmailError(msg, res.status, 'Gmail read');
|
|
250
|
+
const header = (name) => (msg.payload?.headers || []).find((h) => h.name.toLowerCase() === name.toLowerCase())?.value || '';
|
|
251
|
+
const parts = [];
|
|
252
|
+
const walk = (p) => { if (!p) return; if (p.body?.data && /^text\/(plain|html)/.test(p.mimeType || 'text/plain')) parts.push({ type: p.mimeType || 'text/plain', text: Buffer.from(p.body.data, 'base64url').toString('utf8') }); (p.parts || []).forEach(walk); };
|
|
253
|
+
walk(msg.payload);
|
|
254
|
+
const plain = parts.find((p) => p.type.startsWith('text/plain'));
|
|
255
|
+
const html = parts.find((p) => p.type.startsWith('text/html'));
|
|
256
|
+
const body = plain ? plain.text : html ? html.text.replace(/<style[\s\S]*?<\/style>|<script[\s\S]*?<\/script>/gi, '').replace(/<br\s*\/?>|<\/p>|<\/div>/gi, '\n').replace(/<[^>]+>/g, '').replace(/ /g, ' ').replace(/&/g, '&') : (msg.snippet || '');
|
|
257
|
+
return { id: msg.id, threadId: msg.threadId, from: header('From'), to: header('To'), cc: header('Cc'), subject: header('Subject'), date: header('Date'), body };
|
|
258
|
+
}
|
|
259
|
+
|
|
218
260
|
async function gmailSearch(accessToken, query, maxResults = 10) {
|
|
219
261
|
// "*" and "" mean "everything" to a person but are not Gmail syntax; an
|
|
220
262
|
// empty q simply lists the newest mail, which is what they meant.
|
|
@@ -273,7 +315,7 @@ async function slackJoinChannel(accessToken, channelId) {
|
|
|
273
315
|
if (!body.ok && body.error !== 'already_in_channel') throw new Error(body.error || 'Could not join the channel');
|
|
274
316
|
}
|
|
275
317
|
|
|
276
|
-
async function slackPostMessage(accessToken, { channel, text }, { isUserToken = false } = {}) {
|
|
318
|
+
async function slackPostMessage(accessToken, { channel, text, threadTs }, { isUserToken = false, asName = '' } = {}) {
|
|
277
319
|
// A bot token's chat:write only covers channels the app has actually
|
|
278
320
|
// joined - not "any public channel" the way a human member can post to.
|
|
279
321
|
// Auto-joining first (public channels only; that's all resolveSlackChannel
|
|
@@ -287,16 +329,29 @@ async function slackPostMessage(accessToken, { channel, text }, { isUserToken =
|
|
|
287
329
|
// either already belongs to the channel or doesn't - conversations.join
|
|
288
330
|
// isn't the right operation for a user identity the way it is for a bot,
|
|
289
331
|
// so this is skipped entirely on that path.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
const
|
|
332
|
+
// A direct message (D...) is not something to join.
|
|
333
|
+
if (!isUserToken && !/^D/.test(String(channel))) await slackJoinChannel(accessToken, channel);
|
|
334
|
+
|
|
335
|
+
// asName: a Crew bot posting as itself (chat:write.customize). A workspace
|
|
336
|
+
// connected before that scope existed refuses the name: then it posts with
|
|
337
|
+
// the bot's name in the text instead, still never as the user.
|
|
338
|
+
const thread = threadTs ? { thread_ts: threadTs } : {};
|
|
339
|
+
const post = async (extra) => {
|
|
340
|
+
const res = await fetch('https://slack.com/api/chat.postMessage', {
|
|
341
|
+
method: 'POST',
|
|
342
|
+
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
|
|
343
|
+
body: JSON.stringify({ channel, text, ...thread, ...extra }),
|
|
344
|
+
});
|
|
345
|
+
return res.json();
|
|
346
|
+
};
|
|
347
|
+
let named = !!asName && !isUserToken;
|
|
348
|
+
let body = await post(named ? { username: String(asName).slice(0, 80) } : {});
|
|
349
|
+
if (!body.ok && named && /missing_scope|not_allowed|invalid_arguments/.test(String(body.error))) {
|
|
350
|
+
named = false;
|
|
351
|
+
body = await post({ text: `*${String(asName).slice(0, 80)}:* ${text}` });
|
|
352
|
+
}
|
|
298
353
|
if (!body.ok) throw new Error(body.error || 'Slack message failed to send');
|
|
299
|
-
return body;
|
|
354
|
+
return { ...body, named };
|
|
300
355
|
}
|
|
301
356
|
|
|
302
357
|
async function slackListChannels(accessToken) {
|
|
@@ -634,7 +689,7 @@ async function checkAccessToken(name, token) {
|
|
|
634
689
|
module.exports = {
|
|
635
690
|
API, apiCall, checkAccessToken,
|
|
636
691
|
buildGmailAuthUrl, exchangeGmailCode, refreshGmailToken, getGmailProfile, gmailSend, gmailSearch, gmailCreateDraft, gmailError, rawEmail,
|
|
637
|
-
calendarListEvents, calendarInsertEvent, calendarError, GOOGLE_SCOPE,
|
|
692
|
+
calendarListEvents, calendarInsertEvent, calendarPatchEvent, calendarDeleteEvent, gmailGetMessage, calendarError, GOOGLE_SCOPE,
|
|
638
693
|
buildSlackAuthUrl, exchangeSlackCode, slackPostMessage, slackListChannels, slackJoinChannel,
|
|
639
694
|
buildVercelAuthUrl, exchangeVercelCode, getVercelProfile, vercelDeploy, vercelSetEnvVars,
|
|
640
695
|
buildSupabaseAuthUrl, exchangeSupabaseCode, refreshSupabaseToken, supabaseListOrganizations, supabaseCreateProject, supabaseGetProjectKeys,
|
package/lib/permissions.js
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standing permission rules, set once in a file instead of clicked per chat.
|
|
3
|
-
*
|
|
4
|
-
* ~/.codeply/permissions.json (every project)
|
|
5
|
-
* <project>/.codeply/permissions.json (this project, checked first)
|
|
6
|
-
*
|
|
7
|
-
* {
|
|
8
|
-
* "deny": ["run:git push*", "run:rm*", "write_file:*.env", "supabase_delete_project"],
|
|
9
|
-
* "allow": ["run:npm test", "run:npm run *", "run:git status", "edit_file:src/**"]
|
|
10
|
-
* }
|
|
11
|
-
*
|
|
12
|
-
* A rule is "<tool>" or "<tool>:<pattern>". For run the pattern matches the
|
|
13
|
-
* command's name (npm run dev, git checkout) and the full command line; for
|
|
14
|
-
* file tools it matches the path relative to the project. * matches within a
|
|
15
|
-
* path segment, ** across them, and a trailing " *" also matches the bare
|
|
16
|
-
* command. Deny beats allow; project rules beat global ones; anything no rule
|
|
17
|
-
* mentions is asked as usual. Evaluation is the same idea as opencode's
|
|
18
|
-
* permission/ rulesets.
|
|
19
|
-
*/
|
|
20
|
-
const fs = require('fs');
|
|
21
|
-
const os = require('os');
|
|
22
|
-
const path = require('path');
|
|
23
|
-
|
|
24
|
-
function readRules(file) {
|
|
25
|
-
try {
|
|
26
|
-
const j = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
27
|
-
const list = (k) => (Array.isArray(j[k]) ? j[k].map(String).filter(Boolean) : []);
|
|
28
|
-
return { allow: list('allow'), deny: list('deny') };
|
|
29
|
-
} catch { return { allow: [], deny: [] }; }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function wildcard(pattern) {
|
|
33
|
-
let re = '';
|
|
34
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
35
|
-
const c = pattern[i];
|
|
36
|
-
if (c === '*' && pattern[i + 1] === '*') { re += '.*'; i++; }
|
|
37
|
-
else if (c === '*') re += '[^/]*';
|
|
38
|
-
else if (c === '?') re += '.';
|
|
39
|
-
else re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
40
|
-
}
|
|
41
|
-
// "npm run *" should also cover plain "npm run".
|
|
42
|
-
re = re.replace(/ \[\^\/\]\*$/, '(?: .*)?').replace(/ \.\*$/, '(?: .*)?');
|
|
43
|
-
return new RegExp(`^${re}$`, process.platform === 'win32' ? 'i' : '');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Strings a request can be matched against for its tool. */
|
|
47
|
-
function subjects(req, cwd) {
|
|
48
|
-
if (req.tool === 'run') {
|
|
49
|
-
const cmd = String(req.detail || '').trim();
|
|
50
|
-
// Commands match by name AND as full text, so "run:npm run *" and
|
|
51
|
-
// "run:git push*" both work.
|
|
52
|
-
return { cmd, names: Array.isArray(req.patterns) ? req.patterns : [] };
|
|
53
|
-
}
|
|
54
|
-
if (req.path) {
|
|
55
|
-
const abs = path.isAbsolute(req.path) ? req.path : path.resolve(cwd || '.', req.path);
|
|
56
|
-
const rel = path.relative(cwd || '.', abs).replace(/\\/g, '/');
|
|
57
|
-
return { path: rel.startsWith('..') ? abs.replace(/\\/g, '/') : rel };
|
|
58
|
-
}
|
|
59
|
-
return {};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function ruleMatches(rule, req, subj) {
|
|
63
|
-
const i = rule.indexOf(':');
|
|
64
|
-
const tool = (i === -1 ? rule : rule.slice(0, i)).trim();
|
|
65
|
-
const pattern = i === -1 ? null : rule.slice(i + 1).trim();
|
|
66
|
-
if (tool !== req.tool && tool !== '*') return false;
|
|
67
|
-
if (!pattern || pattern === '*') return true;
|
|
68
|
-
const re = wildcard(pattern);
|
|
69
|
-
if (req.tool === 'run') {
|
|
70
|
-
if (re.test(subj.cmd)) return true;
|
|
71
|
-
// For allow on a compound command every part must be covered (checked by
|
|
72
|
-
// the caller); here a single name matching is enough to count.
|
|
73
|
-
return subj.names.some((n) => re.test(n));
|
|
74
|
-
}
|
|
75
|
-
return subj.path ? re.test(subj.path) : false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* @param {object} req the approval request ({tool, detail, patterns, path, danger})
|
|
80
|
-
* @returns {{decision: 'allow'|'deny'|null, rule?: string, file?: string}}
|
|
81
|
-
*/
|
|
82
|
-
function decide(req, cwd) {
|
|
83
|
-
const files = [cwd && path.join(cwd, '.codeply', 'permissions.json'), path.join(os.homedir(), '.codeply', 'permissions.json')].filter(Boolean);
|
|
84
|
-
const sets = files.map((f) => ({ file: f, ...readRules(f) }));
|
|
85
|
-
const subj = subjects(req, cwd);
|
|
86
|
-
for (const s of sets) {
|
|
87
|
-
const hit = s.deny.find((r) => ruleMatches(r, req, subj));
|
|
88
|
-
if (hit) return { decision: 'deny', rule: hit, file: s.file };
|
|
89
|
-
}
|
|
90
|
-
// Risky-looking commands (rm -rf, format, curl | sh...) always ask.
|
|
91
|
-
if (req.danger) return { decision: null };
|
|
92
|
-
for (const s of sets) {
|
|
93
|
-
if (req.tool === 'run') {
|
|
94
|
-
// Allow is judged per command, never on the whole line: "npm run *"
|
|
95
|
-
// must not let "npm run build && npm install" through. A line that
|
|
96
|
-
// can't be split into named commands (subshells, redirects) is never
|
|
97
|
-
// auto-allowed.
|
|
98
|
-
if (!subj.names.length) continue;
|
|
99
|
-
const parts = subj.names.length === 1 ? [{ cmd: subj.cmd, names: subj.names }] : subj.names.map((n) => ({ cmd: n, names: [n] }));
|
|
100
|
-
if (parts.every((p) => s.allow.some((r) => ruleMatches(r, req, p)))) return { decision: 'allow', rule: 'allow rules', file: s.file };
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
const hit = s.allow.find((r) => ruleMatches(r, req, subj));
|
|
104
|
-
if (hit) return { decision: 'allow', rule: hit, file: s.file };
|
|
105
|
-
}
|
|
106
|
-
return { decision: null };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
module.exports = { decide, wildcard };
|
|
1
|
+
/**
|
|
2
|
+
* Standing permission rules, set once in a file instead of clicked per chat.
|
|
3
|
+
*
|
|
4
|
+
* ~/.codeply/permissions.json (every project)
|
|
5
|
+
* <project>/.codeply/permissions.json (this project, checked first)
|
|
6
|
+
*
|
|
7
|
+
* {
|
|
8
|
+
* "deny": ["run:git push*", "run:rm*", "write_file:*.env", "supabase_delete_project"],
|
|
9
|
+
* "allow": ["run:npm test", "run:npm run *", "run:git status", "edit_file:src/**"]
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* A rule is "<tool>" or "<tool>:<pattern>". For run the pattern matches the
|
|
13
|
+
* command's name (npm run dev, git checkout) and the full command line; for
|
|
14
|
+
* file tools it matches the path relative to the project. * matches within a
|
|
15
|
+
* path segment, ** across them, and a trailing " *" also matches the bare
|
|
16
|
+
* command. Deny beats allow; project rules beat global ones; anything no rule
|
|
17
|
+
* mentions is asked as usual. Evaluation is the same idea as opencode's
|
|
18
|
+
* permission/ rulesets.
|
|
19
|
+
*/
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
function readRules(file) {
|
|
25
|
+
try {
|
|
26
|
+
const j = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
27
|
+
const list = (k) => (Array.isArray(j[k]) ? j[k].map(String).filter(Boolean) : []);
|
|
28
|
+
return { allow: list('allow'), deny: list('deny') };
|
|
29
|
+
} catch { return { allow: [], deny: [] }; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function wildcard(pattern) {
|
|
33
|
+
let re = '';
|
|
34
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
35
|
+
const c = pattern[i];
|
|
36
|
+
if (c === '*' && pattern[i + 1] === '*') { re += '.*'; i++; }
|
|
37
|
+
else if (c === '*') re += '[^/]*';
|
|
38
|
+
else if (c === '?') re += '.';
|
|
39
|
+
else re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
40
|
+
}
|
|
41
|
+
// "npm run *" should also cover plain "npm run".
|
|
42
|
+
re = re.replace(/ \[\^\/\]\*$/, '(?: .*)?').replace(/ \.\*$/, '(?: .*)?');
|
|
43
|
+
return new RegExp(`^${re}$`, process.platform === 'win32' ? 'i' : '');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Strings a request can be matched against for its tool. */
|
|
47
|
+
function subjects(req, cwd) {
|
|
48
|
+
if (req.tool === 'run') {
|
|
49
|
+
const cmd = String(req.detail || '').trim();
|
|
50
|
+
// Commands match by name AND as full text, so "run:npm run *" and
|
|
51
|
+
// "run:git push*" both work.
|
|
52
|
+
return { cmd, names: Array.isArray(req.patterns) ? req.patterns : [] };
|
|
53
|
+
}
|
|
54
|
+
if (req.path) {
|
|
55
|
+
const abs = path.isAbsolute(req.path) ? req.path : path.resolve(cwd || '.', req.path);
|
|
56
|
+
const rel = path.relative(cwd || '.', abs).replace(/\\/g, '/');
|
|
57
|
+
return { path: rel.startsWith('..') ? abs.replace(/\\/g, '/') : rel };
|
|
58
|
+
}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ruleMatches(rule, req, subj) {
|
|
63
|
+
const i = rule.indexOf(':');
|
|
64
|
+
const tool = (i === -1 ? rule : rule.slice(0, i)).trim();
|
|
65
|
+
const pattern = i === -1 ? null : rule.slice(i + 1).trim();
|
|
66
|
+
if (tool !== req.tool && tool !== '*') return false;
|
|
67
|
+
if (!pattern || pattern === '*') return true;
|
|
68
|
+
const re = wildcard(pattern);
|
|
69
|
+
if (req.tool === 'run') {
|
|
70
|
+
if (re.test(subj.cmd)) return true;
|
|
71
|
+
// For allow on a compound command every part must be covered (checked by
|
|
72
|
+
// the caller); here a single name matching is enough to count.
|
|
73
|
+
return subj.names.some((n) => re.test(n));
|
|
74
|
+
}
|
|
75
|
+
return subj.path ? re.test(subj.path) : false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {object} req the approval request ({tool, detail, patterns, path, danger})
|
|
80
|
+
* @returns {{decision: 'allow'|'deny'|null, rule?: string, file?: string}}
|
|
81
|
+
*/
|
|
82
|
+
function decide(req, cwd) {
|
|
83
|
+
const files = [cwd && path.join(cwd, '.codeply', 'permissions.json'), path.join(os.homedir(), '.codeply', 'permissions.json')].filter(Boolean);
|
|
84
|
+
const sets = files.map((f) => ({ file: f, ...readRules(f) }));
|
|
85
|
+
const subj = subjects(req, cwd);
|
|
86
|
+
for (const s of sets) {
|
|
87
|
+
const hit = s.deny.find((r) => ruleMatches(r, req, subj));
|
|
88
|
+
if (hit) return { decision: 'deny', rule: hit, file: s.file };
|
|
89
|
+
}
|
|
90
|
+
// Risky-looking commands (rm -rf, format, curl | sh...) always ask.
|
|
91
|
+
if (req.danger) return { decision: null };
|
|
92
|
+
for (const s of sets) {
|
|
93
|
+
if (req.tool === 'run') {
|
|
94
|
+
// Allow is judged per command, never on the whole line: "npm run *"
|
|
95
|
+
// must not let "npm run build && npm install" through. A line that
|
|
96
|
+
// can't be split into named commands (subshells, redirects) is never
|
|
97
|
+
// auto-allowed.
|
|
98
|
+
if (!subj.names.length) continue;
|
|
99
|
+
const parts = subj.names.length === 1 ? [{ cmd: subj.cmd, names: subj.names }] : subj.names.map((n) => ({ cmd: n, names: [n] }));
|
|
100
|
+
if (parts.every((p) => s.allow.some((r) => ruleMatches(r, req, p)))) return { decision: 'allow', rule: 'allow rules', file: s.file };
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const hit = s.allow.find((r) => ruleMatches(r, req, subj));
|
|
104
|
+
if (hit) return { decision: 'allow', rule: hit, file: s.file };
|
|
105
|
+
}
|
|
106
|
+
return { decision: null };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { decide, wildcard };
|