@funstack/skill-installer 1.0.0-alpha.2 → 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/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,18 +1,22 @@
1
1
  {
2
2
  "name": "@funstack/skill-installer",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.3",
4
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/cli.ts"
13
+ "skill-installer": "./dist/cli.js"
13
14
  },
14
15
  "exports": {
15
- ".": "./src/index.ts"
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ }
16
20
  },
17
21
  "keywords": [],
18
22
  "author": "uhyo <uhyo@uhy.ooo>",
@@ -23,9 +27,10 @@
23
27
  "typescript": "^5.9.3"
24
28
  },
25
29
  "files": [
26
- "src"
30
+ "dist"
27
31
  ],
28
32
  "scripts": {
33
+ "build": "tsc",
29
34
  "format": "prettier --write . --experimental-cli",
30
35
  "format:check": "prettier --check . --experimental-cli",
31
36
  "typecheck": "tsc --noEmit",
package/src/cli.ts DELETED
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { install } from "./index.ts";
4
-
5
- async function main() {
6
- const skillPath = process.argv[2];
7
- if (!skillPath) {
8
- console.error("Usage: skill-installer <skill-path>");
9
- console.error(" <skill-path> Path to the skill directory to install");
10
- process.exit(1);
11
- }
12
-
13
- await install(skillPath);
14
- }
15
-
16
- main().catch((error) => {
17
- console.error("Error:", error.message);
18
- process.exit(1);
19
- });
package/src/index.ts DELETED
@@ -1,197 +0,0 @@
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
-
7
- interface Option {
8
- name: string;
9
- path: string | null;
10
- }
11
-
12
- function renderOptions(options: Option[], selectedIndex: number): void {
13
- options.forEach((option, index) => {
14
- const isSelected = index === selectedIndex;
15
- const number = styleText("dim", `${index + 1}.`);
16
- const pathDisplay = styleText("dim", `(${option.path ?? "custom path"})`);
17
-
18
- if (isSelected) {
19
- const indicator = styleText("cyan", "❯");
20
- const name = styleText(["bold", "cyan"], option.name);
21
- console.log(`${indicator} ${number} ${name} ${pathDisplay}`);
22
- } else {
23
- console.log(` ${number} ${option.name} ${pathDisplay}`);
24
- }
25
- });
26
- }
27
-
28
- function clearOptions(count: number): void {
29
- // Move cursor up and clear each line
30
- for (let i = 0; i < count; i++) {
31
- stdout.write("\x1b[1A"); // Move up one line
32
- stdout.write("\x1b[2K"); // Clear the line
33
- }
34
- }
35
-
36
- async function selectOption(
37
- options: Option[],
38
- footer?: string,
39
- ): Promise<number> {
40
- let selectedIndex = 0;
41
-
42
- const render = () => {
43
- renderOptions(options, selectedIndex);
44
- if (footer) {
45
- console.log();
46
- console.log(styleText("dim", footer));
47
- }
48
- };
49
-
50
- const clear = () => {
51
- const lineCount = options.length + (footer ? 2 : 0);
52
- clearOptions(lineCount);
53
- };
54
-
55
- // Initial render
56
- render();
57
-
58
- return new Promise((resolve) => {
59
- stdin.setRawMode(true);
60
- stdin.resume();
61
-
62
- const onKeypress = (data: Buffer) => {
63
- const key = data.toString();
64
-
65
- // Handle arrow keys (escape sequences)
66
- if (key === "\x1b[A") {
67
- // Up arrow
68
- clear();
69
- selectedIndex = (selectedIndex - 1 + options.length) % options.length;
70
- render();
71
- } else if (key === "\x1b[B") {
72
- // Down arrow
73
- clear();
74
- selectedIndex = (selectedIndex + 1) % options.length;
75
- render();
76
- } else if (key >= "1" && key <= "9") {
77
- // Number keys 1-9
78
- const targetIndex = parseInt(key, 10) - 1;
79
- if (targetIndex < options.length) {
80
- clear();
81
- selectedIndex = targetIndex;
82
- render();
83
- }
84
- } else if (key === "\r" || key === "\n") {
85
- // Enter key
86
- stdin.setRawMode(false);
87
- stdin.pause();
88
- stdin.removeListener("data", onKeypress);
89
- resolve(selectedIndex);
90
- } else if (key === "\x03") {
91
- // Ctrl+C
92
- stdin.setRawMode(false);
93
- process.exit(0);
94
- }
95
- };
96
-
97
- stdin.on("data", onKeypress);
98
- });
99
- }
100
-
101
- /**
102
- * Install a skill to the appropriate AI agent directory.
103
- * In TTY mode, prompts the user to select an agent.
104
- * In non-TTY mode, uses the SKILL_INSTALL_PATH environment variable.
105
- *
106
- * @param skillPath - Path to the skill directory to install
107
- * @returns The path where the skill was installed
108
- */
109
- export async function install(skillPath: string): Promise<string> {
110
- // Validate that the skill path exists and is a directory
111
- try {
112
- const stats = await fs.stat(skillPath);
113
- if (!stats.isDirectory()) {
114
- throw new Error(`${skillPath} is not a directory`);
115
- }
116
- } catch (error) {
117
- if (error instanceof Error && error.message.includes("is not a directory")) {
118
- throw error;
119
- }
120
- throw new Error(`${skillPath} does not exist`);
121
- }
122
-
123
- // Determine installation path
124
- let destinationPath: string;
125
-
126
- if (!stdin.isTTY) {
127
- // Non-TTY mode: read from environment variable
128
- const envPath = process.env.SKILL_INSTALL_PATH;
129
- if (!envPath) {
130
- throw new Error(
131
- "stdin is not a TTY and SKILL_INSTALL_PATH is not set.\n\n" +
132
- "In non-interactive mode, set the SKILL_INSTALL_PATH environment variable:\n" +
133
- " SKILL_INSTALL_PATH=./.claude/skills skill-installer <skill-path>",
134
- );
135
- }
136
- destinationPath = envPath;
137
- } else {
138
- // TTY mode: interactive prompt
139
- const options: Option[] = [
140
- { name: "Claude Code", path: "./.claude/skills" },
141
- { name: "Codex", path: "./.codex/skills" },
142
- { name: "GitHub Copilot", path: "./.github/skills" },
143
- { name: "Cursor", path: "./.cursor/skills" },
144
- { name: "Gemini CLI", path: "./.gemini/skills" },
145
- { name: "Windsurf", path: "./.windsurf/skills" },
146
- { name: "OpenCode", path: "./.opencode/skills" },
147
- { name: "Other", path: null },
148
- ];
149
-
150
- console.log(
151
- "\n" +
152
- styleText("bold", "Select AI Agent") +
153
- styleText("dim", " (↑↓ to move, Enter to confirm)"),
154
- );
155
- const selectedIndex = await selectOption(
156
- options,
157
- "Missing your agent? Let us know: https://github.com/uhyo/funstack-skill-installer/issues",
158
- );
159
- const selectedOption = options[selectedIndex]!;
160
-
161
- if (selectedOption.path !== null) {
162
- destinationPath = selectedOption.path;
163
- } else {
164
- const rl = readline.createInterface({ input: stdin, output: stdout });
165
- try {
166
- const customPath = await rl.question(
167
- "\nEnter custom installation path: ",
168
- );
169
- if (!customPath.trim()) {
170
- throw new Error("Installation path cannot be empty");
171
- }
172
- destinationPath = customPath.trim();
173
- } finally {
174
- rl.close();
175
- }
176
- }
177
- }
178
-
179
- // Copy skill files
180
- // Create destination directory if it doesn't exist
181
- await fs.mkdir(destinationPath, { recursive: true });
182
-
183
- // Copy the skill directory contents to the destination
184
- const skillName = path.basename(skillPath);
185
- const finalDestination = path.join(destinationPath, skillName);
186
-
187
- await fs.cp(skillPath, finalDestination, { recursive: true });
188
-
189
- console.log(
190
- "\n" +
191
- styleText("green", "✓") +
192
- " Skill installed successfully to: " +
193
- styleText("bold", finalDestination),
194
- );
195
-
196
- return finalDestination;
197
- }