@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 +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +16 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +167 -0
- package/dist/index.js.map +1 -0
- package/package.json +10 -5
- package/src/cli.ts +0 -19
- package/src/index.ts +0 -197
package/dist/cli.d.ts
ADDED
|
@@ -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
|
package/dist/cli.js.map
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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.
|
|
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": "
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
11
12
|
"bin": {
|
|
12
|
-
"skill-installer": "./
|
|
13
|
+
"skill-installer": "./dist/cli.js"
|
|
13
14
|
},
|
|
14
15
|
"exports": {
|
|
15
|
-
".":
|
|
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
|
-
"
|
|
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
|
-
}
|