codeply-cli 0.1.0 → 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 (351) hide show
  1. package/bin/codeply.js +125 -14
  2. package/lib/agent.mjs +157 -18
  3. package/lib/ai.js +221 -15
  4. package/lib/apply-limit.js +13 -3
  5. package/lib/config.js +63 -16
  6. package/lib/edit-engine.js +1 -1
  7. package/lib/skills.js +358 -0
  8. package/lib/tools.mjs +63 -6
  9. package/lib/tui.mjs +96 -3
  10. package/package.json +3 -2
  11. package/skills/SOURCE.md +58 -0
  12. package/skills/accessibility/SKILL.md +147 -0
  13. package/skills/agent-architecture-audit/SKILL.md +257 -0
  14. package/skills/agent-eval/SKILL.md +147 -0
  15. package/skills/agent-harness-construction/SKILL.md +74 -0
  16. package/skills/agent-introspection-debugging/SKILL.md +154 -0
  17. package/skills/agent-payment-x402/SKILL.md +225 -0
  18. package/skills/agent-self-evaluation/SKILL.md +182 -0
  19. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
  20. package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
  21. package/skills/agent-sort/SKILL.md +216 -0
  22. package/skills/agentic-engineering/SKILL.md +64 -0
  23. package/skills/agentic-os/SKILL.md +388 -0
  24. package/skills/ai-first-engineering/SKILL.md +52 -0
  25. package/skills/ai-regression-testing/SKILL.md +386 -0
  26. package/skills/android-clean-architecture/SKILL.md +340 -0
  27. package/skills/angular-developer/SKILL.md +155 -0
  28. package/skills/angular-developer/references/angular-animations.md +160 -0
  29. package/skills/angular-developer/references/angular-aria.md +410 -0
  30. package/skills/angular-developer/references/cli.md +86 -0
  31. package/skills/angular-developer/references/component-harnesses.md +59 -0
  32. package/skills/angular-developer/references/component-styling.md +91 -0
  33. package/skills/angular-developer/references/components.md +117 -0
  34. package/skills/angular-developer/references/creating-services.md +97 -0
  35. package/skills/angular-developer/references/data-resolvers.md +69 -0
  36. package/skills/angular-developer/references/define-routes.md +67 -0
  37. package/skills/angular-developer/references/defining-providers.md +72 -0
  38. package/skills/angular-developer/references/di-fundamentals.md +120 -0
  39. package/skills/angular-developer/references/e2e-testing.md +56 -0
  40. package/skills/angular-developer/references/effects.md +83 -0
  41. package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  42. package/skills/angular-developer/references/host-elements.md +80 -0
  43. package/skills/angular-developer/references/injection-context.md +63 -0
  44. package/skills/angular-developer/references/inputs.md +101 -0
  45. package/skills/angular-developer/references/linked-signal.md +59 -0
  46. package/skills/angular-developer/references/loading-strategies.md +61 -0
  47. package/skills/angular-developer/references/mcp.md +108 -0
  48. package/skills/angular-developer/references/navigate-to-routes.md +69 -0
  49. package/skills/angular-developer/references/outputs.md +86 -0
  50. package/skills/angular-developer/references/reactive-forms.md +122 -0
  51. package/skills/angular-developer/references/rendering-strategies.md +44 -0
  52. package/skills/angular-developer/references/resource.md +77 -0
  53. package/skills/angular-developer/references/route-animations.md +56 -0
  54. package/skills/angular-developer/references/route-guards.md +52 -0
  55. package/skills/angular-developer/references/router-lifecycle.md +45 -0
  56. package/skills/angular-developer/references/router-testing.md +87 -0
  57. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  58. package/skills/angular-developer/references/signal-forms.md +795 -0
  59. package/skills/angular-developer/references/signals-overview.md +94 -0
  60. package/skills/angular-developer/references/tailwind-css.md +69 -0
  61. package/skills/angular-developer/references/template-driven-forms.md +114 -0
  62. package/skills/angular-developer/references/testing-fundamentals.md +65 -0
  63. package/skills/api-connector-builder/SKILL.md +121 -0
  64. package/skills/api-design/SKILL.md +524 -0
  65. package/skills/architecture-decision-records/SKILL.md +180 -0
  66. package/skills/article-writing/SKILL.md +80 -0
  67. package/skills/automation-audit-ops/SKILL.md +143 -0
  68. package/skills/autonomous-agent-harness/SKILL.md +274 -0
  69. package/skills/autonomous-loops/SKILL.md +611 -0
  70. package/skills/backend-patterns/SKILL.md +562 -0
  71. package/skills/benchmark/SKILL.md +95 -0
  72. package/skills/benchmark-methodology/SKILL.md +191 -0
  73. package/skills/benchmark-optimization-loop/SKILL.md +71 -0
  74. package/skills/blender-motion-state-inspection/SKILL.md +165 -0
  75. package/skills/blueprint/SKILL.md +106 -0
  76. package/skills/brand-discovery/SKILL.md +145 -0
  77. package/skills/brand-discovery/references/10_purpose-why.md +40 -0
  78. package/skills/brand-discovery/references/20_positioning.md +44 -0
  79. package/skills/brand-discovery/references/30_audience-niche.md +52 -0
  80. package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
  81. package/skills/brand-discovery/references/50_voice-tone.md +59 -0
  82. package/skills/brand-discovery/references/60_narrative-story.md +50 -0
  83. package/skills/brand-discovery/references/70_founder-tension.md +49 -0
  84. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
  85. package/skills/brand-voice/SKILL.md +98 -0
  86. package/skills/brand-voice/references/voice-profile-schema.md +55 -0
  87. package/skills/browser-qa/SKILL.md +105 -0
  88. package/skills/bun-runtime/SKILL.md +85 -0
  89. package/skills/canary-watch/SKILL.md +108 -0
  90. package/skills/carrier-relationship-management/SKILL.md +212 -0
  91. package/skills/cisco-ios-patterns/SKILL.md +164 -0
  92. package/skills/ck/SKILL.md +148 -0
  93. package/skills/claude-devfleet/SKILL.md +112 -0
  94. package/skills/click-path-audit/SKILL.md +245 -0
  95. package/skills/clickhouse-io/SKILL.md +445 -0
  96. package/skills/code-tour/SKILL.md +254 -0
  97. package/skills/codebase-onboarding/SKILL.md +234 -0
  98. package/skills/codehealth-mcp/SKILL.md +167 -0
  99. package/skills/coding-standards/SKILL.md +551 -0
  100. package/skills/competitive-platform-analysis/SKILL.md +214 -0
  101. package/skills/competitive-report-structure/SKILL.md +162 -0
  102. package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
  103. package/skills/config-gc/SKILL.md +120 -0
  104. package/skills/configure-ecc/SKILL.md +385 -0
  105. package/skills/connections-optimizer/SKILL.md +190 -0
  106. package/skills/content-engine/SKILL.md +132 -0
  107. package/skills/content-hash-cache-pattern/SKILL.md +162 -0
  108. package/skills/context-budget/SKILL.md +136 -0
  109. package/skills/continuous-agent-loop/SKILL.md +46 -0
  110. package/skills/continuous-learning/SKILL.md +132 -0
  111. package/skills/continuous-learning-v2/SKILL.md +361 -0
  112. package/skills/contract-first/SKILL.md +287 -0
  113. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
  114. package/skills/cost-tracking/SKILL.md +97 -0
  115. package/skills/council/SKILL.md +204 -0
  116. package/skills/cpp-coding-standards/SKILL.md +724 -0
  117. package/skills/cpp-testing/SKILL.md +325 -0
  118. package/skills/crosspost/SKILL.md +112 -0
  119. package/skills/csharp-testing/SKILL.md +322 -0
  120. package/skills/customer-billing-ops/SKILL.md +141 -0
  121. package/skills/customs-trade-compliance/SKILL.md +263 -0
  122. package/skills/dart-flutter-patterns/SKILL.md +564 -0
  123. package/skills/dashboard-builder/SKILL.md +109 -0
  124. package/skills/data-scraper-agent/SKILL.md +765 -0
  125. package/skills/data-throughput-accelerator/SKILL.md +74 -0
  126. package/skills/database-migrations/SKILL.md +430 -0
  127. package/skills/deep-research/SKILL.md +160 -0
  128. package/skills/defi-amm-security/SKILL.md +167 -0
  129. package/skills/delivery-gate/SKILL.md +126 -0
  130. package/skills/deployment-patterns/SKILL.md +428 -0
  131. package/skills/design-system/SKILL.md +83 -0
  132. package/skills/django-celery/SKILL.md +458 -0
  133. package/skills/django-patterns/SKILL.md +735 -0
  134. package/skills/django-security/SKILL.md +644 -0
  135. package/skills/django-tdd/SKILL.md +730 -0
  136. package/skills/django-verification/SKILL.md +470 -0
  137. package/skills/dmux-workflows/SKILL.md +192 -0
  138. package/skills/docker-patterns/SKILL.md +365 -0
  139. package/skills/documentation-lookup/SKILL.md +91 -0
  140. package/skills/dotnet-patterns/SKILL.md +322 -0
  141. package/skills/dynamic-workflow-mode/SKILL.md +124 -0
  142. package/skills/e2e-testing/SKILL.md +327 -0
  143. package/skills/ecc-guide/SKILL.md +190 -0
  144. package/skills/ecc-recipes/SKILL.md +149 -0
  145. package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
  146. package/skills/email-ops/SKILL.md +122 -0
  147. package/skills/energy-procurement/SKILL.md +228 -0
  148. package/skills/enterprise-agent-ops/SKILL.md +51 -0
  149. package/skills/error-handling/SKILL.md +377 -0
  150. package/skills/eval-harness/SKILL.md +271 -0
  151. package/skills/evm-token-decimals/SKILL.md +131 -0
  152. package/skills/exa-search/SKILL.md +108 -0
  153. package/skills/fal-ai-media/SKILL.md +289 -0
  154. package/skills/fastapi-patterns/SKILL.md +514 -0
  155. package/skills/finance-billing-ops/SKILL.md +128 -0
  156. package/skills/flox-environments/SKILL.md +497 -0
  157. package/skills/flutter-dart-code-review/SKILL.md +436 -0
  158. package/skills/foundation-models-on-device/SKILL.md +243 -0
  159. package/skills/frontend-a11y/SKILL.md +446 -0
  160. package/skills/frontend-design-direction/SKILL.md +93 -0
  161. package/skills/frontend-patterns/SKILL.md +657 -0
  162. package/skills/frontend-slides/SKILL.md +185 -0
  163. package/skills/fsharp-testing/SKILL.md +281 -0
  164. package/skills/gan-style-harness/SKILL.md +279 -0
  165. package/skills/gateguard/SKILL.md +133 -0
  166. package/skills/generating-python-installer/SKILL.md +820 -0
  167. package/skills/git-workflow/SKILL.md +716 -0
  168. package/skills/github-ops/SKILL.md +145 -0
  169. package/skills/golang-patterns/SKILL.md +676 -0
  170. package/skills/golang-testing/SKILL.md +721 -0
  171. package/skills/google-workspace-ops/SKILL.md +96 -0
  172. package/skills/growth-log/SKILL.md +128 -0
  173. package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
  174. package/skills/healthcare-emr-patterns/SKILL.md +160 -0
  175. package/skills/healthcare-eval-harness/SKILL.md +208 -0
  176. package/skills/healthcare-phi-compliance/SKILL.md +146 -0
  177. package/skills/hermes-imports/SKILL.md +89 -0
  178. package/skills/hexagonal-architecture/SKILL.md +277 -0
  179. package/skills/hipaa-compliance/SKILL.md +79 -0
  180. package/skills/homelab-network-readiness/SKILL.md +170 -0
  181. package/skills/homelab-network-setup/SKILL.md +130 -0
  182. package/skills/homelab-pihole-dns/SKILL.md +275 -0
  183. package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
  184. package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
  185. package/skills/hookify-rules/SKILL.md +128 -0
  186. package/skills/inherit-legacy-style/SKILL.md +157 -0
  187. package/skills/intent-driven-development/SKILL.md +360 -0
  188. package/skills/inventory-demand-planning/SKILL.md +247 -0
  189. package/skills/investor-materials/SKILL.md +97 -0
  190. package/skills/investor-outreach/SKILL.md +92 -0
  191. package/skills/ios-icon-gen/SKILL.md +158 -0
  192. package/skills/iterative-retrieval/SKILL.md +212 -0
  193. package/skills/ito-basket-compare/SKILL.md +64 -0
  194. package/skills/ito-compute/SKILL.md +129 -0
  195. package/skills/ito-data-atlas-agent/SKILL.md +64 -0
  196. package/skills/ito-market-intelligence/SKILL.md +61 -0
  197. package/skills/ito-trade-planner/SKILL.md +68 -0
  198. package/skills/java-coding-standards/SKILL.md +384 -0
  199. package/skills/jira-integration/SKILL.md +303 -0
  200. package/skills/jpa-patterns/SKILL.md +152 -0
  201. package/skills/knowledge-ops/SKILL.md +155 -0
  202. package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
  203. package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
  204. package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
  205. package/skills/kotlin-patterns/SKILL.md +712 -0
  206. package/skills/kotlin-testing/SKILL.md +825 -0
  207. package/skills/kubernetes-patterns/SKILL.md +756 -0
  208. package/skills/laravel-patterns/SKILL.md +416 -0
  209. package/skills/laravel-plugin-discovery/SKILL.md +230 -0
  210. package/skills/laravel-security/SKILL.md +948 -0
  211. package/skills/laravel-tdd/SKILL.md +675 -0
  212. package/skills/laravel-verification/SKILL.md +180 -0
  213. package/skills/latency-critical-systems/SKILL.md +75 -0
  214. package/skills/lead-intelligence/SKILL.md +322 -0
  215. package/skills/liquid-glass-design/SKILL.md +279 -0
  216. package/skills/llm-trading-agent-security/SKILL.md +147 -0
  217. package/skills/logistics-exception-management/SKILL.md +222 -0
  218. package/skills/loop-design-check/SKILL.md +143 -0
  219. package/skills/mailtrap-email-integration/SKILL.md +77 -0
  220. package/skills/make-interfaces-feel-better/SKILL.md +152 -0
  221. package/skills/manim-video/SKILL.md +90 -0
  222. package/skills/market-research/SKILL.md +76 -0
  223. package/skills/marketing-campaign/SKILL.md +114 -0
  224. package/skills/mcp-server-patterns/SKILL.md +70 -0
  225. package/skills/messages-ops/SKILL.md +105 -0
  226. package/skills/ml-adoption-playbook/SKILL.md +57 -0
  227. package/skills/mle-workflow/SKILL.md +348 -0
  228. package/skills/motion-advanced/SKILL.md +596 -0
  229. package/skills/motion-foundations/SKILL.md +299 -0
  230. package/skills/motion-patterns/SKILL.md +434 -0
  231. package/skills/motion-ui/SKILL.md +576 -0
  232. package/skills/mysql-patterns/SKILL.md +413 -0
  233. package/skills/nanoclaw-repl/SKILL.md +34 -0
  234. package/skills/nestjs-patterns/SKILL.md +231 -0
  235. package/skills/netmiko-ssh-automation/SKILL.md +174 -0
  236. package/skills/network-bgp-diagnostics/SKILL.md +168 -0
  237. package/skills/network-config-validation/SKILL.md +211 -0
  238. package/skills/network-interface-health/SKILL.md +153 -0
  239. package/skills/nextjs-turbopack/SKILL.md +58 -0
  240. package/skills/nodejs-keccak256/SKILL.md +103 -0
  241. package/skills/nutrient-document-processing/SKILL.md +168 -0
  242. package/skills/nuxt4-patterns/SKILL.md +101 -0
  243. package/skills/openclaw-persona-forge/SKILL.md +289 -0
  244. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
  245. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
  246. package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
  247. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
  248. package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
  249. package/skills/openclaw-persona-forge/references/output-template.md +166 -0
  250. package/skills/opensource-pipeline/SKILL.md +256 -0
  251. package/skills/orch-add-feature/SKILL.md +45 -0
  252. package/skills/orch-build-mvp/SKILL.md +49 -0
  253. package/skills/orch-change-feature/SKILL.md +43 -0
  254. package/skills/orch-fix-defect/SKILL.md +43 -0
  255. package/skills/orch-pipeline/SKILL.md +121 -0
  256. package/skills/orch-refine-code/SKILL.md +44 -0
  257. package/skills/parallel-execution-optimizer/SKILL.md +74 -0
  258. package/skills/perl-patterns/SKILL.md +505 -0
  259. package/skills/perl-security/SKILL.md +504 -0
  260. package/skills/perl-testing/SKILL.md +476 -0
  261. package/skills/plan-canvas/SKILL.md +153 -0
  262. package/skills/plan-orchestrate/SKILL.md +263 -0
  263. package/skills/plankton-code-quality/SKILL.md +237 -0
  264. package/skills/postgres-patterns/SKILL.md +148 -0
  265. package/skills/prediction-market-oracle-research/SKILL.md +64 -0
  266. package/skills/prediction-market-risk-review/SKILL.md +61 -0
  267. package/skills/prisma-patterns/SKILL.md +401 -0
  268. package/skills/product-capability/SKILL.md +142 -0
  269. package/skills/product-lens/SKILL.md +93 -0
  270. package/skills/production-audit/SKILL.md +207 -0
  271. package/skills/production-scheduling/SKILL.md +238 -0
  272. package/skills/project-flow-ops/SKILL.md +112 -0
  273. package/skills/prompt-optimizer/SKILL.md +398 -0
  274. package/skills/python-patterns/SKILL.md +751 -0
  275. package/skills/python-testing/SKILL.md +817 -0
  276. package/skills/pytorch-patterns/SKILL.md +397 -0
  277. package/skills/quality-nonconformance/SKILL.md +260 -0
  278. package/skills/quarkus-patterns/SKILL.md +723 -0
  279. package/skills/quarkus-security/SKILL.md +468 -0
  280. package/skills/quarkus-tdd/SKILL.md +812 -0
  281. package/skills/quarkus-verification/SKILL.md +480 -0
  282. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
  283. package/skills/react-native-patterns/SKILL.md +326 -0
  284. package/skills/react-patterns/SKILL.md +342 -0
  285. package/skills/react-performance/SKILL.md +575 -0
  286. package/skills/react-testing/SKILL.md +424 -0
  287. package/skills/recsys-pipeline-architect/SKILL.md +115 -0
  288. package/skills/recursive-decision-ledger/SKILL.md +81 -0
  289. package/skills/redis-patterns/SKILL.md +404 -0
  290. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
  291. package/skills/remotion-video-creation/SKILL.md +43 -0
  292. package/skills/repo-scan/SKILL.md +79 -0
  293. package/skills/research-ops/SKILL.md +113 -0
  294. package/skills/returns-reverse-logistics/SKILL.md +240 -0
  295. package/skills/rules-distill/SKILL.md +265 -0
  296. package/skills/rust-patterns/SKILL.md +500 -0
  297. package/skills/rust-testing/SKILL.md +501 -0
  298. package/skills/safety-guard/SKILL.md +76 -0
  299. package/skills/santa-method/SKILL.md +307 -0
  300. package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
  301. package/skills/scientific-db-uspto-database/SKILL.md +178 -0
  302. package/skills/scientific-pkg-gget/SKILL.md +167 -0
  303. package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
  304. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
  305. package/skills/search-first/SKILL.md +183 -0
  306. package/skills/security-bounty-hunter/SKILL.md +100 -0
  307. package/skills/security-review/SKILL.md +504 -0
  308. package/skills/security-scan/SKILL.md +166 -0
  309. package/skills/seo/SKILL.md +155 -0
  310. package/skills/skill-comply/SKILL.md +59 -0
  311. package/skills/skill-scout/SKILL.md +141 -0
  312. package/skills/skill-stocktake/SKILL.md +195 -0
  313. package/skills/social-graph-ranker/SKILL.md +155 -0
  314. package/skills/social-publisher/SKILL.md +130 -0
  315. package/skills/springboot-patterns/SKILL.md +315 -0
  316. package/skills/springboot-security/SKILL.md +273 -0
  317. package/skills/springboot-tdd/SKILL.md +159 -0
  318. package/skills/springboot-verification/SKILL.md +232 -0
  319. package/skills/strategic-compact/SKILL.md +142 -0
  320. package/skills/swift-actor-persistence/SKILL.md +144 -0
  321. package/skills/swift-concurrency-6-2/SKILL.md +216 -0
  322. package/skills/swift-protocol-di-testing/SKILL.md +191 -0
  323. package/skills/swiftui-patterns/SKILL.md +259 -0
  324. package/skills/taste/SKILL.md +264 -0
  325. package/skills/taste/references/genre-taxonomy.md +87 -0
  326. package/skills/tdd-workflow/SKILL.md +583 -0
  327. package/skills/team-agent-orchestration/SKILL.md +111 -0
  328. package/skills/team-builder/SKILL.md +169 -0
  329. package/skills/terminal-ops/SKILL.md +110 -0
  330. package/skills/tinystruct-patterns/SKILL.md +279 -0
  331. package/skills/tinystruct-patterns/references/architecture.md +90 -0
  332. package/skills/tinystruct-patterns/references/data-handling.md +60 -0
  333. package/skills/tinystruct-patterns/references/database.md +99 -0
  334. package/skills/tinystruct-patterns/references/routing.md +64 -0
  335. package/skills/tinystruct-patterns/references/system-usage.md +97 -0
  336. package/skills/tinystruct-patterns/references/testing.md +72 -0
  337. package/skills/token-budget-advisor/SKILL.md +134 -0
  338. package/skills/ui-demo/SKILL.md +466 -0
  339. package/skills/ui-to-vue/SKILL.md +135 -0
  340. package/skills/uncloud/SKILL.md +344 -0
  341. package/skills/unified-memory/SKILL.md +170 -0
  342. package/skills/unified-notifications-ops/SKILL.md +188 -0
  343. package/skills/verification-loop/SKILL.md +129 -0
  344. package/skills/video-editing/SKILL.md +311 -0
  345. package/skills/videodb/SKILL.md +375 -0
  346. package/skills/visa-doc-translate/SKILL.md +117 -0
  347. package/skills/vite-patterns/SKILL.md +450 -0
  348. package/skills/vue-patterns/SKILL.md +471 -0
  349. package/skills/windows-desktop-e2e/SKILL.md +888 -0
  350. package/skills/workspace-surface-audit/SKILL.md +126 -0
  351. 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
 
