mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-14 15:16:35 +03:00
Add live.mjs combined entry point for fast startup
Previously, starting live mode required ~5-6 sequential bash calls: read .impeccable.md, start server, check config, read reference, inject tag, verify. The new live.mjs does all of this in a single command (~340ms cold, ~90ms when reusing a running server) and returns everything the agent needs in one JSON blob. Workflow is now: 1. node live.mjs # start + inject + load context (1 bash call) 2. navigate browser # optional MCP call 3. node live-poll.mjs # enter poll loop Reference doc collapsed to a single "Start Live Mode" section with the one-command path plus a first-time config creation fallback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
8030bc226a
commit
996c9af78c
@@ -4,28 +4,33 @@ Launch interactive live variant mode: select elements in the browser, pick a des
|
||||
|
||||
- A running development server with hot module replacement (Vite, Next.js, Bun, etc.), OR a static HTML file open in the browser
|
||||
|
||||
## Start the Server
|
||||
## Start Live Mode (one command)
|
||||
|
||||
1. Read `.impeccable.md` if it exists. Keep the design context in mind for variant generation.
|
||||
2. Start the live variant server in the background. The `--background` flag spawns a detached server process, waits for it to be ready, prints the connection JSON to stdout, and exits:
|
||||
```bash
|
||||
node {{scripts_path}}/live-server.mjs --background
|
||||
```
|
||||
The output JSON contains `port` and `token`. Use the port for the script tag below.
|
||||
|
||||
## Inject the Browser Script
|
||||
|
||||
The `live-inject.mjs` script handles insertion deterministically. It reads `config.json` from its own directory (one-time per-project setup).
|
||||
|
||||
### Step 1: Ensure config.json exists
|
||||
The `live.mjs` entry point does everything in a single call: checks config, starts (or reuses) the server, injects the script tag, loads `.impeccable.md` context.
|
||||
|
||||
```bash
|
||||
node {{scripts_path}}/live-inject.mjs --check
|
||||
node {{scripts_path}}/live.mjs
|
||||
```
|
||||
|
||||
If the output says `{"ok": true, ...}`, skip to Step 2.
|
||||
### Happy path
|
||||
|
||||
If the output says `{"ok": false, "error": "config_missing", "path": "..."}`, you need to create the config **once**. Look at the project structure and package.json to determine:
|
||||
Output JSON:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"serverPort": 8400,
|
||||
"serverToken": "...",
|
||||
"pageFile": "public/index.html",
|
||||
"hasContext": true,
|
||||
"context": "...full .impeccable.md contents..."
|
||||
}
|
||||
```
|
||||
|
||||
Keep the `context` in mind for variant generation. If browser automation tools are available, navigate to the page so the user can see it. Then proceed directly to the poll loop — no other setup steps needed.
|
||||
|
||||
### First-time setup (config missing)
|
||||
|
||||
If `live.mjs` outputs `{"ok": false, "error": "config_missing", "configPath": "..."}`, this project has never used live mode before. Create the config at the reported path based on the project's framework:
|
||||
|
||||
| Framework | `file` | `insertBefore` | `commentSyntax` |
|
||||
|-----------|--------|----------------|-----------------|
|
||||
@@ -38,7 +43,7 @@ If the output says `{"ok": false, "error": "config_missing", "path": "..."}`, yo
|
||||
| Astro | the root layout `.astro` file | `</body>` | `html` |
|
||||
| Static site with a non-root HTML file | e.g. `public/index.html` | `</body>` | `html` |
|
||||
|
||||
Write the config to the path reported by `--check`. Example for this project:
|
||||
Use `insertAfter` instead of `insertBefore` if the anchor should be matched **after** a specific line. Example:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -48,17 +53,7 @@ Write the config to the path reported by `--check`. Example for this project:
|
||||
}
|
||||
```
|
||||
|
||||
Use `insertAfter` instead of `insertBefore` if the anchor should be matched **after** a specific line (e.g. just after the main app script).
|
||||
|
||||
### Step 2: Insert the live tag
|
||||
|
||||
```bash
|
||||
node {{scripts_path}}/live-inject.mjs --port PORT
|
||||
```
|
||||
|
||||
Use the `port` from the live-server startup output. The script writes the tag idempotently: if a stale tag is present, it's replaced with one pointing at the new port. Save is automatic.
|
||||
|
||||
If browser automation tools are available, also navigate to the page so the user can see it.
|
||||
Then re-run `node {{scripts_path}}/live.mjs` to proceed.
|
||||
|
||||
## Enter the Poll Loop
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* CLI entry point: prepare everything needed to enter the live variant poll loop.
|
||||
*
|
||||
* Does (all in one command):
|
||||
* 1. Check config.json (returns config_missing if first-ever run)
|
||||
* 2. Start the live server in the background (or reuse a running one)
|
||||
* 3. Inject the browser script tag into the project's entry file
|
||||
* 4. Read .impeccable.md for design context (if present)
|
||||
* 5. Print a single JSON blob with everything the agent needs
|
||||
*
|
||||
* After this, the agent's only remaining steps are:
|
||||
* - Navigate the browser to the page (optional, if browser automation is available)
|
||||
* - Enter the poll loop: `node live-poll.mjs`
|
||||
*
|
||||
* Usage:
|
||||
* node live.mjs # Prepare everything, print JSON, exit
|
||||
* node live.mjs --help
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const PID_FILE = path.join(process.cwd(), '.impeccable-live.json');
|
||||
const CONTEXT_FILE = path.join(process.cwd(), '.impeccable.md');
|
||||
|
||||
async function liveCli() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log(`Usage: node live.mjs
|
||||
|
||||
Prepare everything for live variant mode in a single command:
|
||||
- Checks scripts/config.json (required, created once per project)
|
||||
- Starts (or reuses) the live server in the background
|
||||
- Injects the browser script tag
|
||||
- Reads .impeccable.md for design context
|
||||
|
||||
On success, prints a JSON blob with:
|
||||
{ ok, serverPort, serverToken, pageFile, hasContext, context }
|
||||
|
||||
On config_missing, prints:
|
||||
{ ok: false, error: "config_missing", configPath, hint }
|
||||
|
||||
The agent should then:
|
||||
1. If config_missing, create the config and re-run this script
|
||||
2. Optionally navigate the browser to the page
|
||||
3. Enter the poll loop: node live-poll.mjs`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 1. Check config (fail fast if missing — no point starting anything else)
|
||||
const checkOut = runScript('live-inject.mjs', ['--check']);
|
||||
const checkResult = safeParse(checkOut);
|
||||
if (!checkResult || !checkResult.ok) {
|
||||
console.log(JSON.stringify(checkResult || { ok: false, error: 'check_failed', raw: checkOut }));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 2. Start server (or reuse existing)
|
||||
const serverInfo = ensureServerRunning();
|
||||
if (!serverInfo) {
|
||||
console.log(JSON.stringify({ ok: false, error: 'server_start_failed' }));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 3. Inject the script tag at the current port
|
||||
const injectOut = runScript('live-inject.mjs', ['--port', String(serverInfo.port)]);
|
||||
const injectResult = safeParse(injectOut);
|
||||
if (!injectResult || !injectResult.ok) {
|
||||
console.log(JSON.stringify({
|
||||
ok: false,
|
||||
error: 'inject_failed',
|
||||
detail: injectResult || injectOut,
|
||||
serverPort: serverInfo.port,
|
||||
}));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 4. Load design context if available
|
||||
let context = null;
|
||||
try { context = fs.readFileSync(CONTEXT_FILE, 'utf-8'); } catch { /* optional */ }
|
||||
|
||||
// 5. Emit everything the agent needs
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
serverPort: serverInfo.port,
|
||||
serverToken: serverInfo.token,
|
||||
pageFile: checkResult.config.file,
|
||||
hasContext: !!context,
|
||||
context,
|
||||
}, null, 2));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function runScript(name, args) {
|
||||
const scriptPath = path.join(__dirname, name);
|
||||
const cmd = `node "${scriptPath}" ${args.map(a => `"${a}"`).join(' ')}`;
|
||||
try {
|
||||
return execSync(cmd, { encoding: 'utf-8', cwd: process.cwd(), timeout: 15_000 });
|
||||
} catch (err) {
|
||||
// execSync throws on non-zero exit; return stdout if any
|
||||
return err.stdout || err.message || '';
|
||||
}
|
||||
}
|
||||
|
||||
function safeParse(out) {
|
||||
try { return JSON.parse(String(out).trim()); } catch { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Return { pid, port, token } for the running live server, starting one if needed.
|
||||
*/
|
||||
function ensureServerRunning() {
|
||||
// Try to reuse an existing server
|
||||
try {
|
||||
const existing = JSON.parse(fs.readFileSync(PID_FILE, 'utf-8'));
|
||||
if (existing && existing.pid) {
|
||||
try {
|
||||
process.kill(existing.pid, 0); // throws if dead
|
||||
return existing;
|
||||
} catch { /* stale PID file — the server script will clean it up */ }
|
||||
}
|
||||
} catch { /* no PID file */ }
|
||||
|
||||
// Start a new server
|
||||
const out = runScript('live-server.mjs', ['--background']);
|
||||
return safeParse(out);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Auto-execute
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const _running = process.argv[1];
|
||||
if (_running?.endsWith('live.mjs') || _running?.endsWith('live.mjs/')) {
|
||||
liveCli();
|
||||
}
|
||||
Reference in New Issue
Block a user