@dreamlogic-ai/cli 2.0.2 → 2.0.4

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.
@@ -10,14 +10,14 @@ export async function catalogCommand() {
10
10
  if (!apiKey)
11
11
  return;
12
12
  const client = new ApiClient(getServer(), apiKey);
13
- const spinner = ui.spinner("Fetching skill catalog...");
13
+ const spinner = ui.spinner("正在获取技能目录...");
14
14
  spinner.start();
15
15
  try {
16
16
  const skills = await client.listSkills();
17
- spinner.succeed(` ${skills.length} skill(s) available`);
17
+ spinner.succeed(` ${skills.length} 个技能可用`);
18
18
  console.log();
19
19
  if (skills.length === 0) {
20
- ui.info("No skills available yet. Check back soon!");
20
+ ui.info("暂无可用技能,请稍后再试!");
21
21
  return;
22
22
  }
23
23
  for (const s of skills) {
@@ -32,7 +32,7 @@ export async function catalogCommand() {
32
32
  }
33
33
  }
34
34
  catch (err) {
35
- spinner.fail(" Failed to fetch catalog");
35
+ spinner.fail(" 获取目录失败");
36
36
  ui.err(err.message);
37
37
  process.exitCode = 1;
38
38
  }
@@ -7,7 +7,7 @@ import { ui } from "../lib/ui.js";
7
7
  export function requireAuth() {
8
8
  const key = getApiKey();
9
9
  if (!key) {
10
- ui.err("Not logged in. Run: dreamlogic login");
10
+ ui.err("未登录,请先运行: dreamlogic login");
11
11
  process.exitCode = 1;
12
12
  return null;
13
13
  }
@@ -17,15 +17,15 @@ export async function installCommand(skillIds, opts) {
17
17
  return;
18
18
  const client = new ApiClient(getServer(), apiKey);
19
19
  // Fetch catalog
20
- const spinner = ui.spinner("Fetching available skills...");
20
+ const spinner = ui.spinner("正在获取可用技能...");
21
21
  spinner.start();
22
22
  let skills;
23
23
  try {
24
24
  skills = await client.listSkills();
25
- spinner.succeed(` ${skills.length} skill(s) available`);
25
+ spinner.succeed(` ${skills.length} 个技能可用`);
26
26
  }
27
27
  catch (err) {
28
- spinner.fail(" Failed to connect");
28
+ spinner.fail(" 连接失败");
29
29
  ui.err(err.message);
30
30
  process.exitCode = 1;
31
31
  return;
@@ -35,7 +35,7 @@ export async function installCommand(skillIds, opts) {
35
35
  let selectedIds;
36
36
  if (skillIds.length === 0) {
37
37
  if (skills.length === 0) {
38
- ui.info("No skills available yet.");
38
+ ui.info("暂无可用技能。");
39
39
  return;
40
40
  }
41
41
  const options = skills.map((s) => {
@@ -44,13 +44,13 @@ export async function installCommand(skillIds, opts) {
44
44
  const currentVer = installed[s.id]?.version;
45
45
  let hint = "";
46
46
  if (!isInstalled) {
47
- hint = chalk.green("NEW");
47
+ hint = chalk.green("全新");
48
48
  }
49
49
  else if (currentVer && s.latest_version && currentVer !== s.latest_version) {
50
- hint = chalk.yellow(`UPDATE: ${currentVer} → ${s.latest_version}`);
50
+ hint = chalk.yellow(`可更新: ${currentVer} → ${s.latest_version}`);
51
51
  }
52
52
  else {
53
- hint = chalk.dim("installed");
53
+ hint = chalk.dim("已安装");
54
54
  }
55
55
  return {
56
56
  label: `${s.name} (${ver})`,
@@ -60,17 +60,17 @@ export async function installCommand(skillIds, opts) {
60
60
  });
61
61
  console.log();
62
62
  const selected = await clack.multiselect({
63
- message: "Select skills to install:",
63
+ message: "选择要安装的技能(空格选择,回车确认):",
64
64
  options,
65
65
  required: true,
66
66
  });
67
67
  if (clack.isCancel(selected)) {
68
- clack.cancel("Installation cancelled.");
68
+ clack.cancel("安装已取消。");
69
69
  return;
70
70
  }
71
71
  selectedIds = selected;
72
72
  if (selectedIds.length === 0) {
73
- ui.info("Nothing selected.");
73
+ ui.info("未选择任何技能。");
74
74
  return;
75
75
  }
76
76
  }
@@ -78,8 +78,8 @@ export async function installCommand(skillIds, opts) {
78
78
  // Validate requested skill IDs
79
79
  const invalid = skillIds.filter((id) => !skills.find((s) => s.id === id));
80
80
  if (invalid.length > 0) {
81
- ui.err(`Unknown skill(s): ${invalid.join(", ")}`);
82
- ui.info("Available: " + skills.map((s) => s.id).join(", "));
81
+ ui.err(`未知技能: ${invalid.join(", ")}`);
82
+ ui.info("可用技能: " + skills.map((s) => s.id).join(", "));
83
83
  process.exitCode = 1;
84
84
  return;
85
85
  }
@@ -87,7 +87,7 @@ export async function installCommand(skillIds, opts) {
87
87
  }
88
88
  // R1-12: --from-file only works with single skill
89
89
  if (opts.fromFile && selectedIds.length > 1) {
90
- ui.err("--from-file can only be used with a single skill.");
90
+ ui.err("--from-file 仅支持单个技能安装。");
91
91
  process.exitCode = 1;
92
92
  return;
93
93
  }
@@ -102,11 +102,11 @@ export async function installCommand(skillIds, opts) {
102
102
  return `${chalk.bold(s.name)} ${chalk.dim(ver)} (${size})`;
103
103
  });
104
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 });
105
+ planLines.push(`${chalk.dim("安装目录:")} ${getInstallDir()}`);
106
+ ui.panel("安装计划", planLines.join("\n"));
107
+ const ok = await clack.confirm({ message: "确认安装?", initialValue: true });
108
108
  if (clack.isCancel(ok) || !ok) {
109
- clack.cancel("Cancelled.");
109
+ clack.cancel("已取消。");
110
110
  return;
111
111
  }
112
112
  }
@@ -115,26 +115,26 @@ export async function installCommand(skillIds, opts) {
115
115
  for (const id of selectedIds) {
116
116
  const s = skills.find((s) => s.id === id);
117
117
  if (!s) {
118
- ui.err(`Skill '${id}' no longer in catalog`);
118
+ ui.err(`技能 '${id}' 已不在目录中`);
119
119
  continue;
120
120
  }
121
121
  console.log();
122
- ui.header(`Installing ${s.name}`);
122
+ ui.header(`正在安装 ${s.name}`);
123
123
  try {
124
124
  const result = await installSkill(client, s.id, s.package_file || `${s.id}-${s.latest_version}.zip`, s.package_sha256, s.latest_version || "unknown", { fromFile: opts.fromFile });
125
- ui.ok(`Installed to ${result.path}`);
125
+ ui.ok(`已安装到 ${result.path}`);
126
126
  successCount++;
127
127
  }
128
128
  catch (err) {
129
- ui.err(`Failed to install ${s.name}: ${err.message}`);
129
+ ui.err(`安装 ${s.name} 失败: ${err.message}`);
130
130
  }
131
131
  }
132
132
  console.log();
133
133
  if (successCount === selectedIds.length) {
134
- ui.ok(`All ${successCount} skill(s) installed successfully!`);
134
+ ui.ok(`全部 ${successCount} 个技能安装成功!`);
135
135
  }
136
136
  else {
137
- ui.warning(`${successCount}/${selectedIds.length} installed. Check errors above.`);
137
+ ui.warning(`${successCount}/${selectedIds.length} 已安装,请检查上方错误信息。`);
138
138
  }
139
139
  // ── Agent Registration Step ──
140
140
  if (successCount > 0 && !opts.yes) {
@@ -161,7 +161,7 @@ export async function installCommand(skillIds, opts) {
161
161
  });
162
162
  }
163
163
  const agentChoice = await clack.multiselect({
164
- message: `Register skills with AI agents? (${totalAgents} agents available)`,
164
+ message: `注册技能到 AI Agent?(共 ${totalAgents} 个可用)`,
165
165
  options: agentOptions,
166
166
  required: false,
167
167
  });
@@ -177,7 +177,7 @@ export async function installCommand(skillIds, opts) {
177
177
  const created = results.filter(r => r.status === "created").length;
178
178
  const errors = results.filter(r => r.status === "error");
179
179
  if (created > 0) {
180
- ui.ok(`${id}: registered with ${created} agent location(s)`);
180
+ ui.ok(`${id}: 已注册到 ${created} Agent`);
181
181
  }
182
182
  for (const e of errors) {
183
183
  ui.warning(`${e.agent}: ${e.error}`);
@@ -189,7 +189,7 @@ export async function installCommand(skillIds, opts) {
189
189
  // Suggest MCP setup
190
190
  if (successCount > 0) {
191
191
  console.log();
192
- ui.info("To configure MCP for your AI agent, run:");
192
+ ui.info("如需配置 MCP 连接 AI Agent,请运行:");
193
193
  ui.line(chalk.cyan("dreamlogic setup-mcp"));
194
194
  }
195
195
  }
@@ -8,24 +8,24 @@ export function listCommand() {
8
8
  const installed = loadInstalled();
9
9
  const entries = Object.entries(installed);
10
10
  if (entries.length === 0) {
11
- ui.info("No skills installed. Run: dreamlogic install");
11
+ ui.info("暂无已安装的技能。请运行: dreamlogic install");
12
12
  return;
13
13
  }
14
14
  console.log();
15
- ui.line(`Install directory: ${getInstallDir()}`);
15
+ ui.line(`安装目录: ${getInstallDir()}`);
16
16
  console.log();
17
17
  for (const [id, info] of entries) {
18
18
  ui.line(` ${chalk.green("●")} ${chalk.bold(id)}`);
19
19
  ui.table([
20
- ["Version", info.version],
21
- ["Installed", new Date(info.installed_at).toLocaleString()],
22
- ["Path", info.path],
20
+ ["版本", info.version],
21
+ ["安装时间", new Date(info.installed_at).toLocaleString()],
22
+ ["路径", info.path],
23
23
  ["SHA256", info.sha256.slice(0, 16) + "..."],
24
24
  ]);
25
25
  if (info.previous_version) {
26
- ui.line(` ${ui.dim(`Previous: ${info.previous_version}`)}`);
26
+ ui.line(` ${ui.dim(`上一版本: ${info.previous_version}`)}`);
27
27
  }
28
28
  console.log();
29
29
  }
30
- ui.line(` Total: ${entries.length} skill(s)`);
30
+ ui.line(` 共计: ${entries.length} 个技能`);
31
31
  }
@@ -10,9 +10,9 @@ const KEY_RE = /^sk-(admin|user)-[a-f0-9]{16,}$/;
10
10
  export async function loginCommand(opts) {
11
11
  const existingKey = getApiKey();
12
12
  if (existingKey && !opts.key) {
13
- ui.info(`Already logged in as ${maskKey(existingKey)}`);
13
+ ui.info(`当前已登录: ${maskKey(existingKey)}`);
14
14
  const relogin = await clack.confirm({
15
- message: "Re-authenticate with a different key?",
15
+ message: "是否切换到其他 Key?",
16
16
  initialValue: false,
17
17
  });
18
18
  if (clack.isCancel(relogin) || !relogin)
@@ -21,57 +21,57 @@ export async function loginCommand(opts) {
21
21
  let apiKey = opts.key || "";
22
22
  // R1-04: Warn about key visibility in args/history
23
23
  if (opts.key) {
24
- ui.warning("Key passed via CLI argument it may be visible in shell history.");
25
- ui.info("Prefer: dreamlogic login (interactive) or DREAMLOGIC_API_KEY env var");
24
+ ui.warning("Key 通过命令行参数传入可能会记录在终端历史中");
25
+ ui.info("建议使用交互模式: dreamlogic login 或环境变量 DREAMLOGIC_API_KEY");
26
26
  }
27
27
  if (!apiKey) {
28
28
  const keyInput = await clack.password({
29
- message: "Enter your Dreamlogic API Key:",
29
+ message: "请输入你的 Dreamlogic API Key:",
30
30
  mask: "*",
31
31
  validate: (v) => {
32
32
  if (!v || !KEY_RE.test(v))
33
- return "Key must match format: sk-user-xxxx... or sk-admin-xxxx...";
33
+ return "Key 格式应为: sk-user-xxxx... sk-admin-xxxx...";
34
34
  },
35
35
  });
36
36
  if (clack.isCancel(keyInput)) {
37
- clack.cancel("Login cancelled.");
37
+ clack.cancel("登录已取消");
38
38
  return;
39
39
  }
40
40
  apiKey = keyInput;
41
41
  }
42
42
  if (!KEY_RE.test(apiKey)) {
43
- ui.err("Invalid key format. Expected: sk-user-xxxx... or sk-admin-xxxx...");
43
+ ui.err("Key 格式不正确,应为: sk-user-xxxx... sk-admin-xxxx...");
44
44
  process.exitCode = 1;
45
45
  return;
46
46
  }
47
47
  const server = getServer();
48
- const spinner = ui.spinner("Verifying key...");
48
+ const spinner = ui.spinner("正在验证 Key...");
49
49
  spinner.start();
50
50
  try {
51
51
  const client = new ApiClient(server, apiKey);
52
52
  const user = await client.me();
53
- spinner.succeed(` Welcome, ${ui.brand(user.name)}! (${user.role})`);
53
+ spinner.succeed(` 欢迎回来, ${ui.brand(user.name)}! (${user.role})`);
54
54
  const config = loadConfig() || { api_key: "", server: DEFAULT_SERVER, install_dir: getDefaultInstallDir() };
55
55
  config.api_key = apiKey;
56
56
  config.server = server;
57
57
  if (!config.install_dir)
58
58
  config.install_dir = getDefaultInstallDir();
59
59
  saveConfig(config);
60
- ui.ok("Configuration saved to ~/.dreamlogic/config.json");
60
+ ui.ok("配置已保存到 ~/.dreamlogic/config.json");
61
61
  }
62
62
  catch (err) {
63
- spinner.fail(" Authentication failed");
63
+ spinner.fail(" 认证失败");
64
64
  if (err instanceof ApiError) {
65
65
  if (err.status === 401) {
66
- ui.err("Invalid or disabled API key. Please check your key and try again.");
66
+ ui.err("无效或已禁用的 API Key,请检查后重试。");
67
67
  }
68
68
  else {
69
- ui.err(`Server error: ${err.message}`);
69
+ ui.err(`服务器错误: ${err.message}`);
70
70
  }
71
71
  }
72
72
  else {
73
- ui.err(`Connection failed: ${err.message}`);
74
- ui.info(`Server: ${server}`);
73
+ ui.err(`连接失败: ${err.message}`);
74
+ ui.info(`服务器地址: ${server}`);
75
75
  }
76
76
  process.exitCode = 1;
77
77
  }
@@ -7,13 +7,13 @@ import { ui } from "../lib/ui.js";
7
7
  export async function logoutCommand() {
8
8
  const key = getApiKey();
9
9
  if (!key) {
10
- ui.info("Not logged in.");
10
+ ui.info("当前未登录。");
11
11
  return;
12
12
  }
13
- ui.info(`Current key: ${maskKey(key)}`);
14
- const ok = await clack.confirm({ message: "Clear saved credentials?", initialValue: true });
13
+ ui.info(`当前 Key: ${maskKey(key)}`);
14
+ const ok = await clack.confirm({ message: "确定清除已保存的凭证?", initialValue: true });
15
15
  if (clack.isCancel(ok) || !ok)
16
16
  return;
17
17
  clearConfig();
18
- ui.ok("Credentials cleared.");
18
+ ui.ok("凭证已清除。");
19
19
  }
@@ -8,33 +8,33 @@ import { ui } from "../lib/ui.js";
8
8
  export async function rollbackCommand(skillId) {
9
9
  const installed = loadInstalled();
10
10
  if (!installed[skillId]) {
11
- ui.err(`Skill '${skillId}' is not installed.`);
11
+ ui.err(`技能 '${skillId}' 未安装。`);
12
12
  process.exitCode = 1;
13
13
  return;
14
14
  }
15
15
  const info = installed[skillId];
16
16
  if (!info.previous_version) {
17
- ui.err(`No previous version available for rollback.`);
18
- ui.info(`Current version: ${info.version}`);
17
+ ui.err(`没有可用的历史版本进行回滚。`);
18
+ ui.info(`当前版本: ${info.version}`);
19
19
  return;
20
20
  }
21
- ui.info(`Current: ${info.version}`);
22
- ui.info(`Rollback to: ${info.previous_version}`);
23
- const ok = await clack.confirm({ message: "Proceed with rollback?", initialValue: true });
21
+ ui.info(`当前版本: ${info.version}`);
22
+ ui.info(`回滚到: ${info.previous_version}`);
23
+ const ok = await clack.confirm({ message: "确认回滚?", initialValue: true });
24
24
  if (clack.isCancel(ok) || !ok) {
25
- clack.cancel("Cancelled.");
25
+ clack.cancel("已取消。");
26
26
  return;
27
27
  }
28
- const spinner = ui.spinner("Rolling back...");
28
+ const spinner = ui.spinner("正在回滚...");
29
29
  spinner.start();
30
30
  const success = rollbackSkill(skillId);
31
31
  if (success) {
32
- spinner.succeed(` Rolled back to previous version`);
33
- ui.ok("Rollback complete.");
32
+ spinner.succeed(` 已回滚到上一个版本`);
33
+ ui.ok("回滚完成。");
34
34
  }
35
35
  else {
36
- spinner.fail(" Rollback failed");
37
- ui.err("No backup found. Manual restore required.");
36
+ spinner.fail(" 回滚失败");
37
+ ui.err("未找到备份,需要手动恢复。");
38
38
  process.exitCode = 1;
39
39
  }
40
40
  }
@@ -54,7 +54,7 @@ function getAgents(server, key) {
54
54
  catch {
55
55
  // R2-07: Backup corrupted config instead of silently discarding
56
56
  const bakPath = claudeDesktopPath + ".bak";
57
- writeFileSync(bakPath, readFileSync(claudeDesktopPath));
57
+ writeFileSync(bakPath, readFileSync(claudeDesktopPath), { mode: 0o600 });
58
58
  ui.warning(`Existing config has invalid JSON — backed up to ${bakPath}`);
59
59
  }
60
60
  }
@@ -117,7 +117,7 @@ function getAgents(server, key) {
117
117
  catch {
118
118
  // R2-07: Backup corrupted config
119
119
  const bakPath = path + ".bak";
120
- writeFileSync(bakPath, readFileSync(path));
120
+ writeFileSync(bakPath, readFileSync(path), { mode: 0o600 });
121
121
  ui.warning(`Existing config has invalid JSON — backed up to ${bakPath}`);
122
122
  }
123
123
  }
@@ -140,31 +140,31 @@ export async function setupMcpCommand(opts) {
140
140
  const server = getServer();
141
141
  const agents = getAgents(server, apiKey);
142
142
  console.log();
143
- ui.header("MCP Agent Configuration");
143
+ ui.header("MCP Agent 配置");
144
144
  console.log();
145
145
  // Detect installed agents
146
146
  const detected = agents.filter((a) => a.detect());
147
147
  const notDetected = agents.filter((a) => !a.detect());
148
148
  if (detected.length > 0) {
149
- ui.ok(`Detected ${detected.length} agent(s):`);
149
+ ui.ok(`检测到 ${detected.length} 个 Agent:`);
150
150
  for (const a of detected)
151
151
  ui.line(` ${chalk.green("●")} ${a.name}`);
152
152
  }
153
153
  if (notDetected.length > 0) {
154
- ui.line(` ${ui.dim("Not detected: " + notDetected.map((a) => a.name).join(", "))}`);
154
+ ui.line(` ${ui.dim("未检测到: " + notDetected.map((a) => a.name).join(", "))}`);
155
155
  }
156
156
  if (detected.length === 0) {
157
- ui.warning("No supported agents detected.");
158
- ui.info("Supported agents: " + agents.map((a) => a.name).join(", "));
157
+ ui.warning("未检测到支持的 Agent。");
158
+ ui.info("支持的 Agent: " + agents.map((a) => a.name).join(", "));
159
159
  console.log();
160
- ui.info("Manual MCP configuration:");
161
- ui.line(` Server URL: ${server}/sse`);
160
+ ui.info("手动配置 MCP:");
161
+ ui.line(` 服务器 URL: ${server}/sse`);
162
162
  ui.line(` API Key: ${maskKey(apiKey)}`);
163
163
  return;
164
164
  }
165
- // R1-01: Warn about key in MCP URL (inherent to MCP SSE protocol)
166
- ui.warning("MCP config will contain your API key in the URL (required by MCP protocol).");
167
- ui.line(` Config files are written with restricted permissions (owner-only).`);
165
+ // R1-01: Warn about key in MCP config (inherent to MCP protocol)
166
+ ui.warning("MCP 配置将包含你的 API Key(MCP 协议需要认证)");
167
+ ui.line(` 配置文件将以安全权限写入(仅所有者可读写)。`);
168
168
  // Configure each detected agent
169
169
  for (const agent of detected) {
170
170
  console.log();
@@ -180,26 +180,26 @@ export async function setupMcpCommand(opts) {
180
180
  ui.line(` ${chalk.dim(maskConfigJson(config, apiKey).split("\n").join("\n "))}`);
181
181
  }
182
182
  if (opts.dryRun) {
183
- ui.info("(dry-runno changes made)");
183
+ ui.info("(预览模式未做任何更改)");
184
184
  continue;
185
185
  }
186
186
  const ok = await clack.confirm({
187
- message: `Apply configuration for ${agent.name}?`,
187
+ message: `为 ${agent.name} 应用配置?`,
188
188
  initialValue: true,
189
189
  });
190
190
  if (clack.isCancel(ok) || !ok) {
191
- ui.info("Skipped.");
191
+ ui.info("已跳过。");
192
192
  continue;
193
193
  }
194
194
  try {
195
195
  agent.apply(config);
196
- ui.ok(`${agent.name} configured`);
196
+ ui.ok(`${agent.name} 已配置`);
197
197
  if (agent.name === "Claude Desktop") {
198
- ui.info("Restart Claude Desktop to apply changes.");
198
+ ui.info("请重启 Claude Desktop 以生效。");
199
199
  }
200
200
  }
201
201
  catch (err) {
202
- ui.err(`Failed: ${err.message}`);
202
+ ui.err(`配置失败: ${err.message}`);
203
203
  }
204
204
  }
205
205
  ui.goodbye();
@@ -10,14 +10,14 @@ export async function statusCommand() {
10
10
  const config = loadConfig();
11
11
  const installed = loadInstalled();
12
12
  console.log();
13
- ui.header("Dreamlogic CLI Status");
13
+ ui.header("Dreamlogic CLI 状态总览");
14
14
  console.log();
15
15
  // Config
16
16
  ui.table([
17
- ["CLI Version", CLI_VERSION],
18
- ["Server", config?.server || "(not configured)"],
19
- ["API Key", config?.api_key ? maskKey(config.api_key) : chalk.red("(not set)")],
20
- ["Install Dir", getInstallDir()],
17
+ ["CLI 版本", CLI_VERSION],
18
+ ["服务器", config?.server || "(未配置)"],
19
+ ["API Key", config?.api_key ? maskKey(config.api_key) : chalk.red("(未设置)")],
20
+ ["安装目录", getInstallDir()],
21
21
  ]);
22
22
  // Server health — R2-05: use redirect:"error" like all other fetches
23
23
  const server = getServer();
@@ -33,16 +33,16 @@ export async function statusCommand() {
33
33
  throw new Error("Invalid health response");
34
34
  }
35
35
  console.log();
36
- ui.ok(`Server: ${data.status} (v${data.version})`);
36
+ ui.ok(`服务器: ${data.status} (v${data.version})`);
37
37
  }
38
38
  catch {
39
39
  console.log();
40
- ui.warning("Server unreachable");
40
+ ui.warning("服务器不可达");
41
41
  }
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(`已安装: ${entries.length} 个技能`);
46
46
  if (entries.length > 0) {
47
47
  // Check for updates if authenticated
48
48
  const apiKey = getApiKey();
@@ -15,11 +15,11 @@ export async function updateCommand(opts) {
15
15
  const installed = loadInstalled();
16
16
  const installedIds = Object.keys(installed);
17
17
  if (installedIds.length === 0) {
18
- ui.info("No skills installed. Run: dreamlogic install");
18
+ ui.info("暂无已安装的技能。请运行: dreamlogic install");
19
19
  return;
20
20
  }
21
21
  const client = new ApiClient(getServer(), apiKey);
22
- const spinner = ui.spinner("Checking for updates...");
22
+ const spinner = ui.spinner("正在检查更新...");
23
23
  spinner.start();
24
24
  let skills;
25
25
  try {
@@ -27,7 +27,7 @@ export async function updateCommand(opts) {
27
27
  spinner.stop();
28
28
  }
29
29
  catch (err) {
30
- spinner.fail(" Failed to check updates");
30
+ spinner.fail(" 检查更新失败");
31
31
  ui.err(err.message);
32
32
  process.exitCode = 1;
33
33
  return;
@@ -39,16 +39,16 @@ 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} — 服务器上未找到(已移除?)`);
43
43
  continue;
44
44
  }
45
45
  if (!remote.latest_version) {
46
- ui.line(`${ui.muted("–")} ${remote.name} — no version info`);
46
+ ui.line(`${ui.muted("–")} ${remote.name} — 无版本信息`);
47
47
  continue;
48
48
  }
49
49
  const normalizeVer = (v) => v.replace(/^v/, "");
50
50
  if (normalizeVer(local.version) === normalizeVer(remote.latest_version)) {
51
- ui.line(`${chalk.green("✓")} ${remote.name} ${local.version} — up to date`);
51
+ ui.line(`${chalk.green("✓")} ${remote.name} ${local.version} — 已是最新`);
52
52
  }
53
53
  else {
54
54
  ui.line(`${chalk.yellow("⬆")} ${chalk.bold(remote.name)} ${local.version} → ${chalk.green(remote.latest_version)}`);
@@ -65,11 +65,11 @@ export async function updateCommand(opts) {
65
65
  }
66
66
  if (updates.length === 0) {
67
67
  console.log();
68
- ui.ok("All skills are up to date!");
68
+ ui.ok("所有技能均已是最新版!");
69
69
  return;
70
70
  }
71
71
  console.log();
72
- ui.info(`${updates.length} update(s) available`);
72
+ ui.info(`发现 ${updates.length} 个可更新`);
73
73
  // Select which to update
74
74
  let selectedIds;
75
75
  if (opts.yes) {
@@ -77,14 +77,14 @@ export async function updateCommand(opts) {
77
77
  }
78
78
  else if (updates.length === 1) {
79
79
  const ok = await clack.confirm({
80
- message: `Update ${updates[0].name} to ${updates[0].latestVersion}?`,
80
+ message: `更新 ${updates[0].name} ${updates[0].latestVersion}?`,
81
81
  initialValue: true,
82
82
  });
83
83
  selectedIds = (clack.isCancel(ok) || !ok) ? [] : [updates[0].id];
84
84
  }
85
85
  else {
86
86
  const selected = await clack.multiselect({
87
- message: "Select updates to apply:",
87
+ message: "选择要更新的技能:",
88
88
  options: updates.map((u) => ({
89
89
  label: `${u.name} ${u.currentVersion} → ${u.latestVersion}${u.size ? ` (${ui.fileSize(u.size)})` : ""}`,
90
90
  value: u.id,
@@ -92,13 +92,13 @@ export async function updateCommand(opts) {
92
92
  required: true,
93
93
  });
94
94
  if (clack.isCancel(selected)) {
95
- clack.cancel("Cancelled.");
95
+ clack.cancel("已取消。");
96
96
  return;
97
97
  }
98
98
  selectedIds = selected;
99
99
  }
100
100
  if (selectedIds.length === 0) {
101
- ui.info("No updates selected.");
101
+ ui.info("未选择任何更新。");
102
102
  return;
103
103
  }
104
104
  // Apply updates
@@ -108,23 +108,23 @@ export async function updateCommand(opts) {
108
108
  if (!u)
109
109
  continue;
110
110
  console.log();
111
- ui.header(`Updating ${u.name}`);
111
+ ui.header(`正在更新 ${u.name}`);
112
112
  ui.line(`${u.currentVersion} → ${chalk.green(u.latestVersion)}`);
113
113
  try {
114
114
  await installSkill(client, u.id, u.packageFile, u.sha256, u.latestVersion);
115
- ui.ok(`Updated to ${u.latestVersion}`);
115
+ ui.ok(`已更新到 ${u.latestVersion}`);
116
116
  successCount++;
117
117
  }
118
118
  catch (err) {
119
- ui.err(`Failed: ${err.message}`);
120
- ui.info(`Rollback with: dreamlogic rollback ${u.id}`);
119
+ ui.err(`更新失败: ${err.message}`);
120
+ ui.info(`可回滚: dreamlogic rollback ${u.id}`);
121
121
  }
122
122
  }
123
123
  console.log();
124
124
  if (successCount === selectedIds.length) {
125
- ui.ok(`All ${successCount} update(s) applied!`);
125
+ ui.ok(`全部 ${successCount} 个更新已应用!`);
126
126
  }
127
127
  else {
128
- ui.warning(`${successCount}/${selectedIds.length} updated. Check errors above.`);
128
+ ui.warning(`${successCount}/${selectedIds.length} 已更新,请检查上方错误信息。`);
129
129
  }
130
130
  }
package/dist/index.js CHANGED
@@ -23,65 +23,65 @@ import { setupMcpCommand } from "./commands/setup-mcp.js";
23
23
  const program = new Command();
24
24
  program
25
25
  .name("dreamlogic")
26
- .description(`${CLI_NAME} — AI Skill Manager\n${CLI_AUTHOR}`)
26
+ .description(`${CLI_NAME} — AI 技能管理器\n${CLI_AUTHOR}`)
27
27
  .version(CLI_VERSION, "-v, --version");
28
28
  // ===== login =====
29
29
  program
30
30
  .command("login")
31
- .description("Authenticate with your Dreamlogic API key")
32
- .option("-k, --key <key>", "API key (or enter interactively)")
31
+ .description("登录 验证 API Key")
32
+ .option("-k, --key <key>", "API Key(留空则交互输入)")
33
33
  .action((opts) => loginCommand(opts));
34
34
  // ===== logout =====
35
35
  program
36
36
  .command("logout")
37
- .description("Clear saved credentials")
37
+ .description("登出 清除已保存的凭证")
38
38
  .action(() => logoutCommand());
39
39
  // ===== catalog =====
40
40
  program
41
41
  .command("catalog")
42
- .description("List available skills from server")
42
+ .description("浏览 查看服务器上可用的技能")
43
43
  .action(() => catalogCommand());
44
44
  // ===== install =====
45
45
  program
46
46
  .command("install [skills...]")
47
- .description("Install skills (interactive if no skill specified)")
48
- .option("-y, --yes", "Skip confirmation prompts")
49
- .option("--from-file <path>", "Install from local ZIP file (D-10)")
47
+ .description("安装 下载并安装技能(留空则交互选择)")
48
+ .option("-y, --yes", "跳过确认提示")
49
+ .option("--from-file <path>", "从本地 ZIP 文件安装")
50
50
  .action((skills, opts) => installCommand(skills, opts));
51
51
  // ===== update =====
52
52
  program
53
53
  .command("update")
54
- .description("Check for and apply skill updates")
55
- .option("-y, --yes", "Auto-apply all updates")
54
+ .description("更新 检查并应用技能升级")
55
+ .option("-y, --yes", "自动应用全部更新")
56
56
  .action((opts) => updateCommand(opts));
57
57
  // ===== list =====
58
58
  program
59
59
  .command("list")
60
60
  .alias("ls")
61
- .description("List locally installed skills")
61
+ .description("列表 查看已安装的技能")
62
62
  .action(() => listCommand());
63
63
  // ===== rollback =====
64
64
  program
65
65
  .command("rollback <skill>")
66
- .description("Rollback a skill to its previous version (D-13)")
66
+ .description("回滚 恢复技能到上一个版本")
67
67
  .action((skill) => rollbackCommand(skill));
68
68
  // ===== status =====
69
69
  program
70
70
  .command("status")
71
- .description("Show full status overview")
71
+ .description("状态 查看完整状态概览")
72
72
  .action(() => statusCommand());
73
73
  // ===== setup-mcp =====
74
74
  program
75
75
  .command("setup-mcp")
76
- .description("Auto-configure MCP for AI agents")
77
- .option("--dry-run", "Show what would be configured without applying")
76
+ .description("配置 MCP 自动配置 AI Agent 的 MCP 连接")
77
+ .option("--dry-run", "仅展示配置内容,不实际写入")
78
78
  .action((opts) => setupMcpCommand(opts));
79
79
  // ===== Interactive mode (no command) =====
80
80
  program.action(async () => {
81
81
  ui.banner();
82
82
  const isLoggedIn = !!getApiKey();
83
83
  if (!isLoggedIn) {
84
- ui.info("Welcome! Let's get you set up.");
84
+ ui.info("欢迎使用 Dreamlogic!让我们先完成登录。");
85
85
  console.log();
86
86
  await loginCommand({});
87
87
  if (!getApiKey())
@@ -91,15 +91,15 @@ program.action(async () => {
91
91
  // Interactive menu with @clack/prompts
92
92
  while (true) {
93
93
  const action = await clack.select({
94
- message: "What would you like to do?",
94
+ message: "请选择操作:",
95
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" },
96
+ { value: "install", label: "📦 安装技能", hint: "从服务器下载新技能" },
97
+ { value: "update", label: "⬆️ 检查更新", hint: "升级已安装的技能" },
98
+ { value: "list", label: "📋 已安装列表", hint: "查看本地安装情况" },
99
+ { value: "setup-mcp", label: "🔗 配置 MCP", hint: "自动配置 Claude/Cursor/Cline" },
100
+ { value: "status", label: "📊 状态概览", hint: "完整系统状态" },
101
+ { value: "catalog", label: "🔍 浏览技能目录", hint: "查看全部可用技能" },
102
+ { value: "exit", label: "👋 退出" },
103
103
  ],
104
104
  });
105
105
  if (clack.isCancel(action)) {
@@ -136,8 +136,8 @@ program.exitOverride();
136
136
  async function main() {
137
137
  // BUG-2: Warn if TLS verification is disabled
138
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=");
139
+ ui.warning("检测到 NODE_TLS_REJECT_UNAUTHORIZED=0 — TLS 证书验证已禁用");
140
+ ui.warning("存在安全风险,建议移除:set NODE_TLS_REJECT_UNAUTHORIZED=");
141
141
  }
142
142
  try {
143
143
  await program.parseAsync(process.argv);
@@ -156,7 +156,7 @@ async function main() {
156
156
  clack.cancel("Cancelled.");
157
157
  return;
158
158
  }
159
- ui.err(`Unexpected error: ${err.message}`);
159
+ ui.err(`意外错误: ${err.message}`);
160
160
  process.exitCode = 1;
161
161
  }
162
162
  }
@@ -39,13 +39,20 @@ const AGENT_CONFIGS = [
39
39
  { name: "Aider", skillsDir: ".aider/skills", group: "additional", detect: ".aider" },
40
40
  { name: "Plandex", skillsDir: ".plandex/skills", group: "additional", detect: ".plandex" },
41
41
  ];
42
+ // R4-FIX: Shared safe skill ID pattern
43
+ const SAFE_SKILL_ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
42
44
  /**
43
45
  * Resolve the skills directory for an agent (handles env overrides + home expansion)
44
46
  */
45
47
  function resolveAgentDir(agent) {
46
48
  const home = homedir();
47
49
  if (agent.envOverride && process.env[agent.envOverride]) {
48
- return join(process.env[agent.envOverride], "skills");
50
+ const dir = join(process.env[agent.envOverride], "skills");
51
+ // R4-FIX: Prevent env override pointing outside home dir
52
+ if (!resolve(dir).startsWith(resolve(home))) {
53
+ throw new Error(`Agent dir must be under home directory: ${dir}`);
54
+ }
55
+ return dir;
49
56
  }
50
57
  return join(home, agent.skillsDir);
51
58
  }
@@ -141,6 +148,9 @@ export function registerSkillWithAgents(skillId, skillPath, agents) {
141
148
  * Unregister a skill from all agents (remove symlinks only)
142
149
  */
143
150
  export function unregisterSkillFromAgents(skillId) {
151
+ // R4-FIX: Validate skillId to prevent path traversal (same as registerSkillWithAgents)
152
+ if (!SAFE_SKILL_ID.test(skillId))
153
+ return 0;
144
154
  const home = homedir();
145
155
  let removed = 0;
146
156
  // Check all known agent dirs
@@ -163,6 +173,9 @@ export function unregisterSkillFromAgents(skillId) {
163
173
  * List which agents have a specific skill registered
164
174
  */
165
175
  export function getSkillAgentStatus(skillId) {
176
+ // R4-FIX: Validate skillId to prevent path traversal info leak
177
+ if (!SAFE_SKILL_ID.test(skillId))
178
+ return [];
166
179
  const results = [];
167
180
  const seenDirs = new Set();
168
181
  for (const agent of AGENT_CONFIGS) {
@@ -3,7 +3,7 @@
3
3
  * Key stored with file permissions 600 (owner-only)
4
4
  */
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, statSync } from "fs";
6
- import { join } from "path";
6
+ import { join, resolve } from "path";
7
7
  import { homedir } from "os";
8
8
  import { CONFIG_DIR_NAME, DEFAULT_SERVER, DEFAULT_INSTALL_DIR_NAME, } from "../types.js";
9
9
  /** CFG-01 FIX: Recursively strip prototype pollution keys from parsed JSON */
@@ -21,8 +21,8 @@ function sanitize(obj) {
21
21
  }
22
22
  return obj;
23
23
  }
24
- // R1-06: Key format validation applied everywhere
25
- const KEY_RE = /^sk-(admin|user)-[a-f0-9]{16,}$/;
24
+ // R1-06: Key format validation applied everywhere — R4-FIX: add upper bound
25
+ const KEY_RE = /^sk-(admin|user)-[a-f0-9]{16,128}$/;
26
26
  function getConfigDir() {
27
27
  return join(homedir(), CONFIG_DIR_NAME);
28
28
  }
@@ -37,9 +37,8 @@ export function getDefaultInstallDir() {
37
37
  }
38
38
  export function ensureConfigDir() {
39
39
  const dir = getConfigDir();
40
- if (!existsSync(dir)) {
41
- mkdirSync(dir, { recursive: true, mode: 0o700 });
42
- }
40
+ // R4-FIX: mkdirSync with recursive handles existence; no TOCTOU
41
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
43
42
  }
44
43
  export function loadConfig() {
45
44
  const path = getConfigPath();
@@ -86,10 +85,14 @@ export function getServer() {
86
85
  return url;
87
86
  }
88
87
  export function getInstallDir() {
89
- if (process.env.DREAMLOGIC_INSTALL_DIR)
90
- return process.env.DREAMLOGIC_INSTALL_DIR;
91
- const config = loadConfig();
92
- return config?.install_dir ?? getDefaultInstallDir();
88
+ const dir = process.env.DREAMLOGIC_INSTALL_DIR || loadConfig()?.install_dir || getDefaultInstallDir();
89
+ const resolved = resolve(dir);
90
+ // R4-FIX: Block system-critical paths
91
+ const BLOCKED = ["/usr", "/bin", "/sbin", "/etc", "/var", "/System", "/Library", "/Windows", "/Program Files"];
92
+ if (BLOCKED.some(p => resolved.toLowerCase().startsWith(p.toLowerCase()))) {
93
+ throw new Error(`安装目录不能是系统关键路径: ${resolved}`);
94
+ }
95
+ return resolved;
93
96
  }
94
97
  export function loadInstalled() {
95
98
  const path = getInstalledPath();
@@ -39,11 +39,11 @@ export async function installSkill(client, skillId, packageFile, expectedSha256,
39
39
  const { createHash } = await import("crypto");
40
40
  buffer = readFileSync(opts.fromFile);
41
41
  actualSha256 = createHash("sha256").update(buffer).digest("hex");
42
- ui.info(`Loaded from file: ${opts.fromFile}`);
42
+ ui.info(`从本地文件加载: ${opts.fromFile}`);
43
43
  }
44
44
  else {
45
45
  // Download with progress
46
- const spinner = ui.spinner(`Downloading ${skillId} ${expectedVersion}...`);
46
+ const spinner = ui.spinner(`正在下载 ${skillId} ${expectedVersion}...`);
47
47
  spinner.start();
48
48
  try {
49
49
  const result = await client.downloadPackage(packageFile, (dl, total) => {
@@ -53,25 +53,26 @@ export async function installSkill(client, skillId, packageFile, expectedSha256,
53
53
  });
54
54
  buffer = result.buffer;
55
55
  actualSha256 = result.sha256;
56
- spinner.succeed(` Downloaded ${ui.fileSize(buffer.length)}`);
56
+ spinner.succeed(` 已下载 ${ui.fileSize(buffer.length)}`);
57
57
  }
58
58
  catch (err) {
59
- spinner.fail(` Download failed`);
59
+ spinner.fail(` 下载失败`);
60
60
  throw err;
61
61
  }
62
62
  }
63
- // SHA256 verification
63
+ // SHA256 verification — R4-FIX: no-hash is a security event, not just a warning
64
64
  if (expectedSha256) {
65
65
  if (actualSha256 !== expectedSha256) {
66
- ui.err("SHA256 mismatch! Package may be corrupted or tampered.");
67
- ui.line(` Expected: ${expectedSha256}`);
68
- ui.line(` Actual: ${actualSha256}`);
66
+ ui.err("SHA256 校验失败!文件可能已损坏或被篡改。");
67
+ ui.line(` 期望值: ${expectedSha256}`);
68
+ ui.line(` 实际值: ${actualSha256}`);
69
69
  throw new Error("SHA256 verification failed");
70
70
  }
71
- ui.ok("SHA256 verified");
71
+ ui.ok("SHA256 校验通过");
72
72
  }
73
73
  else {
74
- ui.warning("No SHA256 hash from server skipping verification");
74
+ ui.warning("⚠️ 服务器未提供 SHA256 校验值无法验证包完整性");
75
+ ui.warning(" 这可能意味着包被篡改或服务器配置错误");
75
76
  }
76
77
  // Extract to staging directory (D-05: never extract directly to target)
77
78
  // R1-10 + R2-01/R2-02: Signal cleanup with proper listener management
@@ -86,15 +87,15 @@ export async function installSkill(client, skillId, packageFile, expectedSha256,
86
87
  process.on("SIGTERM", cleanupAndExit);
87
88
  // R2-01: try/finally ensures listeners are always removed
88
89
  try {
89
- const extractSpinner = ui.spinner("Extracting...");
90
+ const extractSpinner = ui.spinner("正在解压...");
90
91
  extractSpinner.start();
91
92
  try {
92
93
  mkdirSync(stagingDir, { recursive: true });
93
94
  await extractZip(buffer, stagingDir);
94
- extractSpinner.succeed(" Extracted");
95
+ extractSpinner.succeed(" 解压完成");
95
96
  }
96
97
  catch (err) {
97
- extractSpinner.fail(" Extraction failed");
98
+ extractSpinner.fail(" 解压失败");
98
99
  rmSync(stagingDir, { recursive: true, force: true });
99
100
  throw err;
100
101
  }
@@ -102,7 +103,7 @@ export async function installSkill(client, skillId, packageFile, expectedSha256,
102
103
  try {
103
104
  if (existsSync(skillDir)) {
104
105
  renameSync(skillDir, backupDir);
105
- ui.info(`Backed up previous version`);
106
+ ui.info(`已备份上一版本`);
106
107
  }
107
108
  const extractedRoot = findExtractedRoot(stagingDir);
108
109
  renameSync(extractedRoot, skillDir);
@@ -147,7 +148,7 @@ export async function installSkill(client, skillId, packageFile, expectedSha256,
147
148
  }
148
149
  catch { /* best-effort cleanup */ }
149
150
  if (existsSync(backupDir)) {
150
- ui.info(`Rollback available: dreamlogic rollback ${skillId}`);
151
+ ui.info(`可回滚: dreamlogic rollback ${skillId}`);
151
152
  }
152
153
  return { path: skillDir, version: expectedVersion };
153
154
  }
@@ -195,6 +196,10 @@ export function rollbackSkill(skillId) {
195
196
  */
196
197
  function findExtractedRoot(stagingDir) {
197
198
  const entries = readdirSync(stagingDir);
199
+ // R4-FIX: Reject empty packages
200
+ if (entries.length === 0) {
201
+ throw new Error("解压后的包为空,不包含任何文件");
202
+ }
198
203
  // If only one directory entry, use that as root
199
204
  if (entries.length === 1) {
200
205
  const single = join(stagingDir, entries[0]);
package/dist/lib/ui.d.ts CHANGED
@@ -27,9 +27,9 @@ export declare const ui: {
27
27
  dimText(msg: string): void;
28
28
  /** Create an ora spinner with brand styling */
29
29
  spinner(text: string): Ora;
30
- /** Format file size */
30
+ /** Format file size — R4-FIX: handle NaN/negative */
31
31
  fileSize(bytes: number): string;
32
- /** Format a skill for display (rich) */
32
+ /** Format a skill for display (rich) — R4-FIX: sanitize server-provided strings */
33
33
  skillLine(s: {
34
34
  id: string;
35
35
  name: string;
package/dist/lib/ui.js CHANGED
@@ -21,6 +21,13 @@ const muted = chalk.hex("#6B7280");
21
21
  const hasTruecolor = chalk.level >= 3;
22
22
  // ===== Gradient helper =====
23
23
  const brandGradient = gradientString(BRAND_GRADIENT);
24
+ // R4-FIX: Strip ANSI escape sequences and control chars from untrusted strings
25
+ function stripAnsi(str) {
26
+ return str
27
+ .replace(/[\x00-\x1F\x7F-\x9F]/g, "") // control chars
28
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "") // CSI sequences
29
+ .replace(/\x1b\][^\x07]*\x07/g, ""); // OSC sequences
30
+ }
24
31
  export const ui = {
25
32
  brand,
26
33
  accent,
@@ -92,21 +99,23 @@ export const ui = {
92
99
  spinner: "dots",
93
100
  });
94
101
  },
95
- /** Format file size */
102
+ /** Format file size — R4-FIX: handle NaN/negative */
96
103
  fileSize(bytes) {
104
+ if (!Number.isFinite(bytes) || bytes < 0)
105
+ return "unknown";
97
106
  if (bytes < 1024)
98
107
  return `${bytes} B`;
99
108
  if (bytes < 1024 * 1024)
100
109
  return `${(bytes / 1024).toFixed(1)} KB`;
101
110
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
102
111
  },
103
- /** Format a skill for display (rich) */
112
+ /** Format a skill for display (rich) — R4-FIX: sanitize server-provided strings */
104
113
  skillLine(s) {
105
- const name = chalk.bold.white(s.name);
106
- const ver = s.version ? muted(` v${s.version}`) : "";
114
+ const name = chalk.bold.white(stripAnsi(s.name));
115
+ const ver = s.version ? muted(` v${stripAnsi(s.version)}`) : "";
107
116
  const size = s.size ? muted(` (${ui.fileSize(s.size)})`) : "";
108
- const desc = muted(s.description);
109
- const tag = s.tag ? ` ${s.tag}` : "";
117
+ const desc = muted(stripAnsi(s.description));
118
+ const tag = s.tag ? ` ${stripAnsi(s.tag)}` : "";
110
119
  return `${name}${ver}${size}\n ${desc}${tag}`;
111
120
  },
112
121
  /** Print a key-value table with alignment */
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 = "2.0.2";
36
+ export declare const CLI_VERSION = "2.0.4";
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 = "2.0.2";
5
+ export const CLI_VERSION = "2.0.4";
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.2",
3
+ "version": "2.0.4",
4
4
  "description": "Dreamlogic AI Skill Manager — Install, update and manage AI agent skills",
5
5
  "type": "module",
6
6
  "bin": {