@pixel-point/toolcraft 0.0.2 → 0.0.3

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 CHANGED
@@ -8,6 +8,10 @@ npx @pixel-point/toolcraft create
8
8
 
9
9
  The create command uses the current directory when no target directory is passed, prompts for missing project values in an interactive terminal, generates the app, runs `pnpm install`, then prints the command to start the dev server.
10
10
 
11
+ After dependencies are installed, Toolcraft installs the required workflow skills
12
+ in a batch through the `skills` CLI. The skill installer uses the same agent,
13
+ scope, and installation prompts as `npx skills add`.
14
+
11
15
  ## License
12
16
 
13
17
  Toolcraft is distributed under the Toolcraft Designer License in `LICENSE.md`.
@@ -23,12 +27,21 @@ Scripted usage:
23
27
  npx @pixel-point/toolcraft create my-toolcraft-app --name my-toolcraft-app --yes --force
24
28
  ```
25
29
 
30
+ Install Toolcraft skills to specific agents or locations:
31
+
32
+ ```bash
33
+ npx @pixel-point/toolcraft create my-toolcraft-app --agent codex --agent claude-code
34
+ npx @pixel-point/toolcraft create my-toolcraft-app --agent codex --global
35
+ npx @pixel-point/toolcraft create my-toolcraft-app --all
36
+ npx @pixel-point/toolcraft create my-toolcraft-app --no-skills
37
+ ```
38
+
26
39
  Local source test without publishing:
27
40
 
28
41
  ```bash
29
42
  mkdir -p /tmp/toolcraft-local-cli-test
30
43
  cd /tmp/toolcraft-local-cli-test
