stylegallery 0.1.4 → 0.1.7
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/DOMAINS.md +11 -9
- package/GOVERNANCE.md +8 -2
- package/LICENSE +28 -0
- package/LICENSE-DOCS +42 -0
- package/NOTICE +38 -0
- package/README.md +43 -6
- package/consumer-reference/agent-native/README.md +7 -7
- package/consumer-reference/agent-native/v2/admission-policy.json +241 -1
- package/consumer-reference/agent-native/v2/material-registry.json +1 -1
- package/consumer-reference/agent-native/v2/schema/admission-policy.schema.json +2 -2
- package/design-engineering/component-contract.md +114 -0
- package/design-engineering/decision-tree.md +70 -0
- package/design-engineering/index.md +60 -2
- package/design-engineering/loading-and-feedback.md +91 -0
- package/design-engineering/native-interaction-contracts.md +94 -0
- package/design-engineering/state-management/decision-tree.md +60 -0
- package/design-engineering/state-management/index.md +61 -0
- package/design-engineering/state-management/patterns/derived-state.md +85 -0
- package/design-engineering/state-management/patterns/draft-and-baseline.md +86 -0
- package/design-engineering/state-management/patterns/id-selection.md +85 -0
- package/design-engineering/state-management/patterns/identity-reset.md +89 -0
- package/design-engineering/state-management/patterns/latest-request-wins.md +94 -0
- package/design-engineering/state-management/patterns/optimistic-overlay.md +98 -0
- package/design-engineering/state-management/patterns/single-flight.md +92 -0
- package/design-engineering/state-management/patterns/single-owner.md +89 -0
- package/design-engineering/state-management/patterns/submitted-snapshot.md +107 -0
- package/design-engineering/state-management/patterns/unsaved-navigation.md +100 -0
- package/design-engineering/state-management/patterns/url-state.md +96 -0
- package/design-engineering/state-management/patterns/versioned-restore.md +95 -0
- package/design-engineering/state-management/recipes/delete-undo.md +65 -0
- package/design-engineering/state-management/recipes/index.md +64 -0
- package/design-engineering/state-management/recipes/multi-step-form.md +65 -0
- package/design-engineering/state-management/recipes/search-detail.md +65 -0
- package/design-engineering/state-management/recipes/settings-save.md +64 -0
- package/design-engineering/state-management/state-brief.md +83 -0
- package/design-engineering/state-management/verification.md +65 -0
- package/design-engineering/text-input-and-internationalization.md +91 -0
- package/design-engineering/worked-examples.md +129 -0
- package/design-terminology/comparison-workflow.md +98 -0
- package/design-terminology/conflict-cases.md +87 -55
- package/design-terminology/index.md +19 -5
- package/design-terminology/interaction-semantics.md +87 -0
- package/design-terminology/relation-types.md +12 -6
- package/design-terminology/source-vocabularies.md +33 -3
- package/design-terminology/token-semantics.md +90 -0
- package/design-terminology/typography-semantics.md +86 -0
- package/game-ui/decision-tree.md +81 -0
- package/game-ui/index.md +11 -1
- package/game-ui/screen-recipes.md +101 -0
- package/game-ui/verification-workflow.md +100 -0
- package/index.md +8 -0
- package/log.md +67 -0
- package/motion/accessible-motion.md +90 -0
- package/motion/decision-tree.md +70 -0
- package/motion/index.md +21 -2
- package/motion/interaction-recipes.md +170 -0
- package/motion/interruption-and-retargeting.md +90 -0
- package/motion/motion-brief.md +125 -0
- package/motion/observed-choreography.md +73 -0
- package/motion/rendering-and-performance.md +89 -0
- package/motion/review-workflow.md +8 -0
- package/motion/vocabulary.md +2 -2
- package/package.json +74 -8
- package/platform-guides/adaptation-workflow.md +76 -0
- package/platform-guides/adaptive-navigation.md +85 -0
- package/platform-guides/android-interaction.md +74 -0
- package/platform-guides/apple-interaction.md +9 -1
- package/platform-guides/compatibility-matrix.md +97 -0
- package/platform-guides/index.md +19 -2
- package/platform-guides/input-and-focus.md +91 -0
- package/platform-guides/preferences-and-accessibility.md +90 -0
- package/platform-guides/windows-interaction.md +75 -0
- package/recipes/homepage.md +8 -0
- package/scripts/compiler/README.md +112 -0
- package/scripts/compiler/cli.mjs +51 -0
- package/scripts/compiler/mcp.mjs +53 -0
- package/scripts/compiler/operations.mjs +70 -0
- package/scripts/compiler/runtime.mjs +156 -0
- package/scripts/sg-entry.mjs +20 -0
- package/scripts/sg-server.mjs +20 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { createStyleGalleryMcpServer } from "../agent-native/mcp-adapter.mjs";
|
|
5
|
+
import { agentNativeRegistry } from "../agent-native/registry.mjs";
|
|
6
|
+
import { compilerOperations, compilerToolDefinitions } from "./operations.mjs";
|
|
7
|
+
import { withCompilerDiscovery } from "./cli.mjs";
|
|
8
|
+
import { executeCompiler, failure } from "./runtime.mjs";
|
|
9
|
+
|
|
10
|
+
// Compose at the transport boundary. The byte-frozen v1 server, registry,
|
|
11
|
+
// conformance receipts, and resources remain independently usable.
|
|
12
|
+
export function createSgMcpServer(config = {}) {
|
|
13
|
+
const server = createStyleGalleryMcpServer();
|
|
14
|
+
const lifetime = new AbortController();
|
|
15
|
+
server.server.onclose = () => lifetime.abort();
|
|
16
|
+
const legacy = agentNativeRegistry.operations.filter(({ read_only }) => read_only);
|
|
17
|
+
const legacyByName = new Map(legacy.map((operation) => [operation.name, operation]));
|
|
18
|
+
const legacyTools = legacy.map((operation) => {
|
|
19
|
+
const inputSchema = structuredClone(operation.input_schema);
|
|
20
|
+
if (Object.hasOwn(inputSchema.properties ?? {}, "reference")) {
|
|
21
|
+
inputSchema.properties.stable_ref = structuredClone(inputSchema.properties.reference);
|
|
22
|
+
inputSchema.required = (inputSchema.required ?? []).filter((key) => key !== "reference");
|
|
23
|
+
inputSchema.anyOf = [{ required: ["reference"] }, { required: ["stable_ref"] }];
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
name: operation.name, description: operation.description, inputSchema,
|
|
27
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
server.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
31
|
+
tools: [...legacyTools, ...compilerToolDefinitions()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
|
|
32
|
+
}));
|
|
33
|
+
const callSchema = CallToolRequestSchema.extend({
|
|
34
|
+
params: CallToolRequestSchema.shape.params.extend({ arguments: z.unknown().optional() }),
|
|
35
|
+
});
|
|
36
|
+
server.server.setRequestHandler(callSchema, async (request, extra) => {
|
|
37
|
+
const operation = request.params.name;
|
|
38
|
+
let input = request.params.arguments ?? {};
|
|
39
|
+
let report;
|
|
40
|
+
if (Object.hasOwn(compilerOperations, operation) || operation === "workflow") {
|
|
41
|
+
const signals = [lifetime.signal, extra.signal, config.signal].filter(Boolean);
|
|
42
|
+
report = await executeCompiler(operation, input, { ...config, signal: AbortSignal.any(signals) });
|
|
43
|
+
} else if (legacyByName.has(operation)) {
|
|
44
|
+
if (input && typeof input === "object" && !Array.isArray(input) && Object.hasOwn(input, "stable_ref")) {
|
|
45
|
+
input = { ...input, reference: input.reference ?? input.stable_ref };
|
|
46
|
+
delete input.stable_ref;
|
|
47
|
+
}
|
|
48
|
+
report = withCompilerDiscovery(agentNativeRegistry.invoke(operation, input), config);
|
|
49
|
+
} else report = failure(operation, "operation_not_exposed", `Operation ${operation} is not exposed.`);
|
|
50
|
+
return { content: [{ type: "text", text: JSON.stringify(report) }], structuredContent: report, isError: report.ok !== true };
|
|
51
|
+
});
|
|
52
|
+
return server;
|
|
53
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const upstream = Object.freeze({
|
|
4
|
+
repository: "https://github.com/changeroa/site-compiler",
|
|
5
|
+
verified_revision: "371849141ba68d12d578c804cc53fe7267c899b0",
|
|
6
|
+
verified_at: "2026-09-22",
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const text = z.string().min(1).refine((value) => !value.includes("\0"), "must not contain NUL");
|
|
10
|
+
const file = text.describe("Filesystem path; relative paths resolve from the SG caller's working directory.");
|
|
11
|
+
const positive = z.number().int().positive();
|
|
12
|
+
const url = z.url().refine((value) => ["http:", "https:"].includes(new URL(value).protocol), "use an HTTP or HTTPS URL");
|
|
13
|
+
const optional = (shape) => Object.fromEntries(Object.entries(shape).map(([key, value]) => [key, value.optional()]));
|
|
14
|
+
const viewport = { width: positive.max(16384), height: positive.max(16384), wait: z.number().int().min(0).max(60000) };
|
|
15
|
+
const capture = { ...viewport, step: positive, quality: positive.max(100), grow: z.boolean() };
|
|
16
|
+
const batch = { only: text, force: z.boolean(), parallel: positive.max(32), timeout_ms: positive };
|
|
17
|
+
const model = { ...batch, model: text, timeout_ms: positive, stylegallery: file };
|
|
18
|
+
const operation = (script, description, required, options = {}, paths = [], extra = {}) => ({
|
|
19
|
+
script, description, paths,
|
|
20
|
+
schema: z.strictObject({ ...required, ...optional(options), runner_timeout_ms: positive.max(21600000).optional() }),
|
|
21
|
+
...extra,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// This registry owns both public transports. Upstream scripts remain in the
|
|
25
|
+
// separately installed checkout and are never admitted as gallery material.
|
|
26
|
+
export const compilerOperations = Object.freeze({
|
|
27
|
+
compile: operation("compile.mjs", "Capture a live website as scenes.json, scene frames, and mounted text. Writes to out; does not transcribe the frames.",
|
|
28
|
+
{ url, out: file }, { ...viewport, substep: positive }, ["out"]),
|
|
29
|
+
timeline: operation("timeline.mjs", "Capture dense scroll frames and a pixel-difference curve. Writes timeline-curve.json and dense/ to out.",
|
|
30
|
+
{ url, out: file }, { ...capture, max_height: positive }, ["out"]),
|
|
31
|
+
transcribe: operation("transcribe.mjs", "Transcribe captured packs into SceneBook and Timeline documents using the installed Claude CLI. May incur model usage. Inspect transcribe.json for per-pack outcomes.",
|
|
32
|
+
{ in: file }, { ...model, image_budget: positive, phase: z.enum(["both", "scenebook", "timeline"]) }, ["in", "stylegallery"], { ledger: "transcribe.json" }),
|
|
33
|
+
sale: operation("sale-edition.mjs", "Generate a sale edition and a private copy audit from transcribed documents. Does not publish or establish rights clearance.",
|
|
34
|
+
{ in: file, out: file }, { audit: file }, ["in", "out", "audit"]),
|
|
35
|
+
gate: operation("qa-gate.mjs", "Check capture/transcription consistency and optionally a sale edition. A failed gate returns a nonzero exit code; json optionally writes the report.",
|
|
36
|
+
{ in: file }, { sale: file, json: file, max_gap_ratio: z.number().min(0).max(1), max_gaps_per_scene: z.number().int().min(0), static_score: z.number().min(0), high_score: z.number().min(0) }, ["in", "sale", "json"]),
|
|
37
|
+
build: operation("build.mjs", "Build package previews from sale editions with original-page and external-media access restricted. Not the faithful-reconstruction route. Uses the installed Claude CLI and may incur model usage. Inspect build.json for per-pack outcomes.",
|
|
38
|
+
{ packs: file, out: file }, model, ["packs", "out", "stylegallery"], { ledger: "build.json" }),
|
|
39
|
+
shots: operation("shots.mjs", "Render built sites into desktop/mobile screenshots and a reduced-motion report under each build's shots/ directory.",
|
|
40
|
+
{ slug: text, builds: file }, { steps: positive.max(1000) }, ["builds"]),
|
|
41
|
+
probe: operation("probe-scroll.mjs", "Inspect website scroll geometry with Chromium; chrome selects installed Chrome. Returns the upstream diagnostic log.",
|
|
42
|
+
{ url }, { chrome: z.boolean() }, [], { positional: ["url"] }),
|
|
43
|
+
"batch-triage": operation("batch-triage.mjs", "Capture candidate sites for triage. Inspect runs.json for individual failures even when the process exits successfully.",
|
|
44
|
+
{ candidates: file, out: file }, { ...batch, step: positive, wait: viewport.wait, quality: capture.quality, grow: z.boolean() }, ["candidates", "out"], { ledger: "runs.json" }),
|
|
45
|
+
triage: operation("triage.mjs", "Rank captured scroll curves and write a triage report. This is heuristic ranking, not product verification.",
|
|
46
|
+
{ in: file }, { json: file, md: file, min_height: positive, pin_score: z.number().min(0), spike_score: z.number().min(0) }, ["in", "json", "md"]),
|
|
47
|
+
"batch-capture": operation("batch-capture.mjs", "Capture scenes and timelines for candidate sites. Inspect capture.json for per-pack outcomes.",
|
|
48
|
+
{ candidates: file, out: file }, { ...batch, step: positive, quality: capture.quality }, ["candidates", "out"], { ledger: "capture.json" }),
|
|
49
|
+
"pack-status": operation("pack-status.mjs", "Update pack status, generating sale editions and running gates as needed. Writes status reports into in.",
|
|
50
|
+
{ candidates: file, in: file }, { force: z.boolean() }, ["candidates", "in"]),
|
|
51
|
+
"pack-release": operation("pack-release.mjs", "Package products from the compiler checkout's out/packs and out/builds into out/release. Replaces matching release folders and ZIP files locally; does not upload or publish.",
|
|
52
|
+
{ products: file }, { only: text }, ["products"]),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const workflowSchema = z.strictObject({ document: z.enum(["workflow", "readme"]).default("workflow") });
|
|
56
|
+
|
|
57
|
+
export function compilerToolDefinitions() {
|
|
58
|
+
return Object.entries(compilerOperations).map(([name, operation]) => {
|
|
59
|
+
const { $schema, ...inputSchema } = z.toJSONSchema(operation.schema);
|
|
60
|
+
return {
|
|
61
|
+
name, description: operation.description, inputSchema,
|
|
62
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
63
|
+
};
|
|
64
|
+
}).concat({
|
|
65
|
+
name: "workflow",
|
|
66
|
+
description: "Read the installed compiler's current workflow or README with revision and content hash, plus the SG component and motion contract routes.",
|
|
67
|
+
inputSchema: { type: "object", properties: { document: { type: "string", enum: ["workflow", "readme"], default: "workflow" } }, additionalProperties: false },
|
|
68
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { compilerOperations, compilerToolDefinitions, upstream, workflowSchema } from "./operations.mjs";
|
|
8
|
+
|
|
9
|
+
const galleryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
10
|
+
const logLimit = 65536;
|
|
11
|
+
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
12
|
+
const handoff = { consumer_reference: "consumer-reference/agent-native/registry.json" };
|
|
13
|
+
|
|
14
|
+
export function failure(operation, code, message) {
|
|
15
|
+
return { ok: false, operation, failures: [{ code, message }] };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function compilerLocation(config) {
|
|
19
|
+
const configured = config.compilerRoot ?? process.env.SG_COMPILER_ROOT;
|
|
20
|
+
if (!configured) throw Object.assign(new Error("Set SG_COMPILER_ROOT to your site-compiler checkout, or pass --compiler-root to the CLI."), { code: "compiler_not_configured" });
|
|
21
|
+
const root = fs.realpathSync(path.resolve(config.cwd ?? process.cwd(), configured));
|
|
22
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
23
|
+
if (manifest.name !== "site-compiler") throw Object.assign(new Error("SG_COMPILER_ROOT must contain the site-compiler package."), { code: "compiler_invalid" });
|
|
24
|
+
const revision = spawnSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8", timeout: 5000 });
|
|
25
|
+
const gitRoot = spawnSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf8", timeout: 5000 });
|
|
26
|
+
const isCheckout = gitRoot.status === 0 && fs.realpathSync(gitRoot.stdout.trim()) === root;
|
|
27
|
+
const status = isCheckout ? spawnSync("git", ["-C", root, "status", "--porcelain", "--untracked-files=no"], { encoding: "utf8", timeout: 5000 }) : null;
|
|
28
|
+
const actualRevision = isCheckout && revision.status === 0 ? revision.stdout.trim() : null;
|
|
29
|
+
return {
|
|
30
|
+
root, package_version: manifest.version, revision: actualRevision,
|
|
31
|
+
tracked_changes: status?.status === 0 ? status.stdout.length > 0 : null,
|
|
32
|
+
matches_verified_revision: actualRevision === upstream.verified_revision && status?.status === 0 && status.stdout.length === 0,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function compilerDiscovery(config = {}) {
|
|
37
|
+
let installation;
|
|
38
|
+
try { installation = { configured: true, ...compilerLocation(config) }; }
|
|
39
|
+
catch (error) { installation = { configured: false, code: error.code ?? "compiler_unavailable", message: error.message }; }
|
|
40
|
+
return {
|
|
41
|
+
...upstream, installation,
|
|
42
|
+
operations: compilerToolDefinitions(),
|
|
43
|
+
contracts: ["design-engineering/component-contract.md", "motion/observed-choreography.md"],
|
|
44
|
+
...handoff,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function checkedFile(root, name) {
|
|
49
|
+
const target = path.join(root, name);
|
|
50
|
+
if (fs.realpathSync(target) !== target || !fs.statSync(target).isFile()) {
|
|
51
|
+
throw Object.assign(new Error(`Compiler entry must be a regular file within its checkout: ${name}`), { code: "compiler_entry_invalid" });
|
|
52
|
+
}
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runProcess(script, args, { cwd, timeout, signal }) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
if (signal?.aborted) return resolve({ exit_code: null, signal: null, cancelled: true, timed_out: false, stdout: "", stderr: "" });
|
|
59
|
+
const child = spawn(process.execPath, [script, ...args], {
|
|
60
|
+
cwd, shell: false, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"],
|
|
61
|
+
});
|
|
62
|
+
const logs = { stdout: Buffer.alloc(0), stderr: Buffer.alloc(0) };
|
|
63
|
+
const truncated = { stdout: false, stderr: false };
|
|
64
|
+
let timedOut = false;
|
|
65
|
+
let cancelled = false;
|
|
66
|
+
let spawnError;
|
|
67
|
+
let forceTimer;
|
|
68
|
+
const kill = (signalName) => {
|
|
69
|
+
try {
|
|
70
|
+
if (process.platform === "win32" && child.pid) {
|
|
71
|
+
spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 5000 });
|
|
72
|
+
}
|
|
73
|
+
else if (child.pid) process.kill(-child.pid, signalName);
|
|
74
|
+
} catch (error) { if (error.code !== "ESRCH") spawnError ??= error.message; }
|
|
75
|
+
};
|
|
76
|
+
const stop = () => {
|
|
77
|
+
kill("SIGTERM");
|
|
78
|
+
forceTimer ??= setTimeout(() => kill("SIGKILL"), 1000);
|
|
79
|
+
forceTimer.unref();
|
|
80
|
+
};
|
|
81
|
+
const abort = () => { cancelled = true; stop(); };
|
|
82
|
+
const timer = setTimeout(() => { timedOut = true; stop(); }, timeout);
|
|
83
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
84
|
+
if (signal?.aborted) abort();
|
|
85
|
+
for (const channel of ["stdout", "stderr"]) child[channel].on("data", (chunk) => {
|
|
86
|
+
const combined = Buffer.concat([logs[channel], chunk]);
|
|
87
|
+
truncated[channel] ||= combined.length > logLimit;
|
|
88
|
+
logs[channel] = combined.subarray(Math.max(0, combined.length - logLimit));
|
|
89
|
+
});
|
|
90
|
+
child.on("error", (error) => { spawnError = error.message; });
|
|
91
|
+
child.on("close", (exitCode, exitSignal) => {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
if (forceTimer) { kill("SIGKILL"); clearTimeout(forceTimer); }
|
|
94
|
+
signal?.removeEventListener("abort", abort);
|
|
95
|
+
resolve({
|
|
96
|
+
exit_code: exitCode, signal: exitSignal, timed_out: timedOut, cancelled,
|
|
97
|
+
stdout: logs.stdout.toString("utf8"), stderr: logs.stderr.toString("utf8"),
|
|
98
|
+
stdout_truncated: truncated.stdout, stderr_truncated: truncated.stderr,
|
|
99
|
+
...(spawnError ? { spawn_error: spawnError } : {}),
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function executeCompiler(operation, input = {}, config = {}) {
|
|
106
|
+
const spec = Object.hasOwn(compilerOperations, operation) ? compilerOperations[operation] : null;
|
|
107
|
+
if (!spec && operation !== "workflow") return failure(operation, "command_unknown", `Unknown compiler command: ${operation}`);
|
|
108
|
+
const parsed = (operation === "workflow" ? workflowSchema : spec.schema).safeParse(input);
|
|
109
|
+
if (!parsed.success) return failure(operation, "input_invalid", parsed.error.message);
|
|
110
|
+
try {
|
|
111
|
+
const compiler = compilerLocation(config);
|
|
112
|
+
if (operation === "workflow") {
|
|
113
|
+
const name = parsed.data.document === "readme" ? "README.md" : "SKILL.md";
|
|
114
|
+
const bytes = fs.readFileSync(checkedFile(compiler.root, name));
|
|
115
|
+
return { ok: true, operation, result: { compiler, source: name, sha256: hash(bytes), text: bytes.toString("utf8"), contracts: compilerDiscovery(config).contracts, ...handoff } };
|
|
116
|
+
}
|
|
117
|
+
const script = checkedFile(compiler.root, spec.script);
|
|
118
|
+
const scriptHash = hash(fs.readFileSync(script));
|
|
119
|
+
const values = { ...parsed.data };
|
|
120
|
+
const timeout = values.runner_timeout_ms ?? 1800000;
|
|
121
|
+
delete values.runner_timeout_ms;
|
|
122
|
+
if (operation === "transcribe" || operation === "build") values.stylegallery ??= galleryRoot;
|
|
123
|
+
for (const key of spec.paths) if (values[key] !== undefined) values[key] = path.resolve(config.cwd ?? process.cwd(), values[key]);
|
|
124
|
+
if (operation === "sale") {
|
|
125
|
+
if (values.in === values.out) return failure(operation, "input_invalid", "sale requires a separate output directory.");
|
|
126
|
+
values.audit ??= path.join(path.dirname(values.out), "copy-audit.private.json");
|
|
127
|
+
const relative = path.relative(values.out, values.audit);
|
|
128
|
+
if (relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative))) {
|
|
129
|
+
return failure(operation, "input_invalid", "The private copy audit must be outside the sale directory.");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const args = [];
|
|
133
|
+
for (const key of spec.positional ?? []) args.push(values[key]);
|
|
134
|
+
for (const [key, value] of Object.entries(values)) {
|
|
135
|
+
if (spec.positional?.includes(key) || value === false) continue;
|
|
136
|
+
args.push(`--${key.replaceAll("_", "-")}`);
|
|
137
|
+
if (value !== true) args.push(String(value));
|
|
138
|
+
}
|
|
139
|
+
const execution = await runProcess(script, args, { cwd: compiler.root, timeout, signal: config.signal });
|
|
140
|
+
const ok = execution.exit_code === 0 && !execution.timed_out && !execution.cancelled && !execution.spawn_error;
|
|
141
|
+
const result = {
|
|
142
|
+
compiler, script: spec.script, script_sha256: scriptHash,
|
|
143
|
+
arguments: args, paths: Object.fromEntries(spec.paths.filter((key) => values[key] !== undefined).map((key) => [key, values[key]])),
|
|
144
|
+
...execution, ...handoff,
|
|
145
|
+
...(spec.ledger ? { ledger: path.join(values.out ?? values.in, spec.ledger), success_scope: "process_exit_only; inspect ledger for each pack" } : {}),
|
|
146
|
+
...(operation === "pack-release" ? { output: path.join(compiler.root, "out/release") } : {}),
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
ok, operation, result,
|
|
150
|
+
...(!ok ? { failures: [{
|
|
151
|
+
code: execution.cancelled ? "compiler_cancelled" : execution.timed_out ? "compiler_timeout" : "compiler_failed",
|
|
152
|
+
message: execution.spawn_error ?? (execution.cancelled ? "Compiler execution cancelled." : execution.timed_out ? `Compiler exceeded ${timeout}ms.` : `Compiler exited with ${execution.exit_code ?? execution.signal}.`),
|
|
153
|
+
}] } : {}),
|
|
154
|
+
};
|
|
155
|
+
} catch (error) { return failure(operation, error.code ?? "compiler_unavailable", error.message); }
|
|
156
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
try {
|
|
4
|
+
const { runSgCli } = await import("./compiler/cli.mjs");
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const cancel = () => controller.abort();
|
|
7
|
+
process.once("SIGINT", cancel);
|
|
8
|
+
process.once("SIGTERM", cancel);
|
|
9
|
+
try {
|
|
10
|
+
const result = await runSgCli(process.argv.slice(2), { signal: controller.signal });
|
|
11
|
+
process.stdout.write(result.output);
|
|
12
|
+
process.exitCode = result.exitCode;
|
|
13
|
+
} finally {
|
|
14
|
+
process.removeListener("SIGINT", cancel);
|
|
15
|
+
process.removeListener("SIGTERM", cancel);
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
process.stdout.write(`${JSON.stringify({ ok: false, operation: null, failures: [{ code: error.code ?? "cli_failed", message: error.message }] }, null, 2)}\n`);
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createSgMcpServer } from "./compiler/mcp.mjs";
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const server = createSgMcpServer({ signal: controller.signal });
|
|
9
|
+
const shutdown = () => {
|
|
10
|
+
controller.abort();
|
|
11
|
+
server.close().catch(() => {});
|
|
12
|
+
};
|
|
13
|
+
process.once("SIGINT", shutdown);
|
|
14
|
+
process.once("SIGTERM", shutdown);
|
|
15
|
+
process.stdin.once("end", shutdown);
|
|
16
|
+
await server.connect(new StdioServerTransport());
|
|
17
|
+
} catch (error) {
|
|
18
|
+
process.stderr.write(`${error.message}\n`);
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
}
|