@penpair/core 0.1.2 → 0.2.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/dist/adapters.d.ts +16 -1
- package/dist/adapters.js +8 -12
- package/dist/approvals.d.ts +32 -0
- package/dist/approvals.js +99 -0
- package/dist/atomic-output.d.ts +5 -0
- package/dist/atomic-output.js +28 -0
- package/dist/audit-rules.d.ts +60 -0
- package/dist/audit-rules.js +177 -0
- package/dist/backup-note.d.ts +4 -0
- package/dist/backup-note.js +28 -0
- package/dist/baseline-coverage.d.ts +25 -0
- package/dist/baseline-coverage.js +114 -0
- package/dist/baselines-config.d.ts +25 -0
- package/dist/baselines-config.js +52 -3
- package/dist/baselines.d.ts +37 -8
- package/dist/baselines.js +132 -52
- package/dist/cli-arguments.d.ts +7 -0
- package/dist/cli-arguments.js +35 -0
- package/dist/cli-main.d.ts +13 -1
- package/dist/cli-main.js +770 -139
- package/dist/client.d.ts +26 -5
- package/dist/client.js +46 -20
- package/dist/command-result.d.ts +159 -0
- package/dist/command-result.js +179 -0
- package/dist/coordination.d.ts +15 -0
- package/dist/coordination.js +122 -0
- package/dist/credentials.d.ts +21 -0
- package/dist/credentials.js +61 -0
- package/dist/deep-check.js +9 -5
- package/dist/design-spec.d.ts +55 -0
- package/dist/design-spec.js +135 -0
- package/dist/endpoint.d.ts +8 -1
- package/dist/endpoint.js +48 -4
- package/dist/errors.d.ts +2 -1
- package/dist/errors.js +8 -4
- package/dist/export.d.ts +18 -0
- package/dist/export.js +42 -7
- package/dist/file-archive.d.ts +16 -0
- package/dist/file-archive.js +32 -0
- package/dist/file-guard.d.ts +8 -0
- package/dist/file-guard.js +40 -0
- package/dist/formats.d.ts +4 -0
- package/dist/formats.js +28 -0
- package/dist/index.d.ts +33 -1
- package/dist/index.js +33 -1
- package/dist/inspection.d.ts +106 -0
- package/dist/inspection.js +344 -0
- package/dist/instance/config-schema.d.ts +422 -0
- package/dist/instance/config-schema.js +76 -0
- package/dist/instance/config.d.ts +29 -0
- package/dist/instance/config.js +102 -11
- package/dist/instance/connect.d.ts +10 -1
- package/dist/instance/connect.js +53 -12
- package/dist/instance/connection.d.ts +14 -0
- package/dist/instance/connection.js +13 -2
- package/dist/instance/init.js +1 -1
- package/dist/instance/setup.js +1 -1
- package/dist/instance/tty.js +3 -1
- package/dist/journal.d.ts +77 -0
- package/dist/journal.js +260 -0
- package/dist/kit-smoke.d.ts +31 -0
- package/dist/kit-smoke.js +83 -0
- package/dist/kit-source.d.ts +2 -0
- package/dist/kit-source.js +309 -0
- package/dist/kit.d.ts +25 -0
- package/dist/kit.js +90 -0
- package/dist/layout.d.ts +15 -0
- package/dist/layout.js +79 -0
- package/dist/live-smoke.d.ts +8 -0
- package/dist/live-smoke.js +14 -0
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.js +102 -0
- package/dist/native-api.d.ts +15 -0
- package/dist/native-api.js +42 -0
- package/dist/onboard.d.ts +10 -0
- package/dist/onboard.js +15 -0
- package/dist/operation.d.ts +20 -1
- package/dist/operation.js +113 -13
- package/dist/png.d.ts +5 -0
- package/dist/png.js +7 -0
- package/dist/portable-tokens.d.ts +22 -0
- package/dist/portable-tokens.js +134 -0
- package/dist/probe.d.ts +6 -2
- package/dist/probe.js +19 -3
- package/dist/project-input.d.ts +6 -0
- package/dist/project-input.js +51 -0
- package/dist/project-snippets.js +12 -7
- package/dist/project-status.d.ts +24 -0
- package/dist/project-status.js +79 -0
- package/dist/registry.d.ts +2 -1
- package/dist/registry.js +17 -3
- package/dist/run-file.d.ts +26 -0
- package/dist/run-file.js +100 -0
- package/dist/shots.d.ts +43 -0
- package/dist/shots.js +90 -0
- package/dist/skill-instructions.d.ts +13 -0
- package/dist/skill-instructions.js +75 -0
- package/dist/skills.d.ts +11 -0
- package/dist/skills.js +71 -33
- package/dist/snippet.d.ts +3 -2
- package/dist/snippet.js +12 -8
- package/dist/snippets/archive.d.ts +9 -0
- package/dist/snippets/archive.js +10 -4
- package/dist/snippets/read.d.ts +1 -1
- package/dist/snippets/read.js +71 -40
- package/dist/structure-snapshot.d.ts +73 -0
- package/dist/structure-snapshot.js +171 -0
- package/dist/token-edit.d.ts +53 -0
- package/dist/token-edit.js +197 -0
- package/dist/tokens-diff.d.ts +19 -6
- package/dist/tokens-diff.js +89 -18
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/penpair-design/SKILL.md +12 -40
- package/skills/penpair-design/references/penpot-plugin-gotchas.md +56 -0
- package/skills/penpair-design/references/project-facts.md +1 -1
- package/skills/penpair-design/references/write-protocol.md +15 -3
- package/skills/penpair-handoff/SKILL.md +14 -0
- package/skills/penpair-handoff/references/handoff-flow.md +11 -0
- package/skills/penpair-recipes/SKILL.md +16 -0
- package/skills/penpair-recipes/references/build-flow.md +11 -0
- package/skills/penpair-setup/SKILL.md +6 -2
package/dist/export.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { waitForStableLayout } from "./layout.js";
|
|
2
|
+
import { PenpairError } from "./errors.js";
|
|
3
|
+
import { commandSleep } from "./command-result.js";
|
|
1
4
|
import { createHash } from "node:crypto";
|
|
2
5
|
import { Buffer } from "node:buffer";
|
|
3
6
|
import { snippet, requirePage } from "./snippet.js";
|
|
@@ -5,18 +8,41 @@ import { runOnPage } from "./operation.js";
|
|
|
5
8
|
const exportSnippet = (shapeId, scale, page) => snippet("export", `${requirePage}
|
|
6
9
|
const s = penpot.currentPage.getShapeById(CFG.id);
|
|
7
10
|
if (!s) return { ok: false, message: "no shape " + CFG.id + " on " + penpot.currentPage.name };
|
|
11
|
+
const active = (penpot.library?.local?.tokens?.themes || []).filter(t=>t.active);
|
|
12
|
+
const capture = {designSize:{width:s.width,height:s.height},activeThemeIds:active.every(t=>typeof t.id==="string"&&t.id)?active.map(t=>t.id).sort():null,fileId:penpot.currentFile.id, pageId:penpot.currentPage.id, frameId:s.id, revision:penpot.currentFile.revn, scale:CFG.scale,
|
|
13
|
+
penpotApiVersion:typeof penpot.version === "string" ? penpot.version : null,
|
|
14
|
+
renderEnvironment:typeof navigator !== "undefined" ? navigator.userAgent : null,
|
|
15
|
+
activeThemes:(penpot.library?.local?.tokens?.themes || []).filter(t=>t.active).map(t=>t.group+"/"+t.name).sort()};
|
|
16
|
+
const geometry = JSON.stringify([s.x,s.y,s.width,s.height]);
|
|
8
17
|
const bytes = await s.export({ type: "png", scale: CFG.scale });
|
|
18
|
+
const afterShape = penpot.currentPage.getShapeById(CFG.id);
|
|
19
|
+
if (penpot.currentFile.id !== capture.fileId || penpot.currentPage.id !== capture.pageId || penpot.currentFile.revn !== capture.revision || !afterShape || afterShape.id !== capture.frameId || JSON.stringify([afterShape.x,afterShape.y,afterShape.width,afterShape.height]) !== geometry)
|
|
20
|
+
return {ok:false, message:"Export source changed during capture"};
|
|
9
21
|
let bin = ""; for (let i = 0; i < bytes.length; i += 8192) bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192));
|
|
10
|
-
return { ok: true, base64: btoa(bin), length: bytes.length };`, { id: shapeId, scale, page });
|
|
11
|
-
const defaultSleep =
|
|
22
|
+
return { ok: true, base64: btoa(bin), length: bytes.length, capture };`, { id: shapeId, scale, page }, "read");
|
|
23
|
+
const defaultSleep = commandSleep;
|
|
12
24
|
/** Exports one shape as PNG. A first call can fail once and then work; this retries, and by default waits for a stable render. */
|
|
13
25
|
export async function exportShapePng(client, shapeId, options = {}) {
|
|
14
26
|
const { page, scale = 1, attempts = 3, stable = true, maxExports = 4, sleep = defaultSleep } = options;
|
|
27
|
+
if (!Number.isFinite(scale) || scale <= 0 || scale > 8 || !Number.isInteger(attempts) || attempts < 1 || attempts > 10 || !Number.isInteger(maxExports) || maxExports < (stable ? 2 : 1) || maxExports > 20)
|
|
28
|
+
throw new Error("Invalid export scale/attempt limits");
|
|
15
29
|
const once = async () => {
|
|
16
30
|
for (let attempt = 1;; attempt++) {
|
|
17
31
|
try {
|
|
18
32
|
const record = await runOnPage(client, exportSnippet(shapeId, scale, page), page, options);
|
|
19
|
-
|
|
33
|
+
const capture = record.capture;
|
|
34
|
+
if (!capture || typeof capture.fileId !== "string" || !capture.fileId || typeof capture.pageId !== "string" || !capture.pageId || capture.frameId !== shapeId || !Number.isInteger(capture.revision) || capture.scale !== scale || !Array.isArray(capture.activeThemes))
|
|
35
|
+
throw new Error("Export did not return target provenance");
|
|
36
|
+
if (capture.designSize && (![capture.designSize.width, capture.designSize.height].every(value => Number.isFinite(value) && value >= 0)))
|
|
37
|
+
throw new Error("Export has malformed design dimensions");
|
|
38
|
+
if (capture.activeThemeIds !== undefined && capture.activeThemeIds !== null && (!Array.isArray(capture.activeThemeIds) || capture.activeThemeIds.some(id => typeof id !== "string" || !id) || new Set(capture.activeThemeIds).size !== capture.activeThemeIds.length))
|
|
39
|
+
throw new Error("Export has malformed active theme identity");
|
|
40
|
+
if (typeof record.base64 !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(record.base64))
|
|
41
|
+
throw new Error("Invalid export byte encoding");
|
|
42
|
+
const png = Buffer.from(record.base64, "base64");
|
|
43
|
+
if (!png.length || png.length !== record.length)
|
|
44
|
+
throw new Error("Export byte length does not match its evidence");
|
|
45
|
+
return { png, capture };
|
|
20
46
|
}
|
|
21
47
|
catch (error) {
|
|
22
48
|
if (attempt >= attempts)
|
|
@@ -26,15 +52,24 @@ export async function exportShapePng(client, shapeId, options = {}) {
|
|
|
26
52
|
}
|
|
27
53
|
};
|
|
28
54
|
let previous = await once();
|
|
55
|
+
let layout;
|
|
56
|
+
if (options.layout !== false) {
|
|
57
|
+
layout = await waitForStableLayout(client, { shapeId, sleep, ...options.layout });
|
|
58
|
+
if (!layout.stable)
|
|
59
|
+
throw new PenpairError("incomplete-output", `Layout did not settle before export: ${layout.reason ?? "unknown reason"}`);
|
|
60
|
+
}
|
|
29
61
|
if (!stable)
|
|
30
|
-
return {
|
|
62
|
+
return { layout, ...previous, stable: false, exports: 1 };
|
|
31
63
|
const hash = (buffer) => createHash("sha256").update(buffer).digest("hex");
|
|
32
64
|
for (let count = 2; count <= maxExports; count++) {
|
|
33
65
|
await sleep(2_000);
|
|
34
66
|
const next = await once();
|
|
35
|
-
|
|
36
|
-
|
|
67
|
+
const identity = ({ designSize: _size, ...value }) => value;
|
|
68
|
+
if (JSON.stringify(identity(next.capture)) !== JSON.stringify(identity(previous.capture)))
|
|
69
|
+
throw new Error("Export identity, revision or render environment changed between captures");
|
|
70
|
+
if (JSON.stringify(next.capture.designSize ?? null) === JSON.stringify(previous.capture.designSize ?? null) && hash(next.png) === hash(previous.png))
|
|
71
|
+
return { layout, ...next, stable: true, exports: count };
|
|
37
72
|
previous = next;
|
|
38
73
|
}
|
|
39
|
-
return {
|
|
74
|
+
return { layout, ...previous, stable: false, exports: maxExports };
|
|
40
75
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PenpotToolClient } from "./client.js";
|
|
2
|
+
export type FileArchive = {
|
|
3
|
+
status: "exported";
|
|
4
|
+
bytes: Buffer;
|
|
5
|
+
fileId: string;
|
|
6
|
+
revn: number;
|
|
7
|
+
sha256: string;
|
|
8
|
+
restoreVerified: false;
|
|
9
|
+
} | {
|
|
10
|
+
status: "unsupported" | "failed" | "too-large" | "changed";
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
/** Full-fidelity recovery is established by import/readback, not by receiving archive bytes. */
|
|
14
|
+
export declare function exportFileArchive(client: PenpotToolClient, options?: {
|
|
15
|
+
maxBytes?: number;
|
|
16
|
+
}): Promise<FileArchive>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { runSnippet, snippet } from "./snippet.js";
|
|
3
|
+
import { redactEndpoint } from "./endpoint.js";
|
|
4
|
+
/** Full-fidelity recovery is established by import/readback, not by receiving archive bytes. */
|
|
5
|
+
export async function exportFileArchive(client, options = {}) {
|
|
6
|
+
const maxBytes = options.maxBytes ?? 512 * 1024;
|
|
7
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > 16 * 1024 * 1024)
|
|
8
|
+
throw new Error("Archive budget must be positive and at most 16 MiB");
|
|
9
|
+
const result = await runSnippet(client, snippet("file-archive", `
|
|
10
|
+
const file = penpot.currentFile, fileId = file.id, revn = file.revn;
|
|
11
|
+
if (typeof file.export !== "function") return { ok: true, status: "unsupported", message: "File export is unavailable; use Penpot's File export menu" };
|
|
12
|
+
let bytes;
|
|
13
|
+
try { bytes = await file.export("penpot", "all"); }
|
|
14
|
+
catch (error) { return { ok: true, status: "failed", message: "Archive export failed: " + String(error && error.message || error) + ". Use the manual export route." }; }
|
|
15
|
+
if (penpot.currentFile.id !== fileId || penpot.currentFile.revn !== revn) return { ok: true, status: "changed", message: "The file changed during export; archive was not accepted" };
|
|
16
|
+
if (!bytes || !Number.isSafeInteger(bytes.length) || bytes.length === 0) return { ok: true, status: "failed", message: "File export returned no archive bytes" };
|
|
17
|
+
if (bytes.length > CFG.maxBytes) return { ok: true, status: "too-large", message: "Archive exceeds the configured response budget; export it manually" };
|
|
18
|
+
let binary = "";
|
|
19
|
+
for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192));
|
|
20
|
+
return { ok: true, status: "exported", fileId, revn, length: bytes.length, base64: btoa(binary) };`, { maxBytes }, "read"));
|
|
21
|
+
if (result.status !== "exported") {
|
|
22
|
+
if (!["unsupported", "failed", "too-large", "changed"].includes(String(result.status)) || typeof result.message !== "string")
|
|
23
|
+
throw new Error("Archive response contained no valid outcome");
|
|
24
|
+
return { status: result.status, message: redactEndpoint(result.message) };
|
|
25
|
+
}
|
|
26
|
+
if (result.ok !== true || typeof result.fileId !== "string" || !Number.isInteger(result.revn) || typeof result.base64 !== "string" || !/^[A-Za-z0-9+/]*={0,2}$/.test(result.base64))
|
|
27
|
+
throw new Error("Archive response contained incomplete metadata");
|
|
28
|
+
const bytes = Buffer.from(result.base64, "base64");
|
|
29
|
+
if (bytes.length === 0 || bytes.length > maxBytes || bytes.length !== result.length)
|
|
30
|
+
throw new Error("Archive response was empty, truncated or over budget");
|
|
31
|
+
return { status: "exported", bytes, fileId: result.fileId, revn: Number(result.revn), sha256: createHash("sha256").update(bytes).digest("hex"), restoreVerified: false };
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type PenpotToolClient } from "./client.js";
|
|
2
|
+
import type { ProjectFile } from "./instance/config.js";
|
|
3
|
+
/** A guard in the delivered execution closes the gap between a connection check and a later batch. */
|
|
4
|
+
export declare function guardFile(client: PenpotToolClient, expected: ProjectFile | undefined, options?: {
|
|
5
|
+
mode?: "strict" | "warn";
|
|
6
|
+
anyFile?: boolean;
|
|
7
|
+
warn?: (message: string) => void;
|
|
8
|
+
}): PenpotToolClient;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { executionEnvelope, maxSnippetBytes } from "./client.js";
|
|
2
|
+
import { PenpairError } from "./errors.js";
|
|
3
|
+
/** A guard in the delivered execution closes the gap between a connection check and a later batch. */
|
|
4
|
+
export function guardFile(client, expected, options = {}) {
|
|
5
|
+
if (!expected || options.anyFile) {
|
|
6
|
+
options.warn?.(options.anyFile ? "--any-file overrides the project's file guard for this run." : "No file ID is pinned. Run `penpair connect --pin-file` to protect this project's file.");
|
|
7
|
+
return client;
|
|
8
|
+
}
|
|
9
|
+
const warningPrefix = "penpair-file-warning: ";
|
|
10
|
+
return {
|
|
11
|
+
...client,
|
|
12
|
+
close: () => client.close(),
|
|
13
|
+
...(client.listTools ? { listTools: () => client.listTools() } : {}),
|
|
14
|
+
async callTool(name, args, callOptions) {
|
|
15
|
+
if (name !== "execute_code" || typeof args.code !== "string")
|
|
16
|
+
throw new PenpairError("file-mismatch", "The file guard only permits guarded execute_code calls.");
|
|
17
|
+
const guard = `{ const __penpairExpectedFile = ${JSON.stringify(expected)};
|
|
18
|
+
const __penpairActualFile = penpot.currentFile;
|
|
19
|
+
if (!__penpairActualFile || __penpairActualFile.id !== __penpairExpectedFile.id) {
|
|
20
|
+
const message = "Expected file " + (__penpairExpectedFile.name || __penpairExpectedFile.id) + " (" + __penpairExpectedFile.id + "), connected to " + (__penpairActualFile ? __penpairActualFile.name + " (" + __penpairActualFile.id + ")" : "no file") + ". Open the expected file and reconnect, or explicitly use --any-file.";
|
|
21
|
+
${options.mode === "warn" ? `console.warn(${JSON.stringify(warningPrefix)} + message);` : 'return { ok: false, fileMismatch: true, message };'}
|
|
22
|
+
} }
|
|
23
|
+
`;
|
|
24
|
+
const code = guard + args.code;
|
|
25
|
+
if (Buffer.byteLength(code, "utf8") > maxSnippetBytes)
|
|
26
|
+
throw new PenpairError("snippet-too-large", "The snippet including its file guard exceeds 90 KB");
|
|
27
|
+
const outcome = await client.callTool(name, { ...args, code }, callOptions);
|
|
28
|
+
const envelope = executionEnvelope(outcome);
|
|
29
|
+
const result = envelope?.result;
|
|
30
|
+
if (typeof result === "object" && result !== null && "fileMismatch" in result && result.fileMismatch === true)
|
|
31
|
+
throw new PenpairError("file-mismatch", String("message" in result ? result.message : "The connected file changed"));
|
|
32
|
+
if (typeof envelope?.log === "string") {
|
|
33
|
+
for (const line of envelope.log.split("\n"))
|
|
34
|
+
if (line.includes(warningPrefix))
|
|
35
|
+
options.warn?.(line.slice(line.indexOf(warningPrefix) + warningPrefix.length));
|
|
36
|
+
}
|
|
37
|
+
return outcome;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Public envelope schemas; runtime validators additionally enforce hashes, identities, hierarchy and budgets. */
|
|
2
|
+
export declare const artifactSchemas: Record<string, unknown>;
|
|
3
|
+
/** Legacy unversioned config is v1; preserve every extension block and require explicit new output for persistence. */
|
|
4
|
+
export declare function migrateProjectConfig(raw: unknown): Record<string, unknown>;
|
package/dist/formats.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { projectConfigSchema } from "./instance/config-schema.js";
|
|
2
|
+
import { validateProjectConfig } from "./instance/config.js";
|
|
3
|
+
import { commandResultSchema } from "./command-result.js";
|
|
4
|
+
const string = { type: "string" }, array = { type: "array" };
|
|
5
|
+
const schema = (name, version, kind, properties, required = Object.keys(properties)) => ({ $schema: "https://json-schema.org/draft/2020-12/schema", $id: `urn:penpair:schema:v${version}:${name}`, title: `${name} v${version}`, type: "object", required: ["schemaVersion", ...(kind ? ["kind"] : []), ...required], properties: { schemaVersion: { const: version }, ...(kind ? { kind: { const: kind } } : {}), ...properties }, additionalProperties: true });
|
|
6
|
+
/** Public envelope schemas; runtime validators additionally enforce hashes, identities, hierarchy and budgets. */
|
|
7
|
+
export const artifactSchemas = {
|
|
8
|
+
"project-config": { ...projectConfigSchema, $id: "urn:penpair:schema:v1:project-config" },
|
|
9
|
+
"command-result": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "urn:penpair:schema:v1:command-result", ...commandResultSchema },
|
|
10
|
+
"inspection": schema("inspection", 1, undefined, { kind: { enum: ["shape-inspection", "token-inspection"] }, fileId: string, pageId: { type: ["string", "null"] }, revision: { type: "integer", minimum: 0 }, complete: { type: "boolean" }, counts: { type: "object" }, reasons: array, producer: { type: "object" } }),
|
|
11
|
+
"component-map": schema("component-map", 1, undefined, { components: { type: "array", maxItems: 1000, items: { type: "object", required: ["libraryId", "componentId", "codeComponent"], properties: { libraryId: string, componentId: string, codeComponent: string, props: { type: "object" }, textProps: { type: "object", additionalProperties: string } } } } }),
|
|
12
|
+
"design-spec": schema("design-spec", 1, "design-spec", { source: { type: "object" }, mappingHash: string, nodes: array, unknowns: array }),
|
|
13
|
+
"approvals": schema("approvals", 1, "design-approvals", { records: { type: "array", maxItems: 10000 } }),
|
|
14
|
+
"structural-manifest": schema("structural-manifest", 1, "structural-snapshot-manifest", { fileId: string, revision: { type: "integer" }, producer: string, fidelity: { const: "review-only" }, pages: { type: "array", minItems: 1, maxItems: 100 }, tokens: { type: "object" }, hash: string }),
|
|
15
|
+
"baseline-manifest": schema("baseline-manifest", 2, undefined, { complete: { type: "boolean" }, fileId: string, pageId: string, captureId: string, frames: { type: "array", maxItems: 1000 } }),
|
|
16
|
+
"portable-tokens": schema("portable-tokens", 1, "portable-tokens", { format: { const: "dtcg-2025.10-subset" }, tokens: { type: "array", minItems: 1, maxItems: 10000, items: { type: "object", required: ["path", "type", "value"], properties: { path: { type: "array", minItems: 1, maxItems: 32, items: string }, type: { enum: ["color", "dimension", "number", "fontFamily", "fontWeight", "typography"] }, value: {}, description: string, extensions: { type: "object" } } } }, groups: { type: "object" }, losses: { type: "array", maxItems: 0 } }),
|
|
17
|
+
};
|
|
18
|
+
/** Legacy unversioned config is v1; preserve every extension block and require explicit new output for persistence. */
|
|
19
|
+
export function migrateProjectConfig(raw) {
|
|
20
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
21
|
+
throw new Error("Config migration requires a JSON object");
|
|
22
|
+
const v = raw;
|
|
23
|
+
if (v.schemaVersion !== undefined && v.schemaVersion !== 1)
|
|
24
|
+
throw new Error("Unsupported project config major version; use the producer's documented migration before opening it");
|
|
25
|
+
const migrated = { ...structuredClone(v), schemaVersion: 1 };
|
|
26
|
+
validateProjectConfig(migrated);
|
|
27
|
+
return migrated;
|
|
28
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
export { version } from "./version.js";
|
|
2
2
|
export * from "./adapters.js";
|
|
3
3
|
export * from "./baselines.js";
|
|
4
|
-
export { runCli } from "./cli-main.js";
|
|
4
|
+
export { runCommand, runCli } from "./cli-main.js";
|
|
5
5
|
export * from "./client.js";
|
|
6
|
+
export { guardFile } from "./file-guard.js";
|
|
6
7
|
export * from "./deep-check.js";
|
|
8
|
+
export { runLiveSmoke } from "./live-smoke.js";
|
|
9
|
+
export * from "./layout.js";
|
|
10
|
+
export * from "./native-api.js";
|
|
11
|
+
export * from "./file-archive.js";
|
|
12
|
+
export { backupNoteCheck } from "./backup-note.js";
|
|
13
|
+
export * from "./journal.js";
|
|
7
14
|
export * from "./local-profile.js";
|
|
8
15
|
export * from "./endpoint.js";
|
|
9
16
|
export * from "./errors.js";
|
|
@@ -18,8 +25,33 @@ export * from "./snippets/archive.js";
|
|
|
18
25
|
export * from "./snippets/read.js";
|
|
19
26
|
export * from "./tokens-diff.js";
|
|
20
27
|
export * from "./instance/config.js";
|
|
28
|
+
export { projectConfigSchema } from "./instance/config-schema.js";
|
|
21
29
|
export * from "./instance/connection.js";
|
|
22
30
|
export * from "./instance/gitignore.js";
|
|
23
31
|
export { defaultInstanceName, init } from "./instance/init.js";
|
|
24
32
|
export type { InitOptions, InitResult } from "./instance/init.js";
|
|
25
33
|
export * from "./instance/runner.js";
|
|
34
|
+
export { writeCoordinator, guardRevision, type WriteLease, type CoordinationOptions } from "./coordination.js";
|
|
35
|
+
export { onboardingChecklist } from "./onboard.js";
|
|
36
|
+
export { atomicWriteFile } from "./atomic-output.js";
|
|
37
|
+
export { commandSleep, withCleanupDeadline, commandResultSchema, collectCommand, commandOutput, type CommandResult, type CommandFailure, type CommandOutput } from "./command-result.js";
|
|
38
|
+
export { resolveProjectInput, resolveProjectOutput, designDataBlock } from "./project-input.js";
|
|
39
|
+
export { planSkillInstructions, applySkillInstructions, type InstructionReport } from "./skill-instructions.js";
|
|
40
|
+
export { parseCommandArgs, CommandArgumentError } from "./cli-arguments.js";
|
|
41
|
+
export { findShapesByName, exportShapeByName, captureShots, type ShapeLookup, type ShapeMatch, type ShotsReport } from "./shots.js";
|
|
42
|
+
export * from "./inspection.js";
|
|
43
|
+
export { tokenTypes, validateEditableToken, validateTokenEdit, planTokenEdit, tokenEditOperation, type EditableToken, type TokenEdit, type TokenEditState, type TokenEditPlan } from "./token-edit.js";
|
|
44
|
+
export { ensureGitignoreEntry } from "./instance/gitignore.js";
|
|
45
|
+
export { prepareFileScript, runFileScript, type FileScriptOptions, type PreparedFileScript } from "./run-file.js";
|
|
46
|
+
export { kit, coreKit, checkKits, loadKits, type BuildKit, type RecipeInfo } from "./kit.js";
|
|
47
|
+
export { runKitSmoke, kitSmokeOperation } from "./kit-smoke.js";
|
|
48
|
+
export { baselineCoverage, readBaselineCoverage, designCaseKey, type BaselineCoverage, type CoverageRow } from "./baseline-coverage.js";
|
|
49
|
+
export * from "./design-spec.js";
|
|
50
|
+
export * from "./mcp-server.js";
|
|
51
|
+
export * from "./approvals.js";
|
|
52
|
+
export * from "./structure-snapshot.js";
|
|
53
|
+
export * from "./portable-tokens.js";
|
|
54
|
+
export * from "./formats.js";
|
|
55
|
+
export * from "./project-status.js";
|
|
56
|
+
export * from "./audit-rules.js";
|
|
57
|
+
export * from "./credentials.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
export { version } from "./version.js";
|
|
2
2
|
export * from "./adapters.js";
|
|
3
3
|
export * from "./baselines.js";
|
|
4
|
-
export { runCli } from "./cli-main.js";
|
|
4
|
+
export { runCommand, runCli } from "./cli-main.js";
|
|
5
5
|
export * from "./client.js";
|
|
6
|
+
export { guardFile } from "./file-guard.js";
|
|
6
7
|
export * from "./deep-check.js";
|
|
8
|
+
export { runLiveSmoke } from "./live-smoke.js";
|
|
9
|
+
export * from "./layout.js";
|
|
10
|
+
export * from "./native-api.js";
|
|
11
|
+
export * from "./file-archive.js";
|
|
12
|
+
export { backupNoteCheck } from "./backup-note.js";
|
|
13
|
+
export * from "./journal.js";
|
|
7
14
|
export * from "./local-profile.js";
|
|
8
15
|
export * from "./endpoint.js";
|
|
9
16
|
export * from "./errors.js";
|
|
@@ -18,7 +25,32 @@ export * from "./snippets/archive.js";
|
|
|
18
25
|
export * from "./snippets/read.js";
|
|
19
26
|
export * from "./tokens-diff.js";
|
|
20
27
|
export * from "./instance/config.js";
|
|
28
|
+
export { projectConfigSchema } from "./instance/config-schema.js";
|
|
21
29
|
export * from "./instance/connection.js";
|
|
22
30
|
export * from "./instance/gitignore.js";
|
|
23
31
|
export { defaultInstanceName, init } from "./instance/init.js";
|
|
24
32
|
export * from "./instance/runner.js";
|
|
33
|
+
export { writeCoordinator, guardRevision } from "./coordination.js";
|
|
34
|
+
export { onboardingChecklist } from "./onboard.js";
|
|
35
|
+
export { atomicWriteFile } from "./atomic-output.js";
|
|
36
|
+
export { commandSleep, withCleanupDeadline, commandResultSchema, collectCommand, commandOutput } from "./command-result.js";
|
|
37
|
+
export { resolveProjectInput, resolveProjectOutput, designDataBlock } from "./project-input.js";
|
|
38
|
+
export { planSkillInstructions, applySkillInstructions } from "./skill-instructions.js";
|
|
39
|
+
export { parseCommandArgs, CommandArgumentError } from "./cli-arguments.js";
|
|
40
|
+
export { findShapesByName, exportShapeByName, captureShots } from "./shots.js";
|
|
41
|
+
export * from "./inspection.js";
|
|
42
|
+
export { tokenTypes, validateEditableToken, validateTokenEdit, planTokenEdit, tokenEditOperation } from "./token-edit.js";
|
|
43
|
+
export { ensureGitignoreEntry } from "./instance/gitignore.js";
|
|
44
|
+
export { prepareFileScript, runFileScript } from "./run-file.js";
|
|
45
|
+
export { kit, coreKit, checkKits, loadKits } from "./kit.js";
|
|
46
|
+
export { runKitSmoke, kitSmokeOperation } from "./kit-smoke.js";
|
|
47
|
+
export { baselineCoverage, readBaselineCoverage, designCaseKey } from "./baseline-coverage.js";
|
|
48
|
+
export * from "./design-spec.js";
|
|
49
|
+
export * from "./mcp-server.js";
|
|
50
|
+
export * from "./approvals.js";
|
|
51
|
+
export * from "./structure-snapshot.js";
|
|
52
|
+
export * from "./portable-tokens.js";
|
|
53
|
+
export * from "./formats.js";
|
|
54
|
+
export * from "./project-status.js";
|
|
55
|
+
export * from "./audit-rules.js";
|
|
56
|
+
export * from "./credentials.js";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type ExecutionMetrics, type PenpotToolClient } from "./client.js";
|
|
2
|
+
export declare const inspectionFields: readonly ["geometry", "text", "layout", "component", "tokens", "style", "metadata"];
|
|
3
|
+
export type InspectionField = typeof inspectionFields[number];
|
|
4
|
+
export interface InspectionQuery {
|
|
5
|
+
kind?: "shape" | "tokens";
|
|
6
|
+
shapeId?: string;
|
|
7
|
+
page?: string;
|
|
8
|
+
fields?: InspectionField[];
|
|
9
|
+
depth?: number;
|
|
10
|
+
maxNodes?: number;
|
|
11
|
+
responseBytes?: number;
|
|
12
|
+
timeBudgetMs?: number;
|
|
13
|
+
documentBytes?: number;
|
|
14
|
+
cursor?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface InspectionChunk {
|
|
17
|
+
schemaVersion: 1;
|
|
18
|
+
kind: "inspection-chunk";
|
|
19
|
+
fileId: string;
|
|
20
|
+
pageId: string | null;
|
|
21
|
+
revision: number;
|
|
22
|
+
queryHash: string;
|
|
23
|
+
digest: string;
|
|
24
|
+
digestAlgorithm: "cyrb53-change-detector";
|
|
25
|
+
sourceComplete: boolean;
|
|
26
|
+
offset: number;
|
|
27
|
+
nextOffset: number | null;
|
|
28
|
+
totalCodeUnits: number;
|
|
29
|
+
chunk: string;
|
|
30
|
+
nextCursor: string | null;
|
|
31
|
+
complete: boolean;
|
|
32
|
+
counts: Record<string, number | null>;
|
|
33
|
+
observationMs: number;
|
|
34
|
+
metrics: ExecutionMetrics;
|
|
35
|
+
}
|
|
36
|
+
export interface InspectionDocument {
|
|
37
|
+
schemaVersion: 1;
|
|
38
|
+
kind: "shape-inspection" | "token-inspection";
|
|
39
|
+
fileId: string;
|
|
40
|
+
pageId: string | null;
|
|
41
|
+
revision: number;
|
|
42
|
+
complete: boolean;
|
|
43
|
+
counts: Record<string, number | null>;
|
|
44
|
+
reasons: string[];
|
|
45
|
+
producer: {
|
|
46
|
+
name: string;
|
|
47
|
+
version: string;
|
|
48
|
+
};
|
|
49
|
+
nodes?: Record<string, unknown>[];
|
|
50
|
+
sets?: Record<string, unknown>[];
|
|
51
|
+
themes?: Record<string, unknown>[];
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
}
|
|
54
|
+
export interface InspectionMetrics {
|
|
55
|
+
calls: number;
|
|
56
|
+
requestSnippetBytes: number;
|
|
57
|
+
responsePayloadBytes: number;
|
|
58
|
+
maxRequestSnippetBytes: number;
|
|
59
|
+
maxResponsePayloadBytes: number;
|
|
60
|
+
assembledBytes: number;
|
|
61
|
+
durationMs: number;
|
|
62
|
+
}
|
|
63
|
+
export type InspectionCapture = InspectionDocument & {
|
|
64
|
+
capturedAt: string;
|
|
65
|
+
sha256: string;
|
|
66
|
+
metrics: InspectionMetrics;
|
|
67
|
+
};
|
|
68
|
+
export interface PartialInspection {
|
|
69
|
+
schemaVersion: 1;
|
|
70
|
+
kind: "inspection-partial";
|
|
71
|
+
fileId: string | null;
|
|
72
|
+
pageId: string | null;
|
|
73
|
+
complete: false;
|
|
74
|
+
reason: string;
|
|
75
|
+
chunks: InspectionChunk[];
|
|
76
|
+
nextCursor: string | null;
|
|
77
|
+
metrics: InspectionMetrics;
|
|
78
|
+
}
|
|
79
|
+
export interface CaptureBudget {
|
|
80
|
+
maxCalls?: number;
|
|
81
|
+
maxTotalBytes?: number;
|
|
82
|
+
timeoutMs?: number;
|
|
83
|
+
}
|
|
84
|
+
export declare function validateInspectionQuery(query: InspectionQuery): {
|
|
85
|
+
queryHash: string;
|
|
86
|
+
producerVersion: string;
|
|
87
|
+
kind: "tokens" | "shape";
|
|
88
|
+
shapeId: string | undefined;
|
|
89
|
+
page: string | undefined;
|
|
90
|
+
fields: ("text" | "geometry" | "layout" | "component" | "tokens" | "style" | "metadata")[];
|
|
91
|
+
depth: number;
|
|
92
|
+
maxNodes: number;
|
|
93
|
+
responseBytes: number;
|
|
94
|
+
timeBudgetMs: number;
|
|
95
|
+
documentBytes: number;
|
|
96
|
+
};
|
|
97
|
+
export declare function inspectChunk(client: PenpotToolClient, query: InspectionQuery, requestTimeoutMs?: number): Promise<InspectionChunk>;
|
|
98
|
+
/** Validate structural evidence independently of the transport envelope. Unknown API fields remain explicit nulls. */
|
|
99
|
+
export declare function validateInspectionDocument(input: unknown): InspectionDocument;
|
|
100
|
+
export declare function reassembleInspection(chunks: InspectionChunk[]): InspectionDocument;
|
|
101
|
+
export declare function validateCaptureBudget(budget?: CaptureBudget): {
|
|
102
|
+
maxCalls: number;
|
|
103
|
+
maxTotalBytes: number;
|
|
104
|
+
timeoutMs: number;
|
|
105
|
+
};
|
|
106
|
+
export declare function captureInspection(client: PenpotToolClient, query: InspectionQuery, budget?: CaptureBudget): Promise<InspectionCapture | PartialInspection>;
|