@funstack/skill-installer 1.0.0-alpha.1 → 1.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @funstack/skill-installer
2
2
 
3
- A CLI tool to install AI Agent skills by copying skill files to the appropriate location.
3
+ A CLI tool and library to install AI Agent skills by copying skill files to the appropriate location.
4
4
 
5
5
  ## Installation
6
6
 
@@ -46,6 +46,20 @@ For CI/CD pipelines or scripted installations, set the `SKILL_INSTALL_PATH` envi
46
46
  SKILL_INSTALL_PATH=./.claude/skills skill-installer ./path/to/my-skill
47
47
  ```
48
48
 
49
+ ### Programmatic Usage
50
+
51
+ You can also use this package as a library:
52
+
53
+ ```typescript
54
+ import { install } from '@funstack/skill-installer';
55
+
56
+ // Prompts the user to select an agent and installs the skill
57
+ const installedPath = await install('./path/to/my-skill');
58
+ console.log(`Installed to: ${installedPath}`);
59
+ ```
60
+
61
+ The `install` function returns a promise that resolves to the final installation path.
62
+
49
63
  ## Supported AI Agents
50
64
 
51
65
  | Agent | Installation Path |
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { install } from "./index.js";
3
+ async function main() {
4
+ const skillPath = process.argv[2];
5
+ if (!skillPath) {
6
+ console.error("Usage: skill-installer <skill-path>");
7
+ console.error(" <skill-path> Path to the skill directory to install");
8
+ process.exit(1);
9
+ }
10
+ await install(skillPath);
11
+ }
12
+ main().catch((error) => {
13
+ console.error("Error:", error.message);
14
+ process.exit(1);
15
+ });
16
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACrD,OAAO,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;AAC3B,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Install a skill to the appropriate AI agent directory.
3
+ * In TTY mode, prompts the user to select an agent.
4
+ * In non-TTY mode, uses the SKILL_INSTALL_PATH environment variable.
5
+ *
6
+ * @param skillPath - Path to the skill directory to install
7
+ * @returns The path where the skill was installed
8
+ */
9
+ export declare function install(skillPath: string): Promise<string>;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoGA;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwFhE"}
package/dist/index.js ADDED
@@ -0,0 +1,167 @@
1
+ import * as readline from "node:readline/promises";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ import { stdin, stdout } from "node:process";
5
+ import { styleText } from "node:util";
6
+ function renderOptions(options, selectedIndex) {
7
+ options.forEach((option, index) => {
8
+ const isSelected = index === selectedIndex;
9
+ const number = styleText("dim", `${index + 1}.`);
10
+ const pathDisplay = styleText("dim", `(${option.path ?? "custom path"})`);
11
+ if (isSelected) {
12
+ const indicator = styleText("cyan", "❯");
13
+ const name = styleText(["bold", "cyan"], option.name);
14
+ console.log(`${indicator} ${number} ${name} ${pathDisplay}`);
15
+ }
16
+ else {
17
+ console.log(` ${number} ${option.name} ${pathDisplay}`);
18
+ }
19
+ });
20
+ }
21
+ function clearOptions(count) {
22
+ // Move cursor up and clear each line
23
+ for (let i = 0; i < count; i++) {
24
+ stdout.write("\x1b[1A"); // Move up one line
25
+ stdout.write("\x1b[2K"); // Clear the line
26
+ }
27
+ }
28
+ async function selectOption(options, footer) {
29
+ let selectedIndex = 0;
30
+ const render = () => {
31
+ renderOptions(options, selectedIndex);
32
+ if (footer) {
33
+ console.log();
34
+ console.log(styleText("dim", footer));
35
+ }
36
+ };
37
+ const clear = () => {
38
+ const lineCount = options.length + (footer ? 2 : 0);
39
+ clearOptions(lineCount);
40
+ };
41
+ // Initial render
42
+ render();
43
+ return new Promise((resolve) => {
44
+ stdin.setRawMode(true);
45
+ stdin.resume();
46
+ const onKeypress = (data) => {
47
+ const key = data.toString();
48
+ // Handle arrow keys (escape sequences)
49
+ if (key === "\x1b[A") {
50
+ // Up arrow
51
+ clear();
52
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
53
+ render();
54
+ }
55
+ else if (key === "\x1b[B") {
56
+ // Down arrow
57
+ clear();
58
+ selectedIndex = (selectedIndex + 1) % options.length;
59
+ render();
60
+ }
61
+ else if (key >= "1" && key <= "9") {
62
+ // Number keys 1-9
63
+ const targetIndex = parseInt(key, 10) - 1;
64
+ if (targetIndex < options.length) {
65
+ clear();
66
+ selectedIndex = targetIndex;
67
+ render();
68
+ }
69
+ }
70
+ else if (key === "\r" || key === "\n") {
71
+ // Enter key
72
+ stdin.setRawMode(false);
73
+ stdin.pause();
74
+ stdin.removeListener("data", onKeypress);
75
+ resolve(selectedIndex);
76
+ }
77
+ else if (key === "\x03") {
78
+ // Ctrl+C
79
+ stdin.setRawMode(false);
80
+ process.exit(0);
81
+ }
82
+ };
83
+ stdin.on("data", onKeypress);
84
+ });
85
+ }
86
+ /**
87
+ * Install a skill to the appropriate AI agent directory.
88
+ * In TTY mode, prompts the user to select an agent.
89
+ * In non-TTY mode, uses the SKILL_INSTALL_PATH environment variable.
90
+ *
91
+ * @param skillPath - Path to the skill directory to install
92
+ * @returns The path where the skill was installed
93
+ */
94
+ export async function install(skillPath) {
95
+ // Validate that the skill path exists and is a directory
96
+ try {
97
+ const stats = await fs.stat(skillPath);
98
+ if (!stats.isDirectory()) {
99
+ throw new Error(`${skillPath} is not a directory`);
100
+ }
101
+ }
102
+ catch (error) {
103
+ if (error instanceof Error && error.message.includes("is not a directory")) {
104
+ throw error;
105
+ }
106
+ throw new Error(`${skillPath} does not exist`);
107
+ }
108
+ // Determine installation path
109
+ let destinationPath;
110
+ if (!stdin.isTTY) {
111
+ // Non-TTY mode: read from environment variable
112
+ const envPath = process.env.SKILL_INSTALL_PATH;
113
+ if (!envPath) {
114
+ throw new Error("stdin is not a TTY and SKILL_INSTALL_PATH is not set.\n\n" +
115
+ "In non-interactive mode, set the SKILL_INSTALL_PATH environment variable:\n" +
116
+ " SKILL_INSTALL_PATH=./.claude/skills skill-installer <skill-path>");
117
+ }
118
+ destinationPath = envPath;
119
+ }
120
+ else {
121
+ // TTY mode: interactive prompt
122
+ const options = [
123
+ { name: "Claude Code", path: "./.claude/skills" },
124
+ { name: "Codex", path: "./.codex/skills" },
125
+ { name: "GitHub Copilot", path: "./.github/skills" },
126
+ { name: "Cursor", path: "./.cursor/skills" },
127
+ { name: "Gemini CLI", path: "./.gemini/skills" },
128
+ { name: "Windsurf", path: "./.windsurf/skills" },
129
+ { name: "OpenCode", path: "./.opencode/skills" },
130
+ { name: "Other", path: null },
131
+ ];
132
+ console.log("\n" +
133
+ styleText("bold", "Select AI Agent") +
134
+ styleText("dim", " (↑↓ to move, Enter to confirm)"));
135
+ const selectedIndex = await selectOption(options, "Missing your agent? Let us know: https://github.com/uhyo/funstack-skill-installer/issues");
136
+ const selectedOption = options[selectedIndex];
137
+ if (selectedOption.path !== null) {
138
+ destinationPath = selectedOption.path;
139
+ }
140
+ else {
141
+ const rl = readline.createInterface({ input: stdin, output: stdout });
142
+ try {
143
+ const customPath = await rl.question("\nEnter custom installation path: ");
144
+ if (!customPath.trim()) {
145
+ throw new Error("Installation path cannot be empty");
146
+ }
147
+ destinationPath = customPath.trim();
148
+ }
149
+ finally {
150
+ rl.close();
151
+ }
152
+ }
153
+ }
154
+ // Copy skill files
155
+ // Create destination directory if it doesn't exist
156
+ await fs.mkdir(destinationPath, { recursive: true });
157
+ // Copy the skill directory contents to the destination
158
+ const skillName = path.basename(skillPath);
159
+ const finalDestination = path.join(destinationPath, skillName);
160
+ await fs.cp(skillPath, finalDestination, { recursive: true });
161
+ console.log("\n" +
162
+ styleText("green", "✓") +
163
+ " Skill installed successfully to: " +
164
+ styleText("bold", finalDestination));
165
+ return finalDestination;
166
+ }
167
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "@funstack/skill-installer",
3
- "version": "1.0.0-alpha.1",
4
- "description": "CLI tool to install Agent Skills",
3
+ "version": "1.0.0-alpha.3",
4
+ "description": "CLI tool and library to install Agent Skills",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/uhyo/funstack-skill-installer.git"
8
8
  },
9
9
  "type": "module",
10
- "main": "src/index.ts",
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
11
12
  "bin": {
12
- "skill-installer": "./src/index.ts"
13
+ "skill-installer": "./dist/cli.js"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ }
13
20
  },
14
21
  "keywords": [],
15
22
  "author": "uhyo <uhyo@uhy.ooo>",
@@ -20,9 +27,10 @@
20
27
  "typescript": "^5.9.3"
21
28
  },
22
29
  "files": [
23
- "src"
30
+ "dist"
24
31
  ],
25
32
  "scripts": {
33
+ "build": "tsc",
26
34
  "format": "prettier --write . --experimental-cli",
27
35
  "format:check": "prettier --check . --experimental-cli",
28
36
  "typecheck": "tsc --noEmit",
package/src/index.ts DELETED
@@ -1,208 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import * as readline from "node:readline/promises";
4
- import * as fs from "node:fs/promises";
5
- import * as path from "node:path";
6
- import { stdin, stdout } from "node:process";
7
- import { styleText } from "node:util";
8
-
9
- interface Option {
10
- name: string;
11
- path: string | null;
12
- }
13
-
14
- function renderOptions(options: Option[], selectedIndex: number): void {
15
- options.forEach((option, index) => {
16
- const isSelected = index === selectedIndex;
17
- const number = styleText("dim", `${index + 1}.`);
18
- const pathDisplay = styleText("dim", `(${option.path ?? "custom path"})`);
19
-
20
- if (isSelected) {
21
- const indicator = styleText("cyan", "❯");
22
- const name = styleText(["bold", "cyan"], option.name);
23
- console.log(`${indicator} ${number} ${name} ${pathDisplay}`);
24
- } else {
25
- console.log(` ${number} ${option.name} ${pathDisplay}`);
26
- }
27
- });
28
- }
29
-
30
- function clearOptions(count: number): void {
31
- // Move cursor up and clear each line
32
- for (let i = 0; i < count; i++) {
33
- stdout.write("\x1b[1A"); // Move up one line
34
- stdout.write("\x1b[2K"); // Clear the line
35
- }
36
- }
37
-
38
- async function selectOption(
39
- options: Option[],
40
- footer?: string,
41
- ): Promise<number> {
42
- let selectedIndex = 0;
43
-
44
- const render = () => {
45
- renderOptions(options, selectedIndex);
46
- if (footer) {
47
- console.log();
48
- console.log(styleText("dim", footer));
49
- }
50
- };
51
-
52
- const clear = () => {
53
- const lineCount = options.length + (footer ? 2 : 0);
54
- clearOptions(lineCount);
55
- };
56
-
57
- // Initial render
58
- render();
59
-
60
- return new Promise((resolve) => {
61
- stdin.setRawMode(true);
62
- stdin.resume();
63
-
64
- const onKeypress = (data: Buffer) => {
65
- const key = data.toString();
66
-
67
- // Handle arrow keys (escape sequences)
68
- if (key === "\x1b[A") {
69
- // Up arrow
70
- clear();
71
- selectedIndex = (selectedIndex - 1 + options.length) % options.length;
72
- render();
73
- } else if (key === "\x1b[B") {
74
- // Down arrow
75
- clear();
76
- selectedIndex = (selectedIndex + 1) % options.length;
77
- render();
78
- } else if (key >= "1" && key <= "9") {
79
- // Number keys 1-9
80
- const targetIndex = parseInt(key, 10) - 1;
81
- if (targetIndex < options.length) {
82
- clear();
83
- selectedIndex = targetIndex;
84
- render();
85
- }
86
- } else if (key === "\r" || key === "\n") {
87
- // Enter key
88
- stdin.setRawMode(false);
89
- stdin.pause();
90
- stdin.removeListener("data", onKeypress);
91
- resolve(selectedIndex);
92
- } else if (key === "\x03") {
93
- // Ctrl+C
94
- stdin.setRawMode(false);
95
- process.exit(0);
96
- }
97
- };
98
-
99
- stdin.on("data", onKeypress);
100
- });
101
- }
102
-
103
- async function main() {
104
- // 1. Parse CLI arguments
105
- const skillPath = process.argv[2];
106
- if (!skillPath) {
107
- console.error("Usage: skill-installer <skill-path>");
108
- console.error(" <skill-path> Path to the skill directory to install");
109
- process.exit(1);
110
- }
111
-
112
- // Validate that the skill path exists and is a directory
113
- try {
114
- const stats = await fs.stat(skillPath);
115
- if (!stats.isDirectory()) {
116
- console.error(`Error: ${skillPath} is not a directory`);
117
- process.exit(1);
118
- }
119
- } catch {
120
- console.error(`Error: ${skillPath} does not exist`);
121
- process.exit(1);
122
- }
123
-
124
- // 2. Determine installation path
125
- let destinationPath: string;
126
-
127
- if (!stdin.isTTY) {
128
- // Non-TTY mode: read from environment variable
129
- const envPath = process.env.SKILL_INSTALL_PATH;
130
- if (!envPath) {
131
- console.error(
132
- "Error: stdin is not a TTY and SKILL_INSTALL_PATH is not set.",
133
- );
134
- console.error("");
135
- console.error(
136
- "In non-interactive mode, set the SKILL_INSTALL_PATH environment variable:",
137
- );
138
- console.error(
139
- " SKILL_INSTALL_PATH=./.claude/skills skill-installer <skill-path>",
140
- );
141
- process.exit(1);
142
- }
143
- destinationPath = envPath;
144
- } else {
145
- // TTY mode: interactive prompt
146
- const options: Option[] = [
147
- { name: "Claude Code", path: "./.claude/skills" },
148
- { name: "Codex", path: "./.codex/skills" },
149
- { name: "GitHub Copilot", path: "./.github/skills" },
150
- { name: "Cursor", path: "./.cursor/skills" },
151
- { name: "Gemini CLI", path: "./.gemini/skills" },
152
- { name: "Windsurf", path: "./.windsurf/skills" },
153
- { name: "OpenCode", path: "./.opencode/skills" },
154
- { name: "Other", path: null },
155
- ];
156
-
157
- console.log(
158
- "\n" +
159
- styleText("bold", "Select AI Agent") +
160
- styleText("dim", " (↑↓ to move, Enter to confirm)"),
161
- );
162
- const selectedIndex = await selectOption(
163
- options,
164
- "Missing your agent? Let us know: https://github.com/uhyo/funstack-skill-installer/issues",
165
- );
166
- const selectedOption = options[selectedIndex]!;
167
-
168
- if (selectedOption.path !== null) {
169
- destinationPath = selectedOption.path;
170
- } else {
171
- const rl = readline.createInterface({ input: stdin, output: stdout });
172
- try {
173
- const customPath = await rl.question(
174
- "\nEnter custom installation path: ",
175
- );
176
- if (!customPath.trim()) {
177
- console.error("Error: Installation path cannot be empty");
178
- process.exit(1);
179
- }
180
- destinationPath = customPath.trim();
181
- } finally {
182
- rl.close();
183
- }
184
- }
185
- }
186
-
187
- // 4. Copy skill files
188
- // Create destination directory if it doesn't exist
189
- await fs.mkdir(destinationPath, { recursive: true });
190
-
191
- // Copy the skill directory contents to the destination
192
- const skillName = path.basename(skillPath);
193
- const finalDestination = path.join(destinationPath, skillName);
194
-
195
- await fs.cp(skillPath, finalDestination, { recursive: true });
196
-
197
- console.log(
198
- "\n" +
199
- styleText("green", "✓") +
200
- " Skill installed successfully to: " +
201
- styleText("bold", finalDestination),
202
- );
203
- }
204
-
205
- main().catch((error) => {
206
- console.error("Error:", error.message);
207
- process.exit(1);
208
- });