codeply-cli 0.5.0 → 0.5.2
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 -0
- package/THIRD_PARTY_NOTICES.md +65 -63
- package/bin/codeply.js +1 -1
- package/lib/agent.mjs +133 -24
- package/lib/ai.js +268 -9
- 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/bots.js +493 -16
- package/lib/chatgpt.js +282 -0
- package/lib/cloud-browser.mjs +143 -143
- package/lib/cloud.mjs +1186 -1105
- package/lib/commands.js +103 -103
- package/lib/config.js +83 -3
- 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/diagnostics.js +192 -192
- package/lib/edit-engine.js +364 -364
- package/lib/github-agent.mjs +311 -311
- package/lib/history.js +75 -75
- package/lib/mail-watch.js +504 -0
- package/lib/mcp.js +258 -258
- package/lib/model-sync.js +114 -0
- package/lib/native-tools.mjs +16 -1
- package/lib/oauth-connectors.js +146 -24
- package/lib/permissions.js +109 -109
- package/lib/plugins.js +353 -353
- package/lib/publish.js +916 -0
- package/lib/reach.js +67 -0
- package/lib/server.mjs +457 -457
- package/lib/session-db.js +220 -220
- package/lib/share.js +168 -168
- package/lib/skills.js +2 -0
- package/lib/snapshot.js +146 -146
- package/lib/telemetry.js +102 -0
- package/lib/terminal-hints.js +154 -154
- package/lib/tools.mjs +316 -14
- package/lib/typecheck-worker.js +147 -147
- package/lib/web-tools.js +209 -158
- package/package.json +49 -49
- 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/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/publish-website/SKILL.md +76 -0
- 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
|
@@ -23,12 +23,14 @@ const terminalHints = require('./terminal-hints.js');
|
|
|
23
23
|
const diagnostics = require('./diagnostics.js');
|
|
24
24
|
const { commandPatterns } = require('./arity.js');
|
|
25
25
|
const mcpLib = require('./mcp.js');
|
|
26
|
+
const reachLib = require('./reach.js');
|
|
26
27
|
const applyLimit = require('./apply-limit.js');
|
|
27
28
|
const config = require('./config.js');
|
|
28
29
|
const skills = require('./skills.js');
|
|
29
30
|
const oauth = require('./oauth-connectors.js');
|
|
30
31
|
const webTools = require('./web-tools.js');
|
|
31
32
|
const applyPatchLib = require('./apply-patch.js');
|
|
33
|
+
const publishLib = require('./publish.js');
|
|
32
34
|
|
|
33
35
|
// Successful writes made while using the hosted model are logged (counts
|
|
34
36
|
// only) for the usage dashboard. There is no cap - see apply-limit.js.
|
|
@@ -713,33 +715,289 @@ async function getValidGmailToken({ force = false } = {}) {
|
|
|
713
715
|
return refreshed.access_token;
|
|
714
716
|
}
|
|
715
717
|
|
|
718
|
+
/**
|
|
719
|
+
* An approval answer: a plain string ('once', 'always', 'reject', 'draft') or
|
|
720
|
+
* { verdict, edits } when the user changed the email on the card before
|
|
721
|
+
* saying yes. Every host may answer either way.
|
|
722
|
+
*/
|
|
723
|
+
function readVerdict(v) {
|
|
724
|
+
if (v && typeof v === 'object') {
|
|
725
|
+
const edits = v.edits && typeof v.edits === 'object' ? v.edits : null;
|
|
726
|
+
return { action: String(v.verdict || v.action || 'reject'), edits };
|
|
727
|
+
}
|
|
728
|
+
return { action: String(v || 'reject'), edits: null };
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/** The email as the user left it on the approval card. */
|
|
732
|
+
function applyEmailEdits(draft, edits) {
|
|
733
|
+
if (!edits) return draft;
|
|
734
|
+
const out = { ...draft };
|
|
735
|
+
for (const k of ['to', 'subject', 'body']) {
|
|
736
|
+
if (typeof edits[k] === 'string') out[k] = k === 'body' ? edits[k].slice(0, 100000) : edits[k].trim().slice(0, 1000);
|
|
737
|
+
}
|
|
738
|
+
return out;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const GMAIL_NOT_CONNECTED = 'Gmail is not connected. Ask the user to connect it from Connect Apps (account menu → Connect Apps) first.';
|
|
742
|
+
|
|
743
|
+
// ─── Drafts kept by Codeply itself (~/.codeply/drafts) ──────────────────────
|
|
744
|
+
// Where gmail_draft saves when Gmail is not connected (or not allowed to
|
|
745
|
+
// draft yet). One JSON file per draft; gmail_send can send one by id.
|
|
746
|
+
function draftsDir() { return path.join(os.homedir(), '.codeply', 'drafts'); }
|
|
747
|
+
const DRAFT_ID = /^d[a-z0-9]{6,30}$/;
|
|
748
|
+
|
|
749
|
+
function saveLocalDraft({ to, subject, body }) {
|
|
750
|
+
const dir = draftsDir();
|
|
751
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
752
|
+
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() };
|
|
754
|
+
fs.writeFileSync(path.join(dir, `${id}.json`), JSON.stringify(draft, null, 2));
|
|
755
|
+
return draft;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function readLocalDraft(id) {
|
|
759
|
+
if (!DRAFT_ID.test(String(id || ''))) return null;
|
|
760
|
+
try { return JSON.parse(fs.readFileSync(path.join(draftsDir(), `${id}.json`), 'utf8')); } catch { return null; }
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function listLocalDrafts() {
|
|
764
|
+
let files = [];
|
|
765
|
+
try { files = fs.readdirSync(draftsDir()).filter((f) => f.endsWith('.json')); } catch { return []; }
|
|
766
|
+
return files.map((f) => { try { return JSON.parse(fs.readFileSync(path.join(draftsDir(), f), 'utf8')); } catch { return null; } })
|
|
767
|
+
.filter((d) => d && d.id).sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/** True only when we know the stored connection lacks a scope (older connections did not record scopes). */
|
|
771
|
+
function lacksScope(integration, scope) {
|
|
772
|
+
return typeof integration.scope === 'string' && integration.scope.trim() !== '' && !integration.scope.split(/\s+/).includes(scope);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Saves a draft: in Gmail when it is connected and allowed to draft,
|
|
777
|
+
* otherwise in Codeply's own drafts. Returns a tool result.
|
|
778
|
+
*/
|
|
779
|
+
async function storeDraft(draft, token) {
|
|
780
|
+
let why = '';
|
|
781
|
+
if (token) {
|
|
782
|
+
if (lacksScope(config.getIntegration('gmail'), oauth.GOOGLE_SCOPE.compose)) {
|
|
783
|
+
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.';
|
|
784
|
+
} else {
|
|
785
|
+
try {
|
|
786
|
+
const r = await oauth.gmailCreateDraft(token, draft);
|
|
787
|
+
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
|
+
} catch (e) {
|
|
789
|
+
if (!e.scopeMissing) return { ok: false, output: `Gmail draft failed: ${e.message}` };
|
|
790
|
+
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
|
+
}
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
why = 'Gmail is not connected, so it was saved in Codeply\'s own drafts (not in Gmail). Connect Gmail in Connect Apps to keep drafts in Gmail.';
|
|
795
|
+
}
|
|
796
|
+
const d = saveLocalDraft(draft);
|
|
797
|
+
return {
|
|
798
|
+
ok: true,
|
|
799
|
+
output: `Draft saved, id ${d.id}. ${why} Nothing was sent. drafts_list shows saved drafts; gmail_send with <draft>${d.id}</draft> sends it once Gmail is connected.`,
|
|
800
|
+
meta: { label: `draft ${d.id}: ${d.subject || '(no subject)'}`, draftId: d.id },
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
716
804
|
async function gmail_send(args, ctx) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
805
|
+
let to = (args.to || '').trim();
|
|
806
|
+
let subject = (args.subject || '').trim();
|
|
807
|
+
let body = args.body || '';
|
|
808
|
+
const draftId = String(args.draft || '').trim();
|
|
809
|
+
let saved = null;
|
|
810
|
+
if (draftId) {
|
|
811
|
+
saved = readLocalDraft(draftId);
|
|
812
|
+
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;
|
|
814
|
+
}
|
|
720
815
|
if (!to) return { ok: false, output: 'gmail_send needs a <to> address.' };
|
|
721
816
|
if (!subject) return { ok: false, output: 'gmail_send needs a <subject>.' };
|
|
722
817
|
|
|
723
818
|
let token;
|
|
724
819
|
try { token = await getValidGmailToken(); } catch (e) { return { ok: false, output: `Gmail send failed: ${e.message}` }; }
|
|
725
|
-
if (!token) return { ok: false, output:
|
|
820
|
+
if (!token) return { ok: false, output: `${GMAIL_NOT_CONNECTED} (gmail_draft can save it as a draft in the meantime.)` };
|
|
726
821
|
|
|
727
|
-
const verdict = await ctx.approve({
|
|
822
|
+
const verdict = readVerdict(await ctx.approve({
|
|
728
823
|
tool: 'gmail_send',
|
|
729
824
|
title: `Send email to ${to}`,
|
|
730
825
|
detail: `Subject: ${subject}\n\n${body}`,
|
|
731
826
|
danger: false,
|
|
732
|
-
|
|
733
|
-
|
|
827
|
+
// The card shows these as editable fields; edits come back with the verdict.
|
|
828
|
+
draft: { to, subject, body },
|
|
829
|
+
}));
|
|
830
|
+
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);
|
|
833
|
+
if (verdict.action === 'draft') {
|
|
834
|
+
const r = await storeDraft(email, token);
|
|
835
|
+
return { ...r, output: `The user chose to save it as a draft instead of sending. ${r.output}` };
|
|
836
|
+
}
|
|
837
|
+
if (!email.to) return { ok: false, output: 'The email has no recipient after the user\'s edits, so it was not sent.' };
|
|
734
838
|
|
|
735
839
|
try {
|
|
736
|
-
const result = await oauth.gmailSend(token,
|
|
737
|
-
|
|
840
|
+
const result = await oauth.gmailSend(token, email);
|
|
841
|
+
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
|
+
return { ok: true, output: `Email sent to ${email.to} (message id ${result.id}).${note}`, meta: { label: `to ${email.to}: ${email.subject}` } };
|
|
738
844
|
} catch (e) {
|
|
739
845
|
return { ok: false, output: `Gmail send failed: ${e.message}` };
|
|
740
846
|
}
|
|
741
847
|
}
|
|
742
848
|
|
|
849
|
+
async function gmail_draft(args, ctx) {
|
|
850
|
+
const draft = { to: (args.to || '').trim(), subject: (args.subject || '').trim(), body: args.body || '' };
|
|
851
|
+
if (!draft.subject && !draft.body.trim()) return { ok: false, output: 'gmail_draft needs at least a <subject> or a <body>.' };
|
|
852
|
+
|
|
853
|
+
let token = null;
|
|
854
|
+
try { token = await getValidGmailToken(); } catch { token = null; } // an expired sign-in still gets a local draft
|
|
855
|
+
|
|
856
|
+
const verdict = readVerdict(await ctx.approve({
|
|
857
|
+
tool: 'gmail_draft',
|
|
858
|
+
title: `Save a draft${draft.to ? ` to ${draft.to}` : ''}${token ? ' in Gmail' : ''}`,
|
|
859
|
+
detail: `Subject: ${draft.subject}\n\n${draft.body}`,
|
|
860
|
+
danger: false,
|
|
861
|
+
draft,
|
|
862
|
+
draftOnly: true,
|
|
863
|
+
}));
|
|
864
|
+
if (verdict.action === 'reject') return { ok: false, output: 'User declined to save the draft.', meta: { rejected: true } };
|
|
865
|
+
return storeDraft(applyEmailEdits(draft, verdict.edits), token);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
async function drafts_list() {
|
|
869
|
+
const list = listLocalDrafts();
|
|
870
|
+
if (!list.length) return { ok: true, output: 'No drafts saved in Codeply. (Drafts saved in Gmail are in Gmail\'s Drafts folder.)', meta: { label: 'no drafts' } };
|
|
871
|
+
const lines = list.slice(0, 30).map((d) => `- ${d.id}: "${d.subject || '(no subject)'}" to ${d.to || '(no recipient yet)'}, saved ${d.createdAt}\n ${String(d.body || '').replace(/\s+/g, ' ').slice(0, 160)}`);
|
|
872
|
+
return { ok: true, output: `${list.length} draft(s) saved in Codeply, newest first:\n\n${lines.join('\n')}\n\nSend one with gmail_send and <draft>id</draft>.`, meta: { label: `${list.length} draft(s)`, count: list.length } };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// ─── Google Calendar (the Gmail connection, with the calendar scope) ────────
|
|
876
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
877
|
+
const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
878
|
+
|
|
879
|
+
/** Local wall-clock ISO with the offset, e.g. 2026-10-03T14:00:00+05:00. */
|
|
880
|
+
function localIso(d) {
|
|
881
|
+
const off = -d.getTimezoneOffset();
|
|
882
|
+
const sign = off >= 0 ? '+' : '-';
|
|
883
|
+
const a = Math.abs(off);
|
|
884
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}T${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}${sign}${pad2(Math.floor(a / 60))}:${pad2(a % 60)}`;
|
|
885
|
+
}
|
|
886
|
+
const ymd = (d) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
887
|
+
const showLocal = (d) => `${WEEKDAYS[d.getDay()]} ${ymd(d)} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* "today", "tomorrow", "2026-10-03" (a whole day, local) or a date-time
|
|
891
|
+
* ("2026-10-03T14:00", local unless it carries an offset). null if unreadable.
|
|
892
|
+
*/
|
|
893
|
+
function parseWhen(input, now = new Date()) {
|
|
894
|
+
const s = String(input || '').trim().toLowerCase();
|
|
895
|
+
if (!s) return null;
|
|
896
|
+
const day = (offset) => { const d = new Date(now); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() + offset); return { date: d, allDay: true }; };
|
|
897
|
+
if (s === 'today') return day(0);
|
|
898
|
+
if (s === 'tomorrow') return day(1);
|
|
899
|
+
if (s === 'yesterday') return day(-1);
|
|
900
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
901
|
+
if (m) return { date: new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])), allDay: true };
|
|
902
|
+
const d = new Date(String(input).trim().replace(' ', 'T'));
|
|
903
|
+
return Number.isNaN(d.getTime()) ? null : { date: d, allDay: false };
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async function googleTokenFor(what) {
|
|
907
|
+
const token = await getValidGmailToken();
|
|
908
|
+
if (!token) return { error: `${what} uses the Gmail connection, and Gmail is not connected. Ask the user to connect Gmail from Connect Apps (account menu → Connect Apps) first; it covers Calendar too.` };
|
|
909
|
+
if (lacksScope(config.getIntegration('gmail'), oauth.GOOGLE_SCOPE.calendar)) {
|
|
910
|
+
return { error: 'Gmail was connected before Calendar access was added. Ask the user to reconnect Gmail in Connect Apps (it covers Calendar too) and allow calendar access.' };
|
|
911
|
+
}
|
|
912
|
+
return { token };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function eventLine(ev) {
|
|
916
|
+
const title = ev.summary || '(no title)';
|
|
917
|
+
let when;
|
|
918
|
+
if (ev.start && ev.start.date) {
|
|
919
|
+
const [y, mo, da] = ev.start.date.split('-').map(Number);
|
|
920
|
+
const s = new Date(y, mo - 1, da);
|
|
921
|
+
when = `${WEEKDAYS[s.getDay()]} ${ev.start.date}, all day`;
|
|
922
|
+
} else {
|
|
923
|
+
const s = new Date(ev.start && ev.start.dateTime);
|
|
924
|
+
const e = new Date(ev.end && ev.end.dateTime);
|
|
925
|
+
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
|
+
}
|
|
927
|
+
return `- ${title}: ${when}${ev.location ? `, at ${ev.location}` : ''}`;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
async function calendar_list(args) {
|
|
931
|
+
const now = new Date();
|
|
932
|
+
const from = args.from ? parseWhen(args.from, now) : parseWhen('today', now);
|
|
933
|
+
if (!from) return { ok: false, output: `calendar_list could not read <from> "${args.from}". Use today, tomorrow, 2026-10-03 or 2026-10-03T14:00.` };
|
|
934
|
+
let to = args.to ? parseWhen(args.to, now) : null;
|
|
935
|
+
if (args.to && !to) return { ok: false, output: `calendar_list could not read <to> "${args.to}". Use today, tomorrow, 2026-10-03 or 2026-10-03T14:00.` };
|
|
936
|
+
let end;
|
|
937
|
+
if (to) { end = new Date(to.date); if (to.allDay) end.setDate(end.getDate() + 1); } // a date as the end means through that whole day
|
|
938
|
+
else if (from.allDay) { end = new Date(from.date); end.setDate(end.getDate() + 1); }
|
|
939
|
+
else end = new Date(from.date.getTime() + 24 * 3600 * 1000);
|
|
940
|
+
if (end <= from.date) return { ok: false, output: 'calendar_list: <to> must be after <from>.' };
|
|
941
|
+
const max = Math.min(50, Math.max(1, parseInt(args.max, 10) || 20));
|
|
942
|
+
const range = `${showLocal(from.date)} to ${showLocal(end)}`;
|
|
943
|
+
try {
|
|
944
|
+
const t = await googleTokenFor('Google Calendar');
|
|
945
|
+
if (t.error) return { ok: false, output: t.error };
|
|
946
|
+
const items = await oauth.calendarListEvents(t.token, { timeMin: localIso(from.date), timeMax: localIso(end), maxResults: max });
|
|
947
|
+
if (!items.length) return { ok: true, output: `No events from ${range} (local time).`, meta: { label: 'no events' } };
|
|
948
|
+
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
|
+
} catch (e) {
|
|
950
|
+
return { ok: false, output: `Calendar list failed: ${e.message} Tell the user this exact error.` };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
async function calendar_add(args, ctx) {
|
|
955
|
+
const title = (args.title || '').trim();
|
|
956
|
+
if (!title) return { ok: false, output: 'calendar_add needs a <title>.' };
|
|
957
|
+
const start = parseWhen(args.start);
|
|
958
|
+
if (!start) return { ok: false, output: `calendar_add needs a <start> it can read (got "${args.start || ''}"). Use 2026-10-03T14:00 for a time, or 2026-10-03 for an all-day event.` };
|
|
959
|
+
let end = args.end ? parseWhen(args.end) : null;
|
|
960
|
+
if (args.end && !end) return { ok: false, output: `calendar_add could not read <end> "${args.end}".` };
|
|
961
|
+
const event = { summary: title };
|
|
962
|
+
if (args.description) event.description = String(args.description);
|
|
963
|
+
if (args.location) event.location = String(args.location).trim();
|
|
964
|
+
let when;
|
|
965
|
+
if (start.allDay && (!end || end.allDay)) {
|
|
966
|
+
// All-day: Google's end date is exclusive, so a one-day event ends the next day.
|
|
967
|
+
const last = end ? end.date : start.date;
|
|
968
|
+
const after = new Date(last); after.setDate(after.getDate() + 1);
|
|
969
|
+
if (after <= start.date) return { ok: false, output: 'calendar_add: <end> must not be before <start>.' };
|
|
970
|
+
event.start = { date: ymd(start.date) };
|
|
971
|
+
event.end = { date: ymd(after) };
|
|
972
|
+
when = ymd(last) === ymd(start.date) ? `${WEEKDAYS[start.date.getDay()]} ${ymd(start.date)}, all day` : `${ymd(start.date)} to ${ymd(last)}, all day`;
|
|
973
|
+
} else {
|
|
974
|
+
const s = start.date;
|
|
975
|
+
const e = end ? end.date : new Date(s.getTime() + 60 * 60 * 1000);
|
|
976
|
+
if (e <= s) return { ok: false, output: 'calendar_add: <end> must be after <start>.' };
|
|
977
|
+
let tz = '';
|
|
978
|
+
try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch {}
|
|
979
|
+
event.start = { dateTime: localIso(s), ...(tz ? { timeZone: tz } : {}) };
|
|
980
|
+
event.end = { dateTime: localIso(e), ...(tz ? { timeZone: tz } : {}) };
|
|
981
|
+
when = `${showLocal(s)} to ${ymd(e) === ymd(s) ? `${pad2(e.getHours())}:${pad2(e.getMinutes())}` : showLocal(e)}`;
|
|
982
|
+
}
|
|
983
|
+
const mins = String(args.reminders || '').split(/[\s,]+/).map((x) => parseInt(x, 10)).filter((n) => Number.isFinite(n) && n >= 0 && n <= 40320).slice(0, 5);
|
|
984
|
+
if (mins.length) event.reminders = { useDefault: false, overrides: mins.map((minutes) => ({ method: 'popup', minutes })) };
|
|
985
|
+
|
|
986
|
+
let t;
|
|
987
|
+
try { t = await googleTokenFor('Google Calendar'); } catch (e) { return { ok: false, output: `Calendar add failed: ${e.message}` }; }
|
|
988
|
+
if (t.error) return { ok: false, output: t.error };
|
|
989
|
+
|
|
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');
|
|
991
|
+
const verdict = readVerdict(await ctx.approve({ tool: 'calendar_add', title: `Add "${title}" to your calendar`, detail, danger: false }));
|
|
992
|
+
if (verdict.action === 'reject') return { ok: false, output: `User declined to add "${title}" to the calendar.`, meta: { rejected: true } };
|
|
993
|
+
try {
|
|
994
|
+
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}` } };
|
|
996
|
+
} catch (e) {
|
|
997
|
+
return { ok: false, output: `Calendar add failed: ${e.message} Tell the user this exact error.` };
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
743
1001
|
async function gmail_search(args, ctx) {
|
|
744
1002
|
const query = (args.query || '').trim();
|
|
745
1003
|
// An empty query is fine: it lists the newest mail ("check my emails").
|
|
@@ -1502,8 +1760,12 @@ function formatApiResult(service, method, apiPath, r) {
|
|
|
1502
1760
|
async function getValidSupabaseToken() {
|
|
1503
1761
|
const sb = config.getIntegration('supabase');
|
|
1504
1762
|
if (!sb.accessToken) return null;
|
|
1763
|
+
// A token with no expiry (a pasted personal access token) never lapses here.
|
|
1505
1764
|
if (!sb.expiresAt || Date.now() < sb.expiresAt - 120000) return sb.accessToken;
|
|
1506
|
-
|
|
1765
|
+
// Past expiry with no way to renew it (no OAuth app credentials): report
|
|
1766
|
+
// "not connected" so the user is asked to reconnect, instead of every call
|
|
1767
|
+
// failing with a bare "Unauthorized".
|
|
1768
|
+
if (!sb.refreshToken || !sb.clientId || !sb.clientSecret) return null;
|
|
1507
1769
|
try {
|
|
1508
1770
|
const t = await oauth.refreshSupabaseToken(sb.clientId, sb.clientSecret, sb.refreshToken);
|
|
1509
1771
|
config.saveIntegration('supabase', {
|
|
@@ -1758,6 +2020,20 @@ async function web_search(args, ctx) {
|
|
|
1758
2020
|
return { ok: true, output: truncate(r.text), meta: { label: query } };
|
|
1759
2021
|
}
|
|
1760
2022
|
|
|
2023
|
+
async function image_search(args, ctx) {
|
|
2024
|
+
const query = String(args.query || '').trim();
|
|
2025
|
+
if (!query) return { ok: false, output: 'image_search needs a <query>.' };
|
|
2026
|
+
const r = await webTools.imageSearch(query, { num: Number(args.num) || 8, signal: ctx.signal });
|
|
2027
|
+
if (!r.ok) return { ok: false, output: r.error, meta: { label: query } };
|
|
2028
|
+
const lines = r.images.map((im, i) => `${i + 1}. ${im.title || 'image'} (${im.width}x${im.height})\n ${im.url}${im.page ? `\n from ${im.page}` : ''}`);
|
|
2029
|
+
return {
|
|
2030
|
+
ok: true,
|
|
2031
|
+
output: `${r.images.length} images for "${query}"${r.via === 'wikimedia' ? ' (Wikimedia Commons, free to use)' : ''}:\n${lines.join('\n')}\n\n` +
|
|
2032
|
+
'Pick the one that fits the section best and download it with fetch_image to a path under assets/. Prefer landscape for heroes and banners.',
|
|
2033
|
+
meta: { label: query },
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1761
2037
|
// ─── lsp ────────────────────────────────────────────────────────────────────
|
|
1762
2038
|
|
|
1763
2039
|
const LSP_OPS = new Set(['definition', 'references', 'implementation', 'hover', 'documentsymbol', 'workspacesymbol']);
|
|
@@ -1950,14 +2226,26 @@ async function ask_bot(args, ctx) {
|
|
|
1950
2226
|
return ctx.askBot({ name: String(args.bot || '').trim(), task: String(args.task || '').trim(), signal: ctx.signal });
|
|
1951
2227
|
}
|
|
1952
2228
|
|
|
2229
|
+
// ─── Reaching the user's phone (lib/reach.js; the host registers the handler) ─
|
|
2230
|
+
const reach_me = (args, ctx) => reachLib.reachMe(args, ctx);
|
|
2231
|
+
const watch_email = (args, ctx) => reachLib.watchEmail(args, ctx);
|
|
2232
|
+
|
|
2233
|
+
// ─── Publishing (Supabase + Vercel + GitHub, see lib/publish.js) ────────────
|
|
2234
|
+
const publishTools = publishLib.makePublishTools({
|
|
2235
|
+
config,
|
|
2236
|
+
getSupabaseToken: () => getValidSupabaseToken(),
|
|
2237
|
+
resolveDir: (p, cwd) => resolvePath(String(p).trim(), cwd).abs,
|
|
2238
|
+
});
|
|
2239
|
+
|
|
1953
2240
|
export const TOOLS = {
|
|
2241
|
+
...publishTools,
|
|
1954
2242
|
todo,
|
|
1955
2243
|
ask_bot,
|
|
1956
2244
|
ask_user,
|
|
1957
2245
|
mcp,
|
|
1958
|
-
web_fetch, web_search, apply_patch, plan_exit, plan_enter, lsp,
|
|
2246
|
+
web_fetch, web_search, image_search, apply_patch, plan_exit, plan_enter, lsp, reach_me, watch_email,
|
|
1959
2247
|
list_dir, read_file, write_file, edit_file, search, run, use_skill, list_skills, fetch_image, browser_check,
|
|
1960
|
-
gmail_send, gmail_search, slack_post_message, vercel_deploy, supabase_create_project, supabase_delete_project, github_create_repo,
|
|
2248
|
+
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,
|
|
1961
2249
|
design_reference_search, view_images, supabase_api, supabase_sql, vercel_api,
|
|
1962
2250
|
};
|
|
1963
2251
|
|
|
@@ -1965,7 +2253,7 @@ export const TOOLS = {
|
|
|
1965
2253
|
// actually prompt for non-read requests (see above); read-only tools
|
|
1966
2254
|
// (browser_check, gmail_search, design_reference_search, view_images, ...)
|
|
1967
2255
|
// never prompt.
|
|
1968
|
-
export const TOOL_NEEDS_APPROVAL = new Set(['write_file', 'edit_file', 'apply_patch', 'run', 'fetch_image', 'gmail_send', 'slack_post_message', 'vercel_deploy', 'supabase_create_project', 'supabase_delete_project', 'github_create_repo', 'supabase_api', 'supabase_sql', 'vercel_api']);
|
|
2256
|
+
export const TOOL_NEEDS_APPROVAL = new Set(['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']);
|
|
1969
2257
|
|
|
1970
2258
|
/** Human-facing verb + colour hint for the transcript. */
|
|
1971
2259
|
export const TOOL_DISPLAY = {
|
|
@@ -1975,6 +2263,9 @@ export const TOOL_DISPLAY = {
|
|
|
1975
2263
|
mcp: { verb: 'mcp', icon: '⧉' },
|
|
1976
2264
|
web_fetch: { verb: 'fetch', icon: '⇩' },
|
|
1977
2265
|
web_search: { verb: 'web', icon: '▸' },
|
|
2266
|
+
image_search: { verb: 'images', icon: '▸' },
|
|
2267
|
+
reach_me: { verb: 'phone', icon: '☎' },
|
|
2268
|
+
watch_email: { verb: 'watch', icon: '✉' },
|
|
1978
2269
|
apply_patch:{ verb: 'patch', icon: '✎' },
|
|
1979
2270
|
plan_exit: { verb: 'plan', icon: '☐' },
|
|
1980
2271
|
plan_enter: { verb: 'plan', icon: '☐' },
|
|
@@ -1991,6 +2282,10 @@ export const TOOL_DISPLAY = {
|
|
|
1991
2282
|
browser_check:{ verb: 'check', icon: '◎' },
|
|
1992
2283
|
gmail_send: { verb: 'email', icon: '✉' },
|
|
1993
2284
|
gmail_search:{ verb: 'search', icon: '✉' },
|
|
2285
|
+
gmail_draft:{ verb: 'draft', icon: '✉' },
|
|
2286
|
+
drafts_list:{ verb: 'drafts', icon: '✉' },
|
|
2287
|
+
calendar_list:{ verb: 'calendar', icon: '▦' },
|
|
2288
|
+
calendar_add:{ verb: 'calendar', icon: '▦' },
|
|
1994
2289
|
slack_post_message:{ verb: 'post', icon: '#' },
|
|
1995
2290
|
vercel_deploy:{ verb: 'deploy', icon: '▲' },
|
|
1996
2291
|
supabase_create_project:{ verb: 'provision', icon: '◆' },
|
|
@@ -2001,14 +2296,21 @@ export const TOOL_DISPLAY = {
|
|
|
2001
2296
|
supabase_api:{ verb: 'supabase', icon: '◆' },
|
|
2002
2297
|
supabase_sql:{ verb: 'sql', icon: '◆' },
|
|
2003
2298
|
vercel_api:{ verb: 'vercel', icon: '▲' },
|
|
2299
|
+
publish_check:{ verb: 'check', icon: '▲' },
|
|
2300
|
+
publish_connect:{ verb: 'connect', icon: '▲' },
|
|
2301
|
+
supabase_setup:{ verb: 'database', icon: '◆' },
|
|
2302
|
+
supabase_schema:{ verb: 'schema', icon: '◆' },
|
|
2303
|
+
publish_deploy:{ verb: 'publish', icon: '▲' },
|
|
2304
|
+
publish_github:{ verb: 'push', icon: '⎇' },
|
|
2004
2305
|
};
|
|
2005
2306
|
|
|
2006
2307
|
// Plan and Ask are read-only. write_file/edit_file are also refused in
|
|
2007
2308
|
// agent.mjs; this covers every other tool that changes something outside the
|
|
2008
2309
|
// conversation, so read-only can't be escaped through a side door.
|
|
2009
2310
|
const MUTATING_TOOLS = new Set([
|
|
2010
|
-
'write_file', 'edit_file', 'apply_patch', 'fetch_image', 'gmail_send', 'slack_post_message',
|
|
2311
|
+
'write_file', 'edit_file', 'apply_patch', 'fetch_image', 'gmail_send', 'gmail_draft', 'calendar_add', 'slack_post_message',
|
|
2011
2312
|
'vercel_deploy', 'supabase_create_project', 'supabase_delete_project', 'github_create_repo',
|
|
2313
|
+
'supabase_setup', 'supabase_schema', 'publish_deploy', 'publish_github',
|
|
2012
2314
|
]);
|
|
2013
2315
|
|
|
2014
2316
|
export async function executeTool(name, args, ctx) {
|