@elliemae/pui-cli 9.0.0-alpha.4 → 9.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,16 @@
8
8
 
9
9
  ## Migration Guide
10
10
 
11
+ ### pui-cli 9 (Node 24, pnpm 11, ESLint 9)
12
+
13
+ Team upgrade guide: [pui-cli 9 migration guide](docs/pui-cli-9-migration.md).
14
+
15
+ Install the bundled Cursor skill in a consumer repo:
16
+
17
+ ```bash
18
+ pnpm exec pui-cli skills install migrate-to-pui-cli-9 --target all
19
+ ```
20
+
11
21
  ### ESLint 9 flat config (alpha)
12
22
 
13
23
  `pui-cli` now ships ESLint 9 with `typescript-eslint` v8 flat configs. Replace legacy `.eslintrc.cjs` with:
package/dist/cjs/cli.js CHANGED
@@ -40,6 +40,7 @@ var import_vitest = require("./commands/vitest.js");
40
40
  var import_version = require("./commands/version.js");
41
41
  var import_tscheck = require("./commands/tscheck.js");
42
42
  var import_buildcdn = require("./commands/buildcdn.js");
43
+ var import_skills = require("./commands/skills.js");
43
44
  const import_meta = {};
44
45
  const __dirname = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
45
46
  (0, import_dotenv.config)();
@@ -57,5 +58,6 @@ process.env.PATH += import_node_path.default.delimiter + import_node_path.defaul
57
58
  await (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).command(import_version.versionCmd).help().argv;
58
59
  await (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).command(import_tscheck.tscheckCmd).help().argv;
59
60
  await (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).command(import_buildcdn.buildCDNCmd).help().argv;
61
+ await (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).command(import_skills.skillsCmd).help().argv;
60
62
  await (0, import_update_notifier.notifyUpdates)();
