create-walle 0.4.3 → 0.4.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-walle",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Set up Wall-E — your personal digital twin",
5
5
  "bin": {
6
6
  "create-walle": "bin/create-walle.js"
@@ -123,6 +123,7 @@
123
123
  <div class="done-section">
124
124
  <a href="/index.html" id="done-link">Go to Dashboard →</a>
125
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>
126
+ <div id="version-label" style="margin-top:16px;font-size:11px;color:#30363d"></div>
126
127
  </div>
127
128
  </div>
128
129
 
@@ -139,6 +140,9 @@
139
140
  if (d.slack_connected) {
140
141
  document.getElementById('slack-btn').outerHTML = '<span class="badge badge-connected">Connected</span>';
141
142
  }
143
+ if (d.version) {
144
+ document.getElementById('version-label').textContent = 'Wall-E v' + d.version;
145
+ }
142
146
  } catch {}
143
147
  }
144
148
 
@@ -202,44 +202,58 @@ function handleApi(req, res, url) {
202
202
  slackConnected = fs.existsSync(tokPath);
203
203
  } catch {}
204
204
  res.writeHead(200, { 'Content-Type': 'application/json' });
205
- res.end(JSON.stringify({ owner_name: ownerName, has_api_key: hasApiKey, slack_connected: slackConnected, needs_setup: setup.needsSetup() }));
205
+ let version = '';
206
+ try { version = require('../package.json').version; } catch {}
207
+ res.end(JSON.stringify({ owner_name: ownerName, has_api_key: hasApiKey, slack_connected: slackConnected, needs_setup: setup.needsSetup(), version }));
206
208
  return;
207
209
  }
