create-walle 0.1.1 → 0.2.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 +276 -61
- package/package.json +1 -1
- package/template/CHANGELOG.md +44 -0
- package/template/bin/github-polish.sh +18 -0
- package/template/bin/install-service.sh +72 -0
- package/template/claude-task-manager/public/css/walle.css +9 -8
- package/template/claude-task-manager/public/index.html +1 -0
- package/template/claude-task-manager/public/setup.html +38 -1
- package/template/claude-task-manager/server.js +59 -0
- package/template/docs/site/astro.config.mjs +28 -0
- package/template/docs/site/package.json +19 -0
- package/template/docs/site/src/content/docs/api.md +189 -0
- package/template/docs/site/src/content/docs/guides/claude-code.md +62 -0
- package/template/docs/site/src/content/docs/guides/configuration.md +100 -0
- package/template/docs/site/src/content/docs/guides/quickstart.md +162 -0
- package/template/docs/site/src/content/docs/index.mdx +32 -0
- package/template/docs/site/src/content/docs/skills.md +137 -0
- package/template/docs/site/src/content.config.ts +7 -0
- package/template/package.json +11 -0
- package/template/wall-e/skills/_bundled/morning-briefing/run.js +110 -56
package/bin/create-walle.js
CHANGED
|
@@ -1,134 +1,349 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const { execFileSync } = require('child_process');
|
|
4
|
+
const { execFileSync, execFile, 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 RED = '\x1b[31m';
|
|
11
12
|
const CYAN = '\x1b[36m';
|
|
12
13
|
const RESET = '\x1b[0m';
|
|
13
14
|
|
|
14
|
-
// Template directory bundled inside this npm package
|
|
15
15
|
const TEMPLATE_DIR = path.join(__dirname, '..', 'template');
|
|
16
|
+
const LABEL = 'com.walle.server';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
try { return execFileSync('git', ['config', 'user.name'], { encoding: 'utf8' }).trim(); } catch {}
|
|
19
|
-
try { return execFileSync('id', ['-F'], { encoding: 'utf8' }).trim(); } catch {} // macOS full name
|
|
20
|
-
return process.env.USER || 'Owner';
|
|
21
|
-
}
|
|
18
|
+
// ── CLI Router ──
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const command = process.argv[2] || '';
|
|
21
|
+
|
|
22
|
+
if (command === 'install') {
|
|
23
|
+
install(process.argv[3]);
|
|
24
|
+
} else if (command === 'start') {
|
|
25
|
+
start();
|
|
26
|
+
} else if (command === 'stop') {
|
|
27
|
+
stop();
|
|
28
|
+
} else if (command === 'status') {
|
|
29
|
+
status();
|
|
30
|
+
} else if (command === 'logs') {
|
|
31
|
+
logs();
|
|
32
|
+
} else if (command === 'uninstall') {
|
|
33
|
+
uninstall();
|
|
34
|
+
} else if (command === '--help' || command === '-h' || command === 'help') {
|
|
35
|
+
usage();
|
|
36
|
+
} else if (command && !command.startsWith('-')) {
|
|
37
|
+
// Backwards compat: bare directory name = install
|
|
38
|
+
install(command);
|
|
39
|
+
} else {
|
|
40
|
+
usage();
|
|
26
41
|
}
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
// ── Commands ──
|
|
44
|
+
|
|
45
|
+
function usage() {
|
|
46
|
+
console.log(`
|
|
47
|
+
${BOLD}${CYAN} Wall-E${RESET} — Your personal digital twin
|
|
48
|
+
|
|
49
|
+
${BOLD}Usage:${RESET}
|
|
50
|
+
|
|
51
|
+
${BOLD}npx create-walle install <directory>${RESET} Install Wall-E and start the service
|
|
52
|
+
${BOLD}npx create-walle start${RESET} Start Wall-E (auto-restarts on reboot)
|
|
53
|
+
${BOLD}npx create-walle stop${RESET} Stop Wall-E
|
|
54
|
+
${BOLD}npx create-walle status${RESET} Check if Wall-E is running
|
|
55
|
+
${BOLD}npx create-walle logs${RESET} Tail the service logs
|
|
56
|
+
${BOLD}npx create-walle uninstall${RESET} Remove the auto-start service
|
|
57
|
+
|
|
58
|
+
${DIM}Run 'start' and 'stop' from inside your Wall-E directory,
|
|
59
|
+
or set WALLE_DIR to point to it.${RESET}
|
|
60
|
+
`);
|
|
39
61
|
}
|
|
40
62
|
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
function install(targetDir) {
|
|
64
|
+
if (!targetDir) {
|
|
65
|
+
console.log(`\n ${RED}Missing directory name.${RESET} Usage: npx create-walle install ./my-agent\n`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
43
68
|
|
|
44
|
-
console.log(`\n${BOLD}${CYAN} Wall-E${RESET} —
|
|
69
|
+
console.log(`\n${BOLD}${CYAN} Wall-E${RESET} — Installing...\n`);
|
|
45
70
|
|
|
46
71
|
if (fs.existsSync(targetDir)) {
|
|
47
|
-
console.log(` Directory "${targetDir}" already exists
|
|
72
|
+
console.log(` ${RED}Directory "${targetDir}" already exists.${RESET}\n`);
|
|
48
73
|
process.exit(1);
|
|
49
74
|
}
|
|
50
|
-
|
|
51
75
|
if (!fs.existsSync(TEMPLATE_DIR)) {
|
|
52
|
-
console.error(' Template
|
|
76
|
+
console.error(' Template not found. Try: npm cache clean --force && npx create-walle@latest');
|
|
53
77
|
process.exit(1);
|
|
54
78
|
}
|
|
55
79
|
|
|
56
|
-
// Auto-detect
|
|
80
|
+
// Auto-detect
|
|
57
81
|
const ownerName = detectName().replace(/[\r\n=]/g, '').trim().slice(0, 200);
|
|
58
82
|
const timezone = detectTimezone();
|
|
59
83
|
const nameParts = ownerName.split(/\s+/);
|
|
84
|
+
const port = process.env.WALLE_PORT || '4567';
|
|
60
85
|
|
|
61
86
|
console.log(` ${DIM}Owner: ${ownerName}${RESET}`);
|
|
62
87
|
console.log(` ${DIM}Timezone: ${timezone}${RESET}`);
|
|
88
|
+
console.log(` ${DIM}Port: ${port}${RESET}`);
|
|
63
89
|
|
|
64
90
|
// Copy template
|
|
65
91
|
console.log(`\n Copying files...`);
|
|
66
92
|
copyRecursive(TEMPLATE_DIR, targetDir);
|
|
67
93
|
|
|
68
|
-
// Install
|
|
94
|
+
// Install deps
|
|
69
95
|
console.log(` Installing dependencies...\n`);
|
|
70
96
|
try {
|
|
71
97
|
execFileSync('npm', ['install', '--loglevel=warn'], { cwd: path.join(targetDir, 'claude-task-manager'), stdio: 'inherit' });
|
|
72
98
|
execFileSync('npm', ['install', '--loglevel=warn'], { cwd: path.join(targetDir, 'wall-e'), stdio: 'inherit' });
|
|
73
|
-
} catch
|
|
74
|
-
console.error(`\n
|
|
99
|
+
} catch {
|
|
100
|
+
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`);
|
|
75
101
|
process.exit(1);
|
|
76
102
|
}
|
|
77
103
|
|
|
78
|
-
//
|
|
104
|
+
// .env
|
|
79
105
|
const envLines = [
|
|
80
106
|
`# Wall-E configuration — generated ${new Date().toISOString().split('T')[0]}`,
|
|
81
|
-
`#
|
|
107
|
+
`# Finish setup at http://localhost:${port}/setup.html`,
|
|
82
108
|
'',
|
|
83
109
|
`WALLE_OWNER_NAME=${ownerName}`,
|
|
84
110
|
'# ANTHROPIC_API_KEY=sk-ant-...',
|
|
85
111
|
'',
|
|
86
|
-
|
|
112
|
+
`CTM_PORT=${port}`,
|
|
113
|
+
'',
|
|
87
114
|
'# SLACK_TOKEN=',
|
|
88
115
|
'# SLACK_OWNER_USER_ID=',
|
|
89
116
|
'# SLACK_OWNER_HANDLE=',
|
|
90
117
|
];
|
|
91
118
|
fs.writeFileSync(path.join(targetDir, '.env'), envLines.join('\n') + '\n', { mode: 0o600 });
|
|
92
119
|
|
|
93
|
-
//
|
|
94
|
-
const walleConfig = {
|
|
95
|
-
owner: {
|
|
96
|
-
name: ownerName,
|
|
97
|
-
first_name: nameParts[0],
|
|
98
|
-
last_name: nameParts.slice(1).join(' '),
|
|
99
|
-
timezone,
|
|
100
|
-
},
|
|
101
|
-
adapters: { ctm: { enabled: true }, slack: { enabled: false } },
|
|
102
|
-
intervals: { ingest_ms: 60000, think_ms: 120000 },
|
|
103
|
-
};
|
|
120
|
+
// wall-e-config.json
|
|
104
121
|
fs.writeFileSync(
|
|
105
122
|
path.join(targetDir, 'wall-e', 'wall-e-config.json'),
|
|
106
|
-
JSON.stringify(
|
|
123
|
+
JSON.stringify({
|
|
124
|
+
owner: { name: ownerName, first_name: nameParts[0], last_name: nameParts.slice(1).join(' '), timezone },
|
|
125
|
+
adapters: { ctm: { enabled: true }, slack: { enabled: false } },
|
|
126
|
+
intervals: { ingest_ms: 60000, think_ms: 120000 },
|
|
127
|
+
}, null, 2) + '\n',
|
|
107
128
|
{ mode: 0o600 }
|
|
108
129
|
);
|
|
109
130
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
131
|
+
// Data dir
|
|
132
|
+
fs.mkdirSync(path.join(process.env.HOME, '.walle', 'data'), { recursive: true });
|
|
113
133
|
|
|
114
|
-
//
|
|
115
|
-
execFileSync('git', ['init', '-q'], { cwd: targetDir, stdio: 'ignore' });
|
|
134
|
+
// Git init
|
|
135
|
+
try { execFileSync('git', ['init', '-q'], { cwd: targetDir, stdio: 'ignore' }); } catch {}
|
|
136
|
+
|
|
137
|
+
// Save install path for start/stop
|
|
138
|
+
saveWalleDir(path.resolve(targetDir));
|
|
139
|
+
|
|
140
|
+
// Install and start the service
|
|
141
|
+
console.log(`\n Starting Wall-E service...`);
|
|
142
|
+
installService(path.resolve(targetDir), port);
|
|
116
143
|
|
|
117
144
|
console.log(`
|
|
118
|
-
${GREEN}${BOLD}
|
|
145
|
+
${GREEN}${BOLD}Wall-E is running!${RESET}
|
|
119
146
|
|
|
120
|
-
${BOLD}
|
|
121
|
-
cd ${targetDir} && node claude-task-manager/server.js
|
|
147
|
+
${BOLD}Open:${RESET} http://localhost:${port}
|
|
122
148
|
|
|
123
|
-
${BOLD}
|
|
124
|
-
|
|
149
|
+
${BOLD}Manage:${RESET}
|
|
150
|
+
npx create-walle stop ${DIM}Stop the service${RESET}
|
|
151
|
+
npx create-walle start ${DIM}Start / restart${RESET}
|
|
152
|
+
npx create-walle status ${DIM}Check status${RESET}
|
|
153
|
+
npx create-walle logs ${DIM}View logs${RESET}
|
|
125
154
|
|
|
126
|
-
${BOLD}
|
|
127
|
-
|
|
155
|
+
${BOLD}Change port:${RESET}
|
|
156
|
+
Edit ${targetDir}/.env → CTM_PORT=8080
|
|
157
|
+
npx create-walle start
|
|
128
158
|
`);
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
|
|
132
|
-
|
|
161
|
+
function start() {
|
|
162
|
+
const dir = findWalleDir();
|
|
163
|
+
const port = readPort(dir);
|
|
164
|
+
|
|
165
|
+
console.log(`\n Starting Wall-E from ${DIM}${dir}${RESET} on port ${port}...`);
|
|
166
|
+
installService(dir, port);
|
|
167
|
+
console.log(` ${GREEN}Running!${RESET} http://localhost:${port}\n`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function stop() {
|
|
171
|
+
const plist = path.join(process.env.HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
172
|
+
if (process.platform === 'darwin' && fs.existsSync(plist)) {
|
|
173
|
+
try { execFileSync('launchctl', ['unload', plist], { stdio: 'ignore' }); } catch {}
|
|
174
|
+
console.log(`\n ${GREEN}Wall-E stopped.${RESET}`);
|
|
175
|
+
console.log(` Run ${BOLD}npx create-walle start${RESET} to restart.\n`);
|
|
176
|
+
} else {
|
|
177
|
+
// Fallback: try killing the process
|
|
178
|
+
try {
|
|
179
|
+
const dir = findWalleDir();
|
|
180
|
+
const port = readPort(dir);
|
|
181
|
+
execFileSync('curl', ['-sX', 'POST', `http://localhost:${port}/api/stop/walle`], { timeout: 3000 });
|
|
182
|
+
} catch {}
|
|
183
|
+
console.log(`\n Stopped (or was not running).\n`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function status() {
|
|
188
|
+
const dir = findWalleDir();
|
|
189
|
+
const port = readPort(dir);
|
|
190
|
+
try {
|
|
191
|
+
const result = execFileSync('curl', ['-s', `http://localhost:${port}/api/services/status`], { encoding: 'utf8', timeout: 3000 });
|
|
192
|
+
const data = JSON.parse(result);
|
|
193
|
+
console.log(`\n ${GREEN}${BOLD}Wall-E is running${RESET} on port ${port}`);
|
|
194
|
+
console.log(` ${DIM}CTM: PID ${data.ctm?.pid || '?'}, uptime ${Math.round((data.ctm?.uptime || 0) / 60)}m${RESET}`);
|
|
195
|
+
console.log(` ${DIM}Wall-E: PID ${data.walle?.pid || '?'}${RESET}`);
|
|
196
|
+
console.log(` ${DIM}Dir: ${dir}${RESET}\n`);
|
|
197
|
+
} catch {
|
|
198
|
+
console.log(`\n ${RED}Wall-E is not running${RESET} (port ${port})`);
|
|
199
|
+
console.log(` Run ${BOLD}npx create-walle start${RESET} to start.\n`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function logs() {
|
|
204
|
+
const logFile = path.join(process.env.HOME, '.walle', 'logs', 'walle.log');
|
|
205
|
+
if (!fs.existsSync(logFile)) {
|
|
206
|
+
console.log(`\n No logs yet at ${logFile}\n`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const child = spawn('tail', ['-f', logFile], { stdio: 'inherit' });
|
|
210
|
+
child.on('error', () => console.log(` Could not tail ${logFile}`));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function uninstall() {
|
|
214
|
+
const plist = path.join(process.env.HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
215
|
+
try { execFileSync('launchctl', ['unload', plist], { stdio: 'ignore' }); } catch {}
|
|
216
|
+
try { fs.unlinkSync(plist); } catch {}
|
|
217
|
+
try { fs.unlinkSync(path.join(process.env.HOME, '.walle', 'install-path')); } catch {}
|
|
218
|
+
console.log(`\n ${GREEN}Service uninstalled.${RESET} Your data in ~/.walle/data is preserved.\n`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── Service Management ──
|
|
222
|
+
|
|
223
|
+
function installService(walleDir, port) {
|
|
224
|
+
const logDir = path.join(process.env.HOME, '.walle', 'logs');
|
|
225
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
226
|
+
|
|
227
|
+
if (process.platform === 'darwin') {
|
|
228
|
+
const nodePath = process.execPath;
|
|
229
|
+
const plistDir = path.join(process.env.HOME, 'Library', 'LaunchAgents');
|
|
230
|
+
fs.mkdirSync(plistDir, { recursive: true });
|
|
231
|
+
const plistPath = path.join(plistDir, `${LABEL}.plist`);
|
|
232
|
+
|
|
233
|
+
// Build env vars from .env file
|
|
234
|
+
let envDict = ` <key>PATH</key>\n <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>\n`;
|
|
235
|
+
envDict += ` <key>HOME</key>\n <string>${process.env.HOME}</string>\n`;
|
|
236
|
+
envDict += ` <key>CTM_PORT</key>\n <string>${port}</string>\n`;
|
|
237
|
+
try {
|
|
238
|
+
const envFile = fs.readFileSync(path.join(walleDir, '.env'), 'utf8');
|
|
239
|
+
for (const line of envFile.split('\n')) {
|
|
240
|
+
const m = line.match(/^\s*([^#=\s]+)\s*=\s*(.+?)\s*$/);
|
|
241
|
+
if (m && m[1] !== 'CTM_PORT' && m[1] !== 'PATH' && m[1] !== 'HOME') {
|
|
242
|
+
envDict += ` <key>${m[1]}</key>\n <string>${m[2]}</string>\n`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch {}
|
|
246
|
+
|
|
247
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
248
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
249
|
+
<plist version="1.0">
|
|
250
|
+
<dict>
|
|
251
|
+
<key>Label</key>
|
|
252
|
+
<string>${LABEL}</string>
|
|
253
|
+
<key>ProgramArguments</key>
|
|
254
|
+
<array>
|
|
255
|
+
<string>${nodePath}</string>
|
|
256
|
+
<string>${walleDir}/claude-task-manager/server.js</string>
|
|
257
|
+
</array>
|
|
258
|
+
<key>WorkingDirectory</key>
|
|
259
|
+
<string>${walleDir}</string>
|
|
260
|
+
<key>EnvironmentVariables</key>
|
|
261
|
+
<dict>
|
|
262
|
+
${envDict} </dict>
|
|
263
|
+
<key>RunAtLoad</key>
|
|
264
|
+
<true/>
|
|
265
|
+
<key>KeepAlive</key>
|
|
266
|
+
<true/>
|
|
267
|
+
<key>StandardOutPath</key>
|
|
268
|
+
<string>${logDir}/walle.log</string>
|
|
269
|
+
<key>StandardErrorPath</key>
|
|
270
|
+
<string>${logDir}/walle.err</string>
|
|
271
|
+
</dict>
|
|
272
|
+
</plist>`;
|
|
273
|
+
|
|
274
|
+
fs.writeFileSync(plistPath, plist);
|
|
275
|
+
try { execFileSync('launchctl', ['unload', plistPath], { stdio: 'ignore' }); } catch {}
|
|
276
|
+
execFileSync('launchctl', ['load', plistPath]);
|
|
277
|
+
} else {
|
|
278
|
+
// Linux/other: just start directly in background
|
|
279
|
+
const child = spawn('node', ['claude-task-manager/server.js'], {
|
|
280
|
+
cwd: walleDir,
|
|
281
|
+
detached: true,
|
|
282
|
+
stdio: ['ignore', fs.openSync(path.join(logDir, 'walle.log'), 'a'), fs.openSync(path.join(logDir, 'walle.err'), 'a')],
|
|
283
|
+
env: { ...process.env, CTM_PORT: port },
|
|
284
|
+
});
|
|
285
|
+
child.unref();
|
|
286
|
+
fs.writeFileSync(path.join(logDir, 'walle.pid'), String(child.pid));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ── Helpers ──
|
|
291
|
+
|
|
292
|
+
function saveWalleDir(dir) {
|
|
293
|
+
const metaDir = path.join(process.env.HOME, '.walle');
|
|
294
|
+
fs.mkdirSync(metaDir, { recursive: true });
|
|
295
|
+
fs.writeFileSync(path.join(metaDir, 'install-path'), dir + '\n');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function findWalleDir() {
|
|
299
|
+
// 1. WALLE_DIR env
|
|
300
|
+
if (process.env.WALLE_DIR && fs.existsSync(path.join(process.env.WALLE_DIR, 'claude-task-manager'))) {
|
|
301
|
+
return process.env.WALLE_DIR;
|
|
302
|
+
}
|
|
303
|
+
// 2. Current directory
|
|
304
|
+
if (fs.existsSync(path.join(process.cwd(), 'claude-task-manager', 'server.js'))) {
|
|
305
|
+
return process.cwd();
|
|
306
|
+
}
|
|
307
|
+
// 3. Saved install path
|
|
308
|
+
const saved = path.join(process.env.HOME, '.walle', 'install-path');
|
|
309
|
+
if (fs.existsSync(saved)) {
|
|
310
|
+
const dir = fs.readFileSync(saved, 'utf8').trim();
|
|
311
|
+
if (fs.existsSync(path.join(dir, 'claude-task-manager', 'server.js'))) return dir;
|
|
312
|
+
}
|
|
313
|
+
console.error(`\n ${RED}Can't find Wall-E installation.${RESET}`);
|
|
314
|
+
console.error(` Run from inside the Wall-E directory, or set WALLE_DIR.\n`);
|
|
133
315
|
process.exit(1);
|
|
134
|
-
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function readPort(dir) {
|
|
319
|
+
try {
|
|
320
|
+
const env = fs.readFileSync(path.join(dir, '.env'), 'utf8');
|
|
321
|
+
const m = env.match(/^CTM_PORT=(\d+)/m);
|
|
322
|
+
if (m) return m[1];
|
|
323
|
+
} catch {}
|
|
324
|
+
return '4567';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function detectName() {
|
|
328
|
+
try { return execFileSync('git', ['config', 'user.name'], { encoding: 'utf8' }).trim(); } catch {}
|
|
329
|
+
try { return execFileSync('id', ['-F'], { encoding: 'utf8' }).trim(); } catch {}
|
|
330
|
+
return process.env.USER || 'Owner';
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function detectTimezone() {
|
|
334
|
+
try { return Intl.DateTimeFormat().resolvedOptions().timeZone; } catch {}
|
|
335
|
+
return 'UTC';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function copyRecursive(src, dest) {
|
|
339
|
+
const stat = fs.statSync(src);
|
|
340
|
+
if (stat.isDirectory()) {
|
|
341
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
342
|
+
for (const entry of fs.readdirSync(src)) {
|
|
343
|
+
if (entry === 'node_modules' || entry === '.git') continue;
|
|
344
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
fs.copyFileSync(src, dest);
|
|
348
|
+
}
|
|
349
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1 (2026-04-02)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Remove personal name from SKILL.md author fields and spec docs
|
|
7
|
+
- Deprecate npm 0.1.0 (contained personal data in author fields)
|
|
8
|
+
|
|
9
|
+
## 0.1.0 (2026-04-02)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Browser-based setup wizard** — auto-detects owner name from `git config` and timezone from system; first visit redirects to `/setup.html` for API key and Slack OAuth
|
|
13
|
+
- **`npx create-walle` scaffolder** — zero-prompt install with bundled template
|
|
14
|
+
- **Documentation site** — quickstart, configuration, Claude Code integration, skill catalog, API reference
|
|
15
|
+
- **README.md** — architecture diagram, quickstart, config table, API overview
|
|
16
|
+
- **Claude Code skill registry** — `claude-code-skill.md` for MCP server integration
|
|
17
|
+
- **Security hardening** — input validation on setup save, .env injection protection, body size limits, API key format checks, cached `needsSetup()`
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Replaced all hardcoded personal data with env vars (`SLACK_OWNER_USER_ID`, `SLACK_OWNER_HANDLE`, `WALLE_OWNER_NAME`)
|
|
21
|
+
- Default data directory changed from `~/Dropbox/...` to `~/.walle/data`
|
|
22
|
+
- Genericized test data (personal names replaced with `Test Owner`/`TestUser`)
|
|
23
|
+
- Git history scrubbed of sensitive strings via `filter-branch`
|
|
24
|
+
|
|
25
|
+
## Pre-release (2026-01 to 2026-04)
|
|
26
|
+
|
|
27
|
+
### Wall-E Agent
|
|
28
|
+
- **Phase 5** — Cloud deployment (Dockerfile, Fly.io, HTTP server)
|
|
29
|
+
- **Phase 4** — iMessage channel, Slack DM, agent-to-agent protocol
|
|
30
|
+
- **Phase 3** — Chat interface with tool-use, confidence scoring, actions UI
|
|
31
|
+
- **Phase 2** — Contradiction detection, reflect loop, knowledge extraction
|
|
32
|
+
- **Phase 1** — Brain DB (SQLite), adapters, ingest + think loops
|
|
33
|
+
- **Slack integration** — 20,933 messages ingested (2022-2026), backfill + incremental sync
|
|
34
|
+
- **Skills system** — 7 bundled skills (calendar, Slack, email, morning briefing, memory search)
|
|
35
|
+
- **MCP client** — Call Slack, SendGrid, and any MCP server from skills
|
|
36
|
+
- **Chat persistence** — Conversation history stored in brain DB
|
|
37
|
+
|
|
38
|
+
### Claude Task Manager (CTM)
|
|
39
|
+
- Terminal multiplexer for Claude Code sessions
|
|
40
|
+
- Rich-text prompt editor with versioning, folders, composite prompts
|
|
41
|
+
- Code review module with diff viewer and inline comments
|
|
42
|
+
- AI-powered session search and analysis
|
|
43
|
+
- Permission manager with natural language rules
|
|
44
|
+
- Task dashboard with recurring tasks and live logs
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Run this after authenticating gh as the ShanniLi account:
|
|
3
|
+
# gh auth login
|
|
4
|
+
# Or manually set these in GitHub Settings > General
|
|
5
|
+
|
|
6
|
+
gh repo edit ShanniLi/tools \
|
|
7
|
+
--description "Wall-E — your personal digital twin. AI agent that learns from Slack, email & calendar. Includes CTM dashboard." \
|
|
8
|
+
--homepage "https://walle.sh" \
|
|
9
|
+
--add-topic "ai" \
|
|
10
|
+
--add-topic "digital-twin" \
|
|
11
|
+
--add-topic "claude" \
|
|
12
|
+
--add-topic "personal-assistant" \
|
|
13
|
+
--add-topic "slack" \
|
|
14
|
+
--add-topic "sqlite" \
|
|
15
|
+
--add-topic "nodejs"
|
|
16
|
+
|
|
17
|
+
echo "Done. Set social preview image manually at:"
|
|
18
|
+
echo " https://github.com/ShanniLi/tools/settings"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Install Wall-E as a macOS Launch Agent so it starts automatically on login.
|
|
3
|
+
# Usage: bash bin/install-service.sh [--uninstall]
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
ROOT="$(dirname "$SCRIPT_DIR")"
|
|
8
|
+
LABEL="com.walle.server"
|
|
9
|
+
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
|
|
10
|
+
PORT="${CTM_PORT:-4567}"
|
|
11
|
+
|
|
12
|
+
if [[ "$1" == "--uninstall" ]]; then
|
|
13
|
+
launchctl unload "$PLIST" 2>/dev/null || true
|
|
14
|
+
rm -f "$PLIST"
|
|
15
|
+
echo "Wall-E service uninstalled."
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# Source .env to get the port
|
|
20
|
+
if [[ -f "$ROOT/.env" ]]; then
|
|
21
|
+
export $(grep -v '^#' "$ROOT/.env" | grep '=' | xargs)
|
|
22
|
+
PORT="${CTM_PORT:-4567}"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
NODE=$(which node)
|
|
26
|
+
LOG_DIR="$HOME/.walle/logs"
|
|
27
|
+
mkdir -p "$LOG_DIR"
|
|
28
|
+
|
|
29
|
+
cat > "$PLIST" << EOF
|
|
30
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
31
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
32
|
+
<plist version="1.0">
|
|
33
|
+
<dict>
|
|
34
|
+
<key>Label</key>
|
|
35
|
+
<string>$LABEL</string>
|
|
36
|
+
<key>ProgramArguments</key>
|
|
37
|
+
<array>
|
|
38
|
+
<string>$NODE</string>
|
|
39
|
+
<string>$ROOT/claude-task-manager/server.js</string>
|
|
40
|
+
</array>
|
|
41
|
+
<key>WorkingDirectory</key>
|
|
42
|
+
<string>$ROOT</string>
|
|
43
|
+
<key>EnvironmentVariables</key>
|
|
44
|
+
<dict>
|
|
45
|
+
<key>PATH</key>
|
|
46
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
47
|
+
<key>HOME</key>
|
|
48
|
+
<string>$HOME</string>
|
|
49
|
+
<key>CTM_PORT</key>
|
|
50
|
+
<string>$PORT</string>
|
|
51
|
+
</dict>
|
|
52
|
+
<key>RunAtLoad</key>
|
|
53
|
+
<true/>
|
|
54
|
+
<key>KeepAlive</key>
|
|
55
|
+
<true/>
|
|
56
|
+
<key>StandardOutPath</key>
|
|
57
|
+
<string>$LOG_DIR/walle.log</string>
|
|
58
|
+
<key>StandardErrorPath</key>
|
|
59
|
+
<string>$LOG_DIR/walle.err</string>
|
|
60
|
+
</dict>
|
|
61
|
+
</plist>
|
|
62
|
+
EOF
|
|
63
|
+
|
|
64
|
+
launchctl unload "$PLIST" 2>/dev/null || true
|
|
65
|
+
launchctl load "$PLIST"
|
|
66
|
+
|
|
67
|
+
echo "Wall-E installed as a macOS service."
|
|
68
|
+
echo ""
|
|
69
|
+
echo " Status: launchctl list | grep walle"
|
|
70
|
+
echo " Logs: tail -f ~/.walle/logs/walle.log"
|
|
71
|
+
echo " Dashboard: http://localhost:$PORT"
|
|
72
|
+
echo " Uninstall: bash bin/install-service.sh --uninstall"
|
|
@@ -408,23 +408,24 @@
|
|
|
408
408
|
|
|
409
409
|
/* Markdown rendered output */
|
|
410
410
|
.we-task-output-md {
|
|
411
|
-
padding:
|
|
411
|
+
padding: 12px 16px;
|
|
412
412
|
overflow-y: auto;
|
|
413
|
-
max-height:
|
|
413
|
+
max-height: 600px;
|
|
414
414
|
}
|
|
415
415
|
.we-task-md {
|
|
416
416
|
font-size: 13px; line-height: 1.6; color: #ccc;
|
|
417
417
|
}
|
|
418
418
|
.we-task-md h1, .we-task-md h2, .we-task-md h3 {
|
|
419
|
-
color: #e2e8f0;
|
|
419
|
+
color: #e2e8f0; font-weight: 600;
|
|
420
420
|
}
|
|
421
|
-
.we-task-md h1 { font-size:
|
|
422
|
-
.we-task-md h2 { font-size: 14px; }
|
|
423
|
-
.we-task-md h3 { font-size: 13px; color: #94a3b8; }
|
|
421
|
+
.we-task-md h1 { font-size: 17px; border-bottom: 1px solid rgba(255,255,255,0.08); padding-bottom: 6px; margin: 16px 0 10px; }
|
|
422
|
+
.we-task-md h2 { font-size: 14px; margin: 18px 0 8px; color: #93c5fd; }
|
|
423
|
+
.we-task-md h3 { font-size: 13px; color: #94a3b8; margin: 12px 0 6px; }
|
|
424
424
|
.we-task-md p { margin: 6px 0; }
|
|
425
425
|
.we-task-md ul, .we-task-md ol { margin: 6px 0; padding-left: 20px; }
|
|
426
|
-
.we-task-md li { margin:
|
|
427
|
-
.we-task-md li::marker { color: #
|
|
426
|
+
.we-task-md li { margin: 4px 0; line-height: 1.55; }
|
|
427
|
+
.we-task-md li::marker { color: #444; }
|
|
428
|
+
.we-task-md li strong { color: #93c5fd; }
|
|
428
429
|
.we-task-md code {
|
|
429
430
|
font-family: 'SF Mono', 'Menlo', 'Monaco', monospace;
|
|
430
431
|
font-size: 12px; background: rgba(255,255,255,0.06);
|
|
@@ -2239,6 +2239,7 @@
|
|
|
2239
2239
|
<button class="nav-pill" data-nav="codereview" onclick="navTo('codereview')" title="Code Review">Review</button>
|
|
2240
2240
|
<button class="nav-pill" data-nav="walle" onclick="navTo('walle')" title="WALL-E Agent">WALL-E</button>
|
|
2241
2241
|
<button class="nav-pill" data-nav="rules" onclick="navTo('rules')" title="Edit CLAUDE.md rules">Rules</button>
|
|
2242
|
+
<a href="/setup.html" class="nav-pill" title="Setup & Settings" style="text-decoration:none;margin-left:auto;font-size:16px;padding:4px 8px;">⚙</a>
|
|
2242
2243
|
</nav>
|
|
2243
2244
|
<div class="topbar-right">
|
|
2244
2245
|
<span id="topbar-context-btns"></span>
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
</div>
|
|
75
75
|
<div class="btn-row">
|
|
76
76
|
<button class="btn btn-primary" id="save-btn" onclick="saveConfig()">Save & Continue</button>
|
|
77
|
+
<button class="btn btn-secondary" id="detect-btn" onclick="detectKey()" title="Check your shell environment for an existing API key">Detect from environment</button>
|
|
77
78
|
<span class="success-msg" id="save-ok">Saved!</span>
|
|
78
79
|
<span class="error-msg" id="save-err"></span>
|
|
79
80
|
</div>
|
|
@@ -120,7 +121,8 @@
|
|
|
120
121
|
|
|
121
122
|
<!-- Done -->
|
|
122
123
|
<div class="done-section">
|
|
123
|
-
<a href="/" id="done-link">Go to Dashboard →</a>
|
|
124
|
+
<a href="/index.html" id="done-link">Go to Dashboard →</a>
|
|
125
|
+
<div style="margin-top:8px;font-size:12px;color:var(--dim)">You can return to this page anytime from the settings icon in the nav bar.</div>
|
|
124
126
|
</div>
|
|
125
127
|
</div>
|
|
126
128
|
|
|
@@ -210,6 +212,41 @@
|
|
|
210
212
|
}
|
|
211
213
|
}
|
|
212
214
|
|
|
215
|
+
async function detectKey() {
|
|
216
|
+
const btn = document.getElementById('detect-btn');
|
|
217
|
+
const errMsg = document.getElementById('save-err');
|
|
218
|
+
const okMsg = document.getElementById('save-ok');
|
|
219
|
+
errMsg.style.display = 'none';
|
|
220
|
+
okMsg.style.display = 'none';
|
|
221
|
+
btn.disabled = true;
|
|
222
|
+
btn.textContent = 'Checking...';
|
|
223
|
+
try {
|
|
224
|
+
const r = await fetch('/api/setup/detect-key');
|
|
225
|
+
const d = await r.json();
|
|
226
|
+
if (d.found) {
|
|
227
|
+
document.getElementById('api-key').value = '';
|
|
228
|
+
document.getElementById('api-key').placeholder = '••••••••••••••• (from ' + (d.source || 'environment') + ')';
|
|
229
|
+
document.getElementById('api-dot').className = 'status-dot ok';
|
|
230
|
+
okMsg.textContent = 'API key detected from ' + (d.source || 'environment') + '!';
|
|
231
|
+
okMsg.style.display = 'inline';
|
|
232
|
+
const ownerVal = document.getElementById('owner-name').value.trim();
|
|
233
|
+
await fetch('/api/setup/save', {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
headers: { 'Content-Type': 'application/json' },
|
|
236
|
+
body: JSON.stringify({ owner_name: ownerVal, api_key: d.key }),
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
errMsg.textContent = d.hint || 'No API key found. Enter one manually.';
|
|
240
|
+
errMsg.style.display = 'inline';
|
|
241
|
+
}
|
|
242
|
+
} catch (e) {
|
|
243
|
+
errMsg.textContent = e.message;
|
|
244
|
+
errMsg.style.display = 'inline';
|
|
245
|
+
}
|
|
246
|
+
btn.textContent = 'Detect from environment';
|
|
247
|
+
btn.disabled = false;
|
|
248
|
+
}
|
|
249
|
+
|
|
213
250
|
loadStatus();
|
|
214
251
|
</script>
|
|
215
252
|
</body>
|