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/tools.mjs
CHANGED
|
@@ -13,9 +13,10 @@ import fs from 'fs';
|
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import os from 'os';
|
|
15
15
|
import crypto from 'crypto';
|
|
16
|
-
import { exec } from 'child_process';
|
|
16
|
+
import { exec, execSync, spawn } from 'child_process';
|
|
17
17
|
import { createRequire } from 'module';
|
|
18
18
|
import { searchLibrary, listCategories, designLibraryConfigured, CATEGORY_LABELS } from './design-library/query.mjs';
|
|
19
|
+
import { design_direction, design_lint } from './design-tools.mjs';
|
|
19
20
|
|
|
20
21
|
const require = createRequire(import.meta.url);
|
|
21
22
|
const editEngine = require('./edit-engine.js');
|
|
@@ -24,6 +25,11 @@ const diagnostics = require('./diagnostics.js');
|
|
|
24
25
|
const { commandPatterns } = require('./arity.js');
|
|
25
26
|
const mcpLib = require('./mcp.js');
|
|
26
27
|
const reachLib = require('./reach.js');
|
|
28
|
+
const lifeLib = require('./life.js');
|
|
29
|
+
const brainLib = require('./brain.js');
|
|
30
|
+
const screenLib = require('./screen.js');
|
|
31
|
+
const computerLib = require('./computer.js');
|
|
32
|
+
const botMailLib = require('./bot-mail.js');
|
|
27
33
|
const applyLimit = require('./apply-limit.js');
|
|
28
34
|
const config = require('./config.js');
|
|
29
35
|
const skills = require('./skills.js');
|
|
@@ -288,6 +294,12 @@ function isPlanPath(p, cwd) {
|
|
|
288
294
|
return !outside && /^\.codeply\/plans\/[^/]+\.md$/i.test(rel);
|
|
289
295
|
}
|
|
290
296
|
|
|
297
|
+
// Design mode works in the design's own folder (its cwd) and writes there without
|
|
298
|
+
// asking: a design is a sandbox. Anything outside it is blocked in executeTool.
|
|
299
|
+
function isDesignWrite(p, ctx) {
|
|
300
|
+
return ctx.mode === 'Design' && !!p && !resolvePath(String(p), ctx.cwd).outside;
|
|
301
|
+
}
|
|
302
|
+
|
|
291
303
|
// ─── Tools ──────────────────────────────────────────────────────────────────
|
|
292
304
|
|
|
293
305
|
async function list_dir(args, ctx) {
|
|
@@ -385,7 +397,7 @@ async function write_file(args, ctx) {
|
|
|
385
397
|
const beforeLines = existed ? before.split('\n').length : 0;
|
|
386
398
|
const afterLines = after.split('\n').length;
|
|
387
399
|
|
|
388
|
-
const verdict = ctx.mode === 'Plan' && isPlanPath(target, ctx.cwd) ? 'allow' : await ctx.approve({
|
|
400
|
+
const verdict = (ctx.mode === 'Plan' && isPlanPath(target, ctx.cwd)) || isDesignWrite(target, ctx) ? 'allow' : await ctx.approve({
|
|
389
401
|
tool: 'write_file',
|
|
390
402
|
path: rel,
|
|
391
403
|
title: existed ? `Overwrite ${rel}` : `Create ${rel}`,
|
|
@@ -475,7 +487,7 @@ async function edit_file(args, ctx) {
|
|
|
475
487
|
const removed = args.search.split('\n').length * occurrences;
|
|
476
488
|
const added = args.replace.split('\n').length * occurrences;
|
|
477
489
|
|
|
478
|
-
const verdict = ctx.mode === 'Plan' && isPlanPath(target, ctx.cwd) ? 'allow' : await ctx.approve({
|
|
490
|
+
const verdict = (ctx.mode === 'Plan' && isPlanPath(target, ctx.cwd)) || isDesignWrite(target, ctx) ? 'allow' : await ctx.approve({
|
|
479
491
|
tool: 'edit_file',
|
|
480
492
|
path: rel,
|
|
481
493
|
title: `Edit ${rel}`,
|
|
@@ -638,7 +650,7 @@ async function fetch_image(args, ctx) {
|
|
|
638
650
|
// `path` rides alongside `detail` (the original url) so a host that wants to
|
|
639
651
|
// offer a real picker doesn't have to scrape the human-readable `title` text
|
|
640
652
|
// to know where the image is headed.
|
|
641
|
-
const verdict = await ctx.approve({
|
|
653
|
+
const verdict = isDesignWrite(target, ctx) ? 'allow' : await ctx.approve({
|
|
642
654
|
tool: 'fetch_image',
|
|
643
655
|
title: `Download image to ${rel}`,
|
|
644
656
|
detail: url,
|
|
@@ -732,7 +744,7 @@ function readVerdict(v) {
|
|
|
732
744
|
function applyEmailEdits(draft, edits) {
|
|
733
745
|
if (!edits) return draft;
|
|
734
746
|
const out = { ...draft };
|
|
735
|
-
for (const k of ['to', 'subject', 'body']) {
|
|
747
|
+
for (const k of ['to', 'cc', 'subject', 'body']) {
|
|
736
748
|
if (typeof edits[k] === 'string') out[k] = k === 'body' ? edits[k].slice(0, 100000) : edits[k].trim().slice(0, 1000);
|
|
737
749
|
}
|
|
738
750
|
return out;
|
|
@@ -746,11 +758,11 @@ const GMAIL_NOT_CONNECTED = 'Gmail is not connected. Ask the user to connect it
|
|
|
746
758
|
function draftsDir() { return path.join(os.homedir(), '.codeply', 'drafts'); }
|
|
747
759
|
const DRAFT_ID = /^d[a-z0-9]{6,30}$/;
|
|
748
760
|
|
|
749
|
-
function saveLocalDraft({ to, subject, body }) {
|
|
761
|
+
function saveLocalDraft({ to, cc, subject, body }) {
|
|
750
762
|
const dir = draftsDir();
|
|
751
763
|
fs.mkdirSync(dir, { recursive: true });
|
|
752
764
|
const id = `d${Date.now().toString(36)}${crypto.randomBytes(3).toString('hex')}`;
|
|
753
|
-
const draft = { id, to: to || '', subject: subject || '', body: body || '', createdAt: new Date().toISOString() };
|
|
765
|
+
const draft = { id, to: to || '', cc: cc || '', subject: subject || '', body: body || '', createdAt: new Date().toISOString() };
|
|
754
766
|
fs.writeFileSync(path.join(dir, `${id}.json`), JSON.stringify(draft, null, 2));
|
|
755
767
|
return draft;
|
|
756
768
|
}
|
|
@@ -786,7 +798,7 @@ async function storeDraft(draft, token) {
|
|
|
786
798
|
const r = await oauth.gmailCreateDraft(token, draft);
|
|
787
799
|
return { ok: true, output: `Draft saved in Gmail's Drafts folder${draft.to ? ` (to ${draft.to})` : ''}, Gmail draft id ${r.id}. Nothing was sent.`, meta: { label: `draft: ${draft.subject || '(no subject)'}` } };
|
|
788
800
|
} catch (e) {
|
|
789
|
-
if (!e.scopeMissing) return { ok: false, output:
|
|
801
|
+
if (!e.scopeMissing) return { ok: false, output: googleFailure('Gmail draft', 'email', e) };
|
|
790
802
|
why = 'Gmail was connected before drafts were allowed, so it was saved in Codeply\'s drafts instead. Reconnect Gmail in Connect Apps to allow drafts in Gmail.';
|
|
791
803
|
}
|
|
792
804
|
}
|
|
@@ -803,6 +815,7 @@ async function storeDraft(draft, token) {
|
|
|
803
815
|
|
|
804
816
|
async function gmail_send(args, ctx) {
|
|
805
817
|
let to = (args.to || '').trim();
|
|
818
|
+
let cc = (args.cc || '').trim();
|
|
806
819
|
let subject = (args.subject || '').trim();
|
|
807
820
|
let body = args.body || '';
|
|
808
821
|
const draftId = String(args.draft || '').trim();
|
|
@@ -810,7 +823,7 @@ async function gmail_send(args, ctx) {
|
|
|
810
823
|
if (draftId) {
|
|
811
824
|
saved = readLocalDraft(draftId);
|
|
812
825
|
if (!saved) return { ok: false, output: `No saved draft with id "${draftId}". drafts_list shows the saved drafts.` };
|
|
813
|
-
to = to || saved.to; subject = subject || saved.subject; body = body || saved.body;
|
|
826
|
+
to = to || saved.to; cc = cc || saved.cc || ''; subject = subject || saved.subject; body = body || saved.body;
|
|
814
827
|
}
|
|
815
828
|
if (!to) return { ok: false, output: 'gmail_send needs a <to> address.' };
|
|
816
829
|
if (!subject) return { ok: false, output: 'gmail_send needs a <subject>.' };
|
|
@@ -821,15 +834,15 @@ async function gmail_send(args, ctx) {
|
|
|
821
834
|
|
|
822
835
|
const verdict = readVerdict(await ctx.approve({
|
|
823
836
|
tool: 'gmail_send',
|
|
824
|
-
title: `Send email to ${to}`,
|
|
825
|
-
detail: `Subject: ${subject}\n\n${body}`,
|
|
837
|
+
title: `Send email to ${to}${cc ? ` (cc ${cc})` : ''}`,
|
|
838
|
+
detail: `${cc ? `Cc: ${cc}\n` : ''}Subject: ${subject}\n\n${body}`,
|
|
826
839
|
danger: false,
|
|
827
840
|
// The card shows these as editable fields; edits come back with the verdict.
|
|
828
|
-
draft: { to, subject, body },
|
|
841
|
+
draft: { to, cc, subject, body },
|
|
829
842
|
}));
|
|
830
843
|
if (verdict.action === 'reject') return { ok: false, output: `User declined to send the email to ${to}.`, meta: { rejected: true } };
|
|
831
|
-
const email = applyEmailEdits({ to, subject, body }, verdict.edits);
|
|
832
|
-
const edited = !!verdict.edits && (email.to !== to || email.subject !== subject || email.body !== body);
|
|
844
|
+
const email = applyEmailEdits({ to, cc, subject, body }, verdict.edits);
|
|
845
|
+
const edited = !!verdict.edits && (email.to !== to || email.cc !== cc || email.subject !== subject || email.body !== body);
|
|
833
846
|
if (verdict.action === 'draft') {
|
|
834
847
|
const r = await storeDraft(email, token);
|
|
835
848
|
return { ...r, output: `The user chose to save it as a draft instead of sending. ${r.output}` };
|
|
@@ -839,15 +852,17 @@ async function gmail_send(args, ctx) {
|
|
|
839
852
|
try {
|
|
840
853
|
const result = await oauth.gmailSend(token, email);
|
|
841
854
|
if (saved) { try { fs.unlinkSync(path.join(draftsDir(), `${saved.id}.json`)); } catch {} }
|
|
842
|
-
const note = edited ? ` The user edited it before sending; what went out: to ${email.to}, subject "${email.subject}".` : '';
|
|
843
|
-
|
|
855
|
+
const note = edited ? ` The user edited it before sending; what went out: to ${email.to}${email.cc ? `, cc ${email.cc}` : ''}, subject "${email.subject}".` : '';
|
|
856
|
+
// Whoever this answers is no longer waiting on the user (the life context).
|
|
857
|
+
try { lifeLib.noteSent([email.to, email.cc].filter(Boolean).join(',')); } catch {}
|
|
858
|
+
return { ok: true, output: `Email sent to ${email.to}${email.cc ? `, cc ${email.cc}` : ''} (message id ${result.id}).${note}`, meta: { label: `to ${email.to}: ${email.subject}` } };
|
|
844
859
|
} catch (e) {
|
|
845
|
-
return { ok: false, output:
|
|
860
|
+
return { ok: false, output: googleFailure('Gmail send', 'email', e) };
|
|
846
861
|
}
|
|
847
862
|
}
|
|
848
863
|
|
|
849
864
|
async function gmail_draft(args, ctx) {
|
|
850
|
-
const draft = { to: (args.to || '').trim(), subject: (args.subject || '').trim(), body: args.body || '' };
|
|
865
|
+
const draft = { to: (args.to || '').trim(), cc: (args.cc || '').trim(), subject: (args.subject || '').trim(), body: args.body || '' };
|
|
851
866
|
if (!draft.subject && !draft.body.trim()) return { ok: false, output: 'gmail_draft needs at least a <subject> or a <body>.' };
|
|
852
867
|
|
|
853
868
|
let token = null;
|
|
@@ -856,7 +871,7 @@ async function gmail_draft(args, ctx) {
|
|
|
856
871
|
const verdict = readVerdict(await ctx.approve({
|
|
857
872
|
tool: 'gmail_draft',
|
|
858
873
|
title: `Save a draft${draft.to ? ` to ${draft.to}` : ''}${token ? ' in Gmail' : ''}`,
|
|
859
|
-
detail: `Subject: ${draft.subject}\n\n${draft.body}`,
|
|
874
|
+
detail: `${draft.cc ? `Cc: ${draft.cc}\n` : ''}Subject: ${draft.subject}\n\n${draft.body}`,
|
|
860
875
|
danger: false,
|
|
861
876
|
draft,
|
|
862
877
|
draftOnly: true,
|
|
@@ -924,7 +939,23 @@ function eventLine(ev) {
|
|
|
924
939
|
const e = new Date(ev.end && ev.end.dateTime);
|
|
925
940
|
when = Number.isNaN(s.getTime()) ? '(no time)' : `${showLocal(s)} to ${Number.isNaN(e.getTime()) ? '?' : (ymd(e) === ymd(s) ? `${pad2(e.getHours())}:${pad2(e.getMinutes())}` : showLocal(e))}`;
|
|
926
941
|
}
|
|
927
|
-
|
|
942
|
+
const guests = (ev.attendees || []).filter((a) => a && a.email && !a.self).map((a) => a.email);
|
|
943
|
+
return `- ${ev.id ? `[${ev.id}] ` : ''}${title}: ${when}${ev.location ? `, at ${ev.location}` : ''}${guests.length ? ` (with ${guests.slice(0, 6).join(', ')})` : ''}`;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* What a failed Google call tells the model. Switched off on Codeply's side
|
|
948
|
+
* (an API not enabled in the Cloud project behind the app): the user can't fix
|
|
949
|
+
* that, so the bot just says it can't reach it right now, with no links, error
|
|
950
|
+
* codes or setup steps. Anything the user can fix (sign in again, allow
|
|
951
|
+
* access) is passed on in plain words.
|
|
952
|
+
*/
|
|
953
|
+
function googleFailure(what, thing, e, extra = '') {
|
|
954
|
+
if (e && e.serviceSide) {
|
|
955
|
+
console.warn(`[google] ${what}: ${e.message}`); // for whoever runs Codeply: enable the API in that Cloud project
|
|
956
|
+
return `${what} is not available in Codeply right now. This is a setup problem on Codeply's side, not something the user can fix. Tell the user in one short, friendly sentence that you can't reach their ${thing} right now. Do not give links, error codes, project numbers or setup steps. Then still help with what they asked, another way.`;
|
|
957
|
+
}
|
|
958
|
+
return `${what} failed: ${e.message}${extra} Tell the user briefly, in plain words, what went wrong and what they can do about it; no raw error codes or links.`;
|
|
928
959
|
}
|
|
929
960
|
|
|
930
961
|
async function calendar_list(args) {
|
|
@@ -947,7 +978,7 @@ async function calendar_list(args) {
|
|
|
947
978
|
if (!items.length) return { ok: true, output: `No events from ${range} (local time).`, meta: { label: 'no events' } };
|
|
948
979
|
return { ok: true, output: `${items.length} event(s) from ${range} (local time):\n${items.map(eventLine).join('\n')}`, meta: { label: `${items.length} event(s)`, count: items.length } };
|
|
949
980
|
} catch (e) {
|
|
950
|
-
return { ok: false, output:
|
|
981
|
+
return { ok: false, output: googleFailure('Calendar list', 'calendar', e) };
|
|
951
982
|
}
|
|
952
983
|
}
|
|
953
984
|
|
|
@@ -961,6 +992,8 @@ async function calendar_add(args, ctx) {
|
|
|
961
992
|
const event = { summary: title };
|
|
962
993
|
if (args.description) event.description = String(args.description);
|
|
963
994
|
if (args.location) event.location = String(args.location).trim();
|
|
995
|
+
const guests = String(args.attendees || args.guests || '').split(/[\s,;]+/).map((x) => x.trim()).filter((x) => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(x)).slice(0, 30);
|
|
996
|
+
if (guests.length) event.attendees = guests.map((email) => ({ email }));
|
|
964
997
|
let when;
|
|
965
998
|
if (start.allDay && (!end || end.allDay)) {
|
|
966
999
|
// All-day: Google's end date is exclusive, so a one-day event ends the next day.
|
|
@@ -987,14 +1020,66 @@ async function calendar_add(args, ctx) {
|
|
|
987
1020
|
try { t = await googleTokenFor('Google Calendar'); } catch (e) { return { ok: false, output: `Calendar add failed: ${e.message}` }; }
|
|
988
1021
|
if (t.error) return { ok: false, output: t.error };
|
|
989
1022
|
|
|
990
|
-
const detail = [`When: ${when} (local time)`, event.location ? `Where: ${event.location}` : '', mins.length ? `Reminders: ${mins.map((m) => `${m} min before`).join(', ')}` : '', event.description ? `\n${event.description}` : ''].filter(Boolean).join('\n');
|
|
1023
|
+
const detail = [`When: ${when} (local time)`, event.location ? `Where: ${event.location}` : '', guests.length ? `Guests (they get an invite): ${guests.join(', ')}` : '', mins.length ? `Reminders: ${mins.map((m) => `${m} min before`).join(', ')}` : '', event.description ? `\n${event.description}` : ''].filter(Boolean).join('\n');
|
|
991
1024
|
const verdict = readVerdict(await ctx.approve({ tool: 'calendar_add', title: `Add "${title}" to your calendar`, detail, danger: false }));
|
|
992
1025
|
if (verdict.action === 'reject') return { ok: false, output: `User declined to add "${title}" to the calendar.`, meta: { rejected: true } };
|
|
993
1026
|
try {
|
|
994
1027
|
const r = await oauth.calendarInsertEvent(t.token, event);
|
|
995
|
-
return { ok: true, output: `Added "${title}" to the calendar: ${when} (local time)${event.location ? `, at ${event.location}` : ''}.${r.htmlLink ? ` Link: ${r.htmlLink}` : ''}`, meta: { label: `${title}: ${when}` } };
|
|
1028
|
+
return { ok: true, output: `Added "${title}" to the calendar: ${when} (local time)${event.location ? `, at ${event.location}` : ''}${guests.length ? `; invites sent to ${guests.join(', ')}` : ''}. Event id ${r.id}.${r.htmlLink ? ` Link: ${r.htmlLink}` : ''}`, meta: { label: `${title}: ${when}` } };
|
|
1029
|
+
} catch (e) {
|
|
1030
|
+
return { ok: false, output: googleFailure('Calendar add', 'calendar', e) };
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/** Move, rename or cancel one event (its id from calendar_list). Asks first; guests are told. */
|
|
1035
|
+
async function calendar_update(args, ctx) {
|
|
1036
|
+
const id = String(args.id || '').trim().replace(/^\[|\]$/g, '');
|
|
1037
|
+
if (!id) return { ok: false, output: 'calendar_update needs the event <id> from calendar_list.' };
|
|
1038
|
+
const cancel = /^(yes|true|1|cancel)$/i.test(String(args.cancel || '').trim());
|
|
1039
|
+
const patch = {};
|
|
1040
|
+
if (args.title) patch.summary = String(args.title).trim();
|
|
1041
|
+
if (args.location) patch.location = String(args.location).trim();
|
|
1042
|
+
if (args.description) patch.description = String(args.description);
|
|
1043
|
+
let when = '';
|
|
1044
|
+
if (args.start) {
|
|
1045
|
+
const start = parseWhen(args.start);
|
|
1046
|
+
if (!start || start.allDay) return { ok: false, output: `calendar_update needs a <start> with a time (got "${args.start}"), like 2026-10-03T14:00.` };
|
|
1047
|
+
const end = args.end ? parseWhen(args.end) : null;
|
|
1048
|
+
const minutes = Number(args.minutes) || 60;
|
|
1049
|
+
const e = end && !end.allDay ? end.date : new Date(start.date.getTime() + minutes * 60000);
|
|
1050
|
+
if (e <= start.date) return { ok: false, output: 'calendar_update: <end> must be after <start>.' };
|
|
1051
|
+
let tz = ''; try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch {}
|
|
1052
|
+
patch.start = { dateTime: localIso(start.date), ...(tz ? { timeZone: tz } : {}) };
|
|
1053
|
+
patch.end = { dateTime: localIso(e), ...(tz ? { timeZone: tz } : {}) };
|
|
1054
|
+
when = `${showLocal(start.date)} to ${pad2(e.getHours())}:${pad2(e.getMinutes())}`;
|
|
1055
|
+
}
|
|
1056
|
+
if (!cancel && !Object.keys(patch).length) return { ok: false, output: 'calendar_update: say what changes (<start>, <title>, <location>), or <cancel>yes</cancel>.' };
|
|
1057
|
+
let t;
|
|
1058
|
+
try { t = await googleTokenFor('Google Calendar'); } catch (e) { return { ok: false, output: `Calendar update failed: ${e.message}` }; }
|
|
1059
|
+
if (t.error) return { ok: false, output: t.error };
|
|
1060
|
+
const title = cancel ? `Cancel the event ${id} on your calendar` : `Change the event on your calendar${patch.summary ? ` to "${patch.summary}"` : ''}${when ? `: ${when}` : ''}`;
|
|
1061
|
+
const verdict = readVerdict(await ctx.approve({ tool: 'calendar_update', title, detail: cancel ? 'The event is removed; any guests are told.' : [when ? `When: ${when} (local time)` : '', patch.location ? `Where: ${patch.location}` : '', 'Guests are told about the change.'].filter(Boolean).join('\n'), danger: false }));
|
|
1062
|
+
if (verdict.action === 'reject') return { ok: false, output: 'User declined the calendar change.', meta: { rejected: true } };
|
|
1063
|
+
try {
|
|
1064
|
+
if (cancel) { await oauth.calendarDeleteEvent(t.token, id); return { ok: true, output: `Cancelled event ${id}.`, meta: { label: `cancelled ${id}` } }; }
|
|
1065
|
+
const r = await oauth.calendarPatchEvent(t.token, id, patch);
|
|
1066
|
+
return { ok: true, output: `Updated "${r.summary || patch.summary || id}"${when ? `: now ${when} (local time)` : ''}.`, meta: { label: `${r.summary || id}${when ? `: ${when}` : ''}` } };
|
|
1067
|
+
} catch (e) {
|
|
1068
|
+
return { ok: false, output: googleFailure('Calendar update', 'calendar', e) };
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
async function gmail_read(args) {
|
|
1073
|
+
const id = String(args.id || '').trim().replace(/^\[|\]$/g, '');
|
|
1074
|
+
if (!id) return { ok: false, output: 'gmail_read needs the <id> of an email, from gmail_search.' };
|
|
1075
|
+
try {
|
|
1076
|
+
const token = await getValidGmailToken();
|
|
1077
|
+
if (!token) return { ok: false, output: GMAIL_NOT_CONNECTED };
|
|
1078
|
+
const m = await oauth.gmailGetMessage(token, id);
|
|
1079
|
+
const body = String(m.body || '').replace(/\r/g, '').replace(/\n{3,}/g, '\n\n').trim();
|
|
1080
|
+
return { ok: true, output: truncate(`From: ${m.from}\nTo: ${m.to}${m.cc ? `\nCc: ${m.cc}` : ''}\nDate: ${m.date}\nSubject: ${m.subject}\nThread: ${m.threadId}\n\n${body || '(empty)'}`), meta: { label: m.subject || id } };
|
|
996
1081
|
} catch (e) {
|
|
997
|
-
return { ok: false, output:
|
|
1082
|
+
return { ok: false, output: googleFailure('Gmail read', 'email', e) };
|
|
998
1083
|
}
|
|
999
1084
|
}
|
|
1000
1085
|
|
|
@@ -1016,10 +1101,10 @@ async function gmail_search(args, ctx) {
|
|
|
1016
1101
|
results = await oauth.gmailSearch(token, query);
|
|
1017
1102
|
}
|
|
1018
1103
|
if (!results.length) return { ok: true, output: query ? `No messages matched "${query}".` : 'The mailbox is empty.', meta: { label } };
|
|
1019
|
-
const lines = results.map((m) => `- ${m.subject || '(no subject)'} - from ${m.from} - ${m.date}\n ${m.snippet}`);
|
|
1020
|
-
return { ok: true, output: `${results.length} message(s)${query ? ` matching "${query}"` : ', newest first'}:\n\n${lines.join('\n')}`, meta: { label, count: results.length } };
|
|
1104
|
+
const lines = results.map((m) => `- [${m.id}] ${m.subject || '(no subject)'} - from ${m.from} - ${m.date}\n ${m.snippet}`);
|
|
1105
|
+
return { ok: true, output: `${results.length} message(s)${query ? ` matching "${query}"` : ', newest first'}:\n\n${lines.join('\n')}\n\n(These are previews. gmail_read with the [id] reads a whole email.)`, meta: { label, count: results.length } };
|
|
1021
1106
|
} catch (e) {
|
|
1022
|
-
return { ok: false, output:
|
|
1107
|
+
return { ok: false, output: googleFailure('Gmail search', 'email', e, ' Do not retry with other queries.') };
|
|
1023
1108
|
}
|
|
1024
1109
|
}
|
|
1025
1110
|
|
|
@@ -1057,10 +1142,16 @@ async function slack_post_message(args, ctx) {
|
|
|
1057
1142
|
// Channel lookup always uses the bot token - channels:read is a bot-only
|
|
1058
1143
|
// scope in this app's setup regardless of which token ends up posting.
|
|
1059
1144
|
const channelId = await resolveSlackChannel(slack.accessToken, channelInput);
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1145
|
+
// A bot posts as itself (the app's bot token, with the bot's name), never
|
|
1146
|
+
// as the user. The plain agent posts with the user token when connected -
|
|
1147
|
+
// messages read as coming from the actual person, not the "Codeply Craft
|
|
1148
|
+
// APP" bot - and falls back to the bot token for anyone who connected
|
|
1149
|
+
// before user-token support existed, or who only approved the bot scopes.
|
|
1150
|
+
const bot = ctx.botId ? require('./bots.js').getBot(ctx.botId) : null;
|
|
1151
|
+
if (bot) {
|
|
1152
|
+
const r = await oauth.slackPostMessage(slack.accessToken, { channel: channelId, text }, { asName: `${bot.name} (assistant)` });
|
|
1153
|
+
return { ok: true, output: `Posted to ${channelInput} as ${bot.name}${r.named ? '' : ' (with the Codeply app\'s name: reconnect Slack in Connect Apps so bots can post under their own names)'}.`, meta: { label: `#${channelInput.replace(/^#/, '')} as ${bot.name}` } };
|
|
1154
|
+
}
|
|
1064
1155
|
const useUserToken = !!slack.userAccessToken;
|
|
1065
1156
|
const postToken = useUserToken ? slack.userAccessToken : slack.accessToken;
|
|
1066
1157
|
await oauth.slackPostMessage(postToken, { channel: channelId, text }, { isUserToken: useUserToken });
|
|
@@ -1539,6 +1630,20 @@ async function run(args, ctx) {
|
|
|
1539
1630
|
});
|
|
1540
1631
|
if (verdict === 'reject') return { ok: false, output: `User declined to run: ${displayCommand}`, meta: { rejected: true } };
|
|
1541
1632
|
|
|
1633
|
+
if (/^(1|true|yes)$/i.test(String(args.background || '').trim())) {
|
|
1634
|
+
if (READ_ONLY_MODES.has(ctx.mode)) return { ok: false, output: `Background commands are not available in ${ctx.mode} mode.` };
|
|
1635
|
+
const j = startJob(displayCommand, execCommand, ctx.cwd);
|
|
1636
|
+
// A few seconds so an instant failure or a "listening on :3000" line comes back now.
|
|
1637
|
+
await new Promise((r) => { const t = setTimeout(r, 4000); j.child.once('close', () => { clearTimeout(t); r(); }); });
|
|
1638
|
+
const state = j.endedAt ? `exited with code ${j.code}` : 'still running in the background';
|
|
1639
|
+
const tail = j.out.trim() ? truncate(j.out.trimEnd(), 3000, { keep: 'ends', spill: false }) : '(no output yet)';
|
|
1640
|
+
return {
|
|
1641
|
+
ok: true,
|
|
1642
|
+
output: `$ ${displayCommand}\n[background job ${j.id}: ${state}]\n${tail}\n\nRead its output later with <codeply:job><id>${j.id}</id></codeply:job>, stop it with <stop>true</stop> in the same block. Do not start it again.`,
|
|
1643
|
+
meta: { label: displayCommand, background: true, jobId: j.id, output: j.out.slice(-JOB_SHOWN), ...(j.endedAt ? { exitCode: j.code } : {}) },
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1542
1647
|
return new Promise((resolve) => {
|
|
1543
1648
|
exec(execCommand, { cwd: ctx.cwd, timeout: RUN_TIMEOUT_MS, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
|
|
1544
1649
|
(err, stdout, stderr) => {
|
|
@@ -1559,12 +1664,84 @@ async function run(args, ctx) {
|
|
|
1559
1664
|
resolve({
|
|
1560
1665
|
ok: true, // a non-zero exit is a real result the model must see, not a tool failure
|
|
1561
1666
|
output: truncate(`$ ${displayCommand}\n[exit ${code}]\n${body}`, MAX_TOOL_OUTPUT, { keep: 'ends' }) + (hint ? `\n[hint] ${hint}` : ''),
|
|
1562
|
-
|
|
1667
|
+
// What the user sees when they open the row: the end of the real output.
|
|
1668
|
+
meta: { label: displayCommand, exitCode: effectiveCode, output: body.slice(-JOB_SHOWN), ...(effectiveCode !== code ? { maskedFailure: true } : {}) },
|
|
1563
1669
|
});
|
|
1564
1670
|
});
|
|
1565
1671
|
});
|
|
1566
1672
|
}
|
|
1567
1673
|
|
|
1674
|
+
// ─── Background commands: dev servers, watchers, long builds ─────────────────
|
|
1675
|
+
// run with <background>true</background> starts the command and answers at once;
|
|
1676
|
+
// job reads its output later or stops it. Jobs live as long as the app does,
|
|
1677
|
+
// and the host (Craft) lists them under the composer through backgroundJobs.
|
|
1678
|
+
const jobs = new Map(); // id -> { id, command, cwd, child, out, startedAt, endedAt, code }
|
|
1679
|
+
const jobListeners = new Set();
|
|
1680
|
+
const JOB_KEEP = 64 * 1024; // output kept per job
|
|
1681
|
+
const JOB_SHOWN = 4000; // output shown on a chat row
|
|
1682
|
+
let jobSeq = 0;
|
|
1683
|
+
|
|
1684
|
+
const jobCard = (j) => ({ id: j.id, command: j.command, cwd: j.cwd, startedAt: j.startedAt, endedAt: j.endedAt, code: j.code, running: !j.endedAt });
|
|
1685
|
+
function jobChanged(j) { for (const fn of jobListeners) { try { fn(jobCard(j)); } catch {} } }
|
|
1686
|
+
|
|
1687
|
+
function startJob(command, execCommand, cwd) {
|
|
1688
|
+
const win = process.platform === 'win32';
|
|
1689
|
+
// detached on POSIX so the whole process group can be stopped; Windows uses taskkill /T.
|
|
1690
|
+
const child = spawn(execCommand, { cwd, shell: true, windowsHide: true, detached: !win });
|
|
1691
|
+
const j = { id: String(++jobSeq), command, cwd, child, out: '', startedAt: Date.now(), endedAt: 0, code: null };
|
|
1692
|
+
const add = (d) => { j.out += d.toString(); if (j.out.length > JOB_KEEP) j.out = j.out.slice(-JOB_KEEP); };
|
|
1693
|
+
child.stdout.on('data', add);
|
|
1694
|
+
child.stderr.on('data', add);
|
|
1695
|
+
child.on('error', (e) => add(`\n[error] ${e.message}\n`));
|
|
1696
|
+
child.on('close', (code) => { j.code = code == null ? 1 : code; j.endedAt = Date.now(); jobChanged(j); });
|
|
1697
|
+
jobs.set(j.id, j);
|
|
1698
|
+
jobChanged(j);
|
|
1699
|
+
return j;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function killJob(j, { sync = false } = {}) {
|
|
1703
|
+
if (j.endedAt) return;
|
|
1704
|
+
try {
|
|
1705
|
+
if (process.platform === 'win32') {
|
|
1706
|
+
const cmd = `taskkill /pid ${j.child.pid} /T /F`;
|
|
1707
|
+
if (sync) execSync(cmd, { windowsHide: true, stdio: 'ignore' }); else exec(cmd, { windowsHide: true }, () => {});
|
|
1708
|
+
} else process.kill(-j.child.pid, 'SIGTERM');
|
|
1709
|
+
} catch { try { j.child.kill(); } catch {} }
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
function jobLine(j) {
|
|
1713
|
+
const secs = Math.round(((j.endedAt || Date.now()) - j.startedAt) / 1000);
|
|
1714
|
+
return `[job ${j.id}] ${j.endedAt ? `exited with code ${j.code} after ${secs}s` : `running for ${secs}s`}: ${j.command}`;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
async function job(args) {
|
|
1718
|
+
const id = String(args.id || '').trim().replace(/^#/, '');
|
|
1719
|
+
if (!id) {
|
|
1720
|
+
if (!jobs.size) return { ok: true, output: 'No background jobs.', meta: { label: 'no background jobs' } };
|
|
1721
|
+
return { ok: true, output: [...jobs.values()].map(jobLine).join('\n'), meta: { label: `${jobs.size} background job${jobs.size === 1 ? '' : 's'}` } };
|
|
1722
|
+
}
|
|
1723
|
+
const j = jobs.get(id);
|
|
1724
|
+
if (!j) return { ok: false, output: `No background job ${id}. Jobs: ${[...jobs.keys()].join(', ') || 'none'}.` };
|
|
1725
|
+
if (/^(1|true|yes)$/i.test(String(args.stop || '').trim())) {
|
|
1726
|
+
const wasRunning = !j.endedAt;
|
|
1727
|
+
killJob(j);
|
|
1728
|
+
if (wasRunning) await new Promise((r) => { const t = setTimeout(r, 2000); j.child.once('close', () => { clearTimeout(t); r(); }); });
|
|
1729
|
+
return { ok: true, output: wasRunning ? `Stopped job ${id}: ${j.command}` : `Job ${id} had already ${jobLine(j).split(': ')[0].replace(/^\[job \d+\] /, '')}.`, meta: { label: `stopped: ${j.command}` } };
|
|
1730
|
+
}
|
|
1731
|
+
const out = j.out.trimEnd() || '(no output yet)';
|
|
1732
|
+
return { ok: true, output: `${jobLine(j)}\n${truncate(out, MAX_TOOL_OUTPUT, { keep: 'ends' })}`, meta: { label: j.command, output: out.slice(-JOB_SHOWN), ...(j.endedAt ? { exitCode: j.code } : {}) } };
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
export const backgroundJobs = {
|
|
1736
|
+
list: () => [...jobs.values()].map(jobCard),
|
|
1737
|
+
output: (id) => (jobs.get(String(id)) || {}).out || '',
|
|
1738
|
+
stop: (id) => { const j = jobs.get(String(id)); if (j) killJob(j); return !!j; },
|
|
1739
|
+
/** Forget finished jobs (the list under the composer clears them). */
|
|
1740
|
+
clear: () => { for (const [id, j] of jobs) if (j.endedAt) jobs.delete(id); },
|
|
1741
|
+
stopAll: () => { for (const j of jobs.values()) killJob(j, { sync: true }); },
|
|
1742
|
+
onChange: (fn) => { jobListeners.add(fn); return () => jobListeners.delete(fn); },
|
|
1743
|
+
};
|
|
1744
|
+
|
|
1568
1745
|
/**
|
|
1569
1746
|
* Load one skill's full instructions on demand. Read-only, no approval - the
|
|
1570
1747
|
* whole point of the skill index living in the system prompt (see
|
|
@@ -2115,7 +2292,7 @@ async function apply_patch(args, ctx) {
|
|
|
2115
2292
|
.filter((l) => l.startsWith(sign) && !l.startsWith(sign.repeat(3) + ' '))
|
|
2116
2293
|
.map((l) => l.slice(1)).join('\n').slice(0, 4000);
|
|
2117
2294
|
|
|
2118
|
-
const verdict = await ctx.approve({
|
|
2295
|
+
const verdict = ctx.mode === 'Design' && !danger ? 'allow' : await ctx.approve({
|
|
2119
2296
|
tool: 'apply_patch',
|
|
2120
2297
|
title: plan.changes.length === 1 ? `Patch ${label(plan.changes[0])}` : `Apply patch (${plan.changes.length} files)`,
|
|
2121
2298
|
detail,
|
|
@@ -2237,8 +2414,86 @@ async function ask_bot(args, ctx) {
|
|
|
2237
2414
|
}
|
|
2238
2415
|
|
|
2239
2416
|
// ─── Reaching the user's phone (lib/reach.js; the host registers the handler) ─
|
|
2240
|
-
|
|
2241
|
-
|
|
2417
|
+
// A reminder at a time also lands on the user's Google Calendar (when connected),
|
|
2418
|
+
// so the day's plan shows it without a second ask. No guests, so no approval.
|
|
2419
|
+
async function reach_me(args, ctx) {
|
|
2420
|
+
const r = await reachLib.reachMe(args, ctx);
|
|
2421
|
+
const at = r.ok ? reachLib.parseWhen(args.at) : null;
|
|
2422
|
+
if (!at) return r;
|
|
2423
|
+
try {
|
|
2424
|
+
const t = await googleTokenFor('Google Calendar');
|
|
2425
|
+
if (t.error) return r;
|
|
2426
|
+
let tz = '';
|
|
2427
|
+
try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch {}
|
|
2428
|
+
const title = String(args.message || '').trim().replace(/\s+/g, ' ').slice(0, 80);
|
|
2429
|
+
const end = new Date(at.getTime() + 30 * 60 * 1000);
|
|
2430
|
+
await oauth.calendarInsertEvent(t.token, {
|
|
2431
|
+
summary: title,
|
|
2432
|
+
description: 'Set as a phone reminder in Codeply.',
|
|
2433
|
+
start: { dateTime: localIso(at), ...(tz ? { timeZone: tz } : {}) },
|
|
2434
|
+
end: { dateTime: localIso(end), ...(tz ? { timeZone: tz } : {}) },
|
|
2435
|
+
reminders: { useDefault: false, overrides: [{ method: 'popup', minutes: 10 }] },
|
|
2436
|
+
});
|
|
2437
|
+
return { ...r, output: `${r.output} It is also on the user's Google Calendar at ${showLocal(at)} with a 10 minute reminder, so do not add it again.`, meta: { ...(r.meta || {}), label: `${(r.meta && r.meta.label) || 'Reminder'} (also on your calendar)` } };
|
|
2438
|
+
} catch { return r; }
|
|
2439
|
+
}
|
|
2440
|
+
const watch_email =(args, ctx) => reachLib.watchEmail(args, ctx);
|
|
2441
|
+
|
|
2442
|
+
// ─── The user's life context (lib/life.js; the host attaches the store) ──────
|
|
2443
|
+
// A bot reads only the sources its Life settings allow; the plain agent reads the non-sensitive ones.
|
|
2444
|
+
function life_context(args, ctx) {
|
|
2445
|
+
const bot = ctx.botId ? require('./bots.js').getBot(ctx.botId) : null;
|
|
2446
|
+
return lifeLib.contextTool(args, { bot });
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// ─── The shared brain (lib/brain.js): one memory for every app and bot ────────
|
|
2450
|
+
const remember = (args, ctx) => brainLib.rememberTool(args, ctx);
|
|
2451
|
+
const recall = (args, ctx) => brainLib.recallTool(args, ctx);
|
|
2452
|
+
|
|
2453
|
+
// Pass a job to the agent whose domain it is, and carry on (no waiting, unlike
|
|
2454
|
+
// ask_bot). The host provides ctx.handOff, which knows who asked for this run.
|
|
2455
|
+
async function hand_off(args, ctx) {
|
|
2456
|
+
if (typeof ctx.handOff !== 'function') return { ok: false, output: 'hand_off is not available here. Do the work yourself, or ask a teammate with ask_bot.' };
|
|
2457
|
+
if (!String(args.intent || '').trim()) return { ok: false, output: 'hand_off needs <intent>: what the other agent should get done.' };
|
|
2458
|
+
return ctx.handOff({ to: String(args.to || '').trim(), intent: String(args.intent || '').trim(), facts: args.facts, decision: args.decision });
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
// ─── The user's screen (lib/screen.js; only a desktop host can look) ──────────
|
|
2462
|
+
// A picture of the screen and its text, for "this", "her message", "what's on my right".
|
|
2463
|
+
const look_at_screen = (args, ctx) => screenLib.lookTool(ctx, args);
|
|
2464
|
+
const use_computer = (args, ctx) => computerLib.tool(args, ctx);
|
|
2465
|
+
|
|
2466
|
+
// ─── A bot's own email (lib/bot-mail.js; the host sends through Codeply's server) ─
|
|
2467
|
+
// The same editable card as gmail_send: what is left on it is what goes out.
|
|
2468
|
+
async function bot_email_send(args, ctx) {
|
|
2469
|
+
const bot = ctx.botId ? require('./bots.js').getBot(ctx.botId) : null;
|
|
2470
|
+
if (!bot) return { ok: false, output: 'Only a bot with its own address can email as itself. Use gmail_send to send from the user\'s Gmail.' };
|
|
2471
|
+
const from = botMailLib.addressOf(bot.id);
|
|
2472
|
+
if (!from) return { ok: false, output: `${bot.name} has no email address yet. The user can give it one in Crew: Edit bot, Email.` };
|
|
2473
|
+
const thread = args.reply_to ? await botMailLib.threadOf(args.reply_to) : null;
|
|
2474
|
+
if (args.reply_to && !thread) return { ok: false, output: `No received email with id "${args.reply_to}". Leave reply_to empty to start a new email.` };
|
|
2475
|
+
const to = String(args.to || (thread && thread.to) || '').trim();
|
|
2476
|
+
const subject = String(args.subject || (thread && thread.subject) || '').trim();
|
|
2477
|
+
const body = String(args.body || '');
|
|
2478
|
+
if (!to) return { ok: false, output: 'bot_email_send needs a <to> address.' };
|
|
2479
|
+
if (!body.trim()) return { ok: false, output: 'bot_email_send needs a <body>.' };
|
|
2480
|
+
const verdict = readVerdict(await ctx.approve({
|
|
2481
|
+
tool: 'bot_email_send',
|
|
2482
|
+
title: `Email ${to} as ${bot.name} (${from})`,
|
|
2483
|
+
detail: `Subject: ${subject || '(no subject)'}\n\n${body}`,
|
|
2484
|
+
danger: false,
|
|
2485
|
+
draft: { to, subject, body },
|
|
2486
|
+
draftOnly: false,
|
|
2487
|
+
}));
|
|
2488
|
+
if (verdict.action === 'reject') return { ok: false, output: `User declined to send the email to ${to}.`, meta: { rejected: true } };
|
|
2489
|
+
if (verdict.action === 'draft') return { ok: false, output: 'The user wants to keep it as a draft. It was not sent; tell them it is on the card above.', meta: { rejected: true } };
|
|
2490
|
+
const email = applyEmailEdits({ to, subject, body }, verdict.edits);
|
|
2491
|
+
if (!email.to) return { ok: false, output: 'The email has no recipient after the user\'s edits, so it was not sent.' };
|
|
2492
|
+
const r = await botMailLib.send({ botId: bot.id, to: email.to, subject: email.subject, text: email.body, replyTo: thread ? thread.id : '' });
|
|
2493
|
+
if (!r || !r.ok) return { ok: false, output: `Not sent: ${(r && (r.error || r.output)) || 'the server did not answer'}` };
|
|
2494
|
+
const left = r.used && r.limits ? ` The crew has ${Math.max(0, r.limits.day - r.used.day)} emails left today and ${Math.max(0, r.limits.month - r.used.month)} this month.` : '';
|
|
2495
|
+
return { ok: true, output: `Sent from ${from} to ${email.to}, subject "${email.subject}".${left}`, meta: { label: `to ${email.to}: ${email.subject}` } };
|
|
2496
|
+
}
|
|
2242
2497
|
|
|
2243
2498
|
// ─── Publishing (Supabase + Vercel + GitHub, see lib/publish.js) ────────────
|
|
2244
2499
|
const publishTools = publishLib.makePublishTools({
|
|
@@ -2295,17 +2550,18 @@ export const TOOLS = {
|
|
|
2295
2550
|
ask_bot,
|
|
2296
2551
|
ask_user,
|
|
2297
2552
|
mcp,
|
|
2298
|
-
web_fetch, web_search, image_search, apply_patch, plan_exit, plan_enter, lsp, reach_me, watch_email,
|
|
2299
|
-
list_dir, read_file, write_file, edit_file, search, run, use_skill, list_skills, fetch_image, browser_check,
|
|
2300
|
-
gmail_send, gmail_search, gmail_draft, drafts_list, calendar_list, calendar_add, slack_post_message, vercel_deploy, supabase_create_project, supabase_delete_project, github_create_repo,
|
|
2553
|
+
web_fetch, web_search, image_search, apply_patch, plan_exit, plan_enter, lsp, reach_me, watch_email, life_context, remember, recall, hand_off, bot_email_send, look_at_screen, use_computer,
|
|
2554
|
+
list_dir, read_file, write_file, edit_file, search, run, job, use_skill, list_skills, fetch_image, browser_check,
|
|
2555
|
+
gmail_send, gmail_search, gmail_read, gmail_draft, drafts_list, calendar_list, calendar_add, calendar_update, slack_post_message, vercel_deploy, supabase_create_project, supabase_delete_project, github_create_repo,
|
|
2301
2556
|
design_reference_search, view_images, supabase_api, supabase_sql, vercel_api, add_model,
|
|
2557
|
+
design_direction, design_lint,
|
|
2302
2558
|
};
|
|
2303
2559
|
|
|
2304
2560
|
// Tools that can change something. supabase_api/supabase_sql/vercel_api only
|
|
2305
2561
|
// actually prompt for non-read requests (see above); read-only tools
|
|
2306
2562
|
// (browser_check, gmail_search, design_reference_search, view_images, ...)
|
|
2307
2563
|
// never prompt.
|
|
2308
|
-
export const TOOL_NEEDS_APPROVAL = new Set(['add_model', 'supabase_setup', 'supabase_schema', 'publish_deploy', 'publish_github', 'write_file', 'edit_file', 'apply_patch', 'run', 'fetch_image', 'gmail_send', 'gmail_draft', 'calendar_add', 'slack_post_message', 'vercel_deploy', 'supabase_create_project', 'supabase_delete_project', 'github_create_repo', 'supabase_api', 'supabase_sql', 'vercel_api']);
|
|
2564
|
+
export const TOOL_NEEDS_APPROVAL = new Set(['use_computer', 'add_model', 'supabase_setup', 'supabase_schema', 'publish_deploy', 'publish_github', 'write_file', 'edit_file', 'apply_patch', 'run', 'fetch_image', 'gmail_send', 'gmail_draft', 'bot_email_send', 'calendar_add', 'calendar_update', 'slack_post_message', 'vercel_deploy', 'supabase_create_project', 'supabase_delete_project', 'github_create_repo', 'supabase_api', 'supabase_sql', 'vercel_api']);
|
|
2309
2565
|
|
|
2310
2566
|
/** Human-facing verb + colour hint for the transcript. */
|
|
2311
2567
|
export const TOOL_DISPLAY = {
|
|
@@ -2318,6 +2574,13 @@ export const TOOL_DISPLAY = {
|
|
|
2318
2574
|
image_search: { verb: 'images', icon: '▸' },
|
|
2319
2575
|
reach_me: { verb: 'phone', icon: '☎' },
|
|
2320
2576
|
watch_email: { verb: 'watch', icon: '✉' },
|
|
2577
|
+
life_context: { verb: 'context', icon: '◷' },
|
|
2578
|
+
remember: { verb: 'remember', icon: '✦' },
|
|
2579
|
+
recall: { verb: 'recall', icon: '✦' },
|
|
2580
|
+
hand_off: { verb: 'handed', icon: '⇢' },
|
|
2581
|
+
look_at_screen: { verb: 'screen', icon: '◉' },
|
|
2582
|
+
use_computer: { verb: 'computer', icon: '◉' },
|
|
2583
|
+
bot_email_send: { verb: 'email', icon: '✉' },
|
|
2321
2584
|
apply_patch:{ verb: 'patch', icon: '✎' },
|
|
2322
2585
|
plan_exit: { verb: 'plan', icon: '☐' },
|
|
2323
2586
|
plan_enter: { verb: 'plan', icon: '☐' },
|
|
@@ -2328,6 +2591,7 @@ export const TOOL_DISPLAY = {
|
|
|
2328
2591
|
edit_file: { verb: 'edit', icon: '✎' },
|
|
2329
2592
|
search: { verb: 'search', icon: '▸' },
|
|
2330
2593
|
run: { verb: 'run', icon: '$' },
|
|
2594
|
+
job: { verb: 'job', icon: '$' },
|
|
2331
2595
|
use_skill: { verb: 'skill', icon: '★' },
|
|
2332
2596
|
list_skills:{ verb: 'skills', icon: '★' },
|
|
2333
2597
|
fetch_image:{ verb: 'fetch', icon: '⇩' },
|
|
@@ -2338,6 +2602,8 @@ export const TOOL_DISPLAY = {
|
|
|
2338
2602
|
drafts_list:{ verb: 'drafts', icon: '✉' },
|
|
2339
2603
|
calendar_list:{ verb: 'calendar', icon: '▦' },
|
|
2340
2604
|
calendar_add:{ verb: 'calendar', icon: '▦' },
|
|
2605
|
+
calendar_update:{ verb: 'calendar', icon: '▦' },
|
|
2606
|
+
gmail_read:{ verb: 'read', icon: '✉' },
|
|
2341
2607
|
add_model:{ verb: 'model', icon: '◆' },
|
|
2342
2608
|
slack_post_message:{ verb: 'post', icon: '#' },
|
|
2343
2609
|
vercel_deploy:{ verb: 'deploy', icon: '▲' },
|
|
@@ -2345,6 +2611,8 @@ export const TOOL_DISPLAY = {
|
|
|
2345
2611
|
supabase_delete_project:{ verb: 'delete', icon: '◆' },
|
|
2346
2612
|
github_create_repo:{ verb: 'push', icon: '⎇' },
|
|
2347
2613
|
design_reference_search:{ verb: 'reference', icon: '◫' },
|
|
2614
|
+
design_direction:{ verb: 'direction', icon: '◐' },
|
|
2615
|
+
design_lint:{ verb: 'check', icon: '◎' },
|
|
2348
2616
|
view_images:{ verb: 'view', icon: '◉' },
|
|
2349
2617
|
supabase_api:{ verb: 'supabase', icon: '◆' },
|
|
2350
2618
|
supabase_sql:{ verb: 'sql', icon: '◆' },
|
|
@@ -2361,17 +2629,35 @@ export const TOOL_DISPLAY = {
|
|
|
2361
2629
|
// agent.mjs; this covers every other tool that changes something outside the
|
|
2362
2630
|
// conversation, so read-only can't be escaped through a side door.
|
|
2363
2631
|
const MUTATING_TOOLS = new Set([
|
|
2364
|
-
'write_file', 'edit_file', 'apply_patch', 'fetch_image', 'gmail_send', 'gmail_draft', 'calendar_add', 'slack_post_message', 'add_model',
|
|
2632
|
+
'write_file', 'edit_file', 'apply_patch', 'fetch_image', 'gmail_send', 'gmail_draft', 'calendar_add', 'calendar_update', 'slack_post_message', 'add_model', 'use_computer',
|
|
2365
2633
|
'vercel_deploy', 'supabase_create_project', 'supabase_delete_project', 'github_create_repo',
|
|
2366
2634
|
'supabase_setup', 'supabase_schema', 'publish_deploy', 'publish_github',
|
|
2367
2635
|
]);
|
|
2368
2636
|
|
|
2637
|
+
// Everything that isn't designing: commands, deploys, databases, messages, other projects.
|
|
2638
|
+
export const DESIGN_BLOCKED = new Set([
|
|
2639
|
+
'run', 'job', 'use_computer', 'lsp', 'mcp', 'ask_bot', 'hand_off', 'plan_enter', 'plan_exit', 'reach_me', 'watch_email', 'add_model', 'browser_check', 'bot_email_send',
|
|
2640
|
+
'gmail_send', 'gmail_draft', 'gmail_search', 'gmail_read', 'drafts_list', 'calendar_list', 'calendar_add', 'calendar_update', 'slack_post_message',
|
|
2641
|
+
'vercel_deploy', 'vercel_api', 'supabase_create_project', 'supabase_delete_project', 'supabase_api', 'supabase_sql',
|
|
2642
|
+
'github_create_repo', 'publish_check', 'publish_connect', 'supabase_setup', 'supabase_schema', 'publish_deploy', 'publish_github',
|
|
2643
|
+
]);
|
|
2644
|
+
|
|
2369
2645
|
export async function executeTool(name, args, ctx) {
|
|
2370
2646
|
const fn = TOOLS[name];
|
|
2371
2647
|
if (!fn) {
|
|
2372
2648
|
return { ok: false, output: `Unknown tool "${name}". Available: ${Object.keys(TOOLS).join(', ')}.` };
|
|
2373
2649
|
}
|
|
2374
2650
|
|
|
2651
|
+
// Design mode only designs: no commands, deploys or messages, and files stay in the design's folder.
|
|
2652
|
+
if (ctx.mode === 'Design') {
|
|
2653
|
+
if (DESIGN_BLOCKED.has(name)) {
|
|
2654
|
+
return { ok: false, output: `Blocked - Design mode only makes designs, so ${name} is not available. When the design is ready, the user can press "Build it in Craft" to make it work.` };
|
|
2655
|
+
}
|
|
2656
|
+
if ((name === 'write_file' || name === 'edit_file' || name === 'fetch_image') && args.path && resolvePath(String(args.path), ctx.cwd).outside) {
|
|
2657
|
+
return { ok: false, output: 'Blocked - in Design mode every file stays inside this design\'s folder. Write it under the design instead (e.g. assets/).' };
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2375
2661
|
const planFileWrite = ctx.mode === 'Plan' && (name === 'write_file' || name === 'edit_file') && isPlanPath(args.path, ctx.cwd);
|
|
2376
2662
|
if (READ_ONLY_MODES.has(ctx.mode) && MUTATING_TOOLS.has(name) && !planFileWrite) {
|
|
2377
2663
|
return { ok: false, output: `Blocked - ${ctx.mode} mode is read-only, so ${name} is not available. Describe what you would do instead, or tell the user to switch to Build mode.` };
|