skillett 0.1.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/commands/add.d.ts +1 -0
- package/dist/commands/add.js +39 -0
- package/dist/commands/connect.d.ts +1 -0
- package/dist/commands/connect.js +81 -0
- package/dist/commands/disconnect.d.ts +1 -0
- package/dist/commands/disconnect.js +43 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +43 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +172 -0
- package/dist/commands/pull.d.ts +3 -0
- package/dist/commands/pull.js +46 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +12 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +85 -0
- package/dist/commands/seed.d.ts +3 -0
- package/dist/commands/seed.js +253 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.js +149 -0
- package/dist/commands/skills.d.ts +1 -0
- package/dist/commands/skills.js +50 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +67 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +112 -0
- package/dist/lib/api.d.ts +25 -0
- package/dist/lib/api.js +50 -0
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.js +115 -0
- package/dist/lib/device-auth.d.ts +21 -0
- package/dist/lib/device-auth.js +38 -0
- package/dist/lib/paths.d.ts +4 -0
- package/dist/lib/paths.js +17 -0
- package/package.json +33 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function add(name: string): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import { loadApiKey } from "../lib/config.js";
|
|
6
|
+
import { downloadSkill } from "../lib/api.js";
|
|
7
|
+
import { ensureSkillsDir, getSkillPath, skillExists } from "../lib/paths.js";
|
|
8
|
+
export async function add(name) {
|
|
9
|
+
const apiKey = loadApiKey();
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
console.log(chalk.red("Not set up. Run: npx skillett setup"));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const folderName = `skillett-${name}`;
|
|
15
|
+
if (skillExists(folderName)) {
|
|
16
|
+
console.log(chalk.yellow(`Skill "${name}" is already installed. Remove it first to reinstall.`));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const spinner = ora(`Downloading ${name} skill…`).start();
|
|
20
|
+
try {
|
|
21
|
+
const { files } = await downloadSkill(apiKey, name);
|
|
22
|
+
ensureSkillsDir();
|
|
23
|
+
const skillDir = getSkillPath(folderName);
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const targetPath = resolve(skillDir, file.file_path);
|
|
26
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
27
|
+
writeFileSync(targetPath, file.content, "utf8");
|
|
28
|
+
}
|
|
29
|
+
spinner.succeed(`Installed ${chalk.bold(name)} skill (${files.length} files)`);
|
|
30
|
+
console.log(chalk.gray(` → .claude/skills/${folderName}/`));
|
|
31
|
+
console.log();
|
|
32
|
+
console.log("Your agent can now use it.");
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
spinner.fail(`Failed to install ${name}`);
|
|
36
|
+
console.error(chalk.red(err.message));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function connect(integration: string): Promise<void>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import open from "open";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { loadApiKey } from "../lib/config.js";
|
|
5
|
+
import { skillettFetch } from "../lib/api.js";
|
|
6
|
+
const POLL_INTERVAL = 3000; // 3 seconds
|
|
7
|
+
const POLL_TIMEOUT = 300000; // 5 minutes
|
|
8
|
+
export async function connect(integration) {
|
|
9
|
+
const apiKey = loadApiKey();
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
console.error(JSON.stringify({ error: "not_authenticated", message: "Run `skillett login` first." }));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const isTTY = process.stdout.isTTY;
|
|
15
|
+
// Initiate connection via API
|
|
16
|
+
const spinner = isTTY ? ora(` Connecting ${integration}…`).start() : null;
|
|
17
|
+
const res = await skillettFetch("/v1/connect", apiKey, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
body: JSON.stringify({ integration }),
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
spinner?.fail(` Failed to connect ${integration}`);
|
|
23
|
+
const body = await res.json().catch(() => ({ error: "request_failed" }));
|
|
24
|
+
console.log(JSON.stringify(body, null, 2));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
// Already connected
|
|
29
|
+
if (data.status === "already_connected") {
|
|
30
|
+
spinner?.succeed(` ${integration} is already connected`);
|
|
31
|
+
if (!isTTY) {
|
|
32
|
+
console.log(JSON.stringify({ status: "connected", integration }));
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// OAuth flow — open browser and poll
|
|
37
|
+
if (data.oauth_url && data.connection_id) {
|
|
38
|
+
spinner?.stop();
|
|
39
|
+
if (isTTY) {
|
|
40
|
+
console.log(chalk.bold(` Complete the ${integration} OAuth flow in your browser.`));
|
|
41
|
+
console.log();
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await open(data.oauth_url);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
if (isTTY) {
|
|
48
|
+
console.log(chalk.cyan(` ${data.oauth_url}`));
|
|
49
|
+
console.log();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const pollSpinner = isTTY ? ora(" Waiting for authorization…").start() : null;
|
|
53
|
+
const deadline = Date.now() + POLL_TIMEOUT;
|
|
54
|
+
while (Date.now() < deadline) {
|
|
55
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
56
|
+
const statusRes = await skillettFetch(`/v1/connect/${data.connection_id}`, apiKey);
|
|
57
|
+
if (!statusRes.ok)
|
|
58
|
+
continue;
|
|
59
|
+
const statusData = await statusRes.json();
|
|
60
|
+
if (statusData.status === "connected") {
|
|
61
|
+
pollSpinner?.succeed(` ${integration} connected`);
|
|
62
|
+
if (!isTTY) {
|
|
63
|
+
console.log(JSON.stringify({ status: "connected", integration }));
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (statusData.status === "error") {
|
|
68
|
+
pollSpinner?.fail(` ${integration} connection failed`);
|
|
69
|
+
if (!isTTY) {
|
|
70
|
+
console.log(JSON.stringify({ status: "error", integration }));
|
|
71
|
+
}
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
pollSpinner?.fail(" Connection timed out");
|
|
76
|
+
if (isTTY) {
|
|
77
|
+
console.log(chalk.gray(" Run `skillett skills` to check the status."));
|
|
78
|
+
}
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function disconnect(integration: string): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { loadApiKey } from "../lib/config.js";
|
|
4
|
+
import { skillettFetch } from "../lib/api.js";
|
|
5
|
+
export async function disconnect(integration) {
|
|
6
|
+
const apiKey = loadApiKey();
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
console.error(JSON.stringify({ error: "not_authenticated", message: "Run `skillett login` first." }));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const isTTY = process.stdout.isTTY;
|
|
12
|
+
const spinner = isTTY ? ora(` Disconnecting ${integration}…`).start() : null;
|
|
13
|
+
// Get the connection to find its ID
|
|
14
|
+
const listRes = await skillettFetch("/connections", apiKey);
|
|
15
|
+
if (!listRes.ok) {
|
|
16
|
+
spinner?.fail(" Failed to fetch connections");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const connections = await listRes.json();
|
|
20
|
+
const conn = connections.find((c) => c.integration?.slug === integration && c.status !== "revoked");
|
|
21
|
+
if (!conn) {
|
|
22
|
+
spinner?.fail(` No active connection found for ${integration}`);
|
|
23
|
+
if (isTTY) {
|
|
24
|
+
console.log(chalk.gray(" Run `skillett skills` to see connected integrations."));
|
|
25
|
+
}
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const deleteRes = await skillettFetch(`/connections/${conn.id}`, apiKey, {
|
|
29
|
+
method: "DELETE",
|
|
30
|
+
});
|
|
31
|
+
if (deleteRes.ok || deleteRes.status === 204) {
|
|
32
|
+
spinner?.succeed(` Disconnected ${integration}`);
|
|
33
|
+
if (!isTTY) {
|
|
34
|
+
console.log(JSON.stringify({ status: "disconnected", integration }));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
spinner?.fail(` Failed to disconnect ${integration}`);
|
|
39
|
+
const body = await deleteRes.json().catch(() => ({}));
|
|
40
|
+
console.log(JSON.stringify(body, null, 2));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function list(): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { loadApiKey } from "../lib/config.js";
|
|
4
|
+
import { listSkills } from "../lib/api.js";
|
|
5
|
+
export async function list() {
|
|
6
|
+
const apiKey = loadApiKey();
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
console.log(chalk.red("Not logged in. Run: npx skillett login"));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const spinner = ora("Fetching skills…").start();
|
|
12
|
+
try {
|
|
13
|
+
const skills = await listSkills(apiKey);
|
|
14
|
+
spinner.stop();
|
|
15
|
+
if (skills.length === 0) {
|
|
16
|
+
console.log(chalk.yellow("No skills available. Connect an integration first."));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(chalk.bold(`Available skills (${skills.length}):`));
|
|
21
|
+
console.log();
|
|
22
|
+
// Group by integration
|
|
23
|
+
const grouped = new Map();
|
|
24
|
+
for (const skill of skills) {
|
|
25
|
+
const key = skill.integration || "other";
|
|
26
|
+
if (!grouped.has(key))
|
|
27
|
+
grouped.set(key, []);
|
|
28
|
+
grouped.get(key).push(skill);
|
|
29
|
+
}
|
|
30
|
+
for (const [integration, integrationSkills] of grouped) {
|
|
31
|
+
console.log(chalk.cyan.bold(` ${integration}`));
|
|
32
|
+
for (const skill of integrationSkills) {
|
|
33
|
+
console.log(` ${chalk.white(skill.name.padEnd(25))} ${chalk.gray(skill.description || "")}`);
|
|
34
|
+
}
|
|
35
|
+
console.log();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
spinner.fail("Failed to fetch skills");
|
|
40
|
+
console.error(chalk.red(err.message));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import open from "open";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import { saveApiKey, checkGitignore, detectPlatform, savePlatform, getCoreSkillPath, getPlatformLabel, } from "../lib/config.js";
|
|
8
|
+
import { validateKey, getDashboardUrl } from "../lib/api.js";
|
|
9
|
+
import { requestDeviceCode, pollForToken } from "../lib/device-auth.js";
|
|
10
|
+
// Note: ensureSkillsDir from paths.ts is no longer used — platform-aware paths are in config.ts
|
|
11
|
+
function prompt(question) {
|
|
12
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
rl.question(question, (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async function promptPlatform() {
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(" Which agent platform are you using?");
|
|
23
|
+
console.log(" 1. Claude Code");
|
|
24
|
+
console.log(" 2. Cursor");
|
|
25
|
+
console.log(" 3. Windsurf");
|
|
26
|
+
console.log(" 4. Other");
|
|
27
|
+
console.log();
|
|
28
|
+
const answer = await prompt(" Enter number (1-4): ");
|
|
29
|
+
switch (answer) {
|
|
30
|
+
case "1": return "claude";
|
|
31
|
+
case "2": return "cursor";
|
|
32
|
+
case "3": return "windsurf";
|
|
33
|
+
default: return "generic";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const CORE_SKILL_MD = `---
|
|
37
|
+
name: skillett
|
|
38
|
+
description: Skillett agent skills platform — run \`skillett help\` to discover available commands.
|
|
39
|
+
user-invocable: false
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
# Skillett
|
|
43
|
+
|
|
44
|
+
Skillett is installed. Run CLI commands to discover and execute agent skills.
|
|
45
|
+
|
|
46
|
+
skillett help # see all commands
|
|
47
|
+
skillett skills # list available integrations
|
|
48
|
+
skillett run ... # execute an endpoint
|
|
49
|
+
`;
|
|
50
|
+
export async function login(options = {}) {
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(chalk.bold(" Skillett Login"));
|
|
53
|
+
console.log();
|
|
54
|
+
let key = options.key || process.env.SKILLETT_API_KEY;
|
|
55
|
+
if (key) {
|
|
56
|
+
// Direct key mode (--key flag or env var)
|
|
57
|
+
if (!key.startsWith("sk_")) {
|
|
58
|
+
console.log(chalk.red(" Invalid key format. Keys start with sk_"));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const spinner = ora(" Validating key…").start();
|
|
62
|
+
const valid = await validateKey(key);
|
|
63
|
+
if (!valid) {
|
|
64
|
+
spinner.fail(" Invalid API key");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
spinner.succeed(" Key validated");
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Device authorization flow
|
|
71
|
+
const spinner = ora(" Requesting device code…").start();
|
|
72
|
+
let deviceResponse;
|
|
73
|
+
try {
|
|
74
|
+
deviceResponse = await requestDeviceCode();
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
spinner.fail(" Failed to start login");
|
|
78
|
+
console.error(chalk.red(` ${err instanceof Error ? err.message : err}`));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
spinner.stop();
|
|
82
|
+
console.log(chalk.bold(" Open this URL and enter the code below:"));
|
|
83
|
+
console.log();
|
|
84
|
+
console.log(chalk.cyan(` ${deviceResponse.verification_uri}`));
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(chalk.bold(` Code: ${chalk.yellow(deviceResponse.user_code)}`));
|
|
87
|
+
console.log();
|
|
88
|
+
// Try to open browser with code pre-filled
|
|
89
|
+
try {
|
|
90
|
+
const url = `${deviceResponse.verification_uri}?code=${deviceResponse.user_code}`;
|
|
91
|
+
await open(url);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Non-fatal
|
|
95
|
+
}
|
|
96
|
+
const pollSpinner = ora(" Waiting for authorization…").start();
|
|
97
|
+
let tokenResponse;
|
|
98
|
+
try {
|
|
99
|
+
tokenResponse = await pollForToken(deviceResponse.device_code, {
|
|
100
|
+
interval: (deviceResponse.interval || 5) * 1000,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
pollSpinner.fail(" Authorization failed");
|
|
105
|
+
console.error(chalk.red(` ${err instanceof Error ? err.message : err}`));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
pollSpinner.succeed(` Authorized${tokenResponse.user?.email ? ` as ${tokenResponse.user.email}` : ""}`);
|
|
109
|
+
console.log();
|
|
110
|
+
// Redirect to API keys page
|
|
111
|
+
const dashboardUrl = getDashboardUrl();
|
|
112
|
+
const apiKeysUrl = `${dashboardUrl}/api-keys`;
|
|
113
|
+
console.log(chalk.bold(" Now grab your API key from the dashboard:"));
|
|
114
|
+
console.log(chalk.cyan(` ${apiKeysUrl}`));
|
|
115
|
+
console.log();
|
|
116
|
+
try {
|
|
117
|
+
await open(apiKeysUrl);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Non-fatal
|
|
121
|
+
}
|
|
122
|
+
key = await prompt(" Paste your API key: ");
|
|
123
|
+
if (!key.startsWith("sk_")) {
|
|
124
|
+
console.log(chalk.red(" Invalid key format. Keys start with sk_"));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const validateSpinner = ora(" Validating key…").start();
|
|
128
|
+
const valid = await validateKey(key);
|
|
129
|
+
if (!valid) {
|
|
130
|
+
validateSpinner.fail(" Invalid API key");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
validateSpinner.succeed(" Key validated");
|
|
134
|
+
}
|
|
135
|
+
// Save to .env
|
|
136
|
+
saveApiKey(key);
|
|
137
|
+
console.log(chalk.green(" Saved to .env"));
|
|
138
|
+
// Check .gitignore
|
|
139
|
+
if (!checkGitignore()) {
|
|
140
|
+
console.log(chalk.yellow(" Warning: .env is not in your .gitignore"));
|
|
141
|
+
}
|
|
142
|
+
// Detect platform
|
|
143
|
+
let platform = "generic";
|
|
144
|
+
const detected = detectPlatform();
|
|
145
|
+
if (detected) {
|
|
146
|
+
const answer = await prompt(` Detected ${chalk.cyan(detected.label)}. Correct? (Y/n) `);
|
|
147
|
+
if (answer.toLowerCase() !== "n") {
|
|
148
|
+
platform = detected.platform;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
platform = await promptPlatform();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
platform = await promptPlatform();
|
|
156
|
+
}
|
|
157
|
+
savePlatform(platform);
|
|
158
|
+
console.log(chalk.green(` Platform: ${getPlatformLabel(platform)}`));
|
|
159
|
+
// Install core skill pointer
|
|
160
|
+
const skillDir = getCoreSkillPath(platform);
|
|
161
|
+
if (!existsSync(skillDir)) {
|
|
162
|
+
mkdirSync(skillDir, { recursive: true });
|
|
163
|
+
}
|
|
164
|
+
writeFileSync(resolve(skillDir, "SKILL.md"), CORE_SKILL_MD);
|
|
165
|
+
console.log(chalk.green(` Installed core skill to ${skillDir.replace(process.cwd(), ".")}/`));
|
|
166
|
+
console.log();
|
|
167
|
+
console.log(chalk.bold(" You're all set! Try:"));
|
|
168
|
+
console.log(" skillett skills # see available integrations");
|
|
169
|
+
console.log(" skillett connect ... # connect an integration");
|
|
170
|
+
console.log(" skillett run ... # execute an endpoint");
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { loadApiKey, loadPlatform, getSkillDocsPath } from "../lib/config.js";
|
|
5
|
+
import { skillettFetch } from "../lib/api.js";
|
|
6
|
+
export async function pull(slug, options = {}) {
|
|
7
|
+
const apiKey = loadApiKey();
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
console.error(JSON.stringify({ error: "not_authenticated", message: "Run `skillett login` first." }));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const platform = loadPlatform() || "generic";
|
|
13
|
+
const targetDir = options.path || getSkillDocsPath(platform, slug);
|
|
14
|
+
const isTTY = process.stdout.isTTY;
|
|
15
|
+
const spinner = isTTY ? ora(` Pulling ${slug} skill docs…`).start() : null;
|
|
16
|
+
// Download skill files from API
|
|
17
|
+
const res = await skillettFetch(`/v1/${slug}/download`, apiKey);
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
spinner?.fail(` Failed to pull ${slug}`);
|
|
20
|
+
const body = await res.json().catch(() => ({ error: "request_failed" }));
|
|
21
|
+
console.error(JSON.stringify(body, null, 2));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
if (!data.files?.length) {
|
|
26
|
+
spinner?.fail(` No files found for ${slug}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
// Write files to target directory
|
|
30
|
+
for (const file of data.files) {
|
|
31
|
+
const filePath = resolve(targetDir, file.file_path);
|
|
32
|
+
const dir = dirname(filePath);
|
|
33
|
+
if (!existsSync(dir)) {
|
|
34
|
+
mkdirSync(dir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
writeFileSync(filePath, file.content);
|
|
37
|
+
}
|
|
38
|
+
spinner?.succeed(` Pulled ${data.files.length} files to ${targetDir.replace(process.cwd(), ".")}`);
|
|
39
|
+
if (!isTTY) {
|
|
40
|
+
console.log(JSON.stringify({
|
|
41
|
+
slug,
|
|
42
|
+
files: data.files.length,
|
|
43
|
+
path: targetDir,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function remove(name: string): Promise<void>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { getSkillPath, skillExists } from "../lib/paths.js";
|
|
4
|
+
export async function remove(name) {
|
|
5
|
+
const folderName = `skillett-${name}`;
|
|
6
|
+
if (!skillExists(folderName)) {
|
|
7
|
+
console.log(chalk.red(`Skill "${name}" is not installed.`));
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
rmSync(getSkillPath(folderName), { recursive: true });
|
|
11
|
+
console.log(chalk.green(`Removed "${name}" skill.`));
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(integration: string, endpoint: string, extraArgs: string[]): Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import { loadApiKey } from "../lib/config.js";
|
|
3
|
+
import { skillettFetch } from "../lib/api.js";
|
|
4
|
+
/**
|
|
5
|
+
* Parse CLI flags into a params object.
|
|
6
|
+
* Handles: --key value, --flag (boolean true), and JSON string mode.
|
|
7
|
+
*/
|
|
8
|
+
function parseParams(args) {
|
|
9
|
+
// JSON mode: single positional argument starting with {
|
|
10
|
+
if (args.length === 1 && args[0].startsWith("{")) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(args[0]);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
console.error(JSON.stringify({ error: "invalid_json", message: "Failed to parse JSON parameter." }));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const params = {};
|
|
20
|
+
let i = 0;
|
|
21
|
+
while (i < args.length) {
|
|
22
|
+
const arg = args[i];
|
|
23
|
+
if (arg.startsWith("--")) {
|
|
24
|
+
const key = arg.slice(2);
|
|
25
|
+
const next = args[i + 1];
|
|
26
|
+
// Boolean flag (no value or next is another flag)
|
|
27
|
+
if (!next || next.startsWith("--")) {
|
|
28
|
+
params[key] = true;
|
|
29
|
+
i++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Numeric value
|
|
33
|
+
if (/^-?\d+(\.\d+)?$/.test(next)) {
|
|
34
|
+
params[key] = Number(next);
|
|
35
|
+
i += 2;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// Comma-separated array
|
|
39
|
+
if (next.includes(",") && !next.startsWith("{")) {
|
|
40
|
+
params[key] = next.split(",").map((s) => s.trim());
|
|
41
|
+
i += 2;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// String value
|
|
45
|
+
params[key] = next;
|
|
46
|
+
i += 2;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
return params;
|
|
52
|
+
}
|
|
53
|
+
export async function run(integration, endpoint, extraArgs) {
|
|
54
|
+
const apiKey = loadApiKey();
|
|
55
|
+
if (!apiKey) {
|
|
56
|
+
console.error(JSON.stringify({ error: "not_authenticated", message: "Run `skillett login` first." }));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const params = parseParams(extraArgs);
|
|
60
|
+
const isTTY = process.stdout.isTTY;
|
|
61
|
+
const spinner = isTTY ? ora({ text: " Executing…", stream: process.stderr }).start() : null;
|
|
62
|
+
try {
|
|
63
|
+
const res = await skillettFetch(`/v1/${integration}/${endpoint}`, apiKey, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: JSON.stringify({ params }),
|
|
66
|
+
});
|
|
67
|
+
spinner?.stop();
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
// Wrap in consistent output shape
|
|
70
|
+
const output = res.ok
|
|
71
|
+
? { status: res.status, data }
|
|
72
|
+
: { status: res.status, ...data };
|
|
73
|
+
console.log(JSON.stringify(output, null, 2));
|
|
74
|
+
process.exit(res.ok ? 0 : 1);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
spinner?.stop();
|
|
78
|
+
console.error(JSON.stringify({
|
|
79
|
+
error: "network_error",
|
|
80
|
+
message: err instanceof Error ? err.message : "Request failed",
|
|
81
|
+
status: 0,
|
|
82
|
+
}));
|
|
83
|
+
process.exit(2);
|
|
84
|
+
}
|
|
85
|
+
}
|