31
- node /Users/alex/Projects/primeui-v2/cli/bin/toolcraft.mjs --name local-cli-test --yes --force --no-install
44
+ node /Users/alex/Projects/primeui-v2/cli/bin/toolcraft.mjs --name local-cli-test --yes --force --no-install --no-skills
32
45
  ```
33
46
 
34
47
  Local tarball test, matching the published package layout:
@@ -37,5 +50,5 @@ Local tarball test, matching the published package layout:
37
50
  cd cli
38
51
  npm pack --pack-destination /tmp
39
52
  cd ..
40
- TOOLCRAFT_SKIP_INSTALL=1 npm exec --package /tmp/pixel-point-toolcraft-0.0.0.tgz -- toolcraft create /tmp/toolcraft-pack-exec-test --name pack-exec-test --yes --force
53
+ TOOLCRAFT_SKIP_INSTALL=1 TOOLCRAFT_SKIP_SKILLS=1 npm exec --package /tmp/pixel-point-toolcraft-0.0.2.tgz -- toolcraft create /tmp/toolcraft-pack-exec-test --name pack-exec-test --yes --force
41
54
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixel-point/toolcraft",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,11 +14,16 @@
14
14
  "scripts",
15
15
  "src",
16
16
  "templates",
17
+ "toolcraft-skills",
17
18
  "package.json"
18
19
  ],
19
20
  "scripts": {
20
21
  "prepack": "node scripts/prepare-pack.mjs",
21
22
  "test": "node --test src/*.test.mjs",
22
23
  "typecheck": "node --check bin/toolcraft.mjs && node --check bin/create-toolcraft-app.mjs && node --check scripts/*.mjs && node --check src/*.mjs"
24
+ },
25
+ "dependencies": {
26
+ "@clack/prompts": "^1.6.0",
27
+ "skills": "^1.5.12"
23
28
  }
24
29
  }
package/src/cli.mjs CHANGED
@@ -1,13 +1,28 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { createRequire } from "node:module";
2
3
  import fs from "node:fs/promises";
3
4
  import path from "node:path";
4
5
  import readline from "node:readline/promises";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import {
9
+ cancel,
10
+ confirm,
11
+ intro,
12
+ isCancel,
13
+ note,
14
+ outro,
15
+ spinner,
16
+ text,
17
+ } from "@clack/prompts";
5
18
 
6
19
  import { pathExists } from "./copy-recursive.mjs";
7
20
  import { generateToolcraft } from "./generate.mjs";
8
21
  import { sanitizePackageName } from "./package-json.mjs";
9
22
 
10
23
  const DEFAULT_PROJECT_NAME = "my-toolcraft-app";
24
+ const PACKAGE_ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
25
+ const require = createRequire(import.meta.url);
11
26
 
12
27
  function writeLine(stream, message = "") {
13
28
  stream.write(`${message}\n`);
@@ -44,6 +59,43 @@ function canPrompt(context) {
44
59
  return Boolean(context.forcePrompts || (stdin.isTTY && stdout.isTTY));
45
60
  }
46
61
 
62
+ function canUseClack(context) {
63
+ return Boolean(
64
+ !context.disableClack &&
65
+ !context.prompts &&
66
+ getStdin(context).isTTY &&
67
+ getStdout(context).isTTY,
68
+ );
69
+ }
70
+
71
+ function handleCancel(value) {
72
+ if (!isCancel(value)) {
73
+ return value;
74
+ }
75
+
76
+ cancel("Operation cancelled");
77
+ throw new Error("Operation cancelled.");
78
+ }
79
+
80
+ async function runWithSpinner(context, startMessage, successMessage, task) {
81
+ if (!canUseClack(context)) {
82
+ writeLine(getStdout(context), startMessage);
83
+ return task();
84
+ }
85
+
86
+ const taskSpinner = spinner();
87
+ taskSpinner.start(startMessage);
88
+
89
+ try {
90
+ const result = await task();
91
+ taskSpinner.stop(successMessage);
92
+ return result;
93
+ } catch (error) {
94
+ taskSpinner.stop("Failed");
95
+ throw error;
96
+ }
97
+ }
98
+
47
99
  function createMissingValueError(valueName) {
48
100
  return new Error(`${valueName} is required. Pass it with a flag, or run in an interactive terminal.`);
49
101
  }
@@ -66,6 +118,17 @@ async function promptText(context, label, defaultValue) {
66
118
  throw createMissingValueError(label);
67
119
  }
68
120
 
121
+ if (canUseClack(context)) {
122
+ const answer = handleCancel(
123
+ await text({
124
+ message: label,
125
+ placeholder: defaultValue,
126
+ defaultValue,
127
+ }),
128
+ );
129
+ return String(answer).trim() || defaultValue;
130
+ }
131
+
69
132
  const rl = readline.createInterface({
70
133
  input: getStdin(context),
71
134
  output: getStdout(context),
@@ -88,6 +151,15 @@ async function promptConfirm(context, label, defaultValue = false) {
88
151
  return defaultValue;
89
152
  }
90
153
 
154
+ if (canUseClack(context)) {
155
+ return handleCancel(
156
+ await confirm({
157
+ message: label,
158
+ initialValue: defaultValue,
159
+ }),
160
+ );
161
+ }
162
+
91
163
  const suffix = defaultValue ? "Y/n" : "y/N";
92
164
  const rl = readline.createInterface({
93
165
  input: getStdin(context),
@@ -131,10 +203,15 @@ function readOptionValue(argv, index, optionName) {
131
203
 
132
204
  export function parseCreateArgs(argv) {
133
205
  const options = {
206
+ agent: [],
207
+ copy: false,
134
208
  force: false,
209
+ global: false,
135
210
  help: false,
136
211
  install: true,
137
212
  name: undefined,
213
+ skills: true,
214
+ skillsAll: false,
138
215
  targetDir: undefined,
139
216
  yes: false,
140
217
  };
@@ -152,6 +229,21 @@ export function parseCreateArgs(argv) {
152
229
  continue;
153
230
  }
154
231
 
232
+ if (arg === "--global" || arg === "-g") {
233
+ options.global = true;
234
+ continue;
235
+ }
236
+
237
+ if (arg === "--copy") {
238
+ options.copy = true;
239
+ continue;
240
+ }
241
+
242
+ if (arg === "--all") {
243
+ options.skillsAll = true;
244
+ continue;
245
+ }
246
+
155
247
  if (arg === "--yes" || arg === "-y") {
156
248
  options.yes = true;
157
249
  continue;
@@ -162,12 +254,31 @@ export function parseCreateArgs(argv) {
162
254
  continue;
163
255
  }
164
256
 
257
+ if (arg === "--no-skills") {
258
+ options.skills = false;
259
+ continue;
260
+ }
261
+
165
262
  if (arg === "--name") {
166
263
  options.name = readOptionValue(argv, index, "--name");
167
264
  index += 1;
168
265
  continue;
169
266
  }
170
267
 
268
+ if (arg === "--agent" || arg === "-a") {
269
+ const startIndex = index;
270
+ while (argv[index + 1] && !argv[index + 1].startsWith("-")) {
271
+ options.agent.push(argv[index + 1]);
272
+ index += 1;
273
+ }
274
+
275
+ if (index === startIndex) {
276
+ throw new Error(`${arg} requires an agent name.`);
277
+ }
278
+
279
+ continue;
280
+ }
281
+
171
282
  if (arg === "--dir") {
172
283
  if (options.targetDir) {
173
284
  throw new Error("Pass either a positional target directory or --dir, not both.");
@@ -239,6 +350,7 @@ export async function resolveCreateOptions(parsedOptions, context = {}) {
239
350
  force,
240
351
  install: parsedOptions.install && context.env?.TOOLCRAFT_SKIP_INSTALL !== "1",
241
352
  name,
353
+ skills: parsedOptions.skills && context.env?.TOOLCRAFT_SKIP_SKILLS !== "1",
242
354
  targetDir,
243
355
  };
244
356
  }
@@ -268,17 +380,106 @@ function runCommand(command, args, options = {}, context = {}) {
268
380
  });
269
381
  }
270
382
 
383
+ function getSkillsCliPath(context = {}) {
384
+ if (context.skillsCliPath) {
385
+ return context.skillsCliPath;
386
+ }
387
+
388
+ const skillsPackageJsonPath = require.resolve("skills/package.json");
389
+ return path.join(path.dirname(skillsPackageJsonPath), "bin/cli.mjs");
390
+ }
391
+
392
+ function getToolcraftSkillsSourceDir(context = {}) {
393
+ return context.toolcraftSkillsSourceDir ?? path.join(PACKAGE_ROOT, "toolcraft-skills");
394
+ }
395
+
396
+ function createSkillsAddArgs(parsedOptions, context = {}) {
397
+ const args = ["add", getToolcraftSkillsSourceDir(context)];
398
+
399
+ if (parsedOptions.skillsAll) {
400
+ args.push("--all");
401
+ } else {
402
+ args.push("--skill", "*");
403
+ }
404
+
405
+ for (const agent of parsedOptions.agent) {
406
+ args.push("--agent", agent);
407
+ }
408
+
409
+ if (parsedOptions.global) {
410
+ args.push("--global");
411
+ }
412
+
413
+ if (parsedOptions.copy) {
414
+ args.push("--copy");
415
+ }
416
+
417
+ if (parsedOptions.yes && !parsedOptions.skillsAll) {
418
+ args.push("--yes");
419
+ }
420
+
421
+ return args;
422
+ }
423
+
424
+ async function installToolcraftSkills(parsedOptions, targetDir, context = {}) {
425
+ const skillsSourceDir = getToolcraftSkillsSourceDir(context);
426
+ const skillsCliPath = getSkillsCliPath(context);
427
+
428
+ const args = createSkillsAddArgs(parsedOptions, {
429
+ ...context,
430
+ toolcraftSkillsSourceDir: skillsSourceDir,
431
+ });
432
+
433
+ await runCommand(
434
+ process.execPath,
435
+ [skillsCliPath, ...args],
436
+ {
437
+ cwd: targetDir,
438
+ env: {
439
+ ...process.env,
440
+ ...(context.env ?? {}),
441
+ },
442
+ stdio: "inherit",
443
+ },
444
+ context,
445
+ );
446
+ }
447
+
448
+ function writeFinalSummary(stdout, result, createOptions) {
449
+ const sameDirectory = result.targetDir === path.resolve(createOptions.cwd);
450
+
451
+ writeLine(stdout, "");
452
+ writeLine(stdout, `Created ${result.packageName} at ${result.targetDir}`);
453
+ writeLine(stdout, "");
454
+ writeLine(stdout, "Next steps:");
455
+ if (!sameDirectory) {
456
+ const displayTargetDir = path.isAbsolute(createOptions.targetDir)
457
+ ? result.targetDir
458
+ : result.relativeTargetDir;
459
+ writeLine(stdout, ` cd ${displayTargetDir}`);
460
+ }
461
+ writeLine(stdout, " pnpm dev");
462
+ }
463
+
271
464
  export async function runCreateCommand(parsedOptions, context = {}) {
272
465
  const cwd = path.resolve(context.cwd ?? process.cwd());
273
466
  const stdout = getStdout(context);
467
+ const usingClack = canUseClack(context);
468
+
469
+ if (usingClack) {
470
+ intro("Create Toolcraft app");
471
+ }
472
+
274
473
  const createOptions = await resolveCreateOptions(parsedOptions, context);
275
474
 
276
- const result = await generateToolcraft({
277
- cwd,
278
- force: createOptions.force,
279
- name: createOptions.name,
280
- targetDir: createOptions.targetDir,
281
- });
475
+ const result = await runWithSpinner(context, "Creating Toolcraft app...", "Created app", () =>
476
+ generateToolcraft({
477
+ cwd,
478
+ force: createOptions.force,
479
+ name: createOptions.name,
480
+ targetDir: createOptions.targetDir,
481
+ }),
482
+ );
282
483
 
283
484
  if (createOptions.install) {
284
485
  writeLine(stdout, "");
@@ -298,19 +499,22 @@ export async function runCreateCommand(parsedOptions, context = {}) {
298
499
  );
299
500
  }
300
501
 
301
- const sameDirectory = result.targetDir === cwd;
502
+ if (createOptions.skills) {
503
+ writeLine(stdout, "");
504
+ writeLine(stdout, "Installing Toolcraft skills with skills add...");
505
+ await installToolcraftSkills(parsedOptions, result.targetDir, context);
506
+ }
302
507
 
303
- writeLine(stdout, "");
304
- writeLine(stdout, `Created ${result.packageName} at ${result.targetDir}`);
305
- writeLine(stdout, "");
306
- writeLine(stdout, "Next steps:");
307
- if (!sameDirectory) {
308
- const displayTargetDir = path.isAbsolute(createOptions.targetDir)
309
- ? result.targetDir
310
- : result.relativeTargetDir;
311
- writeLine(stdout, ` cd ${displayTargetDir}`);
508
+ if (usingClack) {
509
+ const nextSteps = [
510
+ ...(result.targetDir === cwd ? [] : [`cd ${result.relativeTargetDir}`]),
511
+ "pnpm dev",
512
+ ].join("\n");
513
+ note(nextSteps, "Next steps");
514
+ outro(`Created ${result.packageName}`);
515
+ } else {
516
+ writeFinalSummary(stdout, result, { ...createOptions, cwd });
312
517
  }
313
- writeLine(stdout, " pnpm dev");
314
518
 
315
519
  return result;
316
520
  }
@@ -339,6 +543,11 @@ Options:
339
543
  --dir <dir> Target directory for the generated app.
340
544
  --force, -f Allow writing into a non-empty target folder.
341
545
  --yes, -y Use defaults for missing values and skip prompts.
546
+ --agent, -a Install Toolcraft skills to specific agents. Repeat or pass multiple values.
547
+ --global, -g Install Toolcraft skills globally instead of project-level.
548
+ --copy Copy skills instead of using the skills CLI default link behavior.
549
+ --all Install Toolcraft skills to all supported agents without skills prompts.
550
+ --no-skills Skip Toolcraft skills installation.
342
551
  --no-install Skip automatic pnpm install. Intended for local CLI tests and automation.
343
552
  --help, -h Show this help message.`;
