codeply-cli 0.5.1 → 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.
Files changed (387) hide show
  1. package/LICENSE +21 -0
  2. package/THIRD_PARTY_NOTICES.md +65 -63
  3. package/bin/codeply.js +1 -1
  4. package/lib/agent.mjs +133 -24
  5. package/lib/ai.js +268 -9
  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/bots.js +82 -17
  12. package/lib/chatgpt.js +282 -0
  13. package/lib/cloud-browser.mjs +143 -143
  14. package/lib/cloud.mjs +1186 -1144
  15. package/lib/commands.js +103 -103
  16. package/lib/config.js +83 -3
  17. package/lib/design-library/assemble-artifact.mjs +21 -21
  18. package/lib/design-library/cleanup.mjs +45 -45
  19. package/lib/design-library/index.json +22379 -22379
  20. package/lib/design-library/make-table-data.mjs +39 -39
  21. package/lib/diagnostics.js +192 -192
  22. package/lib/edit-engine.js +364 -364
  23. package/lib/github-agent.mjs +311 -311
  24. package/lib/history.js +75 -75
  25. package/lib/mail-watch.js +504 -0
  26. package/lib/mcp.js +258 -258
  27. package/lib/model-sync.js +114 -0
  28. package/lib/native-tools.mjs +16 -1
  29. package/lib/oauth-connectors.js +146 -24
  30. package/lib/permissions.js +109 -109
  31. package/lib/plugins.js +353 -353
  32. package/lib/publish.js +916 -0
  33. package/lib/reach.js +67 -0
  34. package/lib/server.mjs +457 -457
  35. package/lib/session-db.js +220 -220
  36. package/lib/share.js +168 -168
  37. package/lib/skills.js +2 -0
  38. package/lib/snapshot.js +146 -146
  39. package/lib/telemetry.js +102 -0
  40. package/lib/terminal-hints.js +154 -154
  41. package/lib/tools.mjs +316 -14
  42. package/lib/typecheck-worker.js +147 -147
  43. package/lib/web-tools.js +209 -158
  44. package/package.json +49 -49
  45. package/skills/SOURCE.md +58 -58
  46. package/skills/accessibility/SKILL.md +147 -147
  47. package/skills/agent-architecture-audit/SKILL.md +257 -257
  48. package/skills/agent-eval/SKILL.md +147 -147
  49. package/skills/agent-harness-construction/SKILL.md +74 -74
  50. package/skills/agent-introspection-debugging/SKILL.md +154 -154
  51. package/skills/agent-payment-x402/SKILL.md +225 -225
  52. package/skills/agent-self-evaluation/SKILL.md +182 -182
  53. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
  54. package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
  55. package/skills/agent-sort/SKILL.md +216 -216
  56. package/skills/agentic-engineering/SKILL.md +64 -64
  57. package/skills/agentic-os/SKILL.md +388 -388
  58. package/skills/ai-first-engineering/SKILL.md +52 -52
  59. package/skills/ai-regression-testing/SKILL.md +386 -386
  60. package/skills/android-clean-architecture/SKILL.md +340 -340
  61. package/skills/angular-developer/SKILL.md +155 -155
  62. package/skills/angular-developer/references/angular-animations.md +160 -160
  63. package/skills/angular-developer/references/angular-aria.md +410 -410
  64. package/skills/angular-developer/references/cli.md +86 -86
  65. package/skills/angular-developer/references/component-harnesses.md +59 -59
  66. package/skills/angular-developer/references/component-styling.md +91 -91
  67. package/skills/angular-developer/references/components.md +117 -117
  68. package/skills/angular-developer/references/creating-services.md +97 -97
  69. package/skills/angular-developer/references/data-resolvers.md +69 -69
  70. package/skills/angular-developer/references/define-routes.md +67 -67
  71. package/skills/angular-developer/references/defining-providers.md +72 -72
  72. package/skills/angular-developer/references/di-fundamentals.md +120 -120
  73. package/skills/angular-developer/references/e2e-testing.md +56 -56
  74. package/skills/angular-developer/references/effects.md +83 -83
  75. package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
  76. package/skills/angular-developer/references/host-elements.md +80 -80
  77. package/skills/angular-developer/references/injection-context.md +63 -63
  78. package/skills/angular-developer/references/inputs.md +101 -101
  79. package/skills/angular-developer/references/linked-signal.md +59 -59
  80. package/skills/angular-developer/references/loading-strategies.md +61 -61
  81. package/skills/angular-developer/references/mcp.md +108 -108
  82. package/skills/angular-developer/references/navigate-to-routes.md +69 -69
  83. package/skills/angular-developer/references/outputs.md +86 -86
  84. package/skills/angular-developer/references/reactive-forms.md +122 -122
  85. package/skills/angular-developer/references/rendering-strategies.md +44 -44
  86. package/skills/angular-developer/references/resource.md +77 -77
  87. package/skills/angular-developer/references/route-animations.md +56 -56
  88. package/skills/angular-developer/references/route-guards.md +52 -52
  89. package/skills/angular-developer/references/router-lifecycle.md +45 -45
  90. package/skills/angular-developer/references/router-testing.md +87 -87
  91. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
  92. package/skills/angular-developer/references/signal-forms.md +795 -795
  93. package/skills/angular-developer/references/signals-overview.md +94 -94
  94. package/skills/angular-developer/references/tailwind-css.md +69 -69
  95. package/skills/angular-developer/references/template-driven-forms.md +114 -114
  96. package/skills/angular-developer/references/testing-fundamentals.md +65 -65
  97. package/skills/api-connector-builder/SKILL.md +121 -121
  98. package/skills/api-design/SKILL.md +524 -524
  99. package/skills/architecture-decision-records/SKILL.md +180 -180
  100. package/skills/article-writing/SKILL.md +80 -80
  101. package/skills/automation-audit-ops/SKILL.md +143 -143
  102. package/skills/autonomous-agent-harness/SKILL.md +274 -274
  103. package/skills/autonomous-loops/SKILL.md +611 -611
  104. package/skills/backend-patterns/SKILL.md +562 -562
  105. package/skills/benchmark/SKILL.md +95 -95
  106. package/skills/benchmark-methodology/SKILL.md +191 -191
  107. package/skills/benchmark-optimization-loop/SKILL.md +71 -71
  108. package/skills/blender-motion-state-inspection/SKILL.md +165 -165
  109. package/skills/blueprint/SKILL.md +106 -106
  110. package/skills/brand-discovery/SKILL.md +145 -145
  111. package/skills/brand-discovery/references/10_purpose-why.md +40 -40
  112. package/skills/brand-discovery/references/20_positioning.md +44 -44
  113. package/skills/brand-discovery/references/30_audience-niche.md +52 -52
  114. package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
  115. package/skills/brand-discovery/references/50_voice-tone.md +59 -59
  116. package/skills/brand-discovery/references/60_narrative-story.md +50 -50
  117. package/skills/brand-discovery/references/70_founder-tension.md +49 -49
  118. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
  119. package/skills/brand-voice/SKILL.md +98 -98
  120. package/skills/brand-voice/references/voice-profile-schema.md +55 -55
  121. package/skills/browser-qa/SKILL.md +105 -105
  122. package/skills/bun-runtime/SKILL.md +85 -85
  123. package/skills/canary-watch/SKILL.md +108 -108
  124. package/skills/carrier-relationship-management/SKILL.md +212 -212
  125. package/skills/cisco-ios-patterns/SKILL.md +164 -164
  126. package/skills/ck/SKILL.md +148 -148
  127. package/skills/claude-devfleet/SKILL.md +112 -112
  128. package/skills/click-path-audit/SKILL.md +245 -245
  129. package/skills/clickhouse-io/SKILL.md +445 -445
  130. package/skills/code-tour/SKILL.md +254 -254
  131. package/skills/codebase-onboarding/SKILL.md +234 -234
  132. package/skills/codehealth-mcp/SKILL.md +167 -167
  133. package/skills/coding-standards/SKILL.md +551 -551
  134. package/skills/competitive-platform-analysis/SKILL.md +214 -214
  135. package/skills/competitive-report-structure/SKILL.md +162 -162
  136. package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
  137. package/skills/config-gc/SKILL.md +120 -120
  138. package/skills/configure-ecc/SKILL.md +385 -385
  139. package/skills/connections-optimizer/SKILL.md +190 -190
  140. package/skills/content-engine/SKILL.md +132 -132
  141. package/skills/content-hash-cache-pattern/SKILL.md +162 -162
  142. package/skills/context-budget/SKILL.md +136 -136
  143. package/skills/continuous-agent-loop/SKILL.md +46 -46
  144. package/skills/continuous-learning/SKILL.md +132 -132
  145. package/skills/continuous-learning-v2/SKILL.md +361 -361
  146. package/skills/contract-first/SKILL.md +287 -287
  147. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
  148. package/skills/cost-tracking/SKILL.md +97 -97
  149. package/skills/council/SKILL.md +204 -204
  150. package/skills/cpp-coding-standards/SKILL.md +724 -724
  151. package/skills/cpp-testing/SKILL.md +325 -325
  152. package/skills/crosspost/SKILL.md +112 -112
  153. package/skills/csharp-testing/SKILL.md +322 -322
  154. package/skills/customer-billing-ops/SKILL.md +141 -141
  155. package/skills/customs-trade-compliance/SKILL.md +263 -263
  156. package/skills/dart-flutter-patterns/SKILL.md +564 -564
  157. package/skills/dashboard-builder/SKILL.md +109 -109
  158. package/skills/data-scraper-agent/SKILL.md +765 -765
  159. package/skills/data-throughput-accelerator/SKILL.md +74 -74
  160. package/skills/database-migrations/SKILL.md +430 -430
  161. package/skills/deep-research/SKILL.md +160 -160
  162. package/skills/defi-amm-security/SKILL.md +167 -167
  163. package/skills/delivery-gate/SKILL.md +126 -126
  164. package/skills/deployment-patterns/SKILL.md +428 -428
  165. package/skills/design-system/SKILL.md +83 -83
  166. package/skills/django-celery/SKILL.md +458 -458
  167. package/skills/django-patterns/SKILL.md +735 -735
  168. package/skills/django-security/SKILL.md +644 -644
  169. package/skills/django-tdd/SKILL.md +730 -730
  170. package/skills/django-verification/SKILL.md +470 -470
  171. package/skills/dmux-workflows/SKILL.md +192 -192
  172. package/skills/docker-patterns/SKILL.md +365 -365
  173. package/skills/documentation-lookup/SKILL.md +91 -91
  174. package/skills/dotnet-patterns/SKILL.md +322 -322
  175. package/skills/dynamic-workflow-mode/SKILL.md +124 -124
  176. package/skills/e2e-testing/SKILL.md +327 -327
  177. package/skills/ecc-guide/SKILL.md +190 -190
  178. package/skills/ecc-recipes/SKILL.md +149 -149
  179. package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
  180. package/skills/email-ops/SKILL.md +122 -122
  181. package/skills/energy-procurement/SKILL.md +228 -228
  182. package/skills/enterprise-agent-ops/SKILL.md +51 -51
  183. package/skills/error-handling/SKILL.md +377 -377
  184. package/skills/eval-harness/SKILL.md +271 -271
  185. package/skills/evm-token-decimals/SKILL.md +131 -131
  186. package/skills/exa-search/SKILL.md +108 -108
  187. package/skills/fal-ai-media/SKILL.md +289 -289
  188. package/skills/fastapi-patterns/SKILL.md +514 -514
  189. package/skills/finance-billing-ops/SKILL.md +128 -128
  190. package/skills/flox-environments/SKILL.md +497 -497
  191. package/skills/flutter-dart-code-review/SKILL.md +436 -436
  192. package/skills/foundation-models-on-device/SKILL.md +243 -243
  193. package/skills/frontend-a11y/SKILL.md +446 -446
  194. package/skills/frontend-design-direction/SKILL.md +93 -93
  195. package/skills/frontend-patterns/SKILL.md +657 -657
  196. package/skills/frontend-slides/SKILL.md +185 -185
  197. package/skills/fsharp-testing/SKILL.md +281 -281
  198. package/skills/gan-style-harness/SKILL.md +279 -279
  199. package/skills/gateguard/SKILL.md +133 -133
  200. package/skills/generating-python-installer/SKILL.md +820 -820
  201. package/skills/git-workflow/SKILL.md +716 -716
  202. package/skills/github-ops/SKILL.md +145 -145
  203. package/skills/golang-patterns/SKILL.md +676 -676
  204. package/skills/golang-testing/SKILL.md +721 -721
  205. package/skills/google-workspace-ops/SKILL.md +96 -96
  206. package/skills/growth-log/SKILL.md +128 -128
  207. package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
  208. package/skills/healthcare-emr-patterns/SKILL.md +160 -160
  209. package/skills/healthcare-eval-harness/SKILL.md +208 -208
  210. package/skills/healthcare-phi-compliance/SKILL.md +146 -146
  211. package/skills/hermes-imports/SKILL.md +89 -89
  212. package/skills/hexagonal-architecture/SKILL.md +277 -277
  213. package/skills/hipaa-compliance/SKILL.md +79 -79
  214. package/skills/homelab-network-readiness/SKILL.md +170 -170
  215. package/skills/homelab-network-setup/SKILL.md +130 -130
  216. package/skills/homelab-pihole-dns/SKILL.md +275 -275
  217. package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
  218. package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
  219. package/skills/hookify-rules/SKILL.md +128 -128
  220. package/skills/inherit-legacy-style/SKILL.md +157 -157
  221. package/skills/intent-driven-development/SKILL.md +360 -360
  222. package/skills/inventory-demand-planning/SKILL.md +247 -247
  223. package/skills/investor-materials/SKILL.md +97 -97
  224. package/skills/investor-outreach/SKILL.md +92 -92
  225. package/skills/ios-icon-gen/SKILL.md +158 -158
  226. package/skills/iterative-retrieval/SKILL.md +212 -212
  227. package/skills/ito-basket-compare/SKILL.md +64 -64
  228. package/skills/ito-compute/SKILL.md +129 -129
  229. package/skills/ito-data-atlas-agent/SKILL.md +64 -64
  230. package/skills/ito-market-intelligence/SKILL.md +61 -61
  231. package/skills/ito-trade-planner/SKILL.md +68 -68
  232. package/skills/java-coding-standards/SKILL.md +384 -384
  233. package/skills/jira-integration/SKILL.md +303 -303
  234. package/skills/jpa-patterns/SKILL.md +152 -152
  235. package/skills/knowledge-ops/SKILL.md +155 -155
  236. package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
  237. package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
  238. package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
  239. package/skills/kotlin-patterns/SKILL.md +712 -712
  240. package/skills/kotlin-testing/SKILL.md +825 -825
  241. package/skills/kubernetes-patterns/SKILL.md +756 -756
  242. package/skills/laravel-patterns/SKILL.md +416 -416
  243. package/skills/laravel-plugin-discovery/SKILL.md +230 -230
  244. package/skills/laravel-security/SKILL.md +948 -948
  245. package/skills/laravel-tdd/SKILL.md +675 -675
  246. package/skills/laravel-verification/SKILL.md +180 -180
  247. package/skills/latency-critical-systems/SKILL.md +75 -75
  248. package/skills/lead-intelligence/SKILL.md +322 -322
  249. package/skills/liquid-glass-design/SKILL.md +279 -279
  250. package/skills/llm-trading-agent-security/SKILL.md +147 -147
  251. package/skills/logistics-exception-management/SKILL.md +222 -222
  252. package/skills/loop-design-check/SKILL.md +143 -143
  253. package/skills/mailtrap-email-integration/SKILL.md +77 -77
  254. package/skills/make-interfaces-feel-better/SKILL.md +152 -152
  255. package/skills/manim-video/SKILL.md +90 -90
  256. package/skills/market-research/SKILL.md +76 -76
  257. package/skills/marketing-campaign/SKILL.md +114 -114
  258. package/skills/mcp-server-patterns/SKILL.md +70 -70
  259. package/skills/messages-ops/SKILL.md +105 -105
  260. package/skills/ml-adoption-playbook/SKILL.md +57 -57
  261. package/skills/mle-workflow/SKILL.md +348 -348
  262. package/skills/motion-advanced/SKILL.md +596 -596
  263. package/skills/motion-foundations/SKILL.md +299 -299
  264. package/skills/motion-patterns/SKILL.md +434 -434
  265. package/skills/motion-ui/SKILL.md +576 -576
  266. package/skills/mysql-patterns/SKILL.md +413 -413
  267. package/skills/nanoclaw-repl/SKILL.md +34 -34
  268. package/skills/nestjs-patterns/SKILL.md +231 -231
  269. package/skills/netmiko-ssh-automation/SKILL.md +174 -174
  270. package/skills/network-bgp-diagnostics/SKILL.md +168 -168
  271. package/skills/network-config-validation/SKILL.md +211 -211
  272. package/skills/network-interface-health/SKILL.md +153 -153
  273. package/skills/nextjs-turbopack/SKILL.md +58 -58
  274. package/skills/nodejs-keccak256/SKILL.md +103 -103
  275. package/skills/nutrient-document-processing/SKILL.md +168 -168
  276. package/skills/nuxt4-patterns/SKILL.md +101 -101
  277. package/skills/openclaw-persona-forge/SKILL.md +289 -289
  278. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
  279. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
  280. package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
  281. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
  282. package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
  283. package/skills/openclaw-persona-forge/references/output-template.md +166 -166
  284. package/skills/opensource-pipeline/SKILL.md +256 -256
  285. package/skills/orch-add-feature/SKILL.md +45 -45
  286. package/skills/orch-build-mvp/SKILL.md +49 -49
  287. package/skills/orch-change-feature/SKILL.md +43 -43
  288. package/skills/orch-fix-defect/SKILL.md +43 -43
  289. package/skills/orch-pipeline/SKILL.md +121 -121
  290. package/skills/orch-refine-code/SKILL.md +44 -44
  291. package/skills/parallel-execution-optimizer/SKILL.md +74 -74
  292. package/skills/perl-patterns/SKILL.md +505 -505
  293. package/skills/perl-security/SKILL.md +504 -504
  294. package/skills/perl-testing/SKILL.md +476 -476
  295. package/skills/plan-canvas/SKILL.md +153 -153
  296. package/skills/plan-orchestrate/SKILL.md +263 -263
  297. package/skills/plankton-code-quality/SKILL.md +237 -237
  298. package/skills/postgres-patterns/SKILL.md +148 -148
  299. package/skills/prediction-market-oracle-research/SKILL.md +64 -64
  300. package/skills/prediction-market-risk-review/SKILL.md +61 -61
  301. package/skills/premium-web-design/SKILL.md +154 -154
  302. package/skills/prisma-patterns/SKILL.md +401 -401
  303. package/skills/product-capability/SKILL.md +142 -142
  304. package/skills/product-lens/SKILL.md +93 -93
  305. package/skills/production-audit/SKILL.md +207 -207
  306. package/skills/production-scheduling/SKILL.md +238 -238
  307. package/skills/project-flow-ops/SKILL.md +112 -112
  308. package/skills/prompt-optimizer/SKILL.md +398 -398
  309. package/skills/publish-website/SKILL.md +76 -0
  310. package/skills/python-patterns/SKILL.md +751 -751
  311. package/skills/python-testing/SKILL.md +817 -817
  312. package/skills/pytorch-patterns/SKILL.md +397 -397
  313. package/skills/quality-nonconformance/SKILL.md +260 -260
  314. package/skills/quarkus-patterns/SKILL.md +723 -723
  315. package/skills/quarkus-security/SKILL.md +468 -468
  316. package/skills/quarkus-tdd/SKILL.md +812 -812
  317. package/skills/quarkus-verification/SKILL.md +480 -480
  318. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
  319. package/skills/react-native-patterns/SKILL.md +326 -326
  320. package/skills/react-patterns/SKILL.md +342 -342
  321. package/skills/react-performance/SKILL.md +575 -575
  322. package/skills/react-testing/SKILL.md +424 -424
  323. package/skills/recsys-pipeline-architect/SKILL.md +115 -115
  324. package/skills/recursive-decision-ledger/SKILL.md +81 -81
  325. package/skills/redis-patterns/SKILL.md +404 -404
  326. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
  327. package/skills/remotion-video-creation/SKILL.md +43 -43
  328. package/skills/repo-scan/SKILL.md +79 -79
  329. package/skills/research-ops/SKILL.md +113 -113
  330. package/skills/returns-reverse-logistics/SKILL.md +240 -240
  331. package/skills/rules-distill/SKILL.md +265 -265
  332. package/skills/rust-patterns/SKILL.md +500 -500
  333. package/skills/rust-testing/SKILL.md +501 -501
  334. package/skills/safety-guard/SKILL.md +76 -76
  335. package/skills/santa-method/SKILL.md +307 -307
  336. package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
  337. package/skills/scientific-db-uspto-database/SKILL.md +178 -178
  338. package/skills/scientific-pkg-gget/SKILL.md +167 -167
  339. package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
  340. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
  341. package/skills/search-first/SKILL.md +183 -183
  342. package/skills/security-bounty-hunter/SKILL.md +100 -100
  343. package/skills/security-review/SKILL.md +504 -504
  344. package/skills/security-scan/SKILL.md +166 -166
  345. package/skills/seo/SKILL.md +155 -155
  346. package/skills/skill-comply/SKILL.md +59 -59
  347. package/skills/skill-scout/SKILL.md +141 -141
  348. package/skills/skill-stocktake/SKILL.md +195 -195
  349. package/skills/social-graph-ranker/SKILL.md +155 -155
  350. package/skills/social-publisher/SKILL.md +130 -130
  351. package/skills/springboot-patterns/SKILL.md +315 -315
  352. package/skills/springboot-security/SKILL.md +273 -273
  353. package/skills/springboot-tdd/SKILL.md +159 -159
  354. package/skills/springboot-verification/SKILL.md +232 -232
  355. package/skills/strategic-compact/SKILL.md +142 -142
  356. package/skills/swift-actor-persistence/SKILL.md +144 -144
  357. package/skills/swift-concurrency-6-2/SKILL.md +216 -216
  358. package/skills/swift-protocol-di-testing/SKILL.md +191 -191
  359. package/skills/swiftui-patterns/SKILL.md +259 -259
  360. package/skills/taste/SKILL.md +264 -264
  361. package/skills/taste/references/genre-taxonomy.md +87 -87
  362. package/skills/tdd-workflow/SKILL.md +583 -583
  363. package/skills/team-agent-orchestration/SKILL.md +111 -111
  364. package/skills/team-builder/SKILL.md +169 -169
  365. package/skills/terminal-ops/SKILL.md +110 -110
  366. package/skills/tinystruct-patterns/SKILL.md +279 -279
  367. package/skills/tinystruct-patterns/references/architecture.md +90 -90
  368. package/skills/tinystruct-patterns/references/data-handling.md +60 -60
  369. package/skills/tinystruct-patterns/references/database.md +99 -99
  370. package/skills/tinystruct-patterns/references/routing.md +64 -64
  371. package/skills/tinystruct-patterns/references/system-usage.md +97 -97
  372. package/skills/tinystruct-patterns/references/testing.md +72 -72
  373. package/skills/token-budget-advisor/SKILL.md +134 -134
  374. package/skills/ui-demo/SKILL.md +466 -466
  375. package/skills/ui-to-vue/SKILL.md +135 -135
  376. package/skills/uncloud/SKILL.md +344 -344
  377. package/skills/unified-memory/SKILL.md +170 -170
  378. package/skills/unified-notifications-ops/SKILL.md +188 -188
  379. package/skills/verification-loop/SKILL.md +129 -129
  380. package/skills/video-editing/SKILL.md +311 -311
  381. package/skills/videodb/SKILL.md +375 -375
  382. package/skills/visa-doc-translate/SKILL.md +117 -117
  383. package/skills/vite-patterns/SKILL.md +450 -450
  384. package/skills/vue-patterns/SKILL.md +471 -471
  385. package/skills/windows-desktop-e2e/SKILL.md +888 -888
  386. package/skills/workspace-surface-audit/SKILL.md +126 -126
  387. package/skills/x-api/SKILL.md +235 -235
