@dreamlogic-ai/cli 2.0.7 → 2.0.8

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.
@@ -111,7 +111,7 @@ export async function installCommand(skillIds, opts) {
111
111
  return;
112
112
  }
113
113
  }
114
- // R4-FIX #8: 安装前检查磁盘空间(可选警告)
114
+ // R4-FIX #8 + R5-L02 FIX: Check disk space WITHOUT creating dir before confirmation
115
115
  try {
116
116
  const totalSize = selectedIds.reduce((sum, id) => {
117
117
  const s = skills.find((s) => s.id === id);
@@ -119,11 +119,11 @@ export async function installCommand(skillIds, opts) {
119
119
  }, 0);
120
120
  if (totalSize > 0) {
121
121
  const installPath = getInstallDir();
122
- const { mkdirSync } = await import("fs");
123
- mkdirSync(installPath, { recursive: true });
124
- const fsStats = statfsSync(installPath);
122
+ // R5-L02: Only check existing parent dir, don't create install dir yet
123
+ const { existsSync: existsSyncFs } = await import("fs");
124
+ const checkPath = existsSyncFs(installPath) ? installPath : join(installPath, "..");
125
+ const fsStats = statfsSync(checkPath);
125
126
  const freeSpace = fsStats.bsize * fsStats.bavail;
126
- // 需要解压后约 2x 空间(压缩包 + 解压内容)
127
127
  if (freeSpace < totalSize * 2) {
128
128
  ui.warning(`磁盘剩余空间不足: ${ui.fileSize(freeSpace)} 可用, 预计需要 ${ui.fileSize(totalSize * 2)}`);
129
129
  }
@@ -156,54 +156,76 @@ export async function installCommand(skillIds, opts) {
156
156
  else {
157
157
  ui.warning(`${successCount}/${selectedIds.length} 已安装,请检查上方错误信息。`);
158
158
  }
159
- // ── Agent Registration Step ──
159
+ // ── Agent Registration Step (skills.sh-style) ──
160
160
  if (successCount > 0 && !opts.yes) {
161
161
  console.log();
162
162
  const { universal, detected } = detectAgents();
163
163
  const totalAgents = universal.length + detected.length;
164
164
  if (totalAgents > 0) {
165
- // Build options grouped by category
166
- const agentOptions = [];
167
- // Universal agents header
165
+ ui.info(`共检测到 ${totalAgents} AI Agent`);
166
+ console.log();
167
+ // Show universal agents as always-included (locked)
168
+ console.log(chalk.dim(" ── Universal (.agents/skills) ── 自动包含 ──────────"));
168
169
  for (const agent of universal) {
169
- agentOptions.push({
170
- label: agent.name,
171
- value: `u:${agent.name}`,
172
- hint: "universal (.agents/skills)",
173
- });
170
+ console.log(` ${chalk.green("•")} ${agent.name}`);
174
171
  }
175
- // Detected additional agents
176
- for (const agent of detected) {
177
- agentOptions.push({
178
- label: agent.name,
179
- value: `a:${agent.name}`,
180
- hint: `detected (${agent.skillsDir})`,
172
+ // Build selectable options for additional agents
173
+ if (detected.length > 0) {
174
+ console.log();
175
+ console.log(chalk.dim(" ── 已检测的 Agent ──────────────────────────────"));
176
+ const additionalOptions = detected.map(agent => ({
177
+ label: `${agent.name}`,
178
+ value: agent.name,
179
+ hint: agent.skillsDir,
180
+ }));
181
+ const agentChoice = await clack.multiselect({
182
+ message: "选择要额外注册的 Agent(Universal 已自动包含):",
183
+ options: additionalOptions,
184
+ required: false,
181
185
  });
182
- }
183
- const agentChoice = await clack.multiselect({
184
- message: `注册技能到 AI Agent?(共 ${totalAgents} 个可用)`,
185
- options: agentOptions,
186
- required: false,
187
- });
188
- if (!clack.isCancel(agentChoice) && agentChoice.length > 0) {
189
- const chosenNames = agentChoice.map(v => v.replace(/^[ua]:/, ""));
190
- const allAgents = [...universal, ...detected];
191
- const chosenAgents = allAgents.filter(a => chosenNames.includes(a.name));
186
+ // Always register universal + selected additional
187
+ const chosenAdditionalNames = (!clack.isCancel(agentChoice))
188
+ ? agentChoice
189
+ : [];
190
+ const chosenAgents = [
191
+ ...universal,
192
+ ...detected.filter(a => chosenAdditionalNames.includes(a.name)),
193
+ ];
192
194
  // Register each installed skill with chosen agents
193
195
  const installDir = getInstallDir();
194
196
  for (const id of selectedIds) {
195
197
  const skillPath = join(installDir, id);
196
198
  const results = registerSkillWithAgents(id, skillPath, chosenAgents);
197
199
  const created = results.filter(r => r.status === "created").length;
200
+ const existing = results.filter(r => r.status === "exists").length;
198
201
  const errors = results.filter(r => r.status === "error");
199
- if (created > 0) {
200
- ui.ok(`${id}: 已注册到 ${created} 个 Agent`);
202
+ if (created > 0 || existing > 0) {
203
+ ui.ok(`${id}: 已注册到 ${created + existing} 个 Agent (${created} 新建, ${existing} 已存在)`);
201
204
  }
202
205
  for (const e of errors) {
203
206
  ui.warning(`${e.agent}: ${e.error}`);
204
207
  }
205
208
  }
206
209
  }
210
+ else {
211
+ // No additional agents detected — just register universal
212
+ console.log();
213
+ const registerUniversal = await clack.confirm({
214
+ message: "注册技能到 Universal Agent 目录?",
215
+ initialValue: true,
216
+ });
217
+ if (!clack.isCancel(registerUniversal) && registerUniversal) {
218
+ const installDir = getInstallDir();
219
+ for (const id of selectedIds) {
220
+ const skillPath = join(installDir, id);
221
+ const results = registerSkillWithAgents(id, skillPath, universal);
222
+ const created = results.filter(r => r.status === "created").length;
223
+ if (created > 0) {
224
+ ui.ok(`${id}: 已注册到 Universal Agent 目录`);
225
+ }
226
+ }
227
+ }
228
+ }
207
229
  }
208
230
  }
209
231
  // Suggest MCP setup
@@ -119,6 +119,22 @@ export async function updateCommand(opts) {
119
119
  console.log();
120
120
  ui.header(`正在更新 ${u.name}`);
121
121
  ui.line(`${u.currentVersion} → ${chalk.green(u.latestVersion)}`);
122
+ // Lossless upgrade: warn about user-modified files
123
+ const installDir = (await import("../lib/config.js")).getInstallDir();
124
+ const skillDir = (await import("path")).join(installDir, id);
125
+ const { existsSync: existsSyncCheck, readdirSync: readDirCheck } = await import("fs");
126
+ if (existsSyncCheck(skillDir)) {
127
+ try {
128
+ const userFiles = readDirCheck(skillDir, { recursive: true });
129
+ const knownUserDirs = ["memory", "signers", "output", "cache", "logs"];
130
+ const hasUserData = userFiles.some((f) => knownUserDirs.some(d => String(f).startsWith(d + "/") || String(f).startsWith(d + "\\") || String(f) === d));
131
+ if (hasUserData) {
132
+ ui.warning("检测到用户数据目录 — 升级将保留 memory/, signers/, output/ 等用户数据");
133
+ ui.line(chalk.dim(" 原版本已自动备份,可随时回滚: dreamlogic rollback " + id));
134
+ }
135
+ }
136
+ catch { /* best-effort check */ }
137
+ }
122
138
  try {
123
139
  await installSkill(client, u.id, u.packageFile, u.sha256, u.latestVersion);
124
140
  ui.ok(`已更新到 ${u.latestVersion}`);
@@ -11,11 +11,14 @@ import { homedir, platform } from "os";
11
11
  // ===== Agent Directory Map =====
12
12
  // Universal agents: always shown (use .agents/skills as shared dir)
13
13
  // Additional agents: shown only if detected on the system
14
+ // Based on skills.sh (vercel-labs/skills) comprehensive agent directory
14
15
  const AGENT_CONFIGS = [
15
- // ── Universal (.agents/skills) ──
16
+ // ── Universal (.agents/skills) ── always included ──
16
17
  { name: "Amp", skillsDir: ".agents/skills", group: "universal" },
18
+ { name: "Antigravity", skillsDir: ".agents/skills", group: "universal" },
17
19
  { name: "Cline", skillsDir: ".agents/skills", group: "universal" },
18
20
  { name: "Codex", skillsDir: ".agents/skills", group: "universal", envOverride: "CODEX_HOME" },
21
+ { name: "Command Code", skillsDir: ".agents/skills", group: "universal" },
19
22
  { name: "Cursor", skillsDir: ".agents/skills", group: "universal" },
20
23
  { name: "Deep Agents", skillsDir: ".agents/skills", group: "universal" },
21
24
  { name: "Firebender", skillsDir: ".agents/skills", group: "universal" },
@@ -24,7 +27,7 @@ const AGENT_CONFIGS = [
24
27
  { name: "Kimi Code CLI", skillsDir: ".agents/skills", group: "universal" },
25
28
  { name: "OpenCode", skillsDir: ".agents/skills", group: "universal" },
26
29
  { name: "Warp", skillsDir: ".agents/skills", group: "universal" },
27
- // ── Additional agents ──
30
+ // ── Additional agents ── shown only if detected ──
28
31
  { name: "Augment", skillsDir: ".augment/skills", group: "additional", detect: ".augment" },
29
32
  { name: "Claude Code", skillsDir: ".claude/skills", group: "additional", detect: ".claude", envOverride: "CLAUDE_CONFIG_DIR" },
30
33
  { name: "OpenClaw", skillsDir: "skills", group: "additional", detect: "skills" },
@@ -34,10 +37,29 @@ const AGENT_CONFIGS = [
34
37
  { name: "Goose", skillsDir: ".config/goose/skills", group: "additional", detect: ".config/goose" },
35
38
  { name: "Roo Code", skillsDir: ".roo/skills", group: "additional", detect: ".roo" },
36
39
  { name: "Trae", skillsDir: ".trae/skills", group: "additional", detect: ".trae" },
40
+ { name: "Trae CN", skillsDir: ".trae-cn/skills", group: "additional", detect: ".trae-cn" },
37
41
  { name: "Void", skillsDir: ".void/skills", group: "additional", detect: ".void" },
38
42
  { name: "Zed", skillsDir: ".config/zed/skills", group: "additional", detect: ".config/zed" },
39
43
  { name: "Aider", skillsDir: ".aider/skills", group: "additional", detect: ".aider" },
40
44
  { name: "Plandex", skillsDir: ".plandex/skills", group: "additional", detect: ".plandex" },
45
+ { name: "IBM Bob", skillsDir: ".bob/skills", group: "additional", detect: ".bob" },
46
+ { name: "Cortex Code", skillsDir: ".cortex/skills", group: "additional", detect: ".cortex" },
47
+ { name: "Crush", skillsDir: ".crush/skills", group: "additional", detect: ".crush" },
48
+ { name: "Droid", skillsDir: ".droid/skills", group: "additional", detect: ".droid" },
49
+ { name: "iFlow CLI", skillsDir: ".iflow/skills", group: "additional", detect: ".iflow" },
50
+ { name: "Junie", skillsDir: ".junie/skills", group: "additional", detect: ".junie" },
51
+ { name: "Kilo", skillsDir: ".kilo/skills", group: "additional", detect: ".kilo" },
52
+ { name: "Kiro CLI", skillsDir: ".kiro/skills", group: "additional", detect: ".kiro" },
53
+ { name: "Kode", skillsDir: ".kode/skills", group: "additional", detect: ".kode" },
54
+ { name: "Mistral Vibe", skillsDir: ".mistral-vibe/skills", group: "additional", detect: ".mistral-vibe" },
55
+ { name: "Mux", skillsDir: ".mux/skills", group: "additional", detect: ".mux" },
56
+ { name: "Neovate", skillsDir: ".neovate/skills", group: "additional", detect: ".neovate" },
57
+ { name: "OpenHands", skillsDir: ".openhands/skills", group: "additional", detect: ".openhands" },
58
+ { name: "Pi", skillsDir: ".pi/skills", group: "additional", detect: ".pi" },
59
+ { name: "Qoder", skillsDir: ".qoder/skills", group: "additional", detect: ".qoder" },
60
+ { name: "Qwen Code", skillsDir: ".qwen-code/skills", group: "additional", detect: ".qwen-code" },
61
+ { name: "Replit", skillsDir: ".agents/skills", group: "additional", detect: ".replit" },
62
+ { name: "ZenCoder", skillsDir: ".zencoder/skills", group: "additional", detect: ".zencoder" },
41
63
  ];
42
64
  // R4-FIX: Shared safe skill ID pattern
43
65
  const SAFE_SKILL_ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
@@ -246,13 +246,19 @@ function extractZip(buffer, targetDir) {
246
246
  }
247
247
  let totalExtracted = 0;
248
248
  let entryCount = 0;
249
+ let aborted = false; // R5-L06 FIX: Prevent reject-after-close race
250
+ const safeReject = (error) => {
251
+ if (aborted)
252
+ return;
253
+ aborted = true;
254
+ safeReject(error);
255
+ };
249
256
  zipfile.readEntry();
250
257
  zipfile.on("entry", (entry) => {
251
258
  // R1-03: Entry count limit
252
259
  entryCount++;
253
260
  if (entryCount > MAX_ENTRY_COUNT) {
254
- zipfile.close(); // R2-08: Close before reject
255
- reject(new Error(`Too many ZIP entries (>${MAX_ENTRY_COUNT}) — possible zip bomb`));
261
+ safeReject(new Error(`Too many ZIP entries (>${MAX_ENTRY_COUNT}) possible zip bomb`));
256
262
  return;
257
263
  }
258
264
  const entryPath = normalize(entry.fileName);
@@ -261,8 +267,7 @@ function extractZip(buffer, targetDir) {
261
267
  if (entryPath.startsWith("..") ||
262
268
  entryPath.endsWith(`${sep}..`) ||
263
269
  entryPath.includes(`${sep}..${sep}`)) {
264
- zipfile.close(); // R2-08
265
- reject(new Error(`Unsafe path in ZIP: ${entry.fileName}`));
270
+ safeReject(new Error(`Unsafe path in ZIP: ${entry.fileName}`));
266
271
  return;
267
272
  }
268
273
  // R4-FIX #2: 拒绝 Windows Alternate Data Streams (filename:stream)
@@ -270,8 +275,7 @@ function extractZip(buffer, targetDir) {
270
275
  const pathParts = entryPath.split(sep);
271
276
  for (const part of pathParts) {
272
277
  if (part.includes(":") && !/^[A-Za-z]:$/.test(part)) {
273
- zipfile.close();
274
- reject(new Error(`不安全的路径(可能包含 Windows ADS): ${entry.fileName}`));
278
+ safeReject(new Error(`不安全的路径(可能包含 Windows ADS): ${entry.fileName}`));
275
279
  return;
276
280
  }
277
281
  }
@@ -279,20 +283,17 @@ function extractZip(buffer, targetDir) {
279
283
  const externalAttrs = (entry.externalFileAttributes >>> 16) & 0xFFFF;
280
284
  const S_IFLNK = 0xA000;
281
285
  if ((externalAttrs & 0xF000) === S_IFLNK) {
282
- zipfile.close();
283
- reject(new Error(`Symlink entry rejected in ZIP: ${entry.fileName}`));
286
+ safeReject(new Error(`Symlink entry rejected in ZIP: ${entry.fileName}`));
284
287
  return;
285
288
  }
286
289
  // R1-03: Pre-check declared size (actual bytes tracked in counting stream below)
287
290
  if (entry.uncompressedSize > MAX_SINGLE_FILE_SIZE) {
288
- zipfile.close(); // R2-08
289
- reject(new Error(`File too large in ZIP: ${entry.fileName} (${entry.uncompressedSize} bytes)`));
291
+ safeReject(new Error(`File too large in ZIP: ${entry.fileName} (${entry.uncompressedSize} bytes)`));
290
292
  return;
291
293
  }
292
294
  // R1-03: Pre-check cumulative declared size (actual bytes tracked in stream)
293
295
  if (totalExtracted + entry.uncompressedSize > MAX_TOTAL_EXTRACT_SIZE) {
294
- zipfile.close(); // R2-08
295
- reject(new Error(`Total extracted size exceeds ${MAX_TOTAL_EXTRACT_SIZE / 1024 / 1024}MB — possible zip bomb`));
296
+ safeReject(new Error(`Total extracted size exceeds ${MAX_TOTAL_EXTRACT_SIZE / 1024 / 1024}MB — possible zip bomb`));
296
297
  return;
297
298
  }
298
299
  const fullPath = join(targetDir, entryPath);
@@ -301,8 +302,7 @@ function extractZip(buffer, targetDir) {
301
302
  // BUG-1 fix: use path.sep (not hardcoded "/") for Windows compatibility
302
303
  const resolvedTarget = pathResolve(targetDir) + sep;
303
304
  if (!resolvedPath.startsWith(resolvedTarget) && resolvedPath !== pathResolve(targetDir)) {
304
- zipfile.close(); // R2-08
305
- reject(new Error(`Path traversal detected: ${entry.fileName}`));
305
+ safeReject(new Error(`Path traversal detected: ${entry.fileName}`));
306
306
  return;
307
307
  }
308
308
  if (entry.fileName.endsWith("/")) {
@@ -315,8 +315,7 @@ function extractZip(buffer, targetDir) {
315
315
  mkdirSync(join(fullPath, ".."), { recursive: true });
316
316
  zipfile.openReadStream(entry, (err, readStream) => {
317
317
  if (err || !readStream) {
318
- zipfile.close(); // R2-08
319
- reject(err || new Error("Failed to read ZIP entry"));
318
+ safeReject(err || new Error("Failed to read ZIP entry"));
320
319
  return;
321
320
  }
322
321
  // INS-01 FIX: Track actual bytes written (not declared uncompressedSize)
@@ -326,12 +325,18 @@ function extractZip(buffer, targetDir) {
326
325
  fileBytes += chunk.length;
327
326
  totalExtracted += chunk.length;
328
327
  if (fileBytes > MAX_SINGLE_FILE_SIZE) {
329
- zipfile.close();
328
+ if (!aborted) {
329
+ aborted = true;
330
+ zipfile.close();
331
+ }
330
332
  callback(new Error(`Actual file size exceeds limit: ${entry.fileName}`));
331
333
  return;
332
334
  }
333
335
  if (totalExtracted > MAX_TOTAL_EXTRACT_SIZE) {
334
- zipfile.close();
336
+ if (!aborted) {
337
+ aborted = true;
338
+ zipfile.close();
339
+ }
335
340
  callback(new Error(`Total extracted size exceeds ${MAX_TOTAL_EXTRACT_SIZE / 1024 / 1024}MB — possible zip bomb`));
336
341
  return;
337
342
  }
@@ -345,22 +350,21 @@ function extractZip(buffer, targetDir) {
345
350
  try {
346
351
  if (lstatSync(fullPath).isSymbolicLink()) {
347
352
  unlinkSync(fullPath);
348
- zipfile.close();
349
- reject(new Error(`Post-extract symlink detected: ${entry.fileName}`));
353
+ safeReject(new Error(`Post-extract symlink detected: ${entry.fileName}`));
350
354
  return;
351
355
  }
352
356
  }
353
357
  catch { /* stat failed = file doesn't exist, ok to continue */ }
354
358
  zipfile.readEntry();
355
359
  });
356
- writeStream.on("error", (e) => { zipfile.close(); reject(e); });
357
- countingStream.on("error", (e) => { zipfile.close(); reject(e); });
358
- readStream.on("error", (e) => { zipfile.close(); reject(e); });
360
+ writeStream.on("error", (e) => { safeReject(e instanceof Error ? e : new Error(String(e))); });
361
+ countingStream.on("error", (e) => { safeReject(e instanceof Error ? e : new Error(String(e))); });
362
+ readStream.on("error", (e) => { safeReject(e instanceof Error ? e : new Error(String(e))); });
359
363
  });
360
364
  }
361
365
  });
362
366
  zipfile.on("end", () => resolve());
363
- zipfile.on("error", reject);
367
+ zipfile.on("error", (e) => safeReject(e instanceof Error ? e : new Error(String(e))));
364
368
  });
365
369
  });
366
370
  }
package/dist/lib/ui.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare const ui: {
7
7
  error: import("chalk").ChalkInstance;
8
8
  dim: import("chalk").ChalkInstance;
9
9
  muted: import("chalk").ChalkInstance;
10
- /** Animated gradient logo banner — block-art style */
10
+ /** Gradient logo banner — ███ block-art style (inspired by skills.sh) */
11
11
  banner(): void;
12
12
  /** Section header with gradient bar */
13
13
  header(text: string): void;
package/dist/lib/ui.js CHANGED
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * UI module — Dreamlogic CLI visual design system
3
- * Uses @clack/prompts, gradient-string, figlet, boxen, ora
3
+ * Uses @clack/prompts, gradient-string, boxen, ora
4
4
  */
5
5
  import chalk from "chalk";
6
6
  import ora from "ora";
7
7
  import gradientString from "gradient-string";
8
- import figlet from "figlet";
9
8
  import boxen from "boxen";
10
9
  import { CLI_NAME, CLI_AUTHOR, CLI_VERSION } from "../types.js";
11
10
  // ===== Brand palette =====
@@ -36,16 +35,56 @@ export const ui = {
36
35
  error,
37
36
  dim,
38
37
  muted,
39
- /** Animated gradient logo banner — block-art style */
38
+ /** Gradient logo banner — ███ block-art style (inspired by skills.sh) */
40
39
  banner() {
41
- const dream = figlet.textSync("DREAM", { font: "ANSI Shadow" });
42
- const logic = figlet.textSync("LOGIC", { font: "ANSI Shadow" });
43
- const fullLogo = dream + logic;
40
+ // Hardcoded Unicode box-drawing logo for pixel-perfect rendering across all terminals
41
+ const LOGO_LINES = [
42
+ "██████╗ ██████╗ ███████╗ █████╗ ███╗ ███╗",
43
+ "██╔══██╗██╔══██╗██╔════╝██╔══██╗████╗ ████║",
44
+ "██║ ██║██████╔╝█████╗ ███████║██╔████╔██║",
45
+ "██║ ██║██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║",
46
+ "██████╔╝██║ ██║███████╗██║ ██║██║ ╚═╝ ██║",
47
+ "╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝",
48
+ "",
49
+ "██╗ ██████╗ ██████╗ ██╗ ██████╗",
50
+ "██║ ██╔═══██╗██╔════╝ ██║██╔════╝",
51
+ "██║ ██║ ██║██║ ███╗██║██║ ",
52
+ "██║ ██║ ██║██║ ██║██║██║ ",
53
+ "███████╗╚██████╔╝╚██████╔╝██║╚██████╗",
54
+ "╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝",
55
+ ];
56
+ // ANSI 256-color gradient: purple → indigo → blue → cyan (6 per block)
57
+ const GRADIENT_COLORS = [
58
+ "\x1b[38;5;135m", // bright purple
59
+ "\x1b[38;5;134m",
60
+ "\x1b[38;5;99m", // indigo
61
+ "\x1b[38;5;98m",
62
+ "\x1b[38;5;63m", // blue
63
+ "\x1b[38;5;62m",
64
+ "\x1b[38;5;33m", // deep blue
65
+ "\x1b[38;5;38m", // teal
66
+ "\x1b[38;5;44m", // cyan
67
+ "\x1b[38;5;43m",
68
+ "\x1b[38;5;49m", // bright cyan
69
+ "\x1b[38;5;50m",
70
+ "\x1b[38;5;51m", // lightest cyan
71
+ ];
72
+ const RESET = "\x1b[0m";
44
73
  console.log();
45
- console.log(hasTruecolor ? brandGradient.multiline(fullLogo) : chalk.bold.magenta(fullLogo));
46
- console.log(muted(" ") +
74
+ if (hasTruecolor || chalk.level >= 2) {
75
+ LOGO_LINES.forEach((line, i) => {
76
+ const color = GRADIENT_COLORS[i % GRADIENT_COLORS.length];
77
+ console.log(` ${color}${line}${RESET}`);
78
+ });
79
+ }
80
+ else {
81
+ // Fallback: plain bold for 16-color terminals
82
+ LOGO_LINES.forEach(line => console.log(` ${chalk.bold.magenta(line)}`));
83
+ }
84
+ console.log();
85
+ console.log(muted(" ") +
47
86
  (hasTruecolor ? brandGradient(`${CLI_NAME} v${CLI_VERSION}`) : chalk.bold(`${CLI_NAME} v${CLI_VERSION}`)) +
48
- muted(" | ") +
87
+ muted(" ") +
49
88
  muted(CLI_AUTHOR));
50
89
  console.log();
51
90
  },
package/dist/types.d.ts CHANGED
@@ -34,6 +34,6 @@ export interface InstalledRegistry {
34
34
  export declare const DEFAULT_SERVER = "https://skill.dreamlogic-claw.com";
35
35
  export declare const DEFAULT_INSTALL_DIR_NAME = "dreamlogic-skills";
36
36
  export declare const CONFIG_DIR_NAME = ".dreamlogic";
37
- export declare const CLI_VERSION = "2.0.7";
37
+ export declare const CLI_VERSION = "2.0.8";
38
38
  export declare const CLI_NAME = "Dreamlogic CLI";
39
39
  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 = "2.0.7";
5
+ export const CLI_VERSION = "2.0.8";
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": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Dreamlogic AI Skill Manager — Install, update and manage AI agent skills",
5
5
  "type": "module",
6
6
  "bin": {