create-walle 0.3.3 → 0.4.0
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/create-walle.js +166 -61
- package/package.json +1 -1
package/bin/create-walle.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const { execFileSync,
|
|
4
|
+
const { execFileSync, spawn } = require('child_process');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
|
|
8
8
|
const BOLD = '\x1b[1m';
|
|
9
9
|
const DIM = '\x1b[2m';
|
|
10
10
|
const GREEN = '\x1b[32m';
|
|
11
|
+
const YELLOW = '\x1b[33m';
|
|
11
12
|
const RED = '\x1b[31m';
|
|
12
13
|
const CYAN = '\x1b[36m';
|
|
13
14
|
const RESET = '\x1b[0m';
|
|
14
15
|
|
|
15
16
|
const TEMPLATE_DIR = path.join(__dirname, '..', 'template');
|
|
16
17
|
const LABEL = 'com.walle.server';
|
|
18
|
+
const INSTALL_PATH_FILE = path.join(process.env.HOME, '.walle', 'install-path');
|
|
19
|
+
|
|
20
|
+
// Files to preserve during update (user config, not code)
|
|
21
|
+
const PRESERVE_ON_UPDATE = ['.env', 'wall-e/wall-e-config.json'];
|
|
17
22
|
|
|
18
23
|
// ── CLI Router ──
|
|
19
24
|
|
|
@@ -21,6 +26,8 @@ const command = process.argv[2] || '';
|
|
|
21
26
|
|
|
22
27
|
if (command === 'install') {
|
|
23
28
|
install(process.argv[3]);
|
|
29
|
+
} else if (command === 'update' || command === 'upgrade') {
|
|
30
|
+
update();
|
|
24
31
|
} else if (command === 'start') {
|
|
25
32
|
start();
|
|
26
33
|
} else if (command === 'stop') {
|
|
@@ -33,8 +40,10 @@ if (command === 'install') {
|
|
|
33
40
|
uninstall();
|
|
34
41
|
} else if (command === '--help' || command === '-h' || command === 'help') {
|
|
35
42
|
usage();
|
|
43
|
+
} else if (command === '--version' || command === '-v') {
|
|
44
|
+
const pkg = require('../package.json');
|
|
45
|
+
console.log(pkg.version);
|
|
36
46
|
} else if (command && !command.startsWith('-')) {
|
|
37
|
-
// Backwards compat: bare directory name = install
|
|
38
47
|
install(command);
|
|
39
48
|
} else {
|
|
40
49
|
usage();
|
|
@@ -48,28 +57,46 @@ ${BOLD}${CYAN} Wall-E${RESET} — Your personal digital twin
|
|
|
48
57
|
|
|
49
58
|
${BOLD}Usage:${RESET}
|
|
50
59
|
|
|
51
|
-
${BOLD}npx create-walle install <
|
|
52
|
-
${BOLD}npx create-walle
|
|
53
|
-
${BOLD}npx create-walle
|
|
54
|
-
${BOLD}npx create-walle
|
|
55
|
-
${BOLD}npx create-walle
|
|
56
|
-
${BOLD}npx create-walle
|
|
60
|
+
${BOLD}npx create-walle install <dir>${RESET} Install (or detect existing install and update)
|
|
61
|
+
${BOLD}npx create-walle update${RESET} Update code to latest, keep your config
|
|
62
|
+
${BOLD}npx create-walle start${RESET} Start Wall-E (auto-restarts on reboot)
|
|
63
|
+
${BOLD}npx create-walle stop${RESET} Stop Wall-E
|
|
64
|
+
${BOLD}npx create-walle status${RESET} Check if Wall-E is running
|
|
65
|
+
${BOLD}npx create-walle logs${RESET} Tail the service logs
|
|
66
|
+
${BOLD}npx create-walle uninstall${RESET} Remove the auto-start service
|
|
57
67
|
|
|
58
|
-
${DIM}
|
|
59
|
-
|
|
68
|
+
${DIM}Commands like start/stop/update auto-detect your install directory.
|
|
69
|
+
Or set WALLE_DIR to point to it.${RESET}
|
|
60
70
|
`);
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
function install(targetDir) {
|
|
74
|
+
// If no target dir given, check for existing install
|
|
64
75
|
if (!targetDir) {
|
|
76
|
+
const existing = findWalleDirSilent();
|
|
77
|
+
if (existing) {
|
|
78
|
+
console.log(`\n Found existing install at ${DIM}${existing}${RESET}`);
|
|
79
|
+
console.log(` Running update instead...\n`);
|
|
80
|
+
update();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
65
83
|
console.log(`\n ${RED}Missing directory name.${RESET} Usage: npx create-walle install ./my-agent\n`);
|
|
66
84
|
process.exit(1);
|
|
67
85
|
}
|
|
68
86
|
|
|
87
|
+
// If target dir exists and has Wall-E, treat as update
|
|
88
|
+
if (fs.existsSync(targetDir) && fs.existsSync(path.join(targetDir, 'claude-task-manager', 'server.js'))) {
|
|
89
|
+
console.log(`\n ${CYAN}Wall-E already installed at ${targetDir}.${RESET} Updating...\n`);
|
|
90
|
+
saveWalleDir(path.resolve(targetDir));
|
|
91
|
+
update();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fresh install
|
|
69
96
|
console.log(`\n${BOLD}${CYAN} Wall-E${RESET} — Installing...\n`);
|
|
70
97
|
|
|
71
98
|
if (fs.existsSync(targetDir)) {
|
|
72
|
-
console.log(` ${RED}Directory "${targetDir}"
|
|
99
|
+
console.log(` ${RED}Directory "${targetDir}" exists but is not a Wall-E install.${RESET}\n`);
|
|
73
100
|
process.exit(1);
|
|
74
101
|
}
|
|
75
102
|
if (!fs.existsSync(TEMPLATE_DIR)) {
|
|
@@ -77,7 +104,6 @@ function install(targetDir) {
|
|
|
77
104
|
process.exit(1);
|
|
78
105
|
}
|
|
79
106
|
|
|
80
|
-
// Auto-detect
|
|
81
107
|
const ownerName = detectName().replace(/[\r\n=]/g, '').trim().slice(0, 200);
|
|
82
108
|
const timezone = detectTimezone();
|
|
83
109
|
const nameParts = ownerName.split(/\s+/);
|
|
@@ -88,19 +114,11 @@ function install(targetDir) {
|
|
|
88
114
|
console.log(` ${DIM}Timezone: ${timezone}${RESET}`);
|
|
89
115
|
console.log(` ${DIM}Port: ${port}${RESET}`);
|
|
90
116
|
|
|
91
|
-
// Copy template
|
|
92
117
|
console.log(`\n Copying files...`);
|
|
93
118
|
copyRecursive(TEMPLATE_DIR, targetDir);
|
|
94
119
|
|
|
95
|
-
// Install deps
|
|
96
120
|
console.log(` Installing dependencies...\n`);
|
|
97
|
-
|
|
98
|
-
execFileSync('npm', ['install', '--loglevel=warn'], { cwd: path.join(targetDir, 'claude-task-manager'), stdio: 'inherit' });
|
|
99
|
-
execFileSync('npm', ['install', '--loglevel=warn'], { cwd: path.join(targetDir, 'wall-e'), stdio: 'inherit' });
|
|
100
|
-
} catch {
|
|
101
|
-
console.error(`\n ${RED}npm install failed.${RESET} Try manually:\n cd ${targetDir}/claude-task-manager && npm install\n cd ${targetDir}/wall-e && npm install\n`);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
121
|
+
npmInstall(targetDir);
|
|
104
122
|
|
|
105
123
|
// .env
|
|
106
124
|
const envLines = [
|
|
@@ -108,7 +126,7 @@ function install(targetDir) {
|
|
|
108
126
|
`# Finish setup at http://localhost:${port}/setup.html`,
|
|
109
127
|
'',
|
|
110
128
|
`WALLE_OWNER_NAME=${ownerName}`,
|
|
111
|
-
'# ANTHROPIC_API_KEY=
|
|
129
|
+
'# ANTHROPIC_API_KEY=',
|
|
112
130
|
'',
|
|
113
131
|
`CTM_PORT=${port}`,
|
|
114
132
|
`WALL_E_PORT=${wallePort}`,
|
|
@@ -130,13 +148,9 @@ function install(targetDir) {
|
|
|
130
148
|
{ mode: 0o600 }
|
|
131
149
|
);
|
|
132
150
|
|
|
133
|
-
// Data dir
|
|
134
151
|
fs.mkdirSync(path.join(process.env.HOME, '.walle', 'data'), { recursive: true });
|
|
135
|
-
|
|
136
|
-
// Git init
|
|
137
152
|
try { execFileSync('git', ['init', '-q'], { cwd: targetDir, stdio: 'ignore' }); } catch {}
|
|
138
153
|
|
|
139
|
-
// Save install path for start/stop
|
|
140
154
|
saveWalleDir(path.resolve(targetDir));
|
|
141
155
|
|
|
142
156
|
console.log(`
|
|
@@ -147,40 +161,81 @@ function install(targetDir) {
|
|
|
147
161
|
|
|
148
162
|
${BOLD}Then open:${RESET} http://localhost:${port}
|
|
149
163
|
|
|
150
|
-
${BOLD}Auto-start on login (optional):${RESET}
|
|
151
|
-
npx create-walle start
|
|
152
|
-
|
|
153
164
|
${BOLD}Other commands:${RESET}
|
|
154
|
-
npx create-walle
|
|
155
|
-
npx create-walle
|
|
156
|
-
npx create-walle
|
|
165
|
+
npx create-walle update ${DIM}Update to latest version${RESET}
|
|
166
|
+
npx create-walle start ${DIM}Start as background service${RESET}
|
|
167
|
+
npx create-walle stop ${DIM}Stop the service${RESET}
|
|
168
|
+
npx create-walle status ${DIM}Check status${RESET}
|
|
169
|
+
npx create-walle logs ${DIM}View logs${RESET}
|
|
157
170
|
`);
|
|
158
171
|
}
|
|
159
172
|
|
|
160
|
-
function
|
|
173
|
+
function update() {
|
|
161
174
|
const dir = findWalleDir();
|
|
162
175
|
const port = readPort(dir);
|
|
176
|
+
const pkg = require('../package.json');
|
|
177
|
+
|
|
178
|
+
console.log(`${BOLD}${CYAN} Wall-E${RESET} — Updating to v${pkg.version}...\n`);
|
|
179
|
+
console.log(` ${DIM}Directory: ${dir}${RESET}`);
|
|
163
180
|
|
|
181
|
+
if (!fs.existsSync(TEMPLATE_DIR)) {
|
|
182
|
+
console.error(' Template not found. Try: npm cache clean --force && npx create-walle@latest update');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 1. Stop if running
|
|
187
|
+
console.log(` Stopping Wall-E...`);
|
|
188
|
+
stopQuiet(dir, port);
|
|
189
|
+
|
|
190
|
+
// 2. Backup preserved files
|
|
191
|
+
const backups = {};
|
|
192
|
+
for (const relPath of PRESERVE_ON_UPDATE) {
|
|
193
|
+
const fullPath = path.join(dir, relPath);
|
|
194
|
+
if (fs.existsSync(fullPath)) {
|
|
195
|
+
backups[relPath] = fs.readFileSync(fullPath);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 3. Copy new code (overwrite everything except node_modules, .git, data)
|
|
200
|
+
console.log(` Updating code...`);
|
|
201
|
+
copyRecursive(TEMPLATE_DIR, dir);
|
|
202
|
+
|
|
203
|
+
// 4. Restore preserved files
|
|
204
|
+
for (const [relPath, content] of Object.entries(backups)) {
|
|
205
|
+
const fullPath = path.join(dir, relPath);
|
|
206
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
207
|
+
fs.writeFileSync(fullPath, content);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 5. Reinstall deps (in case package.json changed)
|
|
211
|
+
console.log(` Installing dependencies...\n`);
|
|
212
|
+
npmInstall(dir);
|
|
213
|
+
|
|
214
|
+
// 6. Start again
|
|
215
|
+
console.log(`\n Starting Wall-E...`);
|
|
216
|
+
startForegroundOrService(dir, port);
|
|
217
|
+
|
|
218
|
+
console.log(`
|
|
219
|
+
${GREEN}${BOLD}Updated to v${pkg.version}!${RESET} http://localhost:${port}
|
|
220
|
+
|
|
221
|
+
${DIM}Your .env and config were preserved.${RESET}
|
|
222
|
+
`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function start() {
|
|
226
|
+
const dir = findWalleDir();
|
|
227
|
+
const port = readPort(dir);
|
|
164
228
|
console.log(`\n Starting Wall-E from ${DIM}${dir}${RESET} on port ${port}...`);
|
|
165
229
|
installService(dir, port);
|
|
166
230
|
console.log(` ${GREEN}Running!${RESET} http://localhost:${port}\n`);
|
|
167
231
|
}
|
|
168
232
|
|
|
169
233
|
function stop() {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
} else {
|
|
176
|
-
// Fallback: try killing the process
|
|
177
|
-
try {
|
|
178
|
-
const dir = findWalleDir();
|
|
179
|
-
const port = readPort(dir);
|
|
180
|
-
execFileSync('curl', ['-sX', 'POST', `http://localhost:${port}/api/stop/walle`], { timeout: 3000 });
|
|
181
|
-
} catch {}
|
|
182
|
-
console.log(`\n Stopped (or was not running).\n`);
|
|
183
|
-
}
|
|
234
|
+
const dir = findWalleDirSilent();
|
|
235
|
+
const port = dir ? readPort(dir) : '3456';
|
|
236
|
+
stopQuiet(dir, port);
|
|
237
|
+
console.log(`\n ${GREEN}Wall-E stopped.${RESET}`);
|
|
238
|
+
console.log(` Run ${BOLD}npx create-walle start${RESET} to restart.\n`);
|
|
184
239
|
}
|
|
185
240
|
|
|
186
241
|
function status() {
|
|
@@ -213,12 +268,52 @@ function uninstall() {
|
|
|
213
268
|
const plist = path.join(process.env.HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
214
269
|
try { execFileSync('launchctl', ['unload', plist], { stdio: 'ignore' }); } catch {}
|
|
215
270
|
try { fs.unlinkSync(plist); } catch {}
|
|
216
|
-
try { fs.unlinkSync(
|
|
271
|
+
try { fs.unlinkSync(INSTALL_PATH_FILE); } catch {}
|
|
217
272
|
console.log(`\n ${GREEN}Service uninstalled.${RESET} Your data in ~/.walle/data is preserved.\n`);
|
|
218
273
|
}
|
|
219
274
|
|
|
220
275
|
// ── Service Management ──
|
|
221
276
|
|
|
277
|
+
function stopQuiet(dir, port) {
|
|
278
|
+
// Try launchctl first
|
|
279
|
+
const plist = path.join(process.env.HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
280
|
+
if (process.platform === 'darwin' && fs.existsSync(plist)) {
|
|
281
|
+
try { execFileSync('launchctl', ['unload', plist], { stdio: 'ignore' }); } catch {}
|
|
282
|
+
}
|
|
283
|
+
// Also try API
|
|
284
|
+
try { execFileSync('curl', ['-sX', 'POST', `http://localhost:${port}/api/restart/ctm`], { timeout: 2000 }); } catch {}
|
|
285
|
+
// Wait for port to free up
|
|
286
|
+
for (let i = 0; i < 10; i++) {
|
|
287
|
+
try {
|
|
288
|
+
execFileSync('curl', ['-s', '--max-time', '1', `http://localhost:${port}/api/services/status`], { timeout: 2000 });
|
|
289
|
+
// Still running, wait
|
|
290
|
+
execFileSync('sleep', ['0.5']);
|
|
291
|
+
} catch {
|
|
292
|
+
break; // Port is free
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function startForegroundOrService(dir, port) {
|
|
298
|
+
// If launchd plist exists, reload it. Otherwise start in background.
|
|
299
|
+
const plist = path.join(process.env.HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
300
|
+
if (process.platform === 'darwin' && fs.existsSync(plist)) {
|
|
301
|
+
// Update plist with latest paths, then reload
|
|
302
|
+
installService(dir, port);
|
|
303
|
+
} else {
|
|
304
|
+
// Start in background without launchd
|
|
305
|
+
const logDir = path.join(process.env.HOME, '.walle', 'logs');
|
|
306
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
307
|
+
const child = spawn('node', ['claude-task-manager/server.js'], {
|
|
308
|
+
cwd: dir,
|
|
309
|
+
detached: true,
|
|
310
|
+
stdio: ['ignore', fs.openSync(path.join(logDir, 'walle.log'), 'a'), fs.openSync(path.join(logDir, 'walle.err'), 'a')],
|
|
311
|
+
env: { ...process.env, CTM_PORT: port, WALL_E_PORT: String(parseInt(port) + 1) },
|
|
312
|
+
});
|
|
313
|
+
child.unref();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
222
317
|
function installService(walleDir, port) {
|
|
223
318
|
const logDir = path.join(process.env.HOME, '.walle', 'logs');
|
|
224
319
|
fs.mkdirSync(logDir, { recursive: true });
|
|
@@ -229,7 +324,6 @@ function installService(walleDir, port) {
|
|
|
229
324
|
fs.mkdirSync(plistDir, { recursive: true });
|
|
230
325
|
const plistPath = path.join(plistDir, `${LABEL}.plist`);
|
|
231
326
|
|
|
232
|
-
// Build env vars from .env file
|
|
233
327
|
let envDict = ` <key>PATH</key>\n <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>\n`;
|
|
234
328
|
envDict += ` <key>HOME</key>\n <string>${process.env.HOME}</string>\n`;
|
|
235
329
|
envDict += ` <key>CTM_PORT</key>\n <string>${port}</string>\n`;
|
|
@@ -274,7 +368,6 @@ ${envDict} </dict>
|
|
|
274
368
|
try { execFileSync('launchctl', ['unload', plistPath], { stdio: 'ignore' }); } catch {}
|
|
275
369
|
execFileSync('launchctl', ['load', plistPath]);
|
|
276
370
|
} else {
|
|
277
|
-
// Linux/other: just start directly in background
|
|
278
371
|
const child = spawn('node', ['claude-task-manager/server.js'], {
|
|
279
372
|
cwd: walleDir,
|
|
280
373
|
detached: true,
|
|
@@ -288,30 +381,42 @@ ${envDict} </dict>
|
|
|
288
381
|
|
|
289
382
|
// ── Helpers ──
|
|
290
383
|
|
|
384
|
+
function npmInstall(dir) {
|
|
385
|
+
try {
|
|
386
|
+
execFileSync('npm', ['install', '--loglevel=warn'], { cwd: path.join(dir, 'claude-task-manager'), stdio: 'inherit' });
|
|
387
|
+
execFileSync('npm', ['install', '--loglevel=warn'], { cwd: path.join(dir, 'wall-e'), stdio: 'inherit' });
|
|
388
|
+
} catch {
|
|
389
|
+
console.error(`\n ${RED}npm install failed.${RESET} Try manually:\n cd ${dir}/claude-task-manager && npm install\n cd ${dir}/wall-e && npm install\n`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
291
394
|
function saveWalleDir(dir) {
|
|
292
395
|
const metaDir = path.join(process.env.HOME, '.walle');
|
|
293
396
|
fs.mkdirSync(metaDir, { recursive: true });
|
|
294
|
-
fs.writeFileSync(
|
|
397
|
+
fs.writeFileSync(INSTALL_PATH_FILE, dir + '\n');
|
|
295
398
|
}
|
|
296
399
|
|
|
297
400
|
function findWalleDir() {
|
|
298
|
-
|
|
401
|
+
const dir = findWalleDirSilent();
|
|
402
|
+
if (dir) return dir;
|
|
403
|
+
console.error(`\n ${RED}Can't find Wall-E installation.${RESET}`);
|
|
404
|
+
console.error(` Run ${BOLD}npx create-walle install ./my-agent${RESET} first.\n`);
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function findWalleDirSilent() {
|
|
299
409
|
if (process.env.WALLE_DIR && fs.existsSync(path.join(process.env.WALLE_DIR, 'claude-task-manager'))) {
|
|
300
410
|
return process.env.WALLE_DIR;
|
|
301
411
|
}
|
|
302
|
-
// 2. Current directory
|
|
303
412
|
if (fs.existsSync(path.join(process.cwd(), 'claude-task-manager', 'server.js'))) {
|
|
304
413
|
return process.cwd();
|
|
305
414
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (fs.existsSync(saved)) {
|
|
309
|
-
const dir = fs.readFileSync(saved, 'utf8').trim();
|
|
415
|
+
if (fs.existsSync(INSTALL_PATH_FILE)) {
|
|
416
|
+
const dir = fs.readFileSync(INSTALL_PATH_FILE, 'utf8').trim();
|
|
310
417
|
if (fs.existsSync(path.join(dir, 'claude-task-manager', 'server.js'))) return dir;
|
|
311
418
|
}
|
|
312
|
-
|
|
313
|
-
console.error(` Run from inside the Wall-E directory, or set WALLE_DIR.\n`);
|
|
314
|
-
process.exit(1);
|
|
419
|
+
return null;
|
|
315
420
|
}
|
|
316
421
|
|
|
317
422
|
function readPort(dir) {
|
|
@@ -320,7 +425,7 @@ function readPort(dir) {
|
|
|
320
425
|
const m = env.match(/^CTM_PORT=(\d+)/m);
|
|
321
426
|
if (m) return m[1];
|
|
322
427
|
} catch {}
|
|
323
|
-
return '
|
|
428
|
+
return '3456';
|
|
324
429
|
}
|
|
325
430
|
|
|
326
431
|
function detectName() {
|