@@ -32,8 +32,12 @@ export const TOOL_DOCS = {
32
32
  list_skills: { d: 'Search the full skill library.', req: ['query'], p: { query: 'One or two words.' } },
33
33
  fetch_image: { d: 'Download an image into the project.', req: ['url', 'path'], p: { url: 'Image URL.', path: 'Where to save it, e.g. assets/hero.jpg.' } },
34
34
  browser_check: { d: 'Open a page in a real browser and get errors plus a screenshot.', req: ['url'], p: { url: 'file:///absolute/path or http://localhost:port.', wait: 'Extra milliseconds to wait before capturing.', viewport: 'desktop (default), tablet, mobile, or WIDTHxHEIGHT.' } },
35
- gmail_send: { d: 'Send a real email now (no drafts). Only when the user asked for it.', req: ['to', 'subject', 'body'], p: { to: 'Recipient address.', subject: 'Subject line.', body: 'Plain-text body.' } },
35
+ gmail_send: { d: 'Send a real email now. Only when the user asked for it to go out. The user sees it on an editable card first and may change it or save it as a draft instead; the result says what really happened.', req: [], p: { to: 'Recipient address.', subject: 'Subject line.', body: 'Plain-text body.', draft: 'Optional: the id of a draft saved in Codeply (from drafts_list) to send; to/subject/body then come from it.' } },
36
36
  gmail_search: { d: 'Search the connected Gmail account. Leave query empty to list the newest mail.', req: [], p: { query: 'Gmail search syntax, e.g. "is:unread" or "from:x@y.com". Empty = newest mail.' } },
37
+ gmail_draft: { d: 'Save an email as a draft without sending it: in Gmail\'s Drafts when Gmail is connected, otherwise in Codeply\'s own drafts.', req: [], p: { to: 'Recipient address (optional for a draft).', subject: 'Subject line.', body: 'Plain-text body.' } },
38
+ drafts_list: { d: 'List the drafts saved in Codeply (ids, subject, recipient). Send one with gmail_send and its id as draft.', req: [], p: {} },
39
+ calendar_list: { d: 'List events on the user\'s Google Calendar in a time range (default: today). Times are local.', req: [], p: { from: 'Start: today, tomorrow, 2026-10-03 or 2026-10-03T14:00. Default today.', to: 'End (a date means through that whole day). Default: end of the from day.', max: 'Most events to return (1-50, default 20).' } },
40
+ calendar_add: { d: 'Add an event to the user\'s Google Calendar (asks the user first).', req: ['title', 'start'], p: { title: 'Event title.', start: 'Local start: 2026-10-03T14:00, or 2026-10-03 for an all-day event.', end: 'Local end. Default: one hour after start (all-day: the same day).', description: 'Notes for the event.', location: 'Where it is.', reminders: 'Popup reminders, minutes before, comma-separated, e.g. "10,60".' } },
37
41
  slack_post_message: { d: 'Post a real Slack message now. Only when the user asked for it.', req: ['channel', 'text'], p: { channel: 'Channel name or id.', text: 'Message text.' } },
38
42
  vercel_deploy: { d: 'Deploy a folder to the connected Vercel account (live, real).', req: [], p: { path: 'Folder to deploy. Default ".".' } },
39
43
  supabase_create_project: { d: 'Create a new Supabase project (billable, real).', req: ['name'], p: { name: 'Project name.' } },
@@ -44,7 +48,16 @@ export const TOOL_DOCS = {
44
48
  supabase_api: { d: 'Call the Supabase Management API.', req: ['method', 'path'], p: { method: 'GET, POST, PATCH, PUT or DELETE.', path: 'Path starting with /v1/.', body: 'JSON body.' } },
45
49
  supabase_sql: { d: 'Run SQL on a Supabase project database.', req: ['ref', 'query'], p: { ref: 'Project ref.', query: 'SQL.' } },
46
50
  vercel_api: { d: 'Call the Vercel REST API.', req: ['method', 'path'], p: { method: 'HTTP method.', path: 'Path like /v9/projects.', body: 'JSON body.' } },
51
+ publish_check: { d: 'Publishing only: read the project for publishing (stack, database signals, connections, earlier publishes).', req: [], p: { path: 'Folder to publish. Default ".".' } },
52
+ publish_connect: { d: 'Publishing only: ask the user to connect Supabase, Vercel or GitHub in one click and wait until it is connected.', req: ['service'], p: { service: 'supabase, vercel or github.', reason: 'One sentence on why it is needed, in plain words.' } },
53
+ supabase_setup: { d: 'Publishing only: pick (asks the user) or create a Supabase project, then write its URL and anon key into the app where its stack reads them.', req: [], p: { project: 'Optional: a project ref or name, or "new".', name: 'Name for a new project.', region: 'Region for a new project. Default us-east-1.' } },
54
+ supabase_schema: { d: 'Publishing only: run the SQL the app needs on its Supabase project. The user sees and approves it; RLS is turned on for new tables.', req: [], p: { sql: 'The SQL (tables plus RLS policies).', path: 'Or a .sql file in the project.', ref: 'Project ref. Default: the one from supabase_setup.' } },
55
+ publish_deploy: { d: 'Publishing only: deploy to Vercel (asks about GitHub before the first deploy), set env vars, wait until it is live and return the live URL.', req: [], p: { path: 'Folder to publish. Default ".".', name: 'Vercel project name. Default: the folder name or the earlier project.' } },
56
+ publish_github: { d: 'Publishing only, when the user wants GitHub: create or reuse the repo, push, and link it to Vercel so pushes deploy on their own.', req: [], p: { path: 'Folder. Default ".".', name: 'Repo name for a new repo.', private: '"false" for a public repo. Default private.' } },
47
57
  web_fetch: { d: 'Fetch a web page or API URL and get its content as readable text.', req: ['url'], p: { url: 'http:// or https:// URL.', format: '"text" (default, HTML converted to readable text), "html" or "raw".' } },
58
+ reach_me: { d: 'Reach the user on their phone: how=call rings their phone with your voice (only when it is urgent or they asked for a call), how=text sends a notification (anything else). Now, or later with at.', req: ['how', 'message'], p: { how: 'call or text.', message: 'What to tell them, short and specific.', at: 'Optional: when, like 17:30, 5pm, "in 20 minutes" or an ISO time. Empty means now.', bot: 'Optional: which of the user\'s bots it comes from, by name ("tell Shella to call me" means bot=Shella). Empty means you.' } },
59
+ watch_email: { d: 'Watch the inbox for an email from a sender and reach the user the moment one arrives (works in the background, also with Craft closed to the tray).', req: ['from', 'how'], p: { from: 'The email address, or a domain like example.com.', how: 'call or text, when it arrives.', note: 'Optional: what to say about it.', repeat: 'Optional: yes to alert on every email from them; default is once.' } },
60
+ image_search: { d: 'Search the whole web for real photos (like Google Images). Returns image URLs with sizes; download the one you pick with fetch_image.', req: ['query'], p: { query: 'What the photo should show.', num: 'Number of results (1-16, default 8).' } },
48
61
  web_search: { d: 'Search the web for current information (docs, errors, versions, news).', req: ['query'], p: { query: 'The search query.', num: 'Number of results (1-20, default 8).' } },
49
62
  apply_patch: { d: 'Change several files at once (add, update, delete, move) with one patch. Use it for multi-file changes; use edit_file for a single block.', req: ['patch'], p: { patch: 'The patch text: "*** Begin Patch", then "*** Add File: path" (lines start with +), "*** Update File: path" (optional "*** Move to: path", "@@ anchor line", then lines starting with " ", "-" or "+"), "*** Delete File: path", and "*** End Patch".' } },
50
63
  lsp: { d: 'Code navigation for TypeScript/JavaScript: find the real definition or every reference of a symbol, hover for its type, outline a file, or search symbols.', req: ['operation'], p: { operation: 'definition, references, implementation, hover, documentSymbol or workspaceSymbol.', path: 'File the symbol is in (not needed for workspaceSymbol).', line: '1-based line of the symbol.', character: '1-based column of the symbol (or give symbol instead).', symbol: 'The name on that line, e.g. fetchUser.', query: 'For workspaceSymbol: the name to look for.' } },
@@ -109,6 +122,8 @@ export function toNativeMessages(messages, parse, mcpToNative = null) {
109
122
  role: 'assistant',
110
123
  content: prose || '',
111
124
  tool_calls: withIds.map((c) => ({ id: c.id, type: 'function', function: nativeCall(c) })),
125
+ // DeepSeek's thinking mode rejects a tool call whose reasoning is missing.
126
+ ...(m.reasoning_content !== undefined ? { reasoning_content: m.reasoning_content } : {}),
112
127
  });
113
128
  // Collect everything up to the next assistant message.
114
129
  const answers = new Map();
@@ -7,7 +7,23 @@
7
7
  * owns spinning that server up/down; this file only builds URLs and talks to
8
8
  * the providers' token/API endpoints. No secrets are ever logged.
9
9
  */
10
- const GMAIL_SCOPES = 'https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.send';
10
+ // REST API hosts for Vercel, Supabase and GitHub. The environment overrides
11
+ // exist only so the offline tests can point every call at fake local servers;
12
+ // the app itself never sets them.
13
+ const API = {
14
+ vercel: () => (process.env.CODEPLY_VERCEL_API || 'https://api.vercel.com').replace(/\/+$/, ''),
15
+ supabase: () => (process.env.CODEPLY_SUPABASE_API || 'https://api.supabase.com').replace(/\/+$/, ''),
16
+ github: () => (process.env.CODEPLY_GITHUB_API || 'https://api.github.com').replace(/\/+$/, ''),
17
+ };
18
+
19
+ // One Google connection covers Gmail (read, send, drafts) and Google
20
+ // Calendar events. A connection made before compose/calendar were added lacks
21
+ // them; the tools say "reconnect Gmail in Connect Apps" when Google refuses.
22
+ const GOOGLE_SCOPE = {
23
+ compose: 'https://www.googleapis.com/auth/gmail.compose',
24
+ calendar: 'https://www.googleapis.com/auth/calendar.events',
25
+ };
26
+ const GMAIL_SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.send', GOOGLE_SCOPE.compose, GOOGLE_SCOPE.calendar].join(' ');
11
27
  // channels:join is what lets slackPostMessage auto-join a public channel
12
28
  // before posting to it - without it, chat:write alone only covers channels
13
29
  // the app has already been manually invited into.
@@ -86,14 +102,14 @@ async function getGmailProfile(accessToken) {
86
102
  * per-error message so the real cause shows, with a plain-words hint for the
87
103
  * ones that need the user to act.
88
104
  */
89
- function gmailError(data, status, what) {
105
+ function gmailError(data, status, what, scopeHint) {
90
106
  const err = (data && data.error) || {};
91
107
  const first = Array.isArray(err.errors) && err.errors[0] ? err.errors[0] : {};
92
108
  const reason = first.reason || err.status || '';
93
109
  const detail = [err.message, first.message].filter((m, i, a) => m && a.indexOf(m) === i).join(': ');
94
110
  let hint = '';
95
111
  if (status === 401) hint = ' The Gmail sign-in expired or was revoked. Reconnect Gmail in Connect Apps.';
96
- else if (status === 403 && /scope|insufficient/i.test(detail + reason)) hint = ' Gmail was connected without read access. Reconnect Gmail in Connect Apps and allow all requested permissions.';
112
+ else if (status === 403 && /scope|insufficient/i.test(detail + reason)) hint = scopeHint || ' Gmail was connected without read access. Reconnect Gmail in Connect Apps and allow all requested permissions.';
97
113
  else if (status === 403 && /accessNotConfigured|SERVICE_DISABLED|has not been used/i.test(detail + reason)) hint = ' The Gmail API is not enabled on the Google Cloud project behind this app.';
98
114
  else if (/failedPrecondition|FAILED_PRECONDITION/i.test(reason)) hint = ' Google says this account has no usable Gmail mailbox for this app (Gmail turned off, or the account is not a test user of the app). Reconnect Gmail in Connect Apps with the right account.';
99
115
  else if (status === 400) hint = ' Reconnecting Gmail in Connect Apps usually fixes this.';
@@ -111,8 +127,24 @@ function base64UrlEncode(str) {
111
127
  return Buffer.from(str, 'utf8').toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
112
128
  }
113
129
 
130
+ /**
131
+ * RFC 2822 message, base64url as Gmail wants it. A non-ASCII subject is RFC 2047 encoded.
132
+ * inReplyTo / references (Message-ID values) make it a reply in the same thread.
133
+ */
134
+ function rawEmail({ to, subject, body, inReplyTo, references }) {
135
+ const s = String(subject || '');
136
+ const subj = /^[\x20-\x7e]*$/.test(s) ? s : `=?UTF-8?B?${Buffer.from(s, 'utf8').toString('base64')}?=`;
137
+ const oneLine = (v) => String(v || '').replace(/[\r\n]+/g, ' ').trim();
138
+ const head = [
139
+ to ? `To: ${oneLine(to)}` : '', `Subject: ${oneLine(subj)}`,
140
+ inReplyTo ? `In-Reply-To: ${oneLine(inReplyTo)}` : '', references ? `References: ${oneLine(references)}` : '',
141
+ 'Content-Type: text/plain; charset="UTF-8"',
142
+ ].filter(Boolean).join('\r\n');
143
+ return base64UrlEncode(`${head}\r\n\r\n${body || ''}`);
144
+ }
145
+
114
146
  async function gmailSend(accessToken, { to, subject, body }) {
115
- const raw = base64UrlEncode(`To: ${to}\r\nSubject: ${subject}\r\nContent-Type: text/plain; charset="UTF-8"\r\n\r\n${body}`);
147
+ const raw = rawEmail({ to, subject, body });
116
148
  const res = await fetch('https://www.googleapis.com/gmail/v1/users/me/messages/send', {
117
149
  method: 'POST',
118
150
  headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
@@ -123,6 +155,66 @@ async function gmailSend(accessToken, { to, subject, body }) {
123
155
  return data; // { id, threadId, ... }
124
156
  }
125
157
 
158
+ /** Saves a draft in the user's Gmail (needs gmail.compose). threadId + inReplyTo make it a reply in that thread. */
159
+ async function gmailCreateDraft(accessToken, { to, subject, body, threadId, inReplyTo, references }) {
160
+ const message = { raw: rawEmail({ to, subject, body, inReplyTo, references }) };
161
+ if (threadId) message.threadId = String(threadId);
162
+ const res = await fetch('https://www.googleapis.com/gmail/v1/users/me/drafts', {
163
+ method: 'POST',
164
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
165
+ body: JSON.stringify({ message }),
166
+ });
167
+ const data = await readJson(res);
168
+ if (!res.ok) {
169
+ const e = gmailError(data, res.status, 'Gmail draft', ' Gmail was connected before drafts were allowed. Reconnect Gmail in Connect Apps to allow drafts.');
170
+ e.scopeMissing = res.status === 403 && /scope|insufficient/i.test(JSON.stringify(data));
171
+ throw e;
172
+ }
173
+ return data; // { id, message: { id, threadId } }
174
+ }
175
+
176
+ // ─── Google Calendar (same Google connection as Gmail) ──────────────────────
177
+ const CALENDAR_EVENTS_URL = 'https://www.googleapis.com/calendar/v3/calendars/primary/events';
178
+
179
+ function calendarError(data, status, what) {
180
+ const err = (data && data.error) || {};
181
+ const first = Array.isArray(err.errors) && err.errors[0] ? err.errors[0] : {};
182
+ const reason = first.reason || err.status || '';
183
+ const detail = [err.message, first.message].filter((m, i, a) => m && a.indexOf(m) === i).join(': ');
184
+ const text = `${detail} ${reason} ${JSON.stringify(err.details || '')}`;
185
+ let hint = '';
186
+ if (status === 401) hint = ' The Google sign-in expired or was revoked. Reconnect Gmail in Connect Apps.';
187
+ else if (status === 403 && /accessNotConfigured|SERVICE_DISABLED|has not been used|is disabled/i.test(text)) hint = ' The Google Calendar API is not enabled on the Google Cloud project behind this app. Enable "Google Calendar API" in that project (Google Cloud console, APIs & Services, Library), wait a minute, then try again.';
188
+ else if (status === 403 && /scope|insufficient/i.test(text)) hint = ' Google was connected before Calendar access was added. Reconnect Gmail in Connect Apps (it covers Calendar too) and allow calendar access.';
189
+ else if (status === 400) hint = ' Check the start and end times.';
190
+ const e = new Error(`${what} failed (HTTP ${status}${reason ? `, ${reason}` : ''}): ${detail || 'no details from Google'}.${hint}`);
191
+ e.status = status;
192
+ return e;
193
+ }
194
+
195
+ /** Events on the primary calendar between timeMin and timeMax (RFC 3339), soonest first. */
196
+ async function calendarListEvents(accessToken, { timeMin, timeMax, maxResults = 20 }) {
197
+ const params = new URLSearchParams({ singleEvents: 'true', orderBy: 'startTime', maxResults: String(maxResults) });
198
+ if (timeMin) params.set('timeMin', timeMin);
199
+ if (timeMax) params.set('timeMax', timeMax);
200
+ const res = await fetch(`${CALENDAR_EVENTS_URL}?${params}`, { headers: { Authorization: `Bearer ${accessToken}` } });
201
+ const data = await readJson(res);
202
+ if (!res.ok) throw calendarError(data, res.status, 'Calendar list');
203
+ return data.items || [];
204
+ }
205
+
206
+ /** Creates an event on the primary calendar. `event` is a Calendar API event resource. */
207
+ async function calendarInsertEvent(accessToken, event) {
208
+ const res = await fetch(CALENDAR_EVENTS_URL, {
209
+ method: 'POST',
210
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
211
+ body: JSON.stringify(event),
212
+ });
213
+ const data = await readJson(res);
214
+ if (!res.ok) throw calendarError(data, res.status, 'Calendar add');
215
+ return data; // { id, htmlLink, ... }
216
+ }
217
+
126
218
  async function gmailSearch(accessToken, query, maxResults = 10) {
127
219
  // "*" and "" mean "everything" to a person but are not Gmail syntax; an
128
220
  // empty q simply lists the newest mail, which is what they meant.
@@ -242,7 +334,7 @@ function buildVercelAuthUrl(slug, state) {
242
334
  }
243
335
 
244
336
  async function exchangeVercelCode(clientId, clientSecret, code, redirectUri) {
245
- const res = await fetch('https://api.vercel.com/v2/oauth/access_token', {
337
+ const res = await fetch(`${API.vercel()}/v2/oauth/access_token`, {
246
338
  method: 'POST',
247
339
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
248
340
  body: new URLSearchParams({ client_id: clientId, client_secret: clientSecret, code, redirect_uri: redirectUri }),
@@ -253,7 +345,7 @@ async function exchangeVercelCode(clientId, clientSecret, code, redirectUri) {
253
345
  }
254
346
 
255
347
  async function getVercelProfile(accessToken) {
256
- const res = await fetch('https://api.vercel.com/v2/user', {
348
+ const res = await fetch(`${API.vercel()}/v2/user`, {
257
349
  headers: { Authorization: `Bearer ${accessToken}` },
258
350
  });
259
351
  const body = await res.json();
@@ -276,7 +368,7 @@ function buildSupabaseAuthUrl(clientId, redirectUri) {
276
368
 
277
369
  async function exchangeSupabaseCode(clientId, clientSecret, code, redirectUri) {
278
370
  const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
279
- const res = await fetch('https://api.supabase.com/v1/oauth/token', {
371
+ const res = await fetch(`${API.supabase()}/v1/oauth/token`, {
280
372
  method: 'POST',
281
373
  headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Basic ${basic}` },
282
374
  body: new URLSearchParams({ code, grant_type: 'authorization_code', redirect_uri: redirectUri }),
@@ -299,7 +391,7 @@ async function vercelDeploy(accessToken, teamId, projectName, files) {
299
391
  // guess the framework unattended.
300
392
  const params = new URLSearchParams({ skipAutoDetectionConfirmation: '1' });
301
393
  if (teamId) params.set('teamId', teamId);
302
- const res = await fetch(`https://api.vercel.com/v13/deployments${params.toString() ? `?${params}` : ''}`, {
394
+ const res = await fetch(`${API.vercel()}/v13/deployments${params.toString() ? `?${params}` : ''}`, {
303
395
  method: 'POST',
304
396
  headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
305
397
  body: JSON.stringify({ name: projectName, target: 'production', files }),
@@ -313,7 +405,7 @@ async function vercelDeploy(accessToken, teamId, projectName, files) {
313
405
  async function vercelSetEnvVars(accessToken, teamId, projectIdOrName, vars) {
314
406
  const params = new URLSearchParams({ upsert: 'true' });
315
407
  if (teamId) params.set('teamId', teamId);
316
- const res = await fetch(`https://api.vercel.com/v10/projects/${encodeURIComponent(projectIdOrName)}/env?${params}`, {
408
+ const res = await fetch(`${API.vercel()}/v10/projects/${encodeURIComponent(projectIdOrName)}/env?${params}`, {
317
409
  method: 'POST',
318
410
  headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
319
411
  body: JSON.stringify(vars.map((v) => ({
@@ -326,7 +418,7 @@ async function vercelSetEnvVars(accessToken, teamId, projectIdOrName, vars) {
326
418
  }
327
419
 
328
420
  async function supabaseListOrganizations(accessToken) {
329
- const res = await fetch('https://api.supabase.com/v1/organizations', {
421
+ const res = await fetch(`${API.supabase()}/v1/organizations`, {
330
422
  headers: { Authorization: `Bearer ${accessToken}` },
331
423
  });
332
424
  const body = await res.json();
@@ -343,8 +435,8 @@ const SUPABASE_PROVISION_TIMEOUT_MS = 3 * 60 * 1000;
343
435
  * until it flips to `ACTIVE_HEALTHY` (or gives up after the timeout; the
344
436
  * project still exists at that point, it's just not confirmed ready yet).
345
437
  */
346
- async function supabaseCreateProject(accessToken, { name, organizationSlug, dbPass, region = 'us-east-1' }) {
347
- const res = await fetch('https://api.supabase.com/v1/projects', {
438
+ async function supabaseCreateProject(accessToken, { name, organizationSlug, dbPass, region = 'us-east-1', pollMs = SUPABASE_PROVISION_POLL_MS }) {
439
+ const res = await fetch(`${API.supabase()}/v1/projects`, {
348
440
  method: 'POST',
349
441
  headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
350
442
  // Despite older docs listing it as optional, the API now rejects project
@@ -357,8 +449,8 @@ async function supabaseCreateProject(accessToken, { name, organizationSlug, dbPa
357
449
  const ref = project.ref;
358
450
  const deadline = Date.now() + SUPABASE_PROVISION_TIMEOUT_MS;
359
451
  while (project.status !== 'ACTIVE_HEALTHY' && Date.now() < deadline) {
360
- await new Promise((r) => setTimeout(r, SUPABASE_PROVISION_POLL_MS));
361
- const pr = await fetch(`https://api.supabase.com/v1/projects/${ref}`, { headers: { Authorization: `Bearer ${accessToken}` } });
452
+ await new Promise((r) => setTimeout(r, pollMs));
453
+ const pr = await fetch(`${API.supabase()}/v1/projects/${ref}`, { headers: { Authorization: `Bearer ${accessToken}` } });
362
454
  project = await pr.json();
363
455
  if (!pr.ok) throw new Error(project.message || `Supabase project status check failed (HTTP ${pr.status})`);
364
456
  }
@@ -369,7 +461,7 @@ async function supabaseCreateProject(accessToken, { name, organizationSlug, dbPa
369
461
  }
370
462
 
371
463
  async function supabaseListProjects(accessToken) {
372
- const res = await fetch('https://api.supabase.com/v1/projects', {
464
+ const res = await fetch(`${API.supabase()}/v1/projects`, {
373
465
  headers: { Authorization: `Bearer ${accessToken}` },
374
466
  });
375
467
  const body = await res.json();
@@ -379,7 +471,7 @@ async function supabaseListProjects(accessToken) {
379
471
 
380
472
  /** Permanent, irreversible - Supabase does not soft-delete or restore a removed project. */
381
473
  async function supabaseDeleteProject(accessToken, ref) {
382
- const res = await fetch(`https://api.supabase.com/v1/projects/${ref}`, {
474
+ const res = await fetch(`${API.supabase()}/v1/projects/${ref}`, {
383
475
  method: 'DELETE',
384
476
  headers: { Authorization: `Bearer ${accessToken}` },
385
477
  });
@@ -390,7 +482,7 @@ async function supabaseDeleteProject(accessToken, ref) {
390
482
 
391
483
  /** anon/public key + project URL + DB connection string. The connection string is built locally (not fetched) since dbPass was chosen by the caller, not returned by the API. */
392
484
  async function supabaseGetProjectKeys(accessToken, ref, dbPass) {
393
- const res = await fetch(`https://api.supabase.com/v1/projects/${ref}/api-keys?reveal=true`, {
485
+ const res = await fetch(`${API.supabase()}/v1/projects/${ref}/api-keys?reveal=true`, {
394
486
  headers: { Authorization: `Bearer ${accessToken}` },
395
487
  });
396
488
  const keys = await res.json();
@@ -407,7 +499,7 @@ async function supabaseGetProjectKeys(accessToken, ref, dbPass) {
407
499
  /** Supabase OAuth access tokens expire; the refresh token doesn't (until revoked). */
408
500
  async function refreshSupabaseToken(clientId, clientSecret, refreshToken) {
409
501
  const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
410
- const res = await fetch('https://api.supabase.com/v1/oauth/token', {
502
+ const res = await fetch(`${API.supabase()}/v1/oauth/token`, {
411
503
  method: 'POST',
412
504
  headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Basic ${basic}` },
413
505
  body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken }),
@@ -448,17 +540,17 @@ async function apiCall(base, accessToken, method, apiPath, body, query) {
448
540
  }
449
541
 
450
542
  function supabaseApi(accessToken, method, apiPath, body) {
451
- return apiCall('https://api.supabase.com', accessToken, method, apiPath, body);
543
+ return apiCall(API.supabase(), accessToken, method, apiPath, body);
452
544
  }
453
545
 
454
546
  /** Runs SQL against a project's Postgres through the Management API. */
455
547
  function supabaseQuery(accessToken, ref, query) {
456
- return apiCall('https://api.supabase.com', accessToken, 'POST', `/v1/projects/${encodeURIComponent(ref)}/database/query`, { query });
548
+ return apiCall(API.supabase(), accessToken, 'POST', `/v1/projects/${encodeURIComponent(ref)}/database/query`, { query });
457
549
  }
458
550
 
459
551
  /** teamId is appended automatically when the connection was installed to a team. */
460
552
  function vercelApi(accessToken, teamId, method, apiPath, body) {
461
- return apiCall('https://api.vercel.com', accessToken, method, apiPath, body, { teamId });
553
+ return apiCall(API.vercel(), accessToken, method, apiPath, body, { teamId });
462
554
  }
463
555
 
464
556
  // GitHub's standard OAuth Apps flow (docs.github.com/apps/oauth-apps) - much
@@ -486,7 +578,7 @@ async function exchangeGithubCode(clientId, clientSecret, code, redirectUri) {
486
578
  }
487
579
 
488
580
  async function getGithubProfile(accessToken) {
489
- const res = await fetch('https://api.github.com/user', {
581
+ const res = await fetch(`${API.github()}/user`, {
490
582
  headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/vnd.github+json' },
491
583
  });
492
584
  const body = await res.json();
@@ -496,7 +588,7 @@ async function getGithubProfile(accessToken) {
496
588
 
497
589
  /** Creates a new repo under the connected user's account. Defaults to private - safer for an arbitrary local folder than defaulting public. */
498
590
  async function githubCreateRepo(accessToken, name, { private: isPrivate = true } = {}) {
499
- const res = await fetch('https://api.github.com/user/repos', {
591
+ const res = await fetch(`${API.github()}/user/repos`, {
500
592
  method: 'POST',
501
593
  headers: {
502
594
  Authorization: `Bearer ${accessToken}`,
@@ -511,8 +603,38 @@ async function githubCreateRepo(accessToken, name, { private: isPrivate = true }
511
603
  return body; // { name, full_name, html_url, clone_url, default_branch, ... }
512
604
  }
513
605
 
606
+ /**
607
+ * The token fallback in Connect Apps: the user pastes their own personal
608
+ * access token (Vercel account token, Supabase access token, GitHub token)
609
+ * when no OAuth app is configured. One read-only call proves it works and
610
+ * gives the label shown on the card. The token itself is never logged.
611
+ */
612
+ async function checkAccessToken(name, token) {
613
+ const t = String(token || '').trim();
614
+ if (!t || /\s/.test(t)) throw new Error('That does not look like a token. Paste it again without spaces.');
615
+ if (name === 'vercel') {
616
+ const res = await fetch(`${API.vercel()}/v2/user`, { headers: { Authorization: `Bearer ${t}` } });
617
+ const body = await readJson(res);
618
+ if (!res.ok) throw new Error(res.status === 403 || res.status === 401 ? 'Vercel did not accept that token. Create a new one at vercel.com/account/settings/tokens.' : `Vercel check failed (HTTP ${res.status}).`);
619
+ return { accessToken: t, userName: body.user?.username || body.user?.email || '' };
620
+ }
621
+ if (name === 'supabase') {
622
+ const res = await fetch(`${API.supabase()}/v1/organizations`, { headers: { Authorization: `Bearer ${t}` } });
623
+ const body = await readJson(res);
624
+ if (!res.ok) throw new Error(res.status === 401 || res.status === 403 ? 'Supabase did not accept that token. Create a new one at supabase.com/dashboard/account/tokens.' : `Supabase check failed (HTTP ${res.status}).`);
625
+ return { accessToken: t, refreshToken: '', expiresAt: 0, email: Array.isArray(body) && body[0] ? body[0].name : '' };
626
+ }
627
+ if (name === 'github') {
628
+ const userName = await getGithubProfile(t).catch(() => { throw new Error('GitHub did not accept that token. Create one with the "repo" scope at github.com/settings/tokens.'); });
629
+ return { accessToken: t, userName };
630
+ }
631
+ throw new Error(`${name} has no token sign-in.`);
632
+ }
633
+
514
634
  module.exports = {
515
- buildGmailAuthUrl, exchangeGmailCode, refreshGmailToken, getGmailProfile, gmailSend, gmailSearch,
635
+ API, apiCall, checkAccessToken,
636
+ buildGmailAuthUrl, exchangeGmailCode, refreshGmailToken, getGmailProfile, gmailSend, gmailSearch, gmailCreateDraft, gmailError, rawEmail,
637
+ calendarListEvents, calendarInsertEvent, calendarError, GOOGLE_SCOPE,
516
638
  buildSlackAuthUrl, exchangeSlackCode, slackPostMessage, slackListChannels, slackJoinChannel,
517
639
  buildVercelAuthUrl, exchangeVercelCode, getVercelProfile, vercelDeploy, vercelSetEnvVars,
518
640
  buildSupabaseAuthUrl, exchangeSupabaseCode, refreshSupabaseToken, supabaseListOrganizations, supabaseCreateProject, supabaseGetProjectKeys,
@@ -1,109 +1,109 @@
1
- /**
2
- * Standing permission rules, set once in a file instead of clicked per chat.
3
- *
4
- * ~/.codeply/permissions.json (every project)
5
- * <project>/.codeply/permissions.json (this project, checked first)
6
- *
7
- * {
8
- * "deny": ["run:git push*", "run:rm*", "write_file:*.env", "supabase_delete_project"],
9
- * "allow": ["run:npm test", "run:npm run *", "run:git status", "edit_file:src/**"]
10
- * }
11
- *
12
- * A rule is "<tool>" or "<tool>:<pattern>". For run the pattern matches the
13
- * command's name (npm run dev, git checkout) and the full command line; for
14
- * file tools it matches the path relative to the project. * matches within a
15
- * path segment, ** across them, and a trailing " *" also matches the bare
16
- * command. Deny beats allow; project rules beat global ones; anything no rule
17
- * mentions is asked as usual. Evaluation is the same idea as opencode's
18
- * permission/ rulesets.
19
- */
20
- const fs = require('fs');
21
- const os = require('os');
22
- const path = require('path');
23
-
24
- function readRules(file) {
25
- try {
26
- const j = JSON.parse(fs.readFileSync(file, 'utf8'));
27
- const list = (k) => (Array.isArray(j[k]) ? j[k].map(String).filter(Boolean) : []);
28
- return { allow: list('allow'), deny: list('deny') };
29
- } catch { return { allow: [], deny: [] }; }
30
- }
31
-
32
- function wildcard(pattern) {
33
- let re = '';
34
- for (let i = 0; i < pattern.length; i++) {
35
- const c = pattern[i];
36
- if (c === '*' && pattern[i + 1] === '*') { re += '.*'; i++; }
37
- else if (c === '*') re += '[^/]*';
38
- else if (c === '?') re += '.';
39
- else re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
40
- }
41
- // "npm run *" should also cover plain "npm run".
42
- re = re.replace(/ \[\^\/\]\*$/, '(?: .*)?').replace(/ \.\*$/, '(?: .*)?');
43
- return new RegExp(`^${re}$`, process.platform === 'win32' ? 'i' : '');
44
- }
45
-
46
- /** Strings a request can be matched against for its tool. */
47
- function subjects(req, cwd) {
48
- if (req.tool === 'run') {
49
- const cmd = String(req.detail || '').trim();
50
- // Commands match by name AND as full text, so "run:npm run *" and
51
- // "run:git push*" both work.
52
- return { cmd, names: Array.isArray(req.patterns) ? req.patterns : [] };
53
- }
54
- if (req.path) {
55
- const abs = path.isAbsolute(req.path) ? req.path : path.resolve(cwd || '.', req.path);
56
- const rel = path.relative(cwd || '.', abs).replace(/\\/g, '/');
57
- return { path: rel.startsWith('..') ? abs.replace(/\\/g, '/') : rel };
58
- }
59
- return {};
60
- }
61
-
62
- function ruleMatches(rule, req, subj) {
63
- const i = rule.indexOf(':');
64
- const tool = (i === -1 ? rule : rule.slice(0, i)).trim();
65
- const pattern = i === -1 ? null : rule.slice(i + 1).trim();
66
- if (tool !== req.tool && tool !== '*') return false;
67
- if (!pattern || pattern === '*') return true;
68
- const re = wildcard(pattern);
69
- if (req.tool === 'run') {
70
- if (re.test(subj.cmd)) return true;
71
- // For allow on a compound command every part must be covered (checked by
72
- // the caller); here a single name matching is enough to count.
73
- return subj.names.some((n) => re.test(n));
74
- }
75
- return subj.path ? re.test(subj.path) : false;
76
- }
77
-
78
- /**
79
- * @param {object} req the approval request ({tool, detail, patterns, path, danger})
80
- * @returns {{decision: 'allow'|'deny'|null, rule?: string, file?: string}}
81
- */
82
- function decide(req, cwd) {
83
- const files = [cwd && path.join(cwd, '.codeply', 'permissions.json'), path.join(os.homedir(), '.codeply', 'permissions.json')].filter(Boolean);
84
- const sets = files.map((f) => ({ file: f, ...readRules(f) }));
85
- const subj = subjects(req, cwd);
86
- for (const s of sets) {
87
- const hit = s.deny.find((r) => ruleMatches(r, req, subj));
88
- if (hit) return { decision: 'deny', rule: hit, file: s.file };
89
- }
90
- // Risky-looking commands (rm -rf, format, curl | sh...) always ask.
91
- if (req.danger) return { decision: null };
92
- for (const s of sets) {
93
- if (req.tool === 'run') {
94
- // Allow is judged per command, never on the whole line: "npm run *"
95
- // must not let "npm run build && npm install" through. A line that
96
- // can't be split into named commands (subshells, redirects) is never
97
- // auto-allowed.
98
- if (!subj.names.length) continue;
99
- const parts = subj.names.length === 1 ? [{ cmd: subj.cmd, names: subj.names }] : subj.names.map((n) => ({ cmd: n, names: [n] }));
100
- if (parts.every((p) => s.allow.some((r) => ruleMatches(r, req, p)))) return { decision: 'allow', rule: 'allow rules', file: s.file };
101
- continue;
102
- }
103
- const hit = s.allow.find((r) => ruleMatches(r, req, subj));
104
- if (hit) return { decision: 'allow', rule: hit, file: s.file };
105
- }
106
- return { decision: null };
107
- }
108
-
109
- module.exports = { decide, wildcard };
1
+ /**
2
+ * Standing permission rules, set once in a file instead of clicked per chat.
3
+ *
4
+ * ~/.codeply/permissions.json (every project)
5
+ * <project>/.codeply/permissions.json (this project, checked first)
6
+ *
7
+ * {
8
+ * "deny": ["run:git push*", "run:rm*", "write_file:*.env", "supabase_delete_project"],
9
+ * "allow": ["run:npm test", "run:npm run *", "run:git status", "edit_file:src/**"]
10
+ * }
11
+ *
12
+ * A rule is "<tool>" or "<tool>:<pattern>". For run the pattern matches the
13
+ * command's name (npm run dev, git checkout) and the full command line; for
14
+ * file tools it matches the path relative to the project. * matches within a
15
+ * path segment, ** across them, and a trailing " *" also matches the bare
16
+ * command. Deny beats allow; project rules beat global ones; anything no rule
17
+ * mentions is asked as usual. Evaluation is the same idea as opencode's
18
+ * permission/ rulesets.
19
+ */
20
+ const fs = require('fs');
21
+ const os = require('os');
22
+ const path = require('path');
23
+
24
+ function readRules(file) {
25
+ try {
26
+ const j = JSON.parse(fs.readFileSync(file, 'utf8'));
27
+ const list = (k) => (Array.isArray(j[k]) ? j[k].map(String).filter(Boolean) : []);
28
+ return { allow: list('allow'), deny: list('deny') };
29
+ } catch { return { allow: [], deny: [] }; }
30
+ }
31
+
32
+ function wildcard(pattern) {
33
+ let re = '';
34
+ for (let i = 0; i < pattern.length; i++) {
35
+ const c = pattern[i];
36
+ if (c === '*' && pattern[i + 1] === '*') { re += '.*'; i++; }
37
+ else if (c === '*') re += '[^/]*';
38
+ else if (c === '?') re += '.';
39
+ else re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
40
+ }
41
+ // "npm run *" should also cover plain "npm run".
42
+ re = re.replace(/ \[\^\/\]\*$/, '(?: .*)?').replace(/ \.\*$/, '(?: .*)?');
43
+ return new RegExp(`^${re}$`, process.platform === 'win32' ? 'i' : '');
44
+ }
45
+
46
+ /** Strings a request can be matched against for its tool. */
47
+ function subjects(req, cwd) {
48
+ if (req.tool === 'run') {
49
+ const cmd = String(req.detail || '').trim();
50
+ // Commands match by name AND as full text, so "run:npm run *" and
51
+ // "run:git push*" both work.
52
+ return { cmd, names: Array.isArray(req.patterns) ? req.patterns : [] };
53
+ }
54
+ if (req.path) {
55
+ const abs = path.isAbsolute(req.path) ? req.path : path.resolve(cwd || '.', req.path);
56
+ const rel = path.relative(cwd || '.', abs).replace(/\\/g, '/');
57
+ return { path: rel.startsWith('..') ? abs.replace(/\\/g, '/') : rel };
58
+ }
59
+ return {};
60
+ }
61
+
62
+ function ruleMatches(rule, req, subj) {
63
+ const i = rule.indexOf(':');
64
+ const tool = (i === -1 ? rule : rule.slice(0, i)).trim();
65
+ const pattern = i === -1 ? null : rule.slice(i + 1).trim();
66
+ if (tool !== req.tool && tool !== '*') return false;
67
+ if (!pattern || pattern === '*') return true;
68
+ const re = wildcard(pattern);
69
+ if (req.tool === 'run') {
70
+ if (re.test(subj.cmd)) return true;
71
+ // For allow on a compound command every part must be covered (checked by
72
+ // the caller); here a single name matching is enough to count.
73
+ return subj.names.some((n) => re.test(n));
74
+ }
75
+ return subj.path ? re.test(subj.path) : false;
76
+ }
77
+
78
+ /**
79
+ * @param {object} req the approval request ({tool, detail, patterns, path, danger})
80
+ * @returns {{decision: 'allow'|'deny'|null, rule?: string, file?: string}}
81
+ */
82
+ function decide(req, cwd) {
83
+ const files = [cwd && path.join(cwd, '.codeply', 'permissions.json'), path.join(os.homedir(), '.codeply', 'permissions.json')].filter(Boolean);
84
+ const sets = files.map((f) => ({ file: f, ...readRules(f) }));
85
+ const subj = subjects(req, cwd);
86
+ for (const s of sets) {
87
+ const hit = s.deny.find((r) => ruleMatches(r, req, subj));
88
+ if (hit) return { decision: 'deny', rule: hit, file: s.file };
89
+ }
90
+ // Risky-looking commands (rm -rf, format, curl | sh...) always ask.
91
+ if (req.danger) return { decision: null };
92
+ for (const s of sets) {
93
+ if (req.tool === 'run') {
94
+ // Allow is judged per command, never on the whole line: "npm run *"
95
+ // must not let "npm run build && npm install" through. A line that
96
+ // can't be split into named commands (subshells, redirects) is never
97
+ // auto-allowed.
98
+ if (!subj.names.length) continue;
99
+ const parts = subj.names.length === 1 ? [{ cmd: subj.cmd, names: subj.names }] : subj.names.map((n) => ({ cmd: n, names: [n] }));
100
+ if (parts.every((p) => s.allow.some((r) => ruleMatches(r, req, p)))) return { decision: 'allow', rule: 'allow rules', file: s.file };
101
+ continue;
102
+ }
103
+ const hit = s.allow.find((r) => ruleMatches(r, req, subj));
104
+ if (hit) return { decision: 'allow', rule: hit, file: s.file };
105
+ }
106
+ return { decision: null };
107
+ }
108
+
109
+ module.exports = { decide, wildcard };