instagui 0.1.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/dist/cli/index.js +187 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/core/bundled.js +29 -0
  6. package/dist/core/bundled.js.map +1 -0
  7. package/dist/core/cache.js +41 -0
  8. package/dist/core/cache.js.map +1 -0
  9. package/dist/core/capture.js +161 -0
  10. package/dist/core/capture.js.map +1 -0
  11. package/dist/core/compose.js +66 -0
  12. package/dist/core/compose.js.map +1 -0
  13. package/dist/core/errors.js +40 -0
  14. package/dist/core/errors.js.map +1 -0
  15. package/dist/core/extract.js +78 -0
  16. package/dist/core/extract.js.map +1 -0
  17. package/dist/core/golden.js +44 -0
  18. package/dist/core/golden.js.map +1 -0
  19. package/dist/core/onboarding.js +19 -0
  20. package/dist/core/onboarding.js.map +1 -0
  21. package/dist/core/override.js +26 -0
  22. package/dist/core/override.js.map +1 -0
  23. package/dist/core/resolve.js +22 -0
  24. package/dist/core/resolve.js.map +1 -0
  25. package/dist/core/schema-file.js +34 -0
  26. package/dist/core/schema-file.js.map +1 -0
  27. package/dist/core/schema.js +58 -0
  28. package/dist/core/schema.js.map +1 -0
  29. package/dist/server/browser.js +38 -0
  30. package/dist/server/browser.js.map +1 -0
  31. package/dist/server/client.js +146 -0
  32. package/dist/server/client.js.map +1 -0
  33. package/dist/server/page.js +172 -0
  34. package/dist/server/page.js.map +1 -0
  35. package/dist/server/run.js +71 -0
  36. package/dist/server/run.js.map +1 -0
  37. package/dist/server/server.js +205 -0
  38. package/dist/server/server.js.map +1 -0
  39. package/dist/shared/claude-code.js +89 -0
  40. package/dist/shared/claude-code.js.map +1 -0
  41. package/dist/shared/claude.js +33 -0
  42. package/dist/shared/claude.js.map +1 -0
  43. package/dist/shared/config.js +17 -0
  44. package/dist/shared/config.js.map +1 -0
  45. package/dist/shared/engine.js +17 -0
  46. package/dist/shared/engine.js.map +1 -0
  47. package/package.json +61 -0
  48. package/schemas/README.md +32 -0
  49. package/schemas/ffmpeg.json +564 -0
  50. package/schemas/pandoc.json +277 -0
  51. package/schemas/yt-dlp.json +446 -0
