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.
Files changed (53) hide show
  1. package/bin/cli.js +45 -0
  2. package/docs/WEB_UI_GUIDE.md +249 -0
  3. package/package.json +11 -2
  4. package/scripts/test-platform.js +109 -0
  5. package/src/api/routes/index.js +25 -0
  6. package/src/api/routes/instances.js +252 -0
  7. package/src/api/routes/operations.js +118 -0
  8. package/src/api/routes/system.js +42 -0
  9. package/src/api/server.js +147 -0
  10. package/src/api/websocket/index.js +16 -0
  11. package/src/api/websocket/logs.js +127 -0
  12. package/src/cli/commands/add.js +80 -0
  13. package/src/cli/commands/config.js +192 -0
  14. package/src/cli/commands/index.js +89 -0
  15. package/src/cli/commands/info.js +94 -0
  16. package/src/cli/commands/list.js +72 -0
  17. package/src/cli/commands/logs.js +67 -0
  18. package/src/cli/commands/remove.js +97 -0
  19. package/src/cli/commands/restart.js +67 -0
  20. package/src/cli/commands/start.js +101 -0
  21. package/src/cli/commands/status.js +95 -0
  22. package/src/cli/commands/stop.js +53 -0
  23. package/src/cli/commands/ui.js +51 -0
  24. package/src/cli/index.js +110 -0
  25. package/src/core/config.js +5 -10
  26. package/src/core/instance/backup-manager.js +172 -0
  27. package/src/core/instance/config-manager.js +279 -0
  28. package/src/core/instance/index.js +62 -0
  29. package/src/core/instance/manager.js +220 -0
  30. package/src/core/instance/pm2-bridge.js +205 -0
  31. package/src/core/instance/validator.js +161 -0
  32. package/src/core/platform/detector.js +142 -0
  33. package/src/core/platform/docker-bridge.js +372 -0
  34. package/src/core/platform/index.js +27 -0
  35. package/src/core/platform/paths.js +112 -0
  36. package/src/core/platform/pm2-bridge.js +314 -0
  37. package/web/dist/assets/Dashboard-C1smB9Nj.js +1 -0
  38. package/web/dist/assets/Dashboard-ezbZMSpZ.css +1 -0
  39. package/web/dist/assets/InstanceDetail-CRPMV7rg.css +1 -0
  40. package/web/dist/assets/InstanceDetail-C_Ddtrog.js +3 -0
  41. package/web/dist/assets/Instances-CvnH8iDv.css +1 -0
  42. package/web/dist/assets/Instances-_u2__M83.js +1 -0
  43. package/web/dist/assets/Settings-CAu3R9RW.css +1 -0
  44. package/web/dist/assets/Settings-CIa9MX7m.js +1 -0
  45. package/web/dist/assets/_plugin-vue_export-helper-DlAUqK2U.js +1 -0
  46. package/web/dist/assets/element-plus-Jr6qTeY5.js +37 -0
  47. package/web/dist/assets/main-CalRvcyG.css +1 -0
  48. package/web/dist/assets/main-D3cdXAiV.js +7 -0
  49. package/web/dist/assets/vue-vendor-CGSlMM3Y.js +29 -0
  50. package/web/dist/index.html +16 -0
  51. package/SECURITY.md +0 -31
  52. package/docs/legacy/DEVELOPMENT.md +0 -174
  53. 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
+ };