codeply-cli 0.1.1 → 0.2.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 (350) hide show
  1. package/bin/codeply.js +125 -14
  2. package/lib/agent.mjs +152 -14
  3. package/lib/ai.js +217 -14
  4. package/lib/apply-limit.js +13 -3
  5. package/lib/config.js +53 -15
  6. package/lib/skills.js +358 -0
  7. package/lib/tools.mjs +63 -6
  8. package/lib/tui.mjs +95 -2
  9. package/package.json +3 -2
  10. package/skills/SOURCE.md +58 -0
  11. package/skills/accessibility/SKILL.md +147 -0
  12. package/skills/agent-architecture-audit/SKILL.md +257 -0
  13. package/skills/agent-eval/SKILL.md +147 -0
  14. package/skills/agent-harness-construction/SKILL.md +74 -0
  15. package/skills/agent-introspection-debugging/SKILL.md +154 -0
  16. package/skills/agent-payment-x402/SKILL.md +225 -0
  17. package/skills/agent-self-evaluation/SKILL.md +182 -0
  18. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
  19. package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
  20. package/skills/agent-sort/SKILL.md +216 -0
  21. package/skills/agentic-engineering/SKILL.md +64 -0
  22. package/skills/agentic-os/SKILL.md +388 -0
  23. package/skills/ai-first-engineering/SKILL.md +52 -0
  24. package/skills/ai-regression-testing/SKILL.md +386 -0
  25. package/skills/android-clean-architecture/SKILL.md +340 -0
  26. package/skills/angular-developer/SKILL.md +155 -0
  27. package/skills/angular-developer/references/angular-animations.md +160 -0
  28. package/skills/angular-developer/references/angular-aria.md +410 -0
  29. package/skills/angular-developer/references/cli.md +86 -0
  30. package/skills/angular-developer/references/component-harnesses.md +59 -0
  31. package/skills/angular-developer/references/component-styling.md +91 -0
  32. package/skills/angular-developer/references/components.md +117 -0
  33. package/skills/angular-developer/references/creating-services.md +97 -0
  34. package/skills/angular-developer/references/data-resolvers.md +69 -0
  35. package/skills/angular-developer/references/define-routes.md +67 -0
  36. package/skills/angular-developer/references/defining-providers.md +72 -0
  37. package/skills/angular-developer/references/di-fundamentals.md +120 -0
  38. package/skills/angular-developer/references/e2e-testing.md +56 -0
  39. package/skills/angular-developer/references/effects.md +83 -0
  40. package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  41. package/skills/angular-developer/references/host-elements.md +80 -0
  42. package/skills/angular-developer/references/injection-context.md +63 -0
  43. package/skills/angular-developer/references/inputs.md +101 -0
  44. package/skills/angular-developer/references/linked-signal.md +59 -0
  45. package/skills/angular-developer/references/loading-strategies.md +61 -0
  46. package/skills/angular-developer/references/mcp.md +108 -0
  47. package/skills/angular-developer/references/navigate-to-routes.md +69 -0
  48. package/skills/angular-developer/references/outputs.md +86 -0
  49. package/skills/angular-developer/references/reactive-forms.md +122 -0
  50. package/skills/angular-developer/references/rendering-strategies.md +44 -0
  51. package/skills/angular-developer/references/resource.md +77 -0
  52. package/skills/angular-developer/references/route-animations.md +56 -0
  53. package/skills/angular-developer/references/route-guards.md +52 -0
  54. package/skills/angular-developer/references/router-lifecycle.md +45 -0
  55. package/skills/angular-developer/references/router-testing.md +87 -0
  56. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  57. package/skills/angular-developer/references/signal-forms.md +795 -0
  58. package/skills/angular-developer/references/signals-overview.md +94 -0
  59. package/skills/angular-developer/references/tailwind-css.md +69 -0
  60. package/skills/angular-developer/references/template-driven-forms.md +114 -0
  61. package/skills/angular-developer/references/testing-fundamentals.md +65 -0
  62. package/skills/api-connector-builder/SKILL.md +121 -0
  63. package/skills/api-design/SKILL.md +524 -0
  64. package/skills/architecture-decision-records/SKILL.md +180 -0
  65. package/skills/article-writing/SKILL.md +80 -0
  66. package/skills/automation-audit-ops/SKILL.md +143 -0
  67. package/skills/autonomous-agent-harness/SKILL.md +274 -0
  68. package/skills/autonomous-loops/SKILL.md +611 -0
  69. package/skills/backend-patterns/SKILL.md +562 -0
  70. package/skills/benchmark/SKILL.md +95 -0
  71. package/skills/benchmark-methodology/SKILL.md +191 -0
  72. package/skills/benchmark-optimization-loop/SKILL.md +71 -0
  73. package/skills/blender-motion-state-inspection/SKILL.md +165 -0
  74. package/skills/blueprint/SKILL.md +106 -0
  75. package/skills/brand-discovery/SKILL.md +145 -0
  76. package/skills/brand-discovery/references/10_purpose-why.md +40 -0
  77. package/skills/brand-discovery/references/20_positioning.md +44 -0
  78. package/skills/brand-discovery/references/30_audience-niche.md +52 -0
  79. package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
  80. package/skills/brand-discovery/references/50_voice-tone.md +59 -0
  81. package/skills/brand-discovery/references/60_narrative-story.md +50 -0
  82. package/skills/brand-discovery/references/70_founder-tension.md +49 -0
  83. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
  84. package/skills/brand-voice/SKILL.md +98 -0
  85. package/skills/brand-voice/references/voice-profile-schema.md +55 -0
  86. package/skills/browser-qa/SKILL.md +105 -0
  87. package/skills/bun-runtime/SKILL.md +85 -0
  88. package/skills/canary-watch/SKILL.md +108 -0
  89. package/skills/carrier-relationship-management/SKILL.md +212 -0
  90. package/skills/cisco-ios-patterns/SKILL.md +164 -0
  91. package/skills/ck/SKILL.md +148 -0
  92. package/skills/claude-devfleet/SKILL.md +112 -0
  93. package/skills/click-path-audit/SKILL.md +245 -0
  94. package/skills/clickhouse-io/SKILL.md +445 -0
  95. package/skills/code-tour/SKILL.md +254 -0
  96. package/skills/codebase-onboarding/SKILL.md +234 -0
  97. package/skills/codehealth-mcp/SKILL.md +167 -0
  98. package/skills/coding-standards/SKILL.md +551 -0
  99. package/skills/competitive-platform-analysis/SKILL.md +214 -0
  100. package/skills/competitive-report-structure/SKILL.md +162 -0
  101. package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
  102. package/skills/config-gc/SKILL.md +120 -0
  103. package/skills/configure-ecc/SKILL.md +385 -0
  104. package/skills/connections-optimizer/SKILL.md +190 -0
  105. package/skills/content-engine/SKILL.md +132 -0
  106. package/skills/content-hash-cache-pattern/SKILL.md +162 -0
  107. package/skills/context-budget/SKILL.md +136 -0
  108. package/skills/continuous-agent-loop/SKILL.md +46 -0
  109. package/skills/continuous-learning/SKILL.md +132 -0
  110. package/skills/continuous-learning-v2/SKILL.md +361 -0
  111. package/skills/contract-first/SKILL.md +287 -0
  112. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
  113. package/skills/cost-tracking/SKILL.md +97 -0
  114. package/skills/council/SKILL.md +204 -0
  115. package/skills/cpp-coding-standards/SKILL.md +724 -0
  116. package/skills/cpp-testing/SKILL.md +325 -0
  117. package/skills/crosspost/SKILL.md +112 -0
  118. package/skills/csharp-testing/SKILL.md +322 -0
  119. package/skills/customer-billing-ops/SKILL.md +141 -0
  120. package/skills/customs-trade-compliance/SKILL.md +263 -0
  121. package/skills/dart-flutter-patterns/SKILL.md +564 -0
  122. package/skills/dashboard-builder/SKILL.md +109 -0
  123. package/skills/data-scraper-agent/SKILL.md +765 -0
  124. package/skills/data-throughput-accelerator/SKILL.md +74 -0
  125. package/skills/database-migrations/SKILL.md +430 -0
  126. package/skills/deep-research/SKILL.md +160 -0
  127. package/skills/defi-amm-security/SKILL.md +167 -0
  128. package/skills/delivery-gate/SKILL.md +126 -0
  129. package/skills/deployment-patterns/SKILL.md +428 -0
  130. package/skills/design-system/SKILL.md +83 -0
  131. package/skills/django-celery/SKILL.md +458 -0
  132. package/skills/django-patterns/SKILL.md +735 -0
  133. package/skills/django-security/SKILL.md +644 -0
  134. package/skills/django-tdd/SKILL.md +730 -0
  135. package/skills/django-verification/SKILL.md +470 -0
  136. package/skills/dmux-workflows/SKILL.md +192 -0
  137. package/skills/docker-patterns/SKILL.md +365 -0
  138. package/skills/documentation-lookup/SKILL.md +91 -0
  139. package/skills/dotnet-patterns/SKILL.md +322 -0
  140. package/skills/dynamic-workflow-mode/SKILL.md +124 -0
  141. package/skills/e2e-testing/SKILL.md +327 -0
  142. package/skills/ecc-guide/SKILL.md +190 -0
  143. package/skills/ecc-recipes/SKILL.md +149 -0
  144. package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
  145. package/skills/email-ops/SKILL.md +122 -0
  146. package/skills/energy-procurement/SKILL.md +228 -0
  147. package/skills/enterprise-agent-ops/SKILL.md +51 -0
  148. package/skills/error-handling/SKILL.md +377 -0
  149. package/skills/eval-harness/SKILL.md +271 -0
  150. package/skills/evm-token-decimals/SKILL.md +131 -0
  151. package/skills/exa-search/SKILL.md +108 -0
  152. package/skills/fal-ai-media/SKILL.md +289 -0
  153. package/skills/fastapi-patterns/SKILL.md +514 -0
  154. package/skills/finance-billing-ops/SKILL.md +128 -0
  155. package/skills/flox-environments/SKILL.md +497 -0
  156. package/skills/flutter-dart-code-review/SKILL.md +436 -0
  157. package/skills/foundation-models-on-device/SKILL.md +243 -0
  158. package/skills/frontend-a11y/SKILL.md +446 -0
  159. package/skills/frontend-design-direction/SKILL.md +93 -0
  160. package/skills/frontend-patterns/SKILL.md +657 -0
  161. package/skills/frontend-slides/SKILL.md +185 -0
  162. package/skills/fsharp-testing/SKILL.md +281 -0
  163. package/skills/gan-style-harness/SKILL.md +279 -0
  164. package/skills/gateguard/SKILL.md +133 -0
  165. package/skills/generating-python-installer/SKILL.md +820 -0
  166. package/skills/git-workflow/SKILL.md +716 -0
  167. package/skills/github-ops/SKILL.md +145 -0
  168. package/skills/golang-patterns/SKILL.md +676 -0
  169. package/skills/golang-testing/SKILL.md +721 -0
  170. package/skills/google-workspace-ops/SKILL.md +96 -0
  171. package/skills/growth-log/SKILL.md +128 -0
  172. package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
  173. package/skills/healthcare-emr-patterns/SKILL.md +160 -0
  174. package/skills/healthcare-eval-harness/SKILL.md +208 -0
  175. package/skills/healthcare-phi-compliance/SKILL.md +146 -0
  176. package/skills/hermes-imports/SKILL.md +89 -0
  177. package/skills/hexagonal-architecture/SKILL.md +277 -0
  178. package/skills/hipaa-compliance/SKILL.md +79 -0
  179. package/skills/homelab-network-readiness/SKILL.md +170 -0
  180. package/skills/homelab-network-setup/SKILL.md +130 -0
  181. package/skills/homelab-pihole-dns/SKILL.md +275 -0
  182. package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
  183. package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
  184. package/skills/hookify-rules/SKILL.md +128 -0
  185. package/skills/inherit-legacy-style/SKILL.md +157 -0
  186. package/skills/intent-driven-development/SKILL.md +360 -0
  187. package/skills/inventory-demand-planning/SKILL.md +247 -0
  188. package/skills/investor-materials/SKILL.md +97 -0
  189. package/skills/investor-outreach/SKILL.md +92 -0
  190. package/skills/ios-icon-gen/SKILL.md +158 -0
  191. package/skills/iterative-retrieval/SKILL.md +212 -0
  192. package/skills/ito-basket-compare/SKILL.md +64 -0
  193. package/skills/ito-compute/SKILL.md +129 -0
  194. package/skills/ito-data-atlas-agent/SKILL.md +64 -0
  195. package/skills/ito-market-intelligence/SKILL.md +61 -0
  196. package/skills/ito-trade-planner/SKILL.md +68 -0
  197. package/skills/java-coding-standards/SKILL.md +384 -0
  198. package/skills/jira-integration/SKILL.md +303 -0
  199. package/skills/jpa-patterns/SKILL.md +152 -0
  200. package/skills/knowledge-ops/SKILL.md +155 -0
  201. package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
  202. package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
  203. package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
  204. package/skills/kotlin-patterns/SKILL.md +712 -0
  205. package/skills/kotlin-testing/SKILL.md +825 -0
  206. package/skills/kubernetes-patterns/SKILL.md +756 -0
  207. package/skills/laravel-patterns/SKILL.md +416 -0
  208. package/skills/laravel-plugin-discovery/SKILL.md +230 -0
  209. package/skills/laravel-security/SKILL.md +948 -0
  210. package/skills/laravel-tdd/SKILL.md +675 -0
  211. package/skills/laravel-verification/SKILL.md +180 -0
  212. package/skills/latency-critical-systems/SKILL.md +75 -0
  213. package/skills/lead-intelligence/SKILL.md +322 -0
  214. package/skills/liquid-glass-design/SKILL.md +279 -0
  215. package/skills/llm-trading-agent-security/SKILL.md +147 -0
  216. package/skills/logistics-exception-management/SKILL.md +222 -0
  217. package/skills/loop-design-check/SKILL.md +143 -0
  218. package/skills/mailtrap-email-integration/SKILL.md +77 -0
  219. package/skills/make-interfaces-feel-better/SKILL.md +152 -0
  220. package/skills/manim-video/SKILL.md +90 -0
  221. package/skills/market-research/SKILL.md +76 -0
  222. package/skills/marketing-campaign/SKILL.md +114 -0
  223. package/skills/mcp-server-patterns/SKILL.md +70 -0
  224. package/skills/messages-ops/SKILL.md +105 -0
  225. package/skills/ml-adoption-playbook/SKILL.md +57 -0
  226. package/skills/mle-workflow/SKILL.md +348 -0
  227. package/skills/motion-advanced/SKILL.md +596 -0
  228. package/skills/motion-foundations/SKILL.md +299 -0
  229. package/skills/motion-patterns/SKILL.md +434 -0
  230. package/skills/motion-ui/SKILL.md +576 -0
  231. package/skills/mysql-patterns/SKILL.md +413 -0
  232. package/skills/nanoclaw-repl/SKILL.md +34 -0
  233. package/skills/nestjs-patterns/SKILL.md +231 -0
  234. package/skills/netmiko-ssh-automation/SKILL.md +174 -0
  235. package/skills/network-bgp-diagnostics/SKILL.md +168 -0
  236. package/skills/network-config-validation/SKILL.md +211 -0
  237. package/skills/network-interface-health/SKILL.md +153 -0
  238. package/skills/nextjs-turbopack/SKILL.md +58 -0
  239. package/skills/nodejs-keccak256/SKILL.md +103 -0
  240. package/skills/nutrient-document-processing/SKILL.md +168 -0
  241. package/skills/nuxt4-patterns/SKILL.md +101 -0
  242. package/skills/openclaw-persona-forge/SKILL.md +289 -0
  243. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
  244. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
  245. package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
  246. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
  247. package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
  248. package/skills/openclaw-persona-forge/references/output-template.md +166 -0
  249. package/skills/opensource-pipeline/SKILL.md +256 -0
  250. package/skills/orch-add-feature/SKILL.md +45 -0
  251. package/skills/orch-build-mvp/SKILL.md +49 -0
  252. package/skills/orch-change-feature/SKILL.md +43 -0
  253. package/skills/orch-fix-defect/SKILL.md +43 -0
  254. package/skills/orch-pipeline/SKILL.md +121 -0
  255. package/skills/orch-refine-code/SKILL.md +44 -0
  256. package/skills/parallel-execution-optimizer/SKILL.md +74 -0
  257. package/skills/perl-patterns/SKILL.md +505 -0
  258. package/skills/perl-security/SKILL.md +504 -0
  259. package/skills/perl-testing/SKILL.md +476 -0
  260. package/skills/plan-canvas/SKILL.md +153 -0
  261. package/skills/plan-orchestrate/SKILL.md +263 -0
  262. package/skills/plankton-code-quality/SKILL.md +237 -0
  263. package/skills/postgres-patterns/SKILL.md +148 -0
  264. package/skills/prediction-market-oracle-research/SKILL.md +64 -0
  265. package/skills/prediction-market-risk-review/SKILL.md +61 -0
  266. package/skills/prisma-patterns/SKILL.md +401 -0
  267. package/skills/product-capability/SKILL.md +142 -0
  268. package/skills/product-lens/SKILL.md +93 -0
  269. package/skills/production-audit/SKILL.md +207 -0
  270. package/skills/production-scheduling/SKILL.md +238 -0
  271. package/skills/project-flow-ops/SKILL.md +112 -0
  272. package/skills/prompt-optimizer/SKILL.md +398 -0
  273. package/skills/python-patterns/SKILL.md +751 -0
  274. package/skills/python-testing/SKILL.md +817 -0
  275. package/skills/pytorch-patterns/SKILL.md +397 -0
  276. package/skills/quality-nonconformance/SKILL.md +260 -0
  277. package/skills/quarkus-patterns/SKILL.md +723 -0
  278. package/skills/quarkus-security/SKILL.md +468 -0
  279. package/skills/quarkus-tdd/SKILL.md +812 -0
  280. package/skills/quarkus-verification/SKILL.md +480 -0
  281. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
  282. package/skills/react-native-patterns/SKILL.md +326 -0
  283. package/skills/react-patterns/SKILL.md +342 -0
  284. package/skills/react-performance/SKILL.md +575 -0
  285. package/skills/react-testing/SKILL.md +424 -0
  286. package/skills/recsys-pipeline-architect/SKILL.md +115 -0
  287. package/skills/recursive-decision-ledger/SKILL.md +81 -0
  288. package/skills/redis-patterns/SKILL.md +404 -0
  289. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
  290. package/skills/remotion-video-creation/SKILL.md +43 -0
  291. package/skills/repo-scan/SKILL.md +79 -0
  292. package/skills/research-ops/SKILL.md +113 -0
  293. package/skills/returns-reverse-logistics/SKILL.md +240 -0
  294. package/skills/rules-distill/SKILL.md +265 -0
  295. package/skills/rust-patterns/SKILL.md +500 -0
  296. package/skills/rust-testing/SKILL.md +501 -0
  297. package/skills/safety-guard/SKILL.md +76 -0
  298. package/skills/santa-method/SKILL.md +307 -0
  299. package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
  300. package/skills/scientific-db-uspto-database/SKILL.md +178 -0
  301. package/skills/scientific-pkg-gget/SKILL.md +167 -0
  302. package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
  303. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
  304. package/skills/search-first/SKILL.md +183 -0
  305. package/skills/security-bounty-hunter/SKILL.md +100 -0
  306. package/skills/security-review/SKILL.md +504 -0
  307. package/skills/security-scan/SKILL.md +166 -0
  308. package/skills/seo/SKILL.md +155 -0
  309. package/skills/skill-comply/SKILL.md +59 -0
  310. package/skills/skill-scout/SKILL.md +141 -0
  311. package/skills/skill-stocktake/SKILL.md +195 -0
  312. package/skills/social-graph-ranker/SKILL.md +155 -0
  313. package/skills/social-publisher/SKILL.md +130 -0
  314. package/skills/springboot-patterns/SKILL.md +315 -0
  315. package/skills/springboot-security/SKILL.md +273 -0
  316. package/skills/springboot-tdd/SKILL.md +159 -0
  317. package/skills/springboot-verification/SKILL.md +232 -0
  318. package/skills/strategic-compact/SKILL.md +142 -0
  319. package/skills/swift-actor-persistence/SKILL.md +144 -0
  320. package/skills/swift-concurrency-6-2/SKILL.md +216 -0
  321. package/skills/swift-protocol-di-testing/SKILL.md +191 -0
  322. package/skills/swiftui-patterns/SKILL.md +259 -0
  323. package/skills/taste/SKILL.md +264 -0
  324. package/skills/taste/references/genre-taxonomy.md +87 -0
  325. package/skills/tdd-workflow/SKILL.md +583 -0
  326. package/skills/team-agent-orchestration/SKILL.md +111 -0
  327. package/skills/team-builder/SKILL.md +169 -0
  328. package/skills/terminal-ops/SKILL.md +110 -0
  329. package/skills/tinystruct-patterns/SKILL.md +279 -0
  330. package/skills/tinystruct-patterns/references/architecture.md +90 -0
  331. package/skills/tinystruct-patterns/references/data-handling.md +60 -0
  332. package/skills/tinystruct-patterns/references/database.md +99 -0
  333. package/skills/tinystruct-patterns/references/routing.md +64 -0
  334. package/skills/tinystruct-patterns/references/system-usage.md +97 -0
  335. package/skills/tinystruct-patterns/references/testing.md +72 -0
  336. package/skills/token-budget-advisor/SKILL.md +134 -0
  337. package/skills/ui-demo/SKILL.md +466 -0
  338. package/skills/ui-to-vue/SKILL.md +135 -0
  339. package/skills/uncloud/SKILL.md +344 -0
  340. package/skills/unified-memory/SKILL.md +170 -0
  341. package/skills/unified-notifications-ops/SKILL.md +188 -0
  342. package/skills/verification-loop/SKILL.md +129 -0
  343. package/skills/video-editing/SKILL.md +311 -0
  344. package/skills/videodb/SKILL.md +375 -0
  345. package/skills/visa-doc-translate/SKILL.md +117 -0
  346. package/skills/vite-patterns/SKILL.md +450 -0
  347. package/skills/vue-patterns/SKILL.md +471 -0
  348. package/skills/windows-desktop-e2e/SKILL.md +888 -0
  349. package/skills/workspace-surface-audit/SKILL.md +126 -0
  350. package/skills/x-api/SKILL.md +235 -0