@@ -85,7 +95,10 @@ async function chatViaProxy(messages, opts) {
85
95
  'Authorization': `Bearer ${token}`,
86
96
  'apikey': SUPABASE_ANON_KEY,
87
97
  },
88
- body: JSON.stringify({ messages, opts }),
98
+ // meta is optional, display-only context (what was asked, which file) —
99
+ // the proxy logs it to usage_history so CLI activity shows up in the
100
+ // admin dashboard next to the desktop app, same as this app's own calls.
101
+ body: JSON.stringify({ messages, opts, meta: opts.meta }),
89
102
  });
90
103
  } catch (e) {
91
104
  // Network-level failure — also worth another go.
@@ -100,7 +113,11 @@ async function chatViaProxy(messages, opts) {
100
113
  // A structured error from the function itself is final: it means the
101
114
  // function ran and deliberately rejected us (auth, daily cap, provider
102
115
  // error). Retrying that just burns the user's quota.
103
- 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
+ }
104
121
 
105
122
  return { retryable: isTransientStatus(res.status), error: describeStatus(res.status) };
106
123
  });
@@ -156,6 +173,189 @@ async function chatViaOllama(messages, opts, cfg) {
156
173
  });
157
174
  }
158
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
+
159
359
  /**
160
360
  * @param {Array<{role:string, content:string}>} messages
161
361
  * @param {{ json?: boolean, maxTokens?: number, temperature?: number }} opts
@@ -163,9 +363,15 @@ async function chatViaOllama(messages, opts, cfg) {
163
363
  */
