@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.
@@ -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 { checkbox, confirm } from "@inquirer/prompts";
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 choices = skills.map((s) => {
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 tag = "";
43
+ let hint = "";
44
44
  if (!isInstalled) {
45
- tag = chalk.green(" [NEW]");
45
+ hint = chalk.green("NEW");
46
46
  }
47
47
  else if (currentVer && s.latest_version && currentVer !== s.latest_version) {
48
- tag = chalk.yellow(` [UPDATE: ${currentVer} → ${s.latest_version}]`);
48
+ hint = chalk.yellow(`UPDATE: ${currentVer} → ${s.latest_version}`);
49
49
  }
50
50
  else {
51
- tag = chalk.dim(" [INSTALLED]");
51
+ hint = chalk.dim("installed");
52
52
  }
53
53
  return {
54
- name: `${s.name} (${ver}) — ${s.description}${tag}`,
54
+ label: `${s.name} (${ver})`,
55
55
  value: s.id,
56
- checked: !isInstalled, // auto-check new skills
56
+ hint: `${s.description} ${hint}`,
57
57
  };
58
58
  });
59
59
  console.log();
60
- selectedIds = await checkbox({
61
- message: "Select skills to install (Space to select, Enter to confirm):",
62
- choices,
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
- console.log();
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
- continue; // R1-19: guard
97
+ return ` ${id}`;
94
98
  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.");
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
- } // R1-19
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(` ${chalk.cyan("dreamlogic setup-mcp")}`);
141
+ ui.line(chalk.cyan("dreamlogic setup-mcp"));
137
142
  }
138
143
  }
@@ -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}`);
@@ -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 = ` 📦 ${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;
@@ -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
- if (entryPath.startsWith("..") || entryPath.includes("/../")) {
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
- const resolvedTarget = pathResolve(targetDir) + "/";
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
- /** Print the welcome banner */
9
+ muted: import("chalk").ChalkInstance;
10
+ /** Animated gradient logo banner */
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,68 @@ export const ui = {
18
28
  warn,
19
29
  error,
20
30
  dim,
21
- /** Print the welcome banner */
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(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(" ╚══════════════════════════════════════════╝"));
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
- /** Print a section header */
44
+ /** Section header with gradient bar */
32
45
  header(text) {
33
46
  console.log();
34
- console.log(brand("━".repeat(50)));
35
- console.log(brand(" " + text));
36
- console.log(brand("━".repeat(50)));
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
- /** Print success message */
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(" ") + msg);
64
+ console.log(success(" ") + msg);
41
65
  },
42
- /** Print warning */
66
+ /** Warning message */
43
67
  warning(msg) {
44
- console.log(warn(" ⚠️ ") + msg);
68
+ console.log(warn(" ") + msg);
45
69
  },
46
- /** Print error */
70
+ /** Error message */
47
71
  err(msg) {
48
- console.log(error(" ") + msg);
72
+ console.log(error(" ") + msg);
49
73
  },
50
- /** Print info line */
74
+ /** Info line */
51
75
  info(msg) {
52
- console.log(accent(" ") + msg);
76
+ console.log(accent(" ") + msg);
53
77
  },
54
78
  /** Indented line */
55
79
  line(msg) {
56
- console.log(" " + msg);
80
+ console.log(" " + msg);
81
+ },
82
+ /** Dimmed text helper */
83
+ dimText(msg) {
84
+ console.log(muted(" " + msg));
57
85
  },
58
- /** Create a spinner */
86
+ /** Create an ora spinner with brand styling */
59
87
  spinner(text) {
60
- return ora({ text: " " + text, color: "cyan" });
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 ver = s.version ? dim(` (${s.version})`) : "";
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 `${chalk.bold(s.name)}${ver}${dim(s.description)}${tag}`;
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(` ${accent(k.padEnd(maxKey))} ${v}`);
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(dim(" Powered by ") + brand("Dreamlogic-ai") + dim(" · github.com/dreamlogic-ai"));
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 = "1.0.0";
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 = "1.0.0";
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": "1.0.0",
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
- "@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",