foundry-design 0.2.0-beta.1 → 0.2.0-beta.10
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/README.md +63 -13
- package/dist/index.js +220 -64
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts +23 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +496 -89
- package/dist/installer.js.map +1 -1
- package/dist/proxy.d.ts +7 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +106 -0
- package/dist/proxy.js.map +1 -0
- package/dist/release.d.ts +5 -0
- package/dist/release.d.ts.map +1 -0
- package/dist/release.js +12 -0
- package/dist/release.js.map +1 -0
- package/dist/skill/foundry-design-control/SKILL.md +75 -0
- package/dist/skill/foundry-design-control/agents/openai.yaml +4 -0
- package/dist/skill/foundry-design-control/references/apply-run-contract.md +32 -0
- package/dist/skill/foundry-design-control/references/change-contract.md +33 -0
- package/dist/skill/foundry-design-control/references/design-health.md +32 -0
- package/dist/skill/foundry-design-control/references/platform-setup.md +27 -0
- package/dist/skill/foundry-design-control/references/semantic-mapping-contract.md +22 -0
- package/dist/skill/foundry-design-control/references/state-workbench.md +13 -0
- package/dist/skill/foundry-design-control/references/visual-workbench.md +60 -0
- package/dist/skill/foundry-design-control/scripts/foundry.sh +12 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -4,35 +4,82 @@ Foundry is a local-first precision design workbench for Codex, Cursor, and Claud
|
|
|
4
4
|
|
|
5
5
|
## Beta installation
|
|
6
6
|
|
|
7
|
-
Foundry
|
|
7
|
+
Foundry is distributed through npm, so testers do not need GitHub access.
|
|
8
|
+
|
|
9
|
+
> **Current public beta:** `0.2.0-beta.10`. Both npm `latest` and `beta` point to the same tested release. Use the unqualified command below for normal installation.
|
|
10
|
+
|
|
11
|
+
Full documentation is available at [withfoundry.ai](https://withfoundry.ai).
|
|
12
|
+
|
|
13
|
+
### Recommended: one command
|
|
14
|
+
|
|
15
|
+
Open a terminal in the project, or ask Codex, Cursor, or Claude Code to run:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx foundry-design
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That command detects the project and active coding agent, installs or safely updates Foundry, repairs the shared agent connection, validates its integration, starts the project when needed, and opens the visual session. Before changing local configuration, Foundry prints the exact CLI and MCP bridge versions it will use. On the first installation only, restart the coding agent once so it can load the shared MCP connection. A reviewed batch can be queued before the restart and will be claimed when the agent reconnects.
|
|
22
|
+
|
|
23
|
+
After the restart, reopen the same project folder and ask:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
Start Foundry for this project and keep listening for Apply with agent requests.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
That is the normal workflow. Foundry resumes the most recent session for the current source revision and keeps all review and apply state local.
|
|
30
|
+
|
|
31
|
+
### Manual installation
|
|
32
|
+
|
|
33
|
+
From the first project you want to inspect, run:
|
|
8
34
|
|
|
9
35
|
```bash
|
|
10
|
-
npx foundry-design
|
|
36
|
+
npx foundry-design setup --global
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For later projects, keep the shared agent connection and install only the development adapter:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx foundry-design setup --agent none
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then restart the coding agent, reopen the same project folder, and ask:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Start Foundry for this project and keep listening for Apply with agent requests.
|
|
11
49
|
```
|
|
12
50
|
|
|
13
|
-
|
|
51
|
+
You do not need to run a second terminal command when the agent starts Foundry for you. Apply requests remain queued safely when the agent is offline. Run `npx foundry-design doctor --repair` if the connection or generated integration needs repair.
|
|
14
52
|
|
|
15
|
-
|
|
53
|
+
Update an existing installation with:
|
|
16
54
|
|
|
17
55
|
```bash
|
|
18
|
-
npx foundry-design
|
|
56
|
+
npx foundry-design update
|
|
19
57
|
```
|
|
20
58
|
|
|
21
|
-
Foundry
|
|
59
|
+
The updater refreshes checksum-matched Foundry files, agent connections, and skill bundles. It preserves and reports files changed since setup, validates the project, and rolls back the update if Foundry introduces a TypeScript or lint failure. Restart the coding agent after updating.
|
|
60
|
+
|
|
61
|
+
Remove only Foundry-managed integration with:
|
|
22
62
|
|
|
23
63
|
```bash
|
|
24
|
-
npx foundry-design
|
|
64
|
+
npx foundry-design uninstall
|
|
25
65
|
```
|
|
26
66
|
|
|
27
67
|
Supported automatic web integration currently includes Next.js App Router, Vite, and plain HTML. Generic web, SwiftUI, and React Native projects receive explicit setup guidance when a safe automatic edit is not available.
|
|
28
68
|
|
|
29
|
-
|
|
69
|
+
The npm setup above is the public beta installation path and includes the same portable skill and MCP connection used by the agent plugin bundle.
|
|
70
|
+
|
|
71
|
+
If an npm mirror or existing `npx` cache reports an old tag, bypass it with a temporary cache and the exact current release:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
FOUNDRY_NPX_CACHE="$(mktemp -d)"
|
|
75
|
+
npx --yes --prefer-online --registry=https://registry.npmjs.org --cache "$FOUNDRY_NPX_CACHE" --package=foundry-design@0.2.0-beta.10 foundry-design
|
|
76
|
+
```
|
|
30
77
|
|
|
31
|
-
The beta supports Node.js 20 or newer. Read
|
|
78
|
+
The beta supports Node.js 20 or newer. Read the [local-first safety model](https://withfoundry.ai/#safety) before using it with sensitive work.
|
|
32
79
|
|
|
33
80
|
## Agent plugin
|
|
34
81
|
|
|
35
|
-
The portable plugin lives at `plugins/foundry-design-control`. The repository also includes marketplace manifests for Codex, Cursor, and Claude-compatible plugin import. Once installed, ask your agent:
|
|
82
|
+
The portable plugin lives at `plugins/foundry-design-control`. The repository also includes marketplace manifests for Codex, Cursor, and Claude-compatible plugin import. Until public marketplace review is complete, `setup --global` installs the same reusable skill and MCP connection from npm without requiring source-repository access. Once installed, ask your agent:
|
|
36
83
|
|
|
37
84
|
```text
|
|
38
85
|
Start Foundry for this project.
|
|
@@ -42,7 +89,8 @@ Foundry remains local-first. Installing the plugin does not create an account, e
|
|
|
42
89
|
|
|
43
90
|
## Current capabilities
|
|
44
91
|
|
|
45
|
-
-
|
|
92
|
+
- A dedicated local design workspace with a live product canvas, equal Layers and Inspector docks, and a direct-overlay fallback when a product blocks framing
|
|
93
|
+
- Measured browser selection with overlap cycling, parent/child traversal, hover previews, and isolated Shadow DOM canvas instrumentation
|
|
46
94
|
- A searchable, collapsible, virtualized live layer hierarchy with mapped-source context, Shadow DOM traversal, drag reordering, and selection persistence across HMR
|
|
47
95
|
- Multi-selection, measured spacing annotations, sibling-aware resize snapping, keyboard nudging, alignment, distribution, and preview undo/redo
|
|
48
96
|
- A revisioned local design graph for CSS tokens, components, Storybook variants, breakpoints, themes, states, and motion presets
|
|
@@ -54,9 +102,11 @@ Foundry remains local-first. Installing the plugin does not create an account, e
|
|
|
54
102
|
- Design Health scans for contrast, overflow and clipping, touch targets, accessible names, reduced-motion coverage, and project spacing consistency, with evidence, intentional-issue ignores, and safe corrections routed into review
|
|
55
103
|
- An in-app state workbench with real viewport frames, theme switching, forced interaction states, motion controls, and contextual verification
|
|
56
104
|
- Persistent, coalescing change ledger with JSON and consolidated prompt export
|
|
57
|
-
-
|
|
105
|
+
- Center-workspace review with editable approved batches, grouped targets, and unresolved-target blocking
|
|
58
106
|
- Persistent Apply with agent runs across Codex, Cursor, and Claude Code through MCP
|
|
107
|
+
- Leased agent handoffs that safely return abandoned or interrupted claims to the queue
|
|
59
108
|
- Live source, rebuild, validation, retry, and rendered-verification progress
|
|
109
|
+
- Rendered verification that resumes after refresh and does not depend on Review remaining open
|
|
60
110
|
- Local MCP bridge for agent access
|
|
61
111
|
- Debug adapters for web, SwiftUI, and React Native on iOS Simulator
|
|
62
112
|
- Verification records that compare requested and rendered values
|
|
@@ -90,7 +140,7 @@ The runtime binds to `127.0.0.1:4387`. Inspector preview changes remain temporar
|
|
|
90
140
|
- `packages/react-native-adapter`: React Native debug bridge
|
|
91
141
|
- `packages/mcp-server`: agent tools backed by the runtime
|
|
92
142
|
- `packages/swiftui-adapter`: Swift package for inspectable SwiftUI views
|
|
93
|
-
- `apps/inspector`:
|
|
143
|
+
- `apps/inspector`: authenticated local design workspace, canvas shell, and review surface
|
|
94
144
|
- `examples/web-fixture`: end-to-end browser fixture
|
|
95
145
|
|
|
96
146
|
## Safety model
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
-
import { basename,
|
|
4
|
+
import { access, appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { basename, join, resolve } from 'node:path';
|
|
6
6
|
import { createInterface } from 'node:readline/promises';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
8
9
|
import { renderChangePrompt } from 'foundry-design-protocol';
|
|
9
10
|
import { FoundryRuntime, SessionStore } from 'foundry-design-runtime';
|
|
10
|
-
import { createSetupPlan, setupProject, uninstallProject, } from './installer.js';
|
|
11
|
+
import { createSetupPlan, createUpdatePlan, installHostAgentIntegration, installAgentIntegration, setupProject, updateProject, uninstallProject, } from './installer.js';
|
|
11
12
|
import { addSessionParams, detectPlatform } from './project.js';
|
|
12
13
|
import { indexProjectDesign } from './indexer.js';
|
|
14
|
+
import { startBasicPreviewProxy } from './proxy.js';
|
|
15
|
+
import { FOUNDRY_VERSION, releasePreflight } from './release.js';
|
|
13
16
|
const args = process.argv.slice(2);
|
|
14
|
-
const command = args[0] ?? '
|
|
17
|
+
const command = args[0]?.startsWith('-') ? 'launch' : (args[0] ?? 'launch');
|
|
15
18
|
const runtimeRepository = resolve(fileURLToPath(new URL('../../../', import.meta.url)));
|
|
16
19
|
function flag(name) {
|
|
17
20
|
const index = args.indexOf(name);
|
|
@@ -27,17 +30,24 @@ function printHelp() {
|
|
|
27
30
|
console.log(`Foundry Design Control
|
|
28
31
|
|
|
29
32
|
Usage:
|
|
30
|
-
foundry-design
|
|
33
|
+
foundry-design [--project PATH] [--url URL] [--yes] [--no-start]
|
|
34
|
+
foundry-design setup [--project PATH] [--agent codex,cursor,claude] [--global] [--url URL] [--yes]
|
|
35
|
+
foundry-design update [--project PATH] [--agent codex,cursor,claude] [--yes]
|
|
31
36
|
foundry-design init <web|swiftui|react-native> [--project PATH]
|
|
32
|
-
foundry-design start [--project PATH] [--url URL] [--platform PLATFORM] [--no-open] [--no-dev]
|
|
33
|
-
foundry-design doctor [--project PATH]
|
|
37
|
+
foundry-design start [--project PATH] [--url URL] [--platform PLATFORM] [--new] [--no-open] [--no-dev]
|
|
38
|
+
foundry-design doctor [--project PATH] [--repair]
|
|
34
39
|
foundry-design index [--project PATH] [--output FILE]
|
|
35
40
|
foundry-design uninstall [--project PATH] [--yes]
|
|
36
41
|
foundry-design export <SESSION_ID> [--format json|prompt|full] [--output FILE]
|
|
37
|
-
foundry-design install-agent <cursor|claude|codex> [--project PATH]
|
|
42
|
+
foundry-design install-agent <cursor|claude|codex> [--global | --project PATH]
|
|
38
43
|
|
|
39
44
|
Foundry is local-only and never edits product source from inspector controls.`);
|
|
40
45
|
}
|
|
46
|
+
async function pathExists(path) {
|
|
47
|
+
return access(path)
|
|
48
|
+
.then(() => true)
|
|
49
|
+
.catch(() => false);
|
|
50
|
+
}
|
|
41
51
|
async function gitOutput(root, gitArgs) {
|
|
42
52
|
return new Promise((resolveRevision) => {
|
|
43
53
|
const child = spawn('git', gitArgs, {
|
|
@@ -106,9 +116,15 @@ function requestedAgents() {
|
|
|
106
116
|
return [...new Set(agents)];
|
|
107
117
|
}
|
|
108
118
|
async function setup() {
|
|
119
|
+
console.log(`${releasePreflight('set up this project')}\n`);
|
|
109
120
|
const root = projectRoot();
|
|
121
|
+
const requested = requestedAgents();
|
|
122
|
+
const sharedAgentSetup = has('--global');
|
|
123
|
+
const detectedPlan = sharedAgentSetup
|
|
124
|
+
? await createSetupPlan(root, { agents: requested })
|
|
125
|
+
: undefined;
|
|
110
126
|
const options = {
|
|
111
|
-
agents:
|
|
127
|
+
agents: sharedAgentSetup ? [] : requested,
|
|
112
128
|
targetUrl: flag('--url'),
|
|
113
129
|
packageRoot: has('--local-mcp') ? runtimeRepository : undefined,
|
|
114
130
|
};
|
|
@@ -116,7 +132,11 @@ async function setup() {
|
|
|
116
132
|
console.log(`Foundry setup\n\nProject: ${root}\nPlatform: ${plan.platform}`);
|
|
117
133
|
if (plan.framework)
|
|
118
134
|
console.log(`Framework: ${plan.framework}`);
|
|
119
|
-
console.log(`Agent integration: ${
|
|
135
|
+
console.log(`Agent integration: ${sharedAgentSetup
|
|
136
|
+
? `${detectedPlan?.agents.join(', ')} (shared across projects)`
|
|
137
|
+
: plan.agents.length
|
|
138
|
+
? plan.agents.join(', ')
|
|
139
|
+
: 'plugin-provided'}`);
|
|
120
140
|
console.log('\nFiles Foundry will manage:');
|
|
121
141
|
for (const path of plan.files)
|
|
122
142
|
console.log(` ${path}`);
|
|
@@ -128,14 +148,71 @@ async function setup() {
|
|
|
128
148
|
return;
|
|
129
149
|
}
|
|
130
150
|
const result = await setupProject(root, options);
|
|
151
|
+
if (sharedAgentSetup) {
|
|
152
|
+
for (const agent of detectedPlan?.agents ?? []) {
|
|
153
|
+
await installHostAgentIntegration(homedir(), agent, {
|
|
154
|
+
packageRoot: options.packageRoot,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
131
158
|
console.log(`\n✓ Foundry configured ${result.changed.length} files.`);
|
|
159
|
+
for (const check of result.validation) {
|
|
160
|
+
const marker = check.status === 'passed' ? '✓' : check.status === 'skipped' ? '–' : '!';
|
|
161
|
+
console.log(`${marker} ${check.name}: ${check.status.replaceAll('-', ' ')}`);
|
|
162
|
+
}
|
|
163
|
+
if (result.skillDirectories.length) {
|
|
164
|
+
console.log(`✓ Installed the Foundry skill for ${result.agents.join(', ')}.`);
|
|
165
|
+
}
|
|
166
|
+
if (sharedAgentSetup && detectedPlan?.agents.length) {
|
|
167
|
+
console.log(`✓ Installed the shared Foundry connection for ${detectedPlan.agents.join(', ')}.`);
|
|
168
|
+
}
|
|
132
169
|
if (result.targetUrl)
|
|
133
170
|
console.log(`✓ Preview URL: ${result.targetUrl}`);
|
|
134
171
|
if (result.devCommand)
|
|
135
172
|
console.log(`✓ Development command: ${result.devCommand.command} ${result.devCommand.args.join(' ')}`);
|
|
136
|
-
console.log('\
|
|
173
|
+
console.log('\nSetup complete. One coding-agent restart is required so it can load Foundry.');
|
|
174
|
+
console.log(`1. Restart ${sharedAgentSetup && detectedPlan?.agents.length
|
|
175
|
+
? detectedPlan.agents.join(', ')
|
|
176
|
+
: result.agents.length
|
|
177
|
+
? result.agents.join(', ')
|
|
178
|
+
: 'your coding agent'}.`);
|
|
179
|
+
console.log('2. Reopen this exact project folder.');
|
|
180
|
+
console.log('3. Ask: "Start Foundry for this project and keep listening for Apply with agent requests."');
|
|
181
|
+
console.log('\nAfter that, the browser will confirm when the agent is ready.');
|
|
182
|
+
}
|
|
183
|
+
async function update() {
|
|
184
|
+
console.log(`${releasePreflight('update this project')}\n`);
|
|
185
|
+
const root = projectRoot();
|
|
186
|
+
const options = {
|
|
187
|
+
agents: requestedAgents(),
|
|
188
|
+
packageRoot: has('--local-mcp') ? runtimeRepository : undefined,
|
|
189
|
+
};
|
|
190
|
+
const plan = await createUpdatePlan(root, options);
|
|
191
|
+
console.log(`Foundry update\n\nProject: ${root}`);
|
|
192
|
+
console.log(`Agent integration: ${plan.agents.join(', ')}`);
|
|
193
|
+
console.log('\nFoundry will refresh these managed paths:');
|
|
194
|
+
for (const path of plan.files)
|
|
195
|
+
console.log(` ${path}`);
|
|
196
|
+
console.log('\nFiles changed since Foundry installed them will be preserved.');
|
|
197
|
+
if (!(await confirm('\nContinue with this update?'))) {
|
|
198
|
+
console.log('Update cancelled.');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const result = await updateProject(root, options);
|
|
202
|
+
console.log(`\n✓ Foundry refreshed ${result.changed.length} managed paths.`);
|
|
203
|
+
if (result.preserved.length) {
|
|
204
|
+
console.log('Preserved files containing user changes:');
|
|
205
|
+
for (const path of result.preserved)
|
|
206
|
+
console.log(` ${path}`);
|
|
207
|
+
}
|
|
208
|
+
for (const check of result.validation) {
|
|
209
|
+
const marker = check.status === 'passed' ? '✓' : check.status === 'skipped' ? '–' : '!';
|
|
210
|
+
console.log(`${marker} ${check.name}: ${check.status.replaceAll('-', ' ')}`);
|
|
211
|
+
}
|
|
212
|
+
console.log('\nRestart your coding agent so it loads the refreshed Foundry connection and skill.');
|
|
137
213
|
}
|
|
138
214
|
async function initProject() {
|
|
215
|
+
console.log(`${releasePreflight('initialize this project')}\n`);
|
|
139
216
|
const platform = (args[1] ?? 'web');
|
|
140
217
|
if (!['web', 'swiftui', 'react-native'].includes(platform))
|
|
141
218
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
@@ -192,9 +269,7 @@ async function start() {
|
|
|
192
269
|
config?.platform ??
|
|
193
270
|
(await detectPlatform(root));
|
|
194
271
|
const targetUrl = flag('--url') ?? config?.targetUrl;
|
|
195
|
-
|
|
196
|
-
throw new Error('Foundry setup is incomplete. Connect .foundry/web-adapter.ts to a development-only client entry, then set instrumented to true.');
|
|
197
|
-
}
|
|
272
|
+
let basicPreview;
|
|
198
273
|
let developmentServer;
|
|
199
274
|
if (platform === 'web' &&
|
|
200
275
|
targetUrl &&
|
|
@@ -220,54 +295,88 @@ async function start() {
|
|
|
220
295
|
}
|
|
221
296
|
}
|
|
222
297
|
const projectRevision = await revision(root);
|
|
298
|
+
const configuredViewports = config?.design?.viewports ?? [];
|
|
299
|
+
const initialViewport = configuredViewports.find((viewport) => viewport.id.toLowerCase() === 'desktop' || viewport.label.toLowerCase() === 'desktop') ??
|
|
300
|
+
[...configuredViewports].sort((left, right) => right.width - left.width)[0] ?? {
|
|
301
|
+
width: 1440,
|
|
302
|
+
height: 900,
|
|
303
|
+
};
|
|
223
304
|
const context = {
|
|
224
305
|
projectRoot: root,
|
|
225
306
|
revision: projectRevision,
|
|
226
307
|
platform,
|
|
227
308
|
targetUrl,
|
|
228
309
|
targetName: basename(root),
|
|
310
|
+
viewport: { width: initialViewport.width, height: initialViewport.height ?? 900 },
|
|
229
311
|
theme: 'system',
|
|
230
312
|
breakpoint: 'current',
|
|
231
313
|
state: 'current',
|
|
232
314
|
};
|
|
233
315
|
const store = new SessionStore();
|
|
234
|
-
const
|
|
316
|
+
const resumable = has('--new')
|
|
317
|
+
? undefined
|
|
318
|
+
: (await store.list()).find((candidate) => candidate.changeSet.context.projectRoot === root &&
|
|
319
|
+
candidate.changeSet.context.revision === projectRevision &&
|
|
320
|
+
candidate.changeSet.context.targetUrl === targetUrl);
|
|
321
|
+
const session = resumable
|
|
322
|
+
? await store.read(resumable.changeSet.sessionId)
|
|
323
|
+
: await store.create(context);
|
|
235
324
|
if (platform === 'web') {
|
|
236
325
|
const graph = await indexProjectDesign(root, config, projectRevision);
|
|
237
326
|
await store.setDesignGraph(session.changeSet.sessionId, graph);
|
|
238
327
|
console.log(`Indexed ${graph.tokens.length} tokens, ${graph.components.length} components, and ${graph.breakpoints.length} viewports.`);
|
|
239
328
|
}
|
|
240
329
|
const runtime = new FoundryRuntime({ store });
|
|
330
|
+
let ownsRuntime = false;
|
|
241
331
|
try {
|
|
242
332
|
await runtime.start();
|
|
333
|
+
ownsRuntime = true;
|
|
243
334
|
}
|
|
244
335
|
catch (error) {
|
|
245
336
|
if (error.code !== 'EADDRINUSE')
|
|
246
337
|
throw error;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
338
|
+
const healthy = await fetch('http://127.0.0.1:4387/v1/health')
|
|
339
|
+
.then((response) => response.ok)
|
|
340
|
+
.catch(() => false);
|
|
341
|
+
if (!healthy)
|
|
342
|
+
throw new Error('Port 4387 is in use by another process. Stop it and run Foundry again.');
|
|
343
|
+
console.log('Using the existing local Foundry runtime.');
|
|
251
344
|
}
|
|
252
345
|
const reviewUrl = `http://127.0.0.1:4387/?session=${encodeURIComponent(session.changeSet.sessionId)}&token=${encodeURIComponent(session.token)}`;
|
|
346
|
+
if (platform === 'web' && targetUrl && config && !config.instrumented) {
|
|
347
|
+
basicPreview = await startBasicPreviewProxy(targetUrl, config.runtimeUrl);
|
|
348
|
+
console.log('Basic mode: Foundry is attached without changing the project entry point.');
|
|
349
|
+
console.log('Run setup again after adding a supported client entry to enable exact source mapping.');
|
|
350
|
+
}
|
|
253
351
|
const productUrl = targetUrl && platform === 'web'
|
|
254
|
-
? addSessionParams(targetUrl, session.changeSet.sessionId, session.token)
|
|
352
|
+
? addSessionParams(basicPreview?.url ?? targetUrl, session.changeSet.sessionId, session.token)
|
|
353
|
+
: reviewUrl;
|
|
354
|
+
const workspaceUrl = platform === 'web' && targetUrl
|
|
355
|
+
? `${reviewUrl}&preview=${encodeURIComponent(productUrl)}`
|
|
255
356
|
: reviewUrl;
|
|
256
357
|
console.log(`Foundry session ${session.changeSet.sessionId}`);
|
|
358
|
+
if (resumable)
|
|
359
|
+
console.log('Resumed the most recent session for this project revision.');
|
|
257
360
|
console.log(`Platform: ${platform}`);
|
|
258
|
-
console.log(`
|
|
361
|
+
console.log(`Workspace: ${workspaceUrl}`);
|
|
259
362
|
if (targetUrl && platform === 'web')
|
|
260
|
-
console.log(
|
|
363
|
+
console.log(`${basicPreview ? 'Basic' : 'Precision'} preview: ${productUrl}`);
|
|
261
364
|
if (platform === 'web') {
|
|
262
365
|
console.log('\nFirst edit:');
|
|
263
366
|
console.log(' 1. Option-click an element. Shift-click to add more.');
|
|
264
367
|
console.log(' 2. Drag a blue handle or use the inspector for exact values.');
|
|
265
368
|
console.log(' 3. Review the semantic mapping, then Apply with agent.');
|
|
369
|
+
console.log('\nAgent handoff:');
|
|
370
|
+
console.log(' Keep this process running while you design.');
|
|
371
|
+
console.log(' Foundry will show “Agent is ready” only while a coding agent is listening.');
|
|
372
|
+
console.log(' If it is not ready, ask the agent: “Keep listening for Apply with agent requests.”');
|
|
266
373
|
}
|
|
267
374
|
if (!has('--no-open'))
|
|
268
|
-
await openUrl(
|
|
375
|
+
await openUrl(workspaceUrl);
|
|
269
376
|
const shutdown = async () => {
|
|
270
|
-
|
|
377
|
+
if (ownsRuntime)
|
|
378
|
+
await runtime.stop();
|
|
379
|
+
await basicPreview?.stop();
|
|
271
380
|
developmentServer?.kill('SIGTERM');
|
|
272
381
|
process.exit(0);
|
|
273
382
|
};
|
|
@@ -291,6 +400,7 @@ async function indexDesign() {
|
|
|
291
400
|
console.log(content);
|
|
292
401
|
}
|
|
293
402
|
async function uninstall() {
|
|
403
|
+
console.log(`${releasePreflight('remove managed project integration')}\n`);
|
|
294
404
|
const root = projectRoot();
|
|
295
405
|
if (!(await confirm(`Remove Foundry-managed project integration from ${root}?`))) {
|
|
296
406
|
console.log('Uninstall cancelled.');
|
|
@@ -309,32 +419,39 @@ async function doctor() {
|
|
|
309
419
|
const platform = await detectPlatform(root);
|
|
310
420
|
const configPath = join(root, '.foundry', 'foundry.config.json');
|
|
311
421
|
let config;
|
|
422
|
+
const manifest = await readFile(join(root, '.foundry', 'install-manifest.json'), 'utf8')
|
|
423
|
+
.then((content) => JSON.parse(content))
|
|
424
|
+
.catch(() => undefined);
|
|
312
425
|
try {
|
|
313
426
|
config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
314
427
|
}
|
|
315
428
|
catch {
|
|
316
429
|
/* Report setup as missing below. */
|
|
317
430
|
}
|
|
318
|
-
const
|
|
431
|
+
const projectAgentFiles = [
|
|
319
432
|
join(root, '.codex', 'config.toml'),
|
|
320
433
|
join(root, '.cursor', 'mcp.json'),
|
|
321
434
|
join(root, '.mcp.json'),
|
|
322
435
|
];
|
|
436
|
+
const sharedAgentFiles = [
|
|
437
|
+
join(homedir(), '.codex', 'config.toml'),
|
|
438
|
+
join(homedir(), '.cursor', 'mcp.json'),
|
|
439
|
+
join(homedir(), '.claude.json'),
|
|
440
|
+
];
|
|
323
441
|
const configuredAgents = [];
|
|
324
|
-
for (const path of
|
|
442
|
+
for (const path of [...projectAgentFiles, ...sharedAgentFiles]) {
|
|
325
443
|
if ((await readFile(path, 'utf8').catch(() => '')).includes('foundry-design-control'))
|
|
326
444
|
configuredAgents.push(path);
|
|
327
445
|
}
|
|
328
|
-
const pluginProvided = await readFile(join(root, '.foundry', 'install-manifest.json'), 'utf8')
|
|
329
|
-
.then((content) => {
|
|
330
|
-
const manifest = JSON.parse(content);
|
|
331
|
-
return Array.isArray(manifest.agents) && manifest.agents.length === 0;
|
|
332
|
-
})
|
|
333
|
-
.catch(() => false);
|
|
334
446
|
const checks = [
|
|
335
447
|
['Project', root, true],
|
|
336
448
|
['Detected platform', platform, true],
|
|
337
449
|
['Foundry config', configPath, Boolean(config)],
|
|
450
|
+
[
|
|
451
|
+
'Managed integration',
|
|
452
|
+
manifest?.generatorVersion ?? 'not installed',
|
|
453
|
+
manifest?.generatorVersion === FOUNDRY_VERSION,
|
|
454
|
+
],
|
|
338
455
|
[
|
|
339
456
|
'Instrumentation',
|
|
340
457
|
config?.instrumented ? `${config.framework ?? config.platform}` : 'integration pending',
|
|
@@ -342,12 +459,8 @@ async function doctor() {
|
|
|
342
459
|
],
|
|
343
460
|
[
|
|
344
461
|
'Agent connection',
|
|
345
|
-
configuredAgents.length
|
|
346
|
-
|
|
347
|
-
: pluginProvided
|
|
348
|
-
? 'plugin-provided'
|
|
349
|
-
: 'not configured',
|
|
350
|
-
configuredAgents.length > 0 || pluginProvided,
|
|
462
|
+
configuredAgents.length ? configuredAgents.join(', ') : 'not configured',
|
|
463
|
+
configuredAgents.length > 0,
|
|
351
464
|
],
|
|
352
465
|
[
|
|
353
466
|
'Project preview',
|
|
@@ -364,8 +477,49 @@ async function doctor() {
|
|
|
364
477
|
];
|
|
365
478
|
for (const [label, value, passed] of checks)
|
|
366
479
|
console.log(`${passed ? '✓' : '○'} ${label}: ${value}`);
|
|
367
|
-
if (
|
|
368
|
-
console.log('
|
|
480
|
+
if (has('--repair')) {
|
|
481
|
+
console.log(`\n${releasePreflight('repair this project and agent connection')}\n`);
|
|
482
|
+
const detectedAgents = (await createSetupPlan(root)).agents;
|
|
483
|
+
const result = config
|
|
484
|
+
? await updateProject(root, { agents: [] })
|
|
485
|
+
: await setupProject(root, { agents: [], targetUrl: flag('--url') });
|
|
486
|
+
for (const agent of detectedAgents)
|
|
487
|
+
await installHostAgentIntegration(homedir(), agent, {
|
|
488
|
+
packageRoot: has('--local-mcp') ? runtimeRepository : undefined,
|
|
489
|
+
});
|
|
490
|
+
console.log(`✓ Repaired ${result.changed.length} managed paths and the shared ${detectedAgents.join(', ')} connection.`);
|
|
491
|
+
}
|
|
492
|
+
else if (!config || !configuredAgents.length) {
|
|
493
|
+
console.log('Run: foundry-design doctor --repair');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
async function launch() {
|
|
497
|
+
console.log(`${releasePreflight('install, update, and start Foundry')}\n`);
|
|
498
|
+
const root = projectRoot();
|
|
499
|
+
const manifest = join(root, '.foundry', 'install-manifest.json');
|
|
500
|
+
const installed = await pathExists(manifest);
|
|
501
|
+
const detectedAgents = (await createSetupPlan(root)).agents;
|
|
502
|
+
console.log(`Foundry\n\nProject: ${root}`);
|
|
503
|
+
console.log(`${installed ? 'Refreshing' : 'Preparing'} the project and shared ${detectedAgents.join(', ')} connection.`);
|
|
504
|
+
if (!(await confirm('Continue?'))) {
|
|
505
|
+
console.log('Foundry cancelled.');
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const result = installed
|
|
509
|
+
? await updateProject(root, { agents: [], targetUrl: flag('--url') })
|
|
510
|
+
: await setupProject(root, { agents: [], targetUrl: flag('--url') });
|
|
511
|
+
for (const agent of detectedAgents)
|
|
512
|
+
await installHostAgentIntegration(homedir(), agent, {
|
|
513
|
+
packageRoot: has('--local-mcp') ? runtimeRepository : undefined,
|
|
514
|
+
});
|
|
515
|
+
console.log(`✓ ${installed ? 'Updated' : 'Installed'} and validated Foundry.`);
|
|
516
|
+
for (const check of result.validation)
|
|
517
|
+
console.log(`${check.status === 'passed' ? '✓' : check.status === 'skipped' ? '–' : '!'} ${check.name}: ${check.status.replaceAll('-', ' ')}`);
|
|
518
|
+
if (!installed)
|
|
519
|
+
console.log(`\nRestart ${detectedAgents.join(', ')} once to load the shared connection. The visual session will open now and queued batches will wait safely until it reconnects.`);
|
|
520
|
+
if (has('--no-start'))
|
|
521
|
+
return;
|
|
522
|
+
await start();
|
|
369
523
|
}
|
|
370
524
|
async function exportSession() {
|
|
371
525
|
const id = args[1];
|
|
@@ -392,39 +546,41 @@ async function exportSession() {
|
|
|
392
546
|
console.log(content);
|
|
393
547
|
}
|
|
394
548
|
async function installAgent() {
|
|
549
|
+
console.log(`${releasePreflight('install the coding-agent connection')}\n`);
|
|
395
550
|
const agent = args[1];
|
|
396
551
|
if (!agent || !['cursor', 'claude', 'codex'].includes(agent))
|
|
397
552
|
throw new Error('install-agent requires cursor, claude, or codex');
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
553
|
+
if (has('--global')) {
|
|
554
|
+
const result = await installHostAgentIntegration(homedir(), agent, {
|
|
555
|
+
packageRoot: has('--local-mcp') ? runtimeRepository : undefined,
|
|
556
|
+
});
|
|
557
|
+
console.log(`Installed the shared Foundry connection for ${agent}.`);
|
|
558
|
+
console.log(`MCP configuration: ${result.configFile}`);
|
|
559
|
+
console.log(`Skill: ${result.skillDirectory}`);
|
|
560
|
+
if (result.preserved.length) {
|
|
561
|
+
console.log('Preserved customized skill files:');
|
|
562
|
+
for (const path of result.preserved)
|
|
563
|
+
console.log(` ${path}`);
|
|
564
|
+
}
|
|
565
|
+
console.log(`Restart ${agent === 'codex' ? 'Codex' : agent === 'cursor' ? 'Cursor' : 'Claude Code'} once. Future projects can use "npx foundry-design setup --agent none --yes" without another MCP install.`);
|
|
410
566
|
return;
|
|
411
567
|
}
|
|
412
|
-
const
|
|
413
|
-
await
|
|
414
|
-
let current = {};
|
|
415
|
-
try {
|
|
416
|
-
current = JSON.parse(await readFile(file, 'utf8'));
|
|
417
|
-
}
|
|
418
|
-
catch {
|
|
419
|
-
/* Create a new project config. */
|
|
420
|
-
}
|
|
421
|
-
current.mcpServers = { ...current.mcpServers, 'foundry-design-control': server };
|
|
422
|
-
await writeFile(file, `${JSON.stringify(current, null, 2)}\n`);
|
|
568
|
+
const root = projectRoot();
|
|
569
|
+
const file = await installAgentIntegration(root, agent, has('--local-mcp') ? runtimeRepository : undefined);
|
|
423
570
|
console.log(`Installed Foundry MCP configuration for ${agent}: ${file}`);
|
|
571
|
+
console.log(`Restart ${agent === 'codex' ? 'Codex' : agent === 'cursor' ? 'Cursor' : 'Claude Code'} before starting a Foundry apply session.`);
|
|
424
572
|
}
|
|
425
573
|
try {
|
|
426
|
-
if (command === '
|
|
574
|
+
if (has('--version') || command === 'version')
|
|
575
|
+
console.log(FOUNDRY_VERSION);
|
|
576
|
+
else if (has('--help') || command === 'help')
|
|
577
|
+
printHelp();
|
|
578
|
+
else if (command === 'launch')
|
|
579
|
+
await launch();
|
|
580
|
+
else if (command === 'setup')
|
|
427
581
|
await setup();
|
|
582
|
+
else if (command === 'update')
|
|
583
|
+
await update();
|
|
428
584
|
else if (command === 'init')
|
|
429
585
|
await initProject();
|
|
430
586
|
else if (command === 'start')
|