ux-toolkit 0.4.1 → 0.5.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/dist/cli.js CHANGED
@@ -1,124 +1,138 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/cli.ts
4
10
  import { parseArgs } from "util";
11
+ import { createInterface } from "readline";
5
12
 
6
13
  // src/installer.ts
7
- import { existsSync, mkdirSync, cpSync, readdirSync } from "fs";
8
- import { join as join2, dirname as dirname2 } from "path";
14
+ import { existsSync as existsSync2, mkdirSync, cpSync, readdirSync, rmSync, statSync } from "fs";
15
+ import { dirname as dirname2 } from "path";
9
16
 
10
17
  // src/paths.ts
11
- import { homedir } from "os";
12
- import { join, dirname } from "path";
18
+ import { homedir, platform } from "os";
19
+ import { dirname, resolve } from "path";
13
20
  import { fileURLToPath } from "url";
14
- var __dirname = dirname(fileURLToPath(import.meta.url));
15
- function getPackageRoot() {
16
- return join(__dirname, "..");
17
- }
18
- function getGlobalConfigDir() {
19
- return join(homedir(), ".config", "opencode");
20
- }
21
- function getProjectConfigDir(projectRoot = process.cwd()) {
22
- return join(projectRoot, ".opencode");
23
- }
24
- function getDestinationPaths(global, projectRoot) {
25
- const baseDir = global ? getGlobalConfigDir() : getProjectConfigDir(projectRoot);
26
- return {
27
- skills: join(baseDir, "skills"),
28
- agents: join(baseDir, "agents"),
29
- commands: join(baseDir, "commands")
30
- };
31
- }
21
+ import { existsSync } from "fs";
32
22
 
