agent-window 1.0.1 → 1.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/bin/cli.js +45 -0
- package/docs/WEB_UI_GUIDE.md +249 -0
- package/package.json +11 -2
- package/scripts/test-platform.js +109 -0
- package/src/api/routes/index.js +25 -0
- package/src/api/routes/instances.js +252 -0
- package/src/api/routes/operations.js +118 -0
- package/src/api/routes/system.js +42 -0
- package/src/api/server.js +147 -0
- package/src/api/websocket/index.js +16 -0
- package/src/api/websocket/logs.js +127 -0
- package/src/cli/commands/add.js +80 -0
- package/src/cli/commands/config.js +192 -0
- package/src/cli/commands/index.js +89 -0
- package/src/cli/commands/info.js +94 -0
- package/src/cli/commands/list.js +72 -0
- package/src/cli/commands/logs.js +67 -0
- package/src/cli/commands/remove.js +97 -0
- package/src/cli/commands/restart.js +67 -0
- package/src/cli/commands/start.js +101 -0
- package/src/cli/commands/status.js +95 -0
- package/src/cli/commands/stop.js +53 -0
- package/src/cli/commands/ui.js +51 -0
- package/src/cli/index.js +110 -0
- package/src/core/config.js +5 -10
- package/src/core/instance/backup-manager.js +172 -0
- package/src/core/instance/config-manager.js +279 -0
- package/src/core/instance/index.js +62 -0
- package/src/core/instance/manager.js +220 -0
- package/src/core/instance/pm2-bridge.js +205 -0
- package/src/core/instance/validator.js +161 -0
- package/src/core/platform/detector.js +142 -0
- package/src/core/platform/docker-bridge.js +372 -0
- package/src/core/platform/index.js +27 -0
- package/src/core/platform/paths.js +112 -0
- package/src/core/platform/pm2-bridge.js +314 -0
- package/web/dist/assets/Dashboard-C1smB9Nj.js +1 -0
- package/web/dist/assets/Dashboard-ezbZMSpZ.css +1 -0
- package/web/dist/assets/InstanceDetail-CRPMV7rg.css +1 -0
- package/web/dist/assets/InstanceDetail-C_Ddtrog.js +3 -0
- package/web/dist/assets/Instances-CvnH8iDv.css +1 -0
- package/web/dist/assets/Instances-_u2__M83.js +1 -0
- package/web/dist/assets/Settings-CAu3R9RW.css +1 -0
- package/web/dist/assets/Settings-CIa9MX7m.js +1 -0
- package/web/dist/assets/_plugin-vue_export-helper-DlAUqK2U.js +1 -0
- package/web/dist/assets/element-plus-Jr6qTeY5.js +37 -0
- package/web/dist/assets/main-CalRvcyG.css +1 -0
- package/web/dist/assets/main-D3cdXAiV.js +7 -0
- package/web/dist/assets/vue-vendor-CGSlMM3Y.js +29 -0
- package/web/dist/index.html +16 -0
- package/SECURITY.md +0 -31
- package/docs/legacy/DEVELOPMENT.md +0 -174
- package/docs/legacy/HANDOVER.md +0 -149
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker Bridge Module
|
|
3
|
+
*
|
|
4
|
+
* Cross-platform abstraction for Docker commands.
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Native Docker on Linux/macOS
|
|
7
|
+
* - Docker Desktop on Windows
|
|
8
|
+
* - WSL2 Docker backend on Windows
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn, exec } from 'child_process';
|
|
12
|
+
import { promisify } from 'util';
|
|
13
|
+
import { isWindows, isWSLAvailable } from './detector.js';
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Docker execution options
|
|
19
|
+
*/
|
|
20
|
+
class DockerOptions {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.timeout = options.timeout || 30000;
|
|
23
|
+
this.wsl = options.wsl !== false; // Default to WSL on Windows
|
|
24
|
+
this.silent = options.silent || false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute Docker command with platform abstraction
|
|
30
|
+
*/
|
|
31
|
+
async function execDockerCommand(args, options = {}) {
|
|
32
|
+
const opts = new DockerOptions(options);
|
|
33
|
+
let command = 'docker';
|
|
34
|
+
let cmdArgs = args;
|
|
35
|
+
let execOptions = { timeout: opts.timeout };
|
|
36
|
+
|
|
37
|
+
// On Windows, try WSL2 if available
|
|
38
|
+
if (isWindows() && opts.wsl) {
|
|
39
|
+
const wslAvailable = await isWSLAvailable();
|
|
40
|
+
if (wslAvailable) {
|
|
41
|
+
command = 'wsl.exe';
|
|
42
|
+
cmdArgs = ['docker', ...args];
|
|
43
|
+
execOptions.windowsHide = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const stdoutChunks = [];
|
|
49
|
+
const stderrChunks = [];
|
|
50
|
+
|
|
51
|
+
const child = spawn(command, cmdArgs, {
|
|
52
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
53
|
+
...execOptions
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
child.stdout.on('data', (chunk) => {
|
|
57
|
+
stdoutChunks.push(chunk);
|
|
58
|
+
if (!opts.silent) {
|
|
59
|
+
process.stdout.write(chunk);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
child.stderr.on('data', (chunk) => {
|
|
64
|
+
stderrChunks.push(chunk);
|
|
65
|
+
if (!opts.silent) {
|
|
66
|
+
process.stderr.write(chunk);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
child.on('close', (code) => {
|
|
71
|
+
const stdout = Buffer.concat(stdoutChunks).toString('utf8').trim();
|
|
72
|
+
const stderr = Buffer.concat(stderrChunks).toString('utf8').trim();
|
|
73
|
+
|
|
74
|
+
if (code === 0) {
|
|
75
|
+
resolve({ stdout, stderr, code });
|
|
76
|
+
} else {
|
|
77
|
+
reject(new Error(`Docker command failed (exit ${code}): ${stderr || stdout}`));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
child.on('error', (err) => {
|
|
82
|
+
reject(new Error(`Failed to execute docker command: ${err.message}`));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Execute command inside a Docker container
|
|
89
|
+
*/
|
|
90
|
+
async function execContainer(containerName, command, options = {}) {
|
|
91
|
+
const execArgs = ['exec', containerName];
|
|
92
|
+
if (options.workDir) {
|
|
93
|
+
execArgs.push('-w', options.workDir);
|
|
94
|
+
}
|
|
95
|
+
if (options.env) {
|
|
96
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
97
|
+
execArgs.push('-e', `${key}=${value}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
execArgs.push('sh', '-c', command);
|
|
101
|
+
|
|
102
|
+
return execDockerCommand(execArgs, options);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if a container exists
|
|
107
|
+
*/
|
|
108
|
+
async function containerExists(containerName) {
|
|
109
|
+
try {
|
|
110
|
+
const result = await execDockerCommand(
|
|
111
|
+
['ps', '-a', '--filter', `name=${containerName}`, '--format', '{{.Names}}'],
|
|
112
|
+
{ silent: true }
|
|
113
|
+
);
|
|
114
|
+
return result.stdout.includes(containerName);
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if a container is running
|
|
122
|
+
*/
|
|
123
|
+
async function isContainerRunning(containerName) {
|
|
124
|
+
try {
|
|
125
|
+
const result = await execDockerCommand(
|
|
126
|
+
['ps', '--filter', `name=${containerName}`, '--format', '{{.Names}}'],
|
|
127
|
+
{ silent: true }
|
|
128
|
+
);
|
|
129
|
+
return result.stdout.includes(containerName);
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get container info
|
|
137
|
+
*/
|
|
138
|
+
async function inspect(containerName) {
|
|
139
|
+
try {
|
|
140
|
+
const result = await execDockerCommand(
|
|
141
|
+
['inspect', containerName],
|
|
142
|
+
{ silent: true }
|
|
143
|
+
);
|
|
144
|
+
return JSON.parse(result.stdout);
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get container IP address
|
|
152
|
+
*/
|
|
153
|
+
async function getContainerIP(containerName) {
|
|
154
|
+
const inspectData = await inspect(containerName);
|
|
155
|
+
if (!inspectData || inspectData.length === 0) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const networkSettings = inspectData[0].NetworkSettings;
|
|
160
|
+
return networkSettings.IPAddress || null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get container port bindings
|
|
165
|
+
*/
|
|
166
|
+
async function getPortBindings(containerName) {
|
|
167
|
+
const inspectData = await inspect(containerName);
|
|
168
|
+
if (!inspectData || inspectData.length === 0) {
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const networkSettings = inspectData[0].NetworkSettings;
|
|
173
|
+
return networkSettings.Ports || {};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Start a container
|
|
178
|
+
*/
|
|
179
|
+
async function start(containerName, options = {}) {
|
|
180
|
+
return execDockerCommand(['start', containerName], options);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Stop a container
|
|
185
|
+
*/
|
|
186
|
+
async function stop(containerName, options = {}) {
|
|
187
|
+
const timeout = options.timeout || 10;
|
|
188
|
+
return execDockerCommand(['stop', '-t', String(timeout), containerName], options);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Restart a container
|
|
193
|
+
*/
|
|
194
|
+
async function restart(containerName, options = {}) {
|
|
195
|
+
return execDockerCommand(['restart', containerName], options);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Remove a container
|
|
200
|
+
*/
|
|
201
|
+
async function rm(containerName, options = {}) {
|
|
202
|
+
const args = ['rm'];
|
|
203
|
+
if (options.force) args.push('-f');
|
|
204
|
+
args.push(containerName);
|
|
205
|
+
return execDockerCommand(args, options);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Run a new container
|
|
210
|
+
*/
|
|
211
|
+
async function run(image, options = {}) {
|
|
212
|
+
const args = ['run'];
|
|
213
|
+
|
|
214
|
+
if (options.detach) args.push('-d');
|
|
215
|
+
if (options.name) args.push('--name', options.name);
|
|
216
|
+
if (options.remove) args.push('--rm');
|
|
217
|
+
|
|
218
|
+
// Port mappings
|
|
219
|
+
if (options.ports) {
|
|
220
|
+
for (const port of options.ports) {
|
|
221
|
+
args.push('-p', port);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Volume mounts
|
|
226
|
+
if (options.volumes) {
|
|
227
|
+
for (const vol of options.volumes) {
|
|
228
|
+
args.push('-v', vol);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Environment variables
|
|
233
|
+
if (options.env) {
|
|
234
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
235
|
+
args.push('-e', `${key}=${value}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Work directory
|
|
240
|
+
if (options.workDir) {
|
|
241
|
+
args.push('-w', options.workDir);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
args.push(image);
|
|
245
|
+
|
|
246
|
+
if (options.command) {
|
|
247
|
+
args.push(...(Array.isArray(options.command) ? options.command : [options.command]));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return execDockerCommand(args, options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Build an image
|
|
255
|
+
*/
|
|
256
|
+
async function build(path, options = {}) {
|
|
257
|
+
const args = ['build'];
|
|
258
|
+
|
|
259
|
+
if (options.tag) {
|
|
260
|
+
args.push('-t', options.tag);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (options.file) {
|
|
264
|
+
args.push('-f', options.file);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
args.push(path);
|
|
268
|
+
|
|
269
|
+
return execDockerCommand(args, options);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Pull an image
|
|
274
|
+
*/
|
|
275
|
+
async function pull(image, options = {}) {
|
|
276
|
+
return execDockerCommand(['pull', image], options);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get Docker version info
|
|
281
|
+
*/
|
|
282
|
+
async function version() {
|
|
283
|
+
try {
|
|
284
|
+
const result = await execDockerCommand(['version', '--format', 'json'], { silent: true });
|
|
285
|
+
return JSON.parse(result.stdout);
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Check if Docker is available
|
|
293
|
+
*/
|
|
294
|
+
async function isAvailable() {
|
|
295
|
+
try {
|
|
296
|
+
await execDockerCommand(['--version'], { silent: true, timeout: 5000 });
|
|
297
|
+
return true;
|
|
298
|
+
} catch {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get list of containers
|
|
305
|
+
*/
|
|
306
|
+
async function ps(options = {}) {
|
|
307
|
+
const args = ['ps'];
|
|
308
|
+
|
|
309
|
+
if (options.all) args.push('-a');
|
|
310
|
+
|
|
311
|
+
args.push('--format', 'json');
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const result = await execDockerCommand(args, { silent: true });
|
|
315
|
+
return result.stdout.split('\n')
|
|
316
|
+
.filter(line => line.trim())
|
|
317
|
+
.map(line => JSON.parse(line));
|
|
318
|
+
} catch {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get container logs
|
|
325
|
+
*/
|
|
326
|
+
async function logs(containerName, options = {}) {
|
|
327
|
+
const args = ['logs'];
|
|
328
|
+
|
|
329
|
+
if (options.follow) args.push('-f');
|
|
330
|
+
if (options.tail) args.push('--tail', String(options.tail));
|
|
331
|
+
if (options.timestamps) args.push('-t');
|
|
332
|
+
|
|
333
|
+
args.push(containerName);
|
|
334
|
+
|
|
335
|
+
return execDockerCommand(args, options);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Copy file to container
|
|
340
|
+
*/
|
|
341
|
+
async function cpTo(localPath, containerName, containerPath, options = {}) {
|
|
342
|
+
return execDockerCommand(['cp', localPath, `${containerName}:${containerPath}`], options);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Copy file from container
|
|
347
|
+
*/
|
|
348
|
+
async function cpFrom(containerName, containerPath, localPath, options = {}) {
|
|
349
|
+
return execDockerCommand(['cp', `${containerName}:${containerPath}`, localPath], options);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export default {
|
|
353
|
+
exec: execContainer,
|
|
354
|
+
containerExists,
|
|
355
|
+
isContainerRunning,
|
|
356
|
+
inspect,
|
|
357
|
+
getContainerIP,
|
|
358
|
+
getPortBindings,
|
|
359
|
+
start,
|
|
360
|
+
stop,
|
|
361
|
+
restart,
|
|
362
|
+
rm,
|
|
363
|
+
run,
|
|
364
|
+
build,
|
|
365
|
+
pull,
|
|
366
|
+
version,
|
|
367
|
+
isAvailable,
|
|
368
|
+
ps,
|
|
369
|
+
logs,
|
|
370
|
+
cpTo,
|
|
371
|
+
cpFrom
|
|
372
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Module
|
|
3
|
+
*
|
|
4
|
+
* Exports platform detection and abstraction layer for cross-platform support.
|
|
5
|
+
* Supports Windows (with WSL2), macOS, and Linux.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Platform detection
|
|
9
|
+
export {
|
|
10
|
+
Platform,
|
|
11
|
+
currentPlatform,
|
|
12
|
+
isWindows,
|
|
13
|
+
isWSLAvailable,
|
|
14
|
+
isDockerDesktopAvailable,
|
|
15
|
+
isPMAvailable,
|
|
16
|
+
getShell,
|
|
17
|
+
executor
|
|
18
|
+
} from './detector.js';
|
|
19
|
+
|
|
20
|
+
// Docker bridge (cross-platform Docker commands)
|
|
21
|
+
export { default as docker } from './docker-bridge.js';
|
|
22
|
+
|
|
23
|
+
// PM2 bridge (cross-platform PM2 commands)
|
|
24
|
+
export { default as pm2 } from './pm2-bridge.js';
|
|
25
|
+
|
|
26
|
+
// Cross-platform path utilities
|
|
27
|
+
export { default as paths } from './paths.js';
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform path utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles platform-specific path differences between Windows, macOS, and Linux.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { join, normalize } from 'path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get home directory in a cross-platform way.
|
|
12
|
+
* On Unix-like systems, this is equivalent to $HOME.
|
|
13
|
+
* On Windows, Node.js' os.homedir() handles USERPROFILE.
|
|
14
|
+
*/
|
|
15
|
+
export function getHomeDir() {
|
|
16
|
+
return homedir();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get AgentWindow home directory.
|
|
21
|
+
* Can be overridden via AGENT_WINDOW_HOME environment variable.
|
|
22
|
+
*/
|
|
23
|
+
export function getAgentWindowHome() {
|
|
24
|
+
return process.env.AGENT_WINDOW_HOME || join(getHomeDir(), '.agent-window');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get user's bots directory.
|
|
29
|
+
* Convention: ~/bots directory for storing bot configurations.
|
|
30
|
+
*/
|
|
31
|
+
export function getBotsDir() {
|
|
32
|
+
return join(getHomeDir(), 'bots');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get Claude's default config directory.
|
|
37
|
+
*/
|
|
38
|
+
export function getClaudeConfigDir() {
|
|
39
|
+
return join(getHomeDir(), '.claude');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Expand a path with ~ (tilde) to absolute path.
|
|
44
|
+
* Handles both ~/path and plain ~.
|
|
45
|
+
*/
|
|
46
|
+
export function expandTilde(path) {
|
|
47
|
+
if (!path) return path;
|
|
48
|
+
if (path.startsWith('~/')) {
|
|
49
|
+
return join(getHomeDir(), path.slice(2));
|
|
50
|
+
}
|
|
51
|
+
if (path === '~') {
|
|
52
|
+
return getHomeDir();
|
|
53
|
+
}
|
|
54
|
+
return path;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Normalize a path for the current platform.
|
|
59
|
+
* On Windows, converts forward slashes to backslashes.
|
|
60
|
+
* On Unix, ensures forward slashes.
|
|
61
|
+
*/
|
|
62
|
+
export function normalizePath(path) {
|
|
63
|
+
return normalize(path);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get platform-specific config directory.
|
|
68
|
+
* On Windows: %APPDATA%/agent-window or ~/.agent-window
|
|
69
|
+
* On Unix: ~/.config/agent-window or ~/.agent-window
|
|
70
|
+
*/
|
|
71
|
+
export function getConfigDir(appName = 'agent-window') {
|
|
72
|
+
if (process.platform === 'win32') {
|
|
73
|
+
// On Windows, prefer APPDATA, fallback to home
|
|
74
|
+
return process.env.APPDATA
|
|
75
|
+
? join(process.env.APPDATA, appName)
|
|
76
|
+
: join(getHomeDir(), `.${appName}`);
|
|
77
|
+
}
|
|
78
|
+
// On Unix, use XDG_CONFIG_DIR if set, otherwise ~/.config
|
|
79
|
+
const configBase = process.env.XDG_CONFIG_HOME || join(getHomeDir(), '.config');
|
|
80
|
+
return join(configBase, appName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get platform-specific data directory.
|
|
85
|
+
* On Windows: %LOCALAPPDATA%/agent-window
|
|
86
|
+
* On macOS: ~/Library/Application Support/agent-window
|
|
87
|
+
* On Linux: ~/.local/share/agent-window
|
|
88
|
+
*/
|
|
89
|
+
export function getDataDir(appName = 'agent-window') {
|
|
90
|
+
if (process.platform === 'win32') {
|
|
91
|
+
return process.env.LOCALAPPDATA
|
|
92
|
+
? join(process.env.LOCALAPPDATA, appName)
|
|
93
|
+
: join(getHomeDir(), `.${appName}`, 'data');
|
|
94
|
+
}
|
|
95
|
+
if (process.platform === 'darwin') {
|
|
96
|
+
return join(getHomeDir(), 'Library', 'Application Support', appName);
|
|
97
|
+
}
|
|
98
|
+
// Linux and others
|
|
99
|
+
const dataBase = process.env.XDG_DATA_HOME || join(getHomeDir(), '.local', 'share');
|
|
100
|
+
return join(dataBase, appName);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default {
|
|
104
|
+
getHomeDir,
|
|
105
|
+
getAgentWindowHome,
|
|
106
|
+
getBotsDir,
|
|
107
|
+
getClaudeConfigDir,
|
|
108
|
+
expandTilde,
|
|
109
|
+
normalizePath,
|
|
110
|
+
getConfigDir,
|
|
111
|
+
getDataDir
|
|
112
|
+
};
|