208
210
  if (url.pathname === '/api/setup/detect-key' && req.method === 'GET') {
211
+ // Detect API credentials from external sources (NOT .env — that's what we're trying to populate).
212
+ // Priority: 1) devbox auth file, 2) shell environment, 3) Claude Code keychain
213
+ const { execFileSync } = require('child_process');
209
214
  let key = '';
210
215
  let source = '';
211
- let gateway = null; // For corporate/Portkey gateway setups
212
-
213
- // 1. Check for corporate gateway setup (Portkey, cybertron, etc.)
214
- if (process.env.ANTHROPIC_BASE_URL && process.env.ANTHROPIC_CUSTOM_HEADERS_B64) {
215
- gateway = {
216
- base_url: process.env.ANTHROPIC_BASE_URL,
217
- auth_token: process.env.ANTHROPIC_AUTH_TOKEN || 'sk-ant-api03-unused',
218
- custom_headers_b64: process.env.ANTHROPIC_CUSTOM_HEADERS_B64,
219
- };
220
- source = 'Claude Code gateway (' + process.env.ANTHROPIC_BASE_URL.replace(/https?:\/\//, '').split('/')[0] + ')';
221
- }
216
+ let gateway = null;
222
217
 
223
- // 2. Check for direct API key in process.env
224
- if (!gateway && process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.startsWith('sk-ant-')) {
225
- key = process.env.ANTHROPIC_API_KEY;
226
- source = 'environment variable';
218
+ // 1. Devbox corporate gateway (reads auth headers file directly)
219
+ if (process.platform === 'darwin') {
220
+ try {
221
+ const authFile = path.join(process.env.HOME, '.devbox', 'secrets', 'claude', 'auth_headers');
222
+ const headers = fs.readFileSync(authFile, 'utf8').trim();
223
+ if (headers) {
224
+ let baseUrl = '';
225
+ try {
226
+ const claudeScript = fs.readFileSync(path.join(process.env.HOME, '.devbox', 'ai', 'claude', 'claude'), 'utf8');
227
+ const m = claudeScript.match(/VPN_CHECK_URL="(https?:\/\/[^"]+)"/);
228
+ if (m) baseUrl = m[1] + '/v1';
229
+ } catch {}
230
+ if (baseUrl) {
231
+ gateway = {
232
+ base_url: baseUrl,
233
+ auth_token: 'sk-ant-api03-unused',
234
+ custom_headers_b64: Buffer.from(headers).toString('base64'),
235
+ };
236
+ source = 'Claude Code devbox (' + baseUrl.replace(/https?:\/\//, '').split('/')[0] + ')';
237
+ }
238
+ }
239
+ } catch {}
227
240
  }
228
241
 
229
- // 3. Try shell profile for direct API key
242
+ // 2. Shell environment (fresh from login shell, not inherited .env)
230
243
  if (!gateway && !key) {
231
244
  try {
232
- const { execFileSync } = require('child_process');
233
245
  const shell = process.env.SHELL || '/bin/zsh';
234
- const result = execFileSync(shell, ['-ilc', 'echo $ANTHROPIC_API_KEY'], { encoding: 'utf8', timeout: 3000 }).trim();
235
- if (result && result.startsWith('sk-ant-')) { key = result; source = 'shell profile'; }
246
+ const result = execFileSync(shell, ['-ilc', 'echo "$ANTHROPIC_API_KEY"'], { encoding: 'utf8', timeout: 5000 }).trim();
247
+ if (result && result.length > 5 && result !== 'sk-ant-api03-unused') {
248
+ key = result;
249
+ source = 'shell environment';
250
+ }
236
251
  } catch {}
237
252
  }
238
253
 
239
- // 4. Try Claude Code OAuth token from macOS Keychain
254
+ // 3. Claude Code OAuth token from macOS Keychain
240
255
  if (!gateway && !key && process.platform === 'darwin') {
241
256
  try {
242
- const { execFileSync } = require('child_process');
243
257
  const credJson = execFileSync('security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { encoding: 'utf8', timeout: 3000 }).trim();
244
258
  if (credJson) {
245
259
  const cred = JSON.parse(credJson);
@@ -251,11 +265,7 @@ function handleApi(req, res, url) {
251
265
  source = 'Claude Code (OAuth)';
252
266
  } else {
253
267
  res.writeHead(200, { 'Content-Type': 'application/json' });
254
- res.end(JSON.stringify({
255
- found: false,
256
- claude_code_expired: true,
257
- hint: 'Found Claude Code, but the OAuth token has expired. Run "claude" in your terminal once to refresh it, then click Detect again.'
258
- }));
268
+ res.end(JSON.stringify({ found: false, claude_code_expired: true, hint: 'Found Claude Code, but the OAuth token has expired. Run "claude" in your terminal once to refresh it, then click Detect again.' }));
259
269
  return;
260
270
  }
261
271
  }
@@ -269,7 +279,7 @@ function handleApi(req, res, url) {
269
279
  } else if (key) {
270
280
  res.end(JSON.stringify({ found: true, key, source }));
271
281
  } else {
272
- res.end(JSON.stringify({ found: false, hint: 'No API key found. Checked: environment variables, shell profile, Claude Code keychain. You can get a key at console.anthropic.com' }));
282
+ res.end(JSON.stringify({ found: false, hint: 'No API key found. Checked: devbox auth, shell environment, Claude Code keychain. Enter your key manually or get one at console.anthropic.com' }));
273
283
  }
274
284
  return;
275
285
  }
@@ -339,6 +349,10 @@ function handleApi(req, res, url) {
339
349
  }
340
350
  fs.writeFileSync(envPath, lines.join('\n') + '\n', { mode: 0o600 });
341
351
  setup.clearSetupCache(); // so next / request goes to dashboard
352
+ // Restart Wall-E so it picks up the new env vars from .env
353
+ if (apiKey || gw) {
354
+ _restartWalleQuiet();
355
+ }
342
356
  res.writeHead(200, { 'Content-Type': 'application/json' });
343
357
  res.end(JSON.stringify({ ok: true }));
344
358
  } catch (e) {
@@ -2181,6 +2195,24 @@ function apiStartWalle(req, res) {
2181
2195
  });
2182
2196
  }
2183
2197
 
2198
+ // Silent Wall-E restart (no HTTP response needed) — used after saving API key
2199
+ function _restartWalleQuiet() {
2200
+ const walleDir = path.join(__dirname, '..', 'wall-e');
2201
+ const agentScript = path.join(walleDir, 'agent.js');
2202
+ execFile('lsof', ['-ti', ':' + WALLE_PORT], (err, stdout) => {
2203
+ const pids = (stdout || '').trim().split('\n').filter(Boolean);
2204
+ for (const pid of pids) { try { process.kill(parseInt(pid), 'SIGTERM'); } catch {} }
2205
+ setTimeout(() => {
2206
+ const child = require('child_process').spawn(
2207
+ process.execPath, [agentScript],
2208
+ { cwd: walleDir, detached: true, stdio: 'ignore', env: { ...process.env, WALL_E_PORT: String(WALLE_PORT) } }
2209
+ );
2210
+ child.unref();
2211
+ console.log('[setup] Restarted Wall-E (PID ' + child.pid + ') to pick up new API config');
2212
+ }, 1000);
2213
+ });
2214
+ }
2215
+
2184
2216
  function apiRestartCtm(req, res) {
2185
2217
  res.writeHead(200, { 'Content-Type': 'application/json' });
2186
2218
  res.end(JSON.stringify({ ok: true, message: 'CTM server restarting...' }));