codeply-cli 0.1.1 → 0.3.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 (384) hide show
  1. package/THIRD_PARTY_NOTICES.md +63 -0
  2. package/bin/codeply.js +562 -259
  3. package/lib/agent.mjs +1721 -116
  4. package/lib/ai.js +1107 -184
  5. package/lib/apply-limit.js +33 -41
  6. package/lib/apply-patch.js +217 -0
  7. package/lib/arity-table.json +138 -0
  8. package/lib/arity.js +70 -0
  9. package/lib/auth.js +118 -112
  10. package/lib/commands.js +103 -0
  11. package/lib/config.js +392 -98
  12. package/lib/design-library/artifact-template.html +500 -0
  13. package/lib/design-library/assemble-artifact.mjs +21 -0
  14. package/lib/design-library/build.mjs +226 -0
  15. package/lib/design-library/cleanup.mjs +45 -0
  16. package/lib/design-library/index.json +22380 -0
  17. package/lib/design-library/make-table-data.mjs +39 -0
  18. package/lib/design-library/query.mjs +104 -0
  19. package/lib/diagnostics.js +192 -0
  20. package/lib/edit-engine.js +171 -14
  21. package/lib/github-agent.mjs +290 -0
  22. package/lib/history.js +75 -0
  23. package/lib/mcp.js +258 -0
  24. package/lib/native-tools.mjs +189 -0
  25. package/lib/oauth-connectors.js +478 -0
  26. package/lib/permissions.js +109 -0
  27. package/lib/plugins.js +353 -0
  28. package/lib/render-md.mjs +617 -617
  29. package/lib/server.mjs +457 -0
  30. package/lib/session-db.js +220 -0
  31. package/lib/share.js +168 -0
  32. package/lib/skills.js +416 -0
  33. package/lib/snapshot.js +146 -0
  34. package/lib/subagents.js +164 -0
  35. package/lib/terminal-hints.js +154 -0
  36. package/lib/textarea.mjs +258 -258
  37. package/lib/tools.mjs +1709 -73
  38. package/lib/tui.js +7 -7
  39. package/lib/tui.mjs +1086 -899
  40. package/lib/typecheck-worker.js +147 -0
  41. package/lib/web-tools.js +158 -0
  42. package/package.json +5 -3
  43. package/skills/SOURCE.md +58 -0
  44. package/skills/accessibility/SKILL.md +147 -0
  45. package/skills/agent-architecture-audit/SKILL.md +257 -0
  46. package/skills/agent-eval/SKILL.md +147 -0
  47. package/skills/agent-harness-construction/SKILL.md +74 -0
  48. package/skills/agent-introspection-debugging/SKILL.md +154 -0
  49. package/skills/agent-payment-x402/SKILL.md +225 -0
  50. package/skills/agent-self-evaluation/SKILL.md +182 -0
  51. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
  52. package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
  53. package/skills/agent-sort/SKILL.md +216 -0
  54. package/skills/agentic-engineering/SKILL.md +64 -0
  55. package/skills/agentic-os/SKILL.md +388 -0
  56. package/skills/ai-first-engineering/SKILL.md +52 -0
  57. package/skills/ai-regression-testing/SKILL.md +386 -0
  58. package/skills/android-clean-architecture/SKILL.md +340 -0
  59. package/skills/angular-developer/SKILL.md +155 -0
  60. package/skills/angular-developer/references/angular-animations.md +160 -0
  61. package/skills/angular-developer/references/angular-aria.md +410 -0
  62. package/skills/angular-developer/references/cli.md +86 -0
  63. package/skills/angular-developer/references/component-harnesses.md +59 -0
  64. package/skills/angular-developer/references/component-styling.md +91 -0
  65. package/skills/angular-developer/references/components.md +117 -0
  66. package/skills/angular-developer/references/creating-services.md +97 -0
  67. package/skills/angular-developer/references/data-resolvers.md +69 -0
  68. package/skills/angular-developer/references/define-routes.md +67 -0
  69. package/skills/angular-developer/references/defining-providers.md +72 -0
  70. package/skills/angular-developer/references/di-fundamentals.md +120 -0
  71. package/skills/angular-developer/references/e2e-testing.md +56 -0
  72. package/skills/angular-developer/references/effects.md +83 -0
  73. package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  74. package/skills/angular-developer/references/host-elements.md +80 -0
  75. package/skills/angular-developer/references/injection-context.md +63 -0
  76. package/skills/angular-developer/references/inputs.md +101 -0
  77. package/skills/angular-developer/references/linked-signal.md +59 -0
  78. package/skills/angular-developer/references/loading-strategies.md +61 -0
  79. package/skills/angular-developer/references/mcp.md +108 -0
  80. package/skills/angular-developer/references/navigate-to-routes.md +69 -0
  81. package/skills/angular-developer/references/outputs.md +86 -0
  82. package/skills/angular-developer/references/reactive-forms.md +122 -0
  83. package/skills/angular-developer/references/rendering-strategies.md +44 -0
  84. package/skills/angular-developer/references/resource.md +77 -0
  85. package/skills/angular-developer/references/route-animations.md +56 -0
  86. package/skills/angular-developer/references/route-guards.md +52 -0
  87. package/skills/angular-developer/references/router-lifecycle.md +45 -0
  88. package/skills/angular-developer/references/router-testing.md +87 -0
  89. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  90. package/skills/angular-developer/references/signal-forms.md +795 -0
  91. package/skills/angular-developer/references/signals-overview.md +94 -0
  92. package/skills/angular-developer/references/tailwind-css.md +69 -0
  93. package/skills/angular-developer/references/template-driven-forms.md +114 -0
  94. package/skills/angular-developer/references/testing-fundamentals.md +65 -0
  95. package/skills/api-connector-builder/SKILL.md +121 -0
  96. package/skills/api-design/SKILL.md +524 -0
  97. package/skills/architecture-decision-records/SKILL.md +180 -0
  98. package/skills/article-writing/SKILL.md +80 -0
  99. package/skills/automation-audit-ops/SKILL.md +143 -0
  100. package/skills/autonomous-agent-harness/SKILL.md +274 -0
  101. package/skills/autonomous-loops/SKILL.md +611 -0
  102. package/skills/backend-patterns/SKILL.md +562 -0
  103. package/skills/benchmark/SKILL.md +95 -0
  104. package/skills/benchmark-methodology/SKILL.md +191 -0
  105. package/skills/benchmark-optimization-loop/SKILL.md +71 -0
  106. package/skills/blender-motion-state-inspection/SKILL.md +165 -0
  107. package/skills/blueprint/SKILL.md +106 -0
  108. package/skills/brand-discovery/SKILL.md +145 -0
  109. package/skills/brand-discovery/references/10_purpose-why.md +40 -0
  110. package/skills/brand-discovery/references/20_positioning.md +44 -0
  111. package/skills/brand-discovery/references/30_audience-niche.md +52 -0
  112. package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
  113. package/skills/brand-discovery/references/50_voice-tone.md +59 -0
  114. package/skills/brand-discovery/references/60_narrative-story.md +50 -0
  115. package/skills/brand-discovery/references/70_founder-tension.md +49 -0
  116. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
  117. package/skills/brand-voice/SKILL.md +98 -0
  118. package/skills/brand-voice/references/voice-profile-schema.md +55 -0
  119. package/skills/browser-qa/SKILL.md +105 -0
  120. package/skills/bun-runtime/SKILL.md +85 -0
  121. package/skills/canary-watch/SKILL.md +108 -0
  122. package/skills/carrier-relationship-management/SKILL.md +212 -0
  123. package/skills/cisco-ios-patterns/SKILL.md +164 -0
  124. package/skills/ck/SKILL.md +148 -0
  125. package/skills/claude-devfleet/SKILL.md +112 -0
  126. package/skills/click-path-audit/SKILL.md +245 -0
  127. package/skills/clickhouse-io/SKILL.md +445 -0
  128. package/skills/code-tour/SKILL.md +254 -0
  129. package/skills/codebase-onboarding/SKILL.md +234 -0
  130. package/skills/codehealth-mcp/SKILL.md +167 -0
  131. package/skills/coding-standards/SKILL.md +551 -0
  132. package/skills/competitive-platform-analysis/SKILL.md +214 -0
  133. package/skills/competitive-report-structure/SKILL.md +162 -0
  134. package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
  135. package/skills/config-gc/SKILL.md +120 -0
  136. package/skills/configure-ecc/SKILL.md +385 -0
  137. package/skills/connections-optimizer/SKILL.md +190 -0
  138. package/skills/content-engine/SKILL.md +132 -0
  139. package/skills/content-hash-cache-pattern/SKILL.md +162 -0
  140. package/skills/context-budget/SKILL.md +136 -0
  141. package/skills/continuous-agent-loop/SKILL.md +46 -0
  142. package/skills/continuous-learning/SKILL.md +132 -0
  143. package/skills/continuous-learning-v2/SKILL.md +361 -0
  144. package/skills/contract-first/SKILL.md +287 -0
  145. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
  146. package/skills/cost-tracking/SKILL.md +97 -0
  147. package/skills/council/SKILL.md +204 -0
  148. package/skills/cpp-coding-standards/SKILL.md +724 -0
  149. package/skills/cpp-testing/SKILL.md +325 -0
  150. package/skills/crosspost/SKILL.md +112 -0
  151. package/skills/csharp-testing/SKILL.md +322 -0
  152. package/skills/customer-billing-ops/SKILL.md +141 -0
  153. package/skills/customs-trade-compliance/SKILL.md +263 -0
  154. package/skills/dart-flutter-patterns/SKILL.md +564 -0
  155. package/skills/dashboard-builder/SKILL.md +109 -0
  156. package/skills/data-scraper-agent/SKILL.md +765 -0
  157. package/skills/data-throughput-accelerator/SKILL.md +74 -0
  158. package/skills/database-migrations/SKILL.md +430 -0
  159. package/skills/deep-research/SKILL.md +160 -0
  160. package/skills/defi-amm-security/SKILL.md +167 -0
  161. package/skills/delivery-gate/SKILL.md +126 -0
  162. package/skills/deployment-patterns/SKILL.md +428 -0
  163. package/skills/design-system/SKILL.md +83 -0
  164. package/skills/django-celery/SKILL.md +458 -0
  165. package/skills/django-patterns/SKILL.md +735 -0
  166. package/skills/django-security/SKILL.md +644 -0
  167. package/skills/django-tdd/SKILL.md +730 -0
  168. package/skills/django-verification/SKILL.md +470 -0
  169. package/skills/dmux-workflows/SKILL.md +192 -0
  170. package/skills/docker-patterns/SKILL.md +365 -0
  171. package/skills/documentation-lookup/SKILL.md +91 -0
  172. package/skills/dotnet-patterns/SKILL.md +322 -0
  173. package/skills/dynamic-workflow-mode/SKILL.md +124 -0
  174. package/skills/e2e-testing/SKILL.md +327 -0
  175. package/skills/ecc-guide/SKILL.md +190 -0
  176. package/skills/ecc-recipes/SKILL.md +149 -0
  177. package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
  178. package/skills/email-ops/SKILL.md +122 -0
  179. package/skills/energy-procurement/SKILL.md +228 -0
  180. package/skills/enterprise-agent-ops/SKILL.md +51 -0
  181. package/skills/error-handling/SKILL.md +377 -0
  182. package/skills/eval-harness/SKILL.md +271 -0
  183. package/skills/evm-token-decimals/SKILL.md +131 -0
  184. package/skills/exa-search/SKILL.md +108 -0
  185. package/skills/fal-ai-media/SKILL.md +289 -0
  186. package/skills/fastapi-patterns/SKILL.md +514 -0
  187. package/skills/finance-billing-ops/SKILL.md +128 -0
  188. package/skills/flox-environments/SKILL.md +497 -0
  189. package/skills/flutter-dart-code-review/SKILL.md +436 -0
  190. package/skills/foundation-models-on-device/SKILL.md +243 -0
  191. package/skills/frontend-a11y/SKILL.md +446 -0
  192. package/skills/frontend-design-direction/SKILL.md +93 -0
  193. package/skills/frontend-patterns/SKILL.md +657 -0
  194. package/skills/frontend-slides/SKILL.md +185 -0
  195. package/skills/fsharp-testing/SKILL.md +281 -0
  196. package/skills/gan-style-harness/SKILL.md +279 -0
  197. package/skills/gateguard/SKILL.md +133 -0
  198. package/skills/generating-python-installer/SKILL.md +820 -0
  199. package/skills/git-workflow/SKILL.md +716 -0
  200. package/skills/github-ops/SKILL.md +145 -0
  201. package/skills/golang-patterns/SKILL.md +676 -0
  202. package/skills/golang-testing/SKILL.md +721 -0
  203. package/skills/google-workspace-ops/SKILL.md +96 -0
  204. package/skills/growth-log/SKILL.md +128 -0
  205. package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
  206. package/skills/healthcare-emr-patterns/SKILL.md +160 -0
  207. package/skills/healthcare-eval-harness/SKILL.md +208 -0
  208. package/skills/healthcare-phi-compliance/SKILL.md +146 -0
  209. package/skills/hermes-imports/SKILL.md +89 -0
  210. package/skills/hexagonal-architecture/SKILL.md +277 -0
  211. package/skills/hipaa-compliance/SKILL.md +79 -0
  212. package/skills/homelab-network-readiness/SKILL.md +170 -0
  213. package/skills/homelab-network-setup/SKILL.md +130 -0
  214. package/skills/homelab-pihole-dns/SKILL.md +275 -0
  215. package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
  216. package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
  217. package/skills/hookify-rules/SKILL.md +128 -0
  218. package/skills/inherit-legacy-style/SKILL.md +157 -0
  219. package/skills/intent-driven-development/SKILL.md +360 -0
  220. package/skills/inventory-demand-planning/SKILL.md +247 -0
  221. package/skills/investor-materials/SKILL.md +97 -0
  222. package/skills/investor-outreach/SKILL.md +92 -0
  223. package/skills/ios-icon-gen/SKILL.md +158 -0
  224. package/skills/iterative-retrieval/SKILL.md +212 -0
  225. package/skills/ito-basket-compare/SKILL.md +64 -0
  226. package/skills/ito-compute/SKILL.md +129 -0
  227. package/skills/ito-data-atlas-agent/SKILL.md +64 -0
  228. package/skills/ito-market-intelligence/SKILL.md +61 -0
  229. package/skills/ito-trade-planner/SKILL.md +68 -0
  230. package/skills/java-coding-standards/SKILL.md +384 -0
  231. package/skills/jira-integration/SKILL.md +303 -0
  232. package/skills/jpa-patterns/SKILL.md +152 -0
  233. package/skills/knowledge-ops/SKILL.md +155 -0
  234. package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
  235. package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
  236. package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
  237. package/skills/kotlin-patterns/SKILL.md +712 -0
  238. package/skills/kotlin-testing/SKILL.md +825 -0
  239. package/skills/kubernetes-patterns/SKILL.md +756 -0
  240. package/skills/laravel-patterns/SKILL.md +416 -0
  241. package/skills/laravel-plugin-discovery/SKILL.md +230 -0
  242. package/skills/laravel-security/SKILL.md +948 -0
  243. package/skills/laravel-tdd/SKILL.md +675 -0
  244. package/skills/laravel-verification/SKILL.md +180 -0
  245. package/skills/latency-critical-systems/SKILL.md +75 -0
  246. package/skills/lead-intelligence/SKILL.md +322 -0
  247. package/skills/liquid-glass-design/SKILL.md +279 -0
  248. package/skills/llm-trading-agent-security/SKILL.md +147 -0
  249. package/skills/logistics-exception-management/SKILL.md +222 -0
  250. package/skills/loop-design-check/SKILL.md +143 -0
  251. package/skills/mailtrap-email-integration/SKILL.md +77 -0
  252. package/skills/make-interfaces-feel-better/SKILL.md +152 -0
  253. package/skills/manim-video/SKILL.md +90 -0
  254. package/skills/market-research/SKILL.md +76 -0
  255. package/skills/marketing-campaign/SKILL.md +114 -0
  256. package/skills/mcp-server-patterns/SKILL.md +70 -0
  257. package/skills/messages-ops/SKILL.md +105 -0
  258. package/skills/ml-adoption-playbook/SKILL.md +57 -0
  259. package/skills/mle-workflow/SKILL.md +348 -0
  260. package/skills/motion-advanced/SKILL.md +596 -0
  261. package/skills/motion-foundations/SKILL.md +299 -0
  262. package/skills/motion-patterns/SKILL.md +434 -0
  263. package/skills/motion-ui/SKILL.md +576 -0
  264. package/skills/mysql-patterns/SKILL.md +413 -0
  265. package/skills/nanoclaw-repl/SKILL.md +34 -0
  266. package/skills/nestjs-patterns/SKILL.md +231 -0
  267. package/skills/netmiko-ssh-automation/SKILL.md +174 -0
  268. package/skills/network-bgp-diagnostics/SKILL.md +168 -0
  269. package/skills/network-config-validation/SKILL.md +211 -0
  270. package/skills/network-interface-health/SKILL.md +153 -0
  271. package/skills/nextjs-turbopack/SKILL.md +58 -0
  272. package/skills/nodejs-keccak256/SKILL.md +103 -0
  273. package/skills/nutrient-document-processing/SKILL.md +168 -0
  274. package/skills/nuxt4-patterns/SKILL.md +101 -0
  275. package/skills/openclaw-persona-forge/SKILL.md +289 -0
  276. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
  277. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
  278. package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
  279. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
  280. package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
  281. package/skills/openclaw-persona-forge/references/output-template.md +166 -0
  282. package/skills/opensource-pipeline/SKILL.md +256 -0
  283. package/skills/orch-add-feature/SKILL.md +45 -0
  284. package/skills/orch-build-mvp/SKILL.md +49 -0
  285. package/skills/orch-change-feature/SKILL.md +43 -0
  286. package/skills/orch-fix-defect/SKILL.md +43 -0
  287. package/skills/orch-pipeline/SKILL.md +121 -0
  288. package/skills/orch-refine-code/SKILL.md +44 -0
  289. package/skills/parallel-execution-optimizer/SKILL.md +74 -0
  290. package/skills/perl-patterns/SKILL.md +505 -0
  291. package/skills/perl-security/SKILL.md +504 -0
  292. package/skills/perl-testing/SKILL.md +476 -0
  293. package/skills/plan-canvas/SKILL.md +153 -0
  294. package/skills/plan-orchestrate/SKILL.md +263 -0
  295. package/skills/plankton-code-quality/SKILL.md +237 -0
  296. package/skills/postgres-patterns/SKILL.md +148 -0
  297. package/skills/prediction-market-oracle-research/SKILL.md +64 -0
  298. package/skills/prediction-market-risk-review/SKILL.md +61 -0
  299. package/skills/premium-web-design/SKILL.md +154 -0
  300. package/skills/prisma-patterns/SKILL.md +401 -0
  301. package/skills/product-capability/SKILL.md +142 -0
  302. package/skills/product-lens/SKILL.md +93 -0
  303. package/skills/production-audit/SKILL.md +207 -0
  304. package/skills/production-scheduling/SKILL.md +238 -0
  305. package/skills/project-flow-ops/SKILL.md +112 -0
  306. package/skills/prompt-optimizer/SKILL.md +398 -0
  307. package/skills/python-patterns/SKILL.md +751 -0
  308. package/skills/python-testing/SKILL.md +817 -0
  309. package/skills/pytorch-patterns/SKILL.md +397 -0
  310. package/skills/quality-nonconformance/SKILL.md +260 -0
  311. package/skills/quarkus-patterns/SKILL.md +723 -0
  312. package/skills/quarkus-security/SKILL.md +468 -0
  313. package/skills/quarkus-tdd/SKILL.md +812 -0
  314. package/skills/quarkus-verification/SKILL.md +480 -0
  315. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
  316. package/skills/react-native-patterns/SKILL.md +326 -0
  317. package/skills/react-patterns/SKILL.md +342 -0
  318. package/skills/react-performance/SKILL.md +575 -0
  319. package/skills/react-testing/SKILL.md +424 -0
  320. package/skills/recsys-pipeline-architect/SKILL.md +115 -0
  321. package/skills/recursive-decision-ledger/SKILL.md +81 -0
  322. package/skills/redis-patterns/SKILL.md +404 -0
  323. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
  324. package/skills/remotion-video-creation/SKILL.md +43 -0
  325. package/skills/repo-scan/SKILL.md +79 -0
  326. package/skills/research-ops/SKILL.md +113 -0
  327. package/skills/returns-reverse-logistics/SKILL.md +240 -0
  328. package/skills/rules-distill/SKILL.md +265 -0
  329. package/skills/rust-patterns/SKILL.md +500 -0
  330. package/skills/rust-testing/SKILL.md +501 -0
  331. package/skills/safety-guard/SKILL.md +76 -0
  332. package/skills/santa-method/SKILL.md +307 -0
  333. package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
  334. package/skills/scientific-db-uspto-database/SKILL.md +178 -0
  335. package/skills/scientific-pkg-gget/SKILL.md +167 -0
  336. package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
  337. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
  338. package/skills/search-first/SKILL.md +183 -0
  339. package/skills/security-bounty-hunter/SKILL.md +100 -0
  340. package/skills/security-review/SKILL.md +504 -0
  341. package/skills/security-scan/SKILL.md +166 -0
  342. package/skills/seo/SKILL.md +155 -0
  343. package/skills/skill-comply/SKILL.md +59 -0
  344. package/skills/skill-scout/SKILL.md +141 -0
  345. package/skills/skill-stocktake/SKILL.md +195 -0
  346. package/skills/social-graph-ranker/SKILL.md +155 -0
  347. package/skills/social-publisher/SKILL.md +130 -0
  348. package/skills/springboot-patterns/SKILL.md +315 -0
  349. package/skills/springboot-security/SKILL.md +273 -0
  350. package/skills/springboot-tdd/SKILL.md +159 -0
  351. package/skills/springboot-verification/SKILL.md +232 -0
  352. package/skills/strategic-compact/SKILL.md +142 -0
  353. package/skills/swift-actor-persistence/SKILL.md +144 -0
  354. package/skills/swift-concurrency-6-2/SKILL.md +216 -0
  355. package/skills/swift-protocol-di-testing/SKILL.md +191 -0
  356. package/skills/swiftui-patterns/SKILL.md +259 -0
  357. package/skills/taste/SKILL.md +264 -0
  358. package/skills/taste/references/genre-taxonomy.md +87 -0
  359. package/skills/tdd-workflow/SKILL.md +583 -0
  360. package/skills/team-agent-orchestration/SKILL.md +111 -0
  361. package/skills/team-builder/SKILL.md +169 -0
  362. package/skills/terminal-ops/SKILL.md +110 -0
  363. package/skills/tinystruct-patterns/SKILL.md +279 -0
  364. package/skills/tinystruct-patterns/references/architecture.md +90 -0
  365. package/skills/tinystruct-patterns/references/data-handling.md +60 -0
  366. package/skills/tinystruct-patterns/references/database.md +99 -0
  367. package/skills/tinystruct-patterns/references/routing.md +64 -0
  368. package/skills/tinystruct-patterns/references/system-usage.md +97 -0
  369. package/skills/tinystruct-patterns/references/testing.md +72 -0
  370. package/skills/token-budget-advisor/SKILL.md +134 -0
  371. package/skills/ui-demo/SKILL.md +466 -0
  372. package/skills/ui-to-vue/SKILL.md +135 -0
  373. package/skills/uncloud/SKILL.md +344 -0
  374. package/skills/unified-memory/SKILL.md +170 -0
  375. package/skills/unified-notifications-ops/SKILL.md +188 -0
  376. package/skills/verification-loop/SKILL.md +129 -0
  377. package/skills/video-editing/SKILL.md +311 -0
  378. package/skills/videodb/SKILL.md +375 -0
  379. package/skills/visa-doc-translate/SKILL.md +117 -0
  380. package/skills/vite-patterns/SKILL.md +450 -0
  381. package/skills/vue-patterns/SKILL.md +471 -0
  382. package/skills/windows-desktop-e2e/SKILL.md +888 -0
  383. package/skills/workspace-surface-audit/SKILL.md +126 -0
  384. package/skills/x-api/SKILL.md +235 -0
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Codeply - Gmail and Slack OAuth + the real API calls the agent's tools use.
3
+ *
4
+ * Desktop-app OAuth (RFC 8252): the consent screen opens in the user's real
5
+ * system browser, not an embedded webview, and the redirect is caught by a
6
+ * short-lived local HTTP server on a fixed loopback port - Real App/main.js
7
+ * owns spinning that server up/down; this file only builds URLs and talks to
8
+ * the providers' token/API endpoints. No secrets are ever logged.
9
+ */
10
+ const GMAIL_SCOPES = 'https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.send';
11
+ // channels:join is what lets slackPostMessage auto-join a public channel
12
+ // before posting to it - without it, chat:write alone only covers channels
13
+ // the app has already been manually invited into.
14
+ const SLACK_SCOPES = 'chat:write,channels:read,channels:history,channels:join';
15
+ // Separate from the bot scopes above - Slack issues bot and user tokens
16
+ // through entirely different mechanisms in the same OAuth exchange. `scope`
17
+ // requests the bot token (posts as "Codeply Craft APP"); `user_scope`
18
+ // requests a second, independent token that posts as the actual signed-in
19
+ // user. Both come back in one authorize round-trip, but they're genuinely
20
+ // two different tokens with two different scope sets.
21
+ const SLACK_USER_SCOPES = 'chat:write';
22
+
23
+ function buildGmailAuthUrl(clientId, redirectUri) {
24
+ const params = new URLSearchParams({
25
+ client_id: clientId,
26
+ redirect_uri: redirectUri,
27
+ response_type: 'code',
28
+ scope: GMAIL_SCOPES,
29
+ access_type: 'offline', // required to get a refresh_token back
30
+ prompt: 'consent', // forces the refresh_token every time, not just first connect
31
+ });
32
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
33
+ }
34
+
35
+ async function exchangeGmailCode(clientId, clientSecret, code, redirectUri) {
36
+ const res = await fetch('https://oauth2.googleapis.com/token', {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
39
+ body: new URLSearchParams({
40
+ client_id: clientId, client_secret: clientSecret, code,
41
+ redirect_uri: redirectUri, grant_type: 'authorization_code',
42
+ }),
43
+ });
44
+ const body = await res.json();
45
+ if (!res.ok) throw new Error(body.error_description || body.error || `Gmail token exchange failed (HTTP ${res.status})`);
46
+ return body; // { access_token, refresh_token, expires_in, ... }
47
+ }
48
+
49
+ async function refreshGmailToken(clientId, clientSecret, refreshToken) {
50
+ const res = await fetch('https://oauth2.googleapis.com/token', {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
53
+ body: new URLSearchParams({
54
+ client_id: clientId, client_secret: clientSecret,
55
+ refresh_token: refreshToken, grant_type: 'refresh_token',
56
+ }),
57
+ });
58
+ const body = await res.json();
59
+ if (!res.ok) throw new Error(body.error_description || body.error || `Gmail token refresh failed (HTTP ${res.status})`);
60
+ return body; // { access_token, expires_in, ... } - no new refresh_token on refresh
61
+ }
62
+
63
+ async function getGmailProfile(accessToken) {
64
+ const res = await fetch('https://www.googleapis.com/gmail/v1/users/me/profile', {
65
+ headers: { Authorization: `Bearer ${accessToken}` },
66
+ });
67
+ const body = await res.json();
68
+ if (!res.ok) throw new Error(body.error?.message || `Gmail profile lookup failed (HTTP ${res.status})`);
69
+ return body.emailAddress;
70
+ }
71
+
72
+ function base64UrlEncode(str) {
73
+ return Buffer.from(str, 'utf8').toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
74
+ }
75
+
76
+ async function gmailSend(accessToken, { to, subject, body }) {
77
+ const raw = base64UrlEncode(`To: ${to}\r\nSubject: ${subject}\r\nContent-Type: text/plain; charset="UTF-8"\r\n\r\n${body}`);
78
+ const res = await fetch('https://www.googleapis.com/gmail/v1/users/me/messages/send', {
79
+ method: 'POST',
80
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
81
+ body: JSON.stringify({ raw }),
82
+ });
83
+ const data = await res.json();
84
+ if (!res.ok) throw new Error(data.error?.message || `Gmail send failed (HTTP ${res.status})`);
85
+ return data; // { id, threadId, ... }
86
+ }
87
+
88
+ async function gmailSearch(accessToken, query, maxResults = 10) {
89
+ const params = new URLSearchParams({ q: query, maxResults: String(maxResults) });
90
+ const res = await fetch(`https://www.googleapis.com/gmail/v1/users/me/messages?${params}`, {
91
+ headers: { Authorization: `Bearer ${accessToken}` },
92
+ });
93
+ const list = await res.json();
94
+ if (!res.ok) throw new Error(list.error?.message || `Gmail search failed (HTTP ${res.status})`);
95
+ if (!list.messages?.length) return [];
96
+ // The list endpoint only returns bare ids - each result needs its own
97
+ // fetch for subject/from/snippet, capped by maxResults so a broad query
98
+ // can't fan out into dozens of requests.
99
+ const details = await Promise.all(list.messages.map(async (m) => {
100
+ const r = await fetch(
101
+ `https://www.googleapis.com/gmail/v1/users/me/messages/${m.id}?format=metadata&metadataHeaders=Subject&metadataHeaders=From&metadataHeaders=Date`,
102
+ { headers: { Authorization: `Bearer ${accessToken}` } },
103
+ );
104
+ const msg = await r.json();
105
+ const header = (name) => msg.payload?.headers?.find((h) => h.name === name)?.value || '';
106
+ return { id: m.id, subject: header('Subject'), from: header('From'), date: header('Date'), snippet: msg.snippet || '' };
107
+ }));
108
+ return details;
109
+ }
110
+
111
+ function buildSlackAuthUrl(clientId, redirectUri) {
112
+ const params = new URLSearchParams({
113
+ client_id: clientId, redirect_uri: redirectUri,
114
+ scope: SLACK_SCOPES, user_scope: SLACK_USER_SCOPES,
115
+ });
116
+ return `https://slack.com/oauth/v2/authorize?${params}`;
117
+ }
118
+
119
+ async function exchangeSlackCode(clientId, clientSecret, code, redirectUri) {
120
+ const res = await fetch('https://slack.com/api/oauth.v2.access', {
121
+ method: 'POST',
122
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
123
+ body: new URLSearchParams({ client_id: clientId, client_secret: clientSecret, code, redirect_uri: redirectUri }),
124
+ });
125
+ const body = await res.json();
126
+ // Slack's OAuth endpoint returns HTTP 200 even on failure - ok:false in the body is the real signal.
127
+ if (!body.ok) throw new Error(body.error || 'Slack token exchange failed');
128
+ return body; // { access_token, team: { id, name }, ... }
129
+ }
130
+
131
+ /** Public-channel-only equivalent of clicking "Join channel" in Slack. Idempotent - already_in_channel is fine. */
132
+ async function slackJoinChannel(accessToken, channelId) {
133
+ const res = await fetch('https://slack.com/api/conversations.join', {
134
+ method: 'POST',
135
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
136
+ body: JSON.stringify({ channel: channelId }),
137
+ });
138
+ const body = await res.json();
139
+ if (!body.ok && body.error !== 'already_in_channel') throw new Error(body.error || 'Could not join the channel');
140
+ }
141
+
142
+ async function slackPostMessage(accessToken, { channel, text }, { isUserToken = false } = {}) {
143
+ // A bot token's chat:write only covers channels the app has actually
144
+ // joined - not "any public channel" the way a human member can post to.
145
+ // Auto-joining first (public channels only; that's all resolveSlackChannel
146
+ // in tools.mjs ever resolves to) means the agent can post to a channel the
147
+ // very first time it's asked to, instead of failing with not_in_channel
148
+ // until someone manually runs /invite in Slack. Joining a channel it's
149
+ // already in is a harmless no-op (already_in_channel), so this doesn't
150
+ // need to track membership itself - just always try.
151
+ //
152
+ // A user token is different: it posts as an actual workspace member, who
153
+ // either already belongs to the channel or doesn't - conversations.join
154
+ // isn't the right operation for a user identity the way it is for a bot,
155
+ // so this is skipped entirely on that path.
156
+ if (!isUserToken) await slackJoinChannel(accessToken, channel);
157
+
158
+ const res = await fetch('https://slack.com/api/chat.postMessage', {
159
+ method: 'POST',
160
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
161
+ body: JSON.stringify({ channel, text }),
162
+ });
163
+ const body = await res.json();
164
+ if (!body.ok) throw new Error(body.error || 'Slack message failed to send');
165
+ return body;
166
+ }
167
+
168
+ async function slackListChannels(accessToken) {
169
+ // public_channel only - private_channel requires the separate groups:read
170
+ // scope, which isn't part of the bot scope set this app asks for
171
+ // (chat:write, channels:read, channels:history). Slack rejects the WHOLE
172
+ // conversations.list call with missing_scope if any requested type isn't
173
+ // covered, not just the private-channel portion - so asking for a type we
174
+ // don't have scope for breaks lookup even for public channels the bot can
175
+ // see fine, which is exactly what was happening here.
176
+ const res = await fetch('https://slack.com/api/conversations.list?types=public_channel&limit=100', {
177
+ headers: { Authorization: `Bearer ${accessToken}` },
178
+ });
179
+ const body = await res.json();
180
+ if (!body.ok) throw new Error(body.error || 'Slack channel list failed');
181
+ return (body.channels || []).map((c) => ({ id: c.id, name: c.name, isPrivate: c.is_private, isMember: c.is_member }));
182
+ }
183
+
184
+ // Vercel Integrations Console apps do NOT use a plain /oauth/authorize?
185
+ // client_id=... URL - that endpoint belongs to a different, older Vercel
186
+ // OAuth app system and rejects Integrations Console client IDs outright
187
+ // ("App configuration error: The app ID is invalid"). An integration created
188
+ // in the Integrations Console starts its install at this slug-based URL
189
+ // instead (found on the integration's own Console settings page, not
190
+ // derived from client_id), and Vercel redirects back to whichever Redirect
191
+ // URL is configured in the Console (already this app's loopback callback)
192
+ // with ?code=... (and &teamId=... when installed to a team). The code
193
+ // exchange step below is unchanged - that part of the flow really is the
194
+ // same as the old system.
195
+ function buildVercelAuthUrl(slug, state) {
196
+ const params = new URLSearchParams();
197
+ if (state) params.set('state', state);
198
+ const qs = params.toString();
199
+ return `https://vercel.com/integrations/${encodeURIComponent(slug)}/new${qs ? `?${qs}` : ''}`;
200
+ }
201
+
202
+ async function exchangeVercelCode(clientId, clientSecret, code, redirectUri) {
203
+ const res = await fetch('https://api.vercel.com/v2/oauth/access_token', {
204
+ method: 'POST',
205
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
206
+ body: new URLSearchParams({ client_id: clientId, client_secret: clientSecret, code, redirect_uri: redirectUri }),
207
+ });
208
+ const body = await res.json();
209
+ if (!res.ok) throw new Error(body.error?.message || body.error || `Vercel token exchange failed (HTTP ${res.status})`);
210
+ return body; // { access_token, team_id, user_id, ... }
211
+ }
212
+
213
+ async function getVercelProfile(accessToken) {
214
+ const res = await fetch('https://api.vercel.com/v2/user', {
215
+ headers: { Authorization: `Bearer ${accessToken}` },
216
+ });
217
+ const body = await res.json();
218
+ if (!res.ok) throw new Error(body.error?.message || `Vercel profile lookup failed (HTTP ${res.status})`);
219
+ return body.user?.username || body.user?.email || '';
220
+ }
221
+
222
+ // Supabase's Management API OAuth2 flow (supabase.com/docs/guides/platform/oauth-apps):
223
+ // authorize/token both live under api.supabase.com, token exchange uses HTTP
224
+ // Basic auth (client_id:client_secret) rather than form-body credentials.
225
+ const SUPABASE_SCOPES = 'all';
226
+
227
+ function buildSupabaseAuthUrl(clientId, redirectUri) {
228
+ const params = new URLSearchParams({
229
+ client_id: clientId, redirect_uri: redirectUri,
230
+ response_type: 'code', scope: SUPABASE_SCOPES,
231
+ });
232
+ return `https://api.supabase.com/v1/oauth/authorize?${params}`;
233
+ }
234
+
235
+ async function exchangeSupabaseCode(clientId, clientSecret, code, redirectUri) {
236
+ const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
237
+ const res = await fetch('https://api.supabase.com/v1/oauth/token', {
238
+ method: 'POST',
239
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Basic ${basic}` },
240
+ body: new URLSearchParams({ code, grant_type: 'authorization_code', redirect_uri: redirectUri }),
241
+ });
242
+ const body = await res.json();
243
+ if (!res.ok) throw new Error(body.error_description || body.error || `Supabase token exchange failed (HTTP ${res.status})`);
244
+ return body; // { access_token, refresh_token, expires_in, ... }
245
+ }
246
+
247
+ // Non-git deployment: upload isn't needed for small projects - files are
248
+ // inlined as base64 directly in the create-deployment body (Vercel's REST API
249
+ // supports both an inline `data`+`encoding` file or a reference to one
250
+ // pre-uploaded via /v2/files; inlining avoids a second round-trip per file
251
+ // and this app only ever deploys folders small enough for that to be fine).
252
+ async function vercelDeploy(accessToken, teamId, projectName, files) {
253
+ // A brand-new project (no projectSettings passed - framework is left to
254
+ // Vercel's own auto-detection) otherwise gets rejected with "projectSettings
255
+ // is required... or use skipAutoDetectionConfirmation=1" on its first-ever
256
+ // deploy, since Vercel wants an explicit confirmation step before it'll
257
+ // guess the framework unattended.
258
+ const params = new URLSearchParams({ skipAutoDetectionConfirmation: '1' });
259
+ if (teamId) params.set('teamId', teamId);
260
+ const res = await fetch(`https://api.vercel.com/v13/deployments${params.toString() ? `?${params}` : ''}`, {
261
+ method: 'POST',
262
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
263
+ body: JSON.stringify({ name: projectName, target: 'production', files }),
264
+ });
265
+ const body = await res.json();
266
+ if (!res.ok) throw new Error(body.error?.message || body.error_description || `Vercel deploy failed (HTTP ${res.status})`);
267
+ return body; // { id, url, readyState, ... }
268
+ }
269
+
270
+ /** Bulk-create/update project env vars - `vars` is [{ key, value }]. `type: 'encrypted'` hides values in the dashboard by default, same as a normal manual entry. */
271
+ async function vercelSetEnvVars(accessToken, teamId, projectIdOrName, vars) {
272
+ const params = new URLSearchParams({ upsert: 'true' });
273
+ if (teamId) params.set('teamId', teamId);
274
+ const res = await fetch(`https://api.vercel.com/v10/projects/${encodeURIComponent(projectIdOrName)}/env?${params}`, {
275
+ method: 'POST',
276
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
277
+ body: JSON.stringify(vars.map((v) => ({
278
+ key: v.key, value: v.value, type: 'encrypted', target: ['production', 'preview', 'development'],
279
+ }))),
280
+ });
281
+ const body = await res.json();
282
+ if (!res.ok) throw new Error(body.error?.message || `Vercel env var update failed (HTTP ${res.status})`);
283
+ return body; // { created, failed: [...] }
284
+ }
285
+
286
+ async function supabaseListOrganizations(accessToken) {
287
+ const res = await fetch('https://api.supabase.com/v1/organizations', {
288
+ headers: { Authorization: `Bearer ${accessToken}` },
289
+ });
290
+ const body = await res.json();
291
+ if (!res.ok) throw new Error(body.message || `Supabase organization lookup failed (HTTP ${res.status})`);
292
+ return body; // [{ id, slug, name }]
293
+ }
294
+
295
+ const SUPABASE_PROVISION_POLL_MS = 5000;
296
+ const SUPABASE_PROVISION_TIMEOUT_MS = 3 * 60 * 1000;
297
+
298
+ /**
299
+ * Project creation is asynchronous - the initial response comes back
300
+ * `status: "INACTIVE"` while Supabase provisions the database, so this polls
301
+ * until it flips to `ACTIVE_HEALTHY` (or gives up after the timeout; the
302
+ * project still exists at that point, it's just not confirmed ready yet).
303
+ */
304
+ async function supabaseCreateProject(accessToken, { name, organizationSlug, dbPass, region = 'us-east-1' }) {
305
+ const res = await fetch('https://api.supabase.com/v1/projects', {
306
+ method: 'POST',
307
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
308
+ // Despite older docs listing it as optional, the API now rejects project
309
+ // creation without exactly one of region/region_selection set.
310
+ body: JSON.stringify({ name, organization_slug: organizationSlug, db_pass: dbPass, region }),
311
+ });
312
+ let project = await res.json();
313
+ if (!res.ok) throw new Error(project.message || `Supabase project creation failed (HTTP ${res.status})`);
314
+
315
+ const ref = project.ref;
316
+ const deadline = Date.now() + SUPABASE_PROVISION_TIMEOUT_MS;
317
+ while (project.status !== 'ACTIVE_HEALTHY' && Date.now() < deadline) {
318
+ await new Promise((r) => setTimeout(r, SUPABASE_PROVISION_POLL_MS));
319
+ const pr = await fetch(`https://api.supabase.com/v1/projects/${ref}`, { headers: { Authorization: `Bearer ${accessToken}` } });
320
+ project = await pr.json();
321
+ if (!pr.ok) throw new Error(project.message || `Supabase project status check failed (HTTP ${pr.status})`);
322
+ }
323
+ if (project.status !== 'ACTIVE_HEALTHY') {
324
+ throw new Error(`Project ${ref} is still provisioning (status: ${project.status}) after ${SUPABASE_PROVISION_TIMEOUT_MS / 1000}s - it will likely finish shortly; check the Supabase dashboard.`);
325
+ }
326
+ return project; // { ref, name, status, ... }
327
+ }
328
+
329
+ async function supabaseListProjects(accessToken) {
330
+ const res = await fetch('https://api.supabase.com/v1/projects', {
331
+ headers: { Authorization: `Bearer ${accessToken}` },
332
+ });
333
+ const body = await res.json();
334
+ if (!res.ok) throw new Error(body.message || `Supabase project list failed (HTTP ${res.status})`);
335
+ return body; // [{ id, organization_id, name, ref, status, region, ... }]
336
+ }
337
+
338
+ /** Permanent, irreversible - Supabase does not soft-delete or restore a removed project. */
339
+ async function supabaseDeleteProject(accessToken, ref) {
340
+ const res = await fetch(`https://api.supabase.com/v1/projects/${ref}`, {
341
+ method: 'DELETE',
342
+ headers: { Authorization: `Bearer ${accessToken}` },
343
+ });
344
+ if (res.status === 204 || res.status === 200) return true;
345
+ const body = await res.json().catch(() => ({}));
346
+ throw new Error(body.message || `Supabase project deletion failed (HTTP ${res.status})`);
347
+ }
348
+
349
+ /** 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. */
350
+ async function supabaseGetProjectKeys(accessToken, ref, dbPass) {
351
+ const res = await fetch(`https://api.supabase.com/v1/projects/${ref}/api-keys?reveal=true`, {
352
+ headers: { Authorization: `Bearer ${accessToken}` },
353
+ });
354
+ const keys = await res.json();
355
+ if (!res.ok) throw new Error(keys.message || `Supabase API key lookup failed (HTTP ${res.status})`);
356
+ const anon = keys.find((k) => /^anon$/i.test(k.name || '') || /anon|publishable/i.test(k.name || k.type || ''));
357
+ if (!anon?.api_key) throw new Error(`Project ${ref} has no anon/public key yet - it may still be finishing setup.`);
358
+ return {
359
+ url: `https://${ref}.supabase.co`,
360
+ anonKey: anon.api_key,
361
+ databaseUrl: `postgresql://postgres:${dbPass}@db.${ref}.supabase.co:5432/postgres`,
362
+ };
363
+ }
364
+
365
+ /** Supabase OAuth access tokens expire; the refresh token doesn't (until revoked). */
366
+ async function refreshSupabaseToken(clientId, clientSecret, refreshToken) {
367
+ const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
368
+ const res = await fetch('https://api.supabase.com/v1/oauth/token', {
369
+ method: 'POST',
370
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Basic ${basic}` },
371
+ body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken }),
372
+ });
373
+ const body = await res.json().catch(() => ({}));
374
+ if (!res.ok) throw new Error(body.error_description || body.error || `Supabase token refresh failed (HTTP ${res.status})`);
375
+ return body; // { access_token, refresh_token, expires_in }
376
+ }
377
+
378
+ const API_TIMEOUT_MS = 60_000;
379
+
380
+ /**
381
+ * Generic authenticated JSON call used by the agent's full-access tools
382
+ * (supabase_api / vercel_api). Returns { ok, status, body } and never throws
383
+ * for an HTTP error - the status and the provider's own error body are
384
+ * exactly what the agent needs to see to fix its request.
385
+ */
386
+ async function apiCall(base, accessToken, method, apiPath, body, query) {
387
+ const cleanPath = '/' + String(apiPath || '').replace(/^\/+/, '');
388
+ const url = new URL(base + cleanPath);
389
+ for (const [k, v] of Object.entries(query || {})) {
390
+ if (v !== undefined && v !== null && v !== '' && !url.searchParams.has(k)) url.searchParams.set(k, v);
391
+ }
392
+ const init = {
393
+ method,
394
+ headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },
395
+ signal: AbortSignal.timeout(API_TIMEOUT_MS),
396
+ };
397
+ if (body !== undefined && body !== null && body !== '' && method !== 'GET' && method !== 'HEAD') {
398
+ init.headers['Content-Type'] = 'application/json';
399
+ init.body = typeof body === 'string' ? body : JSON.stringify(body);
400
+ }
401
+ const res = await fetch(url, init);
402
+ const text = await res.text();
403
+ let parsed = text;
404
+ try { parsed = text ? JSON.parse(text) : null; } catch {}
405
+ return { ok: res.ok, status: res.status, body: parsed, url: url.toString() };
406
+ }
407
+
408
+ function supabaseApi(accessToken, method, apiPath, body) {
409
+ return apiCall('https://api.supabase.com', accessToken, method, apiPath, body);
410
+ }
411
+
412
+ /** Runs SQL against a project's Postgres through the Management API. */
413
+ function supabaseQuery(accessToken, ref, query) {
414
+ return apiCall('https://api.supabase.com', accessToken, 'POST', `/v1/projects/${encodeURIComponent(ref)}/database/query`, { query });
415
+ }
416
+
417
+ /** teamId is appended automatically when the connection was installed to a team. */
418
+ function vercelApi(accessToken, teamId, method, apiPath, body) {
419
+ return apiCall('https://api.vercel.com', accessToken, method, apiPath, body, { teamId });
420
+ }
421
+
422
+ // GitHub's standard OAuth Apps flow (docs.github.com/apps/oauth-apps) - much
423
+ // simpler registration than Vercel/Supabase: no scopes picker or store
424
+ // listing, just a name + callback URL. `repo` scope is what lets
425
+ // githubCreateRepo actually create repositories under the connected account.
426
+ const GITHUB_SCOPES = 'repo gist';
427
+
428
+ function buildGithubAuthUrl(clientId, redirectUri, state) {
429
+ const params = new URLSearchParams({ client_id: clientId, redirect_uri: redirectUri, scope: GITHUB_SCOPES });
430
+ if (state) params.set('state', state);
431
+ return `https://github.com/login/oauth/authorize?${params}`;
432
+ }
433
+
434
+ async function exchangeGithubCode(clientId, clientSecret, code, redirectUri) {
435
+ const res = await fetch('https://github.com/login/oauth/access_token', {
436
+ method: 'POST',
437
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
438
+ body: new URLSearchParams({ client_id: clientId, client_secret: clientSecret, code, redirect_uri: redirectUri }),
439
+ });
440
+ const body = await res.json();
441
+ if (!res.ok || body.error) throw new Error(body.error_description || body.error || `GitHub token exchange failed (HTTP ${res.status})`);
442
+ return body; // { access_token, scope, token_type }
443
+ }
444
+
445
+ async function getGithubProfile(accessToken) {
446
+ const res = await fetch('https://api.github.com/user', {
447
+ headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/vnd.github+json' },
448
+ });
449
+ const body = await res.json();
450
+ if (!res.ok) throw new Error(body.message || `GitHub profile lookup failed (HTTP ${res.status})`);
451
+ return body.login || '';
452
+ }
453
+
454
+ /** Creates a new repo under the connected user's account. Defaults to private - safer for an arbitrary local folder than defaulting public. */
455
+ async function githubCreateRepo(accessToken, name, { private: isPrivate = true } = {}) {
456
+ const res = await fetch('https://api.github.com/user/repos', {
457
+ method: 'POST',
458
+ headers: {
459
+ Authorization: `Bearer ${accessToken}`,
460
+ Accept: 'application/vnd.github+json',
461
+ 'Content-Type': 'application/json',
462
+ 'X-GitHub-Api-Version': '2022-11-28',
463
+ },
464
+ body: JSON.stringify({ name, private: isPrivate }),
465
+ });
466
+ const body = await res.json();
467
+ if (!res.ok) throw new Error(body.message || `GitHub repo creation failed (HTTP ${res.status})`);
468
+ return body; // { name, full_name, html_url, clone_url, default_branch, ... }
469
+ }
470
+
471
+ module.exports = {
472
+ buildGmailAuthUrl, exchangeGmailCode, refreshGmailToken, getGmailProfile, gmailSend, gmailSearch,
473
+ buildSlackAuthUrl, exchangeSlackCode, slackPostMessage, slackListChannels, slackJoinChannel,
474
+ buildVercelAuthUrl, exchangeVercelCode, getVercelProfile, vercelDeploy, vercelSetEnvVars,
475
+ buildSupabaseAuthUrl, exchangeSupabaseCode, refreshSupabaseToken, supabaseListOrganizations, supabaseCreateProject, supabaseGetProjectKeys,
476
+ supabaseListProjects, supabaseDeleteProject, supabaseApi, supabaseQuery, vercelApi,
477
+ buildGithubAuthUrl, exchangeGithubCode, getGithubProfile, githubCreateRepo,
478
+ };
@@ -0,0 +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 };