@genspark/cli 1.0.23 → 1.0.25

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.
@@ -23,6 +23,12 @@ import { Command } from 'commander';
23
23
  * `argv` is process.argv.slice(2) (no node/script prefix).
24
24
  */
25
25
  export declare function getMeshInvocation(argv: string[]): string[] | null;
26
+ /**
27
+ * Insert the wrapper-only `upgrade` entry at the end of the native help's
28
+ * "Commands:" block. If the block can't be located (native help format
29
+ * changed), append a short note instead so `upgrade` stays documented.
30
+ */
31
+ export declare function augmentMeshHelp(nativeHelp: string): string;
26
32
  /**
27
33
  * Handle a `gsk mesh ...` invocation end-to-end. Dispatches:
28
34
  * - `upgrade` → TS-side version negotiation + download
@@ -1 +1 @@
1
- {"version":3,"file":"mesh.d.ts","sourceRoot":"","sources":["../../src/commands/mesh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAmBnC;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,IAAI,CAyBjE;AA6CD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCrF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAe1D"}
1
+ {"version":3,"file":"mesh.d.ts","sourceRoot":"","sources":["../../src/commands/mesh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAmBnC;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,IAAI,CAyBjE;AA6DD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAgB1D;AAiCD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CrF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAe1D"}
@@ -104,6 +104,72 @@ function spawnNative(bin, args) {
104
104
  // execNative never returns normally — the process exits via the handlers.
105
105
  return new Promise(() => { });
106
106
  }
107
+ // Top-level `gsk mesh` help requests. `gsk mesh help ssh` / `gsk mesh ssh --help`
108
+ // are subcommand help and must pass through untouched, so we only match the
109
+ // single-token forms.
110
+ const HELP_TOKENS = new Set(['--help', '-h', 'help']);
111
+ function isTopLevelMeshHelp(args) {
112
+ return args.length === 1 && HELP_TOKENS.has(args[0]);
113
+ }
114
+ // The `upgrade` subcommand is handled by this wrapper (see runMesh), not by the
115
+ // native binary, so it never appears in the native `--help`. We splice this
116
+ // line into the native help's Commands block. Indentation matches the native
117
+ // clap layout (2-space indent, descriptions aligned at column 11).
118
+ const WRAPPER_UPGRADE_HELP_LINE = ' upgrade Check for and install a newer gsk-mesh release (run `gsk mesh upgrade --help`)';
119
+ /**
120
+ * Insert the wrapper-only `upgrade` entry at the end of the native help's
121
+ * "Commands:" block. If the block can't be located (native help format
122
+ * changed), append a short note instead so `upgrade` stays documented.
123
+ */
124
+ export function augmentMeshHelp(nativeHelp) {
125
+ if (/^\s+upgrade\b/m.test(nativeHelp)) {
126
+ return nativeHelp; // already present — don't double-insert
127
+ }
128
+ const lines = nativeHelp.split('\n');
129
+ const cmdHeader = lines.findIndex(l => /^Commands:/.test(l));
130
+ if (cmdHeader !== -1) {
131
+ // End of the commands block = first blank line after the header.
132
+ let end = cmdHeader + 1;
133
+ while (end < lines.length && lines[end].trim() !== '')
134
+ end++;
135
+ lines.splice(end, 0, WRAPPER_UPGRADE_HELP_LINE);
136
+ return lines.join('\n');
137
+ }
138
+ // Fallback: couldn't find the block; append a note.
139
+ const sep = nativeHelp.endsWith('\n') ? '' : '\n';
140
+ return `${nativeHelp}${sep}\nWrapper-added command:\n${WRAPPER_UPGRADE_HELP_LINE}\n`;
141
+ }
142
+ /**
143
+ * Spawn native gsk-mesh for a top-level `--help`, capturing stdout so we can
144
+ * splice in the wrapper-only `upgrade` command. On any spawn error we exit like
145
+ * spawnNative; the captured help is printed verbatim-plus-`upgrade` on exit.
146
+ *
147
+ * We key off `close`, not `exit`: `exit` can fire while stdout `data` events are
148
+ * still queued, which would augment a truncated buffer. `close` is emitted only
149
+ * after the child has ended AND its stdio streams have been fully drained, so
150
+ * `out` holds the complete native help by then.
151
+ */
152
+ function spawnNativeHelpAugmented(bin, args) {
153
+ const child = spawn(bin, args, { stdio: ['inherit', 'pipe', 'inherit'] });
154
+ let out = '';
155
+ child.stdout?.on('data', (chunk) => {
156
+ out += chunk.toString();
157
+ });
158
+ child.on('error', err => {
159
+ logError(`Failed to launch gsk-mesh: ${err.message}`);
160
+ process.exit(1);
161
+ });
162
+ child.on('close', (code, signal) => {
163
+ process.stdout.write(augmentMeshHelp(out));
164
+ if (signal) {
165
+ process.kill(process.pid, signal);
166
+ }
167
+ else {
168
+ process.exit(code ?? 0);
169
+ }
170
+ });
171
+ return new Promise(() => { });
172
+ }
107
173
  /**
108
174
  * Handle a `gsk mesh ...` invocation end-to-end. Dispatches:
109
175
  * - `upgrade` → TS-side version negotiation + download
@@ -140,6 +206,12 @@ export async function runMesh(args, baseUrlOverride) {
140
206
  // automatically when no binary is installed yet or auto-update is disabled.
141
207
  // Safe to run now: ensureMeshBinary has finished, so no concurrent install.
142
208
  void maybeAutoUpgradeMesh(baseUrlOverride).catch(err => debug(`mesh autorun: ${err.message}`));
209
+ // Top-level help: capture the native help and splice in the wrapper-only
210
+ // `upgrade` command so it's discoverable from `gsk mesh --help`.
211
+ if (isTopLevelMeshHelp(args)) {
212
+ await spawnNativeHelpAugmented(bin, args);
213
+ return;
214
+ }
143
215
  await spawnNative(bin, args);
144
216
  }
145
217
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"mesh.js","sourceRoot":"","sources":["../../src/commands/mesh.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mesh.js","sourceRoot":"","sources":["../../src/commands/mesh.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * `gsk skills pull <ref> --out <dir>` — client-side writer for the backend
3
+ * `skills_pull` action.
4
+ *
5
+ * Why client-side: an HTTP/JSON tool can't materialise a file tree on the
6
+ * user's machine. The backend `skills_pull` action returns the skill's files
7
+ * as `{ path, content_base64 }` rows; this helper decodes them and writes the
8
+ * tree to disk — same split as `sb-git clone-url --execute` — plus a
9
+ * `.genspark-skill.json` provenance sidecar so the tree stays identifiable
10
+ * and refresh-able. Pull is an authoring/export flow (pull → edit → re-upload),
11
+ * not a runtime mount path.
12
+ */
13
+ /** Skill-slug shape the backend enforces (single path component, no separators
14
+ * or `..`). Defined here — the lowest module in the skills-CLI import graph
15
+ * (skills-sync imports `safeJoin` from it) — and re-used by skills-sync so the
16
+ * client can't drift from the server's slug contract. */
17
+ export declare const SLUG_RE: RegExp;
18
+ /** One file row from the backend `skills_pull` response. */
19
+ export interface SkillFile {
20
+ path: string;
21
+ size?: number;
22
+ content_base64: string;
23
+ }
24
+ /** Shape of the `data` field returned by the backend `skills_pull` action. */
25
+ export interface SkillPullData {
26
+ slug: string;
27
+ ref?: string;
28
+ owner?: string;
29
+ publisher_type?: string;
30
+ name?: string;
31
+ commit?: string;
32
+ file_count?: number;
33
+ total_bytes?: number;
34
+ files: SkillFile[];
35
+ }
36
+ /**
37
+ * Provenance sidecar written next to the pulled files. This is what turns a
38
+ * pulled tree from an anonymous file dump into a managed artifact: it records
39
+ * WHICH skill (owner/slug) at WHICH commit landed here, so a later pull of the
40
+ * same skill can refresh the directory in place instead of being refused.
41
+ */
42
+ export declare const SIDECAR_FILENAME = ".genspark-skill.json";
43
+ export interface SkillSidecar {
44
+ ref?: string;
45
+ owner?: string;
46
+ slug?: string;
47
+ commit?: string;
48
+ publisher_type?: string;
49
+ pulled_at?: string;
50
+ file_count?: number;
51
+ content_hash?: string;
52
+ }
53
+ /** Parse the sidecar in `dest`, or null when absent/unreadable. */
54
+ export declare function readSidecar(dest: string): SkillSidecar | null;
55
+ /** Hash the tree currently on disk under `dir`, excluding the sidecar file. */
56
+ export declare function hashTree(dir: string): string;
57
+ /**
58
+ * Resolve the destination directory and decide whether this pull is a
59
+ * sidecar-matched in-place REFRESH. Default dest: the slug in the current
60
+ * directory; an explicit `--out` is honored (with `~` expansion).
61
+ *
62
+ * A non-empty existing directory is refused UNLESS its sidecar attests it
63
+ * holds a previous pull of the SAME skill (slug match, and owner match when
64
+ * both sides carry one) AND that pull is PRISTINE — the on-disk tree still
65
+ * hashes to what the sidecar recorded. A pristine match refreshes in place; an
66
+ * edited tree (or a same-skill dir with no recorded hash) is refused so a
67
+ * re-pull never silently destroys local edits (the pull → edit → re-upload
68
+ * authoring flow). A non-matching dir is refused so a pull never merges into an
69
+ * unrelated tree.
70
+ */
71
+ export declare function resolvePullDest(slug: string, explicit: string | undefined, pulled?: {
72
+ slug: string;
73
+ owner?: string;
74
+ }): {
75
+ dest: string;
76
+ refresh: boolean;
77
+ };
78
+ /**
79
+ * Resolve a server-provided relative file path against `dest`, rejecting any
80
+ * path that would escape the destination (absolute paths, `..` traversal).
81
+ * The backend already constrains paths to the skill subtree; this is
82
+ * defense-in-depth so a crafted response can't write outside `--out`.
83
+ */
84
+ export declare function safeJoin(dest: string, relPath: string): string;
85
+ /**
86
+ * Write a `skills_pull` response to disk. Returns 0 on success, non-zero on
87
+ * any failure (matches the exit-code contract the caller propagates via
88
+ * `process.exit`).
89
+ */
90
+ export declare function writeSkillFiles(data: SkillPullData, explicitDest: string | undefined): number;
91
+ //# sourceMappingURL=skills-pull.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-pull.d.ts","sourceRoot":"","sources":["../../src/commands/skills-pull.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH;;;yDAGyD;AACzD,eAAO,MAAM,OAAO,QAAqC,CAAA;AAEzD,4DAA4D;AAC5D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,yBAAyB,CAAA;AAEtD,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IAInB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAW7D;AAqCD,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAe5C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAgCpC;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAiB9D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,EACnB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,MAAM,CAmHR"}
@@ -0,0 +1,275 @@
1
+ /**
2
+ * `gsk skills pull <ref> --out <dir>` — client-side writer for the backend
3
+ * `skills_pull` action.
4
+ *
5
+ * Why client-side: an HTTP/JSON tool can't materialise a file tree on the
6
+ * user's machine. The backend `skills_pull` action returns the skill's files
7
+ * as `{ path, content_base64 }` rows; this helper decodes them and writes the
8
+ * tree to disk — same split as `sb-git clone-url --execute` — plus a
9
+ * `.genspark-skill.json` provenance sidecar so the tree stays identifiable
10
+ * and refresh-able. Pull is an authoring/export flow (pull → edit → re-upload),
11
+ * not a runtime mount path.
12
+ */
13
+ import * as crypto from 'crypto';
14
+ import * as fs from 'fs';
15
+ import * as os from 'os';
16
+ import * as path from 'path';
17
+ import { info, error as logError } from '../logger.js';
18
+ /** Skill-slug shape the backend enforces (single path component, no separators
19
+ * or `..`). Defined here — the lowest module in the skills-CLI import graph
20
+ * (skills-sync imports `safeJoin` from it) — and re-used by skills-sync so the
21
+ * client can't drift from the server's slug contract. */
22
+ export const SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
23
+ /**
24
+ * Provenance sidecar written next to the pulled files. This is what turns a
25
+ * pulled tree from an anonymous file dump into a managed artifact: it records
26
+ * WHICH skill (owner/slug) at WHICH commit landed here, so a later pull of the
27
+ * same skill can refresh the directory in place instead of being refused.
28
+ */
29
+ export const SIDECAR_FILENAME = '.genspark-skill.json';
30
+ /** Parse the sidecar in `dest`, or null when absent/unreadable. */
31
+ export function readSidecar(dest) {
32
+ try {
33
+ const raw = fs.readFileSync(path.join(dest, SIDECAR_FILENAME), 'utf8');
34
+ const parsed = JSON.parse(raw);
35
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
36
+ return parsed;
37
+ }
38
+ }
39
+ catch {
40
+ // absent or corrupt → treated as "no sidecar" (caller refuses the dir)
41
+ }
42
+ return null;
43
+ }
44
+ /**
45
+ * Content hash of a set of skill files, canonicalized so the write-time hash
46
+ * (over the payload) and the re-pull-time hash (over the on-disk tree) are
47
+ * directly comparable: POSIX-separator relative paths, sorted, each contributes
48
+ * `path\0<bytes>\0`. The sidecar itself is never part of the hash.
49
+ */
50
+ function hashFiles(entries) {
51
+ const hash = crypto.createHash('sha256');
52
+ // Canonicalize the key BEFORE sorting so the write-time hash (payload paths)
53
+ // and the re-pull hash (on-disk paths) agree regardless of platform:
54
+ // - separator: on-disk paths join with the OS separator (`\` on Windows);
55
+ // payload paths are `/`. Sorting on the raw separator orders the two
56
+ // differently (`/` and `\` are different code points), so a pristine
57
+ // Windows tree would never match its own recorded hash.
58
+ // - Unicode form: a normalizing filesystem (legacy HFS+, some SMB mounts)
59
+ // returns filenames in NFD while the backend emits NFC, so the same name
60
+ // hashes differently on write vs re-pull → a pristine tree read as edited.
61
+ // Normalize both (POSIX separators + NFC) and sort AND hash on that key.
62
+ const normalized = entries
63
+ .map(e => ({
64
+ key: e.relPath.split(path.sep).join('/').normalize('NFC'),
65
+ bytes: e.bytes,
66
+ }))
67
+ .sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
68
+ for (const { key, bytes } of normalized) {
69
+ hash.update(key);
70
+ hash.update('\0');
71
+ hash.update(bytes);
72
+ hash.update('\0');
73
+ }
74
+ return hash.digest('hex');
75
+ }
76
+ /** Hash the tree currently on disk under `dir`, excluding the sidecar file. */
77
+ export function hashTree(dir) {
78
+ const entries = [];
79
+ const walk = (abs, rel) => {
80
+ for (const name of fs.readdirSync(abs)) {
81
+ const childAbs = path.join(abs, name);
82
+ const childRel = rel ? path.join(rel, name) : name;
83
+ if (fs.statSync(childAbs).isDirectory()) {
84
+ walk(childAbs, childRel);
85
+ }
86
+ else if (!(rel === '' && name === SIDECAR_FILENAME)) {
87
+ entries.push({ relPath: childRel, bytes: fs.readFileSync(childAbs) });
88
+ }
89
+ }
90
+ };
91
+ walk(dir, '');
92
+ return hashFiles(entries);
93
+ }
94
+ /**
95
+ * Resolve the destination directory and decide whether this pull is a
96
+ * sidecar-matched in-place REFRESH. Default dest: the slug in the current
97
+ * directory; an explicit `--out` is honored (with `~` expansion).
98
+ *
99
+ * A non-empty existing directory is refused UNLESS its sidecar attests it
100
+ * holds a previous pull of the SAME skill (slug match, and owner match when
101
+ * both sides carry one) AND that pull is PRISTINE — the on-disk tree still
102
+ * hashes to what the sidecar recorded. A pristine match refreshes in place; an
103
+ * edited tree (or a same-skill dir with no recorded hash) is refused so a
104
+ * re-pull never silently destroys local edits (the pull → edit → re-upload
105
+ * authoring flow). A non-matching dir is refused so a pull never merges into an
106
+ * unrelated tree.
107
+ */
108
+ export function resolvePullDest(slug, explicit, pulled) {
109
+ const dest = explicit
110
+ ? path.resolve(explicit.replace(/^~(?=\/|$)/, os.homedir()))
111
+ : path.resolve(process.cwd(), slug);
112
+ if (fs.existsSync(dest) && fs.readdirSync(dest).length > 0) {
113
+ const sidecar = readSidecar(dest);
114
+ const sameSkill = sidecar != null &&
115
+ sidecar.slug === (pulled?.slug ?? slug) &&
116
+ (!sidecar.owner || !pulled?.owner || sidecar.owner === pulled.owner);
117
+ if (!sameSkill) {
118
+ throw new Error(`Refusing to write into ${dest}: directory exists and is not empty ` +
119
+ `(no ${SIDECAR_FILENAME} matching this skill). ` +
120
+ `Remove it or pass --out <new-path>.`);
121
+ }
122
+ // Same skill, but only refresh a PRISTINE tree. An edited tree (or one
123
+ // written before content hashing) must not be silently wiped.
124
+ const pristine = typeof sidecar.content_hash === 'string' &&
125
+ sidecar.content_hash === hashTree(dest);
126
+ if (!pristine) {
127
+ throw new Error(`Refusing to overwrite ${dest}: it holds local changes since the last ` +
128
+ `pull of this skill (or was pulled before edit-detection). Save your ` +
129
+ `edits, remove the directory, or pass --out <new-path>.`);
130
+ }
131
+ return { dest, refresh: true };
132
+ }
133
+ return { dest, refresh: false };
134
+ }
135
+ /**
136
+ * Resolve a server-provided relative file path against `dest`, rejecting any
137
+ * path that would escape the destination (absolute paths, `..` traversal).
138
+ * The backend already constrains paths to the skill subtree; this is
139
+ * defense-in-depth so a crafted response can't write outside `--out`.
140
+ */
141
+ export function safeJoin(dest, relPath) {
142
+ // Backend emits POSIX git-tree paths, so a backslash or Windows
143
+ // drive/UNC prefix can only be a crafted payload — reject before
144
+ // path.normalize (whose semantics differ per platform) sees it.
145
+ if (relPath.includes('\\') || /^[a-zA-Z]:/.test(relPath)) {
146
+ throw new Error(`Unsafe file path in skill payload: ${relPath}`);
147
+ }
148
+ const normalized = path.normalize(relPath);
149
+ if (path.isAbsolute(normalized) || normalized.split(path.sep)[0] === '..') {
150
+ throw new Error(`Unsafe file path in skill payload: ${relPath}`);
151
+ }
152
+ const target = path.resolve(dest, normalized);
153
+ const destWithSep = dest.endsWith(path.sep) ? dest : dest + path.sep;
154
+ if (target !== dest && !target.startsWith(destWithSep)) {
155
+ throw new Error(`Unsafe file path in skill payload: ${relPath}`);
156
+ }
157
+ return target;
158
+ }
159
+ /**
160
+ * Write a `skills_pull` response to disk. Returns 0 on success, non-zero on
161
+ * any failure (matches the exit-code contract the caller propagates via
162
+ * `process.exit`).
163
+ */
164
+ export function writeSkillFiles(data, explicitDest) {
165
+ if (!data || !Array.isArray(data.files) || data.files.length === 0) {
166
+ logError('skills pull: response contained no files.');
167
+ return 1;
168
+ }
169
+ if (typeof data.slug !== 'string' || !data.slug.trim()) {
170
+ logError('skills pull: response is missing a skill slug.');
171
+ return 1;
172
+ }
173
+ // The slug computes the default destination dir, so validate its shape the
174
+ // same way the backend does (single path component, no separators / `..`).
175
+ // safeJoin only guards the per-file paths; an unsanitised slug would let a
176
+ // crafted response escape the destination root before the per-file check.
177
+ if (!SLUG_RE.test(data.slug)) {
178
+ logError(`skills pull: response has an unsafe skill slug: ${data.slug}`);
179
+ return 1;
180
+ }
181
+ // The provenance sidecar owns the root SIDECAR_FILENAME. If the skill itself
182
+ // ships a root-level file by that name, writing the sidecar would clobber it
183
+ // and the pristine check (which excludes the root sidecar) would silently
184
+ // drop it — so refuse rather than corrupt the skill. A same-named file in a
185
+ // SUBDIR is fine (only the root sidecar is reserved).
186
+ if (data.files.some(f => f.path === SIDECAR_FILENAME)) {
187
+ logError(`skills pull: skill ships a reserved root file ${SIDECAR_FILENAME}; ` +
188
+ `cannot materialize it without clobbering the provenance sidecar.`);
189
+ return 1;
190
+ }
191
+ let dest;
192
+ let refresh;
193
+ try {
194
+ ;
195
+ ({ dest, refresh } = resolvePullDest(data.slug, explicitDest, {
196
+ slug: data.slug,
197
+ owner: data.owner,
198
+ }));
199
+ }
200
+ catch (e) {
201
+ logError(e.message);
202
+ return 1;
203
+ }
204
+ const destExisted = fs.existsSync(dest);
205
+ // A sidecar-matched refresh REPLACES the previous pull: clear the directory
206
+ // first so files deleted upstream don't linger as stale strays. The old
207
+ // content is by definition a previous served snapshot of this same skill
208
+ // (that is what the sidecar attests), so wiping it loses nothing local.
209
+ if (refresh) {
210
+ try {
211
+ for (const entry of fs.readdirSync(dest)) {
212
+ fs.rmSync(path.join(dest, entry), { recursive: true, force: true });
213
+ }
214
+ }
215
+ catch (e) {
216
+ logError(`skills pull: failed to clear ${dest} for refresh: ${e.message}`);
217
+ return 1;
218
+ }
219
+ }
220
+ // Roll back partial writes on failure so a mid-loop error (a crafted
221
+ // traversal/NUL path, disk-full) never leaves a half-written tree that then
222
+ // permanently fails the next retry on resolvePullDest's non-empty-dir guard.
223
+ try {
224
+ const written = [];
225
+ for (const file of data.files) {
226
+ if (!file.path)
227
+ continue; // skip a degenerate empty path (would resolve to dest itself)
228
+ const target = safeJoin(dest, file.path);
229
+ const bytes = Buffer.from(file.content_base64, 'base64');
230
+ fs.mkdirSync(path.dirname(target), { recursive: true });
231
+ fs.writeFileSync(target, bytes);
232
+ written.push({ relPath: file.path, bytes });
233
+ }
234
+ // Sidecar last, after every file landed — a crash mid-write must never
235
+ // leave a sidecar attesting a complete pull over a partial tree. The
236
+ // content_hash pins this exact tree so a later re-pull can tell a pristine
237
+ // directory (safe to refresh) from one the user has edited.
238
+ const sidecar = {
239
+ ref: data.ref ?? (data.owner ? `${data.owner}/${data.slug}` : data.slug),
240
+ owner: data.owner,
241
+ slug: data.slug,
242
+ commit: data.commit,
243
+ publisher_type: data.publisher_type,
244
+ pulled_at: new Date().toISOString(),
245
+ file_count: data.files.length,
246
+ content_hash: hashFiles(written),
247
+ };
248
+ fs.writeFileSync(path.join(dest, SIDECAR_FILENAME), JSON.stringify(sidecar, null, 2) + '\n');
249
+ }
250
+ catch (e) {
251
+ try {
252
+ if (!destExisted) {
253
+ fs.rmSync(dest, { recursive: true, force: true }); // we created it → remove fully
254
+ }
255
+ else {
256
+ // dest pre-existed as either EMPTY (guard-proven) or a refresh we
257
+ // already cleared — every entry now under it is ours. Clear it back to
258
+ // empty (leaving empty subdirs would trip the non-empty-dir guard and
259
+ // brick every retry). A failed refresh thus ends EMPTY, not restored —
260
+ // inherent to refresh-in-place; the next pull rebuilds it.
261
+ for (const entry of fs.readdirSync(dest)) {
262
+ fs.rmSync(path.join(dest, entry), { recursive: true, force: true });
263
+ }
264
+ }
265
+ }
266
+ catch {
267
+ // best-effort cleanup; the original write error below is what matters
268
+ }
269
+ logError(`skills pull: failed to write files: ${e.message}`);
270
+ return 1;
271
+ }
272
+ info(`${refresh ? 'Refreshed' : 'Pulled'} ${data.files.length} file(s) for ${data.slug} → ${dest}`);
273
+ return 0;
274
+ }
275
+ //# sourceMappingURL=skills-pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-pull.js","sourceRoot":"","sources":["../../src/commands/skills-pull.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `gsk skills save [path]` — client-side directory collector for the backend
3
+ * `skills_save` action.
4
+ *
5
+ * Why client-side: `skills_save` takes `files: [{path, content_base64}]` in
6
+ * its request body (the inverse of `skills_pull`'s response shape) — an
7
+ * HTTP/JSON tool has no way to walk a local directory itself, so the CLI
8
+ * reads the tree and ships the encoded bytes. This is the sentinel-protocol
9
+ * successor referenced in `gsk_tool_adaptor.py`'s `_action_save`: SAS used to
10
+ * drop a `.save_to_self` file for the sandbox host to notice and zip
11
+ * out-of-band; here the CLI does the same walk itself, synchronously, before
12
+ * the request goes out.
13
+ */
14
+ export interface CollectedFiles {
15
+ files: Array<{
16
+ path: string;
17
+ content_base64: string;
18
+ }>;
19
+ totalBytes: number;
20
+ sidecarOwner?: string;
21
+ }
22
+ /**
23
+ * Walk `dir` and collect every real file into a `skills_save` payload.
24
+ *
25
+ * - Symlinks are never followed (skipped outright) — a save must not escape
26
+ * `dir` or loop on a cyclic link.
27
+ * - At the root only, dotfiles are excluded except `.gitignore` (ordinary
28
+ * authoring content) and the pull provenance sidecar `.genspark-skill.json`
29
+ * (metadata, not a skill file). Dotfiles in subdirectories are kept as-is.
30
+ * - Requires a root `SKILL.md`: the server rejects a save without one
31
+ * anyway (frontmatter validation), so failing fast here saves a round trip.
32
+ * - Throws once the running byte total exceeds `maxBytes`.
33
+ *
34
+ * Returns the sidecar's recorded `owner` (if any) so the caller can warn the
35
+ * user before saving into someone else's pulled skill forks it into their
36
+ * own catalog.
37
+ */
38
+ export declare function collectSkillDir(dir: string, maxBytes?: number): CollectedFiles;
39
+ //# sourceMappingURL=skills-save.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-save.d.ts","sourceRoot":"","sources":["../../src/commands/skills-save.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtD,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAA0B,GACnC,cAAc,CA2DhB"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * `gsk skills save [path]` — client-side directory collector for the backend
3
+ * `skills_save` action.
4
+ *
5
+ * Why client-side: `skills_save` takes `files: [{path, content_base64}]` in
6
+ * its request body (the inverse of `skills_pull`'s response shape) — an
7
+ * HTTP/JSON tool has no way to walk a local directory itself, so the CLI
8
+ * reads the tree and ships the encoded bytes. This is the sentinel-protocol
9
+ * successor referenced in `gsk_tool_adaptor.py`'s `_action_save`: SAS used to
10
+ * drop a `.save_to_self` file for the sandbox host to notice and zip
11
+ * out-of-band; here the CLI does the same walk itself, synchronously, before
12
+ * the request goes out.
13
+ */
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import { readSidecar, SIDECAR_FILENAME } from './skills-pull.js';
17
+ /** Mirrors the backend's `_MAX_SAVE_BYTES` (gsk_tool_adaptor.py) so a save
18
+ * that would be rejected server-side fails locally without a round trip. */
19
+ const DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
20
+ /**
21
+ * Walk `dir` and collect every real file into a `skills_save` payload.
22
+ *
23
+ * - Symlinks are never followed (skipped outright) — a save must not escape
24
+ * `dir` or loop on a cyclic link.
25
+ * - At the root only, dotfiles are excluded except `.gitignore` (ordinary
26
+ * authoring content) and the pull provenance sidecar `.genspark-skill.json`
27
+ * (metadata, not a skill file). Dotfiles in subdirectories are kept as-is.
28
+ * - Requires a root `SKILL.md`: the server rejects a save without one
29
+ * anyway (frontmatter validation), so failing fast here saves a round trip.
30
+ * - Throws once the running byte total exceeds `maxBytes`.
31
+ *
32
+ * Returns the sidecar's recorded `owner` (if any) so the caller can warn the
33
+ * user before saving into someone else's pulled skill forks it into their
34
+ * own catalog.
35
+ */
36
+ export function collectSkillDir(dir, maxBytes = DEFAULT_MAX_BYTES) {
37
+ const skillMdPath = path.join(dir, 'SKILL.md');
38
+ if (!fs.existsSync(skillMdPath) || !fs.statSync(skillMdPath).isFile()) {
39
+ throw new Error(`${dir} has no SKILL.md at its root — not a skill directory`);
40
+ }
41
+ const files = [];
42
+ let totalBytes = 0;
43
+ const walk = (abs, rel) => {
44
+ const isRoot = rel === '';
45
+ for (const dirent of fs.readdirSync(abs, { withFileTypes: true })) {
46
+ if (dirent.isSymbolicLink())
47
+ continue;
48
+ if (isRoot &&
49
+ (dirent.name === SIDECAR_FILENAME ||
50
+ (dirent.name.startsWith('.') && dirent.name !== '.gitignore'))) {
51
+ continue;
52
+ }
53
+ const childAbs = path.join(abs, dirent.name);
54
+ const childRel = rel ? path.join(rel, dirent.name) : dirent.name;
55
+ if (dirent.isDirectory()) {
56
+ // Never walk or ship dependency / VCS trees, at ANY depth — they are
57
+ // never part of a skill, can be enormous (blowing the byte cap on
58
+ // junk), and `.git` would leak history. Root `.git` is already caught
59
+ // by the dotfile filter above; this also covers nested ones and
60
+ // `node_modules` (not a dotfile, so otherwise walked).
61
+ if (dirent.name === 'node_modules' || dirent.name === '.git')
62
+ continue;
63
+ walk(childAbs, childRel);
64
+ continue;
65
+ }
66
+ if (!dirent.isFile())
67
+ continue;
68
+ // Cap-check on the stat size BEFORE reading: an over-cap file yields a
69
+ // clean cap error without ballooning memory or tripping Node's
70
+ // ERR_FS_FILE_TOO_LARGE on a huge readFileSync.
71
+ const size = fs.statSync(childAbs).size;
72
+ if (totalBytes + size > maxBytes) {
73
+ throw new Error(`skill directory exceeds the ${maxBytes} byte cap`);
74
+ }
75
+ const bytes = fs.readFileSync(childAbs);
76
+ totalBytes += bytes.length;
77
+ files.push({
78
+ path: childRel.split(path.sep).join('/'),
79
+ content_base64: bytes.toString('base64'),
80
+ });
81
+ }
82
+ };
83
+ walk(dir, '');
84
+ if (files.length === 0) {
85
+ throw new Error(`${dir} contains no files to save`);
86
+ }
87
+ return {
88
+ files,
89
+ totalBytes,
90
+ sidecarOwner: readSidecar(dir)?.owner,
91
+ };
92
+ }
93
+ //# sourceMappingURL=skills-save.js.map