dsh-claude-slides 2.7.1 → 2.8.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/NOTICE.md +2 -1
- package/lib/client.js +1 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/skills/claude-slides/SKILL.md +4 -3
- package/skills/claude-slides/scripts/artifact.mjs +359 -85
- 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 +1149 -411
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// claude-slides: local Artifact tool (prebuilt; do not edit).
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import fs11 from "node:fs";
|
|
4
|
+
import fs13 from "node:fs";
|
|
6
5
|
import fsp3 from "node:fs/promises";
|
|
7
|
-
import
|
|
6
|
+
import path15 from "node:path";
|
|
8
7
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
9
8
|
|
|
10
9
|
var BLOB_ID_PATTERN = /^[0-9a-f]{32}$/;
|
|
@@ -2501,12 +2500,15 @@ async function runningServer(root, kind, probe) {
|
|
|
2501
2500
|
}
|
|
2502
2501
|
}
|
|
2503
2502
|
var DEFAULT_PORTS = { slides: 4710, design: 4720, "design-system": 4730, dashboard: 4740, docs: 4750, motion: 4760 };
|
|
2503
|
+
var PORT_TRIES = 100;
|
|
2504
|
+
function serverOrigin(host, port) {
|
|
2505
|
+
const shown = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host.includes(":") ? `[${host}]` : host;
|
|
2506
|
+
return `http://${shown}:${port}`;
|
|
2507
|
+
}
|
|
2504
2508
|
|
|
2509
|
+
import { spawn } from "node:child_process";
|
|
2505
2510
|
import fs10 from "node:fs";
|
|
2506
|
-
import fsp2 from "node:fs/promises";
|
|
2507
2511
|
import path12 from "node:path";
|
|
2508
|
-
import { promisify } from "node:util";
|
|
2509
|
-
import zlib from "node:zlib";
|
|
2510
2512
|
|
|
2511
2513
|
import crypto from "node:crypto";
|
|
2512
2514
|
import fs9 from "node:fs";
|
|
@@ -2640,6 +2642,88 @@ var Sharing = class _Sharing {
|
|
|
2640
2642
|
}
|
|
2641
2643
|
};
|
|
2642
2644
|
|
|
2645
|
+
var MAX_MESSAGE = 64 * 1024 * 1024;
|
|
2646
|
+
|
|
2647
|
+
var HUB_ROUTE = "/__hub";
|
|
2648
|
+
function buildOf(script2) {
|
|
2649
|
+
const real = fs10.realpathSync(script2);
|
|
2650
|
+
const { mtimeMs, size } = fs10.statSync(real);
|
|
2651
|
+
return `${real}:${mtimeMs}:${size}`;
|
|
2652
|
+
}
|
|
2653
|
+
async function hubAt(origin) {
|
|
2654
|
+
try {
|
|
2655
|
+
const response = await fetch(`${origin}${HUB_ROUTE}`, { signal: AbortSignal.timeout(1e3) });
|
|
2656
|
+
if (!response.ok) return null;
|
|
2657
|
+
const info = await response.json();
|
|
2658
|
+
return info.hub === 1 ? info : null;
|
|
2659
|
+
} catch {
|
|
2660
|
+
return null;
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
async function joinHub(call, below = Infinity) {
|
|
2664
|
+
if (call.port === 0) return null;
|
|
2665
|
+
const ports = call.portGiven ? [call.port] : Array.from({ length: PORT_TRIES }, (_, i) => call.port + i).filter((port) => port <= 65535 && port < below);
|
|
2666
|
+
const build = buildOf(call.script);
|
|
2667
|
+
const origins = ports.map((port) => serverOrigin(call.host, port));
|
|
2668
|
+
const infos = await Promise.all(origins.map(hubAt));
|
|
2669
|
+
for (const [i, info] of infos.entries()) {
|
|
2670
|
+
if (!info || info.kind !== call.kind || info.build !== build || info.host !== call.host) continue;
|
|
2671
|
+
const response = await fetch(`${origins[i]}${HUB_ROUTE}`, {
|
|
2672
|
+
method: "POST",
|
|
2673
|
+
headers: { "Content-Type": "application/json" },
|
|
2674
|
+
body: JSON.stringify({ action: "mount", root: call.root, ...call.agentHook ? { agentHook: call.agentHook } : {}, lan: call.lan === true }),
|
|
2675
|
+
signal: AbortSignal.timeout(2e4)
|
|
2676
|
+
}).catch(() => null);
|
|
2677
|
+
if (!response) continue;
|
|
2678
|
+
const body = await response.json().catch(() => ({}));
|
|
2679
|
+
if (response.ok && body.state) return body.state;
|
|
2680
|
+
if (response.status === 409) continue;
|
|
2681
|
+
throw Object.assign(new Error(body.message ?? `the server answered ${response.status}`), { code: body.code ?? "SERVER_FAILED" });
|
|
2682
|
+
}
|
|
2683
|
+
return null;
|
|
2684
|
+
}
|
|
2685
|
+
async function leaveHub(state) {
|
|
2686
|
+
let remaining = 0;
|
|
2687
|
+
const response = await fetch(`${new URL(state.url).origin}${HUB_ROUTE}`, {
|
|
2688
|
+
method: "POST",
|
|
2689
|
+
headers: { "Content-Type": "application/json" },
|
|
2690
|
+
body: JSON.stringify({ action: "unmount", root: state.root }),
|
|
2691
|
+
signal: AbortSignal.timeout(5e3)
|
|
2692
|
+
}).catch(() => null);
|
|
2693
|
+
const body = response ? await response.json().catch(() => null) : null;
|
|
2694
|
+
if (body?.hub === 1) remaining = Number(body.remaining ?? 0);
|
|
2695
|
+
else if (alive(state.pid)) process.kill(state.pid, "SIGTERM");
|
|
2696
|
+
const deadline = Date.now() + 5e3;
|
|
2697
|
+
while (remaining === 0 && alive(state.pid) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2698
|
+
if (remaining === 0 && alive(state.pid)) return false;
|
|
2699
|
+
fs10.rmSync(path12.join(state.root, STATE_FILE), { force: true });
|
|
2700
|
+
return true;
|
|
2701
|
+
}
|
|
2702
|
+
async function openOnHub(call) {
|
|
2703
|
+
const joined = await joinHub(call);
|
|
2704
|
+
if (joined) return { state: joined };
|
|
2705
|
+
const logFile = path12.join(call.root, LOG_FILE);
|
|
2706
|
+
const fd = fs10.openSync(logFile, "a");
|
|
2707
|
+
const child = spawn(process.execPath, [...call.command, "--background"], { detached: true, stdio: ["ignore", "ignore", fd] });
|
|
2708
|
+
child.unref();
|
|
2709
|
+
fs10.closeSync(fd);
|
|
2710
|
+
let exited = false;
|
|
2711
|
+
child.once("exit", () => exited = true);
|
|
2712
|
+
const deadline = Date.now() + 15e3;
|
|
2713
|
+
while (Date.now() < deadline) {
|
|
2714
|
+
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
2715
|
+
const state = await call.running();
|
|
2716
|
+
if (state) return { state };
|
|
2717
|
+
if (exited && !readState(call.root)) break;
|
|
2718
|
+
}
|
|
2719
|
+
return { tail: fs10.existsSync(logFile) ? fs10.readFileSync(logFile, "utf8").split("\n").slice(-12).join("\n") : "" };
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
import fs11 from "node:fs";
|
|
2723
|
+
import fsp2 from "node:fs/promises";
|
|
2724
|
+
import path13 from "node:path";
|
|
2725
|
+
import { promisify } from "node:util";
|
|
2726
|
+
import zlib from "node:zlib";
|
|
2643
2727
|
var LOCAL_FILES = /* @__PURE__ */ new Set([STATE_FILE, LOG_FILE, SHARING_FILE]);
|
|
2644
2728
|
var LOCAL_DIRS = /* @__PURE__ */ new Set([EXPORTS_DIR]);
|
|
2645
2729
|
var JUNK_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
|
|
@@ -2651,10 +2735,10 @@ var ArchiveTooLarge = class extends Error {
|
|
|
2651
2735
|
code = "TOO_LARGE";
|
|
2652
2736
|
};
|
|
2653
2737
|
async function archiveEntries(root, skip = []) {
|
|
2654
|
-
const folder =
|
|
2655
|
-
const skipped = new Set(skip.map((file) =>
|
|
2738
|
+
const folder = path13.basename(path13.resolve(root)) || "artifact";
|
|
2739
|
+
const skipped = new Set(skip.map((file) => path13.resolve(file)));
|
|
2656
2740
|
const entries = [];
|
|
2657
|
-
const
|
|
2741
|
+
const walk2 = async (dir, rel) => {
|
|
2658
2742
|
let names;
|
|
2659
2743
|
try {
|
|
2660
2744
|
names = await fsp2.readdir(dir, { withFileTypes: true });
|
|
@@ -2665,17 +2749,17 @@ async function archiveEntries(root, skip = []) {
|
|
|
2665
2749
|
for (const dirent of names.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
2666
2750
|
const name = dirent.name;
|
|
2667
2751
|
if (JUNK_FILES.has(name) || isTemp(name) || !rel && (LOCAL_FILES.has(name) || LOCAL_DIRS.has(name) && dirent.isDirectory())) continue;
|
|
2668
|
-
const file =
|
|
2752
|
+
const file = path13.join(dir, name);
|
|
2669
2753
|
if (skipped.has(file)) continue;
|
|
2670
2754
|
const inner = rel ? `${rel}/${name}` : name;
|
|
2671
|
-
if (dirent.isDirectory()) await
|
|
2755
|
+
if (dirent.isDirectory()) await walk2(file, inner);
|
|
2672
2756
|
else if (dirent.isFile()) {
|
|
2673
2757
|
const stat = await fsp2.stat(file).catch(() => null);
|
|
2674
2758
|
if (stat?.isFile()) entries.push({ name: `${folder}/${inner}`, file, size: stat.size, mode: stat.mode & 511, mtime: stat.mtime });
|
|
2675
2759
|
}
|
|
2676
2760
|
}
|
|
2677
2761
|
};
|
|
2678
|
-
await
|
|
2762
|
+
await walk2(path13.resolve(root), "");
|
|
2679
2763
|
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
2764
|
const bound = entries.reduce((sum, entry) => sum + entry.size + 2 * (46 + Buffer.byteLength(entry.name)), 22);
|
|
2681
2765
|
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.`);
|
|
@@ -2724,7 +2808,7 @@ async function writeZip(entries, write) {
|
|
|
2724
2808
|
const crc = crc32(data);
|
|
2725
2809
|
let method = 0;
|
|
2726
2810
|
let body = data;
|
|
2727
|
-
if (data.length > 0 && !STORED.has(
|
|
2811
|
+
if (data.length > 0 && !STORED.has(path13.extname(entry.name).toLowerCase())) {
|
|
2728
2812
|
const deflated = await deflateRaw(data);
|
|
2729
2813
|
if (deflated.length < data.length) {
|
|
2730
2814
|
method = 8;
|
|
@@ -2778,12 +2862,12 @@ async function writeZip(entries, write) {
|
|
|
2778
2862
|
}
|
|
2779
2863
|
function archiveFileName(root, now = /* @__PURE__ */ new Date()) {
|
|
2780
2864
|
const day = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2781
|
-
return `${
|
|
2865
|
+
return `${path13.basename(path13.resolve(root)) || "artifact"}-${day}.zip`;
|
|
2782
2866
|
}
|
|
2783
2867
|
async function writeArchive(root, out) {
|
|
2784
|
-
const file =
|
|
2868
|
+
const file = path13.resolve(out);
|
|
2785
2869
|
const entries = await archiveEntries(root, [file]);
|
|
2786
|
-
const tmp =
|
|
2870
|
+
const tmp = path13.join(path13.dirname(file), `.${path13.basename(file)}.${process.pid}.tmp`);
|
|
2787
2871
|
const handle = await fsp2.open(tmp, "w");
|
|
2788
2872
|
let bytes;
|
|
2789
2873
|
try {
|
|
@@ -2801,14 +2885,164 @@ async function writeArchive(root, out) {
|
|
|
2801
2885
|
return { file, files: entries.length, bytes };
|
|
2802
2886
|
}
|
|
2803
2887
|
async function archiveCommand(root, out) {
|
|
2804
|
-
const where = out ?
|
|
2805
|
-
const target =
|
|
2888
|
+
const where = out ? path13.resolve(out) : path13.dirname(path13.resolve(root));
|
|
2889
|
+
const target = fs11.statSync(where, { throwIfNoEntry: false })?.isDirectory() ? path13.join(where, archiveFileName(root)) : where;
|
|
2806
2890
|
const { file, files, bytes } = await writeArchive(root, target);
|
|
2807
|
-
return { file, folder:
|
|
2891
|
+
return { file, folder: path13.basename(path13.resolve(root)), files, bytes, leftOut: [...LOCAL_FILES] };
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
import fs12 from "node:fs";
|
|
2895
|
+
import path14 from "node:path";
|
|
2896
|
+
var FOLDER = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
2897
|
+
var MAX_FILES = 2e3;
|
|
2898
|
+
var MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
2899
|
+
var DESIGN_SYSTEM_FILE2 = "design-system.json";
|
|
2900
|
+
function readRecords(project, indexFile) {
|
|
2901
|
+
let index;
|
|
2902
|
+
try {
|
|
2903
|
+
index = JSON.parse(fs12.readFileSync(path14.join(project, indexFile), "utf8"));
|
|
2904
|
+
} catch {
|
|
2905
|
+
return [];
|
|
2906
|
+
}
|
|
2907
|
+
const list = index?.designSystems;
|
|
2908
|
+
const records = Array.isArray(list) ? list : list && typeof list === "object" ? Object.values(list) : [];
|
|
2909
|
+
const out = [];
|
|
2910
|
+
for (const raw of records) {
|
|
2911
|
+
const r = raw;
|
|
2912
|
+
if (typeof r?.namespace !== "string" || !FOLDER.test(r.namespace)) continue;
|
|
2913
|
+
out.push({
|
|
2914
|
+
namespace: r.namespace,
|
|
2915
|
+
title: typeof r.title === "string" ? r.title : r.namespace,
|
|
2916
|
+
artifact: typeof r.artifact === "string" ? r.artifact : ""
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
return out;
|
|
2920
|
+
}
|
|
2921
|
+
function plain(p, want) {
|
|
2922
|
+
const stat = fs12.lstatSync(p, { throwIfNoEntry: false });
|
|
2923
|
+
return Boolean(stat && !stat.isSymbolicLink() && (want === "file" ? stat.isFile() : stat.isDirectory()));
|
|
2924
|
+
}
|
|
2925
|
+
function walk(dir, prefix = "", out = []) {
|
|
2926
|
+
for (const entry of fs12.readdirSync(path14.join(dir, prefix), { withFileTypes: true })) {
|
|
2927
|
+
if (out.length >= MAX_FILES) break;
|
|
2928
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2929
|
+
if (entry.isDirectory()) walk(dir, rel, out);
|
|
2930
|
+
else if (entry.isFile()) out.push(rel);
|
|
2931
|
+
}
|
|
2932
|
+
return out;
|
|
2933
|
+
}
|
|
2934
|
+
var projectOf = (candidate) => path14.basename(candidate) === "project" ? candidate : path14.join(candidate, "project");
|
|
2935
|
+
function sourceProject(artifact, root, from) {
|
|
2936
|
+
const given = from ?? artifact;
|
|
2937
|
+
if (!given) return { reason: "the record names no source folder", kind: "none" };
|
|
2938
|
+
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" };
|
|
2939
|
+
const candidates = path14.isAbsolute(given) ? [given] : [path14.resolve(given), path14.resolve(root, given), path14.resolve(root, "..", given)];
|
|
2940
|
+
for (const candidate of candidates) {
|
|
2941
|
+
const project = projectOf(candidate);
|
|
2942
|
+
if (plain(project, "dir")) return { project };
|
|
2943
|
+
}
|
|
2944
|
+
if (!from && !path14.isAbsolute(given)) {
|
|
2945
|
+
let dir = path14.resolve(root, "..");
|
|
2946
|
+
for (let up = path14.dirname(dir); up !== dir; up = path14.dirname(dir)) {
|
|
2947
|
+
dir = up;
|
|
2948
|
+
const project = projectOf(path14.resolve(dir, given));
|
|
2949
|
+
if (plain(path14.join(project, DESIGN_SYSTEM_FILE2), "file")) return { project };
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
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" };
|
|
2953
|
+
}
|
|
2954
|
+
function compareSystem(project, root, record, from) {
|
|
2955
|
+
const diff = { namespace: record.namespace, title: record.title, changed: [], removed: [] };
|
|
2956
|
+
const found = sourceProject(record.artifact, root, from);
|
|
2957
|
+
if (!found.project) return { ...diff, skipped: found.reason, skippedKind: found.kind };
|
|
2958
|
+
diff.source = path14.dirname(found.project);
|
|
2959
|
+
const copy = path14.join(project, "ds", record.namespace);
|
|
2960
|
+
if (!plain(copy, "dir")) return { ...diff, missing: true };
|
|
2961
|
+
for (const rel of walk(copy)) {
|
|
2962
|
+
const mine = path14.join(copy, rel);
|
|
2963
|
+
const theirs = path14.join(found.project, rel);
|
|
2964
|
+
if (!plain(mine, "file")) continue;
|
|
2965
|
+
if (!plain(theirs, "file")) {
|
|
2966
|
+
diff.removed.push(rel);
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
2969
|
+
const a = fs12.statSync(mine);
|
|
2970
|
+
const b = fs12.statSync(theirs);
|
|
2971
|
+
if (a.size > MAX_FILE_BYTES || b.size > MAX_FILE_BYTES) continue;
|
|
2972
|
+
if (a.size !== b.size || !fs12.readFileSync(mine).equals(fs12.readFileSync(theirs))) diff.changed.push(rel);
|
|
2973
|
+
}
|
|
2974
|
+
return diff;
|
|
2975
|
+
}
|
|
2976
|
+
function pick(records, namespace) {
|
|
2977
|
+
if (namespace === void 0) return records;
|
|
2978
|
+
const one = records.filter((r) => r.namespace === namespace);
|
|
2979
|
+
if (one.length === 0) {
|
|
2980
|
+
const names = records.map((r) => r.namespace);
|
|
2981
|
+
throw new Error(`No designSystems record has namespace ${JSON.stringify(namespace)}${names.length ? ` (it has: ${names.join(", ")})` : ""}.`);
|
|
2982
|
+
}
|
|
2983
|
+
return one;
|
|
2984
|
+
}
|
|
2985
|
+
function compareSystems(project, root, indexFile, namespace, from) {
|
|
2986
|
+
return pick(readRecords(project, indexFile), namespace).map((r) => compareSystem(project, root, r, from));
|
|
2987
|
+
}
|
|
2988
|
+
function staleNotes(project, root, indexFile, command) {
|
|
2989
|
+
const notes = [];
|
|
2990
|
+
for (const diff of compareSystems(project, root, indexFile)) {
|
|
2991
|
+
if (diff.changed.length === 0 && diff.removed.length === 0) continue;
|
|
2992
|
+
const parts = [];
|
|
2993
|
+
if (diff.changed.length > 0) parts.push(`${diff.changed.length} changed (${diff.changed.slice(0, 3).join(", ")}${diff.changed.length > 3 ? ", \u2026" : ""})`);
|
|
2994
|
+
if (diff.removed.length > 0) parts.push(`${diff.removed.length} gone from the source`);
|
|
2995
|
+
notes.push(
|
|
2996
|
+
`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).`
|
|
2997
|
+
);
|
|
2998
|
+
}
|
|
2999
|
+
return notes;
|
|
3000
|
+
}
|
|
3001
|
+
function stamp(project, indexFile, namespaces, now) {
|
|
3002
|
+
const file = path14.join(project, indexFile);
|
|
3003
|
+
const text = fs12.readFileSync(file, "utf8");
|
|
3004
|
+
const index = JSON.parse(text);
|
|
3005
|
+
const list = index.designSystems;
|
|
3006
|
+
const records = Array.isArray(list) ? list : list && typeof list === "object" ? Object.values(list) : [];
|
|
3007
|
+
for (const r of records) {
|
|
3008
|
+
const record = r;
|
|
3009
|
+
if (record && typeof record.namespace === "string" && namespaces.has(record.namespace)) record.copiedAt = now;
|
|
3010
|
+
}
|
|
3011
|
+
const indent = /^\{\n/.test(text) ? /\n( +|\t)"/.exec(text)?.[1] ?? 2 : void 0;
|
|
3012
|
+
const out = JSON.stringify(index, null, indent);
|
|
3013
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
3014
|
+
fs12.writeFileSync(tmp, text.endsWith("\n") ? `${out}
|
|
3015
|
+
` : out);
|
|
3016
|
+
fs12.renameSync(tmp, file);
|
|
3017
|
+
}
|
|
3018
|
+
function syncSystems(project, root, indexFile, opts) {
|
|
3019
|
+
const diffs = compareSystems(project, root, indexFile, opts.namespace, opts.from);
|
|
3020
|
+
const results = [];
|
|
3021
|
+
const synced = /* @__PURE__ */ new Set();
|
|
3022
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
3023
|
+
for (const diff of diffs) {
|
|
3024
|
+
const result = { ...diff, copied: [] };
|
|
3025
|
+
if (!opts.check && diff.source && !diff.missing) {
|
|
3026
|
+
for (const rel of diff.changed) {
|
|
3027
|
+
const target = path14.join(project, "ds", diff.namespace, rel);
|
|
3028
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
3029
|
+
fs12.copyFileSync(path14.join(diff.source, "project", rel), tmp);
|
|
3030
|
+
fs12.renameSync(tmp, target);
|
|
3031
|
+
result.copied.push(rel);
|
|
3032
|
+
}
|
|
3033
|
+
if (diff.changed.length > 0) synced.add(diff.namespace);
|
|
3034
|
+
}
|
|
3035
|
+
results.push(result);
|
|
3036
|
+
}
|
|
3037
|
+
if (synced.size > 0) {
|
|
3038
|
+
stamp(project, indexFile, synced, now);
|
|
3039
|
+
for (const r of results) if (synced.has(r.namespace)) r.copiedAt = now;
|
|
3040
|
+
}
|
|
3041
|
+
return results;
|
|
2808
3042
|
}
|
|
2809
3043
|
|
|
2810
3044
|
async function shareCall(url, action, fail2) {
|
|
2811
|
-
const response = await fetch(new URL("
|
|
3045
|
+
const response = await fetch(new URL("__share", url), action ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(action) } : {});
|
|
2812
3046
|
const body = await response.json().catch(() => ({}));
|
|
2813
3047
|
if (!response.ok) fail2("SHARE_REFUSED", String(body.message ?? `the server answered ${response.status}`));
|
|
2814
3048
|
return body;
|
|
@@ -2837,6 +3071,7 @@ async function shareCommand(o) {
|
|
|
2837
3071
|
...rolesOf(o.flags, "disable", o.fail).map((role) => ({ action: "link", role, on: false })),
|
|
2838
3072
|
...guestRoles(o.flags, o.fail)
|
|
2839
3073
|
];
|
|
3074
|
+
const open2 = !off && changes.length === 0;
|
|
2840
3075
|
let server = await o.running();
|
|
2841
3076
|
if (!server) {
|
|
2842
3077
|
const sharing = new Sharing(o.root);
|
|
@@ -2850,12 +3085,19 @@ async function shareCommand(o) {
|
|
|
2850
3085
|
sharing.setLan(false);
|
|
2851
3086
|
return { lan: false, message: "Not shared on the local network. Guests already in get their place back when it is shared again (unless their link was reset)." };
|
|
2852
3087
|
}
|
|
3088
|
+
if (!open2 && !sharing.state.lan) {
|
|
3089
|
+
return {
|
|
3090
|
+
lan: false,
|
|
3091
|
+
links: Object.fromEntries(GUEST_ROLES.map((role) => [role, sharing.state.links[role].on ? "on" : "off"])),
|
|
3092
|
+
message: `Links changed. Not shared on the local network: ${NOT_SHARED_HINT}`
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
2853
3095
|
server = await o.startShared();
|
|
2854
3096
|
if (!server) o.fail("SERVER_FAILED", `The server did not start. Run ${o.serveCommand} in the foreground to see why.`);
|
|
2855
3097
|
}
|
|
2856
3098
|
let view = await shareCall(server.url, null, o.fail);
|
|
2857
3099
|
for (const change of changes) view = await shareCall(server.url, change, o.fail);
|
|
2858
|
-
if (off || !view.lan) view = await shareCall(server.url, { action: "lan", on: !off }, o.fail);
|
|
3100
|
+
if (off || open2 && !view.lan) view = await shareCall(server.url, { action: "lan", on: !off }, o.fail);
|
|
2859
3101
|
const links = view.links;
|
|
2860
3102
|
const shown = (list) => Object.fromEntries(Object.entries(list).map(([role, link]) => [role, link.on ? link.url : "off"]));
|
|
2861
3103
|
const tailnet = view.lan ? view.tailnet : void 0;
|
|
@@ -2872,10 +3114,11 @@ async function shareCommand(o) {
|
|
|
2872
3114
|
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
3115
|
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.",
|
|
2874
3116
|
stop: o.offCommand
|
|
2875
|
-
} : { note:
|
|
3117
|
+
} : { note: `Not shared on the network: the links work on this computer only. ${NOT_SHARED_HINT}` }
|
|
2876
3118
|
};
|
|
2877
3119
|
}
|
|
2878
|
-
var
|
|
3120
|
+
var NOT_SHARED_HINT = "share <root> with no options shares it.";
|
|
3121
|
+
var lanUrls = (view) => (Array.isArray(view.addresses) ? view.addresses : []).map((a) => `http://${String(a)}:${String(view.port)}${String(view.page ?? "/")}`);
|
|
2879
3122
|
async function lanOn(url, flags, fail2) {
|
|
2880
3123
|
if (!flags.has("lan")) return {};
|
|
2881
3124
|
return { lan: lanUrls(await shareCall(url, { action: "lan", on: true }, fail2)) };
|
|
@@ -2888,8 +3131,8 @@ process.emitWarning = ((warning, ...rest) => {
|
|
|
2888
3131
|
});
|
|
2889
3132
|
var KIND = "slides";
|
|
2890
3133
|
var SCRIPT = fileURLToPath2(import.meta.url);
|
|
2891
|
-
var EDITOR_DIR =
|
|
2892
|
-
var SERVER_SCRIPT =
|
|
3134
|
+
var EDITOR_DIR = path15.join(path15.dirname(SCRIPT), "editor");
|
|
3135
|
+
var SERVER_SCRIPT = path15.join(path15.dirname(SCRIPT), "server.mjs");
|
|
2893
3136
|
var MAX_LISTED = 200;
|
|
2894
3137
|
var UPLOAD_MAX_BYTES = 20 * 1024 * 1024;
|
|
2895
3138
|
var INDEX_FILE = KIND === "slides" ? "deck.json" : KIND === "design" ? CANVAS_FILE : DESIGN_SYSTEM_FILE;
|
|
@@ -2902,7 +3145,7 @@ var CliError = class extends Error {
|
|
|
2902
3145
|
this.code = code;
|
|
2903
3146
|
}
|
|
2904
3147
|
};
|
|
2905
|
-
var SWITCHES = /* @__PURE__ */ new Set(["all", "wait", "resolve", "reopen", "lan", "off"]);
|
|
3148
|
+
var SWITCHES = /* @__PURE__ */ new Set(["all", "wait", "resolve", "reopen", "lan", "off", "check"]);
|
|
2906
3149
|
function parseArgs(argv) {
|
|
2907
3150
|
const positional = [];
|
|
2908
3151
|
const flags = /* @__PURE__ */ new Map();
|
|
@@ -2928,13 +3171,13 @@ function quote(s) {
|
|
|
2928
3171
|
}
|
|
2929
3172
|
function resolveArtifact(given, mustExist = true) {
|
|
2930
3173
|
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 =
|
|
3174
|
+
let root = path15.resolve(given);
|
|
3175
|
+
if (path15.basename(root) === "project" && fs13.existsSync(path15.join(root, INDEX_FILE)) && !fs13.existsSync(path15.join(root, "project"))) {
|
|
3176
|
+
root = path15.dirname(root);
|
|
2934
3177
|
}
|
|
2935
|
-
const project =
|
|
3178
|
+
const project = path15.join(root, "project");
|
|
2936
3179
|
if (mustExist) {
|
|
2937
|
-
const stat =
|
|
3180
|
+
const stat = fs13.statSync(project, { throwIfNoEntry: false });
|
|
2938
3181
|
if (!stat?.isDirectory()) {
|
|
2939
3182
|
throw new CliError("NOT_FOUND", `${project} does not exist. Write project/${INDEX_FILE} and the other files under ${root} first.`);
|
|
2940
3183
|
}
|
|
@@ -2956,7 +3199,7 @@ function localize(value) {
|
|
|
2956
3199
|
function designSystemNotes(project, indexFile) {
|
|
2957
3200
|
let index;
|
|
2958
3201
|
try {
|
|
2959
|
-
index = JSON.parse(
|
|
3202
|
+
index = JSON.parse(fs13.readFileSync(path15.join(project, indexFile), "utf8"));
|
|
2960
3203
|
} catch {
|
|
2961
3204
|
return [];
|
|
2962
3205
|
}
|
|
@@ -2970,14 +3213,15 @@ function designSystemNotes(project, indexFile) {
|
|
|
2970
3213
|
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
3214
|
continue;
|
|
2972
3215
|
}
|
|
2973
|
-
const folder =
|
|
2974
|
-
if (!
|
|
3216
|
+
const folder = path15.join(project, "ds", ns);
|
|
3217
|
+
if (!fs13.existsSync(folder)) {
|
|
2975
3218
|
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 (!
|
|
3219
|
+
} else if (!fs13.existsSync(path15.join(folder, "tokens.json"))) {
|
|
2977
3220
|
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
3221
|
}
|
|
2979
3222
|
}
|
|
2980
|
-
|
|
3223
|
+
const root = path15.dirname(project);
|
|
3224
|
+
return [...notes, ...staleNotes(project, root, indexFile, (ns, listOnly) => script("ds-sync", root, ns, ...listOnly ? ["--check"] : []))];
|
|
2981
3225
|
}
|
|
2982
3226
|
async function check(a) {
|
|
2983
3227
|
if (KIND === "slides") {
|
|
@@ -2985,9 +3229,9 @@ async function check(a) {
|
|
|
2985
3229
|
try {
|
|
2986
3230
|
rt = loadSlidesCompiler(EDITOR_DIR);
|
|
2987
3231
|
} catch {
|
|
2988
|
-
throw new CliError("COMPILER_MISSING", `The slides compiler is missing (${
|
|
3232
|
+
throw new CliError("COMPILER_MISSING", `The slides compiler is missing (${path15.join(EDITOR_DIR, "node", "compiler.cjs")}); reinstall the skill.`);
|
|
2989
3233
|
}
|
|
2990
|
-
if (!
|
|
3234
|
+
if (!fs13.existsSync(path15.join(a.project, "deck.json"))) {
|
|
2991
3235
|
return {
|
|
2992
3236
|
problems: 1,
|
|
2993
3237
|
result: {
|
|
@@ -3026,7 +3270,7 @@ async function check(a) {
|
|
|
3026
3270
|
...slides.length > 0 ? { slides } : {},
|
|
3027
3271
|
...dsNotes.length > 0 ? { designSystemNotes: dsNotes } : {},
|
|
3028
3272
|
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." : "")
|
|
3273
|
+
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
3274
|
}
|
|
3031
3275
|
};
|
|
3032
3276
|
}
|
|
@@ -3045,17 +3289,17 @@ async function check(a) {
|
|
|
3045
3289
|
...problems2 > MAX_LISTED ? { more: `${problems2 - MAX_LISTED} more not listed: fix these, then check again` } : {},
|
|
3046
3290
|
...dsNotes.length > 0 ? { designSystemNotes: dsNotes } : {},
|
|
3047
3291
|
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." : "")
|
|
3292
|
+
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
3293
|
}
|
|
3050
3294
|
};
|
|
3051
3295
|
}
|
|
3052
3296
|
const r = await checkDesignSystem(a.project);
|
|
3053
3297
|
const problems = r.issues.length;
|
|
3054
|
-
if (
|
|
3055
|
-
if (!
|
|
3298
|
+
if (fs13.existsSync(path15.join(a.project, DESIGN_SYSTEM_FILE))) {
|
|
3299
|
+
if (!fs13.existsSync(path15.join(a.project, "README.md"))) {
|
|
3056
3300
|
r.notes.push({ file: "README.md", message: "missing: every system includes project/README.md; every reader starts there (Creating, step 1)" });
|
|
3057
3301
|
}
|
|
3058
|
-
if (!
|
|
3302
|
+
if (!fs13.existsSync(path15.join(a.project, "components", "Cover", "preview.html"))) {
|
|
3059
3303
|
r.notes.push({ file: "components/Cover/preview.html", message: "missing: every system gets a cover, written last before the index (cover.md)" });
|
|
3060
3304
|
}
|
|
3061
3305
|
}
|
|
@@ -3081,11 +3325,11 @@ function formatBytes(n) {
|
|
|
3081
3325
|
}
|
|
3082
3326
|
async function upload(a, given, group) {
|
|
3083
3327
|
if (!given) throw new CliError("MISSING_FILE", "Give the file to upload.");
|
|
3084
|
-
const file =
|
|
3328
|
+
const file = path15.resolve(given);
|
|
3085
3329
|
const stat = await fsp3.stat(file).catch(() => null);
|
|
3086
3330
|
if (!stat) throw new CliError("FILE_NOT_FOUND", `No such file: ${given}`);
|
|
3087
3331
|
if (!stat.isFile()) throw new CliError("NOT_A_FILE", `${given} is not a regular file.`);
|
|
3088
|
-
const ext =
|
|
3332
|
+
const ext = path15.extname(file).slice(1).toLowerCase();
|
|
3089
3333
|
const type = contentTypeOf(file);
|
|
3090
3334
|
const isMedia = type.startsWith("image/") || type.startsWith("font/");
|
|
3091
3335
|
let limit = UPLOAD_MAX_BYTES;
|
|
@@ -3110,7 +3354,7 @@ async function upload(a, given, group) {
|
|
|
3110
3354
|
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
3355
|
const bytes = await fsp3.readFile(file);
|
|
3112
3356
|
const uploaded = await new DeckStore(a.project, LAYOUT).putBlob(bytes, type);
|
|
3113
|
-
const base =
|
|
3357
|
+
const base = path15.basename(file);
|
|
3114
3358
|
if (KIND !== "design-system") {
|
|
3115
3359
|
return {
|
|
3116
3360
|
url: uploaded.url,
|
|
@@ -3142,12 +3386,12 @@ function folderName(name) {
|
|
|
3142
3386
|
}
|
|
3143
3387
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "vendor", "target"]);
|
|
3144
3388
|
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) =>
|
|
3389
|
+
const extra = (process.env.CLAUDE_DESIGN_SYSTEMS ?? "").split(path15.delimiter).filter(Boolean);
|
|
3390
|
+
const starts = [...dirs.length > 0 ? dirs : [process.cwd()], ...extra].map((d) => path15.resolve(d));
|
|
3147
3391
|
const found = /* @__PURE__ */ new Map();
|
|
3148
3392
|
const visit = async (dir, depth) => {
|
|
3149
|
-
const project =
|
|
3150
|
-
if (
|
|
3393
|
+
const project = path15.join(dir, "project");
|
|
3394
|
+
if (fs13.existsSync(path15.join(project, DESIGN_SYSTEM_FILE)) && !found.has(dir)) {
|
|
3151
3395
|
const info = await readDesignSystemInfo(project).catch(() => null);
|
|
3152
3396
|
if (info) {
|
|
3153
3397
|
const namespace = info.namespace;
|
|
@@ -3156,8 +3400,8 @@ async function listSystems(dirs) {
|
|
|
3156
3400
|
namespace: namespace ?? null,
|
|
3157
3401
|
folder: folderName(typeof namespace === "string" && namespace.trim() ? namespace : String(info.title ?? "")),
|
|
3158
3402
|
root: dir,
|
|
3159
|
-
readme:
|
|
3160
|
-
tokens:
|
|
3403
|
+
readme: fs13.existsSync(path15.join(project, "README.md")) ? path15.join(project, "README.md") : null,
|
|
3404
|
+
tokens: fs13.existsSync(path15.join(project, "tokens.json")) ? path15.join(project, "tokens.json") : null
|
|
3161
3405
|
});
|
|
3162
3406
|
return;
|
|
3163
3407
|
}
|
|
@@ -3166,7 +3410,7 @@ async function listSystems(dirs) {
|
|
|
3166
3410
|
const entries = await fsp3.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
3167
3411
|
for (const entry of entries) {
|
|
3168
3412
|
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIP_DIRS.has(entry.name) || entry.name === "project") continue;
|
|
3169
|
-
await visit(
|
|
3413
|
+
await visit(path15.join(dir, entry.name), depth - 1);
|
|
3170
3414
|
}
|
|
3171
3415
|
};
|
|
3172
3416
|
for (const start of starts) await visit(start, 4);
|
|
@@ -3190,10 +3434,10 @@ function reusedNote(a, flags) {
|
|
|
3190
3434
|
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
3435
|
}
|
|
3192
3436
|
function assertServerFiles() {
|
|
3193
|
-
if (!
|
|
3437
|
+
if (!fs13.existsSync(path15.join(EDITOR_DIR, "index.html"))) {
|
|
3194
3438
|
throw new CliError("EDITOR_MISSING", `The editor page is missing (${EDITOR_DIR}); reinstall the skill.`);
|
|
3195
3439
|
}
|
|
3196
|
-
if (!
|
|
3440
|
+
if (!fs13.existsSync(SERVER_SCRIPT)) {
|
|
3197
3441
|
throw new CliError("EDITOR_MISSING", `The editor server is missing (${SERVER_SCRIPT}); reinstall the skill.`);
|
|
3198
3442
|
}
|
|
3199
3443
|
}
|
|
@@ -3221,49 +3465,48 @@ async function open(a, flags) {
|
|
|
3221
3465
|
assertServerFiles();
|
|
3222
3466
|
const existing = await runningServer2(a.root);
|
|
3223
3467
|
if (existing) {
|
|
3224
|
-
print({ url: existing.url, root: a.root, pid: existing.pid, reused: true, ...await lanOn2(existing.url, flags), log:
|
|
3468
|
+
print({ url: existing.url, root: a.root, pid: existing.pid, reused: true, ...await lanOn2(existing.url, flags), log: path15.join(a.root, LOG_FILE), stop: script("stop", a.root), ...reusedNote(a, flags) });
|
|
3225
3469
|
return;
|
|
3226
3470
|
}
|
|
3227
3471
|
const state = await startBackground(a, flags);
|
|
3228
|
-
|
|
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) });
|
|
3472
|
+
print({ url: state.url, root: a.root, pid: state.pid, ...state.lan?.length ? { lan: state.lan } : {}, log: path15.join(a.root, LOG_FILE), stop: script("stop", a.root) });
|
|
3230
3473
|
}
|
|
3231
3474
|
async function startBackground(a, flags) {
|
|
3232
|
-
const
|
|
3475
|
+
const host = flags.get("host") ?? "127.0.0.1";
|
|
3476
|
+
const port = flags.has("port") ? Number(flags.get("port")) : DEFAULT_PORTS[KIND];
|
|
3233
3477
|
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new CliError("INVALID_PORT", "--port is a number from 0 to 65535.");
|
|
3234
|
-
const
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3478
|
+
const hook = flags.get("agent-hook") || void 0;
|
|
3479
|
+
const opened = await openOnHub({
|
|
3480
|
+
kind: KIND,
|
|
3481
|
+
script: SERVER_SCRIPT,
|
|
3482
|
+
root: a.root,
|
|
3483
|
+
port,
|
|
3484
|
+
portGiven: flags.has("port"),
|
|
3485
|
+
host,
|
|
3486
|
+
...hook ? { agentHook: hook } : {},
|
|
3487
|
+
lan: flags.has("lan"),
|
|
3488
|
+
command: [SERVER_SCRIPT, a.root, ...serverArgs(flags)],
|
|
3489
|
+
running: () => runningServer2(a.root)
|
|
3490
|
+
});
|
|
3491
|
+
if ("state" in opened) {
|
|
3492
|
+
const view = await shareCall2(opened.state.url, null).catch(() => null);
|
|
3493
|
+
return { ...opened.state, ...view?.lan ? { lan: lanUrls(view) } : {} };
|
|
3247
3494
|
}
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
${tail}` : ""}`);
|
|
3495
|
+
throw new CliError("SERVER_FAILED", `The editor server did not start. Run ${script("serve", a.root)} in the foreground to see why.${opened.tail ? `
|
|
3496
|
+
${opened.tail}` : ""}`);
|
|
3251
3497
|
}
|
|
3252
3498
|
async function stop(a) {
|
|
3253
3499
|
const state = readState(a.root);
|
|
3254
3500
|
if (!state || !alive(state.pid)) {
|
|
3255
|
-
|
|
3501
|
+
fs13.rmSync(path15.join(a.root, STATE_FILE), { force: true });
|
|
3256
3502
|
print({ stopped: false, message: "No editor server is running for this folder." });
|
|
3257
3503
|
return;
|
|
3258
3504
|
}
|
|
3259
|
-
|
|
3260
|
-
const deadline = Date.now() + 5e3;
|
|
3261
|
-
while (alive(state.pid) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3262
|
-
if (alive(state.pid)) {
|
|
3505
|
+
if (!await leaveHub(state)) {
|
|
3263
3506
|
print({ stopped: false, pid: state.pid, message: `The server did not exit within 5 s; end it with: kill ${state.pid}` });
|
|
3264
3507
|
return;
|
|
3265
3508
|
}
|
|
3266
|
-
|
|
3509
|
+
fs13.rmSync(path15.join(a.root, STATE_FILE), { force: true });
|
|
3267
3510
|
print({ stopped: true, pid: state.pid });
|
|
3268
3511
|
}
|
|
3269
3512
|
async function share(a, flags) {
|
|
@@ -3307,7 +3550,7 @@ async function listComments(a, all) {
|
|
|
3307
3550
|
async function artifactTitle(a) {
|
|
3308
3551
|
const info = await readInfo(a.project).catch(() => null);
|
|
3309
3552
|
const title = typeof info?.title === "string" ? info.title.trim() : "";
|
|
3310
|
-
return title && title !== "project" ? title :
|
|
3553
|
+
return title && title !== "project" ? title : path15.basename(a.root);
|
|
3311
3554
|
}
|
|
3312
3555
|
async function waitComments(a, flags) {
|
|
3313
3556
|
const timeout = flags.has("timeout") ? Number(flags.get("timeout")) : Infinity;
|
|
@@ -3399,11 +3642,15 @@ Usage:
|
|
|
3399
3642
|
node scripts/artifact.mjs share <root> share on the local network; prints a link per role
|
|
3400
3643
|
[--reset R] [--enable R] [--disable R] R: editor, commenter, viewer (comma list) or all
|
|
3401
3644
|
[--role G=R,...] give guest G (an id it prints) role R
|
|
3645
|
+
(these change links only: shared or not stays as it was)
|
|
3402
3646
|
[--off] stop sharing (guests keep their place for next time)
|
|
3403
3647
|
node scripts/artifact.mjs check <root> validate project/; exit 1 while problems remain
|
|
3404
3648
|
node scripts/artifact.mjs upload <root> <file>${KIND === "design-system" ? " --group G " : " "} store an upload; prints its /_blob/<id> url
|
|
3405
3649
|
node scripts/artifact.mjs list [dir \u2026] find design systems (folders holding project/design-system.json)
|
|
3406
|
-
node scripts/artifact.mjs
|
|
3650
|
+
${KIND === "design-system" ? "" : ` node scripts/artifact.mjs ds-sync <root> [namespace] [--check] an installed design system is a copy: copy its changed files again
|
|
3651
|
+
from the system's folder and stamp copiedAt (--check: list only;
|
|
3652
|
+
--from <folder>: the source, when the record's path is not found)
|
|
3653
|
+
`} node scripts/artifact.mjs comments <root> [--all] the open comments people left in the editor (--all: resolved too)
|
|
3407
3654
|
node scripts/artifact.mjs comments <root> --wait [--timeout s] wait for a comment sent to the agent from the open editor
|
|
3408
3655
|
node scripts/artifact.mjs comment-reply <root> <id> <text|-> reply as the agent (-: text on stdin) [--resolve]
|
|
3409
3656
|
node scripts/artifact.mjs comment-resolve <root> <id> [--reopen] mark a comment resolved (or open again)
|
|
@@ -3434,16 +3681,43 @@ async function main() {
|
|
|
3434
3681
|
}
|
|
3435
3682
|
case "upload": {
|
|
3436
3683
|
const a = resolveArtifact(positional[0], false);
|
|
3437
|
-
if (!
|
|
3684
|
+
if (!fs13.statSync(a.root, { throwIfNoEntry: false })?.isDirectory()) {
|
|
3438
3685
|
throw new CliError("NOT_FOUND", `${a.root} does not exist. Make the artifact folder first.`);
|
|
3439
3686
|
}
|
|
3440
|
-
|
|
3687
|
+
fs13.mkdirSync(a.project, { recursive: true });
|
|
3441
3688
|
print(await upload(a, positional[1], flags.get("group")));
|
|
3442
3689
|
return 0;
|
|
3443
3690
|
}
|
|
3444
3691
|
case "list":
|
|
3445
3692
|
print(await listSystems(positional));
|
|
3446
3693
|
return 0;
|
|
3694
|
+
case "ds-sync": {
|
|
3695
|
+
if (KIND === "design-system") throw new CliError("NOT_APPLICABLE", "A design system has no installed design systems: ds-sync is for decks and canvases.");
|
|
3696
|
+
const a = resolveArtifact(positional[0]);
|
|
3697
|
+
const check2 = flags.has("check");
|
|
3698
|
+
let results;
|
|
3699
|
+
try {
|
|
3700
|
+
results = syncSystems(a.project, a.root, INDEX_FILE, { namespace: positional[1], from: flags.get("from"), check: check2 });
|
|
3701
|
+
} catch (error) {
|
|
3702
|
+
throw new CliError("NO_SUCH_SYSTEM", error.message);
|
|
3703
|
+
}
|
|
3704
|
+
const behind = results.filter((r) => r.changed.length > 0 || r.removed.length > 0);
|
|
3705
|
+
print({
|
|
3706
|
+
root: a.root,
|
|
3707
|
+
systems: results.map((r) => ({
|
|
3708
|
+
namespace: r.namespace,
|
|
3709
|
+
title: r.title,
|
|
3710
|
+
...r.skipped ? { skipped: r.skipped } : {},
|
|
3711
|
+
...r.missing ? { skipped: `project/ds/${r.namespace}/ does not exist: install it first (SKILL.md, Creating, step 4)` } : {},
|
|
3712
|
+
...r.source ? { source: r.source } : {},
|
|
3713
|
+
changed: r.changed,
|
|
3714
|
+
...r.removed.length > 0 ? { goneFromSource: r.removed } : {},
|
|
3715
|
+
...check2 ? {} : { copied: r.copied, ...r.copiedAt ? { copiedAt: r.copiedAt } : {} }
|
|
3716
|
+
})),
|
|
3717
|
+
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."
|
|
3718
|
+
});
|
|
3719
|
+
return check2 && behind.some((r) => r.changed.length > 0) ? 1 : 0;
|
|
3720
|
+
}
|
|
3447
3721
|
case "comments": {
|
|
3448
3722
|
const a = resolveArtifact(positional[0]);
|
|
3449
3723
|
print(flags.has("wait") ? await waitComments(a, flags) : await listComments(a, flags.has("all")));
|