aiguild 1.0.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/README.md +68 -0
- package/bin/aiguild.mjs +69 -0
- package/package.json +36 -0
- package/src/api.mjs +30 -0
- package/src/commands/auth.mjs +74 -0
- package/src/commands/info.mjs +52 -0
- package/src/commands/install.mjs +84 -0
- package/src/commands/list.mjs +24 -0
- package/src/commands/search.mjs +60 -0
- package/src/commands/update.mjs +78 -0
- package/src/config.mjs +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# aiguild CLI
|
|
2
|
+
|
|
3
|
+
The official command-line tool for [AIGuild](https://aiguild.replit.app) — install and manage AI skills from your terminal.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g aiguild
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with pnpm:
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add -g aiguild
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
**1. Get your API key**
|
|
19
|
+
|
|
20
|
+
Go to [AIGuild → Settings → CLI Access](https://aiguild.replit.app/settings) and click **Generate Key**.
|
|
21
|
+
|
|
22
|
+
**2. Authenticate**
|
|
23
|
+
```bash
|
|
24
|
+
aiguild auth login --key aiguild_xxxxxxxxxxxx
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**3. Search and install skills**
|
|
28
|
+
```bash
|
|
29
|
+
aiguild search "sql optimizer"
|
|
30
|
+
aiguild install sql-query-optimizer
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
| Command | Description |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `aiguild auth login --key <key>` | Authenticate with your API key |
|
|
38
|
+
| `aiguild auth logout` | Remove stored credentials |
|
|
39
|
+
| `aiguild auth status` | Check current authentication |
|
|
40
|
+
| `aiguild search [query]` | Search the skill registry |
|
|
41
|
+
| `aiguild install <slug>` | Install a skill to your project |
|
|
42
|
+
| `aiguild uninstall <slug>` | Remove an installed skill |
|
|
43
|
+
| `aiguild info <slug>` | Show skill details |
|
|
44
|
+
| `aiguild list` | List installed skills |
|
|
45
|
+
| `aiguild update --check` | Check for available updates |
|
|
46
|
+
| `aiguild update --all` | Update all outdated skills |
|
|
47
|
+
| `aiguild config` | Show current configuration |
|
|
48
|
+
|
|
49
|
+
## Options
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
aiguild search --framework crewai # Filter by framework
|
|
53
|
+
aiguild install my-skill --dir ./skills # Custom install directory
|
|
54
|
+
aiguild update --check # Dry run, show available updates only
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Config is stored at `~/.aiguild/config.json`. You can also use environment variables:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
AIGUILD_API_KEY=aiguild_xxx aiguild search
|
|
63
|
+
AIGUILD_API_URL=http://localhost:3000 aiguild search # Use local server
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
- Node.js 18 or higher
|
package/bin/aiguild.mjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { registerAuth } from "../src/commands/auth.mjs";
|
|
4
|
+
import { registerSearch } from "../src/commands/search.mjs";
|
|
5
|
+
import { registerInfo } from "../src/commands/info.mjs";
|
|
6
|
+
import { registerInstall } from "../src/commands/install.mjs";
|
|
7
|
+
import { registerList } from "../src/commands/list.mjs";
|
|
8
|
+
import { registerUpdate } from "../src/commands/update.mjs";
|
|
9
|
+
import { loadConfig } from "../src/config.mjs";
|
|
10
|
+
|
|
11
|
+
const pkg = { version: "1.0.0" };
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name("aiguild")
|
|
17
|
+
.description(
|
|
18
|
+
[
|
|
19
|
+
"",
|
|
20
|
+
" \x1b[1mAIGuild CLI\x1b[0m — the skills registry for AI developers",
|
|
21
|
+
"",
|
|
22
|
+
" Install, search, and manage AI skills from your terminal.",
|
|
23
|
+
" Skills are reusable YAML workflows for LLM agents.",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n")
|
|
26
|
+
)
|
|
27
|
+
.version(pkg.version, "-v, --version", "Print version number")
|
|
28
|
+
.addHelpText(
|
|
29
|
+
"after",
|
|
30
|
+
[
|
|
31
|
+
"",
|
|
32
|
+
" \x1b[2mQuick start:\x1b[0m",
|
|
33
|
+
" aiguild auth login --key <key> Authenticate with your API key",
|
|
34
|
+
" aiguild search Browse the registry",
|
|
35
|
+
" aiguild install <slug> Install a skill",
|
|
36
|
+
" aiguild list List installed skills",
|
|
37
|
+
"",
|
|
38
|
+
" \x1b[2mGet your API key at: https://aiguild.dev \u2192 Settings \u2192 CLI Access\x1b[0m",
|
|
39
|
+
"",
|
|
40
|
+
].join("\n")
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
registerAuth(program);
|
|
44
|
+
registerSearch(program);
|
|
45
|
+
registerInfo(program);
|
|
46
|
+
registerInstall(program);
|
|
47
|
+
registerList(program);
|
|
48
|
+
registerUpdate(program);
|
|
49
|
+
|
|
50
|
+
// `aiguild config` — show current config
|
|
51
|
+
program
|
|
52
|
+
.command("config")
|
|
53
|
+
.description("Show current CLI configuration")
|
|
54
|
+
.action(() => {
|
|
55
|
+
const cfg = loadConfig();
|
|
56
|
+
console.log("\n \x1b[1mCurrent configuration\x1b[0m\n");
|
|
57
|
+
console.log(` API URL \x1b[36m${cfg.apiUrl || "https://aiguild.replit.app"}\x1b[0m`);
|
|
58
|
+
const key = cfg.apiKey;
|
|
59
|
+
if (key) {
|
|
60
|
+
const masked = key.slice(0, 12) + "•".repeat(Math.max(0, key.length - 16)) + key.slice(-4);
|
|
61
|
+
console.log(` API Key \x1b[36m${masked}\x1b[0m`);
|
|
62
|
+
} else {
|
|
63
|
+
console.log(` API Key \x1b[2mnot set\x1b[0m`);
|
|
64
|
+
}
|
|
65
|
+
const count = Object.keys(cfg.installed || {}).length;
|
|
66
|
+
console.log(` Installed \x1b[36m${count}\x1b[0m skill${count !== 1 ? "s" : ""}\n`);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aiguild",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AIGuild CLI — install and manage AI skills from your terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"aiguild": "./bin/aiguild.mjs"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node ./bin/aiguild.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"commander": "^12.1.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"keywords": ["aiguild", "ai", "skills", "cli", "llm", "agent"],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"homepage": "https://aiguild.dev",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/AIGuild/aiguild"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/AIGuild/aiguild/issues"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/api.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getApiUrl } from "./config.mjs";
|
|
2
|
+
|
|
3
|
+
export async function apiFetch(path, opts = {}) {
|
|
4
|
+
const url = `${getApiUrl()}${path}`;
|
|
5
|
+
const res = await fetch(url, {
|
|
6
|
+
...opts,
|
|
7
|
+
headers: {
|
|
8
|
+
"Content-Type": "application/json",
|
|
9
|
+
"User-Agent": "aiguild-cli/1.0.0",
|
|
10
|
+
...(opts.headers || {}),
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const text = await res.text();
|
|
14
|
+
let data;
|
|
15
|
+
try { data = JSON.parse(text); } catch { data = { error: text }; }
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
throw new Error(data?.error || `HTTP ${res.status}`);
|
|
18
|
+
}
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function apiAuthFetch(path, apiKey, opts = {}) {
|
|
23
|
+
return apiFetch(path, {
|
|
24
|
+
...opts,
|
|
25
|
+
headers: {
|
|
26
|
+
...(opts.headers || {}),
|
|
27
|
+
"x-aiguild-key": apiKey,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { apiFetch, apiAuthFetch } from "../api.mjs";
|
|
2
|
+
import { saveConfig, clearConfig, loadConfig, getApiKey } from "../config.mjs";
|
|
3
|
+
|
|
4
|
+
export function registerAuth(program) {
|
|
5
|
+
const auth = program.command("auth").description("Manage CLI authentication");
|
|
6
|
+
|
|
7
|
+
auth
|
|
8
|
+
.command("login")
|
|
9
|
+
.description("Authenticate with your AIGuild API key")
|
|
10
|
+
.requiredOption("--key <key>", "Your AIGuild CLI API key (from Settings → CLI Access)")
|
|
11
|
+
.action(async ({ key }) => {
|
|
12
|
+
if (!key.startsWith("aiguild_")) {
|
|
13
|
+
console.error("\x1b[31m✖\x1b[0m Invalid key format. Keys must start with \x1b[36maiguild_\x1b[0m");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
process.stdout.write(" Verifying key...");
|
|
17
|
+
try {
|
|
18
|
+
const data = await apiFetch("/api/v1/cli/auth", {
|
|
19
|
+
method: "POST",
|
|
20
|
+
body: JSON.stringify({ apiKey: key }),
|
|
21
|
+
});
|
|
22
|
+
saveConfig({ apiKey: key });
|
|
23
|
+
process.stdout.write("\r");
|
|
24
|
+
console.log(`\x1b[32m✔\x1b[0m Authenticated as \x1b[1m${data.user.username}\x1b[0m`);
|
|
25
|
+
console.log(` Rank: \x1b[33m${data.user.rank || "Newcomer"}\x1b[0m · Points: \x1b[36m${data.user.reputationPoints ?? 0}\x1b[0m`);
|
|
26
|
+
console.log(` Key saved to \x1b[2m~/.aiguild/config.json\x1b[0m`);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
process.stdout.write("\r");
|
|
29
|
+
console.error(`\x1b[31m✖\x1b[0m Authentication failed: ${e.message}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
auth
|
|
35
|
+
.command("logout")
|
|
36
|
+
.description("Remove stored credentials")
|
|
37
|
+
.action(() => {
|
|
38
|
+
clearConfig();
|
|
39
|
+
console.log("\x1b[32m✔\x1b[0m Logged out. Config removed from \x1b[2m~/.aiguild/config.json\x1b[0m");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
auth
|
|
43
|
+
.command("status")
|
|
44
|
+
.description("Show current authentication status")
|
|
45
|
+
.action(async () => {
|
|
46
|
+
const key = getApiKey();
|
|
47
|
+
if (!key) {
|
|
48
|
+
console.log("\x1b[33m○\x1b[0m Not authenticated. Run: \x1b[36maiguild auth login --key <key>\x1b[0m");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write(" Checking session...");
|
|
52
|
+
try {
|
|
53
|
+
const data = await apiFetch("/api/v1/cli/auth", {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: JSON.stringify({ apiKey: key }),
|
|
56
|
+
});
|
|
57
|
+
process.stdout.write("\r");
|
|
58
|
+
console.log(`\x1b[32m✔\x1b[0m Authenticated as \x1b[1m${data.user.username}\x1b[0m`);
|
|
59
|
+
console.log(` Rank: \x1b[33m${data.user.rank || "Newcomer"}\x1b[0m · Points: \x1b[36m${data.user.reputationPoints ?? 0}\x1b[0m`);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
process.stdout.write("\r");
|
|
62
|
+
console.error(`\x1b[31m✖\x1b[0m Session invalid: ${e.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
auth
|
|
68
|
+
.command("set-url <url>")
|
|
69
|
+
.description("Override the AIGuild server URL (default: https://aiguild.replit.app)")
|
|
70
|
+
.action((url) => {
|
|
71
|
+
saveConfig({ apiUrl: url });
|
|
72
|
+
console.log(`\x1b[32m✔\x1b[0m API URL set to \x1b[36m${url}\x1b[0m`);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { apiFetch } from "../api.mjs";
|
|
2
|
+
|
|
3
|
+
export function registerInfo(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("info <slug>")
|
|
6
|
+
.description("Show detailed info about a skill")
|
|
7
|
+
.action(async (slug) => {
|
|
8
|
+
process.stdout.write(` Fetching info for "${slug}"...`);
|
|
9
|
+
try {
|
|
10
|
+
const data = await apiFetch(`/api/v1/skills/${encodeURIComponent(slug)}/info`);
|
|
11
|
+
process.stdout.write("\r");
|
|
12
|
+
const s = data.skill || data;
|
|
13
|
+
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(`\x1b[1m ${s.name || s.slug}\x1b[0m \x1b[2mv${s.version || "1.0.0"}\x1b[0m`);
|
|
16
|
+
console.log(` \x1b[2m${s.slug}\x1b[0m`);
|
|
17
|
+
if (s.description) console.log(`\n ${s.description}`);
|
|
18
|
+
console.log();
|
|
19
|
+
|
|
20
|
+
const rows = [
|
|
21
|
+
["Author", s.authorUsername || s.author || "—"],
|
|
22
|
+
["Trust", trustLabel(s.trustStatus)],
|
|
23
|
+
["Installs", String(s.installCount || 0)],
|
|
24
|
+
["Framework", s.compatibleAgents?.join(", ") || "any"],
|
|
25
|
+
["Tags", (s.tags || []).join(", ") || "—"],
|
|
26
|
+
["License", s.license || "MIT"],
|
|
27
|
+
];
|
|
28
|
+
for (const [label, value] of rows) {
|
|
29
|
+
console.log(` \x1b[2m${label.padEnd(10)}\x1b[0m ${value}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (s.yamlContent || s.content) {
|
|
33
|
+
console.log(`\n \x1b[33mYAML Preview:\x1b[0m`);
|
|
34
|
+
const lines = (s.yamlContent || s.content || "").split("\n").slice(0, 8);
|
|
35
|
+
for (const line of lines) console.log(` \x1b[2m${line}\x1b[0m`);
|
|
36
|
+
if ((s.yamlContent || s.content || "").split("\n").length > 8) console.log(` \x1b[2m...\x1b[0m`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(`\n \x1b[2mInstall: aiguild install ${s.slug}\x1b[0m`);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
process.stdout.write("\r");
|
|
42
|
+
console.error(`\x1b[31m✖\x1b[0m Skill not found: ${e.message}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function trustLabel(status) {
|
|
49
|
+
if (status === "master_verified") return "\x1b[33m★ Master Verified\x1b[0m";
|
|
50
|
+
if (status === "peer_reviewed") return "\x1b[36m✓ Peer Reviewed\x1b[0m";
|
|
51
|
+
return "Community";
|
|
52
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { apiFetch } from "../api.mjs";
|
|
4
|
+
import { requireAuth, loadConfig, saveConfig } from "../config.mjs";
|
|
5
|
+
|
|
6
|
+
const INSTALL_DIR_DEFAULT = path.join(process.cwd(), ".aiguild", "skills");
|
|
7
|
+
|
|
8
|
+
function getInstallDir(opts) {
|
|
9
|
+
return opts.dir
|
|
10
|
+
? path.resolve(opts.dir)
|
|
11
|
+
: (loadConfig().installDir || INSTALL_DIR_DEFAULT);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadInstalled() {
|
|
15
|
+
const cfg = loadConfig();
|
|
16
|
+
return cfg.installed || {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function saveInstalled(installed) {
|
|
20
|
+
saveConfig({ installed });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function registerInstall(program) {
|
|
24
|
+
program
|
|
25
|
+
.command("install <slug>")
|
|
26
|
+
.description("Download and install a skill to your project")
|
|
27
|
+
.option("-d, --dir <path>", "Target directory (default: ./.aiguild/skills)")
|
|
28
|
+
.option("--version <ver>", "Install a specific version")
|
|
29
|
+
.action(async (slug, opts) => {
|
|
30
|
+
requireAuth();
|
|
31
|
+
const installDir = getInstallDir(opts);
|
|
32
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
33
|
+
|
|
34
|
+
process.stdout.write(` Fetching \x1b[36m${slug}\x1b[0m...`);
|
|
35
|
+
try {
|
|
36
|
+
const params = opts.version ? `?version=${encodeURIComponent(opts.version)}` : "";
|
|
37
|
+
const data = await apiFetch(`/api/v1/skills/${encodeURIComponent(slug)}/download${params}`);
|
|
38
|
+
process.stdout.write("\r");
|
|
39
|
+
|
|
40
|
+
const skill = data.skill || data;
|
|
41
|
+
const version = data.version || skill.currentVersion || "1.0.0";
|
|
42
|
+
const content = data.content || skill.yamlContent || "";
|
|
43
|
+
const filename = `${slug}.yaml`;
|
|
44
|
+
const filePath = path.join(installDir, filename);
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
47
|
+
|
|
48
|
+
const installed = loadInstalled();
|
|
49
|
+
installed[slug] = {
|
|
50
|
+
version,
|
|
51
|
+
installedAt: new Date().toISOString(),
|
|
52
|
+
path: filePath,
|
|
53
|
+
};
|
|
54
|
+
saveInstalled(installed);
|
|
55
|
+
|
|
56
|
+
console.log(`\x1b[32m✔\x1b[0m Installed \x1b[1m${skill.name || slug}\x1b[0m \x1b[2mv${version}\x1b[0m`);
|
|
57
|
+
console.log(` \x1b[2m→ ${filePath}\x1b[0m`);
|
|
58
|
+
if (skill.compatibleAgents?.length) {
|
|
59
|
+
console.log(` Compatible with: \x1b[36m${skill.compatibleAgents.join(", ")}\x1b[0m`);
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {
|
|
62
|
+
process.stdout.write("\r");
|
|
63
|
+
console.error(`\x1b[31m✖\x1b[0m Install failed: ${e.message}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.command("uninstall <slug>")
|
|
70
|
+
.description("Remove an installed skill")
|
|
71
|
+
.option("-d, --dir <path>", "Skills directory")
|
|
72
|
+
.action(async (slug, opts) => {
|
|
73
|
+
const installed = loadInstalled();
|
|
74
|
+
if (!installed[slug]) {
|
|
75
|
+
console.error(`\x1b[31m✖\x1b[0m "${slug}" is not installed`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const record = installed[slug];
|
|
79
|
+
try { fs.unlinkSync(record.path); } catch {}
|
|
80
|
+
delete installed[slug];
|
|
81
|
+
saveInstalled(installed);
|
|
82
|
+
console.log(`\x1b[32m✔\x1b[0m Uninstalled \x1b[1m${slug}\x1b[0m`);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { loadConfig } from "../config.mjs";
|
|
2
|
+
|
|
3
|
+
export function registerList(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("list")
|
|
6
|
+
.alias("ls")
|
|
7
|
+
.description("List locally installed skills")
|
|
8
|
+
.action(() => {
|
|
9
|
+
const installed = loadConfig().installed || {};
|
|
10
|
+
const slugs = Object.keys(installed);
|
|
11
|
+
if (!slugs.length) {
|
|
12
|
+
console.log("\x1b[33m○\x1b[0m No skills installed yet. Try: \x1b[36maiguild search\x1b[0m");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
console.log(`\n \x1b[1m${slugs.length}\x1b[0m skill${slugs.length !== 1 ? "s" : ""} installed:\n`);
|
|
16
|
+
for (const slug of slugs) {
|
|
17
|
+
const { version, installedAt, path: filePath } = installed[slug];
|
|
18
|
+
const date = installedAt ? new Date(installedAt).toLocaleDateString() : "—";
|
|
19
|
+
console.log(` \x1b[1m${slug}\x1b[0m \x1b[2mv${version} · installed ${date}\x1b[0m`);
|
|
20
|
+
if (filePath) console.log(` \x1b[2m ${filePath}\x1b[0m`);
|
|
21
|
+
console.log();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { apiFetch } from "../api.mjs";
|
|
2
|
+
|
|
3
|
+
function formatTrustBadge(status) {
|
|
4
|
+
if (status === "master_verified") return "\x1b[33m★ Master\x1b[0m";
|
|
5
|
+
if (status === "peer_reviewed") return "\x1b[36m✓ Peer\x1b[0m";
|
|
6
|
+
return "\x1b[2m○ Community\x1b[0m";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatInstallCount(n) {
|
|
10
|
+
if (!n) return "0";
|
|
11
|
+
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
|
|
12
|
+
return String(n);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function registerSearch(program) {
|
|
16
|
+
program
|
|
17
|
+
.command("search [query]")
|
|
18
|
+
.description("Search for skills in the AIGuild registry")
|
|
19
|
+
.option("-f, --framework <framework>", "Filter by framework (crewai, langgraph, n8n, langflow)")
|
|
20
|
+
.option("-t, --tag <tag>", "Filter by tag")
|
|
21
|
+
.option("-n, --limit <n>", "Max results to show", "10")
|
|
22
|
+
.action(async (query, opts) => {
|
|
23
|
+
const params = new URLSearchParams();
|
|
24
|
+
if (query) params.set("q", query);
|
|
25
|
+
if (opts.framework) params.set("framework", opts.framework);
|
|
26
|
+
if (opts.tag) params.set("tag", opts.tag);
|
|
27
|
+
params.set("limit", opts.limit);
|
|
28
|
+
params.set("trustStatus", "all");
|
|
29
|
+
|
|
30
|
+
process.stdout.write(" Searching...");
|
|
31
|
+
try {
|
|
32
|
+
const data = await apiFetch(`/api/v1/skills/search?${params}`);
|
|
33
|
+
process.stdout.write("\r");
|
|
34
|
+
const skills = data.skills || data;
|
|
35
|
+
if (!skills.length) {
|
|
36
|
+
console.log(`\x1b[33m○\x1b[0m No skills found${query ? ` for "${query}"` : ""}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log(`\n Found \x1b[1m${skills.length}\x1b[0m skill${skills.length !== 1 ? "s" : ""}${query ? ` matching "${query}"` : ""}:\n`);
|
|
40
|
+
for (const s of skills) {
|
|
41
|
+
const badge = formatTrustBadge(s.trustStatus);
|
|
42
|
+
const installs = formatInstallCount(s.installCount);
|
|
43
|
+
console.log(` \x1b[1m${s.slug}\x1b[0m ${badge} \x1b[2m↓${installs}\x1b[0m`);
|
|
44
|
+
if (s.name && s.name !== s.slug) console.log(` \x1b[2m${s.name}\x1b[0m`);
|
|
45
|
+
if (s.description) {
|
|
46
|
+
const desc = s.description.length > 80 ? s.description.slice(0, 77) + "..." : s.description;
|
|
47
|
+
console.log(` ${desc}`);
|
|
48
|
+
}
|
|
49
|
+
const tags = (s.tags || []).slice(0, 4).join(" ");
|
|
50
|
+
if (tags) console.log(` \x1b[36m${tags}\x1b[0m`);
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
console.log(` \x1b[2mInstall with: aiguild install <slug>\x1b[0m`);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
process.stdout.write("\r");
|
|
56
|
+
console.error(`\x1b[31m✖\x1b[0m Search failed: ${e.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { apiFetch } from "../api.mjs";
|
|
2
|
+
import { requireAuth, loadConfig, saveConfig } from "../config.mjs";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
export function registerUpdate(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("update [slug]")
|
|
8
|
+
.description("Update installed skills. Omit slug to check all.")
|
|
9
|
+
.option("--check", "Only show available updates, don't install")
|
|
10
|
+
.option("--all", "Update all outdated skills")
|
|
11
|
+
.action(async (slug, opts) => {
|
|
12
|
+
requireAuth();
|
|
13
|
+
const installed = loadConfig().installed || {};
|
|
14
|
+
const targets = slug
|
|
15
|
+
? (installed[slug] ? { [slug]: installed[slug] } : null)
|
|
16
|
+
: installed;
|
|
17
|
+
|
|
18
|
+
if (!targets) {
|
|
19
|
+
console.error(`\x1b[31m✖\x1b[0m "${slug}" is not installed`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const slugs = Object.keys(targets);
|
|
23
|
+
if (!slugs.length) {
|
|
24
|
+
console.log("\x1b[33m○\x1b[0m No skills installed. Run: \x1b[36maiguild install <slug>\x1b[0m");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
process.stdout.write(" Checking for updates...");
|
|
29
|
+
try {
|
|
30
|
+
const data = await apiFetch("/api/v1/skills/installed/updates", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
installed: slugs.map((s) => ({ slug: s, version: targets[s].version })),
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
process.stdout.write("\r");
|
|
37
|
+
|
|
38
|
+
const updates = data.updates || [];
|
|
39
|
+
if (!updates.length) {
|
|
40
|
+
console.log("\x1b[32m✔\x1b[0m All skills are up to date");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`\n \x1b[1m${updates.length}\x1b[0m update${updates.length !== 1 ? "s" : ""} available:\n`);
|
|
45
|
+
for (const u of updates) {
|
|
46
|
+
console.log(` \x1b[1m${u.slug}\x1b[0m \x1b[2mv${u.currentVersion} → \x1b[0m\x1b[32mv${u.latestVersion}\x1b[0m`);
|
|
47
|
+
if (u.changelog) console.log(` \x1b[2m${u.changelog}\x1b[0m`);
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
|
|
51
|
+
if (opts.check) {
|
|
52
|
+
console.log(` \x1b[2mRun \x1b[0m\x1b[36maiguild update --all\x1b[0m\x1b[2m to install all updates\x1b[0m`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const toUpdate = opts.all ? updates : (slug ? updates : []);
|
|
57
|
+
if (!toUpdate.length && !opts.all) {
|
|
58
|
+
console.log(` \x1b[2mRun \x1b[0m\x1b[36maiguild update --all\x1b[0m\x1b[2m to install all updates\x1b[0m`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const u of toUpdate) {
|
|
63
|
+
process.stdout.write(` Updating \x1b[36m${u.slug}\x1b[0m...`);
|
|
64
|
+
const dl = await apiFetch(`/api/v1/skills/${encodeURIComponent(u.slug)}/download`);
|
|
65
|
+
const record = installed[u.slug];
|
|
66
|
+
fs.writeFileSync(record.path, dl.content || "", "utf8");
|
|
67
|
+
installed[u.slug] = { ...record, version: u.latestVersion, installedAt: new Date().toISOString() };
|
|
68
|
+
saveConfig({ installed });
|
|
69
|
+
process.stdout.write("\r");
|
|
70
|
+
console.log(` \x1b[32m✔\x1b[0m Updated \x1b[1m${u.slug}\x1b[0m to \x1b[32mv${u.latestVersion}\x1b[0m`);
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
process.stdout.write("\r");
|
|
74
|
+
console.error(`\x1b[31m✖\x1b[0m Update check failed: ${e.message}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
package/src/config.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), ".aiguild");
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
|
+
|
|
8
|
+
const DEFAULT_API_URL = process.env.AIGUILD_API_URL || "https://api.aiguild.dev";
|
|
9
|
+
|
|
10
|
+
export function loadConfig() {
|
|
11
|
+
try {
|
|
12
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
13
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
|
|
14
|
+
}
|
|
15
|
+
} catch {}
|
|
16
|
+
return { apiUrl: DEFAULT_API_URL };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function saveConfig(updates) {
|
|
20
|
+
const current = loadConfig();
|
|
21
|
+
const next = { ...current, ...updates };
|
|
22
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
23
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2));
|
|
24
|
+
return next;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function clearConfig() {
|
|
28
|
+
try { fs.unlinkSync(CONFIG_FILE); } catch {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getApiUrl() {
|
|
32
|
+
return loadConfig().apiUrl || DEFAULT_API_URL;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getApiKey() {
|
|
36
|
+
return process.env.AIGUILD_API_KEY || loadConfig().apiKey || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function requireAuth() {
|
|
40
|
+
const key = getApiKey();
|
|
41
|
+
if (!key) {
|
|
42
|
+
console.error("\x1b[31m✖\x1b[0m Not authenticated. Run: \x1b[36maiguild auth login --key <your-key>\x1b[0m");
|
|
43
|
+
console.error(" Get your key at: \x1b[2mSettings → CLI Access\x1b[0m");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
return key;
|
|
47
|
+
}
|