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.
@@ -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,3 @@
1
+ export declare function login(options?: {
2
+ key?: string;
3
+ }): Promise<void>;
@@ -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,3 @@
1
+ export declare function pull(slug: string, options?: {
2
+ path?: string;
3
+ }): Promise<void>;
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export declare function seed(skillDir: string, opts: {
2
+ databaseUrl?: string;
3
+ }): Promise<void>;