164
364
  async function chat(messages, opts = {}) {
165
365
  const cfg = getConfig();
166
- return cfg.provider === 'ollama'
167
- ? chatViaOllama(messages, opts, cfg)
168
- : 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
+ }
169
375
  }
170
376
 
171
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 });
@@ -77,13 +100,37 @@ function maskKey(key) {
77
100
  return key.slice(0, 4) + '…' + key.slice(-4);
78
101
  }
79
102
 
80
- /** One-line description of where completions are coming from. */
103
+ /** One-line description of where completions are coming from, model included (for `codeply provider`). */
81
104
  function describeProvider(cfg = getConfig()) {
82
105
  if (cfg.provider === 'ollama') {
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
 
89
- module.exports = { getConfig, saveConfig, maskKey, describeProvider, configPath };
115
+ /** Same, but without naming the specific model — for passive UI chrome (the TUI status line). */
116
+ function describeProviderShort(cfg = getConfig()) {
117
+ if (cfg.provider === 'ollama') {
118
+ const where = /localhost|127\.0\.0\.1/.test(cfg.ollama.host) ? 'local' : 'cloud';
119
+ return `ollama ${where}`;
120
+ }
121
+ if (BYOK_PROVIDERS.includes(cfg.provider)) {
122
+ return `${cfg.provider} (own key)`;
123
+ }
124
+ return 'codeply proxy';
125
+ }
126
+
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
+ };
@@ -155,7 +155,7 @@ async function computeInstructionEdits(instruction, filePath) {
155
155
  ];
156
156
  if (feedback) messages.push({ role: 'user', content: feedback });
157
157
 
158
- const aiResult = await ai.chat(messages, { json: true });
158
+ const aiResult = await ai.chat(messages, { json: true, meta: { promptText: instruction, filePath } });
159
159
  if (!aiResult.success) throw new Error(aiResult.error);
160
160
  if (aiResult.modelUsed) lastModelUsed = aiResult.modelUsed;
161
161
  const data = aiResult.data;