create-walle 0.3.1 → 0.3.3

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.3",
4
4
  "description": "Set up Wall-E — your personal digital twin",
5
5
  "bin": {
6
6
  "create-walle": "bin/create-walle.js"
@@ -159,13 +159,6 @@
159
159
  btn.disabled = false;
160
160
  return;
161
161
  }
162
- if (apiVal && !apiVal.startsWith('sk-ant-')) {
163
- errMsg.textContent = 'API key should start with sk-ant-';
164
- errMsg.style.display = 'inline';
165
- btn.disabled = false;
166
- return;
167
- }
168
-
169
162
  const body = { owner_name: ownerVal, api_key: apiVal };
170
163
 
171
164
  try {
@@ -243,13 +236,16 @@
243
236
  document.getElementById('api-key').value = '';
244
237
  document.getElementById('api-key').placeholder = '••••••••••••••• (from ' + (d.source || 'environment') + ')';
245
238
  document.getElementById('api-dot').className = 'status-dot ok';
246
- okMsg.textContent = 'API key detected from ' + (d.source || 'environment') + '!';
239
+ okMsg.textContent = 'Detected: ' + (d.source || 'environment') + '!';
247
240
  okMsg.style.display = 'inline';
248
241
  const ownerVal = document.getElementById('owner-name').value.trim();
242
+ const saveBody = { owner_name: ownerVal };
243
+ if (d.gateway) saveBody.gateway = d.gateway;
244
+ else saveBody.api_key = d.key;
249
245
  await fetch('/api/setup/save', {
250
246
  method: 'POST',
251
247
  headers: { 'Content-Type': 'application/json' },
252
- body: JSON.stringify({ owner_name: ownerVal, api_key: d.key }),
248
+ body: JSON.stringify(saveBody),
253
249
  });
254
250
  } else {
255
251
  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,29 +311,45 @@ 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
  : '';
304
- if (apiKey && !/^sk-ant-/.test(apiKey)) {
305
- res.writeHead(400, { 'Content-Type': 'application/json' });
306
- res.end(JSON.stringify({ error: 'API key must start with sk-ant-' }));
307
- return;
308
- }
314
+ // Gateway config (corporate Portkey/cybertron setups)
315
+ const gw = data.gateway;
316
+ // Accept any non-empty key (Anthropic, Portkey, or other providers)
309
317
  const envPath = path.resolve(__dirname, '..', '.env');
310
318
  const lines = [];
311
- // Read existing .env or start fresh
319
+ // Read existing .env, strip lines we're about to replace
320
+ const stripPatterns = [/^#?\s*WALLE_OWNER_NAME=/, /^#?\s*ANTHROPIC_API_KEY=/, /^#?\s*ANTHROPIC_BASE_URL=/, /^#?\s*ANTHROPIC_AUTH_TOKEN=/, /^#?\s*ANTHROPIC_CUSTOM_HEADERS_B64=/];
312
321
  try {
313
322
  const existing = fs.readFileSync(envPath, 'utf8');
314
323
  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);
324
+ const shouldStrip = (apiKey || gw) && stripPatterns.some(p => p.test(line));
325
+ if (!shouldStrip || (!apiKey && !gw)) lines.push(line);
326
+ else if (!ownerName || !line.match(/WALLE_OWNER_NAME/)) {
327
+ // Only strip if we have a replacement
328
+ if (stripPatterns.some(p => p.test(line))) continue;
329
+ lines.push(line);
330
+ }
331
+ }
332
+ // Also strip owner name line if we have a new one
333
+ if (ownerName) {
334
+ const idx = lines.findIndex(l => /^#?\s*WALLE_OWNER_NAME=/.test(l));
335
+ if (idx >= 0) lines.splice(idx, 1);
318
336
  }
319
337
  } catch { lines.push('# Wall-E configuration'); lines.push(''); }
320
- // Add values after the header comment
338
+ // Add values
321
339
  if (ownerName) {
322
340
  const insertIdx = lines.findIndex(l => !l.startsWith('#') && l.trim() !== '') || lines.length;
323
341
  lines.splice(insertIdx, 0, `WALLE_OWNER_NAME=${ownerName}`);
324
342
  process.env.WALLE_OWNER_NAME = ownerName;
325
343
  }
326
- if (apiKey) {
344
+ if (gw) {
345
+ // Gateway setup: save all three env vars
346
+ lines.push(`ANTHROPIC_BASE_URL=${gw.base_url}`);
347
+ lines.push(`ANTHROPIC_AUTH_TOKEN=${gw.auth_token}`);
348
+ lines.push(`ANTHROPIC_CUSTOM_HEADERS_B64=${gw.custom_headers_b64}`);
349
+ process.env.ANTHROPIC_BASE_URL = gw.base_url;
350
+ process.env.ANTHROPIC_AUTH_TOKEN = gw.auth_token;
351
+ process.env.ANTHROPIC_CUSTOM_HEADERS_B64 = gw.custom_headers_b64;
352
+ } else if (apiKey) {
327
353
  lines.push(`ANTHROPIC_API_KEY=${apiKey}`);
328
354
  process.env.ANTHROPIC_API_KEY = apiKey;
329
355
  }