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
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ACUVO MCP SERVER — the entry point another agent spawns.
4
+ *
5
+ * Add to any MCP host (Claude Code, Cursor, Cline, Zed):
6
+ *
7
+ * {
8
+ * "mcpServers": {
9
+ * "acuvo": {
10
+ * "command": "npx",
11
+ * "args": ["-y", "acuvo-code", "acuvo-mcp"],
12
+ * "env": { "RENDER_AUDIT_URL": "...", "MODAL_PRESS_URL": "...", "MODAL_VIDEO_SECRET": "..." }
13
+ * }
14
+ * }
15
+ * }
16
+ *
17
+ * ── ⚠️ THIS FILE'S ONLY JOB IS TO NOT BREAK STDIO ───────────────────────────
18
+ * Under MCP, stdout is the wire. A banner, a warning, a stray `console.log` from
19
+ * anything we import — any of it corrupts the JSON-RPC stream and the host
20
+ * reports an unintelligible parse error instead of the real problem. So:
21
+ * everything human goes to stderr, and there is no exception, including the
22
+ * "helpful" line telling the user nothing is configured.
23
+ *
24
+ * ⚠️ AND IT MUST NOT EXIT WHEN IT HAS NOTHING TO OFFER. A server that dies
25
+ * because no endpoint is configured shows up in the host as "failed to connect",
26
+ * which sends the user looking for an install problem. It stays up, answers
27
+ * `initialize`, and returns an EMPTY tool list — which is the truthful answer to
28
+ * "what can you do" and is diagnosable in one glance at the host's UI.
29
+ */
30
+
31
+ import { existsSync } from 'node:fs';
32
+ import { join, dirname, resolve } from 'node:path';
33
+ import { fileURLToPath } from 'node:url';
34
+
35
+ import { createMcpServer, serve, SERVER_VERSION } from '../lib/mcp-server.mjs';
36
+
37
+ /**
38
+ * ── ⚠️ WHY THE ROOT IS AN ARGUMENT AND NOT `process.cwd()` ──────────────────
39
+ *
40
+ * A host spawns its MCP servers with whatever working directory it happens to
41
+ * have — often the user's home, sometimes `/`. Taking cwd as the workspace
42
+ * would make the containment check in `workspace.mjs` pass for every file on
43
+ * the machine: a boundary that exists in the code and nowhere in reality. So
44
+ * the directory is typed by the person editing the host config, exactly the way
45
+ * they type the server list in `mcp.json`.
46
+ *
47
+ * Both spellings, because both appear in real host configs: `--root /path` and
48
+ * `--root=/path`. `ACUVO_MCP_ROOT` works too and loses to the flag, since an
49
+ * `args` array is more visible in a config file than an `env` block.
50
+ */
51
+ function flagValue(argv, flag) {
52
+ const exact = argv.indexOf(flag);
53
+ if (exact !== -1 && argv[exact + 1] !== undefined && !argv[exact + 1].startsWith('-')) return argv[exact + 1];
54
+ const joined = argv.find((a) => a.startsWith(`${flag}=`));
55
+ if (joined) return joined.slice(flag.length + 1);
56
+ return null;
57
+ }
58
+
59
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
60
+
61
+ /**
62
+ * ── LOAD `.env`, SAME REASONING AS bin/acuvo.mjs ────────────────────────────
63
+ * Measured there: without this, every media capability was dark on a machine
64
+ * where all of them work, because nobody exports four variables by hand.
65
+ *
66
+ * ⚠️ THE PACKAGE DIRECTORY IS SEARCHED TOO, AND ONLY HERE DOES THAT MATTER. An
67
+ * MCP server is spawned by a host with whatever cwd the host felt like using —
68
+ * often the user's project, sometimes `/`. The `.env` sitting next to the code
69
+ * is the only location that is reliably ours.
70
+ *
71
+ * ⚠️ A REAL ENVIRONMENT VARIABLE STILL WINS: `loadEnvFile` does not overwrite,
72
+ * so the `env` block in the host's config beats any file. That is the right
73
+ * precedence — the host's config is the thing the user can actually see.
74
+ */
75
+ function loadEnv() {
76
+ if (typeof process.loadEnvFile !== 'function') return;
77
+ const candidates = [
78
+ process.env.ACUVO_ENV_FILE,
79
+ join(process.cwd(), '.env'),
80
+ join(packageRoot, '.env'),
81
+ ].filter(Boolean);
82
+ for (const file of candidates) {
83
+ if (!existsSync(file)) continue;
84
+ try { process.loadEnvFile(file); } catch { /* malformed is not fatal — we may not need it */ }
85
+ }
86
+ }
87
+
88
+ function log(line) {
89
+ // stderr, always. See the header.
90
+ try { process.stderr.write(`acuvo-mcp: ${line}\n`); } catch { /* nothing we can do */ }
91
+ }
92
+
93
+ async function main() {
94
+ if (process.argv.includes('--version')) {
95
+ process.stdout.write(`${SERVER_VERSION}\n`);
96
+ return 0;
97
+ }
98
+ if (process.argv.includes('--help')) {
99
+ process.stdout.write([
100
+ 'acuvo-mcp — expose Acuvo\'s browser-backed capabilities over MCP (stdio).',
101
+ '',
102
+ 'It speaks JSON-RPC on stdin/stdout and is meant to be spawned by an MCP host,',
103
+ 'not run by hand. Four gated groups, and nothing outside them:',
104
+ '',
105
+ ' browser (needs a service URL)',
106
+ ' see_page render HTML in a real browser; get the measured',
107
+ ' layout/contrast defects back',
108
+ ' make_document turn HTML into a real PDF, PNG or PPTX',
109
+ ' Both take the HTML itself, never a file path.',
110
+ '',
111
+ ' workspace reads (needs --root)',
112
+ ' read_file read_lines read_around list_dir find_files search_text',
113
+ '',
114
+ ' document reads (needs --root and a reader service)',
115
+ ' read_document read_table PDF / DOCX / XLSX / scans -> text',
116
+ '',
117
+ ' workspace writes (needs --root AND --allow-write)',
118
+ ' write_file write_files edit_file delete_file',
119
+ '',
120
+ 'NOTHING STARTS A PROCESS. run_command, run_program, evaluate, repl,',
121
+ 'start_process, the acceptance verbs, git and the four LSP verbs are refused',
122
+ 'unconditionally, and there is no flag to turn them on: the calling agent',
123
+ 'already has a shell, and write + run composes into arbitrary code execution.',
124
+ 'generate_image is refused too — it is the one tool that reaches an',
125
+ 'XXIautomate endpoint with no credential set at all, so every call would be',
126
+ 'unmetered GPU on our bill.',
127
+ '',
128
+ 'Options:',
129
+ ' --root <dir> the ONE directory the workspace tools may touch.',
130
+ ' Never inferred from the working directory: a host',
131
+ ' spawns servers wherever it likes, and a root of / or',
132
+ ' ~ makes the containment check meaningless. Without',
133
+ ' it, only the browser tools are served.',
134
+ ' --allow-write also serve write_file, write_files, edit_file and',
135
+ ' delete_file. Off by default; a read-only Acuvo is a',
136
+ ' lens, a writing one changes somebody\'s repository.',
137
+ '',
138
+ 'Environment:',
139
+ ' ACUVO_MCP_ROOT same as --root (the flag wins)',
140
+ ' ACUVO_MCP_WRITE=1 same as --allow-write (only 1/true/yes/on count)',
141
+ ' RENDER_AUDIT_URL render service (without it, see_page is not offered)',
142
+ ' MODAL_PRESS_URL document service (without it, make_document is not offered)',
143
+ ' MODAL_DOC_READ_URL document reader (without it, read_document is not offered)',
144
+ ' MODAL_TABLE_READ_URL table reader (without it, read_table is not offered)',
145
+ ' MODAL_VIDEO_SECRET shared secret for those services, if they require one',
146
+ ' ACUVO_MCP_OUT where rendered files are written (default: <tmp>/acuvo-mcp)',
147
+ ' ACUVO_MCP_MAX_CALLS lifetime render cap (default 200) — renders cost money',
148
+ '',
149
+ 'Example host config:',
150
+ ' "acuvo": {',
151
+ ' "command": "npx",',
152
+ ' "args": ["-y", "acuvo-code", "acuvo-mcp", "--root", "/path/to/project"],',
153
+ ' "env": { "RENDER_AUDIT_URL": "..." }',
154
+ ' }',
155
+ '',
156
+ ].join('\n'));
157
+ return 0;
158
+ }
159
+
160
+ loadEnv();
161
+
162
+ // ⚠️ The flag beats the env var: an `args` array is visible in a host config,
163
+ // an `env` block is the thing people forget they set months ago.
164
+ const rootArg = flagValue(process.argv, '--root') ?? process.env.ACUVO_MCP_ROOT ?? null;
165
+ const allowWrite = process.argv.includes('--allow-write') ? true : undefined;
166
+
167
+ const server = createMcpServer({ env: process.env, workspaceRoot: rootArg, allowWrite });
168
+
169
+ /**
170
+ * ⭐ SAY WHAT IS LIVE, ON STDERR, BEFORE THE FIRST MESSAGE. Hosts surface a
171
+ * server's stderr in their logs, and "0 tools" with no explanation is the
172
+ * single most common MCP support question there is. This makes the answer
173
+ * one line long.
174
+ */
175
+ const names = server.listTools().map((t) => t.name);
176
+ log(`v${SERVER_VERSION} · ${names.length} tool${names.length === 1 ? '' : 's'}${names.length ? `: ${names.join(', ')}` : ''}`);
177
+ if (names.length === 0) {
178
+ log('NO TOOLS. Set RENDER_AUDIT_URL and/or MODAL_PRESS_URL for the browser tools, and/or pass --root <dir> for the workspace tools.');
179
+ }
180
+ /**
181
+ * ⚠️ A REFUSED ROOT IS SHOUTED, NOT SWALLOWED. The operator typed a directory
182
+ * and got a server without workspace tools; if the reason is only visible by
183
+ * reading this source, they will conclude the feature does not work.
184
+ */
185
+ if (server.workspaceError) log(`WORKSPACE ROOT REFUSED — ${server.workspaceError}`);
186
+ else if (server.workspaceRoot) log(`workspace: ${server.workspaceRoot} (${server.writeEnabled ? 'read + WRITE' : 'read-only'})`);
187
+ else log('workspace: none — pass --root <dir> to serve the file tools');
188
+ log(`output directory: ${server.root}`);
189
+
190
+ /**
191
+ * ⚠️ A CRASH MUST NOT BE SILENT. Without these the process vanishes and the
192
+ * host says "server exited"; with them the user gets the actual stack in the
193
+ * place they are already looking.
194
+ */
195
+ process.on('uncaughtException', (err) => { log(`uncaught: ${err?.stack ?? err}`); });
196
+ process.on('unhandledRejection', (err) => { log(`unhandled rejection: ${err?.stack ?? err}`); });
197
+
198
+ await serve(server, { input: process.stdin, output: process.stdout, onLog: log });
199
+ return 0;
200
+ }
201
+
202
+ main().then(
203
+ (code) => process.exit(code),
204
+ (err) => {
205
+ log(`crashed before serving: ${err?.stack ?? err}`);
206
+ process.exit(1);
207
+ },
208
+ );