ux-toolkit 0.4.1 → 0.5.0

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