dsh-claude-slides 2.6.0 → 2.7.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/README.md +3 -1
- package/lib/client.js +625 -40
- package/lib/index.js +273 -51
- package/package.json +8 -2
- package/skills/claude-slides/SKILL.md +22 -2
- package/skills/claude-slides/artifact-type/reference/format.md +1 -1
- package/skills/claude-slides/scripts/artifact.mjs +234 -45
- package/skills/claude-slides/scripts/editor/assets/anthropicons-fizzy-lake-D4byUNVq.woff2 +0 -0
- package/skills/claude-slides/scripts/editor/assets/index-DX-X-CY8.css +1 -0
- package/skills/claude-slides/scripts/editor/assets/index-S9woRl4b.js +3778 -0
- package/skills/claude-slides/scripts/editor/assets/{woff2-decoder-DHpUx46X.js → woff2-decoder-BXtJbtte.js} +1 -1
- package/skills/claude-slides/scripts/editor/index.html +3 -3
- package/skills/claude-slides/scripts/editor/node/compiler.cjs +15073 -3932
- package/skills/claude-slides/scripts/editor/share-ui.js +37 -7
- package/skills/claude-slides/scripts/server.mjs +861 -107
- package/skills/claude-slides/scripts/editor/assets/anthropicons-fizzy-lake-yNspLiPZ.woff2 +0 -0
- package/skills/claude-slides/scripts/editor/assets/index-DooSdFer.css +0 -1
- package/skills/claude-slides/scripts/editor/assets/index-ExkfOJag.js +0 -3775
|
@@ -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
|
|
6
|
-
import
|
|
7
|
-
import
|
|
5
|
+
import fs11 from "node:fs";
|
|
6
|
+
import fsp3 from "node:fs/promises";
|
|
7
|
+
import path13 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}$/;
|
|
@@ -2474,6 +2474,7 @@ import fs8 from "node:fs";
|
|
|
2474
2474
|
import path10 from "node:path";
|
|
2475
2475
|
var STATE_FILE = ".artifact-server.json";
|
|
2476
2476
|
var LOG_FILE = ".artifact-server.log";
|
|
2477
|
+
var EXPORTS_DIR = "exports";
|
|
2477
2478
|
function readState(root) {
|
|
2478
2479
|
try {
|
|
2479
2480
|
return JSON.parse(fs8.readFileSync(path10.join(root, STATE_FILE), "utf8"));
|
|
@@ -2501,6 +2502,12 @@ async function runningServer(root, kind, probe) {
|
|
|
2501
2502
|
}
|
|
2502
2503
|
var DEFAULT_PORTS = { slides: 4710, design: 4720, "design-system": 4730, dashboard: 4740, docs: 4750, motion: 4760 };
|
|
2503
2504
|
|
|
2505
|
+
import fs10 from "node:fs";
|
|
2506
|
+
import fsp2 from "node:fs/promises";
|
|
2507
|
+
import path12 from "node:path";
|
|
2508
|
+
import { promisify } from "node:util";
|
|
2509
|
+
import zlib from "node:zlib";
|
|
2510
|
+
|
|
2504
2511
|
import crypto from "node:crypto";
|
|
2505
2512
|
import fs9 from "node:fs";
|
|
2506
2513
|
import path11 from "node:path";
|
|
@@ -2633,6 +2640,173 @@ var Sharing = class _Sharing {
|
|
|
2633
2640
|
}
|
|
2634
2641
|
};
|
|
2635
2642
|
|
|
2643
|
+
var LOCAL_FILES = /* @__PURE__ */ new Set([STATE_FILE, LOG_FILE, SHARING_FILE]);
|
|
2644
|
+
var LOCAL_DIRS = /* @__PURE__ */ new Set([EXPORTS_DIR]);
|
|
2645
|
+
var JUNK_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
|
|
2646
|
+
var isTemp = (name) => name.endsWith(".tmp") || name.startsWith(".tmp-");
|
|
2647
|
+
var STORED = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".heic", ".mp4", ".m4v", ".mov", ".webm", ".mp3", ".m4a", ".aac", ".ogg", ".opus", ".flac", ".woff", ".woff2", ".zip", ".gz", ".br", ".zst", ".7z"]);
|
|
2648
|
+
var MAX_ENTRIES = 65535;
|
|
2649
|
+
var MAX_BYTES = 4294967295;
|
|
2650
|
+
var ArchiveTooLarge = class extends Error {
|
|
2651
|
+
code = "TOO_LARGE";
|
|
2652
|
+
};
|
|
2653
|
+
async function archiveEntries(root, skip = []) {
|
|
2654
|
+
const folder = path12.basename(path12.resolve(root)) || "artifact";
|
|
2655
|
+
const skipped = new Set(skip.map((file) => path12.resolve(file)));
|
|
2656
|
+
const entries = [];
|
|
2657
|
+
const walk = async (dir, rel) => {
|
|
2658
|
+
let names;
|
|
2659
|
+
try {
|
|
2660
|
+
names = await fsp2.readdir(dir, { withFileTypes: true });
|
|
2661
|
+
} catch (error) {
|
|
2662
|
+
if (rel && error.code === "ENOENT") return;
|
|
2663
|
+
throw error;
|
|
2664
|
+
}
|
|
2665
|
+
for (const dirent of names.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
2666
|
+
const name = dirent.name;
|
|
2667
|
+
if (JUNK_FILES.has(name) || isTemp(name) || !rel && (LOCAL_FILES.has(name) || LOCAL_DIRS.has(name) && dirent.isDirectory())) continue;
|
|
2668
|
+
const file = path12.join(dir, name);
|
|
2669
|
+
if (skipped.has(file)) continue;
|
|
2670
|
+
const inner = rel ? `${rel}/${name}` : name;
|
|
2671
|
+
if (dirent.isDirectory()) await walk(file, inner);
|
|
2672
|
+
else if (dirent.isFile()) {
|
|
2673
|
+
const stat = await fsp2.stat(file).catch(() => null);
|
|
2674
|
+
if (stat?.isFile()) entries.push({ name: `${folder}/${inner}`, file, size: stat.size, mode: stat.mode & 511, mtime: stat.mtime });
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
await walk(path12.resolve(root), "");
|
|
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
|
+
const bound = entries.reduce((sum, entry) => sum + entry.size + 2 * (46 + Buffer.byteLength(entry.name)), 22);
|
|
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.`);
|
|
2682
|
+
return entries;
|
|
2683
|
+
}
|
|
2684
|
+
var CRC_TABLE = (() => {
|
|
2685
|
+
const table = new Uint32Array(256);
|
|
2686
|
+
for (let n = 0; n < 256; n++) {
|
|
2687
|
+
let c = n;
|
|
2688
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
2689
|
+
table[n] = c >>> 0;
|
|
2690
|
+
}
|
|
2691
|
+
return table;
|
|
2692
|
+
})();
|
|
2693
|
+
function crc32(bytes) {
|
|
2694
|
+
let crc = 4294967295;
|
|
2695
|
+
for (let i = 0; i < bytes.length; i++) crc = CRC_TABLE[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
|
|
2696
|
+
return (crc ^ 4294967295) >>> 0;
|
|
2697
|
+
}
|
|
2698
|
+
function dosTime(date) {
|
|
2699
|
+
const year = date.getFullYear();
|
|
2700
|
+
if (year < 1980) return { time: 0, date: 1 << 5 | 1 };
|
|
2701
|
+
if (year > 2107) return { time: 23 << 11 | 59 << 5 | 29, date: 127 << 9 | 12 << 5 | 31 };
|
|
2702
|
+
return {
|
|
2703
|
+
time: date.getHours() << 11 | date.getMinutes() << 5 | date.getSeconds() >> 1,
|
|
2704
|
+
date: year - 1980 << 9 | date.getMonth() + 1 << 5 | date.getDate()
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
var deflateRaw = promisify(zlib.deflateRaw);
|
|
2708
|
+
var UTF8 = 1 << 11;
|
|
2709
|
+
async function writeZip(entries, write) {
|
|
2710
|
+
const central = [];
|
|
2711
|
+
let offset = 0;
|
|
2712
|
+
const put = async (chunk) => {
|
|
2713
|
+
await write(chunk);
|
|
2714
|
+
offset += chunk.length;
|
|
2715
|
+
};
|
|
2716
|
+
for (const entry of entries) {
|
|
2717
|
+
let data;
|
|
2718
|
+
try {
|
|
2719
|
+
data = await fsp2.readFile(entry.file);
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
if (error.code === "ENOENT") continue;
|
|
2722
|
+
throw error;
|
|
2723
|
+
}
|
|
2724
|
+
const crc = crc32(data);
|
|
2725
|
+
let method = 0;
|
|
2726
|
+
let body = data;
|
|
2727
|
+
if (data.length > 0 && !STORED.has(path12.extname(entry.name).toLowerCase())) {
|
|
2728
|
+
const deflated = await deflateRaw(data);
|
|
2729
|
+
if (deflated.length < data.length) {
|
|
2730
|
+
method = 8;
|
|
2731
|
+
body = deflated;
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
if (offset + body.length > MAX_BYTES) throw new ArchiveTooLarge("The folder grew past what a zip holds (4 GiB) while it was being zipped.");
|
|
2735
|
+
const name = Buffer.from(entry.name, "utf8");
|
|
2736
|
+
const { time, date } = dosTime(entry.mtime);
|
|
2737
|
+
const local = Buffer.alloc(30);
|
|
2738
|
+
local.writeUInt32LE(67324752, 0);
|
|
2739
|
+
local.writeUInt16LE(20, 4);
|
|
2740
|
+
local.writeUInt16LE(UTF8, 6);
|
|
2741
|
+
local.writeUInt16LE(method, 8);
|
|
2742
|
+
local.writeUInt16LE(time, 10);
|
|
2743
|
+
local.writeUInt16LE(date, 12);
|
|
2744
|
+
local.writeUInt32LE(crc, 14);
|
|
2745
|
+
local.writeUInt32LE(body.length, 18);
|
|
2746
|
+
local.writeUInt32LE(data.length, 22);
|
|
2747
|
+
local.writeUInt16LE(name.length, 26);
|
|
2748
|
+
local.writeUInt16LE(0, 28);
|
|
2749
|
+
const header = Buffer.alloc(46);
|
|
2750
|
+
header.writeUInt32LE(33639248, 0);
|
|
2751
|
+
header.writeUInt16LE(3 << 8 | 20, 4);
|
|
2752
|
+
header.writeUInt16LE(20, 6);
|
|
2753
|
+
header.writeUInt16LE(UTF8, 8);
|
|
2754
|
+
header.writeUInt16LE(method, 10);
|
|
2755
|
+
header.writeUInt16LE(time, 12);
|
|
2756
|
+
header.writeUInt16LE(date, 14);
|
|
2757
|
+
header.writeUInt32LE(crc, 16);
|
|
2758
|
+
header.writeUInt32LE(body.length, 20);
|
|
2759
|
+
header.writeUInt32LE(data.length, 24);
|
|
2760
|
+
header.writeUInt16LE(name.length, 28);
|
|
2761
|
+
header.writeUInt32LE((32768 | (entry.mode || 420) | 384) << 16 >>> 0, 38);
|
|
2762
|
+
header.writeUInt32LE(offset, 42);
|
|
2763
|
+
central.push(header, name);
|
|
2764
|
+
await put(Buffer.concat([local, name]));
|
|
2765
|
+
await put(body);
|
|
2766
|
+
}
|
|
2767
|
+
const start = offset;
|
|
2768
|
+
const directory = Buffer.concat(central);
|
|
2769
|
+
await put(directory);
|
|
2770
|
+
const end = Buffer.alloc(22);
|
|
2771
|
+
end.writeUInt32LE(101010256, 0);
|
|
2772
|
+
end.writeUInt16LE(central.length / 2, 8);
|
|
2773
|
+
end.writeUInt16LE(central.length / 2, 10);
|
|
2774
|
+
end.writeUInt32LE(directory.length, 12);
|
|
2775
|
+
end.writeUInt32LE(start, 16);
|
|
2776
|
+
await put(end);
|
|
2777
|
+
return offset;
|
|
2778
|
+
}
|
|
2779
|
+
function archiveFileName(root, now = /* @__PURE__ */ new Date()) {
|
|
2780
|
+
const day = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2781
|
+
return `${path12.basename(path12.resolve(root)) || "artifact"}-${day}.zip`;
|
|
2782
|
+
}
|
|
2783
|
+
async function writeArchive(root, out) {
|
|
2784
|
+
const file = path12.resolve(out);
|
|
2785
|
+
const entries = await archiveEntries(root, [file]);
|
|
2786
|
+
const tmp = path12.join(path12.dirname(file), `.${path12.basename(file)}.${process.pid}.tmp`);
|
|
2787
|
+
const handle = await fsp2.open(tmp, "w");
|
|
2788
|
+
let bytes;
|
|
2789
|
+
try {
|
|
2790
|
+
bytes = await writeZip(entries, async (chunk) => {
|
|
2791
|
+
await handle.write(chunk);
|
|
2792
|
+
});
|
|
2793
|
+
await handle.close();
|
|
2794
|
+
} catch (error) {
|
|
2795
|
+
await handle.close().catch(() => {
|
|
2796
|
+
});
|
|
2797
|
+
await fsp2.rm(tmp, { force: true });
|
|
2798
|
+
throw error;
|
|
2799
|
+
}
|
|
2800
|
+
await fsp2.rename(tmp, file);
|
|
2801
|
+
return { file, files: entries.length, bytes };
|
|
2802
|
+
}
|
|
2803
|
+
async function archiveCommand(root, out) {
|
|
2804
|
+
const where = out ? path12.resolve(out) : path12.dirname(path12.resolve(root));
|
|
2805
|
+
const target = fs10.statSync(where, { throwIfNoEntry: false })?.isDirectory() ? path12.join(where, archiveFileName(root)) : where;
|
|
2806
|
+
const { file, files, bytes } = await writeArchive(root, target);
|
|
2807
|
+
return { file, folder: path12.basename(path12.resolve(root)), files, bytes, leftOut: [...LOCAL_FILES] };
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2636
2810
|
async function shareCall(url, action, fail2) {
|
|
2637
2811
|
const response = await fetch(new URL("/__share", url), action ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(action) } : {});
|
|
2638
2812
|
const body = await response.json().catch(() => ({}));
|
|
@@ -2683,13 +2857,20 @@ async function shareCommand(o) {
|
|
|
2683
2857
|
for (const change of changes) view = await shareCall(server.url, change, o.fail);
|
|
2684
2858
|
if (off || !view.lan) view = await shareCall(server.url, { action: "lan", on: !off }, o.fail);
|
|
2685
2859
|
const links = view.links;
|
|
2860
|
+
const shown = (list) => Object.fromEntries(Object.entries(list).map(([role, link]) => [role, link.on ? link.url : "off"]));
|
|
2861
|
+
const tailnet = view.lan ? view.tailnet : void 0;
|
|
2686
2862
|
return {
|
|
2687
2863
|
lan: view.lan,
|
|
2688
2864
|
...view.lan ? { addresses: lanUrls(view) } : {},
|
|
2689
|
-
links:
|
|
2865
|
+
links: shown(links),
|
|
2866
|
+
...tailnet ? {
|
|
2867
|
+
tailscale: shown(tailnet.links),
|
|
2868
|
+
tailscaleNote: `This computer has a Tailscale address (${tailnet.address}): the tailscale links open the same roles for people on your tailnet, from any network. Use the links above for people on this Wi-Fi or LAN.`
|
|
2869
|
+
} : {},
|
|
2690
2870
|
guests: view.guests,
|
|
2691
2871
|
...view.lan ? {
|
|
2692
2872
|
note: "Send the link for the role you want to give: editor edits, commenter comments, viewer reads. Anyone on this network with a link joins under a name they type. The owner (this computer) shares from the Share button too.",
|
|
2873
|
+
security: "It is plain http: others on the network can read what passes. A link is the permission: whoever has it gets its role, so send it only to the people meant to have it, and reset a role's link if it went further.",
|
|
2693
2874
|
stop: o.offCommand
|
|
2694
2875
|
} : { note: "Not shared on the network: the links work on this computer only." }
|
|
2695
2876
|
};
|
|
@@ -2707,8 +2888,8 @@ process.emitWarning = ((warning, ...rest) => {
|
|
|
2707
2888
|
});
|
|
2708
2889
|
var KIND = "slides";
|
|
2709
2890
|
var SCRIPT = fileURLToPath2(import.meta.url);
|
|
2710
|
-
var EDITOR_DIR =
|
|
2711
|
-
var SERVER_SCRIPT =
|
|
2891
|
+
var EDITOR_DIR = path13.join(path13.dirname(SCRIPT), "editor");
|
|
2892
|
+
var SERVER_SCRIPT = path13.join(path13.dirname(SCRIPT), "server.mjs");
|
|
2712
2893
|
var MAX_LISTED = 200;
|
|
2713
2894
|
var UPLOAD_MAX_BYTES = 20 * 1024 * 1024;
|
|
2714
2895
|
var INDEX_FILE = KIND === "slides" ? "deck.json" : KIND === "design" ? CANVAS_FILE : DESIGN_SYSTEM_FILE;
|
|
@@ -2747,13 +2928,13 @@ function quote(s) {
|
|
|
2747
2928
|
}
|
|
2748
2929
|
function resolveArtifact(given, mustExist = true) {
|
|
2749
2930
|
if (!given) throw new CliError("MISSING_ROOT", `Give the artifact folder: the one that holds project/${INDEX_FILE}.`);
|
|
2750
|
-
let root =
|
|
2751
|
-
if (
|
|
2752
|
-
root =
|
|
2931
|
+
let root = path13.resolve(given);
|
|
2932
|
+
if (path13.basename(root) === "project" && fs11.existsSync(path13.join(root, INDEX_FILE)) && !fs11.existsSync(path13.join(root, "project"))) {
|
|
2933
|
+
root = path13.dirname(root);
|
|
2753
2934
|
}
|
|
2754
|
-
const project =
|
|
2935
|
+
const project = path13.join(root, "project");
|
|
2755
2936
|
if (mustExist) {
|
|
2756
|
-
const stat =
|
|
2937
|
+
const stat = fs11.statSync(project, { throwIfNoEntry: false });
|
|
2757
2938
|
if (!stat?.isDirectory()) {
|
|
2758
2939
|
throw new CliError("NOT_FOUND", `${project} does not exist. Write project/${INDEX_FILE} and the other files under ${root} first.`);
|
|
2759
2940
|
}
|
|
@@ -2775,7 +2956,7 @@ function localize(value) {
|
|
|
2775
2956
|
function designSystemNotes(project, indexFile) {
|
|
2776
2957
|
let index;
|
|
2777
2958
|
try {
|
|
2778
|
-
index = JSON.parse(
|
|
2959
|
+
index = JSON.parse(fs11.readFileSync(path13.join(project, indexFile), "utf8"));
|
|
2779
2960
|
} catch {
|
|
2780
2961
|
return [];
|
|
2781
2962
|
}
|
|
@@ -2789,10 +2970,10 @@ function designSystemNotes(project, indexFile) {
|
|
|
2789
2970
|
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>/.`);
|
|
2790
2971
|
continue;
|
|
2791
2972
|
}
|
|
2792
|
-
const folder =
|
|
2793
|
-
if (!
|
|
2973
|
+
const folder = path13.join(project, "ds", ns);
|
|
2974
|
+
if (!fs11.existsSync(folder)) {
|
|
2794
2975
|
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.`);
|
|
2795
|
-
} else if (!
|
|
2976
|
+
} else if (!fs11.existsSync(path13.join(folder, "tokens.json"))) {
|
|
2796
2977
|
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).`);
|
|
2797
2978
|
}
|
|
2798
2979
|
}
|
|
@@ -2804,9 +2985,9 @@ async function check(a) {
|
|
|
2804
2985
|
try {
|
|
2805
2986
|
rt = loadSlidesCompiler(EDITOR_DIR);
|
|
2806
2987
|
} catch {
|
|
2807
|
-
throw new CliError("COMPILER_MISSING", `The slides compiler is missing (${
|
|
2988
|
+
throw new CliError("COMPILER_MISSING", `The slides compiler is missing (${path13.join(EDITOR_DIR, "node", "compiler.cjs")}); reinstall the skill.`);
|
|
2808
2989
|
}
|
|
2809
|
-
if (!
|
|
2990
|
+
if (!fs11.existsSync(path13.join(a.project, "deck.json"))) {
|
|
2810
2991
|
return {
|
|
2811
2992
|
problems: 1,
|
|
2812
2993
|
result: {
|
|
@@ -2870,11 +3051,11 @@ async function check(a) {
|
|
|
2870
3051
|
}
|
|
2871
3052
|
const r = await checkDesignSystem(a.project);
|
|
2872
3053
|
const problems = r.issues.length;
|
|
2873
|
-
if (
|
|
2874
|
-
if (!
|
|
3054
|
+
if (fs11.existsSync(path13.join(a.project, DESIGN_SYSTEM_FILE))) {
|
|
3055
|
+
if (!fs11.existsSync(path13.join(a.project, "README.md"))) {
|
|
2875
3056
|
r.notes.push({ file: "README.md", message: "missing: every system includes project/README.md; every reader starts there (Creating, step 1)" });
|
|
2876
3057
|
}
|
|
2877
|
-
if (!
|
|
3058
|
+
if (!fs11.existsSync(path13.join(a.project, "components", "Cover", "preview.html"))) {
|
|
2878
3059
|
r.notes.push({ file: "components/Cover/preview.html", message: "missing: every system gets a cover, written last before the index (cover.md)" });
|
|
2879
3060
|
}
|
|
2880
3061
|
}
|
|
@@ -2900,11 +3081,11 @@ function formatBytes(n) {
|
|
|
2900
3081
|
}
|
|
2901
3082
|
async function upload(a, given, group) {
|
|
2902
3083
|
if (!given) throw new CliError("MISSING_FILE", "Give the file to upload.");
|
|
2903
|
-
const file =
|
|
2904
|
-
const stat = await
|
|
3084
|
+
const file = path13.resolve(given);
|
|
3085
|
+
const stat = await fsp3.stat(file).catch(() => null);
|
|
2905
3086
|
if (!stat) throw new CliError("FILE_NOT_FOUND", `No such file: ${given}`);
|
|
2906
3087
|
if (!stat.isFile()) throw new CliError("NOT_A_FILE", `${given} is not a regular file.`);
|
|
2907
|
-
const ext =
|
|
3088
|
+
const ext = path13.extname(file).slice(1).toLowerCase();
|
|
2908
3089
|
const type = contentTypeOf(file);
|
|
2909
3090
|
const isMedia = type.startsWith("image/") || type.startsWith("font/");
|
|
2910
3091
|
let limit = UPLOAD_MAX_BYTES;
|
|
@@ -2927,9 +3108,9 @@ async function upload(a, given, group) {
|
|
|
2927
3108
|
limit = designSystemUploadLimit(type);
|
|
2928
3109
|
}
|
|
2929
3110
|
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.`);
|
|
2930
|
-
const bytes = await
|
|
3111
|
+
const bytes = await fsp3.readFile(file);
|
|
2931
3112
|
const uploaded = await new DeckStore(a.project, LAYOUT).putBlob(bytes, type);
|
|
2932
|
-
const base =
|
|
3113
|
+
const base = path13.basename(file);
|
|
2933
3114
|
if (KIND !== "design-system") {
|
|
2934
3115
|
return {
|
|
2935
3116
|
url: uploaded.url,
|
|
@@ -2961,12 +3142,12 @@ function folderName(name) {
|
|
|
2961
3142
|
}
|
|
2962
3143
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "vendor", "target"]);
|
|
2963
3144
|
async function listSystems(dirs) {
|
|
2964
|
-
const extra = (process.env.CLAUDE_DESIGN_SYSTEMS ?? "").split(
|
|
2965
|
-
const starts = [...dirs.length > 0 ? dirs : [process.cwd()], ...extra].map((d) =>
|
|
3145
|
+
const extra = (process.env.CLAUDE_DESIGN_SYSTEMS ?? "").split(path13.delimiter).filter(Boolean);
|
|
3146
|
+
const starts = [...dirs.length > 0 ? dirs : [process.cwd()], ...extra].map((d) => path13.resolve(d));
|
|
2966
3147
|
const found = /* @__PURE__ */ new Map();
|
|
2967
3148
|
const visit = async (dir, depth) => {
|
|
2968
|
-
const project =
|
|
2969
|
-
if (
|
|
3149
|
+
const project = path13.join(dir, "project");
|
|
3150
|
+
if (fs11.existsSync(path13.join(project, DESIGN_SYSTEM_FILE)) && !found.has(dir)) {
|
|
2970
3151
|
const info = await readDesignSystemInfo(project).catch(() => null);
|
|
2971
3152
|
if (info) {
|
|
2972
3153
|
const namespace = info.namespace;
|
|
@@ -2975,17 +3156,17 @@ async function listSystems(dirs) {
|
|
|
2975
3156
|
namespace: namespace ?? null,
|
|
2976
3157
|
folder: folderName(typeof namespace === "string" && namespace.trim() ? namespace : String(info.title ?? "")),
|
|
2977
3158
|
root: dir,
|
|
2978
|
-
readme:
|
|
2979
|
-
tokens:
|
|
3159
|
+
readme: fs11.existsSync(path13.join(project, "README.md")) ? path13.join(project, "README.md") : null,
|
|
3160
|
+
tokens: fs11.existsSync(path13.join(project, "tokens.json")) ? path13.join(project, "tokens.json") : null
|
|
2980
3161
|
});
|
|
2981
3162
|
return;
|
|
2982
3163
|
}
|
|
2983
3164
|
}
|
|
2984
3165
|
if (depth <= 0) return;
|
|
2985
|
-
const entries = await
|
|
3166
|
+
const entries = await fsp3.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2986
3167
|
for (const entry of entries) {
|
|
2987
3168
|
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIP_DIRS.has(entry.name) || entry.name === "project") continue;
|
|
2988
|
-
await visit(
|
|
3169
|
+
await visit(path13.join(dir, entry.name), depth - 1);
|
|
2989
3170
|
}
|
|
2990
3171
|
};
|
|
2991
3172
|
for (const start of starts) await visit(start, 4);
|
|
@@ -3009,10 +3190,10 @@ function reusedNote(a, flags) {
|
|
|
3009
3190
|
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.` } : {};
|
|
3010
3191
|
}
|
|
3011
3192
|
function assertServerFiles() {
|
|
3012
|
-
if (!
|
|
3193
|
+
if (!fs11.existsSync(path13.join(EDITOR_DIR, "index.html"))) {
|
|
3013
3194
|
throw new CliError("EDITOR_MISSING", `The editor page is missing (${EDITOR_DIR}); reinstall the skill.`);
|
|
3014
3195
|
}
|
|
3015
|
-
if (!
|
|
3196
|
+
if (!fs11.existsSync(SERVER_SCRIPT)) {
|
|
3016
3197
|
throw new CliError("EDITOR_MISSING", `The editor server is missing (${SERVER_SCRIPT}); reinstall the skill.`);
|
|
3017
3198
|
}
|
|
3018
3199
|
}
|
|
@@ -3045,16 +3226,16 @@ async function open(a, flags) {
|
|
|
3045
3226
|
}
|
|
3046
3227
|
const state = await startBackground(a, flags);
|
|
3047
3228
|
const view = await shareCall2(state.url, null).catch(() => null);
|
|
3048
|
-
print({ url: state.url, root: a.root, pid: state.pid, ...view?.lan ? { lan: lanUrls(view) } : {}, log:
|
|
3229
|
+
print({ url: state.url, root: a.root, pid: state.pid, ...view?.lan ? { lan: lanUrls(view) } : {}, log: path13.join(a.root, LOG_FILE), stop: script("stop", a.root) });
|
|
3049
3230
|
}
|
|
3050
3231
|
async function startBackground(a, flags) {
|
|
3051
3232
|
const port = Number(flags.get("port") ?? 0);
|
|
3052
3233
|
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new CliError("INVALID_PORT", "--port is a number from 0 to 65535.");
|
|
3053
|
-
const logFile =
|
|
3054
|
-
const fd =
|
|
3234
|
+
const logFile = path13.join(a.root, LOG_FILE);
|
|
3235
|
+
const fd = fs11.openSync(logFile, "a");
|
|
3055
3236
|
const child = spawn(process.execPath, [SERVER_SCRIPT, a.root, ...serverArgs(flags)], { detached: true, stdio: ["ignore", "ignore", fd] });
|
|
3056
3237
|
child.unref();
|
|
3057
|
-
|
|
3238
|
+
fs11.closeSync(fd);
|
|
3058
3239
|
let exited = false;
|
|
3059
3240
|
child.once("exit", () => exited = true);
|
|
3060
3241
|
const deadline = Date.now() + 15e3;
|
|
@@ -3064,14 +3245,14 @@ async function startBackground(a, flags) {
|
|
|
3064
3245
|
if (state && state.pid === child.pid) return state;
|
|
3065
3246
|
if (exited) break;
|
|
3066
3247
|
}
|
|
3067
|
-
const tail =
|
|
3248
|
+
const tail = fs11.existsSync(logFile) ? fs11.readFileSync(logFile, "utf8").split("\n").slice(-12).join("\n") : "";
|
|
3068
3249
|
throw new CliError("SERVER_FAILED", `The editor server did not start. Run ${script("serve", a.root)} in the foreground to see why.${tail ? `
|
|
3069
3250
|
${tail}` : ""}`);
|
|
3070
3251
|
}
|
|
3071
3252
|
async function stop(a) {
|
|
3072
3253
|
const state = readState(a.root);
|
|
3073
3254
|
if (!state || !alive(state.pid)) {
|
|
3074
|
-
|
|
3255
|
+
fs11.rmSync(path13.join(a.root, STATE_FILE), { force: true });
|
|
3075
3256
|
print({ stopped: false, message: "No editor server is running for this folder." });
|
|
3076
3257
|
return;
|
|
3077
3258
|
}
|
|
@@ -3082,7 +3263,7 @@ async function stop(a) {
|
|
|
3082
3263
|
print({ stopped: false, pid: state.pid, message: `The server did not exit within 5 s; end it with: kill ${state.pid}` });
|
|
3083
3264
|
return;
|
|
3084
3265
|
}
|
|
3085
|
-
|
|
3266
|
+
fs11.rmSync(path13.join(a.root, STATE_FILE), { force: true });
|
|
3086
3267
|
print({ stopped: true, pid: state.pid });
|
|
3087
3268
|
}
|
|
3088
3269
|
async function share(a, flags) {
|
|
@@ -3126,7 +3307,7 @@ async function listComments(a, all) {
|
|
|
3126
3307
|
async function artifactTitle(a) {
|
|
3127
3308
|
const info = await readInfo(a.project).catch(() => null);
|
|
3128
3309
|
const title = typeof info?.title === "string" ? info.title.trim() : "";
|
|
3129
|
-
return title && title !== "project" ? title :
|
|
3310
|
+
return title && title !== "project" ? title : path13.basename(a.root);
|
|
3130
3311
|
}
|
|
3131
3312
|
async function waitComments(a, flags) {
|
|
3132
3313
|
const timeout = flags.has("timeout") ? Number(flags.get("timeout")) : Infinity;
|
|
@@ -3226,6 +3407,8 @@ Usage:
|
|
|
3226
3407
|
node scripts/artifact.mjs comments <root> --wait [--timeout s] wait for a comment sent to the agent from the open editor
|
|
3227
3408
|
node scripts/artifact.mjs comment-reply <root> <id> <text|-> reply as the agent (-: text on stdin) [--resolve]
|
|
3228
3409
|
node scripts/artifact.mjs comment-resolve <root> <id> [--reopen] mark a comment resolved (or open again)
|
|
3410
|
+
node scripts/artifact.mjs archive <root> [--out file.zip|dir] the whole folder as one zip (backup, another computer,
|
|
3411
|
+
someone else); beside the folder unless --out says
|
|
3229
3412
|
|
|
3230
3413
|
<root> is the artifact folder: its files live at <root>/project/<path> (project/${INDEX_FILE}, \u2026).`;
|
|
3231
3414
|
async function main() {
|
|
@@ -3251,10 +3434,10 @@ async function main() {
|
|
|
3251
3434
|
}
|
|
3252
3435
|
case "upload": {
|
|
3253
3436
|
const a = resolveArtifact(positional[0], false);
|
|
3254
|
-
if (!
|
|
3437
|
+
if (!fs11.statSync(a.root, { throwIfNoEntry: false })?.isDirectory()) {
|
|
3255
3438
|
throw new CliError("NOT_FOUND", `${a.root} does not exist. Make the artifact folder first.`);
|
|
3256
3439
|
}
|
|
3257
|
-
|
|
3440
|
+
fs11.mkdirSync(a.project, { recursive: true });
|
|
3258
3441
|
print(await upload(a, positional[1], flags.get("group")));
|
|
3259
3442
|
return 0;
|
|
3260
3443
|
}
|
|
@@ -3272,6 +3455,12 @@ async function main() {
|
|
|
3272
3455
|
case "comment-resolve":
|
|
3273
3456
|
print(await resolveComment(resolveArtifact(positional[0]), positional[1], flags.has("reopen")));
|
|
3274
3457
|
return 0;
|
|
3458
|
+
case "archive": {
|
|
3459
|
+
const a = resolveArtifact(positional[0]);
|
|
3460
|
+
const made = await archiveCommand(a.root, flags.get("out"));
|
|
3461
|
+
print({ ...made, open: `unzip it anywhere, then ${script("open", `<there>/${made.folder}`)}` });
|
|
3462
|
+
return 0;
|
|
3463
|
+
}
|
|
3275
3464
|
case void 0:
|
|
3276
3465
|
case "help":
|
|
3277
3466
|
case "--help":
|