@selvakumaresra/specship 0.3.0 → 0.4.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/commands/ss-design-implement.md +5 -0
- package/commands/ss-design-loop.md +125 -0
- package/dist/bin/specship.js +66 -0
- package/dist/bin/specship.js.map +1 -1
- package/dist/designer/artifact-store.js +54 -0
- package/dist/designer/browser.js +141 -0
- package/dist/designer/cdp-ensure.js +60 -0
- package/dist/designer/cdp-env.js +18 -0
- package/dist/designer/cdp-trace.js +599 -0
- package/dist/designer/cross-platform.js +74 -0
- package/dist/designer/designer-controller.js +1413 -0
- package/dist/designer/file-panel.js +39 -0
- package/dist/designer/interstitials.js +97 -0
- package/dist/designer/oopif-reader.js +176 -0
- package/dist/designer/package-meta.js +18 -0
- package/dist/designer/preview-host.js +50 -0
- package/dist/designer/repo-root.js +31 -0
- package/dist/designer/run-state.js +353 -0
- package/dist/designer/session-store.js +59 -0
- package/dist/designer/ui-anchors.js +651 -0
- package/dist/installer/index.d.ts +5 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +3 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/instructions-template.d.ts +17 -0
- package/dist/installer/instructions-template.d.ts.map +1 -1
- package/dist/installer/instructions-template.js +31 -1
- package/dist/installer/instructions-template.js.map +1 -1
- package/dist/installer/targets/claude.d.ts +19 -0
- package/dist/installer/targets/claude.d.ts.map +1 -1
- package/dist/installer/targets/claude.js +98 -1
- package/dist/installer/targets/claude.js.map +1 -1
- package/dist/installer/targets/shared.d.ts +14 -0
- package/dist/installer/targets/shared.d.ts.map +1 -1
- package/dist/installer/targets/shared.js +49 -0
- package/dist/installer/targets/shared.js.map +1 -1
- package/dist/installer/targets/types.d.ts +8 -0
- package/dist/installer/targets/types.d.ts.map +1 -1
- package/dist/mcp/designer-tools.d.ts +33 -0
- package/dist/mcp/designer-tools.d.ts.map +1 -0
- package/dist/mcp/designer-tools.js +313 -0
- package/dist/mcp/designer-tools.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +22 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/web/{chunk-JT7P3DEK.js → chunk-2YUJNZ2Y.js} +3 -3
- package/dist/web/{chunk-JN6W7HCN.js → chunk-45QHGCB4.js} +1 -1
- package/dist/web/{chunk-RAAMPHPJ.js → chunk-A5R3MJMO.js} +1 -1
- package/dist/web/{chunk-2DHIGIOI.js → chunk-ASZ77FMZ.js} +1 -1
- package/dist/web/{chunk-TWXZK6XM.js → chunk-B3YPFY6A.js} +1 -1
- package/dist/web/chunk-D5OCNEJA.js +2 -0
- package/dist/web/{chunk-3SEJX2BK.js → chunk-FHZHD2ZG.js} +1 -1
- package/dist/web/chunk-GR72OOCN.js +1 -0
- package/dist/web/{chunk-DA6SNNAF.js → chunk-GWPVKJIY.js} +1 -1
- package/dist/web/{chunk-YAWCRPHV.js → chunk-NZEZCT65.js} +1 -1
- package/dist/web/{chunk-BCZM5AXU.js → chunk-UBOZGQNK.js} +1 -1
- package/dist/web/{chunk-BPECIDVO.js → chunk-WCKHQIYN.js} +1 -1
- package/dist/web/{chunk-JFYVCXK3.js → chunk-WLIMNDS3.js} +1 -1
- package/dist/web/{chunk-LV4G6QFG.js → chunk-YAMRN47K.js} +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/main-R53HA54V.js +1 -0
- package/dist/web/sw.js +69 -0
- package/dist/workflows/defaults/claude-design-implement.yaml +138 -49
- package/hooks/hooks.json +11 -0
- package/package.json +7 -3
- package/selectors.json +41 -0
- package/dist/web/chunk-2OKMB4KX.js +0 -2
- package/dist/web/chunk-4N5DWG46.js +0 -1
- package/dist/web/main-WVI3YTDU.js +0 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sessionDir = sessionDir;
|
|
7
|
+
exports.saveIteration = saveIteration;
|
|
8
|
+
exports.artifactsRoot = artifactsRoot;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const repo_root_1 = require("./repo-root");
|
|
12
|
+
const ARTIFACTS_ROOT = process.env.DESIGNER_ARTIFACTS_DIR || node_path_1.default.join(repo_root_1.REPO_ROOT, 'artifacts');
|
|
13
|
+
function slug(s) {
|
|
14
|
+
return String(s || 'session')
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
17
|
+
.replace(/^-+|-+$/g, '')
|
|
18
|
+
.slice(0, 64) || 'session';
|
|
19
|
+
}
|
|
20
|
+
function ts() {
|
|
21
|
+
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
22
|
+
}
|
|
23
|
+
function sessionDir(key) {
|
|
24
|
+
const dir = node_path_1.default.join(ARTIFACTS_ROOT, slug(key));
|
|
25
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
26
|
+
return dir;
|
|
27
|
+
}
|
|
28
|
+
function saveIteration(key, input) {
|
|
29
|
+
const dir = sessionDir(key);
|
|
30
|
+
const stamp = ts();
|
|
31
|
+
const base = node_path_1.default.join(dir, stamp);
|
|
32
|
+
const record = {
|
|
33
|
+
at: new Date().toISOString(),
|
|
34
|
+
key,
|
|
35
|
+
prompt: input.prompt,
|
|
36
|
+
fidelity: input.fidelity,
|
|
37
|
+
url: input.url,
|
|
38
|
+
meta: input.meta ?? null,
|
|
39
|
+
files: {}
|
|
40
|
+
};
|
|
41
|
+
if (input.html) {
|
|
42
|
+
const p = `${base}.html`;
|
|
43
|
+
node_fs_1.default.writeFileSync(p, input.html);
|
|
44
|
+
record.files.html = p;
|
|
45
|
+
}
|
|
46
|
+
if (input.screenshotPath) {
|
|
47
|
+
record.files.screenshot = input.screenshotPath;
|
|
48
|
+
}
|
|
49
|
+
node_fs_1.default.writeFileSync(`${base}.json`, JSON.stringify(record, null, 2));
|
|
50
|
+
return record;
|
|
51
|
+
}
|
|
52
|
+
function artifactsRoot() {
|
|
53
|
+
return ARTIFACTS_ROOT;
|
|
54
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBrowser = createBrowser;
|
|
4
|
+
const cross_platform_1 = require("./cross-platform");
|
|
5
|
+
const BIN = process.env.DESIGNER_AGENT_BROWSER_BIN || 'agent-browser';
|
|
6
|
+
const DEFAULT_SESSION = process.env.DESIGNER_SESSION_NAME || 'designer';
|
|
7
|
+
// Default to the dedicated debug Chrome on :9222. Without this, callers that
|
|
8
|
+
// don't export DESIGNER_CDP (e.g. codex shelling `designer` directly) silently
|
|
9
|
+
// fall through to AGENT_BROWSER_SESSION_NAME mode and agent-browser launches
|
|
10
|
+
// its own Chromium instead of attaching to the user's live signed-in Chrome.
|
|
11
|
+
// Set DESIGNER_CDP='' explicitly to opt out and use the session-managed flow.
|
|
12
|
+
const CDP = process.env.DESIGNER_CDP ?? '9222';
|
|
13
|
+
function createBrowser({ session = DEFAULT_SESSION, headed = true, timeoutMs = 30_000, cdp = CDP } = {}) {
|
|
14
|
+
const baseEnv = {
|
|
15
|
+
...process.env,
|
|
16
|
+
AGENT_BROWSER_DEFAULT_TIMEOUT: String(timeoutMs),
|
|
17
|
+
...(cdp ? {} : { AGENT_BROWSER_SESSION_NAME: session }),
|
|
18
|
+
...(headed && !cdp ? { AGENT_BROWSER_HEADED: '1' } : {})
|
|
19
|
+
};
|
|
20
|
+
function connectFlags() {
|
|
21
|
+
if (!cdp)
|
|
22
|
+
return [];
|
|
23
|
+
// agent-browser's daemon honors --cdp only when it first creates a
|
|
24
|
+
// session; an existing session keeps its original connection and
|
|
25
|
+
// silently ignores a different --cdp endpoint (verified on 0.21.4
|
|
26
|
+
// through 0.27.2). Scope the daemon session by endpoint so designer
|
|
27
|
+
// never inherits a connection to some other Chrome — e.g. the user's
|
|
28
|
+
// own agent-browser use against a different port (issue #32 triage).
|
|
29
|
+
const scope = ['--session', `designer-cdp-${cdp.replace(/[^a-zA-Z0-9.-]/g, '_')}`];
|
|
30
|
+
if (cdp === 'auto' || cdp === '1' || cdp === 'true')
|
|
31
|
+
return [...scope, '--auto-connect'];
|
|
32
|
+
return [...scope, '--cdp', cdp];
|
|
33
|
+
}
|
|
34
|
+
function run(args, { input, parseJson = false } = {}) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const finalArgs = [...connectFlags(), ...args];
|
|
37
|
+
const child = (0, cross_platform_1.xspawn)(BIN, finalArgs, { env: baseEnv, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
38
|
+
let stdout = '';
|
|
39
|
+
let stderr = '';
|
|
40
|
+
child.stdout.on('data', (d) => (stdout += d.toString()));
|
|
41
|
+
child.stderr.on('data', (d) => (stderr += d.toString()));
|
|
42
|
+
child.on('error', (err) => reject(err));
|
|
43
|
+
child.on('close', (code) => {
|
|
44
|
+
if (code !== 0) {
|
|
45
|
+
const err = new Error(`agent-browser ${finalArgs.join(' ')} exited ${code}: ${stderr.trim() || stdout.trim()}`);
|
|
46
|
+
err.code = code;
|
|
47
|
+
return reject(err);
|
|
48
|
+
}
|
|
49
|
+
if (!parseJson)
|
|
50
|
+
return resolve(stdout.trim());
|
|
51
|
+
try {
|
|
52
|
+
resolve(JSON.parse(stdout));
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
reject(new Error(`Failed to parse JSON from agent-browser: ${e.message}\n--stdout--\n${stdout}`));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (input != null) {
|
|
59
|
+
child.stdin.write(input);
|
|
60
|
+
child.stdin.end();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
session,
|
|
66
|
+
run,
|
|
67
|
+
open: (url) => run(['open', url]),
|
|
68
|
+
close: () => run(['close']).catch(() => null),
|
|
69
|
+
url: () => run(['get', 'url']),
|
|
70
|
+
title: () => run(['get', 'title']),
|
|
71
|
+
tabs: async () => {
|
|
72
|
+
const out = await run(['tab', 'list', '--json']);
|
|
73
|
+
const env = JSON.parse(out);
|
|
74
|
+
if (env.success === false) {
|
|
75
|
+
throw new Error(`agent-browser tab list failed: ${JSON.stringify(env.error)}`);
|
|
76
|
+
}
|
|
77
|
+
return env.data?.tabs ?? [];
|
|
78
|
+
},
|
|
79
|
+
activateTab: async (index) => {
|
|
80
|
+
await run(['tab', String(index)]);
|
|
81
|
+
},
|
|
82
|
+
reload: () => run(['reload']),
|
|
83
|
+
cookies: async () => {
|
|
84
|
+
const out = await run(['cookies', 'get', '--json']);
|
|
85
|
+
const env = JSON.parse(out);
|
|
86
|
+
if (env.success === false) {
|
|
87
|
+
throw new Error(`agent-browser cookies get failed: ${JSON.stringify(env.error)}`);
|
|
88
|
+
}
|
|
89
|
+
return env.data?.cookies ?? [];
|
|
90
|
+
},
|
|
91
|
+
snapshot: ({ interactive = true, scope } = {}) => {
|
|
92
|
+
const args = ['snapshot', '--json'];
|
|
93
|
+
if (interactive)
|
|
94
|
+
args.push('-i');
|
|
95
|
+
if (scope)
|
|
96
|
+
args.push('-s', scope);
|
|
97
|
+
return run(args, { parseJson: true });
|
|
98
|
+
},
|
|
99
|
+
snapshotText: ({ interactive = true, scope } = {}) => {
|
|
100
|
+
const args = ['snapshot'];
|
|
101
|
+
if (interactive)
|
|
102
|
+
args.push('-i');
|
|
103
|
+
if (scope)
|
|
104
|
+
args.push('-s', scope);
|
|
105
|
+
return run(args);
|
|
106
|
+
},
|
|
107
|
+
click: (sel) => run(['click', sel]),
|
|
108
|
+
fill: (sel, text) => run(['fill', sel, text]),
|
|
109
|
+
type: (sel, text) => run(['type', sel, text]),
|
|
110
|
+
press: (key) => run(['press', key]),
|
|
111
|
+
getText: (sel) => run(['get', 'text', sel]),
|
|
112
|
+
getAttr: (sel, name) => run(['get', 'attr', name, sel]),
|
|
113
|
+
getHtml: (sel) => run(['get', 'html', sel]),
|
|
114
|
+
isVisible: (sel) => run(['is', 'visible', sel]).then((s) => s.trim() === 'true'),
|
|
115
|
+
waitFor: (selOrMs) => run(['wait', String(selOrMs)]),
|
|
116
|
+
waitLoad: (state = 'networkidle') => run(['wait', '--load', state]),
|
|
117
|
+
screenshot: (path, { full = false } = {}) => {
|
|
118
|
+
const args = ['screenshot'];
|
|
119
|
+
if (path)
|
|
120
|
+
args.push(path);
|
|
121
|
+
if (full)
|
|
122
|
+
args.push('--full');
|
|
123
|
+
return run(args);
|
|
124
|
+
},
|
|
125
|
+
// Pipe JS via stdin (agent-browser's `--stdin` flag) instead of argv.
|
|
126
|
+
// Argv-passed JS gets mangled by every shell layer (parens, quotes,
|
|
127
|
+
// newlines all suffer) — most painfully on Windows where cmd.exe
|
|
128
|
+
// doesn't preserve multiline strings — but this is the correct cross-
|
|
129
|
+
// platform path: no escaping required, JS goes through verbatim.
|
|
130
|
+
eval: (js) => run(['eval', '--stdin'], { input: js }),
|
|
131
|
+
evalValue: async (js) => {
|
|
132
|
+
const out = await run(['eval', '--stdin'], { input: js });
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(out);
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
throw new Error(`evalValue: stdout was not JSON-parseable: ${e.message}\n--stdout--\n${out.slice(0, 500)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureCdpUp = ensureCdpUp;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const cross_platform_1 = require("./cross-platform");
|
|
12
|
+
const cdp_env_1 = require("./cdp-env");
|
|
13
|
+
const PORT = process.env.DESIGNER_CDP || '9222';
|
|
14
|
+
const PROFILE = node_path_1.default.join(node_os_1.default.homedir(), '.chrome-designer-profile');
|
|
15
|
+
const CHROME_BIN = process.env.CHROME_BIN || (0, cross_platform_1.defaultChromeBin)();
|
|
16
|
+
async function isCdpUp() {
|
|
17
|
+
try {
|
|
18
|
+
const res = await fetch(`http://127.0.0.1:${PORT}/json/version`, { signal: AbortSignal.timeout(1500) });
|
|
19
|
+
return res.ok;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function sleep(ms) {
|
|
26
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
27
|
+
}
|
|
28
|
+
// Make sure a debug Chrome is listening on CDP before the first tool call.
|
|
29
|
+
// Auto-launch is gated on three conditions:
|
|
30
|
+
// 1. CDP is down (no existing debug server)
|
|
31
|
+
// 2. The dedicated profile exists (user already consented once via `designer setup`)
|
|
32
|
+
// 3. No non-debug Chrome is running (launching would either no-op or steal focus)
|
|
33
|
+
// Otherwise: return an actionable error the caller can surface to the user.
|
|
34
|
+
async function ensureCdpUp() {
|
|
35
|
+
// Respect the explicit opt-out (DESIGNER_CDP=''): never probe or auto-launch
|
|
36
|
+
// the debug Chrome for a user who chose the agent-browser session-managed flow.
|
|
37
|
+
// Throwing here makes any stray CdpSession.attach() degrade to null cleanly.
|
|
38
|
+
if (!(0, cdp_env_1.isCdpEnabled)()) {
|
|
39
|
+
throw new Error("CDP explicitly disabled (DESIGNER_CDP=''); using the agent-browser session-managed flow.");
|
|
40
|
+
}
|
|
41
|
+
if (await isCdpUp())
|
|
42
|
+
return;
|
|
43
|
+
if (!node_fs_1.default.existsSync(PROFILE)) {
|
|
44
|
+
throw new Error(`CDP not up on :${PORT} and no dedicated Chrome profile at ${PROFILE}. Run: designer setup`);
|
|
45
|
+
}
|
|
46
|
+
if ((0, cross_platform_1.isChromeRunning)()) {
|
|
47
|
+
throw new Error(`CDP not up on :${PORT} and a non-debug Chrome is already running. ${cross_platform_1.QUIT_CHROME_HINT} Then retry, or run: designer setup`);
|
|
48
|
+
}
|
|
49
|
+
if (!node_fs_1.default.existsSync(CHROME_BIN)) {
|
|
50
|
+
throw new Error(`CDP not up on :${PORT} and Chrome not found at ${CHROME_BIN}. Set CHROME_BIN or install Chrome.`);
|
|
51
|
+
}
|
|
52
|
+
const child = (0, node_child_process_1.spawn)(CHROME_BIN, ['--remote-debugging-port=' + PORT, '--user-data-dir=' + PROFILE, 'https://claude.ai/design'], { detached: true, stdio: 'ignore' });
|
|
53
|
+
child.unref();
|
|
54
|
+
for (let i = 0; i < 40; i++) {
|
|
55
|
+
await sleep(500);
|
|
56
|
+
if (await isCdpUp())
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
throw new Error(`Auto-launched Chrome but CDP didn't come up on :${PORT} within 20s. Check that the launched window survived, or run designer setup.`);
|
|
60
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isCdpEnabled = isCdpEnabled;
|
|
4
|
+
// Shared resolution of the DESIGNER_CDP opt-out.
|
|
5
|
+
//
|
|
6
|
+
// DESIGNER_CDP has THREE states (see CLAUDE.md): unset or '9222' = CDP on,
|
|
7
|
+
// '' = OFF (the agent-browser session-managed flow). The opt-out is enforced at
|
|
8
|
+
// the GATES (controller attach, the turn-RPC canary, ensureCdpUp), not in the
|
|
9
|
+
// port constants — `browser.ts` resolves the port with `??` (honors '') while
|
|
10
|
+
// `cdp-trace.ts`/`cdp-ensure.ts` use `|| '9222'` (do NOT honor ''), by design.
|
|
11
|
+
//
|
|
12
|
+
// This is the single definition of "is CDP work allowed", so the load-bearing
|
|
13
|
+
// gate can't drift across its call sites (it was inlined in designer-controller
|
|
14
|
+
// 4× and, inverted, in ui-anchors). Pure, no I/O — reads the env at call time so
|
|
15
|
+
// a test or runtime toggle is honored.
|
|
16
|
+
function isCdpEnabled() {
|
|
17
|
+
return (process.env.DESIGNER_CDP ?? '9222') !== '';
|
|
18
|
+
}
|