344
553
  }
package/src/cli.test.mjs CHANGED
@@ -46,10 +46,15 @@ describe("parseCreateArgs", () => {
46
46
  "--no-install",
47
47
  ]),
48
48
  {
49
+ agent: [],
50
+ copy: false,
49
51
  force: true,
52
+ global: false,
50
53
  help: false,
51
54
  install: false,
52
55
  name: "Generated App",
56
+ skills: true,
57
+ skillsAll: false,
53
58
  targetDir: "generated-app",
54
59
  yes: true,
55
60
  },
@@ -60,6 +65,25 @@ describe("parseCreateArgs", () => {
60
65
  assert.equal(parseCreateArgs(["--dir", "target-app"]).targetDir, "target-app");
61
66
  });
62
67
 
68
+ it("parses skills add compatible options", () => {
69
+ const options = parseCreateArgs([
70
+ "generated-app",
71
+ "--agent",
72
+ "codex",
73
+ "claude-code",
74
+ "--global",
75
+ "--copy",
76
+ "--all",
77
+ "--no-skills",
78
+ ]);
79
+
80
+ assert.deepEqual(options.agent, ["codex", "claude-code"]);
81
+ assert.equal(options.global, true);
82
+ assert.equal(options.copy, true);
83
+ assert.equal(options.skillsAll, true);
84
+ assert.equal(options.skills, false);
85
+ });
86
+
63
87
  it("rejects duplicate target directory values", () => {
64
88
  assert.throws(
65
89
  () => parseCreateArgs(["target-app", "--dir", "other-app"]),
@@ -80,6 +104,7 @@ describe("resolveCreateOptions", () => {
80
104
  force: false,
81
105
  install: false,
82
106
  name: path.basename(tempRoot).toLowerCase(),
107
+ skills: true,
83
108
  targetDir: ".",
84
109
  });
85
110
  });
@@ -109,6 +134,7 @@ describe("resolveCreateOptions", () => {
109
134
  force: false,
110
135
  install: true,
111
136
  name: "prompted-app",
137
+ skills: true,
112
138
  targetDir: "./prompted-app",
113
139
  });
114
140
  assert.equal(confirmCalled, false);
@@ -140,6 +166,7 @@ describe("resolveCreateOptions", () => {
140
166
  force: true,
141
167
  install: true,
142
168
  name: "prompted-app",
169
+ skills: true,
143
170
  targetDir: "./prompted-app",
144
171
  });
145
172
  });