package/lib/ai.js CHANGED
@@ -1,22 +1,32 @@
1
1
  /**
2
2
  * Codeply CLI — AI client
3
3
  *
4
- * Two backends, selected by lib/config.js:
4
+ * Backends, selected by lib/config.js:
5
5
  *
6
- * codeply the 'ai-proxy' Supabase Edge Function the desktop app uses (see
7
- * Codeply-App/supabase/functions/ai-proxy). No provider key lives in
8
- * this CLI; it authenticates with the signed-in session from
9
- * lib/auth.js, so usage counts against that account's daily cap.
6
+ * codeply the 'ai-proxy' Supabase Edge Function the desktop app uses
7
+ * (see Codeply-App/supabase/functions/ai-proxy). No provider
8
+ * key lives in this CLI; it authenticates with the signed-in
9
+ * session from lib/auth.js, so usage counts against that
10
+ * account's shared daily cap.
10
11
  *
11
- * ollama Ollama's OpenAI-compatible /v1/chat/completions, either the cloud
12
- * (ollama.com, bearer key) or a local daemon (no key). No Codeply
13
- * sign-in required, and no daily cap.
12
+ * ollama Ollama's OpenAI-compatible /v1/chat/completions, either the
13
+ * cloud (ollama.com, bearer key) or a local daemon (no key).
14
+ * No Codeply sign-in required, and no daily cap.
14
15
  *
15
- * Both return the identical shape — {success, data:{choices:[{message}]}, …} —
16
+ * openrouter, groq, openai BYOK: the user's own key against that
17
+ * provider's OpenAI-compatible chat completions endpoint.
18
+ * No Codeply account involved at all.
19
+ *
20
+ * anthropic BYOK against Anthropic's Messages API, which is NOT
21
+ * OpenAI-compatible (different endpoint, a top-level `system`
22
+ * field instead of a system message, and a `content` array in
23
+ * the response) — handled by its own converter below.
24
+ *
25
+ * All of these return the identical shape — {success, data:{choices:[{message}]}, …} —
16
26
  * so nothing downstream (agent loop, edit engine) knows or cares which ran.
17
27
  */
