@dreamlogic-ai/cli 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/install.js +52 -0
- package/dist/commands/setup-mcp.js +6 -4
- package/dist/lib/agents.d.ts +40 -0
- package/dist/lib/agents.js +178 -0
- package/dist/lib/config.js +17 -2
- package/dist/lib/installer.js +40 -9
- package/dist/lib/ui.d.ts +1 -1
- package/dist/lib/ui.js +6 -5
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +1 -1
package/dist/commands/install.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* Supports: interactive multi-select, single skill by name, --from-file
|
|
4
4
|
*/
|
|
5
5
|
import * as clack from "@clack/prompts";
|
|
6
|
+
import { join } from "path";
|
|
6
7
|
import { ApiClient } from "../lib/api-client.js";
|
|
7
8
|
import { getServer, getInstallDir, loadInstalled } from "../lib/config.js";
|
|
8
9
|
import { installSkill } from "../lib/installer.js";
|
|
10
|
+
import { detectAgents, registerSkillWithAgents } from "../lib/agents.js";
|
|
9
11
|
import { ui } from "../lib/ui.js";
|
|
10
12
|
import { requireAuth } from "./helpers.js";
|
|
11
13
|
import chalk from "chalk";
|
|
@@ -134,6 +136,56 @@ export async function installCommand(skillIds, opts) {
|
|
|
134
136
|
else {
|
|
135
137
|
ui.warning(`${successCount}/${selectedIds.length} installed. Check errors above.`);
|
|
136
138
|
}
|
|
139
|
+
// ── Agent Registration Step ──
|
|
140
|
+
if (successCount > 0 && !opts.yes) {
|
|
141
|
+
console.log();
|
|
142
|
+
const { universal, detected } = detectAgents();
|
|
143
|
+
const totalAgents = universal.length + detected.length;
|
|
144
|
+
if (totalAgents > 0) {
|
|
145
|
+
// Build options grouped by category
|
|
146
|
+
const agentOptions = [];
|
|
147
|
+
// Universal agents header
|
|
148
|
+
for (const agent of universal) {
|
|
149
|
+
agentOptions.push({
|
|
150
|
+
label: agent.name,
|
|
151
|
+
value: `u:${agent.name}`,
|
|
152
|
+
hint: "universal (.agents/skills)",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// Detected additional agents
|
|
156
|
+
for (const agent of detected) {
|
|
157
|
+
agentOptions.push({
|
|
158
|
+
label: agent.name,
|
|
159
|
+
value: `a:${agent.name}`,
|
|
160
|
+
hint: `detected (${agent.skillsDir})`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const agentChoice = await clack.multiselect({
|
|
164
|
+
message: `Register skills with AI agents? (${totalAgents} agents available)`,
|
|
165
|
+
options: agentOptions,
|
|
166
|
+
required: false,
|
|
167
|
+
});
|
|
168
|
+
if (!clack.isCancel(agentChoice) && agentChoice.length > 0) {
|
|
169
|
+
const chosenNames = agentChoice.map(v => v.replace(/^[ua]:/, ""));
|
|
170
|
+
const allAgents = [...universal, ...detected];
|
|
171
|
+
const chosenAgents = allAgents.filter(a => chosenNames.includes(a.name));
|
|
172
|
+
// Register each installed skill with chosen agents
|
|
173
|
+
const installDir = getInstallDir();
|
|
174
|
+
for (const id of selectedIds) {
|
|
175
|
+
const skillPath = join(installDir, id);
|
|
176
|
+
const results = registerSkillWithAgents(id, skillPath, chosenAgents);
|
|
177
|
+
const created = results.filter(r => r.status === "created").length;
|
|
178
|
+
const errors = results.filter(r => r.status === "error");
|
|
179
|
+
if (created > 0) {
|
|
180
|
+
ui.ok(`${id}: registered with ${created} agent location(s)`);
|
|
181
|
+
}
|
|
182
|
+
for (const e of errors) {
|
|
183
|
+
ui.warning(`${e.agent}: ${e.error}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
137
189
|
// Suggest MCP setup
|
|
138
190
|
if (successCount > 0) {
|
|
139
191
|
console.log();
|
|
@@ -40,7 +40,8 @@ function getAgents(server, key) {
|
|
|
40
40
|
generate: () => ({
|
|
41
41
|
mcpServers: {
|
|
42
42
|
"dreamlogic-skills": {
|
|
43
|
-
url: `${server}/sse
|
|
43
|
+
url: `${server}/sse`,
|
|
44
|
+
headers: { "Authorization": `Bearer ${key}` },
|
|
44
45
|
},
|
|
45
46
|
},
|
|
46
47
|
}),
|
|
@@ -87,7 +88,7 @@ function getAgents(server, key) {
|
|
|
87
88
|
},
|
|
88
89
|
generate: () => ({
|
|
89
90
|
// R1-02: Store args as array, not interpolated string
|
|
90
|
-
args: ["mcp", "add", "dreamlogic-skills", "--url", `${server}/sse
|
|
91
|
+
args: ["mcp", "add", "dreamlogic-skills", "--url", `${server}/sse`, "--header", `Authorization: Bearer ${key}`],
|
|
91
92
|
}),
|
|
92
93
|
apply: (config) => {
|
|
93
94
|
// R1-02: execFileSync with arg array — no shell injection possible
|
|
@@ -101,7 +102,8 @@ function getAgents(server, key) {
|
|
|
101
102
|
generate: () => ({
|
|
102
103
|
mcpServers: {
|
|
103
104
|
"dreamlogic-skills": {
|
|
104
|
-
url: `${server}/sse
|
|
105
|
+
url: `${server}/sse`,
|
|
106
|
+
headers: { "Authorization": `Bearer ${key}` },
|
|
105
107
|
},
|
|
106
108
|
},
|
|
107
109
|
}),
|
|
@@ -170,7 +172,7 @@ export async function setupMcpCommand(opts) {
|
|
|
170
172
|
const config = agent.generate(server, apiKey);
|
|
171
173
|
// D-11: Show what will be written (R1-01: mask key in display)
|
|
172
174
|
if (agent.name === "Claude CLI") {
|
|
173
|
-
const maskedCmd = `claude mcp add dreamlogic-skills --url "${server}/sse
|
|
175
|
+
const maskedCmd = `claude mcp add dreamlogic-skills --url "${server}/sse" --header "Authorization: Bearer ${maskKey(apiKey)}"`;
|
|
174
176
|
ui.line(` Command: ${chalk.cyan(maskedCmd)}`);
|
|
175
177
|
}
|
|
176
178
|
else {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface AgentConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
skillsDir: string;
|
|
4
|
+
envOverride?: string;
|
|
5
|
+
group: "universal" | "additional";
|
|
6
|
+
detect?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Detect which agents are installed on this system
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectAgents(): {
|
|
12
|
+
universal: AgentConfig[];
|
|
13
|
+
detected: AgentConfig[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Get all available agents (universal + detected)
|
|
17
|
+
*/
|
|
18
|
+
export declare function getAllAgents(): AgentConfig[];
|
|
19
|
+
/**
|
|
20
|
+
* Register a skill with specific agents by creating symlinks
|
|
21
|
+
* Returns array of { agent, path, status } results
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerSkillWithAgents(skillId: string, skillPath: string, agents: AgentConfig[]): Array<{
|
|
24
|
+
agent: string;
|
|
25
|
+
path: string;
|
|
26
|
+
status: "created" | "updated" | "exists" | "error";
|
|
27
|
+
error?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Unregister a skill from all agents (remove symlinks only)
|
|
31
|
+
*/
|
|
32
|
+
export declare function unregisterSkillFromAgents(skillId: string): number;
|
|
33
|
+
/**
|
|
34
|
+
* List which agents have a specific skill registered
|
|
35
|
+
*/
|
|
36
|
+
export declare function getSkillAgentStatus(skillId: string): Array<{
|
|
37
|
+
agent: string;
|
|
38
|
+
registered: boolean;
|
|
39
|
+
path: string;
|
|
40
|
+
}>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Registry — detect installed AI agents and register skills
|
|
3
|
+
* Inspired by skills.sh (vercel-labs/skills) agent directory mapping
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Each agent has a known skills directory. We create symlinks
|
|
6
|
+
* from the agent's skills dir to the actual skill install location.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, symlinkSync, lstatSync, readlinkSync, unlinkSync } from "fs";
|
|
9
|
+
import { join, resolve } from "path";
|
|
10
|
+
import { homedir, platform } from "os";
|
|
11
|
+
// ===== Agent Directory Map =====
|
|
12
|
+
// Universal agents: always shown (use .agents/skills as shared dir)
|
|
13
|
+
// Additional agents: shown only if detected on the system
|
|
14
|
+
const AGENT_CONFIGS = [
|
|
15
|
+
// ── Universal (.agents/skills) ──
|
|
16
|
+
{ name: "Amp", skillsDir: ".agents/skills", group: "universal" },
|
|
17
|
+
{ name: "Cline", skillsDir: ".agents/skills", group: "universal" },
|
|
18
|
+
{ name: "Codex", skillsDir: ".agents/skills", group: "universal", envOverride: "CODEX_HOME" },
|
|
19
|
+
{ name: "Cursor", skillsDir: ".agents/skills", group: "universal" },
|
|
20
|
+
{ name: "Deep Agents", skillsDir: ".agents/skills", group: "universal" },
|
|
21
|
+
{ name: "Firebender", skillsDir: ".agents/skills", group: "universal" },
|
|
22
|
+
{ name: "Gemini CLI", skillsDir: ".agents/skills", group: "universal" },
|
|
23
|
+
{ name: "GitHub Copilot", skillsDir: ".agents/skills", group: "universal" },
|
|
24
|
+
{ name: "Kimi Code CLI", skillsDir: ".agents/skills", group: "universal" },
|
|
25
|
+
{ name: "OpenCode", skillsDir: ".agents/skills", group: "universal" },
|
|
26
|
+
{ name: "Warp", skillsDir: ".agents/skills", group: "universal" },
|
|
27
|
+
// ── Additional agents ──
|
|
28
|
+
{ name: "Augment", skillsDir: ".augment/skills", group: "additional", detect: ".augment" },
|
|
29
|
+
{ name: "Claude Code", skillsDir: ".claude/skills", group: "additional", detect: ".claude", envOverride: "CLAUDE_CONFIG_DIR" },
|
|
30
|
+
{ name: "OpenClaw", skillsDir: "skills", group: "additional", detect: "skills" },
|
|
31
|
+
{ name: "CodeBuddy", skillsDir: ".codebuddy/skills", group: "additional", detect: ".codebuddy" },
|
|
32
|
+
{ name: "Continue", skillsDir: ".continue/skills", group: "additional", detect: ".continue" },
|
|
33
|
+
{ name: "Windsurf", skillsDir: ".codeium/windsurf/skills", group: "additional", detect: ".codeium" },
|
|
34
|
+
{ name: "Goose", skillsDir: ".config/goose/skills", group: "additional", detect: ".config/goose" },
|
|
35
|
+
{ name: "Roo Code", skillsDir: ".roo/skills", group: "additional", detect: ".roo" },
|
|
36
|
+
{ name: "Trae", skillsDir: ".trae/skills", group: "additional", detect: ".trae" },
|
|
37
|
+
{ name: "Void", skillsDir: ".void/skills", group: "additional", detect: ".void" },
|
|
38
|
+
{ name: "Zed", skillsDir: ".config/zed/skills", group: "additional", detect: ".config/zed" },
|
|
39
|
+
{ name: "Aider", skillsDir: ".aider/skills", group: "additional", detect: ".aider" },
|
|
40
|
+
{ name: "Plandex", skillsDir: ".plandex/skills", group: "additional", detect: ".plandex" },
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the skills directory for an agent (handles env overrides + home expansion)
|
|
44
|
+
*/
|
|
45
|
+
function resolveAgentDir(agent) {
|
|
46
|
+
const home = homedir();
|
|
47
|
+
if (agent.envOverride && process.env[agent.envOverride]) {
|
|
48
|
+
return join(process.env[agent.envOverride], "skills");
|
|
49
|
+
}
|
|
50
|
+
return join(home, agent.skillsDir);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Detect which agents are installed on this system
|
|
54
|
+
*/
|
|
55
|
+
export function detectAgents() {
|
|
56
|
+
const home = homedir();
|
|
57
|
+
const universal = AGENT_CONFIGS.filter(a => a.group === "universal");
|
|
58
|
+
const additional = AGENT_CONFIGS.filter(a => a.group === "additional");
|
|
59
|
+
const detected = additional.filter(agent => {
|
|
60
|
+
if (!agent.detect)
|
|
61
|
+
return false;
|
|
62
|
+
const checkPath = join(home, agent.detect);
|
|
63
|
+
return existsSync(checkPath);
|
|
64
|
+
});
|
|
65
|
+
return { universal, detected };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get all available agents (universal + detected)
|
|
69
|
+
*/
|
|
70
|
+
export function getAllAgents() {
|
|
71
|
+
const { universal, detected } = detectAgents();
|
|
72
|
+
return [...universal, ...detected];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Register a skill with specific agents by creating symlinks
|
|
76
|
+
* Returns array of { agent, path, status } results
|
|
77
|
+
*/
|
|
78
|
+
export function registerSkillWithAgents(skillId, skillPath, agents) {
|
|
79
|
+
const results = [];
|
|
80
|
+
// R3-08: Validate skill ID to prevent path traversal via symlink target
|
|
81
|
+
const SAFE_SKILL_ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
82
|
+
if (!SAFE_SKILL_ID.test(skillId)) {
|
|
83
|
+
return [{ agent: "all", path: "", status: "error", error: "Invalid skill ID" }];
|
|
84
|
+
}
|
|
85
|
+
const resolvedSkillPath = resolve(skillPath);
|
|
86
|
+
// Deduplicate by resolved dir (universal agents share .agents/skills)
|
|
87
|
+
const seenDirs = new Set();
|
|
88
|
+
for (const agent of agents) {
|
|
89
|
+
const agentDir = resolveAgentDir(agent);
|
|
90
|
+
// Skip duplicates (multiple universal agents → same .agents/skills dir)
|
|
91
|
+
if (seenDirs.has(agentDir)) {
|
|
92
|
+
results.push({ agent: agent.name, path: agentDir, status: "exists" });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
seenDirs.add(agentDir);
|
|
96
|
+
const targetLink = join(agentDir, skillId);
|
|
97
|
+
try {
|
|
98
|
+
// Ensure parent dir exists
|
|
99
|
+
mkdirSync(agentDir, { recursive: true });
|
|
100
|
+
// Check if link already exists
|
|
101
|
+
if (existsSync(targetLink)) {
|
|
102
|
+
try {
|
|
103
|
+
const stat = lstatSync(targetLink);
|
|
104
|
+
if (stat.isSymbolicLink()) {
|
|
105
|
+
const existing = readlinkSync(targetLink);
|
|
106
|
+
if (resolve(existing) === resolvedSkillPath) {
|
|
107
|
+
results.push({ agent: agent.name, path: targetLink, status: "exists" });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Different target — update
|
|
111
|
+
unlinkSync(targetLink);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Not a symlink (real directory) — skip to avoid data loss
|
|
115
|
+
results.push({ agent: agent.name, path: targetLink, status: "exists" });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Stat failed — try to proceed
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Create symlink
|
|
124
|
+
// On Windows, use junction for directories (no admin required)
|
|
125
|
+
const linkType = platform() === "win32" ? "junction" : "dir";
|
|
126
|
+
symlinkSync(resolvedSkillPath, targetLink, linkType);
|
|
127
|
+
results.push({ agent: agent.name, path: targetLink, status: "created" });
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
results.push({
|
|
131
|
+
agent: agent.name,
|
|
132
|
+
path: targetLink,
|
|
133
|
+
status: "error",
|
|
134
|
+
error: err.message,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Unregister a skill from all agents (remove symlinks only)
|
|
142
|
+
*/
|
|
143
|
+
export function unregisterSkillFromAgents(skillId) {
|
|
144
|
+
const home = homedir();
|
|
145
|
+
let removed = 0;
|
|
146
|
+
// Check all known agent dirs
|
|
147
|
+
const allDirs = new Set(AGENT_CONFIGS.map(a => resolveAgentDir(a)));
|
|
148
|
+
for (const dir of allDirs) {
|
|
149
|
+
const targetLink = join(dir, skillId);
|
|
150
|
+
try {
|
|
151
|
+
if (existsSync(targetLink) && lstatSync(targetLink).isSymbolicLink()) {
|
|
152
|
+
unlinkSync(targetLink);
|
|
153
|
+
removed++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Skip errors
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return removed;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* List which agents have a specific skill registered
|
|
164
|
+
*/
|
|
165
|
+
export function getSkillAgentStatus(skillId) {
|
|
166
|
+
const results = [];
|
|
167
|
+
const seenDirs = new Set();
|
|
168
|
+
for (const agent of AGENT_CONFIGS) {
|
|
169
|
+
const agentDir = resolveAgentDir(agent);
|
|
170
|
+
if (seenDirs.has(agentDir))
|
|
171
|
+
continue;
|
|
172
|
+
seenDirs.add(agentDir);
|
|
173
|
+
const targetLink = join(agentDir, skillId);
|
|
174
|
+
const registered = existsSync(targetLink);
|
|
175
|
+
results.push({ agent: agent.name, registered, path: targetLink });
|
|
176
|
+
}
|
|
177
|
+
return results;
|
|
178
|
+
}
|
package/dist/lib/config.js
CHANGED
|
@@ -6,6 +6,21 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, statSync
|
|
|
6
6
|
import { join } from "path";
|
|
7
7
|
import { homedir } from "os";
|
|
8
8
|
import { CONFIG_DIR_NAME, DEFAULT_SERVER, DEFAULT_INSTALL_DIR_NAME, } from "../types.js";
|
|
9
|
+
/** CFG-01 FIX: Recursively strip prototype pollution keys from parsed JSON */
|
|
10
|
+
function sanitize(obj) {
|
|
11
|
+
if (typeof obj !== "object" || obj === null)
|
|
12
|
+
return obj;
|
|
13
|
+
const BANNED = new Set(["__proto__", "constructor", "prototype"]);
|
|
14
|
+
for (const key of Object.keys(obj)) {
|
|
15
|
+
if (BANNED.has(key)) {
|
|
16
|
+
delete obj[key];
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
obj[key] = sanitize(obj[key]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return obj;
|
|
23
|
+
}
|
|
9
24
|
// R1-06: Key format validation applied everywhere
|
|
10
25
|
const KEY_RE = /^sk-(admin|user)-[a-f0-9]{16,}$/;
|
|
11
26
|
function getConfigDir() {
|
|
@@ -31,7 +46,7 @@ export function loadConfig() {
|
|
|
31
46
|
if (!existsSync(path))
|
|
32
47
|
return null;
|
|
33
48
|
try {
|
|
34
|
-
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
49
|
+
const data = sanitize(JSON.parse(readFileSync(path, "utf-8")));
|
|
35
50
|
// R2-13: Basic shape validation
|
|
36
51
|
if (typeof data !== "object" || data === null)
|
|
37
52
|
return null;
|
|
@@ -81,7 +96,7 @@ export function loadInstalled() {
|
|
|
81
96
|
if (!existsSync(path))
|
|
82
97
|
return {};
|
|
83
98
|
try {
|
|
84
|
-
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
99
|
+
const data = sanitize(JSON.parse(readFileSync(path, "utf-8")));
|
|
85
100
|
// R2-13: Basic shape validation
|
|
86
101
|
if (typeof data !== "object" || data === null || Array.isArray(data))
|
|
87
102
|
return {};
|
package/dist/lib/installer.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
|
|
10
10
|
import { join, normalize, resolve as pathResolve, sep } from "path";
|
|
11
|
-
import {
|
|
11
|
+
import { Transform as TransformStream } from "stream";
|
|
12
12
|
import yauzl from "yauzl";
|
|
13
13
|
import { loadInstalled, saveInstalled, getInstallDir } from "./config.js";
|
|
14
14
|
import { ui } from "./ui.js";
|
|
@@ -155,6 +155,10 @@ export async function installSkill(client, skillId, packageFile, expectedSha256,
|
|
|
155
155
|
* Rollback a skill to its backup (D-13)
|
|
156
156
|
*/
|
|
157
157
|
export function rollbackSkill(skillId) {
|
|
158
|
+
// INS-03/RBK-01 FIX: Validate skill ID to prevent path traversal
|
|
159
|
+
if (!SAFE_SKILL_ID.test(skillId)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
158
162
|
const installDir = getInstallDir();
|
|
159
163
|
const skillDir = join(installDir, skillId);
|
|
160
164
|
let entries;
|
|
@@ -235,15 +239,22 @@ function extractZip(buffer, targetDir) {
|
|
|
235
239
|
reject(new Error(`Unsafe path in ZIP: ${entry.fileName}`));
|
|
236
240
|
return;
|
|
237
241
|
}
|
|
238
|
-
// Reject
|
|
242
|
+
// INS-02 FIX: Reject symlink entries (external attributes bit 0xA000)
|
|
243
|
+
const externalAttrs = (entry.externalFileAttributes >>> 16) & 0xFFFF;
|
|
244
|
+
const S_IFLNK = 0xA000;
|
|
245
|
+
if ((externalAttrs & 0xF000) === S_IFLNK) {
|
|
246
|
+
zipfile.close();
|
|
247
|
+
reject(new Error(`Symlink entry rejected in ZIP: ${entry.fileName}`));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// R1-03: Pre-check declared size (actual bytes tracked in counting stream below)
|
|
239
251
|
if (entry.uncompressedSize > MAX_SINGLE_FILE_SIZE) {
|
|
240
252
|
zipfile.close(); // R2-08
|
|
241
253
|
reject(new Error(`File too large in ZIP: ${entry.fileName} (${entry.uncompressedSize} bytes)`));
|
|
242
254
|
return;
|
|
243
255
|
}
|
|
244
|
-
// R1-03:
|
|
245
|
-
totalExtracted
|
|
246
|
-
if (totalExtracted > MAX_TOTAL_EXTRACT_SIZE) {
|
|
256
|
+
// R1-03: Pre-check cumulative declared size (actual bytes tracked in stream)
|
|
257
|
+
if (totalExtracted + entry.uncompressedSize > MAX_TOTAL_EXTRACT_SIZE) {
|
|
247
258
|
zipfile.close(); // R2-08
|
|
248
259
|
reject(new Error(`Total extracted size exceeds ${MAX_TOTAL_EXTRACT_SIZE / 1024 / 1024}MB — possible zip bomb`));
|
|
249
260
|
return;
|
|
@@ -272,11 +283,31 @@ function extractZip(buffer, targetDir) {
|
|
|
272
283
|
reject(err || new Error("Failed to read ZIP entry"));
|
|
273
284
|
return;
|
|
274
285
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
286
|
+
// INS-01 FIX: Track actual bytes written (not declared uncompressedSize)
|
|
287
|
+
let fileBytes = 0;
|
|
288
|
+
const countingStream = new TransformStream({
|
|
289
|
+
transform(chunk, _encoding, callback) {
|
|
290
|
+
fileBytes += chunk.length;
|
|
291
|
+
totalExtracted += chunk.length;
|
|
292
|
+
if (fileBytes > MAX_SINGLE_FILE_SIZE) {
|
|
293
|
+
zipfile.close();
|
|
294
|
+
callback(new Error(`Actual file size exceeds limit: ${entry.fileName}`));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (totalExtracted > MAX_TOTAL_EXTRACT_SIZE) {
|
|
298
|
+
zipfile.close();
|
|
299
|
+
callback(new Error(`Total extracted size exceeds ${MAX_TOTAL_EXTRACT_SIZE / 1024 / 1024}MB — possible zip bomb`));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
callback(null, chunk);
|
|
303
|
+
},
|
|
279
304
|
});
|
|
305
|
+
const writeStream = createWriteStream(fullPath);
|
|
306
|
+
readStream.pipe(countingStream).pipe(writeStream);
|
|
307
|
+
writeStream.on("finish", () => zipfile.readEntry());
|
|
308
|
+
writeStream.on("error", (e) => { zipfile.close(); reject(e); });
|
|
309
|
+
countingStream.on("error", (e) => { zipfile.close(); reject(e); });
|
|
310
|
+
readStream.on("error", (e) => { zipfile.close(); reject(e); });
|
|
280
311
|
});
|
|
281
312
|
}
|
|
282
313
|
});
|
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 */
|
|
10
|
+
/** Animated gradient logo banner — block-art style */
|
|
11
11
|
banner(): void;
|
|
12
12
|
/** Section header with gradient bar */
|
|
13
13
|
header(text: string): void;
|
package/dist/lib/ui.js
CHANGED
|
@@ -29,16 +29,17 @@ export const ui = {
|
|
|
29
29
|
error,
|
|
30
30
|
dim,
|
|
31
31
|
muted,
|
|
32
|
-
/** Animated gradient logo banner */
|
|
32
|
+
/** Animated gradient logo banner — block-art style */
|
|
33
33
|
banner() {
|
|
34
|
-
const
|
|
34
|
+
const dream = figlet.textSync("DREAM", { font: "ANSI Shadow" });
|
|
35
|
+
const logic = figlet.textSync("LOGIC", { font: "ANSI Shadow" });
|
|
36
|
+
const fullLogo = dream + logic;
|
|
35
37
|
console.log();
|
|
36
|
-
console.log(hasTruecolor ? brandGradient.multiline(
|
|
37
|
-
console.log(muted("
|
|
38
|
+
console.log(hasTruecolor ? brandGradient.multiline(fullLogo) : chalk.bold.magenta(fullLogo));
|
|
39
|
+
console.log(muted(" ") +
|
|
38
40
|
(hasTruecolor ? brandGradient(`${CLI_NAME} v${CLI_VERSION}`) : chalk.bold(`${CLI_NAME} v${CLI_VERSION}`)) +
|
|
39
41
|
muted(" | ") +
|
|
40
42
|
muted(CLI_AUTHOR));
|
|
41
|
-
console.log(muted(" " + "─".repeat(48)));
|
|
42
43
|
console.log();
|
|
43
44
|
},
|
|
44
45
|
/** Section header with gradient bar */
|
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.
|
|
36
|
+
export declare const CLI_VERSION = "2.0.2";
|
|
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.
|
|
5
|
+
export const CLI_VERSION = "2.0.2";
|
|
6
6
|
export const CLI_NAME = "Dreamlogic CLI";
|
|
7
7
|
export const CLI_AUTHOR = "Dreamlogic-ai by MAJORNINE";
|