33
- // src/installer.ts
34
- async function install(options = {}) {
35
- const {
36
- global: isGlobal = false,
37
- projectRoot = process.cwd(),
38
- categories = ["skills", "agents", "commands"],
39
- force = false,
40
- verbose = false
41
- } = options;
42
- const result = {
43
- installed: [],
44
- skipped: [],
45
- errors: []
46
- };
47
- const packageRoot = getPackageRoot();
48
- const destinations = getDestinationPaths(isGlobal, projectRoot);
49
- const log = (msg) => {
50
- if (verbose) console.log(msg);
51
- };
52
- if (categories.includes("skills")) {
53
- const skillsDir = join2(packageRoot, "skills");
54
- if (existsSync(skillsDir)) {
55
- const skills = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
56
- for (const skill of skills) {
57
- const src = join2(skillsDir, skill);
58
- const dest = join2(destinations.skills, skill);
59
- try {
60
- if (existsSync(dest) && !force) {
61
- result.skipped.push(`skill:${skill}`);
62
- log(`Skipped skill/${skill} (already exists)`);
63
- continue;
64
- }
65
- mkdirSync(dirname2(dest), { recursive: true });
66
- cpSync(src, dest, { recursive: true });
67
- result.installed.push(`skill:${skill}`);
68
- log(`Installed skill/${skill}`);
69
- } catch (err) {
70
- result.errors.push(`skill:${skill}: ${err}`);
71
- }
23
+ // src/utils.ts
24
+ import { join as pathJoin } from "path";
25
+ function safeJoin(...paths) {
26
+ const stringPaths = [];
27
+ for (const p of paths) {
28
+ if (typeof p === "string") {
29
+ stringPaths.push(p);
30
+ } else if (p != null) {
31
+ const str = String(p);
32
+ if (str && str !== "[object Object]" && str !== "undefined" && str !== "null") {
33
+ stringPaths.push(str);
72
34
  }
73
35
  }
74
36
  }
75
- if (categories.includes("agents")) {
76
- const agentsDir = join2(packageRoot, "agents");
77
- if (existsSync(agentsDir)) {
78
- const agents = readdirSync(agentsDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
79
- for (const agent of agents) {
80
- const src = join2(agentsDir, `${agent}.md`);
81
- const dest = join2(destinations.agents, `${agent}.md`);
82
- try {
83
- if (existsSync(dest) && !force) {
84
- result.skipped.push(`agent:${agent}`);
85
- log(`Skipped agents/${agent}.md (already exists)`);
86
- continue;
87
- }
88
- mkdirSync(dirname2(dest), { recursive: true });
89
- cpSync(src, dest);
90
- result.installed.push(`agent:${agent}`);
91
- log(`Installed agents/${agent}.md`);
92
- } catch (err) {
93
- result.errors.push(`agent:${agent}: ${err}`);
94
- }
95
- }
37
+ if (stringPaths.length === 0) {
38
+ return ".";
39
+ }
40
+ try {
41
+ return pathJoin(...stringPaths);
42
+ } catch {
43
+ const sep = stringPaths[0].includes("\\") ? "\\" : "/";
44
+ return stringPaths.join(sep).replace(/[/\\]+/g, sep);
45
+ }
46
+ }
47
+
48
+ // src/paths.ts
49
+ function getCurrentDirname() {
50
+ try {
51
+ if (import.meta?.url) {
52
+ return dirname(fileURLToPath(import.meta.url));
96
53
  }
54
+ } catch {
97
55
  }
98
- if (categories.includes("commands")) {
99
- const commandsDir = join2(packageRoot, "commands");
100
- if (existsSync(commandsDir)) {
101
- const commands = readdirSync(commandsDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
102
- for (const command of commands) {
103
- const src = join2(commandsDir, `${command}.md`);
104
- const dest = join2(destinations.commands, `${command}.md`);
105
- try {
106
- if (existsSync(dest) && !force) {
107
- result.skipped.push(`command:${command}`);
108
- log(`Skipped commands/${command}.md (already exists)`);
109
- continue;
110
- }
111
- mkdirSync(dirname2(dest), { recursive: true });
112
- cpSync(src, dest);
113
- result.installed.push(`command:${command}`);
114
- log(`Installed commands/${command}.md`);
115
- } catch (err) {
116
- result.errors.push(`command:${command}: ${err}`);
117
- }
56
+ try {
57
+ const packageJsonPath = __require.resolve("ux-toolkit/package.json");
58
+ return safeJoin(dirname(packageJsonPath), "dist");
59
+ } catch {
60
+ }
61
+ const cwd = process.cwd();
62
+ try {
63
+ const fs = __require("fs");
64
+ const pkgPath = resolve(cwd, "package.json");
65
+ if (fs.existsSync(pkgPath)) {
66
+ const pkg = __require(pkgPath);
67
+ if (pkg.name === "ux-toolkit") {
68
+ return resolve(cwd, "dist");
118
69
  }
119
70
  }
71
+ } catch {
120
72
  }
121
- return result;
73
+ return resolve(cwd, "dist");
74
+ }
75
+ var currentDirname = getCurrentDirname();
76
+ function getPackageRoot() {
77
+ return safeJoin(currentDirname, "..");
78
+ }
79
+ function getGlobalConfigDir() {
80
+ if (process.env.UX_TOOLKIT_CONFIG_DIR) {
81
+ return resolve(process.env.UX_TOOLKIT_CONFIG_DIR);
82
+ }
83
+ if (process.env.OPENCODE_CONFIG_DIR) {
84
+ return resolve(process.env.OPENCODE_CONFIG_DIR);
85
+ }
86
+ const home = homedir();
87
+ const currentPlatform = platform();
88
+ if (currentPlatform === "linux" && process.env.XDG_CONFIG_HOME) {
89
+ return safeJoin(process.env.XDG_CONFIG_HOME, "opencode");
90
+ }
91
+ return safeJoin(home, ".config", "opencode");
92
+ }
93
+ function isOpenCodeInstalled() {
94
+ return existsSync(getGlobalConfigDir());
95
+ }
96
+ function getClaudeConfigDir() {
97
+ if (process.env.CLAUDE_CONFIG_DIR) {
98
+ return resolve(process.env.CLAUDE_CONFIG_DIR);
99
+ }
100
+ return safeJoin(homedir(), ".claude");
101
+ }
102
+ function isClaudeInstalled() {
103
+ return existsSync(getClaudeConfigDir());
104
+ }
105
+ function getPlatformInfo() {
106
+ const opencodeDir = getGlobalConfigDir();
107
+ const claudeDir = getClaudeConfigDir();
108
+ return {
109
+ platform: platform(),
110
+ opencode: {
111
+ configDir: opencodeDir,
112
+ exists: existsSync(opencodeDir)
113
+ },
114
+ claude: {
115
+ configDir: claudeDir,
116
+ exists: existsSync(claudeDir)
117
+ }
118
+ };
119
+ }
120
+ function getProjectConfigDir(projectRoot = process.cwd(), target = "opencode") {
121
+ const dirName = target === "claude" ? ".claude" : ".opencode";
122
+ return safeJoin(projectRoot, dirName);
123
+ }
124
+ function getDestinationPaths(global, projectRoot, target = "opencode") {
125
+ let baseDir;
126
+ if (global) {
127
+ baseDir = target === "claude" ? getClaudeConfigDir() : getGlobalConfigDir();
128
+ } else {
129
+ baseDir = getProjectConfigDir(projectRoot, target);
130
+ }
131
+ return {
132
+ skills: safeJoin(baseDir, "skills"),
133
+ agents: safeJoin(baseDir, "agents"),
134
+ commands: safeJoin(baseDir, "commands")
135
+ };
122
136
  }
123
137
 
124
138
  // src/manifest.ts
@@ -359,20 +373,282 @@ var COMMANDS = [
359
373
  description: "Comprehensive UX audit"
360
374
  },
361
375
  {
362
- name: "a11y-check",
376
+ name: "ux-a11y-check",
363
377
  description: "Quick accessibility scan"
364
378
  },
365
379
  {
366
- name: "design-review",
380
+ name: "ux-design-review",
367
381
  description: "Visual consistency check"
368
382
  },
369
383
  {
370
- name: "screenshot-review",
384
+ name: "ux-screenshot-review",
371
385
  description: "Visual review from screenshot"
372
386
  }
373
387
  ];
374
388
 
389
+ // src/installer.ts
390
+ async function install(options = {}) {
391
+ const {
392
+ global: isGlobal = false,
393
+ projectRoot = process.cwd(),
394
+ target = "opencode",
395
+ categories = ["skills", "agents", "commands"],
396
+ skills: specificSkills,
397
+ agents: specificAgents,
398
+ commands: specificCommands,
399
+ force = false,
400
+ verbose = false
401
+ } = options;
402
+ const hasSpecificComponents = specificSkills?.length || specificAgents?.length || specificCommands?.length;
403
+ const effectiveCategories = hasSpecificComponents ? categories.filter((cat) => {
404
+ if (cat === "skills" && specificSkills?.length) return true;
405
+ if (cat === "agents" && specificAgents?.length) return true;
406
+ if (cat === "commands" && specificCommands?.length) return true;
407
+ return false;
408
+ }) : categories;
409
+ const result = {
410
+ installed: [],
411
+ skipped: [],
412
+ errors: []
413
+ };
414
+ const packageRoot = getPackageRoot();
415
+ const destinations = getDestinationPaths(isGlobal, projectRoot, target);
416
+ const log = (msg) => {
417
+ if (verbose) console.log(msg);
418
+ };
419
+ if (effectiveCategories.includes("skills")) {
420
+ const skillsDir = safeJoin(packageRoot, "skills");
421
+ if (existsSync2(skillsDir)) {
422
+ let skills = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
423
+ if (specificSkills?.length) {
424
+ const requested = new Set(specificSkills.map((s) => s.toLowerCase()));
425
+ skills = skills.filter((s) => requested.has(s.toLowerCase()));
426
+ }
427
+ for (const skill of skills) {
428
+ const src = safeJoin(skillsDir, skill);
429
+ const dest = safeJoin(destinations.skills, skill);
430
+ try {
431
+ if (existsSync2(dest) && !force) {
432
+ result.skipped.push(`skill:${skill}`);
433
+ log(`Skipped skill/${skill} (already exists)`);
434
+ continue;
435
+ }
436
+ mkdirSync(dirname2(dest), { recursive: true });
437
+ cpSync(src, dest, { recursive: true });
438
+ result.installed.push(`skill:${skill}`);
439
+ log(`Installed skill/${skill}`);
440
+ } catch (err) {
441
+ result.errors.push(`skill:${skill}: ${err}`);
442
+ }
443
+ }
444
+ }
445
+ }
446
+ if (effectiveCategories.includes("agents")) {
447
+ const agentsDir = safeJoin(packageRoot, "agents");
448
+ if (existsSync2(agentsDir)) {
449
+ let agents = readdirSync(agentsDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
450
+ if (specificAgents?.length) {
451
+ const requested = new Set(specificAgents.map((a) => a.toLowerCase()));
452
+ agents = agents.filter((a) => requested.has(a.toLowerCase()));
453
+ }
454
+ for (const agent of agents) {
455
+ const src = safeJoin(agentsDir, `${agent}.md`);
456
+ const dest = safeJoin(destinations.agents, `${agent}.md`);
457
+ try {
458
+ if (existsSync2(dest) && !force) {
459
+ result.skipped.push(`agent:${agent}`);
460
+ log(`Skipped agents/${agent}.md (already exists)`);
461
+ continue;
462
+ }
463
+ mkdirSync(dirname2(dest), { recursive: true });
464
+ cpSync(src, dest);
465
+ result.installed.push(`agent:${agent}`);
466
+ log(`Installed agents/${agent}.md`);
467
+ } catch (err) {
468
+ result.errors.push(`agent:${agent}: ${err}`);
469
+ }
470
+ }
471
+ }
472
+ }
473
+ if (effectiveCategories.includes("commands")) {
474
+ const commandsDir = safeJoin(packageRoot, "commands");
475
+ if (existsSync2(commandsDir)) {
476
+ let commands = readdirSync(commandsDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
477
+ if (specificCommands?.length) {
478
+ const requested = new Set(specificCommands.map((c) => c.toLowerCase()));
479
+ commands = commands.filter((c) => requested.has(c.toLowerCase()));
480
+ }
481
+ for (const command of commands) {
482
+ const src = safeJoin(commandsDir, `${command}.md`);
483
+ const dest = safeJoin(destinations.commands, `${command}.md`);
484
+ try {
485
+ if (existsSync2(dest) && !force) {
486
+ result.skipped.push(`command:${command}`);
487
+ log(`Skipped commands/${command}.md (already exists)`);
488
+ continue;
489
+ }
490
+ mkdirSync(dirname2(dest), { recursive: true });
491
+ cpSync(src, dest);
492
+ result.installed.push(`command:${command}`);
493
+ log(`Installed commands/${command}.md`);
494
+ } catch (err) {
495
+ result.errors.push(`command:${command}: ${err}`);
496
+ }
497
+ }
498
+ }
499
+ }
500
+ return result;
501
+ }
502
+ function checkComponentStatus(componentPath, name) {
503
+ const installed = existsSync2(componentPath);
504
+ if (!installed) {
505
+ return { name, installed: false };
506
+ }
507
+ try {
508
+ const stats = statSync(componentPath);
509
+ return {
510
+ name,
511
+ installed: true,
512
+ path: componentPath,
513
+ modifiedAt: stats.mtime
514
+ };
515
+ } catch {
516
+ return { name, installed: true, path: componentPath };
517
+ }
518
+ }
519
+ function getTargetStatus(target, isGlobal, projectRoot) {
520
+ const available = target === "opencode" ? isOpenCodeInstalled() : isClaudeInstalled();
521
+ const destinations = getDestinationPaths(isGlobal, projectRoot, target);
522
+ const skillStatuses = SKILLS.map(
523
+ (s) => checkComponentStatus(safeJoin(destinations.skills, s.name), s.name)
524
+ );
525
+ const agentStatuses = AGENTS.map(
526
+ (a) => checkComponentStatus(safeJoin(destinations.agents, `${a.name}.md`), a.name)
527
+ );
528
+ const commandStatuses = COMMANDS.map(
529
+ (c) => checkComponentStatus(safeJoin(destinations.commands, `${c.name}.md`), c.name)
530
+ );
531
+ return {
532
+ target,
533
+ available,
534
+ configDir: target === "opencode" ? isGlobal ? destinations.skills.replace(/[/\\]skills$/, "") : destinations.skills.replace(/[/\\]skills$/, "") : isGlobal ? destinations.skills.replace(/[/\\]skills$/, "") : destinations.skills.replace(/[/\\]skills$/, ""),
535
+ skills: {
536
+ installed: skillStatuses.filter((s) => s.installed).length,
537
+ total: SKILLS.length,
538
+ components: skillStatuses
539
+ },
540
+ agents: {
541
+ installed: agentStatuses.filter((a) => a.installed).length,
542
+ total: AGENTS.length,
543
+ components: agentStatuses
544
+ },
545
+ commands: {
546
+ installed: commandStatuses.filter((c) => c.installed).length,
547
+ total: COMMANDS.length,
548
+ components: commandStatuses
549
+ }
550
+ };
551
+ }
552
+ async function getStatus(options = {}) {
553
+ const { global: isGlobal = true, projectRoot = process.cwd() } = options;
554
+ return {
555
+ opencode: getTargetStatus("opencode", isGlobal, projectRoot),
556
+ claude: getTargetStatus("claude", isGlobal, projectRoot)
557
+ };
558
+ }
559
+ async function uninstall(options = {}) {
560
+ const {
561
+ global: isGlobal = false,
562
+ projectRoot = process.cwd(),
563
+ target = "opencode",
564
+ categories = ["skills", "agents", "commands"],
565
+ verbose = false
566
+ } = options;
567
+ const result = {
568
+ removed: [],
569
+ notFound: [],
570
+ errors: []
571
+ };
572
+ const destinations = getDestinationPaths(isGlobal, projectRoot, target);
573
+ const log = (msg) => {
574
+ if (verbose) console.log(msg);
575
+ };
576
+ const ourSkills = SKILLS.map((s) => s.name);
577
+ const ourAgents = AGENTS.map((a) => a.name);
578
+ const ourCommands = COMMANDS.map((c) => c.name);
579
+ if (categories.includes("skills")) {
580
+ for (const skill of ourSkills) {
581
+ const dest = safeJoin(destinations.skills, skill);
582
+ try {
583
+ if (!existsSync2(dest)) {
584
+ result.notFound.push(`skill:${skill}`);
585
+ log(`Not found: skill/${skill}`);
586
+ continue;
587
+ }
588
+ rmSync(dest, { recursive: true, force: true });
589
+ result.removed.push(`skill:${skill}`);
590
+ log(`Removed skill/${skill}`);
591
+ } catch (err) {
592
+ result.errors.push(`skill:${skill}: ${err}`);
593
+ }
594
+ }
595
+ }
596
+ if (categories.includes("agents")) {
597
+ for (const agent of ourAgents) {
598
+ const dest = safeJoin(destinations.agents, `${agent}.md`);
599
+ try {
600
+ if (!existsSync2(dest)) {
601
+ result.notFound.push(`agent:${agent}`);
602
+ log(`Not found: agents/${agent}.md`);
603
+ continue;
604
+ }
605
+ rmSync(dest, { force: true });
606
+ result.removed.push(`agent:${agent}`);
607
+ log(`Removed agents/${agent}.md`);
608
+ } catch (err) {
609
+ result.errors.push(`agent:${agent}: ${err}`);
610
+ }
611
+ }
612
+ }
613
+ if (categories.includes("commands")) {
614
+ for (const command of ourCommands) {
615
+ const dest = safeJoin(destinations.commands, `${command}.md`);
616
+ try {
617
+ if (!existsSync2(dest)) {
618
+ result.notFound.push(`command:${command}`);
619
+ log(`Not found: commands/${command}.md`);
620
+ continue;
621
+ }
622
+ rmSync(dest, { force: true });
623
+ result.removed.push(`command:${command}`);
624
+ log(`Removed commands/${command}.md`);
625
+ } catch (err) {
626
+ result.errors.push(`command:${command}: ${err}`);
627
+ }
628
+ }
629
+ }
630
+ return result;
631
+ }
632
+
375
633
  // src/cli.ts
634
+ async function prompt(question) {
635
+ const rl = createInterface({
636
+ input: process.stdin,
637
+ output: process.stdout
638
+ });
639
+ return new Promise((resolve2) => {
640
+ rl.question(question, (answer) => {
641
+ rl.close();
642
+ resolve2(answer.trim().toLowerCase());
643
+ });
644
+ });
645
+ }
646
+ async function promptYesNo(question, defaultYes = true) {
647
+ const suffix = defaultYes ? "[Y/n]" : "[y/N]";
648
+ const answer = await prompt(`${question} ${suffix} `);
649
+ if (answer === "") return defaultYes;
650
+ return answer === "y" || answer === "yes";
651
+ }
376
652
  var HELP = `
377
653
  ux-toolkit - AI-powered UI/UX review toolkit
378
654
 
@@ -381,20 +657,67 @@ USAGE:
381
657
 
382
658
  COMMANDS:
383
659
  install Install skills, agents, and commands
660
+ uninstall Remove installed skills, agents, and commands
661
+ upgrade Reinstall all components (alias for install --force)
662
+ status Show what's installed vs available
663
+ doctor Diagnose installation issues
384
664
  list List available components
665
+ info Show platform and config information
666
+
667
+ TARGET (choose one):
668
+ --opencode Target OpenCode (default) - ~/.config/opencode
669
+ --claude Target Claude Code - ~/.claude
670
+
671
+ SCOPE:
672
+ --global, -g Install to global config (default)
673
+ --project, -p Install to project config (.opencode/ or .claude/)
385
674
 
386
675
  OPTIONS:
387
- --global, -g Install to global config (~/.config/opencode)
388
- --project, -p Install to project config (.opencode/)
676
+ --all, -a Install to all detected platforms (no prompt)
389
677
  --force, -f Overwrite existing files
390
678
  --verbose, -v Verbose output
679
+ --only Install specific categories (skills,agents,commands)
680
+ --skill Install specific skill(s) by name (can repeat)
681
+ --agent Install specific agent(s) by name (can repeat)
682
+ --command Install specific command(s) by name (can repeat)
391
683
  --help, -h Show this help
392
684
 
685
+ ENVIRONMENT:
686
+ UX_TOOLKIT_CONFIG_DIR Override config directory
687
+ OPENCODE_CONFIG_DIR OpenCode config directory override
688
+ CLAUDE_CONFIG_DIR Claude Code config directory override
689
+ XDG_CONFIG_HOME Linux XDG config home (respected)
690
+
393
691
  EXAMPLES:
692
+ # OpenCode (default)
394
693
  npx ux-toolkit install --global
395
- npx ux-toolkit install --project --force
694
+ npx ux-toolkit install --opencode --global
695
+
696
+ # Claude Code
697
+ npx ux-toolkit install --claude --global
698
+ npx ux-toolkit uninstall --claude --global
699
+
700
+ # Both platforms
701
+ npx ux-toolkit install --opencode --global && npx ux-toolkit install --claude --global
702
+
703
+ # Project-level
704
+ npx ux-toolkit install --project
705
+ npx ux-toolkit install --claude --project
706
+
707
+ # Selective
708
+ npx ux-toolkit install --global --only=skills,agents
709
+
710
+ # Other commands
711
+ npx ux-toolkit info
396
712
  npx ux-toolkit list
397
713
  `;
714
+ function parseCategories(only) {
715
+ if (!only) return void 0;
716
+ const valid = ["skills", "agents", "commands"];
717
+ const parsed = only.split(",").map((s) => s.trim().toLowerCase());
718
+ const filtered = parsed.filter((c) => valid.includes(c));
719
+ return filtered.length > 0 ? filtered : void 0;
720
+ }
398
721
  async function main() {
399
722
  const { values, positionals } = parseArgs({
400
723
  allowPositionals: true,
@@ -403,7 +726,14 @@ async function main() {
403
726
  project: { type: "boolean", short: "p", default: false },
404
727
  force: { type: "boolean", short: "f", default: false },
405
728
  verbose: { type: "boolean", short: "v", default: false },
406
- help: { type: "boolean", short: "h", default: false }
729
+ help: { type: "boolean", short: "h", default: false },
730
+ only: { type: "string" },
731
+ opencode: { type: "boolean", default: false },
732
+ claude: { type: "boolean", default: false },
733
+ all: { type: "boolean", short: "a", default: false },
734
+ skill: { type: "string", multiple: true },
735
+ agent: { type: "string", multiple: true },
736
+ command: { type: "string", multiple: true }
407
737
  }
408
738
  });
409
739
  if (values.help || positionals.length === 0) {
@@ -411,25 +741,103 @@ async function main() {
411
741
  process.exit(0);
412
742
  }
413
743
  const command = positionals[0];
744
+ const isGlobal = values.global || !values.project;
745
+ const categories = parseCategories(values.only);
746
+ async function runInstall(target, showHeader = true) {
747
+ const targetName = target === "claude" ? "Claude Code" : "OpenCode";
748
+ if (showHeader) {
749
+ console.log(`
750
+ Installing UX Toolkit to ${targetName} ${isGlobal ? "globally" : "in project"}...
751
+ `);
752
+ }
753
+ const result = await install({
754
+ global: isGlobal,
755
+ target,
756
+ force: values.force,
757
+ verbose: values.verbose,
758
+ categories,
759
+ skills: values.skill,
760
+ agents: values.agent,
761
+ commands: values.command
762
+ });
763
+ if (result.installed.length > 0) {
764
+ console.log(`Installed ${result.installed.length} components:`);
765
+ result.installed.forEach((item) => console.log(` + ${item}`));
766
+ }
767
+ if (result.skipped.length > 0) {
768
+ console.log(`
769
+ Skipped ${result.skipped.length} (already exist, use --force to overwrite):`);
770
+ result.skipped.forEach((item) => console.log(` - ${item}`));
771
+ }
772
+ if (result.errors.length > 0) {
773
+ console.error(`
774
+ Errors:`);
775
+ result.errors.forEach((err) => console.error(` ! ${err}`));
776
+ return false;
777
+ }
778
+ return true;
779
+ }
414
780
  switch (command) {
415
781
  case "install": {
416
- const isGlobal = values.global || !values.project;
782
+ const opencodeInstalled = isOpenCodeInstalled();
783
+ const claudeInstalled = isClaudeInstalled();
784
+ const explicitTarget = values.opencode || values.claude;
785
+ let targets = [];
786
+ if (values.all) {
787
+ if (opencodeInstalled) targets.push("opencode");
788
+ if (claudeInstalled) targets.push("claude");
789
+ if (targets.length === 0) {
790
+ console.warn("\n\u26A0\uFE0F No platforms detected. Installing to OpenCode by default.\n");
791
+ targets = ["opencode"];
792
+ }
793
+ } else if (explicitTarget) {
794
+ targets = [values.claude ? "claude" : "opencode"];
795
+ } else if (isGlobal && opencodeInstalled && claudeInstalled) {
796
+ console.log("\n\u{1F50D} Detected both OpenCode and Claude Code installations.\n");
797
+ const installBoth = await promptYesNo("Install to both platforms?", true);
798
+ if (installBoth) {
799
+ targets = ["opencode", "claude"];
800
+ } else {
801
+ const choice = await prompt("Which platform? (opencode/claude) [opencode]: ");
802
+ targets = [choice === "claude" ? "claude" : "opencode"];
803
+ }
804
+ } else if (isGlobal && claudeInstalled && !opencodeInstalled) {
805
+ console.log("\n\u{1F50D} Detected Claude Code (OpenCode not found).\n");
806
+ targets = ["claude"];
807
+ } else {
808
+ targets = ["opencode"];
809
+ }
810
+ let hasErrors = false;
811
+ for (let i = 0; i < targets.length; i++) {
812
+ const target = targets[i];
813
+ if (i > 0) console.log("");
814
+ const success = await runInstall(target);
815
+ if (!success) hasErrors = true;
816
+ }
817
+ console.log("\nDone!");
818
+ if (hasErrors) process.exit(1);
819
+ break;
820
+ }
821
+ case "uninstall": {
822
+ const target = values.claude ? "claude" : "opencode";
823
+ const targetName = target === "claude" ? "Claude Code" : "OpenCode";
417
824
  console.log(`
418
- Installing UX Toolkit ${isGlobal ? "globally" : "to project"}...
825
+ Uninstalling UX Toolkit from ${targetName} ${isGlobal ? "globally" : "in project"}...
419
826
  `);
420
- const result = await install({
827
+ const result = await uninstall({
421
828
  global: isGlobal,
422
- force: values.force,
423
- verbose: values.verbose
829
+ target,
830
+ verbose: values.verbose,
831
+ categories
424
832
  });
425
- if (result.installed.length > 0) {
426
- console.log(`Installed ${result.installed.length} components:`);
427
- result.installed.forEach((item) => console.log(` + ${item}`));
833
+ if (result.removed.length > 0) {
834
+ console.log(`Removed ${result.removed.length} components:`);
835
+ result.removed.forEach((item) => console.log(` - ${item}`));
428
836
  }
429
- if (result.skipped.length > 0) {
837
+ if (result.notFound.length > 0 && values.verbose) {
430
838
  console.log(`
431
- Skipped ${result.skipped.length} (already exist, use --force to overwrite):`);
432
- result.skipped.forEach((item) => console.log(` - ${item}`));
839
+ Not found (${result.notFound.length}):`);
840
+ result.notFound.forEach((item) => console.log(` ? ${item}`));
433
841
  }
434
842
  if (result.errors.length > 0) {
435
843
  console.error(`
@@ -440,6 +848,245 @@ Errors:`);
440
848
  console.log("\nDone!");
441
849
  break;
442
850
  }
851
+ case "upgrade": {
852
+ const target = values.claude ? "claude" : "opencode";
853
+ const targetName = target === "claude" ? "Claude Code" : "OpenCode";
854
+ console.log(`
855
+ Upgrading UX Toolkit in ${targetName} ${isGlobal ? "globally" : "in project"}...
856
+ `);
857
+ const result = await install({
858
+ global: isGlobal,
859
+ target,
860
+ force: true,
861
+ // Always force for upgrade
862
+ verbose: values.verbose,
863
+ categories
864
+ });
865
+ if (result.installed.length > 0) {
866
+ console.log(`Upgraded ${result.installed.length} components:`);
867
+ result.installed.forEach((item) => console.log(` \u2191 ${item}`));
868
+ }
869
+ if (result.errors.length > 0) {
870
+ console.error(`
871
+ Errors:`);
872
+ result.errors.forEach((err) => console.error(` ! ${err}`));
873
+ process.exit(1);
874
+ }
875
+ console.log("\nDone!");
876
+ break;
877
+ }
878
+ case "status": {
879
+ const status = await getStatus({ global: isGlobal });
880
+ console.log("\nUX Toolkit - Installation Status\n");
881
+ const formatStatus = (installed, total) => {
882
+ if (installed === 0) return `\u2717 0/${total}`;
883
+ if (installed === total) return `\u2713 ${installed}/${total}`;
884
+ return `\u25D0 ${installed}/${total}`;
885
+ };
886
+ const getMissing = (components) => {
887
+ return components.filter((c) => !c.installed).map((c) => c.name);
888
+ };
889
+ console.log(" OpenCode:");
890
+ if (!status.opencode.available) {
891
+ console.log(" Not installed (no config directory found)");
892
+ } else {
893
+ console.log(` Skills: ${formatStatus(status.opencode.skills.installed, status.opencode.skills.total)}`);
894
+ console.log(` Agents: ${formatStatus(status.opencode.agents.installed, status.opencode.agents.total)}`);
895
+ console.log(` Commands: ${formatStatus(status.opencode.commands.installed, status.opencode.commands.total)}`);
896
+ if (values.verbose) {
897
+ const missingSkills = getMissing(status.opencode.skills.components);
898
+ const missingAgents = getMissing(status.opencode.agents.components);
899
+ const missingCommands = getMissing(status.opencode.commands.components);
900
+ if (missingSkills.length > 0) {
901
+ console.log(` Missing skills: ${missingSkills.join(", ")}`);
902
+ }
903
+ if (missingAgents.length > 0) {
904
+ console.log(` Missing agents: ${missingAgents.join(", ")}`);
905
+ }
906
+ if (missingCommands.length > 0) {
907
+ console.log(` Missing commands: ${missingCommands.join(", ")}`);
908
+ }
909
+ }
910
+ }
911
+ console.log("\n Claude Code:");
912
+ if (!status.claude.available) {
913
+ console.log(" Not installed (no config directory found)");
914
+ } else {
915
+ console.log(` Skills: ${formatStatus(status.claude.skills.installed, status.claude.skills.total)}`);
916
+ console.log(` Agents: ${formatStatus(status.claude.agents.installed, status.claude.agents.total)}`);
917
+ console.log(` Commands: ${formatStatus(status.claude.commands.installed, status.claude.commands.total)}`);
918
+ if (values.verbose) {
919
+ const missingSkills = getMissing(status.claude.skills.components);
920
+ const missingAgents = getMissing(status.claude.agents.components);
921
+ const missingCommands = getMissing(status.claude.commands.components);
922
+ if (missingSkills.length > 0) {
923
+ console.log(` Missing skills: ${missingSkills.join(", ")}`);
924
+ }
925
+ if (missingAgents.length > 0) {
926
+ console.log(` Missing agents: ${missingAgents.join(", ")}`);
927
+ }
928
+ if (missingCommands.length > 0) {
929
+ console.log(` Missing commands: ${missingCommands.join(", ")}`);
930
+ }
931
+ }
932
+ }
933
+ const totalOpencode = status.opencode.skills.installed + status.opencode.agents.installed + status.opencode.commands.installed;
934
+ const totalClaude = status.claude.skills.installed + status.claude.agents.installed + status.claude.commands.installed;
935
+ const totalAvailable = SKILLS.length + AGENTS.length + COMMANDS.length;
936
+ console.log("\n Summary:");
937
+ if (status.opencode.available && totalOpencode === totalAvailable) {
938
+ console.log(" OpenCode: \u2713 Fully installed");
939
+ } else if (status.opencode.available && totalOpencode > 0) {
940
+ console.log(` OpenCode: \u25D0 Partially installed (${totalOpencode}/${totalAvailable})`);
941
+ } else if (status.opencode.available) {
942
+ console.log(" OpenCode: \u2717 Not installed");
943
+ }
944
+ if (status.claude.available && totalClaude === totalAvailable) {
945
+ console.log(" Claude Code: \u2713 Fully installed");
946
+ } else if (status.claude.available && totalClaude > 0) {
947
+ console.log(` Claude Code: \u25D0 Partially installed (${totalClaude}/${totalAvailable})`);
948
+ } else if (status.claude.available) {
949
+ console.log(" Claude Code: \u2717 Not installed");
950
+ }
951
+ break;
952
+ }
953
+ case "doctor": {
954
+ console.log("\nUX Toolkit - Diagnostics\n");
955
+ const issues = [];
956
+ const warnings = [];
957
+ const ok = [];
958
+ const nodeVersion = process.version;
959
+ const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0], 10);
960
+ if (majorVersion < 18) {
961
+ issues.push(`Node.js ${nodeVersion} is below minimum (v18+)`);
962
+ } else {
963
+ ok.push(`Node.js ${nodeVersion}`);
964
+ }
965
+ const platformInfo = getPlatformInfo();
966
+ const { existsSync: existsSync3 } = await import("fs");
967
+ if (platformInfo.opencode.exists) {
968
+ ok.push(`OpenCode detected at ${platformInfo.opencode.configDir}`);
969
+ const skillsDir = safeJoin(platformInfo.opencode.configDir, "skills");
970
+ const agentsDir = safeJoin(platformInfo.opencode.configDir, "agents");
971
+ const commandsDir = safeJoin(platformInfo.opencode.configDir, "commands");
972
+ if (!existsSync3(skillsDir)) {
973
+ warnings.push("OpenCode skills directory missing");
974
+ }
975
+ if (!existsSync3(agentsDir)) {
976
+ warnings.push("OpenCode agents directory missing");
977
+ }
978
+ if (!existsSync3(commandsDir)) {
979
+ warnings.push("OpenCode commands directory missing");
980
+ }
981
+ } else {
982
+ warnings.push("OpenCode not detected (config directory not found)");
983
+ }
984
+ if (platformInfo.claude.exists) {
985
+ ok.push(`Claude Code detected at ${platformInfo.claude.configDir}`);
986
+ const skillsDir = safeJoin(platformInfo.claude.configDir, "skills");
987
+ const agentsDir = safeJoin(platformInfo.claude.configDir, "agents");
988
+ const commandsDir = safeJoin(platformInfo.claude.configDir, "commands");
989
+ if (!existsSync3(skillsDir)) {
990
+ warnings.push("Claude Code skills directory missing");
991
+ }
992
+ if (!existsSync3(agentsDir)) {
993
+ warnings.push("Claude Code agents directory missing");
994
+ }
995
+ if (!existsSync3(commandsDir)) {
996
+ warnings.push("Claude Code commands directory missing");
997
+ }
998
+ } else {
999
+ warnings.push("Claude Code not detected (config directory not found)");
1000
+ }
1001
+ const status = await getStatus({ global: true });
1002
+ const opencodeTotal = status.opencode.skills.installed + status.opencode.agents.installed + status.opencode.commands.installed;
1003
+ const claudeTotal = status.claude.skills.installed + status.claude.agents.installed + status.claude.commands.installed;
1004
+ const totalAvailable = SKILLS.length + AGENTS.length + COMMANDS.length;
1005
+ if (status.opencode.available) {
1006
+ if (opencodeTotal === totalAvailable) {
1007
+ ok.push("OpenCode: All components installed");
1008
+ } else if (opencodeTotal > 0) {
1009
+ warnings.push(`OpenCode: Partial installation (${opencodeTotal}/${totalAvailable})`);
1010
+ } else {
1011
+ warnings.push("OpenCode: No components installed");
1012
+ }
1013
+ }
1014
+ if (status.claude.available) {
1015
+ if (claudeTotal === totalAvailable) {
1016
+ ok.push("Claude Code: All components installed");
1017
+ } else if (claudeTotal > 0) {
1018
+ warnings.push(`Claude Code: Partial installation (${claudeTotal}/${totalAvailable})`);
1019
+ } else {
1020
+ warnings.push("Claude Code: No components installed");
1021
+ }
1022
+ }
1023
+ if (ok.length > 0) {
1024
+ console.log(" \u2713 OK:");
1025
+ ok.forEach((item) => console.log(` \u2022 ${item}`));
1026
+ }
1027
+ if (warnings.length > 0) {
1028
+ console.log("\n \u26A0 Warnings:");
1029
+ warnings.forEach((item) => console.log(` \u2022 ${item}`));
1030
+ }
1031
+ if (issues.length > 0) {
1032
+ console.log("\n \u2717 Issues:");
1033
+ issues.forEach((item) => console.log(` \u2022 ${item}`));
1034
+ }
1035
+ console.log("\n Summary:");
1036
+ if (issues.length === 0 && warnings.length === 0) {
1037
+ console.log(" Everything looks good!");
1038
+ } else if (issues.length === 0) {
1039
+ console.log(` ${warnings.length} warning(s), no critical issues`);
1040
+ } else {
1041
+ console.log(` ${issues.length} issue(s), ${warnings.length} warning(s)`);
1042
+ }
1043
+ if (warnings.length > 0 || issues.length > 0) {
1044
+ console.log("\n Suggestions:");
1045
+ if (!platformInfo.opencode.exists && !platformInfo.claude.exists) {
1046
+ console.log(" \u2022 Install OpenCode or Claude Code first");
1047
+ }
1048
+ if (opencodeTotal < totalAvailable && status.opencode.available) {
1049
+ console.log(" \u2022 Run: npx ux-toolkit install --opencode --global");
1050
+ }
1051
+ if (claudeTotal < totalAvailable && status.claude.available) {
1052
+ console.log(" \u2022 Run: npx ux-toolkit install --claude --global");
1053
+ }
1054
+ }
1055
+ break;
1056
+ }
1057
+ case "info": {
1058
+ const platformInfo = getPlatformInfo();
1059
+ console.log("\nUX Toolkit - Platform Information\n");
1060
+ console.log(` Platform: ${platformInfo.platform}`);
1061
+ console.log(` Node Version: ${process.version}`);
1062
+ console.log("\n OpenCode:");
1063
+ console.log(` Config Dir: ${platformInfo.opencode.configDir}`);
1064
+ console.log(` Installed: ${platformInfo.opencode.exists ? "Yes" : "No"}`);
1065
+ console.log("\n Claude Code:");
1066
+ console.log(` Config Dir: ${platformInfo.claude.configDir}`);
1067
+ console.log(` Installed: ${platformInfo.claude.exists ? "Yes" : "No"}`);
1068
+ if (process.env.UX_TOOLKIT_CONFIG_DIR || process.env.OPENCODE_CONFIG_DIR || process.env.CLAUDE_CONFIG_DIR || process.env.XDG_CONFIG_HOME) {
1069
+ console.log("\n Environment Overrides:");
1070
+ if (process.env.UX_TOOLKIT_CONFIG_DIR) {
1071
+ console.log(` UX_TOOLKIT_CONFIG_DIR: ${process.env.UX_TOOLKIT_CONFIG_DIR}`);
1072
+ }
1073
+ if (process.env.OPENCODE_CONFIG_DIR) {
1074
+ console.log(` OPENCODE_CONFIG_DIR: ${process.env.OPENCODE_CONFIG_DIR}`);
1075
+ }
1076
+ if (process.env.CLAUDE_CONFIG_DIR) {
1077
+ console.log(` CLAUDE_CONFIG_DIR: ${process.env.CLAUDE_CONFIG_DIR}`);
1078
+ }
1079
+ if (process.env.XDG_CONFIG_HOME) {
1080
+ console.log(` XDG_CONFIG_HOME: ${process.env.XDG_CONFIG_HOME}`);
1081
+ }
1082
+ }
1083
+ console.log(`
1084
+ Components:`);
1085
+ console.log(` Skills: ${SKILLS.length}`);
1086
+ console.log(` Agents: ${AGENTS.length}`);
1087
+ console.log(` Commands: ${COMMANDS.length}`);
1088
+ break;
1089
+ }
443
1090
  case "list": {
444
1091
  console.log("\nSkills:");
445
1092
  SKILLS.forEach((s) => console.log(` ${s.name.padEnd(25)} ${s.description}`));