acuvo-code 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 (154) hide show
  1. package/CHANGELOG.md +328 -0
  2. package/ENTERPRISE.md +927 -0
  3. package/LICENSE +120 -0
  4. package/README.md +1245 -0
  5. package/ROADMAP.md +556 -0
  6. package/bin/acuvo-mcp.mjs +208 -0
  7. package/bin/acuvo.mjs +3108 -0
  8. package/lib/acceptance-consent.mjs +168 -0
  9. package/lib/acceptance.mjs +859 -0
  10. package/lib/account.mjs +226 -0
  11. package/lib/acuvo-dir.mjs +72 -0
  12. package/lib/acuvo-models.mjs +141 -0
  13. package/lib/apply-patch.mjs +570 -0
  14. package/lib/ask-user.mjs +173 -0
  15. package/lib/audit.mjs +530 -0
  16. package/lib/auto-lease.mjs +174 -0
  17. package/lib/background.mjs +842 -0
  18. package/lib/best-of.mjs +334 -0
  19. package/lib/board.mjs +232 -0
  20. package/lib/breaker.mjs +93 -0
  21. package/lib/budget.mjs +1375 -0
  22. package/lib/builtin-skills.mjs +135 -0
  23. package/lib/cache-floor.mjs +204 -0
  24. package/lib/chain.mjs +303 -0
  25. package/lib/changed-paths.mjs +84 -0
  26. package/lib/chat.mjs +434 -0
  27. package/lib/checkpoint.mjs +637 -0
  28. package/lib/child-lifetime.mjs +71 -0
  29. package/lib/cli-args.mjs +1255 -0
  30. package/lib/code-review.mjs +1382 -0
  31. package/lib/colour.mjs +82 -0
  32. package/lib/command.mjs +2847 -0
  33. package/lib/compact.mjs +1151 -0
  34. package/lib/completion.mjs +515 -0
  35. package/lib/creative-engines.mjs +779 -0
  36. package/lib/db-inspect.mjs +1624 -0
  37. package/lib/delete.mjs +111 -0
  38. package/lib/design-loop.mjs +570 -0
  39. package/lib/diff-preview.mjs +1044 -0
  40. package/lib/doctor.mjs +2139 -0
  41. package/lib/dropped.mjs +216 -0
  42. package/lib/edit-diagnostics.mjs +277 -0
  43. package/lib/edit.mjs +460 -0
  44. package/lib/env-file.mjs +250 -0
  45. package/lib/escalate.mjs +702 -0
  46. package/lib/evaluate.mjs +284 -0
  47. package/lib/fetch-text.mjs +952 -0
  48. package/lib/fleet-budget.mjs +256 -0
  49. package/lib/gh.mjs +1536 -0
  50. package/lib/git.mjs +1341 -0
  51. package/lib/github.mjs +261 -0
  52. package/lib/h2.mjs +194 -0
  53. package/lib/handoff.mjs +417 -0
  54. package/lib/hooks.mjs +626 -0
  55. package/lib/http-probe.mjs +907 -0
  56. package/lib/image-director.mjs +322 -0
  57. package/lib/image-edit.mjs +522 -0
  58. package/lib/imagegen.mjs +998 -0
  59. package/lib/interrupt.mjs +234 -0
  60. package/lib/learned.mjs +353 -0
  61. package/lib/lease-watch.mjs +115 -0
  62. package/lib/lease.mjs +868 -0
  63. package/lib/localize.mjs +834 -0
  64. package/lib/log-tail.mjs +1052 -0
  65. package/lib/login.mjs +157 -0
  66. package/lib/lsp.mjs +1613 -0
  67. package/lib/mcp-consent.mjs +377 -0
  68. package/lib/mcp-defaults.mjs +780 -0
  69. package/lib/mcp-server.mjs +1343 -0
  70. package/lib/mcp.mjs +1263 -0
  71. package/lib/media.mjs +1283 -0
  72. package/lib/memory-workspace.mjs +179 -0
  73. package/lib/model-json.mjs +132 -0
  74. package/lib/model-tier.mjs +171 -0
  75. package/lib/model.mjs +1445 -0
  76. package/lib/parallel.mjs +144 -0
  77. package/lib/perchance.mjs +210 -0
  78. package/lib/plan-coherence.mjs +1461 -0
  79. package/lib/plan-ledger.mjs +981 -0
  80. package/lib/plan.mjs +461 -0
  81. package/lib/policy.mjs +783 -0
  82. package/lib/prefix-order.mjs +38 -0
  83. package/lib/project-memory.mjs +127 -0
  84. package/lib/prompt.mjs +109 -0
  85. package/lib/python.mjs +862 -0
  86. package/lib/rcfile.mjs +853 -0
  87. package/lib/read-window.mjs +743 -0
  88. package/lib/refute-tools.mjs +34 -0
  89. package/lib/refute.mjs +806 -0
  90. package/lib/repl-driver.mjs +264 -0
  91. package/lib/repl.mjs +324 -0
  92. package/lib/replay.mjs +1218 -0
  93. package/lib/repo-map.mjs +1101 -0
  94. package/lib/report.mjs +419 -0
  95. package/lib/search-rank.mjs +99 -0
  96. package/lib/search.mjs +659 -0
  97. package/lib/secret-paths.mjs +54 -0
  98. package/lib/session.mjs +1017 -0
  99. package/lib/skills.mjs +703 -0
  100. package/lib/slash.mjs +356 -0
  101. package/lib/spawn-argv.mjs +1151 -0
  102. package/lib/spend.mjs +250 -0
  103. package/lib/steer.mjs +280 -0
  104. package/lib/stream.mjs +253 -0
  105. package/lib/stuck.mjs +712 -0
  106. package/lib/subagent.mjs +749 -0
  107. package/lib/terminal-graphics.mjs +171 -0
  108. package/lib/tool-prefix.mjs +226 -0
  109. package/lib/tool-shortlist.mjs +162 -0
  110. package/lib/tools.mjs +2333 -0
  111. package/lib/tsserver.mjs +423 -0
  112. package/lib/turn.mjs +5672 -0
  113. package/lib/untrusted-block.mjs +271 -0
  114. package/lib/verify-claim.mjs +299 -0
  115. package/lib/vision.mjs +330 -0
  116. package/lib/voice-task.mjs +561 -0
  117. package/lib/warm-provider.mjs +255 -0
  118. package/lib/websearch.mjs +401 -0
  119. package/lib/workspace.mjs +928 -0
  120. package/lib/write-approval.mjs +235 -0
  121. package/lib/write-many.mjs +162 -0
  122. package/package.json +62 -0
  123. package/scripts/bundle.mjs +768 -0
  124. package/scripts/cache-floor.mjs +176 -0
  125. package/scripts/machine.mjs +226 -0
  126. package/scripts/test.mjs +139 -0
  127. package/skills/accessibility.md +87 -0
  128. package/skills/acuvo-design-system.md +123 -0
  129. package/skills/animation.md +84 -0
  130. package/skills/api-design.md +82 -0
  131. package/skills/auth-and-sessions.md +78 -0
  132. package/skills/build-with-a-framework.md +101 -0
  133. package/skills/colour-and-contrast.md +112 -0
  134. package/skills/creative-engines.md +81 -0
  135. package/skills/css-layout.md +85 -0
  136. package/skills/data-and-charts.md +77 -0
  137. package/skills/debugging.md +76 -0
  138. package/skills/designing-by-looking.md +84 -0
  139. package/skills/error-handling.md +78 -0
  140. package/skills/forms-and-validation.md +93 -0
  141. package/skills/nextjs-app-router.md +75 -0
  142. package/skills/page-composition.md +103 -0
  143. package/skills/performance.md +77 -0
  144. package/skills/plan-before-building.md +52 -0
  145. package/skills/planning-and-delegating.md +72 -0
  146. package/skills/refactoring.md +70 -0
  147. package/skills/security-basics.md +76 -0
  148. package/skills/state-management.md +73 -0
  149. package/skills/supabase-multitenant.md +72 -0
  150. package/skills/typescript-strict.md +90 -0
  151. package/skills/typography.md +135 -0
  152. package/skills/verify-your-own-work.md +62 -0
  153. package/skills/web-app-quality.md +62 -0
  154. package/skills/working-in-the-background.md +64 -0
