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.
- package/CHANGELOG.md +328 -0
- package/ENTERPRISE.md +927 -0
- package/LICENSE +120 -0
- package/README.md +1245 -0
- package/ROADMAP.md +556 -0
- package/bin/acuvo-mcp.mjs +208 -0
- package/bin/acuvo.mjs +3108 -0
- package/lib/acceptance-consent.mjs +168 -0
- package/lib/acceptance.mjs +859 -0
- package/lib/account.mjs +226 -0
- package/lib/acuvo-dir.mjs +72 -0
- package/lib/acuvo-models.mjs +141 -0
- package/lib/apply-patch.mjs +570 -0
- package/lib/ask-user.mjs +173 -0
- package/lib/audit.mjs +530 -0
- package/lib/auto-lease.mjs +174 -0
- package/lib/background.mjs +842 -0
- package/lib/best-of.mjs +334 -0
- package/lib/board.mjs +232 -0
- package/lib/breaker.mjs +93 -0
- package/lib/budget.mjs +1375 -0
- package/lib/builtin-skills.mjs +135 -0
- package/lib/cache-floor.mjs +204 -0
- package/lib/chain.mjs +303 -0
- package/lib/changed-paths.mjs +84 -0
- package/lib/chat.mjs +434 -0
- package/lib/checkpoint.mjs +637 -0
- package/lib/child-lifetime.mjs +71 -0
- package/lib/cli-args.mjs +1255 -0
- package/lib/code-review.mjs +1382 -0
- package/lib/colour.mjs +82 -0
- package/lib/command.mjs +2847 -0
- package/lib/compact.mjs +1151 -0
- package/lib/completion.mjs +515 -0
- package/lib/creative-engines.mjs +779 -0
- package/lib/db-inspect.mjs +1624 -0
- package/lib/delete.mjs +111 -0
- package/lib/design-loop.mjs +570 -0
- package/lib/diff-preview.mjs +1044 -0
- package/lib/doctor.mjs +2139 -0
- package/lib/dropped.mjs +216 -0
- package/lib/edit-diagnostics.mjs +277 -0
- package/lib/edit.mjs +460 -0
- package/lib/env-file.mjs +250 -0
- package/lib/escalate.mjs +702 -0
- package/lib/evaluate.mjs +284 -0
- package/lib/fetch-text.mjs +952 -0
- package/lib/fleet-budget.mjs +256 -0
- package/lib/gh.mjs +1536 -0
- package/lib/git.mjs +1341 -0
- package/lib/github.mjs +261 -0
- package/lib/h2.mjs +194 -0
- package/lib/handoff.mjs +417 -0
- package/lib/hooks.mjs +626 -0
- package/lib/http-probe.mjs +907 -0
- package/lib/image-director.mjs +322 -0
- package/lib/image-edit.mjs +522 -0
- package/lib/imagegen.mjs +998 -0
- package/lib/interrupt.mjs +234 -0
- package/lib/learned.mjs +353 -0
- package/lib/lease-watch.mjs +115 -0
- package/lib/lease.mjs +868 -0
- package/lib/localize.mjs +834 -0
- package/lib/log-tail.mjs +1052 -0
- package/lib/login.mjs +157 -0
- package/lib/lsp.mjs +1613 -0
- package/lib/mcp-consent.mjs +377 -0
- package/lib/mcp-defaults.mjs +780 -0
- package/lib/mcp-server.mjs +1343 -0
- package/lib/mcp.mjs +1263 -0
- package/lib/media.mjs +1283 -0
- package/lib/memory-workspace.mjs +179 -0
- package/lib/model-json.mjs +132 -0
- package/lib/model-tier.mjs +171 -0
- package/lib/model.mjs +1445 -0
- package/lib/parallel.mjs +144 -0
- package/lib/perchance.mjs +210 -0
- package/lib/plan-coherence.mjs +1461 -0
- package/lib/plan-ledger.mjs +981 -0
- package/lib/plan.mjs +461 -0
- package/lib/policy.mjs +783 -0
- package/lib/prefix-order.mjs +38 -0
- package/lib/project-memory.mjs +127 -0
- package/lib/prompt.mjs +109 -0
- package/lib/python.mjs +862 -0
- package/lib/rcfile.mjs +853 -0
- package/lib/read-window.mjs +743 -0
- package/lib/refute-tools.mjs +34 -0
- package/lib/refute.mjs +806 -0
- package/lib/repl-driver.mjs +264 -0
- package/lib/repl.mjs +324 -0
- package/lib/replay.mjs +1218 -0
- package/lib/repo-map.mjs +1101 -0
- package/lib/report.mjs +419 -0
- package/lib/search-rank.mjs +99 -0
- package/lib/search.mjs +659 -0
- package/lib/secret-paths.mjs +54 -0
- package/lib/session.mjs +1017 -0
- package/lib/skills.mjs +703 -0
- package/lib/slash.mjs +356 -0
- package/lib/spawn-argv.mjs +1151 -0
- package/lib/spend.mjs +250 -0
- package/lib/steer.mjs +280 -0
- package/lib/stream.mjs +253 -0
- package/lib/stuck.mjs +712 -0
- package/lib/subagent.mjs +749 -0
- package/lib/terminal-graphics.mjs +171 -0
- package/lib/tool-prefix.mjs +226 -0
- package/lib/tool-shortlist.mjs +162 -0
- package/lib/tools.mjs +2333 -0
- package/lib/tsserver.mjs +423 -0
- package/lib/turn.mjs +5672 -0
- package/lib/untrusted-block.mjs +271 -0
- package/lib/verify-claim.mjs +299 -0
- package/lib/vision.mjs +330 -0
- package/lib/voice-task.mjs +561 -0
- package/lib/warm-provider.mjs +255 -0
- package/lib/websearch.mjs +401 -0
- package/lib/workspace.mjs +928 -0
- package/lib/write-approval.mjs +235 -0
- package/lib/write-many.mjs +162 -0
- package/package.json +62 -0
- package/scripts/bundle.mjs +768 -0
- package/scripts/cache-floor.mjs +176 -0
- package/scripts/machine.mjs +226 -0
- package/scripts/test.mjs +139 -0
- package/skills/accessibility.md +87 -0
- package/skills/acuvo-design-system.md +123 -0
- package/skills/animation.md +84 -0
- package/skills/api-design.md +82 -0
- package/skills/auth-and-sessions.md +78 -0
- package/skills/build-with-a-framework.md +101 -0
- package/skills/colour-and-contrast.md +112 -0
- package/skills/creative-engines.md +81 -0
- package/skills/css-layout.md +85 -0
- package/skills/data-and-charts.md +77 -0
- package/skills/debugging.md +76 -0
- package/skills/designing-by-looking.md +84 -0
- package/skills/error-handling.md +78 -0
- package/skills/forms-and-validation.md +93 -0
- package/skills/nextjs-app-router.md +75 -0
- package/skills/page-composition.md +103 -0
- package/skills/performance.md +77 -0
- package/skills/plan-before-building.md +52 -0
- package/skills/planning-and-delegating.md +72 -0
- package/skills/refactoring.md +70 -0
- package/skills/security-basics.md +76 -0
- package/skills/state-management.md +73 -0
- package/skills/supabase-multitenant.md +72 -0
- package/skills/typescript-strict.md +90 -0
- package/skills/typography.md +135 -0
- package/skills/verify-your-own-work.md +62 -0
- package/skills/web-app-quality.md +62 -0
- 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
|
+
}
|