create-walle 0.3.1 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-walle",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Set up Wall-E — your personal digital twin",
5
5
  "bin": {
6
6
  "create-walle": "bin/create-walle.js"
@@ -243,13 +243,16 @@
243
243
  document.getElementById('api-key').value = '';
244
244
  document.getElementById('api-key').placeholder = '••••••••••••••• (from ' + (d.source || 'environment') + ')';
245
245
  document.getElementById('api-dot').className = 'status-dot ok';
246
- okMsg.textContent = 'API key detected from ' + (d.source || 'environment') + '!';
246
+ okMsg.textContent = 'Detected: ' + (d.source || 'environment') + '!';
247
247
  okMsg.style.display = 'inline';
248
248
  const ownerVal = document.getElementById('owner-name').value.trim();
249
+ const saveBody = { owner_name: ownerVal };
250
+ if (d.gateway) saveBody.gateway = d.gateway;
251
+ else saveBody.api_key = d.key;
249
252
  await fetch('/api/setup/save', {
250
253
  method: 'POST',
251
254
  headers: { 'Content-Type': 'application/json' },
252
- body: JSON.stringify({ owner_name: ownerVal, api_key: d.key }),
255
+ body: JSON.stringify(saveBody),
253
256
  });
254
257
  } else {
255
258
  errMsg.textContent = d.hint || 'No API key found. Enter one manually.';
@@ -228,15 +228,26 @@ function handleApi(req, res, url) {
228
228
  if (url.pathname === '/api/setup/detect-key' && req.method === 'GET') {
229
229
  let key = '';
230
230
  let source = '';
231
+ let gateway = null; // For corporate/Portkey gateway setups
232
+
233
+ // 1. Check for corporate gateway setup (Portkey, cybertron, etc.)
234
+ if (process.env.ANTHROPIC_BASE_URL && process.env.ANTHROPIC_CUSTOM_HEADERS_B64) {
235
+ gateway = {
236
+ base_url: process.env.ANTHROPIC_BASE_URL,
237
+ auth_token: process.env.ANTHROPIC_AUTH_TOKEN || 'sk-ant-api03-unused',
238
+ custom_headers_b64: process.env.ANTHROPIC_CUSTOM_HEADERS_B64,
239
+ };
240
+ source = 'Claude Code gateway (' + process.env.ANTHROPIC_BASE_URL.replace(/https?:\/\//, '').split('/')[0] + ')';
241
+ }
231
242
 
232
- // 1. Check process.env
233
- if (process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.startsWith('sk-ant-')) {
243
+ // 2. Check for direct API key in process.env
244
+ if (!gateway && process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.startsWith('sk-ant-')) {
234
245
  key = process.env.ANTHROPIC_API_KEY;
235
246
  source = 'environment variable';
236
247
  }
237
248
 
238
- // 2. Try shell profile
239
- if (!key) {
249
+ // 3. Try shell profile for direct API key
250
+ if (!gateway && !key) {
240
251
  try {
241
252
  const { execFileSync } = require('child_process');
242
253
  const shell = process.env.SHELL || '/bin/zsh';
@@ -245,8 +256,8 @@ function handleApi(req, res, url) {
245
256
  } catch {}
246
257
  }
247
258
 
248
- // 3. Try Claude Code OAuth token from macOS Keychain
249
- if (!key && process.platform === 'darwin') {
259
+ // 4. Try Claude Code OAuth token from macOS Keychain
260
+ if (!gateway && !key && process.platform === 'darwin') {
250
261
  try {
251
262
  const { execFileSync } = require('child_process');
252
263
  const credJson = execFileSync('security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { encoding: 'utf8', timeout: 3000 }).trim();
@@ -259,7 +270,6 @@ function handleApi(req, res, url) {
259
270
  key = oauth.accessToken;
260
271
  source = 'Claude Code (OAuth)';
261
272
  } else {
262
- // Token expired — tell user to refresh
263
273
  res.writeHead(200, { 'Content-Type': 'application/json' });
264
274
  res.end(JSON.stringify({
265
275
  found: false,
@@ -274,10 +284,10 @@ function handleApi(req, res, url) {
274
284
  }
275
285
 
276
286
  res.writeHead(200, { 'Content-Type': 'application/json' });
277
- if (key) {
287
+ if (gateway) {
288
+ res.end(JSON.stringify({ found: true, gateway, source }));
289
+ } else if (key) {
278
290
  res.end(JSON.stringify({ found: true, key, source }));
279
- } else if (process.env.ANTHROPIC_BASE_URL) {
280
- res.end(JSON.stringify({ found: false, hint: 'Your environment uses an API gateway (' + process.env.ANTHROPIC_BASE_URL + '). You may not need a separate API key — try going to the dashboard.' }));
281
291
  } else {
282
292
  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' }));
283
293
  }
@@ -301,6 +311,8 @@ function handleApi(req, res, url) {
301
311
  const apiKey = typeof data.api_key === 'string'
302
312
  ? data.api_key.replace(/[\r\n\s]/g, '').slice(0, 200)
303
313
  : '';
314
+ // Gateway config (corporate Portkey/cybertron setups)
315
+ const gw = data.gateway;
304
316
  if (apiKey && !/^sk-ant-/.test(apiKey)) {
305
317
  res.writeHead(400, { 'Content-Type': 'application/json' });
306
318
  res.end(JSON.stringify({ error: 'API key must start with sk-ant-' }));
@@ -308,22 +320,40 @@ function handleApi(req, res, url) {
308
320
  }
309
321
  const envPath = path.resolve(__dirname, '..', '.env');
310
322
  const lines = [];
311
- // Read existing .env or start fresh
323
+ // Read existing .env, strip lines we're about to replace
324
+ const stripPatterns = [/^#?\s*WALLE_OWNER_NAME=/, /^#?\s*ANTHROPIC_API_KEY=/, /^#?\s*ANTHROPIC_BASE_URL=/, /^#?\s*ANTHROPIC_AUTH_TOKEN=/, /^#?\s*ANTHROPIC_CUSTOM_HEADERS_B64=/];
312
325
  try {
313
326
  const existing = fs.readFileSync(envPath, 'utf8');
314
327
  for (const line of existing.split('\n')) {
315
- if (line.match(/^#?\s*ANTHROPIC_API_KEY=/) && apiKey) continue;
316
- if (line.match(/^#?\s*WALLE_OWNER_NAME=/) && ownerName) continue;
317
- lines.push(line);
328
+ const shouldStrip = (apiKey || gw) && stripPatterns.some(p => p.test(line));
329
+ if (!shouldStrip || (!apiKey && !gw)) lines.push(line);
330
+ else if (!ownerName || !line.match(/WALLE_OWNER_NAME/)) {
331
+ // Only strip if we have a replacement
332
+ if (stripPatterns.some(p => p.test(line))) continue;
333
+ lines.push(line);
334
+ }
335
+ }
336
+ // Also strip owner name line if we have a new one
337
+ if (ownerName) {
338
+ const idx = lines.findIndex(l => /^#?\s*WALLE_OWNER_NAME=/.test(l));
339
+ if (idx >= 0) lines.splice(idx, 1);
318
340
  }
319
341
  } catch { lines.push('# Wall-E configuration'); lines.push(''); }
320
- // Add values after the header comment
342
+ // Add values
321
343
  if (ownerName) {
322
344
  const insertIdx = lines.findIndex(l => !l.startsWith('#') && l.trim() !== '') || lines.length;
323
345
  lines.splice(insertIdx, 0, `WALLE_OWNER_NAME=${ownerName}`);
324
346
  process.env.WALLE_OWNER_NAME = ownerName;
325
347
  }
326
- if (apiKey) {
348
+ if (gw) {
349
+ // Gateway setup: save all three env vars
350
+ lines.push(`ANTHROPIC_BASE_URL=${gw.base_url}`);
351
+ lines.push(`ANTHROPIC_AUTH_TOKEN=${gw.auth_token}`);
352
+ lines.push(`ANTHROPIC_CUSTOM_HEADERS_B64=${gw.custom_headers_b64}`);
353
+ process.env.ANTHROPIC_BASE_URL = gw.base_url;
354
+ process.env.ANTHROPIC_AUTH_TOKEN = gw.auth_token;
355
+ process.env.ANTHROPIC_CUSTOM_HEADERS_B64 = gw.custom_headers_b64;
356
+ } else if (apiKey) {
327
357
  lines.push(`ANTHROPIC_API_KEY=${apiKey}`);
328
358
  process.env.ANTHROPIC_API_KEY = apiKey;
329
359
  }