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.
Files changed (429) hide show
  1. package/LICENSE +21 -21
  2. package/THIRD_PARTY_NOTICES.md +65 -65
  3. package/bin/codeply.js +8 -3
  4. package/lib/agent.mjs +2596 -2268
  5. package/lib/ai.js +135 -27
  6. package/lib/ai.mjs +6 -6
  7. package/lib/apply-patch.js +217 -217
  8. package/lib/arity-table.json +138 -138
  9. package/lib/arity.js +70 -70
  10. package/lib/auth.mjs +6 -6
  11. package/lib/autopilot.js +84 -0
  12. package/lib/bot-mail.js +46 -0
  13. package/lib/bots.js +142 -15
  14. package/lib/brain.js +914 -0
  15. package/lib/call-assist.js +120 -0
  16. package/lib/chatgpt.js +282 -282
  17. package/lib/cloud-browser.mjs +143 -143
  18. package/lib/cloud.mjs +1396 -1312
  19. package/lib/commands.js +103 -103
  20. package/lib/computer.js +136 -0
  21. package/lib/design-library/assemble-artifact.mjs +21 -21
  22. package/lib/design-library/cleanup.mjs +45 -45
  23. package/lib/design-library/index.json +22379 -22379
  24. package/lib/design-library/make-table-data.mjs +39 -39
  25. package/lib/design-tools.mjs +173 -0
  26. package/lib/diagnostics.js +192 -192
  27. package/lib/dsh-acp.mjs +444 -0
  28. package/lib/edit-engine.js +364 -364
  29. package/lib/github-agent.mjs +326 -326
  30. package/lib/handoff.mjs +245 -0
  31. package/lib/history.js +78 -75
  32. package/lib/life-sources.js +319 -0
  33. package/lib/life-store.js +356 -0
  34. package/lib/life-triggers.js +286 -0
  35. package/lib/life.js +487 -0
  36. package/lib/mail-watch.js +3 -3
  37. package/lib/mcp.js +258 -258
  38. package/lib/native-tools.mjs +17 -5
  39. package/lib/oauth-connectors.js +76 -21
  40. package/lib/permissions.js +109 -109
  41. package/lib/plugins.js +356 -353
  42. package/lib/reach.js +67 -67
  43. package/lib/render-md.mjs +647 -617
  44. package/lib/screen.js +77 -0
  45. package/lib/server.mjs +458 -457
  46. package/lib/session-db.js +220 -220
  47. package/lib/share.js +208 -168
  48. package/lib/skills.js +4 -1
  49. package/lib/snapshot.js +146 -146
  50. package/lib/subagents.js +4 -2
  51. package/lib/telemetry.js +105 -105
  52. package/lib/terminal-hints.js +154 -154
  53. package/lib/textarea.mjs +264 -258
  54. package/lib/tools.mjs +326 -40
  55. package/lib/tui.mjs +1459 -1086
  56. package/lib/typecheck-worker.js +147 -147
  57. package/lib/web-tools.js +209 -209
  58. package/package.json +1 -1
  59. package/skills/SOURCE.md +58 -58
  60. package/skills/accessibility/SKILL.md +147 -147
  61. package/skills/agent-architecture-audit/SKILL.md +257 -257
  62. package/skills/agent-eval/SKILL.md +147 -147
  63. package/skills/agent-harness-construction/SKILL.md +74 -74
  64. package/skills/agent-introspection-debugging/SKILL.md +154 -154
  65. package/skills/agent-payment-x402/SKILL.md +225 -225
  66. package/skills/agent-self-evaluation/SKILL.md +182 -182
  67. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
  68. package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
  69. package/skills/agent-sort/SKILL.md +216 -216
  70. package/skills/agentic-engineering/SKILL.md +64 -64
  71. package/skills/agentic-os/SKILL.md +388 -388
  72. package/skills/ai-first-engineering/SKILL.md +52 -52
  73. package/skills/ai-regression-testing/SKILL.md +386 -386
  74. package/skills/android-clean-architecture/SKILL.md +340 -340
  75. package/skills/angular-developer/SKILL.md +155 -155
  76. package/skills/angular-developer/references/angular-animations.md +160 -160
  77. package/skills/angular-developer/references/angular-aria.md +410 -410
  78. package/skills/angular-developer/references/cli.md +86 -86
  79. package/skills/angular-developer/references/component-harnesses.md +59 -59
  80. package/skills/angular-developer/references/component-styling.md +91 -91
  81. package/skills/angular-developer/references/components.md +117 -117
  82. package/skills/angular-developer/references/creating-services.md +97 -97
  83. package/skills/angular-developer/references/data-resolvers.md +69 -69
  84. package/skills/angular-developer/references/define-routes.md +67 -67
  85. package/skills/angular-developer/references/defining-providers.md +72 -72
  86. package/skills/angular-developer/references/di-fundamentals.md +120 -120
  87. package/skills/angular-developer/references/e2e-testing.md +56 -56
  88. package/skills/angular-developer/references/effects.md +83 -83
  89. package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
  90. package/skills/angular-developer/references/host-elements.md +80 -80
  91. package/skills/angular-developer/references/injection-context.md +63 -63
  92. package/skills/angular-developer/references/inputs.md +101 -101
  93. package/skills/angular-developer/references/linked-signal.md +59 -59
  94. package/skills/angular-developer/references/loading-strategies.md +61 -61
  95. package/skills/angular-developer/references/mcp.md +108 -108
  96. package/skills/angular-developer/references/navigate-to-routes.md +69 -69
  97. package/skills/angular-developer/references/outputs.md +86 -86
  98. package/skills/angular-developer/references/reactive-forms.md +122 -122
  99. package/skills/angular-developer/references/rendering-strategies.md +44 -44
  100. package/skills/angular-developer/references/resource.md +77 -77
  101. package/skills/angular-developer/references/route-animations.md +56 -56
  102. package/skills/angular-developer/references/route-guards.md +52 -52
  103. package/skills/angular-developer/references/router-lifecycle.md +45 -45
  104. package/skills/angular-developer/references/router-testing.md +87 -87
  105. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
  106. package/skills/angular-developer/references/signal-forms.md +795 -795
  107. package/skills/angular-developer/references/signals-overview.md +94 -94
  108. package/skills/angular-developer/references/tailwind-css.md +69 -69
  109. package/skills/angular-developer/references/template-driven-forms.md +114 -114
  110. package/skills/angular-developer/references/testing-fundamentals.md +65 -65
  111. package/skills/api-connector-builder/SKILL.md +121 -121
  112. package/skills/api-design/SKILL.md +524 -524
  113. package/skills/architecture-decision-records/SKILL.md +180 -180
  114. package/skills/article-writing/SKILL.md +80 -80
  115. package/skills/automation-audit-ops/SKILL.md +143 -143
  116. package/skills/autonomous-agent-harness/SKILL.md +274 -274
  117. package/skills/autonomous-loops/SKILL.md +611 -611
  118. package/skills/backend-patterns/SKILL.md +562 -562
  119. package/skills/benchmark/SKILL.md +95 -95
  120. package/skills/benchmark-methodology/SKILL.md +191 -191
  121. package/skills/benchmark-optimization-loop/SKILL.md +71 -71
  122. package/skills/blender-motion-state-inspection/SKILL.md +165 -165
  123. package/skills/blueprint/SKILL.md +106 -106
  124. package/skills/brand-discovery/SKILL.md +145 -145
  125. package/skills/brand-discovery/references/10_purpose-why.md +40 -40
  126. package/skills/brand-discovery/references/20_positioning.md +44 -44
  127. package/skills/brand-discovery/references/30_audience-niche.md +52 -52
  128. package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
  129. package/skills/brand-discovery/references/50_voice-tone.md +59 -59
  130. package/skills/brand-discovery/references/60_narrative-story.md +50 -50
  131. package/skills/brand-discovery/references/70_founder-tension.md +49 -49
  132. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
  133. package/skills/brand-voice/SKILL.md +98 -98
  134. package/skills/brand-voice/references/voice-profile-schema.md +55 -55
  135. package/skills/browser-qa/SKILL.md +105 -105
  136. package/skills/bun-runtime/SKILL.md +85 -85
  137. package/skills/canary-watch/SKILL.md +108 -108
  138. package/skills/carrier-relationship-management/SKILL.md +212 -212
  139. package/skills/cisco-ios-patterns/SKILL.md +164 -164
  140. package/skills/ck/SKILL.md +148 -148
  141. package/skills/claude-devfleet/SKILL.md +112 -112
  142. package/skills/click-path-audit/SKILL.md +245 -245
  143. package/skills/clickhouse-io/SKILL.md +445 -445
  144. package/skills/code-tour/SKILL.md +254 -254
  145. package/skills/codebase-onboarding/SKILL.md +234 -234
  146. package/skills/codehealth-mcp/SKILL.md +167 -167
  147. package/skills/codeply-taste/LICENSE +21 -0
  148. package/skills/codeply-taste/SKILL.md +100 -0
  149. package/skills/codeply-taste/SOURCE.md +10 -0
  150. package/skills/codeply-taste/assets/base.css +100 -0
  151. package/skills/codeply-taste/assets/base.js +37 -0
  152. package/skills/codeply-taste/library/directions.json +1078 -0
  153. package/skills/codeply-taste/library/heroes.json +894 -0
  154. package/skills/codeply-taste/library/motions.json +57 -0
  155. package/skills/codeply-taste/library/palettes.json +1485 -0
  156. package/skills/codeply-taste/library/photos.json +7240 -0
  157. package/skills/codeply-taste/library/sections.json +4136 -0
  158. package/skills/codeply-taste/library/sites.json +7274 -0
  159. package/skills/codeply-taste/library/type.json +2024 -0
  160. package/skills/codeply-taste/references/colour.md +141 -0
  161. package/skills/codeply-taste/references/copy.md +177 -0
  162. package/skills/codeply-taste/references/critique.md +171 -0
  163. package/skills/codeply-taste/references/imagery.md +377 -0
  164. package/skills/codeply-taste/references/layout.md +178 -0
  165. package/skills/codeply-taste/references/motion.md +292 -0
  166. package/skills/codeply-taste/references/research.md +306 -0
  167. package/skills/codeply-taste/references/tells.md +115 -0
  168. package/skills/codeply-taste/references/type.md +170 -0
  169. package/skills/codeply-taste/scripts/assets.mjs +865 -0
  170. package/skills/codeply-taste/scripts/draw.mjs +525 -0
  171. package/skills/codeply-taste/scripts/inspect.mjs +205 -0
  172. package/skills/codeply-taste/scripts/lib/cdp.mjs +205 -0
  173. package/skills/codeply-taste/scripts/lib/probe.mjs +569 -0
  174. package/skills/codeply-taste/scripts/lib/rules.mjs +137 -0
  175. package/skills/codeply-taste/scripts/lint.mjs +108 -0
  176. package/skills/coding-standards/SKILL.md +551 -551
  177. package/skills/competitive-platform-analysis/SKILL.md +214 -214
  178. package/skills/competitive-report-structure/SKILL.md +162 -162
  179. package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
  180. package/skills/config-gc/SKILL.md +120 -120
  181. package/skills/configure-ecc/SKILL.md +385 -385
  182. package/skills/connections-optimizer/SKILL.md +190 -190
  183. package/skills/content-engine/SKILL.md +132 -132
  184. package/skills/content-hash-cache-pattern/SKILL.md +162 -162
  185. package/skills/context-budget/SKILL.md +136 -136
  186. package/skills/continuous-agent-loop/SKILL.md +46 -46
  187. package/skills/continuous-learning/SKILL.md +132 -132
  188. package/skills/continuous-learning-v2/SKILL.md +361 -361
  189. package/skills/contract-first/SKILL.md +287 -287
  190. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
  191. package/skills/cost-tracking/SKILL.md +97 -97
  192. package/skills/council/SKILL.md +204 -204
  193. package/skills/cpp-coding-standards/SKILL.md +724 -724
  194. package/skills/cpp-testing/SKILL.md +325 -325
  195. package/skills/crosspost/SKILL.md +112 -112
  196. package/skills/csharp-testing/SKILL.md +322 -322
  197. package/skills/customer-billing-ops/SKILL.md +141 -141
  198. package/skills/customs-trade-compliance/SKILL.md +263 -263
  199. package/skills/dart-flutter-patterns/SKILL.md +564 -564
  200. package/skills/dashboard-builder/SKILL.md +109 -109
  201. package/skills/data-scraper-agent/SKILL.md +765 -765
  202. package/skills/data-throughput-accelerator/SKILL.md +74 -74
  203. package/skills/database-migrations/SKILL.md +430 -430
  204. package/skills/deep-research/SKILL.md +160 -160
  205. package/skills/defi-amm-security/SKILL.md +167 -167
  206. package/skills/delivery-gate/SKILL.md +126 -126
  207. package/skills/deployment-patterns/SKILL.md +428 -428
  208. package/skills/design-system/SKILL.md +83 -83
  209. package/skills/django-celery/SKILL.md +458 -458
  210. package/skills/django-patterns/SKILL.md +735 -735
  211. package/skills/django-security/SKILL.md +644 -644
  212. package/skills/django-tdd/SKILL.md +730 -730
  213. package/skills/django-verification/SKILL.md +470 -470
  214. package/skills/dmux-workflows/SKILL.md +192 -192
  215. package/skills/docker-patterns/SKILL.md +365 -365
  216. package/skills/documentation-lookup/SKILL.md +91 -91
  217. package/skills/dotnet-patterns/SKILL.md +322 -322
  218. package/skills/dynamic-workflow-mode/SKILL.md +124 -124
  219. package/skills/e2e-testing/SKILL.md +327 -327
  220. package/skills/ecc-guide/SKILL.md +190 -190
  221. package/skills/ecc-recipes/SKILL.md +149 -149
  222. package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
  223. package/skills/email-ops/SKILL.md +122 -122
  224. package/skills/energy-procurement/SKILL.md +228 -228
  225. package/skills/enterprise-agent-ops/SKILL.md +51 -51
  226. package/skills/error-handling/SKILL.md +377 -377
  227. package/skills/eval-harness/SKILL.md +271 -271
  228. package/skills/evm-token-decimals/SKILL.md +131 -131
  229. package/skills/exa-search/SKILL.md +108 -108
  230. package/skills/fal-ai-media/SKILL.md +289 -289
  231. package/skills/fastapi-patterns/SKILL.md +514 -514
  232. package/skills/finance-billing-ops/SKILL.md +128 -128
  233. package/skills/flox-environments/SKILL.md +497 -497
  234. package/skills/flutter-dart-code-review/SKILL.md +436 -436
  235. package/skills/foundation-models-on-device/SKILL.md +243 -243
  236. package/skills/frontend-a11y/SKILL.md +446 -446
  237. package/skills/frontend-design-direction/SKILL.md +93 -93
  238. package/skills/frontend-patterns/SKILL.md +657 -657
  239. package/skills/frontend-slides/SKILL.md +185 -185
  240. package/skills/fsharp-testing/SKILL.md +281 -281
  241. package/skills/gan-style-harness/SKILL.md +279 -279
  242. package/skills/gateguard/SKILL.md +133 -133
  243. package/skills/generating-python-installer/SKILL.md +820 -820
  244. package/skills/git-workflow/SKILL.md +716 -716
  245. package/skills/github-ops/SKILL.md +145 -145
  246. package/skills/golang-patterns/SKILL.md +676 -676
  247. package/skills/golang-testing/SKILL.md +721 -721
  248. package/skills/google-workspace-ops/SKILL.md +96 -96
  249. package/skills/growth-log/SKILL.md +128 -128
  250. package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
  251. package/skills/healthcare-emr-patterns/SKILL.md +160 -160
  252. package/skills/healthcare-eval-harness/SKILL.md +208 -208
  253. package/skills/healthcare-phi-compliance/SKILL.md +146 -146
  254. package/skills/hermes-imports/SKILL.md +89 -89
  255. package/skills/hexagonal-architecture/SKILL.md +277 -277
  256. package/skills/hipaa-compliance/SKILL.md +79 -79
  257. package/skills/homelab-network-readiness/SKILL.md +170 -170
  258. package/skills/homelab-network-setup/SKILL.md +130 -130
  259. package/skills/homelab-pihole-dns/SKILL.md +275 -275
  260. package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
  261. package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
  262. package/skills/hookify-rules/SKILL.md +128 -128
  263. package/skills/inherit-legacy-style/SKILL.md +157 -157
  264. package/skills/intent-driven-development/SKILL.md +360 -360
  265. package/skills/inventory-demand-planning/SKILL.md +247 -247
  266. package/skills/investor-materials/SKILL.md +97 -97
  267. package/skills/investor-outreach/SKILL.md +92 -92
  268. package/skills/ios-icon-gen/SKILL.md +158 -158
  269. package/skills/iterative-retrieval/SKILL.md +212 -212
  270. package/skills/ito-basket-compare/SKILL.md +64 -64
  271. package/skills/ito-compute/SKILL.md +129 -129
  272. package/skills/ito-data-atlas-agent/SKILL.md +64 -64
  273. package/skills/ito-market-intelligence/SKILL.md +61 -61
  274. package/skills/ito-trade-planner/SKILL.md +68 -68
  275. package/skills/java-coding-standards/SKILL.md +384 -384
  276. package/skills/jira-integration/SKILL.md +303 -303
  277. package/skills/jpa-patterns/SKILL.md +152 -152
  278. package/skills/knowledge-ops/SKILL.md +155 -155
  279. package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
  280. package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
  281. package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
  282. package/skills/kotlin-patterns/SKILL.md +712 -712
  283. package/skills/kotlin-testing/SKILL.md +825 -825
  284. package/skills/kubernetes-patterns/SKILL.md +756 -756
  285. package/skills/laravel-patterns/SKILL.md +416 -416
  286. package/skills/laravel-plugin-discovery/SKILL.md +230 -230
  287. package/skills/laravel-security/SKILL.md +948 -948
  288. package/skills/laravel-tdd/SKILL.md +675 -675
  289. package/skills/laravel-verification/SKILL.md +180 -180
  290. package/skills/latency-critical-systems/SKILL.md +75 -75
  291. package/skills/lead-intelligence/SKILL.md +322 -322
  292. package/skills/liquid-glass-design/SKILL.md +279 -279
  293. package/skills/llm-trading-agent-security/SKILL.md +147 -147
  294. package/skills/logistics-exception-management/SKILL.md +222 -222
  295. package/skills/loop-design-check/SKILL.md +143 -143
  296. package/skills/mailtrap-email-integration/SKILL.md +77 -77
  297. package/skills/make-interfaces-feel-better/SKILL.md +152 -152
  298. package/skills/manim-video/SKILL.md +90 -90
  299. package/skills/market-research/SKILL.md +76 -76
  300. package/skills/marketing-campaign/SKILL.md +114 -114
  301. package/skills/mcp-server-patterns/SKILL.md +70 -70
  302. package/skills/messages-ops/SKILL.md +105 -105
  303. package/skills/ml-adoption-playbook/SKILL.md +57 -57
  304. package/skills/mle-workflow/SKILL.md +348 -348
  305. package/skills/motion-advanced/SKILL.md +596 -596
  306. package/skills/motion-foundations/SKILL.md +299 -299
  307. package/skills/motion-patterns/SKILL.md +434 -434
  308. package/skills/motion-ui/SKILL.md +576 -576
  309. package/skills/mysql-patterns/SKILL.md +413 -413
  310. package/skills/nanoclaw-repl/SKILL.md +34 -34
  311. package/skills/nestjs-patterns/SKILL.md +231 -231
  312. package/skills/netmiko-ssh-automation/SKILL.md +174 -174
  313. package/skills/network-bgp-diagnostics/SKILL.md +168 -168
  314. package/skills/network-config-validation/SKILL.md +211 -211
  315. package/skills/network-interface-health/SKILL.md +153 -153
  316. package/skills/nextjs-turbopack/SKILL.md +58 -58
  317. package/skills/nodejs-keccak256/SKILL.md +103 -103
  318. package/skills/nutrient-document-processing/SKILL.md +168 -168
  319. package/skills/nuxt4-patterns/SKILL.md +101 -101
  320. package/skills/openclaw-persona-forge/SKILL.md +289 -289
  321. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
  322. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
  323. package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
  324. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
  325. package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
  326. package/skills/openclaw-persona-forge/references/output-template.md +166 -166
  327. package/skills/opensource-pipeline/SKILL.md +256 -256
  328. package/skills/orch-add-feature/SKILL.md +45 -45
  329. package/skills/orch-build-mvp/SKILL.md +49 -49
  330. package/skills/orch-change-feature/SKILL.md +43 -43
  331. package/skills/orch-fix-defect/SKILL.md +43 -43
  332. package/skills/orch-pipeline/SKILL.md +121 -121
  333. package/skills/orch-refine-code/SKILL.md +44 -44
  334. package/skills/parallel-execution-optimizer/SKILL.md +74 -74
  335. package/skills/perl-patterns/SKILL.md +505 -505
  336. package/skills/perl-security/SKILL.md +504 -504
  337. package/skills/perl-testing/SKILL.md +476 -476
  338. package/skills/plan-canvas/SKILL.md +153 -153
  339. package/skills/plan-orchestrate/SKILL.md +263 -263
  340. package/skills/plankton-code-quality/SKILL.md +237 -237
  341. package/skills/postgres-patterns/SKILL.md +148 -148
  342. package/skills/prediction-market-oracle-research/SKILL.md +64 -64
  343. package/skills/prediction-market-risk-review/SKILL.md +61 -61
  344. package/skills/premium-web-design/SKILL.md +154 -154
  345. package/skills/prisma-patterns/SKILL.md +401 -401
  346. package/skills/product-capability/SKILL.md +142 -142
  347. package/skills/product-lens/SKILL.md +93 -93
  348. package/skills/production-audit/SKILL.md +207 -207
  349. package/skills/production-scheduling/SKILL.md +238 -238
  350. package/skills/project-flow-ops/SKILL.md +112 -112
  351. package/skills/prompt-optimizer/SKILL.md +398 -398
  352. package/skills/python-patterns/SKILL.md +751 -751
  353. package/skills/python-testing/SKILL.md +817 -817
  354. package/skills/pytorch-patterns/SKILL.md +397 -397
  355. package/skills/quality-nonconformance/SKILL.md +260 -260
  356. package/skills/quarkus-patterns/SKILL.md +723 -723
  357. package/skills/quarkus-security/SKILL.md +468 -468
  358. package/skills/quarkus-tdd/SKILL.md +812 -812
  359. package/skills/quarkus-verification/SKILL.md +480 -480
  360. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
  361. package/skills/react-native-patterns/SKILL.md +326 -326
  362. package/skills/react-patterns/SKILL.md +342 -342
  363. package/skills/react-performance/SKILL.md +575 -575
  364. package/skills/react-testing/SKILL.md +424 -424
  365. package/skills/recsys-pipeline-architect/SKILL.md +115 -115
  366. package/skills/recursive-decision-ledger/SKILL.md +81 -81
  367. package/skills/redis-patterns/SKILL.md +404 -404
  368. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
  369. package/skills/remotion-video-creation/SKILL.md +43 -43
  370. package/skills/repo-scan/SKILL.md +79 -79
  371. package/skills/research-ops/SKILL.md +113 -113
  372. package/skills/returns-reverse-logistics/SKILL.md +240 -240
  373. package/skills/rules-distill/SKILL.md +265 -265
  374. package/skills/rust-patterns/SKILL.md +500 -500
  375. package/skills/rust-testing/SKILL.md +501 -501
  376. package/skills/safety-guard/SKILL.md +76 -76
  377. package/skills/santa-method/SKILL.md +307 -307
  378. package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
  379. package/skills/scientific-db-uspto-database/SKILL.md +178 -178
  380. package/skills/scientific-pkg-gget/SKILL.md +167 -167
  381. package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
  382. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
  383. package/skills/search-first/SKILL.md +183 -183
  384. package/skills/security-bounty-hunter/SKILL.md +100 -100
  385. package/skills/security-review/SKILL.md +504 -504
  386. package/skills/security-scan/SKILL.md +166 -166
  387. package/skills/seo/SKILL.md +155 -155
  388. package/skills/skill-comply/SKILL.md +59 -59
  389. package/skills/skill-scout/SKILL.md +141 -141
  390. package/skills/skill-stocktake/SKILL.md +195 -195
  391. package/skills/social-graph-ranker/SKILL.md +155 -155
  392. package/skills/social-publisher/SKILL.md +130 -130
  393. package/skills/springboot-patterns/SKILL.md +315 -315
  394. package/skills/springboot-security/SKILL.md +273 -273
  395. package/skills/springboot-tdd/SKILL.md +159 -159
  396. package/skills/springboot-verification/SKILL.md +232 -232
  397. package/skills/strategic-compact/SKILL.md +142 -142
  398. package/skills/swift-actor-persistence/SKILL.md +144 -144
  399. package/skills/swift-concurrency-6-2/SKILL.md +216 -216
  400. package/skills/swift-protocol-di-testing/SKILL.md +191 -191
  401. package/skills/swiftui-patterns/SKILL.md +259 -259
  402. package/skills/taste/SKILL.md +264 -264
  403. package/skills/taste/references/genre-taxonomy.md +87 -87
  404. package/skills/tdd-workflow/SKILL.md +583 -583
  405. package/skills/team-agent-orchestration/SKILL.md +111 -111
  406. package/skills/team-builder/SKILL.md +169 -169
  407. package/skills/terminal-ops/SKILL.md +110 -110
  408. package/skills/tinystruct-patterns/SKILL.md +279 -279
  409. package/skills/tinystruct-patterns/references/architecture.md +90 -90
  410. package/skills/tinystruct-patterns/references/data-handling.md +60 -60
  411. package/skills/tinystruct-patterns/references/database.md +99 -99
  412. package/skills/tinystruct-patterns/references/routing.md +64 -64
  413. package/skills/tinystruct-patterns/references/system-usage.md +97 -97
  414. package/skills/tinystruct-patterns/references/testing.md +72 -72
  415. package/skills/token-budget-advisor/SKILL.md +134 -134
  416. package/skills/ui-demo/SKILL.md +466 -466
  417. package/skills/ui-to-vue/SKILL.md +135 -135
  418. package/skills/uncloud/SKILL.md +344 -344
  419. package/skills/unified-memory/SKILL.md +170 -170
  420. package/skills/unified-notifications-ops/SKILL.md +188 -188
  421. package/skills/verification-loop/SKILL.md +129 -129
  422. package/skills/video-editing/SKILL.md +311 -311
  423. package/skills/videodb/SKILL.md +375 -375
  424. package/skills/visa-doc-translate/SKILL.md +117 -117
  425. package/skills/vite-patterns/SKILL.md +450 -450
  426. package/skills/vue-patterns/SKILL.md +471 -471
  427. package/skills/windows-desktop-e2e/SKILL.md +888 -888
  428. package/skills/workspace-surface-audit/SKILL.md +126 -126
  429. 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: `Gmail draft failed: ${e.message}` };
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
- return { ok: true, output: `Email sent to ${email.to} (message id ${result.id}).${note}`, meta: { label: `to ${email.to}: ${email.subject}` } };
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: `Gmail send failed: ${e.message}` };
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
- return `- ${title}: ${when}${ev.location ? `, at ${ev.location}` : ''}`;
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: `Calendar list failed: ${e.message} Tell the user this exact error.` };
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: `Calendar add failed: ${e.message} Tell the user this exact error.` };
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: `Gmail search failed: ${e.message} Do not retry with other queries; tell the user this exact error.` };
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
- // Posting defaults to the user token when connected - messages read as
1061
- // coming from the actual person, not the "Codeply Craft APP" bot. Falls
1062
- // back to the bot token for anyone who connected before user-token
1063
- // support existed, or who only approved the bot scopes.
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
- meta: { label: displayCommand, exitCode: effectiveCode, ...(effectiveCode !== code ? { maskedFailure: true } : {}) },
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
- const reach_me = (args, ctx) => reachLib.reachMe(args, ctx);
2241
- const watch_email = (args, ctx) => reachLib.watchEmail(args, ctx);
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.` };