18
28
  const { SUPABASE_URL, SUPABASE_ANON_KEY, getAccessToken } = require('./auth');
19
- const { getConfig } = require('./config');
29
+ const { getConfig, byokHint } = require('./config');
20
30
 
21
31
  const AI_PROXY_URL = `${SUPABASE_URL}/functions/v1/ai-proxy`;
22
32
 
@@ -103,7 +113,11 @@ async function chatViaProxy(messages, opts) {
103
113
  // A structured error from the function itself is final: it means the
104
114
  // function ran and deliberately rejected us (auth, daily cap, provider
105
115
  // error). Retrying that just burns the user's quota.
106
- if (body.error) return { done: true, value: { success: false, error: body.error } };
116
+ if (body.error) {
117
+ const hitCap = isRateLimitError(body.error) || /daily .* (limit|cap)/i.test(body.error);
118
+ const error = hitCap ? `${body.error} ${byokHint()}` : body.error;
119
+ return { done: true, value: { success: false, error } };
120
+ }
107
121
 
108
122
  return { retryable: isTransientStatus(res.status), error: describeStatus(res.status) };
109
123
  });
@@ -159,6 +173,189 @@ async function chatViaOllama(messages, opts, cfg) {
159
173
  });
160
174
  }
161
175
 
176
+ // OpenAI-compatible /v1/chat/completions endpoints for the three providers
177
+ // that speak that same dialect. Ollama has its own function above because it
178
+ // also has to handle "no key at all for a local daemon", which these never do.
179
+ const OPENAI_COMPATIBLE = {
180
+ openrouter: {
181
+ url: 'https://openrouter.ai/api/v1/chat/completions',
182
+ label: 'OpenRouter',
183
+ extraHeaders: { 'HTTP-Referer': 'https://codeply.online', 'X-Title': 'Codeply' },
184
+ },
185
+ groq: {
186
+ url: 'https://api.groq.com/openai/v1/chat/completions',
187
+ label: 'Groq',
188
+ },
189
+ openai: {
190
+ url: 'https://api.openai.com/v1/chat/completions',
191
+ label: 'OpenAI',
192
+ },
193
+ };
194
+
195
+ async function chatViaOpenAICompatible(messages, opts, providerName, cfg) {
196
+ const { url, label, extraHeaders } = OPENAI_COMPATIBLE[providerName];
197
+ const { apiKey, model } = cfg[providerName];
198
+ if (!apiKey) {
199
+ return { success: false, error: `No ${label} API key set. Run \`codeply provider ${providerName} --key <key>\`.` };
200
+ }
201
+
202
+ return withRetries(async () => {
203
+ let res;
204
+ try {
205
+ res = await fetch(url, {
206
+ method: 'POST',
207
+ headers: {
208
+ 'Content-Type': 'application/json',
209
+ 'Authorization': `Bearer ${apiKey}`,
210
+ ...extraHeaders,
211
+ },
212
+ body: JSON.stringify({
213
+ model,
214
+ messages,
215
+ temperature: opts.temperature ?? 0,
216
+ ...(opts.maxTokens ? { max_tokens: opts.maxTokens } : {}),
217
+ ...(opts.json ? { response_format: { type: 'json_object' } } : {}),
218
+ }),
219
+ });
220
+ } catch (e) {
221
+ return { retryable: true, error: e.message };
222
+ }
223
+
224
+ const body = await res.json().catch(() => ({}));
225
+
226
+ if (res.ok && body.choices?.[0]) {
227
+ return { done: true, value: { success: true, data: body, modelUsed: body.model || model } };
228
+ }
229
+
230
+ const apiError = body?.error?.message || body?.error;
231
+ if (apiError) {
232
+ const msg = typeof apiError === 'string' ? apiError : JSON.stringify(apiError);
233
+ const fatal = /not found|does not exist|unknown model|unauthor|invalid.*key|forbidden/i.test(msg);
234
+ if (fatal) return { done: true, value: { success: false, error: `${label}: ${msg}` } };
235
+ return { retryable: isTransientStatus(res.status), error: `${label}: ${msg}` };
236
+ }
237
+
238
+ return { retryable: isTransientStatus(res.status), error: describeStatus(res.status) };
239
+ });
240
+ }
241
+
242
+ // Below this many characters a block essentially never clears Anthropic's
243
+ // minimum-token floor for caching (1024 tokens on Sonnet-class models, higher
244
+ // on Haiku) — offering cache_control on it just adds a cache-write surcharge
245
+ // for a block that will never actually be served from cache. ~4 chars/token is
246
+ // a deliberately conservative floor, not a precise count.
247
+ const ANTHROPIC_CACHEABLE_MIN_CHARS = 4000;
248
+
249
+ /**
250
+ * Anthropic's Messages API isn't OpenAI-shaped: system prompt is a top-level
251
+ * field (not a message with role "system"), and the reply comes back as a
252
+ * `content` block array rather than `choices[0].message`. Converted both ways
253
+ * here so the rest of the codebase never has to know Anthropic is different.
254
+ *
255
+ * Prompt caching (`cache_control: {type:'ephemeral'}`) is applied at two
256
+ * breakpoints, because of how the agent loop in lib/agent.mjs actually calls
257
+ * this: one user turn can take up to 24 steps, and EVERY step resends the
258
+ * ENTIRE message array built up so far — there is no other way to talk to a
259
+ * stateless chat completions API. Without caching, a 20-step turn re-bills the
260
+ * system prompt and the whole growing transcript from scratch 20 times over.
261
+ * With it:
262
+ * 1. The system prompt (mode instructions + tool reference + skill index +
263
+ * project context) is byte-identical across every step of a turn, and
264
+ * usually across the whole session — cached at ~10% of its input-token
265
+ * cost after the first call.
266
+ * 2. Everything except the newest message is marked as a second breakpoint,
267
+ * so step N's request reuses step N-1's cache instead of re-billing the
268
+ * whole transcript-so-far — turning per-step cost from O(conversation
269
+ * length) into roughly O(what's new since the last step).
270
+ * Anthropic allows the read side of a cache hit to apply automatically for any
271
+ * request sharing a cached prefix, so these two breakpoints are enough; more
272
+ * would just spend the (4/hr) breakpoint limit for no further benefit here.
273
+ */
274
+ async function chatViaAnthropic(messages, opts, cfg) {
275
+ const { apiKey, model } = cfg.anthropic;
276
+ if (!apiKey) {
277
+ return { success: false, error: 'No Anthropic API key set. Run `codeply provider anthropic --key <key>`.' };
278
+ }
279
+
280
+ const systemText = messages.filter((m) => m.role === 'system').map((m) => m.content).join('\n\n');
281
+ const turns = messages
282
+ .filter((m) => m.role !== 'system')
283
+ .map((m) => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content }));
284
+
285
+ // Anthropic has no json_object response mode — ask for it in plain English instead.
286
+ const systemText2 = opts.json
287
+ ? `${systemText}\n\nRespond with ONLY a single valid JSON object. No prose, no markdown fences, nothing before or after it.`
288
+ : systemText;
289
+
290
+ const cacheControl = { type: 'ephemeral' };
291
+ const system = systemText2.length >= ANTHROPIC_CACHEABLE_MIN_CHARS
292
+ ? [{ type: 'text', text: systemText2, cache_control: cacheControl }]
293
+ : systemText2;
294
+
295
+ // Breakpoint 2: everything but the newest message. A single-message request
296
+ // (the very first call of a brand-new turn) has nothing "so far" to cache.
297
+ if (turns.length >= 2) {
298
+ const idx = turns.length - 2;
299
+ if (turns[idx].content.length >= ANTHROPIC_CACHEABLE_MIN_CHARS) {
300
+ turns[idx] = {
301
+ role: turns[idx].role,
302
+ content: [{ type: 'text', text: turns[idx].content, cache_control: cacheControl }],
303
+ };
304
+ }
305
+ }
306
+
307
+ return withRetries(async () => {
308
+ let res;
309
+ try {
310
+ res = await fetch('https://api.anthropic.com/v1/messages', {
311
+ method: 'POST',
312
+ headers: {
313
+ 'Content-Type': 'application/json',
314
+ 'x-api-key': apiKey,
315
+ 'anthropic-version': '2023-06-01',
316
+ },
317
+ body: JSON.stringify({
318
+ model,
319
+ system,
320
+ messages: turns,
321
+ max_tokens: opts.maxTokens || 8192,
322
+ temperature: opts.temperature ?? 0,
323
+ }),
324
+ });
325
+ } catch (e) {
326
+ return { retryable: true, error: e.message };
327
+ }
328
+
329
+ const body = await res.json().catch(() => ({}));
330
+
331
+ if (res.ok && Array.isArray(body.content)) {
332
+ const text = body.content.filter((b) => b.type === 'text').map((b) => b.text).join('');
333
+ const data = {
334
+ choices: [{ message: { role: 'assistant', content: text }, finish_reason: body.stop_reason === 'max_tokens' ? 'length' : 'stop' }],
335
+ usage: {
336
+ prompt_tokens: body.usage?.input_tokens || 0,
337
+ completion_tokens: body.usage?.output_tokens || 0,
338
+ total_tokens: (body.usage?.input_tokens || 0) + (body.usage?.output_tokens || 0),
339
+ // Surfaced for anyone instrumenting cost later — not read anywhere yet.
340
+ cache_read_tokens: body.usage?.cache_read_input_tokens || 0,
341
+ cache_write_tokens: body.usage?.cache_creation_input_tokens || 0,
342
+ },
343
+ };
344
+ return { done: true, value: { success: true, data, modelUsed: model } };
345
+ }
346
+
347
+ const apiError = body?.error?.message || body?.error;
348
+ if (apiError) {
349
+ const msg = typeof apiError === 'string' ? apiError : JSON.stringify(apiError);
350
+ const fatal = /not_found_error|authentication_error|permission_error|invalid.*key/i.test(JSON.stringify(body.error || {})) || res.status === 401 || res.status === 403 || res.status === 404;
351
+ if (fatal) return { done: true, value: { success: false, error: `Anthropic: ${msg}` } };
352
+ return { retryable: isTransientStatus(res.status), error: `Anthropic: ${msg}` };
353
+ }
354
+
355
+ return { retryable: isTransientStatus(res.status), error: describeStatus(res.status) };
356
+ });
357
+ }
358
+
162
359
  /**
163
360
  * @param {Array<{role:string, content:string}>} messages
164
361
  * @param {{ json?: boolean, maxTokens?: number, temperature?: number }} opts
@@ -166,9 +363,15 @@ async function chatViaOllama(messages, opts, cfg) {
166
363
  */