@@ -0,0 +1,66 @@
1
+ /** Pick the canonical flag token from a help-style flag field, e.g.
2
+ * "-c, --codec" → "-c", "--output" → "--output", "-c/--codec" → "-c". First listed wins
3
+ * (that is what the tool's help shows first). */
4
+ export function firstFlag(flag) {
5
+ const tokens = flag.split(/[\s,|/]+/).filter((t) => t.length > 0);
6
+ const dashed = tokens.find((t) => t.startsWith('-'));
7
+ return dashed ?? tokens[0] ?? flag;
8
+ }
9
+ /** Non-empty string value → contributes an argument. Empty string / undefined / null → not.
10
+ * (Booleans are handled separately.) */
11
+ function asValue(v) {
12
+ if (v === undefined || v === null || typeof v === 'boolean')
13
+ return null;
14
+ const s = typeof v === 'string' ? v : String(v);
15
+ return s.length === 0 ? null : s;
16
+ }
17
+ function pushOption(args, o, raw) {
18
+ if (o.type === 'boolean') {
19
+ // A checked box contributes the bare flag; unchecked/absent contributes nothing.
20
+ if (raw === true || raw === 'true')
21
+ args.push(firstFlag(o.flag));
22
+ return;
23
+ }
24
+ const value = asValue(raw);
25
+ if (value === null)
26
+ return; // empty/default → no argument
27
+ args.push(firstFlag(o.flag), value);
28
+ }
29
+ /**
30
+ * Compose the argument array (excluding the tool name) from `state`, honoring Schema order:
31
+ * options first, then positionals. Empty/default fields contribute nothing.
32
+ */
33
+ export function composeArgs(schema, state) {
34
+ const args = [];
35
+ const options = state.options ?? {};
36
+ const positionals = state.positionals ?? {};
37
+ for (const o of schema.options) {
38
+ pushOption(args, o, options[o.name]);
39
+ }
40
+ for (const p of schema.positionals) {
41
+ const value = asValue(positionals[p.name]);
42
+ if (value !== null)
43
+ args.push(value);
44
+ }
45
+ return args;
46
+ }
47
+ /** Wrap an argument for display only when it contains characters that would be ambiguous
48
+ * unquoted. POSIX single-quote style; purely illustrative (Run uses the array, not this). */
49
+ function quoteForDisplay(arg) {
50
+ if (arg === '')
51
+ return "''";
52
+ if (/^[A-Za-z0-9_./:=@%+-]+$/.test(arg))
53
+ return arg;
54
+ return "'" + arg.replace(/'/g, "'\\''") + "'";
55
+ }
56
+ /** Render the composed command as a readable string: `tool arg1 arg2 …`. Derived from the
57
+ * SAME array `composeArgs` returns, so preview never drifts from execution. */
58
+ export function previewString(tool, args) {
59
+ return [tool, ...args.map(quoteForDisplay)].join(' ');
60
+ }
61
+ /** Convenience: the arg array and its preview string in one call, from one composition. */
62
+ export function compose(schema, state) {
63
+ const args = composeArgs(schema, state);
64
+ return { args, preview: previewString(schema.tool, args) };
65
+ }
66
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ // Exit-code contract (spine): 0 ok · 2 known precondition failure (distinct messages) ·
2
+ // 1 unexpected. Core throws typed errors; the CLI maps them to process exit codes so the
3
+ // core stays testable and free of process.exit.
4
+ /** A known, user-facing precondition failure → exit code 2. Message is safe to print
5
+ * as-is (no stack trace). */
6
+ export class PreconditionError extends Error {
7
+ exitCode = 2;
8
+ /** Path to a written debug artifact, when the failure produced one. */
9
+ debugFile;
10
+ constructor(message, debugFile) {
11
+ super(message);
12
+ this.name = 'PreconditionError';
13
+ this.debugFile = debugFile;
14
+ }
15
+ }
16
+ // Capture failures — distinct types so Story 1.3 can route distinct messages/exit codes.
17
+ // All are precondition failures (exit 2); messages here are serviceable and refined in 1.3.
18
+ /** The named tool is not found on PATH. */
19
+ export class ToolNotFoundError extends PreconditionError {
20
+ tool;
21
+ constructor(tool) {
22
+ super(`Tool not found: "${tool}" is not on your PATH. Check the name and that it is installed.`);
23
+ this.tool = tool;
24
+ this.name = 'ToolNotFoundError';
25
+ }
26
+ }
27
+ /** The tool ran but produced no usable help text through any fallback. */
28
+ export class NoHelpError extends PreconditionError {
29
+ tool;
30
+ detail;
31
+ constructor(tool, detail) {
32
+ super(`No help output: "${tool}" produced no usable help via --help, -h, help, or man` +
33
+ (detail ? ` (${detail})` : '') +
34
+ `. Pass --help-file <path> or pipe help text on stdin.`);
35
+ this.tool = tool;
36
+ this.detail = detail;
37
+ this.name = 'NoHelpError';
38
+ }
39
+ }
40
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,yFAAyF;AACzF,gDAAgD;AAEhD;8BAC8B;AAC9B,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,QAAQ,GAAG,CAAC,CAAC;IACtB,uEAAuE;IAC9D,SAAS,CAAU;IAE5B,YAAY,OAAe,EAAE,SAAkB;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED,yFAAyF;AACzF,4FAA4F;AAE5F,2CAA2C;AAC3C,MAAM,OAAO,iBAAkB,SAAQ,iBAAiB;IAC1B;IAA5B,YAA4B,IAAY;QACtC,KAAK,CAAC,oBAAoB,IAAI,iEAAiE,CAAC,CAAC;QADvE,SAAI,GAAJ,IAAI,CAAQ;QAEtC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,0EAA0E;AAC1E,MAAM,OAAO,WAAY,SAAQ,iBAAiB;IAE9B;IACA;IAFlB,YACkB,IAAY,EACZ,MAAe;QAE/B,KAAK,CACH,oBAAoB,IAAI,wDAAwD;YAC9E,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9B,uDAAuD,CAC1D,CAAC;QAPc,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAS;QAO/B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF"}
@@ -0,0 +1,78 @@
1
+ // core/extract.ts — the AI bet, isolated. Help text in → validated Schema out.
2
+ // Flow: build prompt → ask Claude for JSON matching the Schema → Schema.parse().
3
+ // On malformed output: exactly one retry, then write the raw output to a debug file and
4
+ // fail with a clear precondition error (exit 2). No server, no UI here.
5
+ import { writeFileSync } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { Schema } from './schema.js';
8
+ import { PreconditionError } from './errors.js';
9
+ import { resolveComplete } from '../shared/engine.js';
10
+ /** Locked decision: extraction model. */
11
+ export const DEFAULT_MODEL = 'claude-haiku-4-5';
12
+ const SYSTEM_PROMPT = `You extract a machine-usable description of a command-line tool's interface from its --help text, matching the provided JSON schema exactly.
13
+
14
+ Rules — follow all of them:
15
+ 1. GROUND EVERY FLAG IN THE HELP. Only include options whose flag appears verbatim in the help text. Never invent, guess, complete, or "improve" a flag. If you are unsure a flag exists, omit it. A hallucinated flag is worse than a missing one.
16
+ 2. THE TOOL IS FLAT — no subcommands. If the help lists sub-commands, verbs, or usage examples (e.g. "tool build", "tool run"), do NOT model them as a tree and do NOT invent per-subcommand options. Extract only the tool's own top-level options and positional arguments.
17
+ 3. SHORT-ONLY FLAGS COUNT. If an option has only a short form (e.g. "-x" with no long form), capture it exactly. Never drop an option for lacking a "--long" form. Put the flag text exactly as written in "flag" (include every form shown, e.g. "-c, --codec").
18
+ 4. HUGE HELP: if the tool exposes hundreds of options (codec/format dumps, etc.), extract the generally useful options a person would set from the main option sections. You may omit exhaustive rare entries. Coverage of the common, task-relevant options matters more than completeness.
19
+ 5. POSITIONALS ARE FIRST-CLASS. Capture positional arguments (input/output files, etc.) in "positionals", not as flags. Infer "required" and "variadic" from the usage line ("<output>" is required; "[file...]" is variadic and optional).
20
+ 6. TYPES: use "boolean" for switches that take no value; "enum" when the help lists a fixed set of allowed values (put them in "enumValues"); "number" for numeric values; "path" for file/directory/path values; "string" otherwise. "enumValues" is [] unless the type is "enum".
21
+ 7. "group" is the help section header the option sits under (e.g. "Video options"); "" if none. "description" is a concise one-line description from the help, or "" if none.
22
+ 8. "tool" must echo the tool name given by the user exactly. "summary" is a one-line description of the tool if the help states one, else "".`;
23
+ export function buildUserPrompt(helpText, tool) {
24
+ return `Tool: ${tool}\n\nHelp text:\n"""\n${helpText}\n"""`;
25
+ }
26
+ /**
27
+ * Extract a validated Schema from help text. Exactly one retry on malformed output; on a
28
+ * second failure the raw model output is written to a debug file (path carried on the
29
+ * thrown PreconditionError) and never discarded.
30
+ */
31
+ export async function extractSchema(helpText, tool, opts = {}) {
32
+ const completeFn = opts.complete ?? resolveComplete();
33
+ const req = {
34
+ model: opts.model ?? DEFAULT_MODEL,
35
+ system: SYSTEM_PROMPT,
36
+ user: buildUserPrompt(helpText, tool),
37
+ outputSchema: Schema,
38
+ maxTokens: opts.maxTokens,
39
+ };
40
+ const maxAttempts = 2; // initial + exactly one retry
41
+ let lastRaw = '';
42
+ let lastReason = '';
43
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
44
+ lastRaw = await completeFn(req, opts.client);
45
+ const outcome = validate(lastRaw);
46
+ if (outcome.ok) {
47
+ return { schema: outcome.schema, attempts: attempt };
48
+ }
49
+ lastReason = outcome.reason;
50
+ }
51
+ // Both attempts failed — persist the invalid output for prompt tuning, then fail clearly.
52
+ const debugFile = writeDebugFile(lastRaw, tool, lastReason, opts);
53
+ throw new PreconditionError(`Extraction failed: the model did not return a valid Schema for "${tool}" after one retry (${lastReason}). ` +
54
+ `The invalid output was saved to ${debugFile} for inspection.`, debugFile);
55
+ }
56
+ function validate(raw) {
57
+ let json;
58
+ try {
59
+ json = JSON.parse(raw);
60
+ }
61
+ catch (e) {
62
+ return { ok: false, reason: `response was not valid JSON: ${e.message}` };
63
+ }
64
+ const parsed = Schema.safeParse(json);
65
+ if (!parsed.success) {
66
+ return { ok: false, reason: `response did not match the Schema shape: ${parsed.error.message}` };
67
+ }
68
+ return { ok: true, schema: parsed.data };
69
+ }
70
+ function writeDebugFile(raw, tool, reason, opts) {
71
+ const dir = opts.debugDir ?? process.cwd();
72
+ const ts = opts.now ?? Date.now();
73
+ const file = path.join(dir, `instagui-debug-${tool.replace(/[^\w.-]/g, '_')}-${ts}.json`);
74
+ const body = JSON.stringify({ tool, reason, rawOutput: raw }, null, 2);
75
+ writeFileSync(file, body, 'utf8');
76
+ return file;
77
+ }
78
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/core/extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /** Pull the individual flag tokens out of a `flag` field like "-c, --codec" or
2
+ * "-c/--codec" → ["-c", "--codec"]. Only tokens that start with "-" count. */
3
+ export function flagTokens(flag) {
4
+ return flag
5
+ .split(/[\s,/|]+/)
6
+ .map((t) => t.trim())
7
+ .filter((t) => t.startsWith('-'));
8
+ }
9
+ /**
10
+ * Return every flag token in the Schema that does NOT appear verbatim in the help text.
11
+ * A non-empty result means the model invented flags — a hallucination, and a failed
12
+ * extraction regardless of JSON validity.
13
+ */
14
+ export function findHallucinatedFlags(schema, helpText) {
15
+ const hallucinated = [];
16
+ for (const opt of schema.options) {
17
+ for (const token of flagTokens(opt.flag)) {
18
+ if (!helpText.includes(token))
19
+ hallucinated.push(token);
20
+ }
21
+ }
22
+ return [...new Set(hallucinated)];
23
+ }
24
+ /**
25
+ * Verify that every option a demo task needs is present with the correct flag and type.
26
+ * Used by the per-tool golden tests; a failure here is a failed extraction even when the
27
+ * Schema parses.
28
+ */
29
+ export function goldenCheck(schema, required) {
30
+ const missing = [];
31
+ const typeMismatches = [];
32
+ for (const need of required) {
33
+ const match = schema.options.find((opt) => flagTokens(opt.flag).includes(need.flag));
34
+ if (!match) {
35
+ missing.push(need.flag);
36
+ continue;
37
+ }
38
+ if (match.type !== need.type) {
39
+ typeMismatches.push(`${need.flag}: expected ${need.type}, got ${match.type}`);
40
+ }
41
+ }
42
+ return { ok: missing.length === 0 && typeMismatches.length === 0, missing, typeMismatches };
43
+ }
44
+ //# sourceMappingURL=golden.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"golden.js","sourceRoot":"","sources":["../../src/core/golden.ts"],"names":[],"mappings":"AAMA;+EAC+E;AAC/E,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI;SACR,KAAK,CAAC,UAAU,CAAC;SACjB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,QAAgB;IACpE,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACjC,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;AACpC,CAAC;AAkBD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,QAA0B;IACpE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACrF,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;YAC7B,cAAc,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,cAAc,IAAI,CAAC,IAAI,SAAS,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC9F,CAAC"}
@@ -0,0 +1,19 @@
1
+ // Story 2.4 — friendly onboarding when a key is genuinely needed. This error is thrown ONLY
2
+ // on the extraction tier, after override/cache/bundled have all missed: a user who can be
3
+ // served without a key (demo tools, a cache hit, or --schema) never sees it.
4
+ //
5
+ // The message is intentionally kind and actionable — what to get, where, and how to set it
6
+ // on both Windows and POSIX — never a stack trace or a silent failure.
7
+ import { PreconditionError } from './errors.js';
8
+ import { API_KEY_ENV } from '../shared/config.js';
9
+ /** The one-time, key-needed onboarding error (exit 2). Never prints or reads the key. */
10
+ export function apiKeyOnboardingError() {
11
+ return new PreconditionError(`This tool needs a one-time AI extraction, and no schema was found in your cache or the ` +
12
+ `bundled demo schemas — so an Anthropic API key is required.\n` +
13
+ ` 1. Get a key: https://console.anthropic.com/settings/keys\n` +
14
+ ` 2. Set it in your shell, then re-run:\n` +
15
+ ` PowerShell: $env:${API_KEY_ENV}="sk-ant-..."\n` +
16
+ ` POSIX: export ${API_KEY_ENV}="sk-ant-..."\n` +
17
+ `Tip: the bundled demo tools (ffmpeg, yt-dlp, pandoc) work with no key at all.`);
18
+ }
19
+ //# sourceMappingURL=onboarding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../../src/core/onboarding.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,0FAA0F;AAC1F,6EAA6E;AAC7E,EAAE;AACF,2FAA2F;AAC3F,uEAAuE;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,yFAAyF;AACzF,MAAM,UAAU,qBAAqB;IACnC,OAAO,IAAI,iBAAiB,CAC1B,yFAAyF;QACvF,+DAA+D;QAC/D,gEAAgE;QAChE,2CAA2C;QAC3C,4BAA4B,WAAW,iBAAiB;QACxD,8BAA8B,WAAW,iBAAiB;QAC1D,+EAA+E,CAClF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ // Story 2.2 — the `--schema <file>` override. A power user supplies their own hand-tuned
2
+ // Schema; instagui uses it directly with NO tool-help invocation and NO API call. It sits at
3
+ // the top of the resolution precedence (override > cache > bundled > extraction).
4
+ //
5
+ // Unlike the cache/bundled tiers, a bad override is a user-facing error, not a silent
6
+ // fall-through: the user named this file explicitly, so a clear, reason-specific message
7
+ // (exit code 2) is the right response.
8
+ import { PreconditionError } from './errors.js';
9
+ import { readSchemaFile } from './schema-file.js';
10
+ /**
11
+ * Load and validate a `--schema` override file. Returns the parsed Schema, or throws a
12
+ * PreconditionError (exit 2) whose message points at exactly what is wrong.
13
+ */
14
+ export function loadOverrideSchema(file) {
15
+ const result = readSchemaFile(file);
16
+ if (result.ok)
17
+ return result.schema;
18
+ const messages = {
19
+ missing: `--schema file not found: ${file}`,
20
+ unreadable: `--schema file could not be read: ${file} (${result.detail})`,
21
+ 'invalid-json': `--schema file is not valid JSON: ${file} (${result.detail})`,
22
+ 'invalid-schema': `--schema file does not match the instagui Schema: ${file}\n${result.detail}`,
23
+ };
24
+ throw new PreconditionError(messages[result.reason]);
25
+ }
26
+ //# sourceMappingURL=override.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"override.js","sourceRoot":"","sources":["../../src/core/override.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,6FAA6F;AAC7F,kFAAkF;AAClF,EAAE;AACF,sFAAsF;AACtF,yFAAyF;AACzF,uCAAuC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC;IAEpC,MAAM,QAAQ,GAAyC;QACrD,OAAO,EAAE,4BAA4B,IAAI,EAAE;QAC3C,UAAU,EAAE,oCAAoC,IAAI,KAAK,MAAM,CAAC,MAAM,GAAG;QACzE,cAAc,EAAE,oCAAoC,IAAI,KAAK,MAAM,CAAC,MAAM,GAAG;QAC7E,gBAAgB,EAAE,qDAAqD,IAAI,KAAK,MAAM,CAAC,MAAM,EAAE;KAChG,CAAC;IACF,MAAM,IAAI,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Resolve a Schema for `input.tool` by the fixed precedence above. `--schema` short-circuits
3
+ * before any capture or cache read; `--refresh` skips the two free lookup tiers so the
4
+ * result comes from a fresh extraction and overwrites the user cache.
5
+ */
6
+ export async function resolveSchema(input, deps) {
7
+ if (input.schemaFile) {
8
+ return { schema: deps.loadOverride(input.schemaFile), source: 'override' };
9
+ }
10
+ if (!input.refresh) {
11
+ const cached = deps.readCache(input.tool);
12
+ if (cached)
13
+ return { schema: cached, source: 'cache' };
14
+ const bundled = deps.readBundled(input.tool);
15
+ if (bundled)
16
+ return { schema: bundled, source: 'bundled' };
17
+ }
18
+ const schema = await deps.extract();
19
+ const cachedTo = deps.writeCache(input.tool, schema);
20
+ return { schema, source: 'extraction', cachedTo };
21
+ }
22
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/core/resolve.ts"],"names":[],"mappings":"AA2CA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAmB,EAAE,IAAiB;IACxE,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACrB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC7E,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,MAAM;YAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAEvD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,OAAO;YAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC7D,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;AACpD,CAAC"}
@@ -0,0 +1,34 @@
1
+ // Shared schema-file reader used by all three non-extraction resolution tiers
2
+ // (Story 2.1 cache, 2.2 --schema override, 2.3 bundled). One place decides how a Schema
3
+ // JSON file is read and validated; each caller decides what to do with a failure:
4
+ // • cache / bundled → treat any failure as "not available", fall through the chain
5
+ // • --schema override → surface the specific reason as a friendly PreconditionError
6
+ import { readFileSync } from 'node:fs';
7
+ import { Schema } from './schema.js';
8
+ /**
9
+ * Read and validate a Schema JSON file. Never throws — the outcome is returned so each
10
+ * tier can choose its own failure behavior (silent fall-through vs. a friendly error).
11
+ */
12
+ export function readSchemaFile(file) {
13
+ let raw;
14
+ try {
15
+ raw = readFileSync(file, 'utf8');
16
+ }
17
+ catch (e) {
18
+ const err = e;
19
+ return { ok: false, reason: err.code === 'ENOENT' ? 'missing' : 'unreadable', detail: err.message };
20
+ }
21
+ let json;
22
+ try {
23
+ json = JSON.parse(raw);
24
+ }
25
+ catch (e) {
26
+ return { ok: false, reason: 'invalid-json', detail: e.message };
27
+ }
28
+ const parsed = Schema.safeParse(json);
29
+ if (!parsed.success) {
30
+ return { ok: false, reason: 'invalid-schema', detail: parsed.error.message };
31
+ }
32
+ return { ok: true, schema: parsed.data };
33
+ }
34
+ //# sourceMappingURL=schema-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-file.js","sourceRoot":"","sources":["../../src/core/schema-file.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wFAAwF;AACxF,kFAAkF;AAClF,sFAAsF;AACtF,sFAAsF;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAUrC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAA0B,CAAC;QACvC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;IACtG,CAAC;IACD,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAG,CAAW,CAAC,OAAO,EAAE,CAAC;IAC7E,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC/E,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,58 @@
1
+ // AD-4 — THE Schema contract. One Zod object, three consumers:
2
+ // • zodOutputFormat(Schema) → constrains the extraction API call (shared/claude.ts)
3
+ // • z.infer<typeof Schema> → the TS type used everywhere downstream
4
+ // • Schema.parse(value) → runtime validation of extraction output, cache reads,
5
+ // --schema overrides, and bundled schemas
6
+ //
7
+ // Authored with `zod/v4` on purpose: @anthropic-ai/sdk's zodOutputFormat binds to the
8
+ // zod v4 API (zod ≥3.25 ships it at the `zod/v4` subpath). Importing plain `zod` (v3)
9
+ // here would type-mismatch against zodOutputFormat.
10
+ import { z } from 'zod/v4';
11
+ /**
12
+ * The control kind an option maps to in the Form. `path` is a string field in v1
13
+ * (no native picker — scope fence), kept distinct from `string` so the UI can hint.
14
+ */
15
+ export const OptionType = z.enum(['string', 'number', 'boolean', 'enum', 'path']);
16
+ /**
17
+ * A flag-style option. Every field is required by design: structured-output JSON schema
18
+ * mode is happiest with all-required + additionalProperties:false, and an explicit empty
19
+ * value ("" / [] / false) is less ambiguous to the model than an omitted key.
20
+ */
21
+ export const Option = z.object({
22
+ /** Canonical identifier, e.g. "codec" or "verbose". Stable key for form + compose. */
23
+ name: z.string(),
24
+ /** The literal flag as it appears in help, e.g. "--codec", "-c", "-c/--codec". */
25
+ flag: z.string(),
26
+ type: OptionType,
27
+ /** One-line description; "" if the help gives none. */
28
+ description: z.string(),
29
+ /** Allowed values when type === "enum"; [] otherwise. */
30
+ enumValues: z.array(z.string()),
31
+ /** Whether the tool requires this option. */
32
+ required: z.boolean(),
33
+ /** Help-text section this belongs to, e.g. "Video options"; "" if ungrouped. */
34
+ group: z.string(),
35
+ });
36
+ /**
37
+ * A positional argument (e.g. ffmpeg input/output files). The money demo does not work
38
+ * without these — they are first-class, not folded into flags.
39
+ */
40
+ export const Positional = z.object({
41
+ name: z.string(),
42
+ type: OptionType,
43
+ description: z.string(),
44
+ required: z.boolean(),
45
+ /** True if the positional accepts multiple values (e.g. "files..."). */
46
+ variadic: z.boolean(),
47
+ });
48
+ /** The single structured description of a Tool's interface — the contract between
49
+ * capture, UI, and execution. */
50
+ export const Schema = z.object({
51
+ /** The tool binary name, echoed from the request. */
52
+ tool: z.string(),
53
+ /** One-line summary of what the tool does; "" if unknown. */
54
+ summary: z.string(),
55
+ options: z.array(Option),
56
+ positionals: z.array(Positional),
57
+ });
58
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,uFAAuF;AACvF,wEAAwE;AACxE,uFAAuF;AACvF,0EAA0E;AAC1E,EAAE;AACF,sFAAsF;AACtF,sFAAsF;AACtF,oDAAoD;AACpD,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAGlF;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,sFAAsF;IACtF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,kFAAkF;IAClF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,UAAU;IAChB,uDAAuD;IACvD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,yDAAyD;IACzD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,gFAAgF;IAChF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;CAClB,CAAC,CAAC;AAGH;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,UAAU;IAChB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,wEAAwE;IACxE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;CACtB,CAAC,CAAC;AAGH;kCACkC;AAClC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,qDAAqD;IACrD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,6DAA6D;IAC7D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;IACxB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;CACjC,CAAC,CAAC"}
@@ -0,0 +1,38 @@
1
+ // server/browser.ts — auto-open the served Form in the default browser (AC 3.1). The URL is
2
+ // always printed by the CLI too, so this is a convenience: a failure here is non-fatal.
3
+ //
4
+ // spawn is injected so tests can assert the platform-correct command WITHOUT launching a
5
+ // browser. Uses spawn with an args array (never a shell string) even for the opener.
6
+ import { spawn } from 'node:child_process';
7
+ /**
8
+ * The platform-appropriate command to open `url` in the default browser.
9
+ * win32 → cmd /c start "" <url> (empty title arg so a quoted URL isn't taken as the title)
10
+ * darwin → open <url>
11
+ * else → xdg-open <url>
12
+ * The URL is passed as a single argument — no shell interpolation.
13
+ */
14
+ export function openCommand(url, platform) {
15
+ if (platform === 'win32')
16
+ return { cmd: 'cmd', args: ['/c', 'start', '', url] };
17
+ if (platform === 'darwin')
18
+ return { cmd: 'open', args: [url] };
19
+ return { cmd: 'xdg-open', args: [url] };
20
+ }
21
+ /** Best-effort: launch the browser, swallowing any failure (the printed URL is the fallback). */
22
+ export function openBrowser(url, deps = {}) {
23
+ const platform = deps.platform ?? process.platform;
24
+ const spawnFn = deps.spawnFn ?? spawn;
25
+ const { cmd, args } = openCommand(url, platform);
26
+ try {
27
+ const child = spawnFn(cmd, args, { stdio: 'ignore', detached: false, windowsHide: true });
28
+ child.on('error', () => {
29
+ /* browser open is best-effort; the URL was printed */
30
+ });
31
+ child.unref?.();
32
+ }
33
+ catch {
34
+ /* ignore — non-fatal */
35
+ }
36
+ return { cmd, args };
37
+ }
38
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/server/browser.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,wFAAwF;AACxF,EAAE;AACF,yFAAyF;AACzF,qFAAqF;AACrF,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAS3C;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,QAAyB;IAChE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;IAChF,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/D,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AAC1C,CAAC;AAOD,iGAAiG;AACjG,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,OAAwB,EAAE;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;IACtC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1F,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,sDAAsD;QACxD,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,146 @@
1
+ // server/client.ts — the browser-side script embedded in the served page (as a string, so
2
+ // the build stays a plain `tsc` with no bundler). It never composes commands itself: it
3
+ // collects form state and asks the server (POST /preview, POST /run) so core/compose.ts is
4
+ // the single source of truth. Run output arrives over SSE (/events); closing that stream is
5
+ // what the server watches to kill an orphaned child (AD-5).
6
+ //
7
+ // Authored as a template string rather than real TS because it runs in the browser, not Node.
8
+ // Kept intentionally small and framework-free (vanilla DOM).
9
+ export const CLIENT_SCRIPT = String.raw `
10
+ (function () {
11
+ var form = document.getElementById('form');
12
+ var previewEl = document.getElementById('preview');
13
+ var statusEl = document.getElementById('status');
14
+ var copyBtn = document.getElementById('copy');
15
+ var runBtn = document.getElementById('run');
16
+ var stopBtn = document.getElementById('stop');
17
+ var outputEl = document.getElementById('output');
18
+ var streamEl = document.getElementById('stream');
19
+ var exitEl = document.getElementById('exit');
20
+
21
+ var es = null;
22
+
23
+ // Collect { options, positionals } from every [data-kind] control. Booleans are
24
+ // true/false; everything else is the raw string value (compose decides emptiness).
25
+ function collectState() {
26
+ var options = {}, positionals = {};
27
+ var els = form.querySelectorAll('[data-kind]');
28
+ for (var i = 0; i < els.length; i++) {
29
+ var el = els[i];
30
+ var name = el.getAttribute('data-name');
31
+ var kind = el.getAttribute('data-kind');
32
+ var value = el.type === 'checkbox' ? el.checked : el.value;
33
+ if (kind === 'option') options[name] = value; else positionals[name] = value;
34
+ }
35
+ return { options: options, positionals: positionals };
36
+ }
37
+
38
+ var previewTimer = null;
39
+ function schedulePreview() {
40
+ if (previewTimer) clearTimeout(previewTimer);
41
+ previewTimer = setTimeout(updatePreview, 60);
42
+ }
43
+
44
+ function updatePreview() {
45
+ fetch('/preview', {
46
+ method: 'POST',
47
+ headers: { 'content-type': 'application/json' },
48
+ body: JSON.stringify(collectState()),
49
+ })
50
+ .then(function (r) { return r.ok ? r.json() : null; })
51
+ .then(function (data) { if (data && typeof data.preview === 'string') previewEl.textContent = data.preview; })
52
+ .catch(function () { /* preview is best-effort; leave the last good value */ });
53
+ }
54
+
55
+ form.addEventListener('input', schedulePreview);
56
+ form.addEventListener('change', schedulePreview);
57
+
58
+ copyBtn.addEventListener('click', function () {
59
+ var text = previewEl.textContent || '';
60
+ var done = function () { statusEl.textContent = 'Copied.'; setTimeout(function () { statusEl.textContent = ''; }, 1500); };
61
+ if (navigator.clipboard && navigator.clipboard.writeText) {
62
+ navigator.clipboard.writeText(text).then(done, fallbackCopy);
63
+ } else fallbackCopy();
64
+ function fallbackCopy() {
65
+ var ta = document.createElement('textarea');
66
+ ta.value = text; document.body.appendChild(ta); ta.select();
67
+ try { document.execCommand('copy'); done(); } catch (e) {}
68
+ document.body.removeChild(ta);
69
+ }
70
+ });
71
+
72
+ function setRunning(running) {
73
+ runBtn.disabled = running;
74
+ stopBtn.disabled = !running;
75
+ form.querySelectorAll('[data-kind]').forEach(function (el) { el.disabled = running; });
76
+ }
77
+
78
+ function closeStream() { if (es) { es.close(); es = null; } }
79
+
80
+ runBtn.addEventListener('click', function () {
81
+ outputEl.hidden = false;
82
+ streamEl.textContent = '';
83
+ exitEl.textContent = '';
84
+ exitEl.className = 'exit';
85
+ statusEl.textContent = 'Starting…';
86
+ setRunning(true);
87
+
88
+ var state = collectState();
89
+ closeStream();
90
+ es = new EventSource('/events');
91
+
92
+ es.addEventListener('out', function (ev) {
93
+ streamEl.textContent += ev.data === '' ? '\n' : JSON.parse(ev.data);
94
+ streamEl.scrollTop = streamEl.scrollHeight;
95
+ });
96
+ es.addEventListener('exit', function (ev) {
97
+ var payload = JSON.parse(ev.data);
98
+ var code = payload.code;
99
+ if (payload.signal) exitEl.textContent = 'Stopped (' + payload.signal + ')';
100
+ else if (code === null) exitEl.textContent = 'Command failed to start'; // e.g. missing binary
101
+ else exitEl.textContent = 'Exit code: ' + code;
102
+ // Anything but a clean 0 is styled as an error (red, bold) — including a failed start.
103
+ exitEl.className = 'exit ' + (code === 0 ? 'ok' : 'bad');
104
+ statusEl.textContent = '';
105
+ setRunning(false);
106
+ closeStream();
107
+ });
108
+ es.onopen = function () {
109
+ statusEl.textContent = 'Running…';
110
+ fetch('/run', {
111
+ method: 'POST',
112
+ headers: { 'content-type': 'application/json' },
113
+ body: JSON.stringify(state),
114
+ }).then(function (r) {
115
+ if (!r.ok) {
116
+ return r.text().then(function (t) {
117
+ statusEl.textContent = 'Run rejected: ' + (t || r.status);
118
+ setRunning(false);
119
+ closeStream();
120
+ });
121
+ }
122
+ }).catch(function () {
123
+ statusEl.textContent = 'Run failed to start.';
124
+ setRunning(false);
125
+ closeStream();
126
+ });
127
+ };
128
+ es.onerror = function () {
129
+ // A stream error after exit is expected (server closed it); only surface if still running.
130
+ if (es && runBtn.disabled && !stopBtn.disabled) {
131
+ statusEl.textContent = 'Connection lost.';
132
+ setRunning(false);
133
+ closeStream();
134
+ }
135
+ };
136
+ });
137
+
138
+ stopBtn.addEventListener('click', function () {
139
+ statusEl.textContent = 'Stopping…';
140
+ fetch('/stop', { method: 'POST' }).catch(function () {});
141
+ });
142
+
143
+ updatePreview();
144
+ })();
145
+ `;
146
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/server/client.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,wFAAwF;AACxF,2FAA2F;AAC3F,4FAA4F;AAC5F,4DAA4D;AAC5D,EAAE;AACF,8FAA8F;AAC9F,6DAA6D;AAE7D,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwItC,CAAC"}