claude-flow 3.5.24 → 3.5.25
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/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-tools.js +2 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/config-tools.js +10 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/swarm-tools.d.ts +2 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/swarm-tools.js +216 -30
- package/v3/@claude-flow/cli/dist/src/transfer/storage/gcs.js +22 -6
- package/v3/@claude-flow/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.25",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -61,6 +61,8 @@ const commandLoaders = {
|
|
|
61
61
|
guidance: () => import('./guidance.js'),
|
|
62
62
|
// RVFA Appliance Management
|
|
63
63
|
appliance: () => import('./appliance.js'),
|
|
64
|
+
'appliance-advanced': () => import('./appliance-advanced.js'),
|
|
65
|
+
'transfer-store': () => import('./transfer-store.js'),
|
|
64
66
|
};
|
|
65
67
|
// Cache for loaded commands
|
|
66
68
|
const loadedCommands = new Map();
|
|
@@ -10,10 +10,10 @@ const browserSessions = new Map();
|
|
|
10
10
|
* Execute agent-browser CLI command
|
|
11
11
|
*/
|
|
12
12
|
async function execBrowserCommand(args, session = 'default') {
|
|
13
|
-
const {
|
|
13
|
+
const { execFileSync } = await import('child_process');
|
|
14
14
|
try {
|
|
15
15
|
const fullArgs = ['--session', session, '--json', ...args];
|
|
16
|
-
const result =
|
|
16
|
+
const result = execFileSync('agent-browser', fullArgs, {
|
|
17
17
|
encoding: 'utf-8',
|
|
18
18
|
timeout: 30000,
|
|
19
19
|
});
|
|
@@ -30,7 +30,7 @@ function getConfigPath() {
|
|
|
30
30
|
function ensureConfigDir() {
|
|
31
31
|
const dir = getConfigDir();
|
|
32
32
|
if (!existsSync(dir)) {
|
|
33
|
-
mkdirSync(dir, { recursive: true });
|
|
33
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
function loadConfigStore() {
|
|
@@ -80,7 +80,16 @@ function filterDangerousKeys(obj) {
|
|
|
80
80
|
return filtered;
|
|
81
81
|
}
|
|
82
82
|
function setNestedValue(obj, key, value) {
|
|
83
|
+
const MAX_NESTING_DEPTH = 10;
|
|
83
84
|
const parts = key.split('.');
|
|
85
|
+
if (parts.length > MAX_NESTING_DEPTH) {
|
|
86
|
+
throw new Error(`Key exceeds maximum nesting depth of ${MAX_NESTING_DEPTH}`);
|
|
87
|
+
}
|
|
88
|
+
for (const part of parts) {
|
|
89
|
+
if (DANGEROUS_KEYS.has(part)) {
|
|
90
|
+
throw new Error(`Dangerous key segment rejected: ${part}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
84
93
|
let current = obj;
|
|
85
94
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
86
95
|
const part = parts[i];
|
|
@@ -213,6 +213,7 @@ export const memoryTools = [
|
|
|
213
213
|
const { getEntry } = await getMemoryFunctions();
|
|
214
214
|
const key = input.key;
|
|
215
215
|
const namespace = input.namespace || 'default';
|
|
216
|
+
validateMemoryInput(key);
|
|
216
217
|
try {
|
|
217
218
|
const result = await getEntry({ key, namespace });
|
|
218
219
|
if (result.found && result.entry) {
|
|
@@ -337,6 +338,7 @@ export const memoryTools = [
|
|
|
337
338
|
const { deleteEntry } = await getMemoryFunctions();
|
|
338
339
|
const key = input.key;
|
|
339
340
|
const namespace = input.namespace || 'default';
|
|
341
|
+
validateMemoryInput(key);
|
|
340
342
|
try {
|
|
341
343
|
const result = await deleteEntry({ key, namespace });
|
|
342
344
|
return {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Swarm MCP Tools for CLI
|
|
3
3
|
*
|
|
4
|
-
* Tool definitions for swarm coordination.
|
|
4
|
+
* Tool definitions for swarm coordination with file-based state persistence.
|
|
5
|
+
* Replaces previous stub implementations with real state tracking.
|
|
5
6
|
*/
|
|
6
7
|
import type { MCPTool } from './types.js';
|
|
7
8
|
export declare const swarmTools: MCPTool[];
|
|
@@ -1,82 +1,212 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Swarm MCP Tools for CLI
|
|
3
3
|
*
|
|
4
|
-
* Tool definitions for swarm coordination.
|
|
4
|
+
* Tool definitions for swarm coordination with file-based state persistence.
|
|
5
|
+
* Replaces previous stub implementations with real state tracking.
|
|
5
6
|
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
// Swarm state persistence
|
|
10
|
+
const SWARM_DIR = '.claude-flow/swarm';
|
|
11
|
+
const SWARM_STATE_FILE = 'swarm-state.json';
|
|
12
|
+
function getSwarmDir() {
|
|
13
|
+
return join(process.cwd(), SWARM_DIR);
|
|
14
|
+
}
|
|
15
|
+
function getSwarmStatePath() {
|
|
16
|
+
return join(getSwarmDir(), SWARM_STATE_FILE);
|
|
17
|
+
}
|
|
18
|
+
function ensureSwarmDir() {
|
|
19
|
+
const dir = getSwarmDir();
|
|
20
|
+
if (!existsSync(dir)) {
|
|
21
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function loadSwarmStore() {
|
|
25
|
+
try {
|
|
26
|
+
const path = getSwarmStatePath();
|
|
27
|
+
if (existsSync(path)) {
|
|
28
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch { /* return default */ }
|
|
32
|
+
return { swarms: {}, version: '3.0.0' };
|
|
33
|
+
}
|
|
34
|
+
function saveSwarmStore(store) {
|
|
35
|
+
ensureSwarmDir();
|
|
36
|
+
writeFileSync(getSwarmStatePath(), JSON.stringify(store, null, 2), 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
// Input validation
|
|
39
|
+
const VALID_TOPOLOGIES = new Set([
|
|
40
|
+
'hierarchical', 'mesh', 'hierarchical-mesh', 'ring', 'star', 'hybrid', 'adaptive',
|
|
41
|
+
]);
|
|
6
42
|
export const swarmTools = [
|
|
7
43
|
{
|
|
8
44
|
name: 'swarm_init',
|
|
9
|
-
description: 'Initialize a swarm',
|
|
45
|
+
description: 'Initialize a swarm with persistent state tracking',
|
|
10
46
|
category: 'swarm',
|
|
11
47
|
inputSchema: {
|
|
12
48
|
type: 'object',
|
|
13
49
|
properties: {
|
|
14
|
-
topology: { type: 'string', description: 'Swarm topology type' },
|
|
15
|
-
maxAgents: { type: 'number', description: 'Maximum number of agents' },
|
|
16
|
-
|
|
50
|
+
topology: { type: 'string', description: 'Swarm topology type (hierarchical, mesh, hierarchical-mesh, ring, star, hybrid, adaptive)' },
|
|
51
|
+
maxAgents: { type: 'number', description: 'Maximum number of agents (1-50)' },
|
|
52
|
+
strategy: { type: 'string', description: 'Agent strategy (specialized, balanced, adaptive)' },
|
|
53
|
+
config: { type: 'object', description: 'Additional swarm configuration' },
|
|
17
54
|
},
|
|
18
55
|
},
|
|
19
56
|
handler: async (input) => {
|
|
20
57
|
const topology = input.topology || 'hierarchical-mesh';
|
|
21
|
-
const maxAgents = input.maxAgents || 15;
|
|
58
|
+
const maxAgents = Math.min(Math.max(input.maxAgents || 15, 1), 50);
|
|
59
|
+
const strategy = input.strategy || 'specialized';
|
|
22
60
|
const config = (input.config || {});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
61
|
+
if (!VALID_TOPOLOGIES.has(topology)) {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: `Invalid topology: ${topology}. Valid: ${[...VALID_TOPOLOGIES].join(', ')}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const swarmId = `swarm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
68
|
+
const now = new Date().toISOString();
|
|
69
|
+
const swarmState = {
|
|
70
|
+
swarmId,
|
|
26
71
|
topology,
|
|
27
|
-
|
|
72
|
+
maxAgents,
|
|
73
|
+
status: 'running',
|
|
74
|
+
agents: [],
|
|
75
|
+
tasks: [],
|
|
28
76
|
config: {
|
|
29
77
|
topology,
|
|
30
78
|
maxAgents,
|
|
31
|
-
|
|
79
|
+
strategy,
|
|
32
80
|
communicationProtocol: config.communicationProtocol || 'message-bus',
|
|
33
81
|
autoScaling: config.autoScaling ?? true,
|
|
34
82
|
consensusMechanism: config.consensusMechanism || 'majority',
|
|
35
83
|
},
|
|
84
|
+
createdAt: now,
|
|
85
|
+
updatedAt: now,
|
|
86
|
+
};
|
|
87
|
+
const store = loadSwarmStore();
|
|
88
|
+
store.swarms[swarmId] = swarmState;
|
|
89
|
+
saveSwarmStore(store);
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
swarmId,
|
|
93
|
+
topology,
|
|
94
|
+
strategy,
|
|
95
|
+
maxAgents,
|
|
96
|
+
initializedAt: now,
|
|
97
|
+
config: swarmState.config,
|
|
98
|
+
persisted: true,
|
|
36
99
|
};
|
|
37
100
|
},
|
|
38
101
|
},
|
|
39
102
|
{
|
|
40
103
|
name: 'swarm_status',
|
|
41
|
-
description: 'Get swarm status',
|
|
104
|
+
description: 'Get swarm status from persistent state',
|
|
42
105
|
category: 'swarm',
|
|
43
106
|
inputSchema: {
|
|
44
107
|
type: 'object',
|
|
45
108
|
properties: {
|
|
46
|
-
swarmId: { type: 'string', description: 'Swarm ID' },
|
|
109
|
+
swarmId: { type: 'string', description: 'Swarm ID (omit for most recent)' },
|
|
47
110
|
},
|
|
48
111
|
},
|
|
49
112
|
handler: async (input) => {
|
|
113
|
+
const store = loadSwarmStore();
|
|
114
|
+
const swarmId = input.swarmId;
|
|
115
|
+
if (swarmId && store.swarms[swarmId]) {
|
|
116
|
+
const swarm = store.swarms[swarmId];
|
|
117
|
+
return {
|
|
118
|
+
swarmId: swarm.swarmId,
|
|
119
|
+
status: swarm.status,
|
|
120
|
+
topology: swarm.topology,
|
|
121
|
+
maxAgents: swarm.maxAgents,
|
|
122
|
+
agentCount: swarm.agents.length,
|
|
123
|
+
taskCount: swarm.tasks.length,
|
|
124
|
+
config: swarm.config,
|
|
125
|
+
createdAt: swarm.createdAt,
|
|
126
|
+
updatedAt: swarm.updatedAt,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// Return most recent swarm if no ID specified
|
|
130
|
+
const swarmIds = Object.keys(store.swarms);
|
|
131
|
+
if (swarmIds.length === 0) {
|
|
132
|
+
return {
|
|
133
|
+
status: 'no_swarm',
|
|
134
|
+
message: 'No active swarms. Use swarm_init to create one.',
|
|
135
|
+
totalSwarms: 0,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const latest = swarmIds
|
|
139
|
+
.map(id => store.swarms[id])
|
|
140
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())[0];
|
|
50
141
|
return {
|
|
51
|
-
swarmId:
|
|
52
|
-
status:
|
|
53
|
-
|
|
54
|
-
|
|
142
|
+
swarmId: latest.swarmId,
|
|
143
|
+
status: latest.status,
|
|
144
|
+
topology: latest.topology,
|
|
145
|
+
maxAgents: latest.maxAgents,
|
|
146
|
+
agentCount: latest.agents.length,
|
|
147
|
+
taskCount: latest.tasks.length,
|
|
148
|
+
config: latest.config,
|
|
149
|
+
createdAt: latest.createdAt,
|
|
150
|
+
updatedAt: latest.updatedAt,
|
|
151
|
+
totalSwarms: swarmIds.length,
|
|
55
152
|
};
|
|
56
153
|
},
|
|
57
154
|
},
|
|
58
155
|
{
|
|
59
156
|
name: 'swarm_shutdown',
|
|
60
|
-
description: 'Shutdown a swarm',
|
|
157
|
+
description: 'Shutdown a swarm and update persistent state',
|
|
61
158
|
category: 'swarm',
|
|
62
159
|
inputSchema: {
|
|
63
160
|
type: 'object',
|
|
64
161
|
properties: {
|
|
65
|
-
swarmId: { type: 'string', description: 'Swarm ID' },
|
|
66
|
-
graceful: { type: 'boolean', description: 'Graceful shutdown' },
|
|
162
|
+
swarmId: { type: 'string', description: 'Swarm ID to shutdown' },
|
|
163
|
+
graceful: { type: 'boolean', description: 'Graceful shutdown (default: true)' },
|
|
67
164
|
},
|
|
68
165
|
},
|
|
69
166
|
handler: async (input) => {
|
|
167
|
+
const store = loadSwarmStore();
|
|
168
|
+
const swarmId = input.swarmId;
|
|
169
|
+
// Find the swarm
|
|
170
|
+
let target;
|
|
171
|
+
if (swarmId && store.swarms[swarmId]) {
|
|
172
|
+
target = store.swarms[swarmId];
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Shutdown most recent running swarm
|
|
176
|
+
const running = Object.values(store.swarms)
|
|
177
|
+
.filter(s => s.status === 'running')
|
|
178
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
179
|
+
target = running[0];
|
|
180
|
+
}
|
|
181
|
+
if (!target) {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
error: swarmId ? `Swarm ${swarmId} not found` : 'No running swarms to shutdown',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (target.status === 'terminated') {
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
swarmId: target.swarmId,
|
|
191
|
+
error: 'Swarm already terminated',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
target.status = 'terminated';
|
|
195
|
+
target.updatedAt = new Date().toISOString();
|
|
196
|
+
saveSwarmStore(store);
|
|
70
197
|
return {
|
|
71
198
|
success: true,
|
|
72
|
-
swarmId:
|
|
199
|
+
swarmId: target.swarmId,
|
|
73
200
|
terminated: true,
|
|
201
|
+
graceful: input.graceful ?? true,
|
|
202
|
+
agentsTerminated: target.agents.length,
|
|
203
|
+
terminatedAt: target.updatedAt,
|
|
74
204
|
};
|
|
75
205
|
},
|
|
76
206
|
},
|
|
77
207
|
{
|
|
78
208
|
name: 'swarm_health',
|
|
79
|
-
description: 'Check swarm health status',
|
|
209
|
+
description: 'Check swarm health status with real state inspection',
|
|
80
210
|
category: 'swarm',
|
|
81
211
|
inputSchema: {
|
|
82
212
|
type: 'object',
|
|
@@ -85,15 +215,71 @@ export const swarmTools = [
|
|
|
85
215
|
},
|
|
86
216
|
},
|
|
87
217
|
handler: async (input) => {
|
|
218
|
+
const store = loadSwarmStore();
|
|
219
|
+
const swarmId = input.swarmId;
|
|
220
|
+
// Find the swarm
|
|
221
|
+
let target;
|
|
222
|
+
if (swarmId) {
|
|
223
|
+
target = store.swarms[swarmId];
|
|
224
|
+
if (!target) {
|
|
225
|
+
return {
|
|
226
|
+
status: 'not_found',
|
|
227
|
+
healthy: false,
|
|
228
|
+
checks: [
|
|
229
|
+
{ name: 'swarm_exists', status: 'fail', message: `Swarm ${swarmId} not found` },
|
|
230
|
+
],
|
|
231
|
+
checkedAt: new Date().toISOString(),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
const running = Object.values(store.swarms)
|
|
237
|
+
.filter(s => s.status === 'running')
|
|
238
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
239
|
+
target = running[0];
|
|
240
|
+
}
|
|
241
|
+
if (!target) {
|
|
242
|
+
return {
|
|
243
|
+
status: 'no_swarm',
|
|
244
|
+
healthy: false,
|
|
245
|
+
checks: [
|
|
246
|
+
{ name: 'swarm_exists', status: 'fail', message: 'No active swarm found' },
|
|
247
|
+
],
|
|
248
|
+
checkedAt: new Date().toISOString(),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const isRunning = target.status === 'running';
|
|
252
|
+
const stateFileExists = existsSync(getSwarmStatePath());
|
|
253
|
+
const checks = [
|
|
254
|
+
{
|
|
255
|
+
name: 'coordinator',
|
|
256
|
+
status: isRunning ? 'ok' : 'warn',
|
|
257
|
+
message: isRunning ? 'Coordinator active' : `Swarm status: ${target.status}`,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'agents',
|
|
261
|
+
status: target.agents.length > 0 ? 'ok' : 'info',
|
|
262
|
+
message: `${target.agents.length} agents registered (max: ${target.maxAgents})`,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'persistence',
|
|
266
|
+
status: stateFileExists ? 'ok' : 'warn',
|
|
267
|
+
message: stateFileExists ? 'State file persisted' : 'State file missing',
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'topology',
|
|
271
|
+
status: 'ok',
|
|
272
|
+
message: `Topology: ${target.topology}`,
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
const healthy = isRunning && stateFileExists;
|
|
88
276
|
return {
|
|
89
|
-
status: 'healthy',
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{ name: 'messaging', status: 'ok', message: 'Message bus active' },
|
|
96
|
-
],
|
|
277
|
+
status: healthy ? 'healthy' : 'degraded',
|
|
278
|
+
healthy,
|
|
279
|
+
swarmId: target.swarmId,
|
|
280
|
+
topology: target.topology,
|
|
281
|
+
agentCount: target.agents.length,
|
|
282
|
+
checks,
|
|
97
283
|
checkedAt: new Date().toISOString(),
|
|
98
284
|
};
|
|
99
285
|
},
|
|
@@ -78,6 +78,10 @@ export async function uploadToGCS(content, options = {}) {
|
|
|
78
78
|
const contentId = generateContentId(content);
|
|
79
79
|
const checksum = crypto.createHash('sha256').update(content).digest('hex');
|
|
80
80
|
const fileName = options.name || `${contentId}.cfp.json`;
|
|
81
|
+
// Validate filename to prevent path traversal
|
|
82
|
+
if (!/^[a-zA-Z0-9._\-]+$/.test(fileName) || fileName.includes('..')) {
|
|
83
|
+
throw new Error(`Invalid filename: ${fileName}`);
|
|
84
|
+
}
|
|
81
85
|
const objectPath = config.prefix ? `${config.prefix}/${fileName}` : fileName;
|
|
82
86
|
// S-1: Validate bucket name and object path to prevent command injection
|
|
83
87
|
if (!isValidBucketName(config.bucket)) {
|
|
@@ -111,8 +115,11 @@ export async function uploadToGCS(content, options = {}) {
|
|
|
111
115
|
// Metadata update failed, but upload succeeded
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
|
-
// Clean up temp file
|
|
115
|
-
|
|
118
|
+
// Clean up temp file (validate path is within temp dir)
|
|
119
|
+
const resolvedTemp = path.resolve(tempFile);
|
|
120
|
+
if (resolvedTemp.startsWith(path.resolve(tempDir))) {
|
|
121
|
+
fs.unlinkSync(tempFile);
|
|
122
|
+
}
|
|
116
123
|
const uri = `gs://${config.bucket}/${objectPath}`;
|
|
117
124
|
const publicUrl = `https://storage.googleapis.com/${config.bucket}/${objectPath}`;
|
|
118
125
|
console.log(`[GCS] Upload complete: ${uri}`);
|
|
@@ -126,9 +133,12 @@ export async function uploadToGCS(content, options = {}) {
|
|
|
126
133
|
};
|
|
127
134
|
}
|
|
128
135
|
catch (error) {
|
|
129
|
-
// Clean up temp file on error
|
|
136
|
+
// Clean up temp file on error (validate path is within temp dir)
|
|
130
137
|
try {
|
|
131
|
-
|
|
138
|
+
const resolvedTemp = path.resolve(tempFile);
|
|
139
|
+
if (resolvedTemp.startsWith(path.resolve(tempDir))) {
|
|
140
|
+
fs.unlinkSync(tempFile);
|
|
141
|
+
}
|
|
132
142
|
}
|
|
133
143
|
catch { /* ignore */ }
|
|
134
144
|
throw new Error(`GCS upload failed: ${error}`);
|
|
@@ -150,13 +160,19 @@ export async function downloadFromGCS(uri, config) {
|
|
|
150
160
|
downloadArgs.push(`--project=${cfg.projectId}`);
|
|
151
161
|
execFileSync('gcloud', downloadArgs, { encoding: 'utf-8', stdio: 'pipe' });
|
|
152
162
|
const content = fs.readFileSync(tempFile);
|
|
153
|
-
|
|
163
|
+
const resolvedTemp = path.resolve(tempFile);
|
|
164
|
+
if (resolvedTemp.startsWith(path.resolve(tempDir))) {
|
|
165
|
+
fs.unlinkSync(tempFile);
|
|
166
|
+
}
|
|
154
167
|
console.log(`[GCS] Downloaded ${content.length} bytes`);
|
|
155
168
|
return content;
|
|
156
169
|
}
|
|
157
170
|
catch (error) {
|
|
158
171
|
try {
|
|
159
|
-
|
|
172
|
+
const resolvedTemp = path.resolve(tempFile);
|
|
173
|
+
if (resolvedTemp.startsWith(path.resolve(tempDir))) {
|
|
174
|
+
fs.unlinkSync(tempFile);
|
|
175
|
+
}
|
|
160
176
|
}
|
|
161
177
|
catch { /* ignore */ }
|
|
162
178
|
console.error(`[GCS] Download failed: ${error}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|