@@ -167,7 +194,7 @@ describe("runToolcraftCli", () => {
167
194
  const stdout = createWritableCapture();
168
195
  const stderr = createWritableCapture();
169
196
 
170
- const result = await runToolcraftCli(["--no-install"], {
197
+ const result = await runToolcraftCli(["--no-install", "--no-skills"], {
171
198
  cwd: tempRoot,
172
199
  env: {},
173
200
  stderr,
@@ -197,6 +224,7 @@ describe("runToolcraftCli", () => {
197
224
  "--yes",
198
225
  "--force",
199
226
  "--no-install",
227
+ "--no-skills",
200
228
  ],
201
229
  {
202
230
  cwd: tempRoot,
@@ -216,16 +244,20 @@ describe("runToolcraftCli", () => {
216
244
  assert.equal(stderr.text, "");
217
245
  });
218
246
 
219
- it("runs pnpm install by default after creating the app", async () => {
247
+ it("runs pnpm install and Toolcraft skills install by default after creating the app", async () => {
220
248
  const tempRoot = await createTempRoot();
221
249
  const stdout = createWritableCapture();
222
250
  const installs = [];
251
+ const skillsCliPath = path.join(tempRoot, "skills-cli.mjs");
252
+ const skillsSourceDir = path.join(tempRoot, "toolcraft-skills");
223
253
 
224
254
  await runToolcraftCli(["create", "install-app", "--name", "Install App", "--yes", "--force"], {
225
255
  cwd: tempRoot,
226
256
  env: {},
227
257
  stdout,
258
+ skillsCliPath,
228
259
  throwOnError: true,
260
+ toolcraftSkillsSourceDir: skillsSourceDir,
229
261
  async runCommand(command, args, options) {
230
262
  installs.push({ args, command, cwd: options.cwd });
231
263
  },
@@ -237,11 +269,100 @@ describe("runToolcraftCli", () => {
237
269
  command: "pnpm",
238
270
  cwd: path.join(tempRoot, "install-app"),
239
271
  },
272
+ {
273
+ args: [skillsCliPath, "add", skillsSourceDir, "--skill", "*", "--yes"],
274
+ command: process.execPath,
275
+ cwd: path.join(tempRoot, "install-app"),
276
+ },
240
277
  ]);
241
278
  assert.match(stdout.text, /Installing dependencies with pnpm/);
279
+ assert.match(stdout.text, /Installing Toolcraft skills/);
242
280
  assert.match(stdout.text, /pnpm dev/);
243
281
  });
244
282
 
283
+ it("forwards skills add compatible options", async () => {
284
+ const tempRoot = await createTempRoot();
285
+ const skillsCommands = [];
286
+ const skillsCliPath = path.join(tempRoot, "skills-cli.mjs");
287
+ const skillsSourceDir = path.join(tempRoot, "toolcraft-skills");
288
+
289
+ await runToolcraftCli(
290
+ [
291
+ "create",
292
+ "skills-options-app",
293
+ "--name",
294
+ "Skills Options App",
295
+ "--yes",
296
+ "--force",
297
+ "--no-install",
298
+ "--agent",
299
+ "codex",
300
+ "claude-code",
301
+ "--global",
302
+ "--copy",
303
+ ],
304
+ {
305
+ cwd: tempRoot,
306
+ env: {},
307
+ skillsCliPath,
308
+ throwOnError: true,
309
+ toolcraftSkillsSourceDir: skillsSourceDir,
310
+ async runCommand(command, args, options) {
311
+ skillsCommands.push({ args, command, cwd: options.cwd });
312
+ },
313
+ },
314
+ );
315
+
316
+ assert.deepEqual(skillsCommands, [
317
+ {
318
+ args: [
319
+ skillsCliPath,
320
+ "add",
321
+ skillsSourceDir,
322
+ "--skill",
323
+ "*",
324
+ "--agent",
325
+ "codex",
326
+ "--agent",
327
+ "claude-code",
328
+ "--global",
329
+ "--copy",
330
+ "--yes",
331
+ ],
332
+ command: process.execPath,
333
+ cwd: path.join(tempRoot, "skills-options-app"),
334
+ },
335
+ ]);
336
+ });
337
+
338
+ it("skips Toolcraft skills installation with --no-skills", async () => {
339
+ const tempRoot = await createTempRoot();
340
+ const commands = [];
341
+
342
+ await runToolcraftCli(
343
+ [
344
+ "create",
345
+ "no-skills-app",
346
+ "--name",
347
+ "No Skills App",
348
+ "--yes",
349
+ "--force",
350
+ "--no-install",
351
+ "--no-skills",
352
+ ],
353
+ {
354
+ cwd: tempRoot,
355
+ env: {},
356
+ throwOnError: true,
357
+ async runCommand(command, args, options) {
358
+ commands.push({ args, command, cwd: options.cwd });
359
+ },
360
+ },
361
+ );
362
+
363
+ assert.deepEqual(commands, []);
364
+ });
365
+
245
366
  it("prints create help with the npx command", async () => {
246
367
  const stdout = createWritableCapture();
247
368
 
@@ -252,6 +373,9 @@ describe("runToolcraftCli", () => {
252
373
 
253
374
  assert.equal(result.ok, true);
254
375
  assert.match(stdout.text, /npx @pixel-point\/toolcraft create/);
376
+ assert.match(stdout.text, /--agent/);
377
+ assert.match(stdout.text, /--global/);
378
+ assert.match(stdout.text, /--no-skills/);
255
379
  assert.match(stdout.text, /--no-install/);
256
380
  });
257
381
 
@@ -269,6 +393,7 @@ describe("runToolcraftCli", () => {
269
393
  "--yes",
270
394
  "--force",
271
395
  "--no-install",
396
+ "--no-skills",
272
397
  ],
273
398
  {
274
399
  cwd: tempRoot,
@@ -3,31 +3,66 @@
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
6
7
 
7
8
  const codexHome = process.env.CODEX_HOME
8
9
  ? path.resolve(process.env.CODEX_HOME)
9
10
  : path.join(os.homedir(), ".codex");
11
+ const claudeHome = process.env.CLAUDE_HOME
12
+ ? path.resolve(process.env.CLAUDE_HOME)
13
+ : path.join(os.homedir(), ".claude");
14
+ const cursorHome = path.join(os.homedir(), ".cursor");
15
+ const configHome = process.env.XDG_CONFIG_HOME
16
+ ? path.resolve(process.env.XDG_CONFIG_HOME)
17
+ : path.join(os.homedir(), ".config");
18
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
19
+ const projectRoot = path.resolve(scriptDir, "..");
20
+
21
+ const skillRoots = [
22
+ path.join(projectRoot, ".agents/skills"),
23
+ path.join(projectRoot, ".claude/skills"),
24
+ path.join(codexHome, "skills"),
25
+ path.join(claudeHome, "skills"),
26
+ path.join(cursorHome, "skills"),
27
+ path.join(configHome, "opencode/skills"),
28
+ path.join(configHome, "agents/skills"),
29
+ ];
30
+
31
+ function skillPaths(name) {
32
+ return skillRoots.map((root) => path.join(root, name, "SKILL.md"));
33
+ }
10
34
 
11
35
  const requiredSkills = [
12
36
  {
13
37
  name: "brainstorming",
14
38
  purpose: "shape the app behavior, panels, controls, canvas, media, export, and ambiguity before code",
15
- paths: [path.join(codexHome, "skills/brainstorming/SKILL.md")],
39
+ paths: skillPaths("brainstorming"),
16
40
  },
17
41
  {
18
42
  name: "writing-plans",
19
43
  purpose: "turn the approved app spec into a deterministic Toolcraft implementation plan",
20
- paths: [path.join(codexHome, "skills/writing-plans/SKILL.md")],
44
+ paths: skillPaths("writing-plans"),
21
45
  },
22
46
  {
23
47
  name: "systematic-debugging",
24
48
  purpose: "investigate root cause before fixing broken controls, tests, builds, visual regressions, or runtime bugs",
25
- paths: [path.join(codexHome, "skills/systematic-debugging/SKILL.md")],
49
+ paths: skillPaths("systematic-debugging"),
50
+ },
51
+ {
52
+ name: "figma",
53
+ purpose: "inspect actual Figma structure before implementing Figma-referenced Toolcraft apps",
54
+ paths: skillPaths("figma"),
55
+ },
56
+ {
57
+ name: "figma-implement-design",
58
+ purpose: "translate inspected Figma structure into Toolcraft schema, renderer, and verification coverage",
59
+ paths: skillPaths("figma-implement-design"),
26
60
  },
27
61
  {
28
62
  name: "browser",
29
63
  purpose: "verify the generated UI in a running local browser after implementation",
30
64
  paths: [
65
+ ...skillPaths("browser"),
31
66
  path.join(codexHome, "skills/browser/SKILL.md"),
32
67
  path.join(codexHome, "skills/browser/browser/SKILL.md"),
33
68
  path.join(codexHome, "plugins/cache/openai-bundled/browser/0.1.0-alpha2/skills/browser/SKILL.md"),
@@ -90,6 +125,6 @@ for (const skill of missingSkills) {
90
125
  console.error("");
91
126
  console.error("If your AI environment supports Codex skills, install the missing skills before implementation.");
92
127
  console.error("If installation is not available, stop and ask the user to install them.");
93
- console.error(`Checked CODEX_HOME: ${codexHome}`);
128
+ console.error("Checked project and global skill locations for Codex, Claude Code, Cursor, and OpenCode-compatible agents.");
94
129
 
95
130
  process.exit(1);
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: brainstorming
3
+ description: Use before creating Toolcraft app features, changing behavior, or assembling app specs.
4
+ ---
5
+
6
+ # Toolcraft Brainstorming
7
+
8
+ Use this skill before changing a Toolcraft generated app spec, behavior, controls,
9
+ canvas output, panels, export behavior, renderer technique, timeline, layers, or
10
+ media flow.
11
+
12
+ ## Process
13
+
14
+ 1. Read the generated app `AGENTS.md` and relevant `docs/toolcraft/*` contract
15
+ files first.
16
+ 2. Identify the product goal, visible output, editable entities, required
17
+ controls, export behavior, and verification tier.
18
+ 3. Make a Control Section Inventory grouped by product entity or workflow stage,
19
+ not by UI component type.
20
+ 4. Decide whether layers, timeline, persistence, settings transfer, custom
21
+ controls, or a custom renderer are actually required.
22
+ 5. Record decisions in `docs/toolcraft/agent-worklog.md`.
23
+
24
+ ## Toolcraft Rule
25
+
26
+ The local Toolcraft app contract is the source of truth. Do not ask the user to
27
+ confirm decisions already covered by the prompt, `AGENTS.md`, or local
28
+ Toolcraft docs. Continue when the product behavior is clear.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: browser
3
+ description: Use to verify Toolcraft generated apps in a real local browser after implementation.
4
+ ---
5
+
6
+ # Toolcraft Browser Verification
7
+
8
+ Use this skill after implementing generated app behavior that affects visible UI,
9
+ canvas output, export behavior, upload/media flow, panels, controls, timeline,
10
+ layers, or viewport interactions.
11
+
12
+ ## Process
13
+
14
+ 1. Run the app with `pnpm dev`.
15
+ 2. Open the local URL in a real browser.
16
+ 3. Verify the product output, canvas backing, controls, panel actions, upload or
17
+ export flow, responsive layout, and any timeline or layer behavior touched by
18
+ the change.
19
+ 4. For final delivery, run the generated app verification commands required by
20
+ `AGENTS.md`.
21
+
22
+ ## Toolcraft Rule
23
+
24
+ Browser verification is part of the Toolcraft contract. A typecheck or build
25
+ alone is not enough for visible app behavior.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: figma
3
+ description: Use when a Toolcraft task includes a Figma URL, node ID, or design-to-code requirement.
4
+ ---
5
+
6
+ # Toolcraft Figma Workflow
7
+
8
+ Use this skill when the user provides a Figma URL, node ID, or asks to implement
9
+ or match a Figma design.
10
+
11
+ ## Process
12
+
13
+ 1. Inspect the Figma file through the available Figma MCP or design context
14
+ tooling.
15
+ 2. Read actual nodes, layers, components, variables, styles, assets, and layout
16
+ structure.
17
+ 3. Translate the design into Toolcraft schema controls, product renderer output,
18
+ canvas sizing, export behavior, and verification coverage.
19
+ 4. Use screenshots only for final visual QA, not as the source of truth.
20
+
21
+ ## Toolcraft Rule
22
+
23
+ Do not implement a Figma task from a screenshot or by eye when Figma context is
24
+ available. The Figma structure is the reference.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: figma-implement-design
3
+ description: Use to translate inspected Figma structure into a production-ready Toolcraft generated app.
4
+ ---
5
+
6
+ # Toolcraft Figma Implementation
7
+
8
+ Use this skill after Figma context has been inspected and the task is to build or
9
+ update a Toolcraft generated app from that design.
10
+
11
+ ## Process
12
+
13
+ 1. Map Figma sections, components, and visual entities to product output and
14
+ schema controls.
15
+ 2. Use built-in Toolcraft controls before custom controls.
16
+ 3. Keep app UI out of `canvasContent`; render product output there only.
17
+ 4. Preserve the Toolcraft runtime canvas backing.
18
+ 5. Add acceptance, browser, and performance coverage for every visible product
19
+ entity.
20
+
21
+ ## Toolcraft Rule
22
+
23
+ Build through `defineToolcraft` and `ToolcraftApp`. Do not recreate Toolcraft
24
+ panels, toolbar, controls, canvas, layers, or timeline by hand.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: systematic-debugging
3
+ description: Use before fixing broken Toolcraft controls, tests, builds, visual mismatches, exports, or runtime regressions.
4
+ ---
5
+
6
+ # Toolcraft Systematic Debugging
7
+
8
+ Use this skill before fixing any Toolcraft generated app failure.
9
+
10
+ ## Process
11
+
12
+ 1. Reproduce the failure with the smallest command or browser action.
13
+ 2. Read the full error, stack trace, failing assertion, or visual mismatch.
14
+ 3. Identify whether the root cause is app schema, app renderer, acceptance data,
15
+ browser test setup, copied runtime source, dependencies, or generated docs.
16
+ 4. Compare the failure against the local Toolcraft docs and existing passing
17
+ patterns in the generated app.
18
+ 5. Make one targeted fix and rerun the relevant verification.
19
+
20
+ ## Toolcraft Rule
21
+
22
+ Fix source behavior in the monorepo runtime or starter when the generated
23
+ template is wrong. Do not patch copied `src/toolcraft` files inside one generated
24
+ app unless the user explicitly wants a local fork.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: writing-plans
3
+ description: Use before Toolcraft code changes once the product behavior or approved spec is clear.
4
+ ---
5
+
6
+ # Toolcraft Writing Plans
7
+
8
+ Use this skill before editing generated app code from a clear Toolcraft spec or
9
+ user request.
10
+
11
+ ## Plan Content
12
+
13
+ Write a concise implementation plan that names:
14
+
15
+ 1. Files to change under `src/app`, `src/routes`, docs, tests, or scripts.
16
+ 2. Schema controls, sections, panel actions, renderer output, timeline, layers,
17
+ persistence, settings transfer, and export paths affected by the work.
18
+ 3. Acceptance, browser, and performance coverage required by the selected
19
+ verification tier.
20
+ 4. Commands to run before completion.
21
+
22
+ ## Toolcraft Rule
23
+
24
+ Keep implementation plans focused on app behavior and verification. Do not move
25
+ runtime behavior into route-local state, do not patch copied `src/toolcraft`
26
+ internals for one app, and do not replace built-in Toolcraft surfaces.