@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.
@@ -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 { checkbox, confirm } from "@inquirer/prompts";
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 choices = skills.map((s) => {
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 tag = "";
45
+ let hint = "";
44
46
  if (!isInstalled) {
45
- tag = chalk.green(" [NEW]");
47
+ hint = chalk.green("NEW");
46
48
  }
47
49
  else if (currentVer && s.latest_version && currentVer !== s.latest_version) {
48
- tag = chalk.yellow(` [UPDATE: ${currentVer} → ${s.latest_version}]`);
50
+ hint = chalk.yellow(`UPDATE: ${currentVer} → ${s.latest_version}`);
49
51
  }
50
52
  else {
51
- tag = chalk.dim(" [INSTALLED]");
53
+ hint = chalk.dim("installed");
52
54
  }
53
55
  return {
54
- name: `${s.name} (${ver}) — ${s.description}${tag}`,
56
+ label: `${s.name} (${ver})`,
55
57
  value: s.id,
56
- checked: !isInstalled, // auto-check new skills
58
+ hint: `${s.description} ${hint}`,
57
59
  };
58
60
  });
59
61
  console.log();
60
- selectedIds = await checkbox({
61
- message: "Select skills to install (Space to select, Enter to confirm):",
62
- choices,
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
- console.log();
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
- continue; // R1-19: guard
99
+ return ` ${id}`;
94
100
  const ver = s.latest_version || "unknown";
95
- const size = s.package_size ? ui.fileSize(s.package_size) : "unknown";
96
- ui.line(` 📦 ${chalk.bold(s.name)} ${ver} (${size})`);
97
- }
98
- ui.line(` 📂 Install to: ${getInstallDir()}`);
99
- console.log();
100
- const ok = await confirm({ message: "Proceed?", default: true });
101
- if (!ok) {
102
- ui.info("Cancelled.");
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
- } // R1-19
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(` ${chalk.cyan("dreamlogic setup-mcp")}`);
193
+ ui.line(chalk.cyan("dreamlogic setup-mcp"));
137
194
  }
138
195
  }
@@ -12,7 +12,7 @@ export function listCommand() {
12
12
  return;
13
13
  }
14
14
  console.log();
