dsh-claude-slides 2.7.1 → 2.7.2
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/lib/client.js +1 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/skills/claude-slides/SKILL.md +1 -0
- package/skills/claude-slides/scripts/artifact.mjs +230 -49
- package/skills/claude-slides/scripts/editor/assets/{index-S9woRl4b.js → index-kN-1njux.js} +129 -129
- package/skills/claude-slides/scripts/editor/assets/{woff2-decoder-BXtJbtte.js → woff2-decoder-P1USYjds.js} +1 -1
- package/skills/claude-slides/scripts/editor/index.html +1 -1
- package/skills/claude-slides/scripts/editor/node/compiler.cjs +80 -15
- package/skills/claude-slides/scripts/server.mjs +265 -101
package/lib/client.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// dsh-claude-slides 2.7.
|
|
1
|
+
// dsh-claude-slides 2.7.2: built from baoyu-design.
|
|
2
2
|
var define_PLUGIN_default = { name: "claude-slides", provider: "dsh-claude-slides", skills: [{ name: "claude-slides", description: "Make and revise slide decks in the Slides artifact format (project/deck.json plus one HTML file per slide) as local files, then open them in a local slide editor where the user can present, retype, drag, and export PDF, HTML or PowerPoint. Use when the user asks for an editable slide deck or presentation in this format, names claude-slides or the Slides type, or wants to revise a folder that holds project/deck.json. A one-off single-file HTML deck or prototype is baoyu-design's.", comments: true }] };
|
|
3
3
|
|
|
4
4
|
import { spawn as spawn2 } from "node:child_process";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-claude-slides",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.2",
|
|
4
4
|
"description": "DeepSeek Harness plugin: the claude-slides skill from baoyu-design. The agent writes editable slide decks as files and opens them in a local slide editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -79,6 +79,7 @@ leave alone stays as it is, and removing a slide is deleting its file. The rest
|
|
|
79
79
|
- `upload <root> <file>` → `{url:"/_blob/<id>", …}`: stores a copy under `project/assets/` (png, jpg, gif, webp, avif, svg; woff2, woff, ttf, otf; ≤20 MB); the same bytes always get the same `url`. `<root>` must exist.
|
|
80
80
|
- `list [dir …]` → the design systems in the working directory (or the folders given, and those in `$CLAUDE_DESIGN_SYSTEMS`), each `{title, namespace, folder, root, readme, tokens}`: its files are at `<its root>/project/<path>`, its uploads at `<its root>/project/_blobs/<id>.<ext>`.
|
|
81
81
|
- `check <root>` → `problems` (0: done); when any, `deckProblems` and per slide `problems` with the fix, slide `notes` (hints; fix the "排版" and "警告" ones), and `designSystemNotes` when an install looks incomplete; it exits 1 while problems remain. `notChecked` names what it can't see: text sizes and contrast stay yours.
|
|
82
|
+
- `ds-sync <root> [<folder>] [--check]` → an installed design system is a copy, and a copy never changes by itself. This compares each file of `project/ds/<folder>/` with the same file in the system's own folder (the record's `artifact`, a path on this machine; a system installed from claude.ai has none, and `--from <folder>` names one), copies the ones that differ byte for byte, and sets the record's `copiedAt`. `--check` only lists them (`changed`, and `goneFromSource` for a file the system dropped, which stays in the copy) and exits 1 while a copy is behind. Files the system gained are not copied: compare its README with the copy. A relative `artifact` is looked for from the working directory, then the artifact's folder and the folders above it. `check` notes a copy that is behind in `designSystemNotes`, and the Theme tab says so beside "View … design system" with a Sync button (the owner's), or says its source folder wasn't found.
|
|
82
83
|
- `open <root>` → `{url, pid, stop, …}`: starts the editor page on this machine in the background (once; later calls reuse it, `reused: true`) and prints its address, 127.0.0.1:4710 (the next free port up when 4710 is taken; `--port n` starts from n, `--port 0` takes any). `stop <root>` ends it (`serve <root>` runs it in the foreground instead). The page saves the user's edits into the same files, and shows yours as soon as you write them. `.artifact-server.json` and `.artifact-server.log` beside `project/` are its own: leave them; so is `comments.json` (the comments, see Comments), which only the comment commands below change.
|
|
83
84
|
- `share <root>` (or `open <root> --lan`): opens the page to the local network and prints a link per role (Sharing, below).
|
|
84
85
|
- `archive <root> [--out file.zip|dir]` → `{file, files, bytes, open}`: the whole folder, history and comments too, as one zip beside it (`--out`: another file or folder), to back it up, move it to another computer or send it to someone; unzipped anywhere, `open` carries on. The owner's Download folder button in the page (between History and Share) does the same. The server's own files and the share links stay out.
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// claude-slides: local Artifact tool (prebuilt; do not edit).
|
|
3
3
|
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import
|
|
5
|
+
import fs12 from "node:fs";
|
|
6
6
|
import fsp3 from "node:fs/promises";
|
|
7
|
-
import
|
|
7
|
+
import path14 from "node:path";
|
|
8
8
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
9
9
|
|
|
10
10
|
var BLOB_ID_PATTERN = /^[0-9a-f]{32}$/;
|
|
@@ -2654,7 +2654,7 @@ async function archiveEntries(root, skip = []) {
|
|
|
2654
2654
|
const folder = path12.basename(path12.resolve(root)) || "artifact";
|
|
2655
2655
|
const skipped = new Set(skip.map((file) => path12.resolve(file)));
|
|
2656
2656
|
const entries = [];
|
|
2657
|
-
const
|
|
2657
|
+
const walk2 = async (dir, rel) => {
|
|
2658
2658
|
let names;
|
|
2659
2659
|
try {
|
|
2660
2660
|
names = await fsp2.readdir(dir, { withFileTypes: true });
|
|
@@ -2668,14 +2668,14 @@ async function archiveEntries(root, skip = []) {
|
|
|
2668
2668
|
const file = path12.join(dir, name);
|
|
2669
2669
|
if (skipped.has(file)) continue;
|
|
2670
2670
|
const inner = rel ? `${rel}/${name}` : name;
|
|
2671
|
-
if (dirent.isDirectory()) await
|
|
2671
|
+
if (dirent.isDirectory()) await walk2(file, inner);
|
|
2672
2672
|
else if (dirent.isFile()) {
|
|
2673
2673
|
const stat = await fsp2.stat(file).catch(() => null);
|
|
2674
2674
|
if (stat?.isFile()) entries.push({ name: `${folder}/${inner}`, file, size: stat.size, mode: stat.mode & 511, mtime: stat.mtime });
|
|
2675
2675
|
}
|
|
2676
2676
|
}
|
|
2677
2677
|
};
|
|
2678
|
-
await
|
|
2678
|
+
await walk2(path12.resolve(root), "");
|
|
2679
2679
|
if (entries.length > MAX_ENTRIES) throw new ArchiveTooLarge(`The folder holds ${entries.length} files, more than a zip holds (${MAX_ENTRIES}); copy the folder itself instead.`);
|
|
2680
2680
|
const bound = entries.reduce((sum, entry) => sum + entry.size + 2 * (46 + Buffer.byteLength(entry.name)), 22);
|
|
2681
2681
|
if (bound > MAX_BYTES) throw new ArchiveTooLarge(`The folder holds ${(bound / 2 ** 30).toFixed(1)} GiB, more than a zip holds (4 GiB); copy the folder itself instead.`);
|
|
@@ -2807,6 +2807,156 @@ async function archiveCommand(root, out) {
|
|
|
2807
2807
|
return { file, folder: path12.basename(path12.resolve(root)), files, bytes, leftOut: [...LOCAL_FILES] };
|
|
2808
2808
|
}
|
|
2809
2809
|
|
|
2810
|
+
import fs11 from "node:fs";
|
|
2811
|
+
import path13 from "node:path";
|
|
2812
|
+
var FOLDER = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
2813
|
+
var MAX_FILES = 2e3;
|
|
2814
|
+
var MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
2815
|
+
var DESIGN_SYSTEM_FILE2 = "design-system.json";
|
|
2816
|
+
function readRecords(project, indexFile) {
|
|
2817
|
+
let index;
|
|
2818
|
+
try {
|
|
2819
|
+
index = JSON.parse(fs11.readFileSync(path13.join(project, indexFile), "utf8"));
|
|
2820
|
+
} catch {
|
|
2821
|
+
return [];
|
|
2822
|
+
}
|
|
2823
|
+
const list = index?.designSystems;
|
|
2824
|
+
const records = Array.isArray(list) ? list : list && typeof list === "object" ? Object.values(list) : [];
|
|
2825
|
+
const out = [];
|
|
2826
|
+
for (const raw of records) {
|
|
2827
|
+
const r = raw;
|
|
2828
|
+
if (typeof r?.namespace !== "string" || !FOLDER.test(r.namespace)) continue;
|
|
2829
|
+
out.push({
|
|
2830
|
+
namespace: r.namespace,
|
|
2831
|
+
title: typeof r.title === "string" ? r.title : r.namespace,
|
|
2832
|
+
artifact: typeof r.artifact === "string" ? r.artifact : ""
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
return out;
|
|
2836
|
+
}
|
|
2837
|
+
function plain(p, want) {
|
|
2838
|
+
const stat = fs11.lstatSync(p, { throwIfNoEntry: false });
|
|
2839
|
+
return Boolean(stat && !stat.isSymbolicLink() && (want === "file" ? stat.isFile() : stat.isDirectory()));
|
|
2840
|
+
}
|
|
2841
|
+
function walk(dir, prefix = "", out = []) {
|
|
2842
|
+
for (const entry of fs11.readdirSync(path13.join(dir, prefix), { withFileTypes: true })) {
|
|
2843
|
+
if (out.length >= MAX_FILES) break;
|
|
2844
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2845
|
+
if (entry.isDirectory()) walk(dir, rel, out);
|
|
2846
|
+
else if (entry.isFile()) out.push(rel);
|
|
2847
|
+
}
|
|
2848
|
+
return out;
|
|
2849
|
+
}
|
|
2850
|
+
var projectOf = (candidate) => path13.basename(candidate) === "project" ? candidate : path13.join(candidate, "project");
|
|
2851
|
+
function sourceProject(artifact, root, from) {
|
|
2852
|
+
const given = from ?? artifact;
|
|
2853
|
+
if (!given) return { reason: "the record names no source folder", kind: "none" };
|
|
2854
|
+
if (!from && /^[a-z][a-z0-9+.-]*:\/\//i.test(given)) return { reason: `its source is ${given}, which is not a folder on this machine`, kind: "remote" };
|
|
2855
|
+
const candidates = path13.isAbsolute(given) ? [given] : [path13.resolve(given), path13.resolve(root, given), path13.resolve(root, "..", given)];
|
|
2856
|
+
for (const candidate of candidates) {
|
|
2857
|
+
const project = projectOf(candidate);
|
|
2858
|
+
if (plain(project, "dir")) return { project };
|
|
2859
|
+
}
|
|
2860
|
+
if (!from && !path13.isAbsolute(given)) {
|
|
2861
|
+
let dir = path13.resolve(root, "..");
|
|
2862
|
+
for (let up = path13.dirname(dir); up !== dir; up = path13.dirname(dir)) {
|
|
2863
|
+
dir = up;
|
|
2864
|
+
const project = projectOf(path13.resolve(dir, given));
|
|
2865
|
+
if (plain(path13.join(project, DESIGN_SYSTEM_FILE2), "file")) return { project };
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
return { reason: `its source folder ${given} was not found${from ? "" : ` (tried from ${process.cwd()} and the folders above the artifact); pass --from <folder>`}`, kind: "not-found" };
|
|
2869
|
+
}
|
|
2870
|
+
function compareSystem(project, root, record, from) {
|
|
2871
|
+
const diff = { namespace: record.namespace, title: record.title, changed: [], removed: [] };
|
|
2872
|
+
const found = sourceProject(record.artifact, root, from);
|
|
2873
|
+
if (!found.project) return { ...diff, skipped: found.reason, skippedKind: found.kind };
|
|
2874
|
+
diff.source = path13.dirname(found.project);
|
|
2875
|
+
const copy = path13.join(project, "ds", record.namespace);
|
|
2876
|
+
if (!plain(copy, "dir")) return { ...diff, missing: true };
|
|
2877
|
+
for (const rel of walk(copy)) {
|
|
2878
|
+
const mine = path13.join(copy, rel);
|
|
2879
|
+
const theirs = path13.join(found.project, rel);
|
|
2880
|
+
if (!plain(mine, "file")) continue;
|
|
2881
|
+
if (!plain(theirs, "file")) {
|
|
2882
|
+
diff.removed.push(rel);
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
const a = fs11.statSync(mine);
|
|
2886
|
+
const b = fs11.statSync(theirs);
|
|
2887
|
+
if (a.size > MAX_FILE_BYTES || b.size > MAX_FILE_BYTES) continue;
|
|
2888
|
+
if (a.size !== b.size || !fs11.readFileSync(mine).equals(fs11.readFileSync(theirs))) diff.changed.push(rel);
|
|
2889
|
+
}
|
|
2890
|
+
return diff;
|
|
2891
|
+
}
|
|
2892
|
+
function pick(records, namespace) {
|
|
2893
|
+
if (namespace === void 0) return records;
|
|
2894
|
+
const one = records.filter((r) => r.namespace === namespace);
|
|
2895
|
+
if (one.length === 0) {
|
|
2896
|
+
const names = records.map((r) => r.namespace);
|
|
2897
|
+
throw new Error(`No designSystems record has namespace ${JSON.stringify(namespace)}${names.length ? ` (it has: ${names.join(", ")})` : ""}.`);
|
|
2898
|
+
}
|
|
2899
|
+
return one;
|
|
2900
|
+
}
|
|
2901
|
+
function compareSystems(project, root, indexFile, namespace, from) {
|
|
2902
|
+
return pick(readRecords(project, indexFile), namespace).map((r) => compareSystem(project, root, r, from));
|
|
2903
|
+
}
|
|
2904
|
+
function staleNotes(project, root, indexFile, command) {
|
|
2905
|
+
const notes = [];
|
|
2906
|
+
for (const diff of compareSystems(project, root, indexFile)) {
|
|
2907
|
+
if (diff.changed.length === 0 && diff.removed.length === 0) continue;
|
|
2908
|
+
const parts = [];
|
|
2909
|
+
if (diff.changed.length > 0) parts.push(`${diff.changed.length} changed (${diff.changed.slice(0, 3).join(", ")}${diff.changed.length > 3 ? ", \u2026" : ""})`);
|
|
2910
|
+
if (diff.removed.length > 0) parts.push(`${diff.removed.length} gone from the source`);
|
|
2911
|
+
notes.push(
|
|
2912
|
+
`project/ds/${diff.namespace}/ is behind its source system ${diff.source}: ${parts.join(", ")}. A copy never changes by itself: run \`${command(diff.namespace, false)}\` to copy the changed files again (\`${command(diff.namespace, true)}\` lists them).`
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
return notes;
|
|
2916
|
+
}
|
|
2917
|
+
function stamp(project, indexFile, namespaces, now) {
|
|
2918
|
+
const file = path13.join(project, indexFile);
|
|
2919
|
+
const text = fs11.readFileSync(file, "utf8");
|
|
2920
|
+
const index = JSON.parse(text);
|
|
2921
|
+
const list = index.designSystems;
|
|
2922
|
+
const records = Array.isArray(list) ? list : list && typeof list === "object" ? Object.values(list) : [];
|
|
2923
|
+
for (const r of records) {
|
|
2924
|
+
const record = r;
|
|
2925
|
+
if (record && typeof record.namespace === "string" && namespaces.has(record.namespace)) record.copiedAt = now;
|
|
2926
|
+
}
|
|
2927
|
+
const indent = /^\{\n/.test(text) ? /\n( +|\t)"/.exec(text)?.[1] ?? 2 : void 0;
|
|
2928
|
+
const out = JSON.stringify(index, null, indent);
|
|
2929
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
2930
|
+
fs11.writeFileSync(tmp, text.endsWith("\n") ? `${out}
|
|
2931
|
+
` : out);
|
|
2932
|
+
fs11.renameSync(tmp, file);
|
|
2933
|
+
}
|
|
2934
|
+
function syncSystems(project, root, indexFile, opts) {
|
|
2935
|
+
const diffs = compareSystems(project, root, indexFile, opts.namespace, opts.from);
|
|
2936
|
+
const results = [];
|
|
2937
|
+
const synced = /* @__PURE__ */ new Set();
|
|
2938
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
2939
|
+
for (const diff of diffs) {
|
|
2940
|
+
const result = { ...diff, copied: [] };
|
|
2941
|
+
if (!opts.check && diff.source && !diff.missing) {
|
|
2942
|
+
for (const rel of diff.changed) {
|
|
2943
|
+
const target = path13.join(project, "ds", diff.namespace, rel);
|
|
2944
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
2945
|
+
fs11.copyFileSync(path13.join(diff.source, "project", rel), tmp);
|
|
2946
|
+
fs11.renameSync(tmp, target);
|
|
2947
|
+
result.copied.push(rel);
|
|
2948
|
+
}
|
|
2949
|
+
if (diff.changed.length > 0) synced.add(diff.namespace);
|
|
2950
|
+
}
|
|
2951
|
+
results.push(result);
|
|
2952
|
+
}
|
|
2953
|
+
if (synced.size > 0) {
|
|
2954
|
+
stamp(project, indexFile, synced, now);
|
|
2955
|
+
for (const r of results) if (synced.has(r.namespace)) r.copiedAt = now;
|
|
2956
|
+
}
|
|
2957
|
+
return results;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2810
2960
|
async function shareCall(url, action, fail2) {
|
|
2811
2961
|
const response = await fetch(new URL("/__share", url), action ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(action) } : {});
|
|
2812
2962
|
const body = await response.json().catch(() => ({}));
|
|
@@ -2888,8 +3038,8 @@ process.emitWarning = ((warning, ...rest) => {
|
|
|
2888
3038
|
});
|
|
2889
3039
|
var KIND = "slides";
|
|
2890
3040
|
var SCRIPT = fileURLToPath2(import.meta.url);
|
|
2891
|
-
var EDITOR_DIR =
|
|
2892
|
-
var SERVER_SCRIPT =
|
|
3041
|
+
var EDITOR_DIR = path14.join(path14.dirname(SCRIPT), "editor");
|
|
3042
|
+
var SERVER_SCRIPT = path14.join(path14.dirname(SCRIPT), "server.mjs");
|
|
2893
3043
|
var MAX_LISTED = 200;
|
|
2894
3044
|
var UPLOAD_MAX_BYTES = 20 * 1024 * 1024;
|
|
2895
3045
|
var INDEX_FILE = KIND === "slides" ? "deck.json" : KIND === "design" ? CANVAS_FILE : DESIGN_SYSTEM_FILE;
|
|
@@ -2902,7 +3052,7 @@ var CliError = class extends Error {
|
|
|
2902
3052
|
this.code = code;
|
|
2903
3053
|
}
|
|
2904
3054
|
};
|
|
2905
|
-
var SWITCHES = /* @__PURE__ */ new Set(["all", "wait", "resolve", "reopen", "lan", "off"]);
|
|
3055
|
+
var SWITCHES = /* @__PURE__ */ new Set(["all", "wait", "resolve", "reopen", "lan", "off", "check"]);
|
|
2906
3056
|
function parseArgs(argv) {
|
|
2907
3057
|
const positional = [];
|
|
2908
3058
|
const flags = /* @__PURE__ */ new Map();
|
|
@@ -2928,13 +3078,13 @@ function quote(s) {
|
|
|
2928
3078
|
}
|
|
2929
3079
|
function resolveArtifact(given, mustExist = true) {
|
|
2930
3080
|
if (!given) throw new CliError("MISSING_ROOT", `Give the artifact folder: the one that holds project/${INDEX_FILE}.`);
|
|
2931
|
-
let root =
|
|
2932
|
-
if (
|
|
2933
|
-
root =
|
|
3081
|
+
let root = path14.resolve(given);
|
|
3082
|
+
if (path14.basename(root) === "project" && fs12.existsSync(path14.join(root, INDEX_FILE)) && !fs12.existsSync(path14.join(root, "project"))) {
|
|
3083
|
+
root = path14.dirname(root);
|
|
2934
3084
|
}
|
|
2935
|
-
const project =
|
|
3085
|
+
const project = path14.join(root, "project");
|
|
2936
3086
|
if (mustExist) {
|
|
2937
|
-
const stat =
|
|
3087
|
+
const stat = fs12.statSync(project, { throwIfNoEntry: false });
|
|
2938
3088
|
if (!stat?.isDirectory()) {
|
|
2939
3089
|
throw new CliError("NOT_FOUND", `${project} does not exist. Write project/${INDEX_FILE} and the other files under ${root} first.`);
|
|
2940
3090
|
}
|
|
@@ -2956,7 +3106,7 @@ function localize(value) {
|
|
|
2956
3106
|
function designSystemNotes(project, indexFile) {
|
|
2957
3107
|
let index;
|
|
2958
3108
|
try {
|
|
2959
|
-
index = JSON.parse(
|
|
3109
|
+
index = JSON.parse(fs12.readFileSync(path14.join(project, indexFile), "utf8"));
|
|
2960
3110
|
} catch {
|
|
2961
3111
|
return [];
|
|
2962
3112
|
}
|
|
@@ -2970,14 +3120,15 @@ function designSystemNotes(project, indexFile) {
|
|
|
2970
3120
|
notes.push(`designSystems namespace ${JSON.stringify(ns)} is not a folder name ([a-z0-9][a-z0-9_-]{0,63}): the page skips it. Use the lower-case folder (list prints it as folder), the same in the record and in project/ds/<folder>/.`);
|
|
2971
3121
|
continue;
|
|
2972
3122
|
}
|
|
2973
|
-
const folder =
|
|
2974
|
-
if (!
|
|
3123
|
+
const folder = path14.join(project, "ds", ns);
|
|
3124
|
+
if (!fs12.existsSync(folder)) {
|
|
2975
3125
|
notes.push(`designSystems names "${ns}" but project/ds/${ns}/ does not exist: copy the system's project/tokens.json (and the files you use) there, or take the record out.`);
|
|
2976
|
-
} else if (!
|
|
3126
|
+
} else if (!fs12.existsSync(path14.join(folder, "tokens.json"))) {
|
|
2977
3127
|
notes.push(`project/ds/${ns}/ has no tokens.json, so the Theme and color menus show bare hexes: copy the system's project/tokens.json there (a system with none: nothing to do).`);
|
|
2978
3128
|
}
|
|
2979
3129
|
}
|
|
2980
|
-
|
|
3130
|
+
const root = path14.dirname(project);
|
|
3131
|
+
return [...notes, ...staleNotes(project, root, indexFile, (ns, listOnly) => script("ds-sync", root, ns, ...listOnly ? ["--check"] : []))];
|
|
2981
3132
|
}
|
|
2982
3133
|
async function check(a) {
|
|
2983
3134
|
if (KIND === "slides") {
|
|
@@ -2985,9 +3136,9 @@ async function check(a) {
|
|
|
2985
3136
|
try {
|
|
2986
3137
|
rt = loadSlidesCompiler(EDITOR_DIR);
|
|
2987
3138
|
} catch {
|
|
2988
|
-
throw new CliError("COMPILER_MISSING", `The slides compiler is missing (${
|
|
3139
|
+
throw new CliError("COMPILER_MISSING", `The slides compiler is missing (${path14.join(EDITOR_DIR, "node", "compiler.cjs")}); reinstall the skill.`);
|
|
2989
3140
|
}
|
|
2990
|
-
if (!
|
|
3141
|
+
if (!fs12.existsSync(path14.join(a.project, "deck.json"))) {
|
|
2991
3142
|
return {
|
|
2992
3143
|
problems: 1,
|
|
2993
3144
|
result: {
|
|
@@ -3026,7 +3177,7 @@ async function check(a) {
|
|
|
3026
3177
|
...slides.length > 0 ? { slides } : {},
|
|
3027
3178
|
...dsNotes.length > 0 ? { designSystemNotes: dsNotes } : {},
|
|
3028
3179
|
notChecked: "Text sizes, contrast, whether a section id matches its file name and whether an image path exists are not checked (nothing is rendered).",
|
|
3029
|
-
next: problems2 > 0 ? `Fix the files named in deckProblems and in each slide's problems (paths are under project/), then run check again until problems is 0. notes are hints and lose no content; fix the layout ones (starting "\u6392\u7248") and the warnings (starting "\u8B66\u544A": a font or image falls back) too.` : `No problems. Show it to the user: ${script("open", a.root)} prints the editor URL.` + (layoutNotes ? ' Some slides carry layout notes ("\u6392\u7248": content may overflow or overlap) or warnings ("\u8B66\u544A": a font or image falls back); fix them and check again.' : "") + (report.orphans.length > 0 ? " orphans are slide files missing from order: they do not show; delete the unneeded ones." : "") + (dsNotes.length > 0 ? " Read designSystemNotes: a design system install looks incomplete." : "")
|
|
3180
|
+
next: problems2 > 0 ? `Fix the files named in deckProblems and in each slide's problems (paths are under project/), then run check again until problems is 0. notes are hints and lose no content; fix the layout ones (starting "\u6392\u7248") and the warnings (starting "\u8B66\u544A": a font or image falls back) too.` : `No problems. Show it to the user: ${script("open", a.root)} prints the editor URL.` + (layoutNotes ? ' Some slides carry layout notes ("\u6392\u7248": content may overflow or overlap) or warnings ("\u8B66\u544A": a font or image falls back); fix them and check again.' : "") + (report.orphans.length > 0 ? " orphans are slide files missing from order: they do not show; delete the unneeded ones." : "") + (dsNotes.length > 0 ? " Read designSystemNotes: a design system install looks incomplete or behind its source." : "")
|
|
3030
3181
|
}
|
|
3031
3182
|
};
|
|
3032
3183
|
}
|
|
@@ -3045,17 +3196,17 @@ async function check(a) {
|
|
|
3045
3196
|
...problems2 > MAX_LISTED ? { more: `${problems2 - MAX_LISTED} more not listed: fix these, then check again` } : {},
|
|
3046
3197
|
...dsNotes.length > 0 ? { designSystemNotes: dsNotes } : {},
|
|
3047
3198
|
notChecked: "Whether the artboard scripts run, and how the layout looks, is not checked (nothing is rendered).",
|
|
3048
|
-
next: problems2 > 0 ? 'Fix the files per issues (file is a path under project/; "." is the whole folder), then run check again until problems is 0.' : `No problems. Show it to the user: ${script("open", a.root)} prints the editor URL.` + (dsNotes.length > 0 ? " Read designSystemNotes: a design system install looks incomplete." : "")
|
|
3199
|
+
next: problems2 > 0 ? 'Fix the files per issues (file is a path under project/; "." is the whole folder), then run check again until problems is 0.' : `No problems. Show it to the user: ${script("open", a.root)} prints the editor URL.` + (dsNotes.length > 0 ? " Read designSystemNotes: a design system install looks incomplete or behind its source." : "")
|
|
3049
3200
|
}
|
|
3050
3201
|
};
|
|
3051
3202
|
}
|
|
3052
3203
|
const r = await checkDesignSystem(a.project);
|
|
3053
3204
|
const problems = r.issues.length;
|
|
3054
|
-
if (
|
|
3055
|
-
if (!
|
|
3205
|
+
if (fs12.existsSync(path14.join(a.project, DESIGN_SYSTEM_FILE))) {
|
|
3206
|
+
if (!fs12.existsSync(path14.join(a.project, "README.md"))) {
|
|
3056
3207
|
r.notes.push({ file: "README.md", message: "missing: every system includes project/README.md; every reader starts there (Creating, step 1)" });
|
|
3057
3208
|
}
|
|
3058
|
-
if (!
|
|
3209
|
+
if (!fs12.existsSync(path14.join(a.project, "components", "Cover", "preview.html"))) {
|
|
3059
3210
|
r.notes.push({ file: "components/Cover/preview.html", message: "missing: every system gets a cover, written last before the index (cover.md)" });
|
|
3060
3211
|
}
|
|
3061
3212
|
}
|
|
@@ -3081,11 +3232,11 @@ function formatBytes(n) {
|
|
|
3081
3232
|
}
|
|
3082
3233
|
async function upload(a, given, group) {
|
|
3083
3234
|
if (!given) throw new CliError("MISSING_FILE", "Give the file to upload.");
|
|
3084
|
-
const file =
|
|
3235
|
+
const file = path14.resolve(given);
|
|
3085
3236
|
const stat = await fsp3.stat(file).catch(() => null);
|
|
3086
3237
|
if (!stat) throw new CliError("FILE_NOT_FOUND", `No such file: ${given}`);
|
|
3087
3238
|
if (!stat.isFile()) throw new CliError("NOT_A_FILE", `${given} is not a regular file.`);
|
|
3088
|
-
const ext =
|
|
3239
|
+
const ext = path14.extname(file).slice(1).toLowerCase();
|
|
3089
3240
|
const type = contentTypeOf(file);
|
|
3090
3241
|
const isMedia = type.startsWith("image/") || type.startsWith("font/");
|
|
3091
3242
|
let limit = UPLOAD_MAX_BYTES;
|
|
@@ -3110,7 +3261,7 @@ async function upload(a, given, group) {
|
|
|
3110
3261
|
if (stat.size > limit) throw new CliError("FILE_TOO_LARGE", `${given} is ${formatBytes(stat.size)}, over the ${formatBytes(limit)} limit: compress or shrink it first.`);
|
|
3111
3262
|
const bytes = await fsp3.readFile(file);
|
|
3112
3263
|
const uploaded = await new DeckStore(a.project, LAYOUT).putBlob(bytes, type);
|
|
3113
|
-
const base =
|
|
3264
|
+
const base = path14.basename(file);
|
|
3114
3265
|
if (KIND !== "design-system") {
|
|
3115
3266
|
return {
|
|
3116
3267
|
url: uploaded.url,
|
|
@@ -3142,12 +3293,12 @@ function folderName(name) {
|
|
|
3142
3293
|
}
|
|
3143
3294
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "vendor", "target"]);
|
|
3144
3295
|
async function listSystems(dirs) {
|
|
3145
|
-
const extra = (process.env.CLAUDE_DESIGN_SYSTEMS ?? "").split(
|
|
3146
|
-
const starts = [...dirs.length > 0 ? dirs : [process.cwd()], ...extra].map((d) =>
|
|
3296
|
+
const extra = (process.env.CLAUDE_DESIGN_SYSTEMS ?? "").split(path14.delimiter).filter(Boolean);
|
|
3297
|
+
const starts = [...dirs.length > 0 ? dirs : [process.cwd()], ...extra].map((d) => path14.resolve(d));
|
|
3147
3298
|
const found = /* @__PURE__ */ new Map();
|
|
3148
3299
|
const visit = async (dir, depth) => {
|
|
3149
|
-
const project =
|
|
3150
|
-
if (
|
|
3300
|
+
const project = path14.join(dir, "project");
|
|
3301
|
+
if (fs12.existsSync(path14.join(project, DESIGN_SYSTEM_FILE)) && !found.has(dir)) {
|
|
3151
3302
|
const info = await readDesignSystemInfo(project).catch(() => null);
|
|
3152
3303
|
if (info) {
|
|
3153
3304
|
const namespace = info.namespace;
|
|
@@ -3156,8 +3307,8 @@ async function listSystems(dirs) {
|
|
|
3156
3307
|
namespace: namespace ?? null,
|
|
3157
3308
|
folder: folderName(typeof namespace === "string" && namespace.trim() ? namespace : String(info.title ?? "")),
|
|
3158
3309
|
root: dir,
|
|
3159
|
-
readme:
|
|
3160
|
-
tokens:
|
|
3310
|
+
readme: fs12.existsSync(path14.join(project, "README.md")) ? path14.join(project, "README.md") : null,
|
|
3311
|
+
tokens: fs12.existsSync(path14.join(project, "tokens.json")) ? path14.join(project, "tokens.json") : null
|
|
3161
3312
|
});
|
|
3162
3313
|
return;
|
|
3163
3314
|
}
|
|
@@ -3166,7 +3317,7 @@ async function listSystems(dirs) {
|
|
|
3166
3317
|
const entries = await fsp3.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
3167
3318
|
for (const entry of entries) {
|
|
3168
3319
|
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIP_DIRS.has(entry.name) || entry.name === "project") continue;
|
|
3169
|
-
await visit(
|
|
3320
|
+
await visit(path14.join(dir, entry.name), depth - 1);
|
|
3170
3321
|
}
|
|
3171
3322
|
};
|
|
3172
3323
|
for (const start of starts) await visit(start, 4);
|
|
@@ -3190,10 +3341,10 @@ function reusedNote(a, flags) {
|
|
|
3190
3341
|
return flags.get("agent-hook") ? { note: `--agent-hook was not applied: the server was already running. To use it: ${script("stop", a.root)}, then open again.` } : {};
|
|
3191
3342
|
}
|
|
3192
3343
|
function assertServerFiles() {
|
|
3193
|
-
if (!
|
|
3344
|
+
if (!fs12.existsSync(path14.join(EDITOR_DIR, "index.html"))) {
|
|
3194
3345
|
throw new CliError("EDITOR_MISSING", `The editor page is missing (${EDITOR_DIR}); reinstall the skill.`);
|
|
3195
3346
|
}
|
|
3196
|
-
if (!
|
|
3347
|
+
if (!fs12.existsSync(SERVER_SCRIPT)) {
|
|
3197
3348
|
throw new CliError("EDITOR_MISSING", `The editor server is missing (${SERVER_SCRIPT}); reinstall the skill.`);
|
|
3198
3349
|
}
|
|
3199
3350
|
}
|
|
@@ -3221,21 +3372,21 @@ async function open(a, flags) {
|
|
|
3221
3372
|
assertServerFiles();
|
|
3222
3373
|
const existing = await runningServer2(a.root);
|
|
3223
3374
|
if (existing) {
|
|
3224
|
-
print({ url: existing.url, root: a.root, pid: existing.pid, reused: true, ...await lanOn2(existing.url, flags), log:
|
|
3375
|
+
print({ url: existing.url, root: a.root, pid: existing.pid, reused: true, ...await lanOn2(existing.url, flags), log: path14.join(a.root, LOG_FILE), stop: script("stop", a.root), ...reusedNote(a, flags) });
|
|
3225
3376
|
return;
|
|
3226
3377
|
}
|
|
3227
3378
|
const state = await startBackground(a, flags);
|
|
3228
3379
|
const view = await shareCall2(state.url, null).catch(() => null);
|
|
3229
|
-
print({ url: state.url, root: a.root, pid: state.pid, ...view?.lan ? { lan: lanUrls(view) } : {}, log:
|
|
3380
|
+
print({ url: state.url, root: a.root, pid: state.pid, ...view?.lan ? { lan: lanUrls(view) } : {}, log: path14.join(a.root, LOG_FILE), stop: script("stop", a.root) });
|
|
3230
3381
|
}
|
|
3231
3382
|
async function startBackground(a, flags) {
|
|
3232
3383
|
const port = Number(flags.get("port") ?? 0);
|
|
3233
3384
|
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new CliError("INVALID_PORT", "--port is a number from 0 to 65535.");
|
|
3234
|
-
const logFile =
|
|
3235
|
-
const fd =
|
|
3385
|
+
const logFile = path14.join(a.root, LOG_FILE);
|
|
3386
|
+
const fd = fs12.openSync(logFile, "a");
|
|
3236
3387
|
const child = spawn(process.execPath, [SERVER_SCRIPT, a.root, ...serverArgs(flags)], { detached: true, stdio: ["ignore", "ignore", fd] });
|
|
3237
3388
|
child.unref();
|
|
3238
|
-
|
|
3389
|
+
fs12.closeSync(fd);
|
|
3239
3390
|
let exited = false;
|
|
3240
3391
|
child.once("exit", () => exited = true);
|
|
3241
3392
|
const deadline = Date.now() + 15e3;
|
|
@@ -3245,14 +3396,14 @@ async function startBackground(a, flags) {
|
|
|
3245
3396
|
if (state && state.pid === child.pid) return state;
|
|
3246
3397
|
if (exited) break;
|
|
3247
3398
|
}
|
|
3248
|
-
const tail =
|
|
3399
|
+
const tail = fs12.existsSync(logFile) ? fs12.readFileSync(logFile, "utf8").split("\n").slice(-12).join("\n") : "";
|
|
3249
3400
|
throw new CliError("SERVER_FAILED", `The editor server did not start. Run ${script("serve", a.root)} in the foreground to see why.${tail ? `
|
|
3250
3401
|
${tail}` : ""}`);
|
|
3251
3402
|
}
|
|
3252
3403
|
async function stop(a) {
|
|
3253
3404
|
const state = readState(a.root);
|
|
3254
3405
|
if (!state || !alive(state.pid)) {
|
|
3255
|
-
|
|
3406
|
+
fs12.rmSync(path14.join(a.root, STATE_FILE), { force: true });
|
|
3256
3407
|
print({ stopped: false, message: "No editor server is running for this folder." });
|
|
3257
3408
|
return;
|
|
3258
3409
|
}
|
|
@@ -3263,7 +3414,7 @@ async function stop(a) {
|
|
|
3263
3414
|
print({ stopped: false, pid: state.pid, message: `The server did not exit within 5 s; end it with: kill ${state.pid}` });
|
|
3264
3415
|
return;
|
|
3265
3416
|
}
|
|
3266
|
-
|
|
3417
|
+
fs12.rmSync(path14.join(a.root, STATE_FILE), { force: true });
|
|
3267
3418
|
print({ stopped: true, pid: state.pid });
|
|
3268
3419
|
}
|
|
3269
3420
|
async function share(a, flags) {
|
|
@@ -3307,7 +3458,7 @@ async function listComments(a, all) {
|
|
|
3307
3458
|
async function artifactTitle(a) {
|
|
3308
3459
|
const info = await readInfo(a.project).catch(() => null);
|
|
3309
3460
|
const title = typeof info?.title === "string" ? info.title.trim() : "";
|
|
3310
|
-
return title && title !== "project" ? title :
|
|
3461
|
+
return title && title !== "project" ? title : path14.basename(a.root);
|
|
3311
3462
|
}
|
|
3312
3463
|
async function waitComments(a, flags) {
|
|
3313
3464
|
const timeout = flags.has("timeout") ? Number(flags.get("timeout")) : Infinity;
|
|
@@ -3403,7 +3554,10 @@ Usage:
|
|
|
3403
3554
|
node scripts/artifact.mjs check <root> validate project/; exit 1 while problems remain
|
|
3404
3555
|
node scripts/artifact.mjs upload <root> <file>${KIND === "design-system" ? " --group G " : " "} store an upload; prints its /_blob/<id> url
|
|
3405
3556
|
node scripts/artifact.mjs list [dir \u2026] find design systems (folders holding project/design-system.json)
|
|
3406
|
-
node scripts/artifact.mjs
|
|
3557
|
+
${KIND === "design-system" ? "" : ` node scripts/artifact.mjs ds-sync <root> [namespace] [--check] an installed design system is a copy: copy its changed files again
|
|
3558
|
+
from the system's folder and stamp copiedAt (--check: list only;
|
|
3559
|
+
--from <folder>: the source, when the record's path is not found)
|
|
3560
|
+
`} node scripts/artifact.mjs comments <root> [--all] the open comments people left in the editor (--all: resolved too)
|
|
3407
3561
|
node scripts/artifact.mjs comments <root> --wait [--timeout s] wait for a comment sent to the agent from the open editor
|
|
3408
3562
|
node scripts/artifact.mjs comment-reply <root> <id> <text|-> reply as the agent (-: text on stdin) [--resolve]
|
|
3409
3563
|
node scripts/artifact.mjs comment-resolve <root> <id> [--reopen] mark a comment resolved (or open again)
|
|
@@ -3434,16 +3588,43 @@ async function main() {
|
|
|
3434
3588
|
}
|
|
3435
3589
|
case "upload": {
|
|
3436
3590
|
const a = resolveArtifact(positional[0], false);
|
|
3437
|
-
if (!
|
|
3591
|
+
if (!fs12.statSync(a.root, { throwIfNoEntry: false })?.isDirectory()) {
|
|
3438
3592
|
throw new CliError("NOT_FOUND", `${a.root} does not exist. Make the artifact folder first.`);
|
|
3439
3593
|
}
|
|
3440
|
-
|
|
3594
|
+
fs12.mkdirSync(a.project, { recursive: true });
|
|
3441
3595
|
print(await upload(a, positional[1], flags.get("group")));
|
|
3442
3596
|
return 0;
|
|
3443
3597
|
}
|
|
3444
3598
|
case "list":
|
|
3445
3599
|
print(await listSystems(positional));
|
|
3446
3600
|
return 0;
|
|
3601
|
+
case "ds-sync": {
|
|
3602
|
+
if (KIND === "design-system") throw new CliError("NOT_APPLICABLE", "A design system has no installed design systems: ds-sync is for decks and canvases.");
|
|
3603
|
+
const a = resolveArtifact(positional[0]);
|
|
3604
|
+
const check2 = flags.has("check");
|
|
3605
|
+
let results;
|
|
3606
|
+
try {
|
|
3607
|
+
results = syncSystems(a.project, a.root, INDEX_FILE, { namespace: positional[1], from: flags.get("from"), check: check2 });
|
|
3608
|
+
} catch (error) {
|
|
3609
|
+
throw new CliError("NO_SUCH_SYSTEM", error.message);
|
|
3610
|
+
}
|
|
3611
|
+
const behind = results.filter((r) => r.changed.length > 0 || r.removed.length > 0);
|
|
3612
|
+
print({
|
|
3613
|
+
root: a.root,
|
|
3614
|
+
systems: results.map((r) => ({
|
|
3615
|
+
namespace: r.namespace,
|
|
3616
|
+
title: r.title,
|
|
3617
|
+
...r.skipped ? { skipped: r.skipped } : {},
|
|
3618
|
+
...r.missing ? { skipped: `project/ds/${r.namespace}/ does not exist: install it first (SKILL.md, Creating, step 4)` } : {},
|
|
3619
|
+
...r.source ? { source: r.source } : {},
|
|
3620
|
+
changed: r.changed,
|
|
3621
|
+
...r.removed.length > 0 ? { goneFromSource: r.removed } : {},
|
|
3622
|
+
...check2 ? {} : { copied: r.copied, ...r.copiedAt ? { copiedAt: r.copiedAt } : {} }
|
|
3623
|
+
})),
|
|
3624
|
+
next: check2 ? behind.length > 0 ? `Behind its source. Run ${script("ds-sync", a.root)} to copy the changed files again.` : "Every copy matches its source (or its source was not found: see skipped)." : results.some((r) => r.copied.length > 0) ? "Copied. The open editor shows the new files at once (a file over 2 MB, or a types file, after a reload). Files the source dropped stay in the copy: remove one, and change the artboards that load it, if the source no longer has it. Files the source gained are not copied: compare its README with project/ds/<folder>/." : "Nothing to copy."
|
|
3625
|
+
});
|
|
3626
|
+
return check2 && behind.some((r) => r.changed.length > 0) ? 1 : 0;
|
|
3627
|
+
}
|
|
3447
3628
|
case "comments": {
|
|
3448
3629
|
const a = resolveArtifact(positional[0]);
|
|
3449
3630
|
print(flags.has("wait") ? await waitComments(a, flags) : await listComments(a, flags.has("all")));
|