@pixel-point/toolcraft 0.0.2 → 0.0.4

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.
Files changed (38) hide show
  1. package/README.md +15 -2
  2. package/package.json +6 -1
  3. package/src/cli.mjs +226 -17
  4. package/src/cli.test.mjs +127 -2
  5. package/templates/runtime/contracts/component-contracts.test.ts +28 -7
  6. package/templates/runtime/contracts/component-contracts.ts +14 -7
  7. package/templates/runtime/contracts/decision-contracts.ts +1 -1
  8. package/templates/runtime/export/export.test.ts +65 -0
  9. package/templates/runtime/export/export.ts +54 -1
  10. package/templates/runtime/react/controls-panel.test.tsx +54 -6
  11. package/templates/runtime/react/controls-panel.tsx +216 -0
  12. package/templates/runtime/react/settings-transfer.test.ts +6 -0
  13. package/templates/runtime/react/settings-transfer.ts +28 -2
  14. package/templates/runtime/schema/canvas-aspect-ratio-presets.ts +50 -0
  15. package/templates/runtime/schema/define-toolcraft.test.ts +45 -1
  16. package/templates/runtime/schema/define-toolcraft.ts +60 -2
  17. package/templates/runtime/schema/keyframe-capability.test.ts +7 -0
  18. package/templates/runtime/schema/keyframe-capability.ts +2 -2
  19. package/templates/runtime/schema/runtime-targets.ts +5 -0
  20. package/templates/runtime/state/create-template-state.test.ts +6 -0
  21. package/templates/runtime/state/reducer.test.ts +55 -0
  22. package/templates/runtime/state/reducer.ts +214 -9
  23. package/templates/starter/AGENTS.md +5 -3
  24. package/templates/starter/docs/toolcraft/acceptance-testing.md +3 -1
  25. package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
  26. package/templates/starter/docs/toolcraft/component-rules.md +12 -5
  27. package/templates/starter/docs/toolcraft/schema-reference.md +45 -7
  28. package/templates/starter/scripts/check-ai-skills.mjs +39 -4
  29. package/templates/starter/src/app/starter-acceptance.test.ts +623 -21
  30. package/templates/starter/src/app/starter-acceptance.ts +290 -3
  31. package/templates/ui/components/control-layout/index.tsx +4 -4
  32. package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1 -1
  33. package/toolcraft-skills/brainstorming/SKILL.md +28 -0
  34. package/toolcraft-skills/browser/SKILL.md +25 -0
  35. package/toolcraft-skills/figma/SKILL.md +24 -0
  36. package/toolcraft-skills/figma-implement-design/SKILL.md +24 -0
  37. package/toolcraft-skills/systematic-debugging/SKILL.md +24 -0
  38. package/toolcraft-skills/writing-plans/SKILL.md +26 -0
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.4",
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,