@dreamlogic-ai/cli 1.0.0 → 2.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/dist/commands/install.js +31 -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/installer.js +8 -4
- package/dist/lib/ui.d.ts +17 -9
- package/dist/lib/ui.js +82 -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,7 +2,7 @@
|
|
|
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
6
|
import { ApiClient } from "../lib/api-client.js";
|
|
7
7
|
import { getServer, getInstallDir, loadInstalled } from "../lib/config.js";
|
|
8
8
|
import { installSkill } from "../lib/installer.js";
|
|
@@ -36,31 +36,37 @@ export async function installCommand(skillIds, opts) {
|
|
|
36
36
|
ui.info("No skills available yet.");
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
const
|
|
39
|
+
const options = skills.map((s) => {
|
|
40
40
|
const isInstalled = !!installed[s.id];
|
|
41
41
|
const ver = s.latest_version || "unknown";
|
|
42
42
|
const currentVer = installed[s.id]?.version;
|
|
43
|
-
let
|
|
43
|
+
let hint = "";
|
|
44
44
|
if (!isInstalled) {
|
|
45
|
-
|
|
45
|
+
hint = chalk.green("NEW");
|
|
46
46
|
}
|
|
47
47
|
else if (currentVer && s.latest_version && currentVer !== s.latest_version) {
|
|
48
|
-
|
|
48
|
+
hint = chalk.yellow(`UPDATE: ${currentVer} → ${s.latest_version}`);
|
|
49
49
|
}
|
|
50
50
|
else {
|
|
51
|
-
|
|
51
|
+
hint = chalk.dim("installed");
|
|
52
52
|
}
|
|
53
53
|
return {
|
|
54
|
-
|
|
54
|
+
label: `${s.name} (${ver})`,
|
|
55
55
|
value: s.id,
|
|
56
|
-
|
|
56
|
+
hint: `${s.description} ${hint}`,
|
|
57
57
|
};
|
|
58
58
|
});
|
|
59
59
|
console.log();
|
|
60
|
-
|
|
61
|
-
message: "Select skills to install
|
|
62
|
-
|
|
60
|
+
const selected = await clack.multiselect({
|
|
61
|
+
message: "Select skills to install:",
|
|
62
|
+
options,
|
|
63
|
+
required: true,
|
|
63
64
|
});
|
|
65
|
+
if (clack.isCancel(selected)) {
|
|
66
|
+
clack.cancel("Installation cancelled.");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
selectedIds = selected;
|
|
64
70
|
if (selectedIds.length === 0) {
|
|
65
71
|
ui.info("Nothing selected.");
|
|
66
72
|
return;
|
|
@@ -85,21 +91,20 @@ export async function installCommand(skillIds, opts) {
|
|
|
85
91
|
}
|
|
86
92
|
// Confirm
|
|
87
93
|
if (!opts.yes) {
|
|
88
|
-
|
|
89
|
-
ui.header("Installation Plan");
|
|
90
|
-
for (const id of selectedIds) {
|
|
94
|
+
const planLines = selectedIds.map((id) => {
|
|
91
95
|
const s = skills.find((s) => s.id === id);
|
|
92
96
|
if (!s)
|
|
93
|
-
|
|
97
|
+
return ` ${id}`;
|
|
94
98
|
const ver = s.latest_version || "unknown";
|
|
95
|
-
const size = s.package_size ? ui.fileSize(s.package_size) : "
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
const size = s.package_size ? ui.fileSize(s.package_size) : "?";
|
|
100
|
+
return `${chalk.bold(s.name)} ${chalk.dim(ver)} (${size})`;
|
|
101
|
+
});
|
|
102
|
+
planLines.push("");
|
|
103
|
+
planLines.push(`${chalk.dim("Install to:")} ${getInstallDir()}`);
|
|
104
|
+
ui.panel("Installation Plan", planLines.join("\n"));
|
|
105
|
+
const ok = await clack.confirm({ message: "Proceed?", initialValue: true });
|
|
106
|
+
if (clack.isCancel(ok) || !ok) {
|
|
107
|
+
clack.cancel("Cancelled.");
|
|
103
108
|
return;
|
|
104
109
|
}
|
|
105
110
|
}
|
|
@@ -110,7 +115,7 @@ export async function installCommand(skillIds, opts) {
|
|
|
110
115
|
if (!s) {
|
|
111
116
|
ui.err(`Skill '${id}' no longer in catalog`);
|
|
112
117
|
continue;
|
|
113
|
-
}
|
|
118
|
+
}
|
|
114
119
|
console.log();
|
|
115
120
|
ui.header(`Installing ${s.name}`);
|
|
116
121
|
try {
|
|
@@ -124,7 +129,7 @@ export async function installCommand(skillIds, opts) {
|
|
|
124
129
|
}
|
|
125
130
|
console.log();
|
|
126
131
|
if (successCount === selectedIds.length) {
|
|
127
|
-
ui.ok(`All ${successCount} skill(s) installed successfully
|
|
132
|
+
ui.ok(`All ${successCount} skill(s) installed successfully!`);
|
|
128
133
|
}
|
|
129
134
|
else {
|
|
130
135
|
ui.warning(`${successCount}/${selectedIds.length} installed. Check errors above.`);
|
|
@@ -133,6 +138,6 @@ export async function installCommand(skillIds, opts) {
|
|
|
133
138
|
if (successCount > 0) {
|
|
134
139
|
console.log();
|
|
135
140
|
ui.info("To configure MCP for your AI agent, run:");
|
|
136
|
-
ui.line(
|
|
141
|
+
ui.line(chalk.cyan("dreamlogic setup-mcp"));
|
|
137
142
|
}
|
|
138
143
|
}
|
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}`);
|
package/dist/lib/installer.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
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";
|
|
10
|
+
import { join, normalize, resolve as pathResolve, sep } from "path";
|
|
11
11
|
import { pipeline } from "stream/promises";
|
|
12
12
|
import yauzl from "yauzl";
|
|
13
13
|
import { loadInstalled, saveInstalled, getInstallDir } from "./config.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;
|
|
@@ -227,7 +227,10 @@ function extractZip(buffer, targetDir) {
|
|
|
227
227
|
}
|
|
228
228
|
const entryPath = normalize(entry.fileName);
|
|
229
229
|
// D-03 / Security: reject path traversal
|
|
230
|
-
|
|
230
|
+
// PH8-01 + CLI-007: comprehensive cross-platform check
|
|
231
|
+
if (entryPath.startsWith("..") ||
|
|
232
|
+
entryPath.endsWith(`${sep}..`) ||
|
|
233
|
+
entryPath.includes(`${sep}..${sep}`)) {
|
|
231
234
|
zipfile.close(); // R2-08
|
|
232
235
|
reject(new Error(`Unsafe path in ZIP: ${entry.fileName}`));
|
|
233
236
|
return;
|
|
@@ -248,7 +251,8 @@ function extractZip(buffer, targetDir) {
|
|
|
248
251
|
const fullPath = join(targetDir, entryPath);
|
|
249
252
|
const resolvedPath = pathResolve(fullPath);
|
|
250
253
|
// R2-04: Trailing separator prevents prefix collision
|
|
251
|
-
|
|
254
|
+
// BUG-1 fix: use path.sep (not hardcoded "/") for Windows compatibility
|
|
255
|
+
const resolvedTarget = pathResolve(targetDir) + sep;
|
|
252
256
|
if (!resolvedPath.startsWith(resolvedTarget) && resolvedPath !== pathResolve(targetDir)) {
|
|
253
257
|
zipfile.close(); // R2-08
|
|
254
258
|
reject(new Error(`Path traversal detected: ${entry.fileName}`));
|
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 */
|
|
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,68 @@ export const ui = {
|
|
|
18
28
|
warn,
|
|
19
29
|
error,
|
|
20
30
|
dim,
|
|
21
|
-
|
|
31
|
+
muted,
|
|
32
|
+
/** Animated gradient logo banner */
|
|
22
33
|
banner() {
|
|
34
|
+
const logo = figlet.textSync("DreamLogic", { font: "Small" });
|
|
23
35
|
console.log();
|
|
24
|
-
console.log(
|
|
25
|
-
console.log(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
console.log(hasTruecolor ? brandGradient.multiline(logo) : chalk.bold.magenta(logo));
|
|
37
|
+
console.log(muted(" ") +
|
|
38
|
+
(hasTruecolor ? brandGradient(`${CLI_NAME} v${CLI_VERSION}`) : chalk.bold(`${CLI_NAME} v${CLI_VERSION}`)) +
|
|
39
|
+
muted(" | ") +
|
|
40
|
+
muted(CLI_AUTHOR));
|
|
41
|
+
console.log(muted(" " + "─".repeat(48)));
|
|
29
42
|
console.log();
|
|
30
43
|
},
|
|
31
|
-
/**
|
|
44
|
+
/** Section header with gradient bar */
|
|
32
45
|
header(text) {
|
|
33
46
|
console.log();
|
|
34
|
-
console.log(
|
|
35
|
-
console.log(
|
|
36
|
-
console.log(
|
|
47
|
+
console.log(hasTruecolor ? brandGradient("━".repeat(50)) : chalk.magenta("━".repeat(50)));
|
|
48
|
+
console.log(" " + chalk.bold.white(text));
|
|
49
|
+
console.log(hasTruecolor ? brandGradient("━".repeat(50)) : chalk.magenta("━".repeat(50)));
|
|
37
50
|
},
|
|
38
|
-
/**
|
|
51
|
+
/** Boxed panel for important information */
|
|
52
|
+
panel(title, content) {
|
|
53
|
+
console.log(boxen(content, {
|
|
54
|
+
title,
|
|
55
|
+
titleAlignment: "left",
|
|
56
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
57
|
+
margin: { top: 0, bottom: 0, left: 2, right: 0 },
|
|
58
|
+
borderStyle: "round",
|
|
59
|
+
borderColor: "#7C3AED",
|
|
60
|
+
}));
|
|
61
|
+
},
|
|
62
|
+
/** Success message */
|
|
39
63
|
ok(msg) {
|
|
40
|
-
console.log(success("
|
|
64
|
+
console.log(success(" ✓ ") + msg);
|
|
41
65
|
},
|
|
42
|
-
/**
|
|
66
|
+
/** Warning message */
|
|
43
67
|
warning(msg) {
|
|
44
|
-
console.log(warn("
|
|
68
|
+
console.log(warn(" ▲ ") + msg);
|
|
45
69
|
},
|
|
46
|
-
/**
|
|
70
|
+
/** Error message */
|
|
47
71
|
err(msg) {
|
|
48
|
-
console.log(error("
|
|
72
|
+
console.log(error(" ✗ ") + msg);
|
|
49
73
|
},
|
|
50
|
-
/**
|
|
74
|
+
/** Info line */
|
|
51
75
|
info(msg) {
|
|
52
|
-
console.log(accent("
|
|
76
|
+
console.log(accent(" ● ") + msg);
|
|
53
77
|
},
|
|
54
78
|
/** Indented line */
|
|
55
79
|
line(msg) {
|
|
56
|
-
console.log("
|
|
80
|
+
console.log(" " + msg);
|
|
81
|
+
},
|
|
82
|
+
/** Dimmed text helper */
|
|
83
|
+
dimText(msg) {
|
|
84
|
+
console.log(muted(" " + msg));
|
|
57
85
|
},
|
|
58
|
-
/** Create
|
|
86
|
+
/** Create an ora spinner with brand styling */
|
|
59
87
|
spinner(text) {
|
|
60
|
-
return ora({
|
|
88
|
+
return ora({
|
|
89
|
+
text: " " + text,
|
|
90
|
+
color: "magenta",
|
|
91
|
+
spinner: "dots",
|
|
92
|
+
});
|
|
61
93
|
},
|
|
62
94
|
/** Format file size */
|
|
63
95
|
fileSize(bytes) {
|
|
@@ -67,23 +99,39 @@ export const ui = {
|
|
|
67
99
|
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
68
100
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
69
101
|
},
|
|
70
|
-
/** Format a skill for display */
|
|
102
|
+
/** Format a skill for display (rich) */
|
|
71
103
|
skillLine(s) {
|
|
72
|
-
const
|
|
104
|
+
const name = chalk.bold.white(s.name);
|
|
105
|
+
const ver = s.version ? muted(` v${s.version}`) : "";
|
|
106
|
+
const size = s.size ? muted(` (${ui.fileSize(s.size)})`) : "";
|
|
107
|
+
const desc = muted(s.description);
|
|
73
108
|
const tag = s.tag ? ` ${s.tag}` : "";
|
|
74
|
-
return `${
|
|
109
|
+
return `${name}${ver}${size}\n ${desc}${tag}`;
|
|
75
110
|
},
|
|
76
|
-
/** Print a key-value table */
|
|
111
|
+
/** Print a key-value table with alignment */
|
|
77
112
|
table(rows) {
|
|
78
113
|
const maxKey = Math.max(...rows.map(([k]) => k.length));
|
|
79
114
|
for (const [k, v] of rows) {
|
|
80
|
-
console.log(`
|
|
115
|
+
console.log(` ${accent(k.padEnd(maxKey))} ${v}`);
|
|
81
116
|
}
|
|
82
117
|
},
|
|
83
118
|
/** Goodbye message */
|
|
84
119
|
goodbye() {
|
|
85
120
|
console.log();
|
|
86
|
-
console.log(
|
|
121
|
+
console.log(muted(" ") +
|
|
122
|
+
(hasTruecolor ? brandGradient("Powered by Dreamlogic-ai") : chalk.bold.magenta("Powered by Dreamlogic-ai")) +
|
|
123
|
+
muted(" · github.com/dreamlogic-ai"));
|
|
87
124
|
console.log();
|
|
88
125
|
},
|
|
126
|
+
/** Status badge */
|
|
127
|
+
badge(text, color) {
|
|
128
|
+
const colors = {
|
|
129
|
+
green: success,
|
|
130
|
+
yellow: warn,
|
|
131
|
+
red: error,
|
|
132
|
+
blue: accent,
|
|
133
|
+
purple: brand,
|
|
134
|
+
};
|
|
135
|
+
return (colors[color] || muted)(`[${text}]`);
|
|
136
|
+
},
|
|
89
137
|
};
|
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.0";
|
|
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.0";
|
|
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.0",
|
|
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",
|