@dreamlogic-ai/cli 1.0.0 → 2.0.1
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/install.js +83 -26
- package/dist/commands/list.js +1 -1
- package/dist/commands/login.js +14 -6
- package/dist/commands/logout.js +6 -6
- package/dist/commands/rollback.js +4 -4
- package/dist/commands/setup-mcp.js +4 -4
- package/dist/commands/status.js +1 -1
- package/dist/commands/update.js +21 -17
- package/dist/index.js +26 -18
- package/dist/lib/agents.d.ts +40 -0
- package/dist/lib/agents.js +173 -0
- package/dist/lib/config.js +14 -2
- package/dist/lib/installer.js +48 -13
- package/dist/lib/ui.d.ts +17 -9
- package/dist/lib/ui.js +83 -34
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +7 -2
package/dist/commands/install.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* install command — download and install skills
|
|
3
3
|
* Supports: interactive multi-select, single skill by name, --from-file
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
5
|
+
import * as clack from "@clack/prompts";
|
|
6
|
+
import { join } from "path";
|
|
6
7
|
import { ApiClient } from "../lib/api-client.js";
|
|
7
8
|
import { getServer, getInstallDir, loadInstalled } from "../lib/config.js";
|
|
8
9
|
import { installSkill } from "../lib/installer.js";
|
|
10
|
+
import { detectAgents, registerSkillWithAgents } from "../lib/agents.js";
|
|
9
11
|
import { ui } from "../lib/ui.js";
|
|
10
12
|
import { requireAuth } from "./helpers.js";
|
|
11
13
|
import chalk from "chalk";
|
|
@@ -36,31 +38,37 @@ export async function installCommand(skillIds, opts) {
|
|
|
36
38
|
ui.info("No skills available yet.");
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
|
-
const
|
|
41
|
+
const options = skills.map((s) => {
|
|
40
42
|
const isInstalled = !!installed[s.id];
|
|
41
43
|
const ver = s.latest_version || "unknown";
|
|
42
44
|
const currentVer = installed[s.id]?.version;
|
|
43
|
-
let
|
|
45
|
+
let hint = "";
|
|
44
46
|
if (!isInstalled) {
|
|
45
|
-
|
|
47
|
+
hint = chalk.green("NEW");
|
|
46
48
|
}
|
|
47
49
|
else if (currentVer && s.latest_version && currentVer !== s.latest_version) {
|
|
48
|
-
|
|
50
|
+
hint = chalk.yellow(`UPDATE: ${currentVer} → ${s.latest_version}`);
|
|
49
51
|
}
|
|
50
52
|
else {
|
|
51
|
-
|
|
53
|
+
hint = chalk.dim("installed");
|
|
52
54
|
}
|
|
53
55
|
return {
|
|
54
|
-
|
|
56
|
+
label: `${s.name} (${ver})`,
|
|
55
57
|
value: s.id,
|
|
56
|
-
|
|
58
|
+
hint: `${s.description} ${hint}`,
|
|
57
59
|
};
|
|
58
60
|
});
|
|
59
61
|
console.log();
|
|
60
|
-
|
|
61
|
-
message: "Select skills to install
|
|
62
|
-
|
|
62
|
+
const selected = await clack.multiselect({
|
|
63
|
+
message: "Select skills to install:",
|
|
64
|
+
options,
|
|
65
|
+
required: true,
|
|
63
66
|
});
|
|
67
|
+
if (clack.isCancel(selected)) {
|
|
68
|
+
clack.cancel("Installation cancelled.");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
selectedIds = selected;
|
|
64
72
|
if (selectedIds.length === 0) {
|
|
65
73
|
ui.info("Nothing selected.");
|
|
66
74
|
return;
|
|
@@ -85,21 +93,20 @@ export async function installCommand(skillIds, opts) {
|
|
|
85
93
|
}
|
|
86
94
|
// Confirm
|
|
87
95
|
if (!opts.yes) {
|
|
88
|
-
|
|
89
|
-
ui.header("Installation Plan");
|
|
90
|
-
for (const id of selectedIds) {
|
|
96
|
+
const planLines = selectedIds.map((id) => {
|
|
91
97
|
const s = skills.find((s) => s.id === id);
|
|
92
98
|
if (!s)
|
|
93
|
-
|
|
99
|
+
return ` ${id}`;
|
|
94
100
|
const ver = s.latest_version || "unknown";
|
|
95
|
-
const size = s.package_size ? ui.fileSize(s.package_size) : "
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
const size = s.package_size ? ui.fileSize(s.package_size) : "?";
|
|
102
|
+
return `${chalk.bold(s.name)} ${chalk.dim(ver)} (${size})`;
|
|
103
|
+
});
|
|
104
|
+
planLines.push("");
|
|
105
|
+
planLines.push(`${chalk.dim("Install to:")} ${getInstallDir()}`);
|
|
106
|
+
ui.panel("Installation Plan", planLines.join("\n"));
|
|
107
|
+
const ok = await clack.confirm({ message: "Proceed?", initialValue: true });
|
|
108
|
+
if (clack.isCancel(ok) || !ok) {
|
|
109
|
+
clack.cancel("Cancelled.");
|
|
103
110
|
return;
|
|
104
111
|
}
|
|
105
112
|
}
|
|
@@ -110,7 +117,7 @@ export async function installCommand(skillIds, opts) {
|
|
|
110
117
|
if (!s) {
|
|
111
118
|
ui.err(`Skill '${id}' no longer in catalog`);
|
|
112
119
|
continue;
|
|
113
|
-
}
|
|
120
|
+
}
|
|
114
121
|
console.log();
|
|
115
122
|
ui.header(`Installing ${s.name}`);
|
|
116
123
|
try {
|
|
@@ -124,15 +131,65 @@ export async function installCommand(skillIds, opts) {
|
|
|
124
131
|
}
|
|
125
132
|
console.log();
|
|
126
133
|
if (successCount === selectedIds.length) {
|
|
127
|
-
ui.ok(`All ${successCount} skill(s) installed successfully
|
|
134
|
+
ui.ok(`All ${successCount} skill(s) installed successfully!`);
|
|
128
135
|
}
|
|
129
136
|
else {
|
|
130
137
|
ui.warning(`${successCount}/${selectedIds.length} installed. Check errors above.`);
|
|
131
138
|
}
|
|
139
|
+
// ── Agent Registration Step ──
|
|
140
|
+
if (successCount > 0 && !opts.yes) {
|
|
141
|
+
console.log();
|
|
142
|
+
const { universal, detected } = detectAgents();
|
|
143
|
+
const totalAgents = universal.length + detected.length;
|
|
144
|
+
if (totalAgents > 0) {
|
|
145
|
+
// Build options grouped by category
|
|
146
|
+
const agentOptions = [];
|
|
147
|
+
// Universal agents header
|
|
148
|
+
for (const agent of universal) {
|
|
149
|
+
agentOptions.push({
|
|
150
|
+
label: agent.name,
|
|
151
|
+
value: `u:${agent.name}`,
|
|
152
|
+
hint: "universal (.agents/skills)",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// Detected additional agents
|
|
156
|
+
for (const agent of detected) {
|
|
157
|
+
agentOptions.push({
|
|
158
|
+
label: agent.name,
|
|
159
|
+
value: `a:${agent.name}`,
|
|
160
|
+
hint: `detected (${agent.skillsDir})`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const agentChoice = await clack.multiselect({
|
|
164
|
+
message: `Register skills with AI agents? (${totalAgents} agents available)`,
|
|
165
|
+
options: agentOptions,
|
|
166
|
+
required: false,
|
|
167
|
+
});
|
|
168
|
+
if (!clack.isCancel(agentChoice) && agentChoice.length > 0) {
|
|
169
|
+
const chosenNames = agentChoice.map(v => v.replace(/^[ua]:/, ""));
|
|
170
|
+
const allAgents = [...universal, ...detected];
|
|
171
|
+
const chosenAgents = allAgents.filter(a => chosenNames.includes(a.name));
|
|
172
|
+
// Register each installed skill with chosen agents
|
|
173
|
+
const installDir = getInstallDir();
|
|
174
|
+
for (const id of selectedIds) {
|
|
175
|
+
const skillPath = join(installDir, id);
|
|
176
|
+
const results = registerSkillWithAgents(id, skillPath, chosenAgents);
|
|
177
|
+
const created = results.filter(r => r.status === "created").length;
|
|
178
|
+
const errors = results.filter(r => r.status === "error");
|
|
179
|
+
if (created > 0) {
|
|
180
|
+
ui.ok(`${id}: registered with ${created} agent location(s)`);
|
|
181
|
+
}
|
|
182
|
+
for (const e of errors) {
|
|
183
|
+
ui.warning(`${e.agent}: ${e.error}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
132
189
|
// Suggest MCP setup
|
|
133
190
|
if (successCount > 0) {
|
|
134
191
|
console.log();
|
|
135
192
|
ui.info("To configure MCP for your AI agent, run:");
|
|
136
|
-
ui.line(
|
|
193
|
+
ui.line(chalk.cyan("dreamlogic setup-mcp"));
|
|
137
194
|
}
|
|
138
195
|
}
|
package/dist/commands/list.js
CHANGED
|
@@ -12,7 +12,7 @@ export function listCommand() {
|
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
console.log();
|
|
15
|
-
ui.line(
|
|
15
|
+
ui.line(`Install directory: ${getInstallDir()}`);
|
|
16
16
|
console.log();
|
|
17
17
|
for (const [id, info] of entries) {
|
|
18
18
|
ui.line(` ${chalk.green("●")} ${chalk.bold(id)}`);
|
package/dist/commands/login.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* login command — authenticate and save API key
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import * as clack from "@clack/prompts";
|
|
5
5
|
import { ApiClient, ApiError } from "../lib/api-client.js";
|
|
6
6
|
import { loadConfig, saveConfig, getApiKey, getServer, maskKey, getDefaultInstallDir, } from "../lib/config.js";
|
|
7
7
|
import { DEFAULT_SERVER } from "../types.js";
|
|
@@ -11,11 +11,11 @@ export async function loginCommand(opts) {
|
|
|
11
11
|
const existingKey = getApiKey();
|
|
12
12
|
if (existingKey && !opts.key) {
|
|
13
13
|
ui.info(`Already logged in as ${maskKey(existingKey)}`);
|
|
14
|
-
const relogin = await confirm({
|
|
14
|
+
const relogin = await clack.confirm({
|
|
15
15
|
message: "Re-authenticate with a different key?",
|
|
16
|
-
|
|
16
|
+
initialValue: false,
|
|
17
17
|
});
|
|
18
|
-
if (!relogin)
|
|
18
|
+
if (clack.isCancel(relogin) || !relogin)
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
let apiKey = opts.key || "";
|
|
@@ -25,11 +25,19 @@ export async function loginCommand(opts) {
|
|
|
25
25
|
ui.info("Prefer: dreamlogic login (interactive) or DREAMLOGIC_API_KEY env var");
|
|
26
26
|
}
|
|
27
27
|
if (!apiKey) {
|
|
28
|
-
|
|
28
|
+
const keyInput = await clack.password({
|
|
29
29
|
message: "Enter your Dreamlogic API Key:",
|
|
30
30
|
mask: "*",
|
|
31
|
-
validate: (v) =>
|
|
31
|
+
validate: (v) => {
|
|
32
|
+
if (!v || !KEY_RE.test(v))
|
|
33
|
+
return "Key must match format: sk-user-xxxx... or sk-admin-xxxx...";
|
|
34
|
+
},
|
|
32
35
|
});
|
|
36
|
+
if (clack.isCancel(keyInput)) {
|
|
37
|
+
clack.cancel("Login cancelled.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
apiKey = keyInput;
|
|
33
41
|
}
|
|
34
42
|
if (!KEY_RE.test(apiKey)) {
|
|
35
43
|
ui.err("Invalid key format. Expected: sk-user-xxxx... or sk-admin-xxxx...");
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* logout command — clear saved credentials
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import * as clack from "@clack/prompts";
|
|
5
5
|
import { clearConfig, maskKey, getApiKey } from "../lib/config.js";
|
|
6
6
|
import { ui } from "../lib/ui.js";
|
|
7
7
|
export async function logoutCommand() {
|
|
@@ -11,9 +11,9 @@ export async function logoutCommand() {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
ui.info(`Current key: ${maskKey(key)}`);
|
|
14
|
-
const ok = await confirm({ message: "Clear saved credentials?",
|
|
15
|
-
if (ok)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
const ok = await clack.confirm({ message: "Clear saved credentials?", initialValue: true });
|
|
15
|
+
if (clack.isCancel(ok) || !ok)
|
|
16
|
+
return;
|
|
17
|
+
clearConfig();
|
|
18
|
+
ui.ok("Credentials cleared.");
|
|
19
19
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* rollback command — restore previous version of a skill (D-13)
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import * as clack from "@clack/prompts";
|
|
5
5
|
import { rollbackSkill } from "../lib/installer.js";
|
|
6
6
|
import { loadInstalled } from "../lib/config.js";
|
|
7
7
|
import { ui } from "../lib/ui.js";
|
|
@@ -20,9 +20,9 @@ export async function rollbackCommand(skillId) {
|
|
|
20
20
|
}
|
|
21
21
|
ui.info(`Current: ${info.version}`);
|
|
22
22
|
ui.info(`Rollback to: ${info.previous_version}`);
|
|
23
|
-
const ok = await confirm({ message: "Proceed with rollback?",
|
|
24
|
-
if (!ok) {
|
|
25
|
-
|
|
23
|
+
const ok = await clack.confirm({ message: "Proceed with rollback?", initialValue: true });
|
|
24
|
+
if (clack.isCancel(ok) || !ok) {
|
|
25
|
+
clack.cancel("Cancelled.");
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
const spinner = ui.spinner("Rolling back...");
|
|
@@ -8,7 +8,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "f
|
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { homedir, platform } from "os";
|
|
10
10
|
import { execFileSync } from "child_process";
|
|
11
|
-
import
|
|
11
|
+
import * as clack from "@clack/prompts";
|
|
12
12
|
import { getServer, maskKey } from "../lib/config.js";
|
|
13
13
|
import { ui } from "../lib/ui.js";
|
|
14
14
|
import { requireAuth } from "./helpers.js";
|
|
@@ -181,11 +181,11 @@ export async function setupMcpCommand(opts) {
|
|
|
181
181
|
ui.info("(dry-run — no changes made)");
|
|
182
182
|
continue;
|
|
183
183
|
}
|
|
184
|
-
const ok = await confirm({
|
|
184
|
+
const ok = await clack.confirm({
|
|
185
185
|
message: `Apply configuration for ${agent.name}?`,
|
|
186
|
-
|
|
186
|
+
initialValue: true,
|
|
187
187
|
});
|
|
188
|
-
if (!ok) {
|
|
188
|
+
if (clack.isCancel(ok) || !ok) {
|
|
189
189
|
ui.info("Skipped.");
|
|
190
190
|
continue;
|
|
191
191
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -42,7 +42,7 @@ export async function statusCommand() {
|
|
|
42
42
|
// Installed skills
|
|
43
43
|
const entries = Object.entries(installed);
|
|
44
44
|
console.log();
|
|
45
|
-
ui.line(
|
|
45
|
+
ui.line(`Installed: ${entries.length} skill(s)`);
|
|
46
46
|
if (entries.length > 0) {
|
|
47
47
|
// Check for updates if authenticated
|
|
48
48
|
const apiKey = getApiKey();
|
package/dist/commands/update.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* update command — check for and apply skill updates
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import * as clack from "@clack/prompts";
|
|
5
5
|
import { ApiClient } from "../lib/api-client.js";
|
|
6
6
|
import { getServer, loadInstalled } from "../lib/config.js";
|
|
7
7
|
import { installSkill } from "../lib/installer.js";
|
|
@@ -39,20 +39,19 @@ export async function updateCommand(opts) {
|
|
|
39
39
|
const local = installed[id];
|
|
40
40
|
const remote = skills.find((s) => s.id === id);
|
|
41
41
|
if (!remote) {
|
|
42
|
-
ui.line(
|
|
42
|
+
ui.line(`${ui.warn("?")} ${id} — not found on server (removed?)`);
|
|
43
43
|
continue;
|
|
44
44
|
}
|
|
45
45
|
if (!remote.latest_version) {
|
|
46
|
-
ui.line(
|
|
46
|
+
ui.line(`${ui.muted("–")} ${remote.name} — no version info`);
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
-
// R1-16: Normalize version comparison (strip leading 'v')
|
|
50
49
|
const normalizeVer = (v) => v.replace(/^v/, "");
|
|
51
50
|
if (normalizeVer(local.version) === normalizeVer(remote.latest_version)) {
|
|
52
|
-
ui.line(
|
|
51
|
+
ui.line(`${chalk.green("✓")} ${remote.name} ${local.version} — up to date`);
|
|
53
52
|
}
|
|
54
53
|
else {
|
|
55
|
-
ui.line(
|
|
54
|
+
ui.line(`${chalk.yellow("⬆")} ${chalk.bold(remote.name)} ${local.version} → ${chalk.green(remote.latest_version)}`);
|
|
56
55
|
updates.push({
|
|
57
56
|
id: remote.id,
|
|
58
57
|
name: remote.name,
|
|
@@ -66,7 +65,7 @@ export async function updateCommand(opts) {
|
|
|
66
65
|
}
|
|
67
66
|
if (updates.length === 0) {
|
|
68
67
|
console.log();
|
|
69
|
-
ui.ok("All skills are up to date!
|
|
68
|
+
ui.ok("All skills are up to date!");
|
|
70
69
|
return;
|
|
71
70
|
}
|
|
72
71
|
console.log();
|
|
@@ -77,21 +76,26 @@ export async function updateCommand(opts) {
|
|
|
77
76
|
selectedIds = updates.map((u) => u.id);
|
|
78
77
|
}
|
|
79
78
|
else if (updates.length === 1) {
|
|
80
|
-
const ok = await confirm({
|
|
79
|
+
const ok = await clack.confirm({
|
|
81
80
|
message: `Update ${updates[0].name} to ${updates[0].latestVersion}?`,
|
|
82
|
-
|
|
81
|
+
initialValue: true,
|
|
83
82
|
});
|
|
84
|
-
selectedIds = ok ? [updates[0].id]
|
|
83
|
+
selectedIds = (clack.isCancel(ok) || !ok) ? [] : [updates[0].id];
|
|
85
84
|
}
|
|
86
85
|
else {
|
|
87
|
-
|
|
86
|
+
const selected = await clack.multiselect({
|
|
88
87
|
message: "Select updates to apply:",
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
options: updates.map((u) => ({
|
|
89
|
+
label: `${u.name} ${u.currentVersion} → ${u.latestVersion}${u.size ? ` (${ui.fileSize(u.size)})` : ""}`,
|
|
91
90
|
value: u.id,
|
|
92
|
-
checked: true,
|
|
93
91
|
})),
|
|
92
|
+
required: true,
|
|
94
93
|
});
|
|
94
|
+
if (clack.isCancel(selected)) {
|
|
95
|
+
clack.cancel("Cancelled.");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
selectedIds = selected;
|
|
95
99
|
}
|
|
96
100
|
if (selectedIds.length === 0) {
|
|
97
101
|
ui.info("No updates selected.");
|
|
@@ -102,10 +106,10 @@ export async function updateCommand(opts) {
|
|
|
102
106
|
for (const id of selectedIds) {
|
|
103
107
|
const u = updates.find((u) => u.id === id);
|
|
104
108
|
if (!u)
|
|
105
|
-
continue;
|
|
109
|
+
continue;
|
|
106
110
|
console.log();
|
|
107
111
|
ui.header(`Updating ${u.name}`);
|
|
108
|
-
ui.line(
|
|
112
|
+
ui.line(`${u.currentVersion} → ${chalk.green(u.latestVersion)}`);
|
|
109
113
|
try {
|
|
110
114
|
await installSkill(client, u.id, u.packageFile, u.sha256, u.latestVersion);
|
|
111
115
|
ui.ok(`Updated to ${u.latestVersion}`);
|
|
@@ -118,7 +122,7 @@ export async function updateCommand(opts) {
|
|
|
118
122
|
}
|
|
119
123
|
console.log();
|
|
120
124
|
if (successCount === selectedIds.length) {
|
|
121
|
-
ui.ok(`All ${successCount} update(s) applied
|
|
125
|
+
ui.ok(`All ${successCount} update(s) applied!`);
|
|
122
126
|
}
|
|
123
127
|
else {
|
|
124
128
|
ui.warning(`${successCount}/${selectedIds.length} updated. Check errors above.`);
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Install, update and manage AI agent skills for your team.
|
|
7
7
|
*/
|
|
8
8
|
import { Command } from "commander";
|
|
9
|
-
import
|
|
9
|
+
import * as clack from "@clack/prompts";
|
|
10
10
|
import { CLI_VERSION, CLI_NAME, CLI_AUTHOR } from "./types.js";
|
|
11
11
|
import { ui } from "./lib/ui.js";
|
|
12
12
|
import { getApiKey } from "./lib/config.js";
|
|
@@ -81,28 +81,31 @@ program.action(async () => {
|
|
|
81
81
|
ui.banner();
|
|
82
82
|
const isLoggedIn = !!getApiKey();
|
|
83
83
|
if (!isLoggedIn) {
|
|
84
|
-
ui.info("
|
|
84
|
+
ui.info("Welcome! Let's get you set up.");
|
|
85
85
|
console.log();
|
|
86
86
|
await loginCommand({});
|
|
87
87
|
if (!getApiKey())
|
|
88
|
-
return;
|
|
88
|
+
return;
|
|
89
89
|
console.log();
|
|
90
90
|
}
|
|
91
|
-
// Interactive menu
|
|
91
|
+
// Interactive menu with @clack/prompts
|
|
92
92
|
while (true) {
|
|
93
|
-
|
|
94
|
-
const action = await select({
|
|
93
|
+
const action = await clack.select({
|
|
95
94
|
message: "What would you like to do?",
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
{
|
|
99
|
-
{
|
|
100
|
-
{
|
|
101
|
-
{
|
|
102
|
-
{
|
|
103
|
-
{
|
|
95
|
+
options: [
|
|
96
|
+
{ value: "install", label: "Install skills", hint: "download new skills from server" },
|
|
97
|
+
{ value: "update", label: "Check for updates", hint: "update installed skills" },
|
|
98
|
+
{ value: "list", label: "List installed skills", hint: "show local installations" },
|
|
99
|
+
{ value: "setup-mcp", label: "Configure MCP for Agent", hint: "auto-setup Claude/Cursor/Cline" },
|
|
100
|
+
{ value: "status", label: "Status overview", hint: "full system status" },
|
|
101
|
+
{ value: "catalog", label: "Browse skill catalog", hint: "see all available skills" },
|
|
102
|
+
{ value: "exit", label: "Exit" },
|
|
104
103
|
],
|
|
105
104
|
});
|
|
105
|
+
if (clack.isCancel(action)) {
|
|
106
|
+
ui.goodbye();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
106
109
|
switch (action) {
|
|
107
110
|
case "install":
|
|
108
111
|
await installCommand([], {});
|
|
@@ -131,21 +134,26 @@ program.action(async () => {
|
|
|
131
134
|
// Handle errors gracefully
|
|
132
135
|
program.exitOverride();
|
|
133
136
|
async function main() {
|
|
137
|
+
// BUG-2: Warn if TLS verification is disabled
|
|
138
|
+
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0") {
|
|
139
|
+
ui.warning("NODE_TLS_REJECT_UNAUTHORIZED=0 detected — TLS verification disabled.");
|
|
140
|
+
ui.warning("This is a security risk. Remove it: set NODE_TLS_REJECT_UNAUTHORIZED=");
|
|
141
|
+
}
|
|
134
142
|
try {
|
|
135
143
|
await program.parseAsync(process.argv);
|
|
136
144
|
}
|
|
137
145
|
catch (err) {
|
|
138
146
|
if (err && typeof err === "object" && "code" in err) {
|
|
139
147
|
const code = err.code;
|
|
140
|
-
// Commander built-in exit (help, version)
|
|
141
148
|
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
142
149
|
return;
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
|
-
//
|
|
146
|
-
|
|
152
|
+
// CLI-001: clack returns isCancel symbol (handled at each prompt site)
|
|
153
|
+
// Catch any remaining cancellation-like errors gracefully
|
|
154
|
+
if (err instanceof Error && /cancel/i.test(err.message)) {
|
|
147
155
|
console.log();
|
|
148
|
-
|
|
156
|
+
clack.cancel("Cancelled.");
|
|
149
157
|
return;
|
|
150
158
|
}
|
|
151
159
|
ui.err(`Unexpected error: ${err.message}`);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface AgentConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
skillsDir: string;
|
|
4
|
+
envOverride?: string;
|
|
5
|
+
group: "universal" | "additional";
|
|
6
|
+
detect?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Detect which agents are installed on this system
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectAgents(): {
|
|
12
|
+
universal: AgentConfig[];
|
|
13
|
+
detected: AgentConfig[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Get all available agents (universal + detected)
|
|
17
|
+
*/
|
|
18
|
+
export declare function getAllAgents(): AgentConfig[];
|
|
19
|
+
/**
|
|
20
|
+
* Register a skill with specific agents by creating symlinks
|
|
21
|
+
* Returns array of { agent, path, status } results
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerSkillWithAgents(skillId: string, skillPath: string, agents: AgentConfig[]): Array<{
|
|
24
|
+
agent: string;
|
|
25
|
+
path: string;
|
|
26
|
+
status: "created" | "updated" | "exists" | "error";
|
|
27
|
+
error?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Unregister a skill from all agents (remove symlinks only)
|
|
31
|
+
*/
|
|
32
|
+
export declare function unregisterSkillFromAgents(skillId: string): number;
|
|
33
|
+
/**
|
|
34
|
+
* List which agents have a specific skill registered
|
|
35
|
+
*/
|
|
36
|
+
export declare function getSkillAgentStatus(skillId: string): Array<{
|
|
37
|
+
agent: string;
|
|
38
|
+
registered: boolean;
|
|
39
|
+
path: string;
|
|
40
|
+
}>;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Registry — detect installed AI agents and register skills
|
|
3
|
+
* Inspired by skills.sh (vercel-labs/skills) agent directory mapping
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Each agent has a known skills directory. We create symlinks
|
|
6
|
+
* from the agent's skills dir to the actual skill install location.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, symlinkSync, lstatSync, readlinkSync, unlinkSync } from "fs";
|
|
9
|
+
import { join, resolve } from "path";
|
|
10
|
+
import { homedir, platform } from "os";
|
|
11
|
+
// ===== Agent Directory Map =====
|
|
12
|
+
// Universal agents: always shown (use .agents/skills as shared dir)
|
|
13
|
+
// Additional agents: shown only if detected on the system
|
|
14
|
+
const AGENT_CONFIGS = [
|
|
15
|
+
// ── Universal (.agents/skills) ──
|
|
16
|
+
{ name: "Amp", skillsDir: ".agents/skills", group: "universal" },
|
|
17
|
+
{ name: "Cline", skillsDir: ".agents/skills", group: "universal" },
|
|
18
|
+
{ name: "Codex", skillsDir: ".agents/skills", group: "universal", envOverride: "CODEX_HOME" },
|
|
19
|
+
{ name: "Cursor", skillsDir: ".agents/skills", group: "universal" },
|
|
20
|
+
{ name: "Deep Agents", skillsDir: ".agents/skills", group: "universal" },
|
|
21
|
+
{ name: "Firebender", skillsDir: ".agents/skills", group: "universal" },
|
|
22
|
+
{ name: "Gemini CLI", skillsDir: ".agents/skills", group: "universal" },
|
|
23
|
+
{ name: "GitHub Copilot", skillsDir: ".agents/skills", group: "universal" },
|
|
24
|
+
{ name: "Kimi Code CLI", skillsDir: ".agents/skills", group: "universal" },
|
|
25
|
+
{ name: "OpenCode", skillsDir: ".agents/skills", group: "universal" },
|
|
26
|
+
{ name: "Warp", skillsDir: ".agents/skills", group: "universal" },
|
|
27
|
+
// ── Additional agents ──
|
|
28
|
+
{ name: "Augment", skillsDir: ".augment/skills", group: "additional", detect: ".augment" },
|
|
29
|
+
{ name: "Claude Code", skillsDir: ".claude/skills", group: "additional", detect: ".claude", envOverride: "CLAUDE_CONFIG_DIR" },
|
|
30
|
+
{ name: "OpenClaw", skillsDir: "skills", group: "additional", detect: "skills" },
|
|
31
|
+
{ name: "CodeBuddy", skillsDir: ".codebuddy/skills", group: "additional", detect: ".codebuddy" },
|
|
32
|
+
{ name: "Continue", skillsDir: ".continue/skills", group: "additional", detect: ".continue" },
|
|
33
|
+
{ name: "Windsurf", skillsDir: ".codeium/windsurf/skills", group: "additional", detect: ".codeium" },
|
|
34
|
+
{ name: "Goose", skillsDir: ".config/goose/skills", group: "additional", detect: ".config/goose" },
|
|
35
|
+
{ name: "Roo Code", skillsDir: ".roo/skills", group: "additional", detect: ".roo" },
|
|
36
|
+
{ name: "Trae", skillsDir: ".trae/skills", group: "additional", detect: ".trae" },
|
|
37
|
+
{ name: "Void", skillsDir: ".void/skills", group: "additional", detect: ".void" },
|
|
38
|
+
{ name: "Zed", skillsDir: ".config/zed/skills", group: "additional", detect: ".config/zed" },
|
|
39
|
+
{ name: "Aider", skillsDir: ".aider/skills", group: "additional", detect: ".aider" },
|
|
40
|
+
{ name: "Plandex", skillsDir: ".plandex/skills", group: "additional", detect: ".plandex" },
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the skills directory for an agent (handles env overrides + home expansion)
|
|
44
|
+
*/
|
|
45
|
+
function resolveAgentDir(agent) {
|
|
46
|
+
const home = homedir();
|
|
47
|
+
if (agent.envOverride && process.env[agent.envOverride]) {
|
|
48
|
+
return join(process.env[agent.envOverride], "skills");
|
|
49
|
+
}
|
|
50
|
+
return join(home, agent.skillsDir);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Detect which agents are installed on this system
|
|
54
|
+
*/
|
|
55
|
+
export function detectAgents() {
|
|
56
|
+
const home = homedir();
|
|
57
|
+
const universal = AGENT_CONFIGS.filter(a => a.group === "universal");
|
|
58
|
+
const additional = AGENT_CONFIGS.filter(a => a.group === "additional");
|
|
59
|
+
const detected = additional.filter(agent => {
|
|
60
|
+
if (!agent.detect)
|
|
61
|
+
return false;
|
|
62
|
+
const checkPath = join(home, agent.detect);
|
|
63
|
+
return existsSync(checkPath);
|
|
64
|
+
});
|
|
65
|
+
return { universal, detected };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get all available agents (universal + detected)
|
|
69
|
+
*/
|
|
70
|
+
export function getAllAgents() {
|
|
71
|
+
const { universal, detected } = detectAgents();
|
|
72
|
+
return [...universal, ...detected];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Register a skill with specific agents by creating symlinks
|
|
76
|
+
* Returns array of { agent, path, status } results
|
|
77
|
+
*/
|
|
78
|
+
export function registerSkillWithAgents(skillId, skillPath, agents) {
|
|
79
|
+
const results = [];
|
|
80
|
+
const resolvedSkillPath = resolve(skillPath);
|
|
81
|
+
// Deduplicate by resolved dir (universal agents share .agents/skills)
|
|
82
|
+
const seenDirs = new Set();
|
|
83
|
+
for (const agent of agents) {
|
|
84
|
+
const agentDir = resolveAgentDir(agent);
|
|
85
|
+
// Skip duplicates (multiple universal agents → same .agents/skills dir)
|
|
86
|
+
if (seenDirs.has(agentDir)) {
|
|
87
|
+
results.push({ agent: agent.name, path: agentDir, status: "exists" });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
seenDirs.add(agentDir);
|
|
91
|
+
const targetLink = join(agentDir, skillId);
|
|
92
|
+
try {
|
|
93
|
+
// Ensure parent dir exists
|
|
94
|
+
mkdirSync(agentDir, { recursive: true });
|
|
95
|
+
// Check if link already exists
|
|
96
|
+
if (existsSync(targetLink)) {
|
|
97
|
+
try {
|
|
98
|
+
const stat = lstatSync(targetLink);
|
|
99
|
+
if (stat.isSymbolicLink()) {
|
|
100
|
+
const existing = readlinkSync(targetLink);
|
|
101
|
+
if (resolve(existing) === resolvedSkillPath) {
|
|
102
|
+
results.push({ agent: agent.name, path: targetLink, status: "exists" });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Different target — update
|
|
106
|
+
unlinkSync(targetLink);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Not a symlink (real directory) — skip to avoid data loss
|
|
110
|
+
results.push({ agent: agent.name, path: targetLink, status: "exists" });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Stat failed — try to proceed
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Create symlink
|
|
119
|
+
// On Windows, use junction for directories (no admin required)
|
|
120
|
+
const linkType = platform() === "win32" ? "junction" : "dir";
|
|
121
|
+
symlinkSync(resolvedSkillPath, targetLink, linkType);
|
|
122
|
+
results.push({ agent: agent.name, path: targetLink, status: "created" });
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
results.push({
|
|
126
|
+
agent: agent.name,
|
|
127
|
+
path: targetLink,
|
|
128
|
+
status: "error",
|
|
129
|
+
error: err.message,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Unregister a skill from all agents (remove symlinks only)
|
|
137
|
+
*/
|
|
138
|
+
export function unregisterSkillFromAgents(skillId) {
|
|
139
|
+
const home = homedir();
|
|
140
|
+
let removed = 0;
|
|
141
|
+
// Check all known agent dirs
|
|
142
|
+
const allDirs = new Set(AGENT_CONFIGS.map(a => resolveAgentDir(a)));
|
|
143
|
+
for (const dir of allDirs) {
|
|
144
|
+
const targetLink = join(dir, skillId);
|
|
145
|
+
try {
|
|
146
|
+
if (existsSync(targetLink) && lstatSync(targetLink).isSymbolicLink()) {
|
|
147
|
+
unlinkSync(targetLink);
|
|
148
|
+
removed++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Skip errors
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return removed;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* List which agents have a specific skill registered
|
|
159
|
+
*/
|
|
160
|
+
export function getSkillAgentStatus(skillId) {
|
|
161
|
+
const results = [];
|
|
162
|
+
const seenDirs = new Set();
|
|
163
|
+
for (const agent of AGENT_CONFIGS) {
|
|
164
|
+
const agentDir = resolveAgentDir(agent);
|
|
165
|
+
if (seenDirs.has(agentDir))
|
|
166
|
+
continue;
|
|
167
|
+
seenDirs.add(agentDir);
|
|
168
|
+
const targetLink = join(agentDir, skillId);
|
|
169
|
+
const registered = existsSync(targetLink);
|
|
170
|
+
results.push({ agent: agent.name, registered, path: targetLink });
|
|
171
|
+
}
|
|
172
|
+
return results;
|
|
173
|
+
}
|
package/dist/lib/config.js
CHANGED
|
@@ -6,6 +6,18 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, statSync
|
|
|
6
6
|
import { join } from "path";
|
|
7
7
|
import { homedir } from "os";
|
|
8
8
|
import { CONFIG_DIR_NAME, DEFAULT_SERVER, DEFAULT_INSTALL_DIR_NAME, } from "../types.js";
|
|
9
|
+
/** CFG-01 FIX: Strip prototype pollution keys from parsed JSON */
|
|
10
|
+
function sanitize(obj) {
|
|
11
|
+
if (typeof obj !== "object" || obj === null)
|
|
12
|
+
return obj;
|
|
13
|
+
const BANNED = new Set(["__proto__", "constructor", "prototype"]);
|
|
14
|
+
for (const key of Object.keys(obj)) {
|
|
15
|
+
if (BANNED.has(key)) {
|
|
16
|
+
delete obj[key];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return obj;
|
|
20
|
+
}
|
|
9
21
|
// R1-06: Key format validation applied everywhere
|
|
10
22
|
const KEY_RE = /^sk-(admin|user)-[a-f0-9]{16,}$/;
|
|
11
23
|
function getConfigDir() {
|
|
@@ -31,7 +43,7 @@ export function loadConfig() {
|
|
|
31
43
|
if (!existsSync(path))
|
|
32
44
|
return null;
|
|
33
45
|
try {
|
|
34
|
-
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
46
|
+
const data = sanitize(JSON.parse(readFileSync(path, "utf-8")));
|
|
35
47
|
// R2-13: Basic shape validation
|
|
36
48
|
if (typeof data !== "object" || data === null)
|
|
37
49
|
return null;
|
|
@@ -81,7 +93,7 @@ export function loadInstalled() {
|
|
|
81
93
|
if (!existsSync(path))
|
|
82
94
|
return {};
|
|
83
95
|
try {
|
|
84
|
-
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
96
|
+
const data = sanitize(JSON.parse(readFileSync(path, "utf-8")));
|
|
85
97
|
// R2-13: Basic shape validation
|
|
86
98
|
if (typeof data !== "object" || data === null || Array.isArray(data))
|
|
87
99
|
return {};
|
package/dist/lib/installer.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* R1-10: SIGINT/SIGTERM cleanup handler
|
|
8
8
|
*/
|
|
9
9
|
import { createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
|
|
10
|
-
import { join, normalize, resolve as pathResolve } from "path";
|
|
11
|
-
import {
|
|
10
|
+
import { join, normalize, resolve as pathResolve, sep } from "path";
|
|
11
|
+
import { Transform as TransformStream } from "stream";
|
|
12
12
|
import yauzl from "yauzl";
|
|
13
13
|
import { loadInstalled, saveInstalled, getInstallDir } from "./config.js";
|
|
14
14
|
import { ui } from "./ui.js";
|
|
@@ -49,7 +49,7 @@ export async function installSkill(client, skillId, packageFile, expectedSha256,
|
|
|
49
49
|
const result = await client.downloadPackage(packageFile, (dl, total) => {
|
|
50
50
|
const pct = Math.round((dl / total) * 100);
|
|
51
51
|
const bar = "█".repeat(Math.round(pct / 4)) + "░".repeat(25 - Math.round(pct / 4));
|
|
52
|
-
spinner.text = `
|
|
52
|
+
spinner.text = ` ${bar} ${pct}% | ${ui.fileSize(dl)}`;
|
|
53
53
|
});
|
|
54
54
|
buffer = result.buffer;
|
|
55
55
|
actualSha256 = result.sha256;
|
|
@@ -155,6 +155,10 @@ export async function installSkill(client, skillId, packageFile, expectedSha256,
|
|
|
155
155
|
* Rollback a skill to its backup (D-13)
|
|
156
156
|
*/
|
|
157
157
|
export function rollbackSkill(skillId) {
|
|
158
|
+
// INS-03/RBK-01 FIX: Validate skill ID to prevent path traversal
|
|
159
|
+
if (!SAFE_SKILL_ID.test(skillId)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
158
162
|
const installDir = getInstallDir();
|
|
159
163
|
const skillDir = join(installDir, skillId);
|
|
160
164
|
let entries;
|
|
@@ -227,20 +231,30 @@ function extractZip(buffer, targetDir) {
|
|
|
227
231
|
}
|
|
228
232
|
const entryPath = normalize(entry.fileName);
|
|
229
233
|
// D-03 / Security: reject path traversal
|
|
230
|
-
|
|
234
|
+
// PH8-01 + CLI-007: comprehensive cross-platform check
|
|
235
|
+
if (entryPath.startsWith("..") ||
|
|
236
|
+
entryPath.endsWith(`${sep}..`) ||
|
|
237
|
+
entryPath.includes(`${sep}..${sep}`)) {
|
|
231
238
|
zipfile.close(); // R2-08
|
|
232
239
|
reject(new Error(`Unsafe path in ZIP: ${entry.fileName}`));
|
|
233
240
|
return;
|
|
234
241
|
}
|
|
235
|
-
// Reject
|
|
242
|
+
// INS-02 FIX: Reject symlink entries (external attributes bit 0xA000)
|
|
243
|
+
const externalAttrs = (entry.externalFileAttributes >>> 16) & 0xFFFF;
|
|
244
|
+
const S_IFLNK = 0xA000;
|
|
245
|
+
if ((externalAttrs & 0xF000) === S_IFLNK) {
|
|
246
|
+
zipfile.close();
|
|
247
|
+
reject(new Error(`Symlink entry rejected in ZIP: ${entry.fileName}`));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// R1-03: Pre-check declared size (actual bytes tracked in counting stream below)
|
|
236
251
|
if (entry.uncompressedSize > MAX_SINGLE_FILE_SIZE) {
|
|
237
252
|
zipfile.close(); // R2-08
|
|
238
253
|
reject(new Error(`File too large in ZIP: ${entry.fileName} (${entry.uncompressedSize} bytes)`));
|
|
239
254
|
return;
|
|
240
255
|
}
|
|
241
|
-
// R1-03:
|
|
242
|
-
totalExtracted
|
|
243
|
-
if (totalExtracted > MAX_TOTAL_EXTRACT_SIZE) {
|
|
256
|
+
// R1-03: Pre-check cumulative declared size (actual bytes tracked in stream)
|
|
257
|
+
if (totalExtracted + entry.uncompressedSize > MAX_TOTAL_EXTRACT_SIZE) {
|
|
244
258
|
zipfile.close(); // R2-08
|
|
245
259
|
reject(new Error(`Total extracted size exceeds ${MAX_TOTAL_EXTRACT_SIZE / 1024 / 1024}MB — possible zip bomb`));
|
|
246
260
|
return;
|
|
@@ -248,7 +262,8 @@ function extractZip(buffer, targetDir) {
|
|
|
248
262
|
const fullPath = join(targetDir, entryPath);
|
|
249
263
|
const resolvedPath = pathResolve(fullPath);
|
|
250
264
|
// R2-04: Trailing separator prevents prefix collision
|
|
251
|
-
|
|
265
|
+
// BUG-1 fix: use path.sep (not hardcoded "/") for Windows compatibility
|
|
266
|
+
const resolvedTarget = pathResolve(targetDir) + sep;
|
|
252
267
|
if (!resolvedPath.startsWith(resolvedTarget) && resolvedPath !== pathResolve(targetDir)) {
|
|
253
268
|
zipfile.close(); // R2-08
|
|
254
269
|
reject(new Error(`Path traversal detected: ${entry.fileName}`));
|
|
@@ -268,11 +283,31 @@ function extractZip(buffer, targetDir) {
|
|
|
268
283
|
reject(err || new Error("Failed to read ZIP entry"));
|
|
269
284
|
return;
|
|
270
285
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
286
|
+
// INS-01 FIX: Track actual bytes written (not declared uncompressedSize)
|
|
287
|
+
let fileBytes = 0;
|
|
288
|
+
const countingStream = new TransformStream({
|
|
289
|
+
transform(chunk, _encoding, callback) {
|
|
290
|
+
fileBytes += chunk.length;
|
|
291
|
+
totalExtracted += chunk.length;
|
|
292
|
+
if (fileBytes > MAX_SINGLE_FILE_SIZE) {
|
|
293
|
+
zipfile.close();
|
|
294
|
+
callback(new Error(`Actual file size exceeds limit: ${entry.fileName}`));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (totalExtracted > MAX_TOTAL_EXTRACT_SIZE) {
|
|
298
|
+
zipfile.close();
|
|
299
|
+
callback(new Error(`Total extracted size exceeds ${MAX_TOTAL_EXTRACT_SIZE / 1024 / 1024}MB — possible zip bomb`));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
callback(null, chunk);
|
|
303
|
+
},
|
|
275
304
|
});
|
|
305
|
+
const writeStream = createWriteStream(fullPath);
|
|
306
|
+
readStream.pipe(countingStream).pipe(writeStream);
|
|
307
|
+
writeStream.on("finish", () => zipfile.readEntry());
|
|
308
|
+
writeStream.on("error", (e) => { zipfile.close(); reject(e); });
|
|
309
|
+
countingStream.on("error", (e) => { zipfile.close(); reject(e); });
|
|
310
|
+
readStream.on("error", (e) => { zipfile.close(); reject(e); });
|
|
276
311
|
});
|
|
277
312
|
}
|
|
278
313
|
});
|
package/dist/lib/ui.d.ts
CHANGED
|
@@ -6,34 +6,42 @@ export declare const ui: {
|
|
|
6
6
|
warn: import("chalk").ChalkInstance;
|
|
7
7
|
error: import("chalk").ChalkInstance;
|
|
8
8
|
dim: import("chalk").ChalkInstance;
|
|
9
|
-
|
|
9
|
+
muted: import("chalk").ChalkInstance;
|
|
10
|
+
/** Animated gradient logo banner — block-art style */
|
|
10
11
|
banner(): void;
|
|
11
|
-
/**
|
|
12
|
+
/** Section header with gradient bar */
|
|
12
13
|
header(text: string): void;
|
|
13
|
-
/**
|
|
14
|
+
/** Boxed panel for important information */
|
|
15
|
+
panel(title: string, content: string): void;
|
|
16
|
+
/** Success message */
|
|
14
17
|
ok(msg: string): void;
|
|
15
|
-
/**
|
|
18
|
+
/** Warning message */
|
|
16
19
|
warning(msg: string): void;
|
|
17
|
-
/**
|
|
20
|
+
/** Error message */
|
|
18
21
|
err(msg: string): void;
|
|
19
|
-
/**
|
|
22
|
+
/** Info line */
|
|
20
23
|
info(msg: string): void;
|
|
21
24
|
/** Indented line */
|
|
22
25
|
line(msg: string): void;
|
|
23
|
-
/**
|
|
26
|
+
/** Dimmed text helper */
|
|
27
|
+
dimText(msg: string): void;
|
|
28
|
+
/** Create an ora spinner with brand styling */
|
|
24
29
|
spinner(text: string): Ora;
|
|
25
30
|
/** Format file size */
|
|
26
31
|
fileSize(bytes: number): string;
|
|
27
|
-
/** Format a skill for display */
|
|
32
|
+
/** Format a skill for display (rich) */
|
|
28
33
|
skillLine(s: {
|
|
29
34
|
id: string;
|
|
30
35
|
name: string;
|
|
31
36
|
version?: string;
|
|
32
37
|
description: string;
|
|
33
38
|
tag?: string;
|
|
39
|
+
size?: number;
|
|
34
40
|
}): string;
|
|
35
|
-
/** Print a key-value table */
|
|
41
|
+
/** Print a key-value table with alignment */
|
|
36
42
|
table(rows: [string, string][]): void;
|
|
37
43
|
/** Goodbye message */
|
|
38
44
|
goodbye(): void;
|
|
45
|
+
/** Status badge */
|
|
46
|
+
badge(text: string, color: "green" | "yellow" | "red" | "blue" | "purple"): string;
|
|
39
47
|
};
|
package/dist/lib/ui.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* UI
|
|
2
|
+
* UI module — Dreamlogic CLI visual design system
|
|
3
|
+
* Uses @clack/prompts, gradient-string, figlet, boxen, ora
|
|
3
4
|
*/
|
|
4
5
|
import chalk from "chalk";
|
|
5
6
|
import ora from "ora";
|
|
7
|
+
import gradientString from "gradient-string";
|
|
8
|
+
import figlet from "figlet";
|
|
9
|
+
import boxen from "boxen";
|
|
6
10
|
import { CLI_NAME, CLI_AUTHOR, CLI_VERSION } from "../types.js";
|
|
7
|
-
// Brand
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
// ===== Brand palette =====
|
|
12
|
+
const BRAND_GRADIENT = ["#7C3AED", "#6366F1", "#3B82F6", "#06B6D4"];
|
|
13
|
+
const brand = chalk.hex("#7C3AED");
|
|
14
|
+
const accent = chalk.hex("#06B6D4");
|
|
15
|
+
const success = chalk.hex("#22C55E");
|
|
16
|
+
const warn = chalk.hex("#F59E0B");
|
|
17
|
+
const error = chalk.hex("#EF4444");
|
|
13
18
|
const dim = chalk.dim;
|
|
19
|
+
const muted = chalk.hex("#6B7280");
|
|
20
|
+
// CLI-004: Check truecolor support — fall back to plain on legacy terminals
|
|
21
|
+
const hasTruecolor = chalk.level >= 3;
|
|
22
|
+
// ===== Gradient helper =====
|
|
23
|
+
const brandGradient = gradientString(BRAND_GRADIENT);
|
|
14
24
|
export const ui = {
|
|
15
25
|
brand,
|
|
16
26
|
accent,
|
|
@@ -18,46 +28,69 @@ export const ui = {
|
|
|
18
28
|
warn,
|
|
19
29
|
error,
|
|
20
30
|
dim,
|
|
21
|
-
|
|
31
|
+
muted,
|
|
32
|
+
/** Animated gradient logo banner — block-art style */
|
|
22
33
|
banner() {
|
|
34
|
+
const dream = figlet.textSync("DREAM", { font: "ANSI Shadow" });
|
|
35
|
+
const logic = figlet.textSync("LOGIC", { font: "ANSI Shadow" });
|
|
36
|
+
const fullLogo = dream + logic;
|
|
23
37
|
console.log();
|
|
24
|
-
console.log(
|
|
25
|
-
console.log(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
console.log(hasTruecolor ? brandGradient.multiline(fullLogo) : chalk.bold.magenta(fullLogo));
|
|
39
|
+
console.log(muted(" ") +
|
|
40
|
+
(hasTruecolor ? brandGradient(`${CLI_NAME} v${CLI_VERSION}`) : chalk.bold(`${CLI_NAME} v${CLI_VERSION}`)) +
|
|
41
|
+
muted(" | ") +
|
|
42
|
+
muted(CLI_AUTHOR));
|
|
29
43
|
console.log();
|
|
30
44
|
},
|
|
31
|
-
/**
|
|
45
|
+
/** Section header with gradient bar */
|
|
32
46
|
header(text) {
|
|
33
47
|
console.log();
|
|
34
|
-
console.log(
|
|
35
|
-
console.log(
|
|
36
|
-
console.log(
|
|
48
|
+
console.log(hasTruecolor ? brandGradient("━".repeat(50)) : chalk.magenta("━".repeat(50)));
|
|
49
|
+
console.log(" " + chalk.bold.white(text));
|
|
50
|
+
console.log(hasTruecolor ? brandGradient("━".repeat(50)) : chalk.magenta("━".repeat(50)));
|
|
37
51
|
},
|
|
38
|
-
/**
|
|
52
|
+
/** Boxed panel for important information */
|
|
53
|
+
panel(title, content) {
|
|
54
|
+
console.log(boxen(content, {
|
|
55
|
+
title,
|
|
56
|
+
titleAlignment: "left",
|
|
57
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
58
|
+
margin: { top: 0, bottom: 0, left: 2, right: 0 },
|
|
59
|
+
borderStyle: "round",
|
|
60
|
+
borderColor: "#7C3AED",
|
|
61
|
+
}));
|
|
62
|
+
},
|
|
63
|
+
/** Success message */
|
|
39
64
|
ok(msg) {
|
|
40
|
-
console.log(success("
|
|
65
|
+
console.log(success(" ✓ ") + msg);
|
|
41
66
|
},
|
|
42
|
-
/**
|
|
67
|
+
/** Warning message */
|
|
43
68
|
warning(msg) {
|
|
44
|
-
console.log(warn("
|
|
69
|
+
console.log(warn(" ▲ ") + msg);
|
|
45
70
|
},
|
|
46
|
-
/**
|
|
71
|
+
/** Error message */
|
|
47
72
|
err(msg) {
|
|
48
|
-
console.log(error("
|
|
73
|
+
console.log(error(" ✗ ") + msg);
|
|
49
74
|
},
|
|
50
|
-
/**
|
|
75
|
+
/** Info line */
|
|
51
76
|
info(msg) {
|
|
52
|
-
console.log(accent("
|
|
77
|
+
console.log(accent(" ● ") + msg);
|
|
53
78
|
},
|
|
54
79
|
/** Indented line */
|
|
55
80
|
line(msg) {
|
|
56
|
-
console.log("
|
|
81
|
+
console.log(" " + msg);
|
|
82
|
+
},
|
|
83
|
+
/** Dimmed text helper */
|
|
84
|
+
dimText(msg) {
|
|
85
|
+
console.log(muted(" " + msg));
|
|
57
86
|
},
|
|
58
|
-
/** Create
|
|
87
|
+
/** Create an ora spinner with brand styling */
|
|
59
88
|
spinner(text) {
|
|
60
|
-
return ora({
|
|
89
|
+
return ora({
|
|
90
|
+
text: " " + text,
|
|
91
|
+
color: "magenta",
|
|
92
|
+
spinner: "dots",
|
|
93
|
+
});
|
|
61
94
|
},
|
|
62
95
|
/** Format file size */
|
|
63
96
|
fileSize(bytes) {
|
|
@@ -67,23 +100,39 @@ export const ui = {
|
|
|
67
100
|
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
68
101
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
69
102
|
},
|
|
70
|
-
/** Format a skill for display */
|
|
103
|
+
/** Format a skill for display (rich) */
|
|
71
104
|
skillLine(s) {
|
|
72
|
-
const
|
|
105
|
+
const name = chalk.bold.white(s.name);
|
|
106
|
+
const ver = s.version ? muted(` v${s.version}`) : "";
|
|
107
|
+
const size = s.size ? muted(` (${ui.fileSize(s.size)})`) : "";
|
|
108
|
+
const desc = muted(s.description);
|
|
73
109
|
const tag = s.tag ? ` ${s.tag}` : "";
|
|
74
|
-
return `${
|
|
110
|
+
return `${name}${ver}${size}\n ${desc}${tag}`;
|
|
75
111
|
},
|
|
76
|
-
/** Print a key-value table */
|
|
112
|
+
/** Print a key-value table with alignment */
|
|
77
113
|
table(rows) {
|
|
78
114
|
const maxKey = Math.max(...rows.map(([k]) => k.length));
|
|
79
115
|
for (const [k, v] of rows) {
|
|
80
|
-
console.log(`
|
|
116
|
+
console.log(` ${accent(k.padEnd(maxKey))} ${v}`);
|
|
81
117
|
}
|
|
82
118
|
},
|
|
83
119
|
/** Goodbye message */
|
|
84
120
|
goodbye() {
|
|
85
121
|
console.log();
|
|
86
|
-
console.log(
|
|
122
|
+
console.log(muted(" ") +
|
|
123
|
+
(hasTruecolor ? brandGradient("Powered by Dreamlogic-ai") : chalk.bold.magenta("Powered by Dreamlogic-ai")) +
|
|
124
|
+
muted(" · github.com/dreamlogic-ai"));
|
|
87
125
|
console.log();
|
|
88
126
|
},
|
|
127
|
+
/** Status badge */
|
|
128
|
+
badge(text, color) {
|
|
129
|
+
const colors = {
|
|
130
|
+
green: success,
|
|
131
|
+
yellow: warn,
|
|
132
|
+
red: error,
|
|
133
|
+
blue: accent,
|
|
134
|
+
purple: brand,
|
|
135
|
+
};
|
|
136
|
+
return (colors[color] || muted)(`[${text}]`);
|
|
137
|
+
},
|
|
89
138
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -33,6 +33,6 @@ export interface InstalledRegistry {
|
|
|
33
33
|
export declare const DEFAULT_SERVER = "https://skill.dreamlogic-claw.com";
|
|
34
34
|
export declare const DEFAULT_INSTALL_DIR_NAME = "dreamlogic-skills";
|
|
35
35
|
export declare const CONFIG_DIR_NAME = ".dreamlogic";
|
|
36
|
-
export declare const CLI_VERSION = "
|
|
36
|
+
export declare const CLI_VERSION = "2.0.1";
|
|
37
37
|
export declare const CLI_NAME = "Dreamlogic CLI";
|
|
38
38
|
export declare const CLI_AUTHOR = "Dreamlogic-ai by MAJORNINE";
|
package/dist/types.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
export const DEFAULT_SERVER = "https://skill.dreamlogic-claw.com";
|
|
3
3
|
export const DEFAULT_INSTALL_DIR_NAME = "dreamlogic-skills";
|
|
4
4
|
export const CONFIG_DIR_NAME = ".dreamlogic";
|
|
5
|
-
export const CLI_VERSION = "
|
|
5
|
+
export const CLI_VERSION = "2.0.1";
|
|
6
6
|
export const CLI_NAME = "Dreamlogic CLI";
|
|
7
7
|
export const CLI_AUTHOR = "Dreamlogic-ai by MAJORNINE";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreamlogic-ai/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Dreamlogic AI Skill Manager — Install, update and manage AI agent skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,13 +34,18 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@
|
|
37
|
+
"@clack/prompts": "^1.2.0",
|
|
38
|
+
"boxen": "^8.0.1",
|
|
38
39
|
"chalk": "^5.6.2",
|
|
39
40
|
"commander": "^12.1.0",
|
|
41
|
+
"figlet": "^1.11.0",
|
|
42
|
+
"gradient-string": "^3.0.0",
|
|
40
43
|
"ora": "^8.2.0",
|
|
41
44
|
"yauzl": "^3.3.0"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
47
|
+
"@types/figlet": "^1.7.0",
|
|
48
|
+
"@types/gradient-string": "^1.1.6",
|
|
44
49
|
"@types/node": "^22.19.17",
|
|
45
50
|
"@types/yauzl": "^2.10.3",
|
|
46
51
|
"tsx": "^4.21.0",
|