167
364
  async function chat(messages, opts = {}) {
168
365
  const cfg = getConfig();
169
- return cfg.provider === 'ollama'
170
- ? chatViaOllama(messages, opts, cfg)
171
- : chatViaProxy(messages, opts);
366
+ switch (cfg.provider) {
367
+ case 'ollama': return chatViaOllama(messages, opts, cfg);
368
+ case 'anthropic': return chatViaAnthropic(messages, opts, cfg);
369
+ case 'openrouter':
370
+ case 'groq':
371
+ case 'openai':
372
+ return chatViaOpenAICompatible(messages, opts, cfg.provider, cfg);
373
+ default: return chatViaProxy(messages, opts);
374
+ }
172
375
  }
173
376
 
174
377
  /** Chat helper that expects a JSON object back and parses it. */
@@ -26,13 +26,23 @@ async function checkApplyLimit() {
26
26
  }
27
27
  }
28
28
 
29
- /** Records one successful file write toward the daily cap. Fire-and-forget. */
30
- async function recordApplyEvent(filePath) {
29
+ /**
30
+ * Records one successful file write toward the daily cap. Fire-and-forget.
31
+ * `linesAdded`/`linesRemoved` are what the admin dashboard's per-user view
32
+ * sums up to show how much someone actually changed, not just how often —
33
+ * deliberately just counts, never the file content itself.
34
+ */
35
+ async function recordApplyEvent(filePath, linesAdded = 0, linesRemoved = 0) {
31
36
  const session = await getSession();
32
37
  if (!session) return;
33
38
  const supabase = getClient();
34
39
  try {
35
- await supabase.from('apply_history').insert({ user_id: session.user.id, file_path: filePath || '' });
40
+ await supabase.from('apply_history').insert({
41
+ user_id: session.user.id,
42
+ file_path: filePath || '',
43
+ lines_added: linesAdded || 0,
44
+ lines_removed: linesRemoved || 0,
45
+ });
36
46
  } catch (e) {
37
47
  console.warn('[apply-limit] record failed:', e.message);
38
48
  }
package/lib/config.js CHANGED
@@ -2,14 +2,22 @@
2
2
  * Codeply CLI — model provider configuration.
3
3
  *
4
4
  * Lives in ~/.codeply/config.json, next to auth.json, deliberately OUTSIDE the
5
- * repo: it holds an API key and must never be committable. Environment
5
+ * repo: it holds API keys and must never be committable. Environment
6
6
  * variables win over the file so a key can be supplied per-shell without ever
7
7
  * touching disk.
8
8
  *
9
9
  * Providers:
10
- * codeply the hosted ai-proxy (default; needs `codeply login`)
11
- * ollama Ollama's OpenAI-compatible API — cloud (ollama.com, needs a key)
12
- * or a local daemon (http://localhost:11434, no key needed)
10
+ * codeply the hosted ai-proxy (default; needs `codeply login`), shares
11
+ * a daily cap with the desktop app
12
+ * ollama Ollama's OpenAI-compatible API — cloud (ollama.com, needs a
13
+ * key) or a local daemon (http://localhost:11434, no key)
14
+ * openrouter bring your own OpenRouter key, no cap
15
+ * groq bring your own Groq key, no cap
16
+ * anthropic bring your own Anthropic key, no cap
17
+ * openai bring your own OpenAI key, no cap
18
+ *
19
+ * The last four are "BYOK": once a key is set, the CLI talks to that
20
+ * provider directly and never touches the shared Codeply account limit.
13
21
  */
14
22
  const fs = require('fs');
15
23
  const path = require('path');
@@ -18,13 +26,16 @@ const os = require('os');
18
26
  const configDir = path.join(os.homedir(), '.codeply');
19
27
  const configPath = path.join(configDir, 'config.json');
20
28
 
29
+ const PROVIDERS = ['codeply', 'ollama', 'openrouter', 'groq', 'anthropic', 'openai'];
30
+ const BYOK_PROVIDERS = ['openrouter', 'groq', 'anthropic', 'openai'];
31
+
21
32
  const DEFAULTS = {
22
33
  provider: 'codeply',
23
- ollama: {
24
- host: 'https://ollama.com',
25
- model: 'gemma4:31b',
26
- apiKey: '',
27
- },
34
+ ollama: { host: 'https://ollama.com', model: 'gemma4:31b', apiKey: '' },
35
+ openrouter: { model: 'openrouter/auto', apiKey: '' },
36
+ groq: { model: 'openai/gpt-oss-120b', apiKey: '' },
37
+ anthropic: { model: 'claude-sonnet-5', apiKey: '' },
38
+ openai: { model: 'gpt-4o-mini', apiKey: '' },
28
39
  };
29
40
 
30
41
  function readFile() {
@@ -41,6 +52,16 @@ function getConfig() {
41
52
  const provider = process.env.CODEPLY_PROVIDER || file.provider || DEFAULTS.provider;
42
53
  const fileOllama = file.ollama || {};
43
54
 
55
+ const byok = {};
56
+ for (const name of BYOK_PROVIDERS) {
57
+ const fileEntry = file[name] || {};
58
+ const envPrefix = name.toUpperCase();
59
+ byok[name] = {
60
+ model: process.env[`${envPrefix}_MODEL`] || fileEntry.model || DEFAULTS[name].model,
61
+ apiKey: process.env[`${envPrefix}_API_KEY`] || fileEntry.apiKey || '',
62
+ };
63
+ }
64
+
44
65
  return {
45
66
  provider,
46
67
  ollama: {
@@ -48,17 +69,19 @@ function getConfig() {
48
69
  model: process.env.OLLAMA_MODEL || fileOllama.model || DEFAULTS.ollama.model,
49
70
  apiKey: process.env.OLLAMA_API_KEY || fileOllama.apiKey || '',
50
71
  },
72
+ ...byok,
51
73
  };
52
74
  }
53
75
 
54
76
  /** Merge a patch into the stored config. Written user-only where supported. */
55
77
  function saveConfig(patch) {
56
78
  const current = readFile();
57
- const next = {
58
- ...current,
59
- ...patch,
60
- ollama: { ...(current.ollama || {}), ...(patch.ollama || {}) },
61
- };
79
+ const next = { ...current, ...patch };
80
+ for (const name of ['ollama', ...BYOK_PROVIDERS]) {
81
+ if (current[name] || patch[name]) {
82
+ next[name] = { ...(current[name] || {}), ...(patch[name] || {}) };
83
+ }
84
+ }
62
85
  try {
63
86
  if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
64
87
  fs.writeFileSync(configPath, JSON.stringify(next, null, 2), { mode: 0o600 });
@@ -83,6 +106,9 @@ function describeProvider(cfg = getConfig()) {
83
106
  const where = /localhost|127\.0\.0\.1/.test(cfg.ollama.host) ? 'local' : 'cloud';
84
107
  return `ollama ${where} · ${cfg.ollama.model}`;
85
108
  }
109
+ if (BYOK_PROVIDERS.includes(cfg.provider)) {
110
+ return `${cfg.provider} (own key) · ${cfg[cfg.provider].model}`;
111
+ }
86
112
  return 'codeply proxy';
87
113
  }
88
114
 
@@ -92,7 +118,19 @@ function describeProviderShort(cfg = getConfig()) {
92
118
  const where = /localhost|127\.0\.0\.1/.test(cfg.ollama.host) ? 'local' : 'cloud';
93
119
  return `ollama ${where}`;
94
120
  }
121
+ if (BYOK_PROVIDERS.includes(cfg.provider)) {
122
+ return `${cfg.provider} (own key)`;
123
+ }
95
124
  return 'codeply proxy';
96
125
  }
97
126
 
98
- module.exports = { getConfig, saveConfig, maskKey, describeProvider, describeProviderShort, configPath };
127
+ /** Suggested next step when the shared codeply account hits its daily cap. */
128
+ function byokHint() {
129
+ return 'Have your own API key? Bring your own model instead, no shared daily cap: ' +
130
+ 'codeply provider openrouter --key <key> (or groq, anthropic, openai).';
131
+ }
132
+
133
+ module.exports = {
134
+ getConfig, saveConfig, maskKey, describeProvider, describeProviderShort, byokHint,
135
+ configPath, PROVIDERS, BYOK_PROVIDERS,
136
+ };