61
63
  })();
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var skills_exports = {};
30
+ __export(skills_exports, {
31
+ skillsCmd: () => skillsCmd
32
+ });
33
+ module.exports = __toCommonJS(skills_exports);
34
+ var import_node_path = __toESM(require("node:path"), 1);
35
+ var import_node_url = require("node:url");
36
+ var import_node_module = require("node:module");
37
+ var import_promises = require("node:fs/promises");
38
+ var import_node_fs = require("node:fs");
39
+ var import_yargs = __toESM(require("yargs"), 1);
40
+ var import_utils = require("./utils.js");
41
+ const import_meta = {};
42
+ const SKILL_TARGETS = ["cursor", "claude", "copilot", "all"];
43
+ const TARGET_RELATIVE_DIRS = {
44
+ cursor: [".cursor", "skills"],
45
+ claude: [".claude", "skills"],
46
+ copilot: [".github", "skills"]
47
+ };
48
+ const __dirname = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
49
+ const getPackageRoot = () => {
50
+ const require2 = (0, import_node_module.createRequire)(import_meta.url);
51
+ try {
52
+ return import_node_path.default.dirname(require2.resolve("@elliemae/pui-cli/package.json"));
53
+ } catch {
54
+ return import_node_path.default.resolve(__dirname, "..", "..");
55
+ }
56
+ };
57
+ const getBundledSkillsDir = () => import_node_path.default.join(getPackageRoot(), "lib", "skills");
58
+ const getTargetSkillsDir = (target) => import_node_path.default.join(process.cwd(), ...TARGET_RELATIVE_DIRS[target]);
59
+ const resolveTargets = (target) => {
60
+ const selected = target ? Array.isArray(target) ? target : [target] : ["cursor"];
61
+ if (selected.includes("all")) {
62
+ return ["cursor", "claude", "copilot"];
63
+ }
64
+ return selected;
65
+ };
66
+ const pathExists = async (targetPath) => {
67
+ try {
68
+ await (0, import_promises.access)(targetPath, import_node_fs.constants.F_OK);
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ };
74
+ const listSkillNames = async (skillsDir) => {
75
+ const entries = await (0, import_promises.readdir)(skillsDir, { withFileTypes: true });
76
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
77
+ };
78
+ const copySkillDir = async (src, dest) => {
79
+ await (0, import_promises.mkdir)(dest, { recursive: true });
80
+ const entries = await (0, import_promises.readdir)(src, { withFileTypes: true });
81
+ await Promise.all(
82
+ entries.map(async (entry) => {
83
+ const srcPath = import_node_path.default.join(src, entry.name);
84
+ const destPath = import_node_path.default.join(dest, entry.name);
85
+ if (entry.isDirectory()) {
86
+ return copySkillDir(srcPath, destPath);
87
+ }
88
+ return (0, import_promises.copyFile)(srcPath, destPath);
89
+ })
90
+ );
91
+ };
92
+ const installSkillToTarget = async (skillName, target, force) => {
93
+ const bundledDir = getBundledSkillsDir();
94
+ const src = import_node_path.default.join(bundledDir, skillName);
95
+ const dest = import_node_path.default.join(getTargetSkillsDir(target), skillName);
96
+ if (!await pathExists(src)) {
97
+ throw new Error(
98
+ `Skill "${skillName}" not found. Run "pui-cli skills list" for available skills.`
99
+ );
100
+ }
101
+ const srcStat = await (0, import_promises.stat)(src);
102
+ if (!srcStat.isDirectory()) {
103
+ throw new Error(`Skill "${skillName}" is not a valid skill directory.`);
104
+ }
105
+ if (await pathExists(dest) && !force) {
106
+ (0, import_utils.logWarning)(
107
+ `Skipped "${skillName}" for ${target} \u2014 already exists at ${dest}. Use --force to overwrite.`
108
+ );
109
+ return false;
110
+ }
111
+ await copySkillDir(src, dest);
112
+ (0, import_utils.logSuccess)(`Installed skill "${skillName}" to ${dest}`);
113
+ return true;
114
+ };
115
+ const installSkill = async (skillName, targets, force) => {
116
+ let installed = 0;
117
+ for (const target of targets) {
118
+ await (0, import_promises.mkdir)(getTargetSkillsDir(target), { recursive: true });
119
+ if (await installSkillToTarget(skillName, target, force)) {
120
+ installed += 1;
121
+ }
122
+ }
123
+ return installed > 0;
124
+ };
125
+ const runList = async () => {
126
+ const bundledDir = getBundledSkillsDir();
127
+ if (!await pathExists(bundledDir)) {
128
+ throw new Error(`Bundled skills directory not found: ${bundledDir}`);
129
+ }
130
+ const skills = await listSkillNames(bundledDir);
131
+ if (!skills.length) {
132
+ (0, import_utils.logInfo)("No bundled skills found.");
133
+ return;
134
+ }
135
+ (0, import_utils.logInfo)("Available skills:");
136
+ skills.forEach((name) => (0, import_utils.logInfo)(` - ${name}`));
137
+ };
138
+ const logReloadHint = (targets) => {
139
+ const hints = [];
140
+ if (targets.includes("cursor")) {
141
+ hints.push("Cursor (.cursor/skills/)");
142
+ }
143
+ if (targets.includes("claude")) {
144
+ hints.push("Claude Code (.claude/skills/)");
145
+ }
146
+ if (targets.includes("copilot")) {
147
+ hints.push("GitHub Copilot (.github/skills/)");
148
+ }
149
+ if (hints.length) {
150
+ (0, import_utils.logInfo)(
151
+ `Restart or reload your agent to pick up skills in: ${hints.join(", ")}.`
152
+ );
153
+ }
154
+ };
155
+ const runInstall = async (name, force, target) => {
156
+ const bundledDir = getBundledSkillsDir();
157
+ if (!await pathExists(bundledDir)) {
158
+ throw new Error(`Bundled skills directory not found: ${bundledDir}`);
159
+ }
160
+ const targets = resolveTargets(target);
161
+ const skills = name ? [name] : await listSkillNames(bundledDir);
162
+ if (!skills.length) {
163
+ (0, import_utils.logInfo)("No bundled skills to install.");
164
+ return;
165
+ }
166
+ let installed = 0;
167
+ for (const skillName of skills) {
168
+ if (await installSkill(skillName, targets, force)) {
169
+ installed += 1;
170
+ }
171
+ }
172
+ if (installed) {
173
+ logReloadHint(targets);
174
+ }
175
+ };
176
+ const skillsCmd = {
177
+ command: "skills <action> [name]",
178
+ describe: "Install bundled agent skills for Cursor, Claude Code, and GitHub Copilot",
179
+ builder: (yargsRef) => yargsRef.positional("action", {
180
+ describe: "List bundled skills or install them into the current repo",
181
+ choices: ["list", "install"],
182
+ demandOption: true
183
+ }).positional("name", {
184
+ describe: "Skill folder name (install all when omitted)",
185
+ type: "string"
186
+ }).option("target", {
187
+ describe: "Install destination: cursor (.cursor/skills), claude (.claude/skills), copilot (.github/skills), or all",
188
+ choices: SKILL_TARGETS,
189
+ default: "cursor"
190
+ }).option("force", {
191
+ describe: "Overwrite an existing skill directory",
192
+ type: "boolean",
193
+ default: false
194
+ }).help(),
195
+ handler: async (argv) => {
196
+ try {
197
+ if (argv.action === "list") {
198
+ await runList();
199
+ return;
200
+ }
201
+ await runInstall(argv.name, Boolean(argv.force), argv.target);
202
+ } catch (err) {
203
+ (0, import_utils.logError)(err.message);
204
+ (0, import_yargs.default)().exit(-1, err);
205
+ }
206
+ }
207
+ };
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: migrate-to-pui-cli-9
3
+ description: >-
4
+ Migrate a PUI app or library to pui-cli 9 (Node 24, pnpm 11, ESLint 9 flat config).
5
+ Use when upgrading @elliemae/pui-cli, migrating from .eslintrc.cjs to eslint.config.mjs,
6
+ fixing ESLint 9 lint failures, or adopting the shared flat config from pui-cli.
7
+ ---
8
+
9
+ # Migrate to pui-cli 9
10
+
11
+ Upgrades a PUI repo from pui-cli 8 (ESLint 8 + `.eslintrc.cjs`) to pui-cli 9
12
+ (ESLint 9 + `eslint.config.mjs` + Node 24 + pnpm 11).
13
+
14
+ ## Pre-flight: Toolchain
15
+
16
+ | Requirement | Version |
17
+ | ------------------- | --------------------------------- |
18
+ | Node.js | **24** (see repo `.node-version`) |
19
+ | pnpm | **11** |
20
+ | `@elliemae/pui-cli` | **9.x** (alpha/beta until GA) |
21
+
22
+ ```bash
23
+ fnm use 24 # or nvm/volta equivalent
24
+ corepack enable
25
+ pnpm install
26
+ ```
27
+
28
+ ## Migration steps (phased PRs recommended)
29
+
30
+ ### Phase 1 — Toolchain only
31
+
32
+ 1. Update `.node-version` to `24` if needed.
33
+ 2. Ensure CI Jenkins/docker images use Node 24.
34
+ 3. Upgrade pnpm to 11 (`packageManager` field in root `package.json`).
35
+ 4. Run `pnpm install`, `pnpm test`, `pnpm run build` — fix any Node/pnpm breakages only.
36
+
37
+ ### Phase 2 — Bump pui-cli
38
+
39
+ ```bash
40
+ pnpm add -D @elliemae/pui-cli@9
41
+ ```
42
+
43
+ Re-run install and smoke-test build/test without ESLint changes yet if the bump is large.
44
+
45
+ ### Phase 3 — ESLint 9 flat config
46
+
47
+ **React apps and libraries:**
48
+
49
+ ```js
50
+ // eslint.config.mjs
51
+ import { eslintFlatConfig } from '@elliemae/pui-cli/eslint';
52
+
53
+ export default eslintFlatConfig;
54
+ ```
55
+
56
+ **Node / TS services (non-React):**
57
+
58
+ ```js
59
+ // eslint.config.mjs
60
+ import { eslintFlatBaseConfig } from '@elliemae/pui-cli/eslint';
61
+
62
+ export default eslintFlatBaseConfig;
63
+ ```
64
+
65
+ Then:
66
+
67
+ 1. Delete `.eslintrc.cjs` and `.eslintignore` (ignores are in the shared config).
68
+ 2. Run `pnpm exec pui-cli lint --fix`.
69
+ 3. Run `pnpm exec pui-cli lint` until **zero errors** (warnings may remain).
70
+
71
+ **Strict mode** (only after default config is clean):
72
+
73
+ ```js
74
+ import { eslintFlatConfigStrict } from '@elliemae/pui-cli/eslint';
75
+ export default eslintFlatConfigStrict;
76
+ ```
77
+
78
+ ### Phase 4 — Repo-specific overrides (if needed)
79
+
80
+ Add overrides **after** importing the shared config only when lint debt blocks the migration:
81
+
82
+ ```js
83
+ import { eslintFlatConfig } from '@elliemae/pui-cli/eslint';
84
+
85
+ export default [
86
+ ...eslintFlatConfig,
87
+ {
88
+ rules: {
89
+ // Temporary: legacy had ~100 no-explicit-any warnings via @typescript-eslint v5
90
+ '@typescript-eslint/no-explicit-any': 'warn',
91
+ },
92
+ },
93
+ ];
94
+ ```
95
+
96
+ Remove overrides in a follow-up debt PR. Do not copy Airbnb or legacy `.eslintrc` rules.
97
+
98
+ ### Phase 5 — Verify
99
+
100
+ - [ ] `pnpm exec pui-cli lint` — 0 errors
101
+ - [ ] `pnpm exec pui-cli tscheck --files`
102
+ - [ ] `pnpm test`
103
+ - [ ] `pnpm run build` (or `pui-cli build`)
104
+ - [ ] Pre-commit / lint-staged passes
105
+ - [ ] CI green on the target branch
106
+
107
+ ## Common lint fixes after upgrade
108
+
109
+ | Symptom | Fix |
110
+ | ------------------------------------ | ---------------------------------------------------------------------- |
111
+ | `import-x/no-unresolved` | Use `import type` for type-only imports |
112
+ | `@typescript-eslint/no-explicit-any` | Type the value or add targeted override (warn) during transition |
113
+ | `@typescript-eslint/no-unused-vars` | Remove or prefix with `_` |
114
+ | Stale `eslint-disable` comments | Remove disables for rules no longer in config |
115
+ | Test/fixture files flagged | Shared config includes `lib/**/tests/**` globs — ensure pui-cli 9.0.0+ |
116
+ | `.d.ts` files | pui-cli turns off `no-explicit-any` for `**/*.d.ts` |
117
+
118
+ Full rule comparison: [eslint-rules-migration.md](https://docs.pui.mortgagetech.q1.ice.com/cli/eslint-rules-migration) (also at `docs/eslint-rules-migration.md` in pui-cli).
119
+
120
+ ## Install this skill in Cursor
121
+
122
+ From the consumer repo (after `@elliemae/pui-cli` is installed):
123
+
124
+ ```bash
125
+ pnpm exec pui-cli skills install migrate-to-pui-cli-9 --target all
126
+ ```
127
+
128
+ Skills are copied to `.cursor/skills/`, `.claude/skills/`, and `.github/skills/` for Cursor, Claude Code, and GitHub Copilot.
129
+
130
+ ## What NOT to change
131
+
132
+ - Application business logic — migration is tooling/config only
133
+ - Webpack/babel config unless pui-cli 9 release notes require it
134
+ - Prettier / Stylelint / commitlint configs unless pui-cli 9 bumps those presets
135
+
136
+ ## Additional resources
137
+
138
+ - [pui-cli 9 migration guide](https://docs.pui.mortgagetech.q1.ice.com/cli/pui-cli-9-migration)
139
+ - [ESLint rules migration guide](https://docs.pui.mortgagetech.q1.ice.com/cli/eslint-rules-migration)
140
+ - Reference one-liner: `pui-react-boilerplate/eslint.config.mjs`
package/dist/esm/cli.js CHANGED
@@ -17,6 +17,7 @@ import { vitestCmd } from "./commands/vitest.js";
17
17
  import { versionCmd } from "./commands/version.js";
18
18
  import { tscheckCmd } from "./commands/tscheck.js";
19
19
  import { buildCDNCmd } from "./commands/buildcdn.js";
20
+ import { skillsCmd } from "./commands/skills.js";
20
21
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
22
  envConfig();
22
23
  process.env.PATH += path.delimiter + path.join(__dirname, "..", "node_modules", ".bin");
@@ -33,5 +34,6 @@ process.env.PATH += path.delimiter + path.join(__dirname, "..", "node_modules",
33
34
  await yargs(hideBin(process.argv)).command(versionCmd).help().argv;
34
35
  await yargs(hideBin(process.argv)).command(tscheckCmd).help().argv;
35
36
  await yargs(hideBin(process.argv)).command(buildCDNCmd).help().argv;
37
+ await yargs(hideBin(process.argv)).command(skillsCmd).help().argv;
36
38
  await notifyUpdates();
37
39
  })();
@@ -0,0 +1,176 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { createRequire } from "node:module";
4
+ import { access, copyFile, mkdir, readdir, stat } from "node:fs/promises";
5
+ import { constants } from "node:fs";
6
+ import yargs from "yargs";
7
+ import { logError, logInfo, logSuccess, logWarning } from "./utils.js";
8
+ const SKILL_TARGETS = ["cursor", "claude", "copilot", "all"];
9
+ const TARGET_RELATIVE_DIRS = {
10
+ cursor: [".cursor", "skills"],
11
+ claude: [".claude", "skills"],
12
+ copilot: [".github", "skills"]
13
+ };
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const getPackageRoot = () => {
16
+ const require2 = createRequire(import.meta.url);
17
+ try {
18
+ return path.dirname(require2.resolve("@elliemae/pui-cli/package.json"));
19
+ } catch {
20
+ return path.resolve(__dirname, "..", "..");
21
+ }
22
+ };
23
+ const getBundledSkillsDir = () => path.join(getPackageRoot(), "lib", "skills");
24
+ const getTargetSkillsDir = (target) => path.join(process.cwd(), ...TARGET_RELATIVE_DIRS[target]);
25
+ const resolveTargets = (target) => {
26
+ const selected = target ? Array.isArray(target) ? target : [target] : ["cursor"];
27
+ if (selected.includes("all")) {
28
+ return ["cursor", "claude", "copilot"];
29
+ }
30
+ return selected;
31
+ };
32
+ const pathExists = async (targetPath) => {
33
+ try {
34
+ await access(targetPath, constants.F_OK);
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ };
40
+ const listSkillNames = async (skillsDir) => {
41
+ const entries = await readdir(skillsDir, { withFileTypes: true });
42
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
43
+ };
44
+ const copySkillDir = async (src, dest) => {
45
+ await mkdir(dest, { recursive: true });
46
+ const entries = await readdir(src, { withFileTypes: true });
47
+ await Promise.all(
48
+ entries.map(async (entry) => {
49
+ const srcPath = path.join(src, entry.name);
50
+ const destPath = path.join(dest, entry.name);
51
+ if (entry.isDirectory()) {
52
+ return copySkillDir(srcPath, destPath);
53
+ }
54
+ return copyFile(srcPath, destPath);
55
+ })
56
+ );
57
+ };
58
+ const installSkillToTarget = async (skillName, target, force) => {
59
+ const bundledDir = getBundledSkillsDir();
60
+ const src = path.join(bundledDir, skillName);
61
+ const dest = path.join(getTargetSkillsDir(target), skillName);
62
+ if (!await pathExists(src)) {
63
+ throw new Error(
64
+ `Skill "${skillName}" not found. Run "pui-cli skills list" for available skills.`
65
+ );
66
+ }
67
+ const srcStat = await stat(src);
68
+ if (!srcStat.isDirectory()) {
69
+ throw new Error(`Skill "${skillName}" is not a valid skill directory.`);
70
+ }
71
+ if (await pathExists(dest) && !force) {
72
+ logWarning(
73
+ `Skipped "${skillName}" for ${target} \u2014 already exists at ${dest}. Use --force to overwrite.`
74
+ );
75
+ return false;
76
+ }
77
+ await copySkillDir(src, dest);
78
+ logSuccess(`Installed skill "${skillName}" to ${dest}`);
79
+ return true;
80
+ };
81
+ const installSkill = async (skillName, targets, force) => {
82
+ let installed = 0;
83
+ for (const target of targets) {
84
+ await mkdir(getTargetSkillsDir(target), { recursive: true });
85
+ if (await installSkillToTarget(skillName, target, force)) {
86
+ installed += 1;
87
+ }
88
+ }
89
+ return installed > 0;
90
+ };
91
+ const runList = async () => {
92
+ const bundledDir = getBundledSkillsDir();
93
+ if (!await pathExists(bundledDir)) {
94
+ throw new Error(`Bundled skills directory not found: ${bundledDir}`);
95
+ }
96
+ const skills = await listSkillNames(bundledDir);
97
+ if (!skills.length) {
98
+ logInfo("No bundled skills found.");
99
+ return;
100
+ }
101
+ logInfo("Available skills:");
102
+ skills.forEach((name) => logInfo(` - ${name}`));
103
+ };
104
+ const logReloadHint = (targets) => {
105
+ const hints = [];
106
+ if (targets.includes("cursor")) {
107
+ hints.push("Cursor (.cursor/skills/)");
108
+ }
109
+ if (targets.includes("claude")) {
110
+ hints.push("Claude Code (.claude/skills/)");
111
+ }
112
+ if (targets.includes("copilot")) {
113
+ hints.push("GitHub Copilot (.github/skills/)");
114
+ }
115
+ if (hints.length) {
116
+ logInfo(
117
+ `Restart or reload your agent to pick up skills in: ${hints.join(", ")}.`
118
+ );
119
+ }
120
+ };
121
+ const runInstall = async (name, force, target) => {
122
+ const bundledDir = getBundledSkillsDir();
123
+ if (!await pathExists(bundledDir)) {
124
+ throw new Error(`Bundled skills directory not found: ${bundledDir}`);
125
+ }
126
+ const targets = resolveTargets(target);
127
+ const skills = name ? [name] : await listSkillNames(bundledDir);
128
+ if (!skills.length) {
129
+ logInfo("No bundled skills to install.");
130
+ return;
131
+ }
132
+ let installed = 0;
133
+ for (const skillName of skills) {
134
+ if (await installSkill(skillName, targets, force)) {
135
+ installed += 1;
136
+ }
137
+ }
138
+ if (installed) {
139
+ logReloadHint(targets);
140
+ }
141
+ };
142
+ const skillsCmd = {
143
+ command: "skills <action> [name]",
144
+ describe: "Install bundled agent skills for Cursor, Claude Code, and GitHub Copilot",
145
+ builder: (yargsRef) => yargsRef.positional("action", {
146
+ describe: "List bundled skills or install them into the current repo",
147
+ choices: ["list", "install"],
148
+ demandOption: true
149
+ }).positional("name", {
150
+ describe: "Skill folder name (install all when omitted)",
151
+ type: "string"
152
+ }).option("target", {
153
+ describe: "Install destination: cursor (.cursor/skills), claude (.claude/skills), copilot (.github/skills), or all",
154
+ choices: SKILL_TARGETS,
155
+ default: "cursor"
156
+ }).option("force", {
157
+ describe: "Overwrite an existing skill directory",
158
+ type: "boolean",
159
+ default: false
160
+ }).help(),
161
+ handler: async (argv) => {
162
+ try {
163
+ if (argv.action === "list") {
164
+ await runList();
165
+ return;
166
+ }
167
+ await runInstall(argv.name, Boolean(argv.force), argv.target);
168
+ } catch (err) {
169
+ logError(err.message);
170
+ yargs().exit(-1, err);
171
+ }
172
+ }
173
+ };
174
+ export {
175
+ skillsCmd
176
+ };