package/lib/login.mjs ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * ── ⚠️⚠️⚠️ THE ONE MISSING LINK IN THE PAID PRODUCT ─────────────────────────
3
+ *
4
+ * Measured 2026-08-19. Every part of "you buy Acuvo credits and never see a
5
+ * provider key" exists EXCEPT the step that stores the credential:
6
+ *
7
+ * ✅ the user mints a key in the console `app/[tenantSlug]/api-keys`
8
+ * ✅ the gateway verifies it and meters usage `/api/cli/v1/chat/completions`
9
+ * ✅ the CLI reads it `account.mjs:resolveCredential`
10
+ * ❌ NOTHING EVER WRITES `~/.acuvo/credentials`
11
+ *
12
+ * `writeAccount` is exported, documented, and called by **nothing but its own
13
+ * tests**. So `resolveCredential` finds no account, falls through to
14
+ * `OPENROUTER_API_KEY`, and every user is on BYOK — which `account.mjs`'s own
15
+ * header calls out as *"never the plan"* and which makes *"the product's
16
+ * storefront an advertisement for somebody else."*
17
+ *
18
+ * ⭐ One command is the difference between a wrapper and a product. This is it.
19
+ *
20
+ * ── ⚠️ WHY IT VERIFIES BEFORE IT WRITES ─────────────────────────────────────
21
+ *
22
+ * A saved-but-invalid token is the worst outcome available: every later run
23
+ * fails at the model call, far from the mistake, with an error about chat
24
+ * completions rather than about login. Verifying costs one round trip at the
25
+ * only moment the user is thinking about credentials.
26
+ *
27
+ * ── ⚠️ AND WHY THE TOKEN CAN COME FROM STDIN ────────────────────────────────
28
+ *
29
+ * `acuvo --login xxi_live_…` puts a live credential in shell history, in
30
+ * `ps` output, and in any terminal recording. `gh auth login --with-token`
31
+ * reads stdin for exactly this reason. Both spellings are supported; the piped
32
+ * one is the one the docs should show.
33
+ *
34
+ * Pure and dependency-free apart from an injected `fetch`, so the whole thing
35
+ * is testable without a network or a home directory.
36
+ */
37
+
38
+ /** The shape the console mints — `generateApiKey()` in `lib/api-auth.ts`. */
39
+ const TOKEN_RE = /^xxi_live_[0-9a-f]{64}$/;
40
+
41
+ /**
42
+ * Is this the right SHAPE? Cheap, offline, and it catches the overwhelmingly
43
+ * common mistakes — a pasted prefix from the UI table, a truncated copy, a
44
+ * whole `Authorization: Bearer …` header, or somebody's OpenRouter key.
45
+ *
46
+ * ⚠️ Shape is not validity. It never claims the key works; that is the
47
+ * gateway's answer and this function must not be mistaken for it.
48
+ */
49
+ export function validateTokenShape(raw) {
50
+ const token = String(raw ?? '').trim();
51
+ if (!token) {
52
+ return { ok: false, reason: 'no token given. Paste the key you created at Settings → API keys, or pipe it: `acuvo --login < key.txt`' };
53
+ }
54
+ if (/^Bearer\s/i.test(token)) {
55
+ return { ok: false, reason: 'that is a whole Authorization header — pass just the key, starting `xxi_live_`' };
56
+ }
57
+ if (token.startsWith('sk-') || token.startsWith('sk_')) {
58
+ return { ok: false, reason: 'that looks like a provider key (OpenRouter/OpenAI). Acuvo Code uses an ACUVO key so your usage is billed to your Acuvo credits — create one at Settings → API keys.' };
59
+ }
60
+ if (!token.startsWith('xxi_live_')) {
61
+ return { ok: false, reason: 'an Acuvo key starts `xxi_live_`. Create one at Settings → API keys and copy the whole value — it is only shown once.' };
62
+ }
63
+ if (!TOKEN_RE.test(token)) {
64
+ return { ok: false, reason: 'that key looks truncated. The console shows an abbreviated PREFIX in the table; copy the full value from the dialog shown when you created it.' };
65
+ }
66
+ return { ok: true, token };
67
+ }
68
+
69
+ /**
70
+ * Ask the gateway whether the key actually works, with the smallest possible
71
+ * request.
72
+ *
73
+ * ⚠️ `max_tokens: 1` because this is a real completion and therefore a real
74
+ * (tiny) charge against the user's credits. Verifying must not be something a
75
+ * user pays meaningfully for, and a request that bills a cent to check a
76
+ * password would be a bad trade.
77
+ *
78
+ * ⚠️ THE FAILURE MODES ARE SEPARATED ON PURPOSE. "Your key is wrong",
79
+ * "your account has no credit" and "we could not reach the gateway" need three
80
+ * different actions from the user, and collapsing them into "login failed" is
81
+ * how someone on a plane deletes a perfectly good credential.
82
+ */
83
+ export async function verifyToken(token, gatewayUrl, { fetchImpl = fetch, timeoutMs = 15000 } = {}) {
84
+ const controller = new AbortController();
85
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
86
+ let res;
87
+ try {
88
+ res = await fetchImpl(gatewayUrl, {
89
+ method: 'POST',
90
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
91
+ body: JSON.stringify({ model: 'probe', messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 }),
92
+ signal: controller.signal,
93
+ });
94
+ } catch (err) {
95
+ const aborted = err && (err.name === 'AbortError' || /abort/i.test(String(err.message ?? '')));
96
+ return {
97
+ ok: false,
98
+ kind: 'unreachable',
99
+ reason: aborted
100
+ ? `the gateway did not answer within ${Math.round(timeoutMs / 1000)}s. Your key was NOT saved; try again when you have a connection.`
101
+ : `could not reach the gateway (${String(err?.message ?? err)}). Your key was NOT saved.`,
102
+ };
103
+ } finally {
104
+ clearTimeout(timer);
105
+ }
106
+
107
+ if (res.status === 401 || res.status === 403) {
108
+ return { ok: false, kind: 'rejected', reason: 'the gateway rejected that key. Check it was copied whole, and that it has the `cli.run` scope.' };
109
+ }
110
+ if (res.status === 402) {
111
+ return { ok: false, kind: 'no-credit', reason: 'that key is valid but the account has no credits. Top up, then log in again.' };
112
+ }
113
+ /**
114
+ * ⭐ ANY OTHER STATUS COUNTS AS AUTHENTICATED, and that is deliberate. A 400
115
+ * about the `probe` model, a 429, a 500 — all of them mean the gateway got
116
+ * past auth to have an opinion about the request. Demanding a 200 would make
117
+ * login fail whenever the upstream provider was briefly unhappy, which is
118
+ * precisely when a user should still be able to configure their machine.
119
+ */
120
+ return { ok: true, kind: 'ok', status: res.status };
121
+ }
122
+
123
+ /** Never print a live credential. The prefix is enough to identify it. */
124
+ export function maskToken(token) {
125
+ const t = String(token ?? '');
126
+ return t.length <= 17 ? t : `${t.slice(0, 17)}…`;
127
+ }
128
+
129
+ /**
130
+ * The human line for `--whoami`, from `resolveCredential`'s result.
131
+ *
132
+ * ⚠️ `byok` IS REPORTED AS A WARNING, not as a success. It works, but it bills
133
+ * the user's own provider account instead of their Acuvo credits, which means
134
+ * they are paying twice and we are metering nothing — so the state has to be
135
+ * visible rather than silently fine.
136
+ */
137
+ export function describeAuth(cred) {
138
+ if (!cred || cred.mode === 'unconfigured') {
139
+ return {
140
+ ok: false,
141
+ line: 'Not logged in. Create a key at Settings → API keys (scope `cli.run`), then: acuvo --login < key.txt',
142
+ };
143
+ }
144
+ if (cred.mode === 'byok') {
145
+ return {
146
+ ok: true,
147
+ warn: true,
148
+ line: 'Using OPENROUTER_API_KEY from your environment (BYOK).\n'
149
+ + ' ⚠️ This bills YOUR provider account, not your Acuvo credits, and nothing is metered.\n'
150
+ + ' Run `acuvo --login` with an Acuvo key to use your credits instead.',
151
+ };
152
+ }
153
+ return {
154
+ ok: true,
155
+ line: `Logged in as ${cred.email ?? 'an Acuvo account'} (${maskToken(cred.token)})\n gateway: ${cred.url}`,
156
+ };
157
+ }