15
- ui.line(`📂 Install directory: ${getInstallDir()}`);
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)}`);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * login command — authenticate and save API key
3
3
  */
4
- import { password, confirm } from "@inquirer/prompts";
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
- default: false,
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
- apiKey = await password({
28
+ const keyInput = await clack.password({
29
29
  message: "Enter your Dreamlogic API Key:",
30
30
  mask: "*",
31
- validate: (v) => KEY_RE.test(v) || "Key must match format: sk-user-xxxx... or sk-admin-xxxx...",
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...");
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * logout command — clear saved credentials
3
3
  */
4
- import { confirm } from "@inquirer/prompts";
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?", default: true });
15
- if (ok) {
16
- clearConfig();
17
- ui.ok("Credentials cleared.");
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 { confirm } from "@inquirer/prompts";
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?", default: true });
24
- if (!ok) {
25
- ui.info("Cancelled.");
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 { confirm } from "@inquirer/prompts";
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
- default: true,
186
+ initialValue: true,
187
187
  });
188
- if (!ok) {
188
+ if (clack.isCancel(ok) || !ok) {
189
189
  ui.info("Skipped.");
190
190
  continue;
191
191
  }
@@ -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(`📦 Installed: ${entries.length} skill(s)`);
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();
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * update command — check for and apply skill updates
3
3
  */
4
- import { confirm, checkbox } from "@inquirer/prompts";
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(` ${ui.warn("?")} ${id} — not found on server (removed?)`);
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(` ${ui.dim("–")} ${remote.name} — no version info`);
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(` ${chalk.green("✓")} ${remote.name} ${local.version} — up to date`);
51
+ ui.line(`${chalk.green("✓")} ${remote.name} ${local.version} — up to date`);
53
52
  }
54
53
  else {
55
- ui.line(` ${chalk.yellow("⬆")} ${chalk.bold(remote.name)} ${local.version} → ${chalk.green(remote.latest_version)}`);
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
- default: true,
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
- selectedIds = await checkbox({
86
+ const selected = await clack.multiselect({
88
87
  message: "Select updates to apply:",
89
- choices: updates.map((u) => ({
90
- name: `${u.name} ${u.currentVersion} → ${u.latestVersion}${u.size ? ` (${ui.fileSize(u.size)})` : ""}`,
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; // R1-19
109
+ continue;
106
110
  console.log();
107
111
  ui.header(`Updating ${u.name}`);
108
- ui.line(` ${u.currentVersion} → ${chalk.green(u.latestVersion)}`);
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 { select } from "@inquirer/prompts";
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("First time? Let's get you set up!");
84
+ ui.info("Welcome! Let's get you set up.");
85
85
  console.log();
86
86
  await loginCommand({});
87
87
  if (!getApiKey())
88
- return; // login failed
88
+ return;
89
89
  console.log();
90
90
  }
91
- // Interactive menu
91
+ // Interactive menu with @clack/prompts
92
92
  while (true) {
93
- console.log();
94
- const action = await select({
93
+ const action = await clack.select({
95
94
  message: "What would you like to do?",
96
- choices: [
97
- { name: "📦 Install skills", value: "install" },
98
- { name: "🔄 Check for updates", value: "update" },
99
- { name: "📋 List installed skills", value: "list" },
100
- { name: "⚙️ Configure MCP for Agent", value: "setup-mcp" },
101
- { name: "📊 Status overview", value: "status" },
102
- { name: "📚 Browse skill catalog", value: "catalog" },
103
- { name: "🚪 Exit", value: "exit" },
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
- // User cancelled (Ctrl+C on inquirer)
146
- if (err && typeof err === "object" && "name" in err && err.name === "ExitPromptError") {
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
- ui.dim(" Cancelled.");
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
+ }
@@ -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 {};
@@ -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 { pipeline } from "stream/promises";
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 = ` 📦 ${bar} ${pct}% | ${ui.fileSize(dl)}`;
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
- if (entryPath.startsWith("..") || entryPath.includes("/../")) {
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 oversized single files
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: Cumulative size limit
242
- totalExtracted += entry.uncompressedSize;
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
- const resolvedTarget = pathResolve(targetDir) + "/";
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
- const writeStream = createWriteStream(fullPath);
272
- pipeline(readStream, writeStream).then(() => zipfile.readEntry()).catch((e) => {
273
- zipfile.close(); // R2-08
274
- reject(e);
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
- /** Print the welcome banner */
9
+ muted: import("chalk").ChalkInstance;
10
+ /** Animated gradient logo banner — block-art style */
10
11
  banner(): void;
11
- /** Print a section header */
12
+ /** Section header with gradient bar */
12
13
  header(text: string): void;
13
- /** Print success message */
14
+ /** Boxed panel for important information */
15
+ panel(title: string, content: string): void;
16
+ /** Success message */
14
17
  ok(msg: string): void;
15
- /** Print warning */
18
+ /** Warning message */
16
19
  warning(msg: string): void;
17
- /** Print error */
20
+ /** Error message */
18
21
  err(msg: string): void;
19
- /** Print info line */
22
+ /** Info line */
20
23
  info(msg: string): void;
21
24
  /** Indented line */
22
25
  line(msg: string): void;
23
- /** Create a spinner */
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 helpersbranded output, spinners, prompts
2
+ * UI moduleDreamlogic 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 colors
8
- const brand = chalk.hex("#7C3AED"); // Purple
9
- const accent = chalk.hex("#06B6D4"); // Cyan
10
- const success = chalk.green;
11
- const warn = chalk.yellow;
12
- const error = chalk.red;
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
- /** Print the welcome banner */
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(brand(" ╔══════════════════════════════════════════╗"));
25
- console.log(brand("") + " 🚀 " + chalk.bold.white(CLI_NAME) + " v" + CLI_VERSION + brand(" ║"));
26
- console.log(brand(" ║") + " " + dim("AI Skill Manager for your team") + brand(" ║"));
27
- console.log(brand(" ║") + " " + dim(CLI_AUTHOR) + brand(" ║"));
28
- console.log(brand(" ╚══════════════════════════════════════════╝"));
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
- /** Print a section header */
45
+ /** Section header with gradient bar */
32
46
  header(text) {
33
47
  console.log();
34
- console.log(brand("━".repeat(50)));
35
- console.log(brand(" " + text));
36
- console.log(brand("━".repeat(50)));
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
- /** Print success message */
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(" ") + msg);
65
+ console.log(success(" ") + msg);
41
66
  },
42
- /** Print warning */
67
+ /** Warning message */
43
68
  warning(msg) {
44
- console.log(warn(" ⚠️ ") + msg);
69
+ console.log(warn(" ") + msg);
45
70
  },
46
- /** Print error */
71
+ /** Error message */
47
72
  err(msg) {
48
- console.log(error(" ") + msg);
73
+ console.log(error(" ") + msg);
49
74
  },
50
- /** Print info line */
75
+ /** Info line */
51
76
  info(msg) {
52
- console.log(accent(" ") + msg);
77
+ console.log(accent(" ") + msg);
53
78
  },
54
79
  /** Indented line */
55
80
  line(msg) {
56
- console.log(" " + msg);
81
+ console.log(" " + msg);
82
+ },
83
+ /** Dimmed text helper */
84
+ dimText(msg) {
85
+ console.log(muted(" " + msg));
57
86
  },
58
- /** Create a spinner */
87
+ /** Create an ora spinner with brand styling */
59
88
  spinner(text) {
60
- return ora({ text: " " + text, color: "cyan" });
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 ver = s.version ? dim(` (${s.version})`) : "";
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 `${chalk.bold(s.name)}${ver}${dim(s.description)}${tag}`;
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(` ${accent(k.padEnd(maxKey))} ${v}`);
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(dim(" Powered by ") + brand("Dreamlogic-ai") + dim(" · github.com/dreamlogic-ai"));
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 = "1.0.0";
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 = "1.0.0";
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": "1.0.0",
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
- "@inquirer/prompts": "^7.10.1",
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",