create-walle 0.4.5 → 0.5.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 +18 -1
- package/package.json +1 -1
- package/template/claude-task-manager/public/css/walle.css +2 -0
- package/template/claude-task-manager/public/js/walle.js +48 -1
- package/template/claude-task-manager/public/setup.html +55 -30
- package/template/claude-task-manager/server.js +23 -8
- package/template/package.json +1 -1
package/bin/create-walle.js
CHANGED
|
@@ -151,6 +151,9 @@ function install(targetDir) {
|
|
|
151
151
|
fs.mkdirSync(path.join(process.env.HOME, '.walle', 'data'), { recursive: true });
|
|
152
152
|
try { execFileSync('git', ['init', '-q'], { cwd: targetDir, stdio: 'ignore' }); } catch {}
|
|
153
153
|
|
|
154
|
+
// Stamp version into root package.json so the settings page shows it
|
|
155
|
+
stampVersion(targetDir);
|
|
156
|
+
|
|
154
157
|
saveWalleDir(path.resolve(targetDir));
|
|
155
158
|
|
|
156
159
|
// Start the service
|
|
@@ -208,7 +211,10 @@ function update() {
|
|
|
208
211
|
fs.writeFileSync(fullPath, content);
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
// 5.
|
|
214
|
+
// 5. Stamp version
|
|
215
|
+
stampVersion(dir);
|
|
216
|
+
|
|
217
|
+
// 6. Reinstall deps (in case package.json changed)
|
|
212
218
|
console.log(` Installing dependencies...\n`);
|
|
213
219
|
npmInstall(dir);
|
|
214
220
|
|
|
@@ -392,6 +398,17 @@ function npmInstall(dir) {
|
|
|
392
398
|
}
|
|
393
399
|
}
|
|
394
400
|
|
|
401
|
+
function stampVersion(dir) {
|
|
402
|
+
try {
|
|
403
|
+
const ver = require('../package.json').version;
|
|
404
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
405
|
+
let pkg = {};
|
|
406
|
+
try { pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch {}
|
|
407
|
+
pkg.version = ver;
|
|
408
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
409
|
+
} catch {}
|
|
410
|
+
}
|
|
411
|
+
|
|
395
412
|
function saveWalleDir(dir) {
|
|
396
413
|
const metaDir = path.join(process.env.HOME, '.walle');
|
|
397
414
|
fs.mkdirSync(metaDir, { recursive: true });
|
package/package.json
CHANGED
|
@@ -82,6 +82,8 @@
|
|
|
82
82
|
}
|
|
83
83
|
.walle-btn:hover { background: rgba(255,255,255,0.08); }
|
|
84
84
|
.walle-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
85
|
+
.walle-btn.danger { color: #f85149; border-color: #f8514933; }
|
|
86
|
+
.walle-btn.danger:hover { background: #f8514918; border-color: #f85149; }
|
|
85
87
|
|
|
86
88
|
/* Loading / Empty */
|
|
87
89
|
.walle-loading { text-align: center; padding: 40px; color: var(--fg-muted, #888); font-size: 13px; }
|
|
@@ -778,6 +778,7 @@ function renderChatUI() {
|
|
|
778
778
|
html += '<span class="we-export-count">' + chatSelected.size + ' selected</span>';
|
|
779
779
|
html += '<button class="walle-btn" onclick="WE._exportAsText()">Copy as Text</button>';
|
|
780
780
|
html += '<button class="walle-btn" onclick="WE._exportAsImage()">Save as Image</button>';
|
|
781
|
+
html += '<button class="walle-btn danger" onclick="WE._deleteSelected()">Delete</button>';
|
|
781
782
|
html += '<button class="we-chat-search-clear" onclick="WE._clearSelection()" title="Clear selection">×</button>';
|
|
782
783
|
html += '</div>';
|
|
783
784
|
}
|
|
@@ -1381,7 +1382,23 @@ WE._toggleTurn = function(turnIdx) {
|
|
|
1381
1382
|
} else {
|
|
1382
1383
|
chatSelected.add(turnIdx);
|
|
1383
1384
|
}
|
|
1384
|
-
|
|
1385
|
+
// Update just the clicked turn visually — don't re-render (preserves scroll)
|
|
1386
|
+
var el = document.querySelector('.we-turn-group[data-turn="' + turnIdx + '"]');
|
|
1387
|
+
if (el) {
|
|
1388
|
+
el.classList.toggle('we-selected', chatSelected.has(turnIdx));
|
|
1389
|
+
var cb = el.querySelector('.we-turn-checkbox');
|
|
1390
|
+
if (cb) cb.textContent = chatSelected.has(turnIdx) ? '\u2611' : '\u2610';
|
|
1391
|
+
}
|
|
1392
|
+
// Update the export bar
|
|
1393
|
+
var bar = document.querySelector('.we-export-bar');
|
|
1394
|
+
if (chatSelected.size > 0 && !bar) {
|
|
1395
|
+
renderChatUI(); // first selection — need to add toolbar
|
|
1396
|
+
} else if (chatSelected.size === 0 && bar) {
|
|
1397
|
+
bar.remove();
|
|
1398
|
+
} else if (bar) {
|
|
1399
|
+
var countEl = bar.querySelector('.we-export-count');
|
|
1400
|
+
if (countEl) countEl.textContent = chatSelected.size + ' selected';
|
|
1401
|
+
}
|
|
1385
1402
|
};
|
|
1386
1403
|
|
|
1387
1404
|
WE._clearSelection = function() {
|
|
@@ -1515,6 +1532,36 @@ WE._exportAsImage = function() {
|
|
|
1515
1532
|
}
|
|
1516
1533
|
};
|
|
1517
1534
|
|
|
1535
|
+
WE._deleteSelected = function() {
|
|
1536
|
+
var turns = _getSelectedTurns();
|
|
1537
|
+
if (turns.length === 0) return;
|
|
1538
|
+
if (!confirm('Delete ' + turns.length + ' message' + (turns.length > 1 ? 's' : '') + '? This cannot be undone.')) return;
|
|
1539
|
+
|
|
1540
|
+
var count = turns.length;
|
|
1541
|
+
var promises = turns.map(function(t) {
|
|
1542
|
+
return apiPost('/chat/delete', {
|
|
1543
|
+
session_id: cache.chatSessionId || 'default',
|
|
1544
|
+
user_content: t.userText || undefined,
|
|
1545
|
+
assistant_content: t.assistantText || undefined,
|
|
1546
|
+
});
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
Promise.all(promises).then(function() {
|
|
1550
|
+
chatSelected.clear();
|
|
1551
|
+
chatSelectMode = false;
|
|
1552
|
+
// Reload chat history
|
|
1553
|
+
api('/chat/history?session_id=' + encodeURIComponent(cache.chatSessionId || 'default') + '&limit=200').then(function(result) {
|
|
1554
|
+
chatHistory = (result.data || []).map(function(m) {
|
|
1555
|
+
return { role: m.role, text: m.content || m.text || '', ts: m.timestamp };
|
|
1556
|
+
});
|
|
1557
|
+
renderChatUI();
|
|
1558
|
+
if (typeof showToast === 'function') showToast('Deleted ' + count + ' message' + (count > 1 ? 's' : ''));
|
|
1559
|
+
});
|
|
1560
|
+
}).catch(function(err) {
|
|
1561
|
+
if (typeof showToast === 'function') showToast('Delete failed: ' + (err.message || 'unknown error'), 'var(--red)');
|
|
1562
|
+
});
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1518
1565
|
// ---- Chat Search ----
|
|
1519
1566
|
WE._onChatSearch = function(val) {
|
|
1520
1567
|
chatSearchQuery = val;
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
18
18
|
.status-dot.ok { background: var(--green); }
|
|
19
19
|
.status-dot.missing { background: var(--yellow); }
|
|
20
|
-
.status-dot.error { background: var(--red); }
|
|
21
20
|
label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: var(--dim); }
|
|
22
21
|
input[type="text"], input[type="password"] {
|
|
23
22
|
width: 100%; padding: 10px 12px; background: var(--bg); border: 1px solid var(--border);
|
|
@@ -32,9 +31,8 @@
|
|
|
32
31
|
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
33
32
|
.btn-secondary { background: var(--border); color: var(--text); }
|
|
34
33
|
.btn-secondary:hover { background: #3d444d; }
|
|
35
|
-
.btn-row { display: flex; gap: 12px; align-items: center; margin-top: 8px; }
|
|
36
|
-
.
|
|
37
|
-
.error-msg { color: var(--red); font-size: 13px; display: none; }
|
|
34
|
+
.btn-row { display: flex; gap: 12px; align-items: center; margin-top: 8px; flex-wrap: wrap; }
|
|
35
|
+
.error-msg { color: var(--red); font-size: 13px; display: none; width: 100%; margin-top: 4px; }
|
|
38
36
|
.integration { display: flex; align-items: center; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid var(--border); }
|
|
39
37
|
.integration:last-child { border-bottom: none; }
|
|
40
38
|
.integration-info { display: flex; align-items: center; gap: 12px; }
|
|
@@ -47,6 +45,19 @@
|
|
|
47
45
|
.done-section { text-align: center; padding: 16px 0; }
|
|
48
46
|
.done-section a { color: var(--accent); text-decoration: none; font-weight: 500; }
|
|
49
47
|
.done-section a:hover { text-decoration: underline; }
|
|
48
|
+
|
|
49
|
+
/* Toast notification */
|
|
50
|
+
.toast {
|
|
51
|
+
position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(80px);
|
|
52
|
+
background: var(--card); border: 1px solid var(--border); border-radius: 10px;
|
|
53
|
+
padding: 12px 24px; font-size: 14px; font-weight: 500;
|
|
54
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
|
55
|
+
opacity: 0; transition: all 0.3s ease; pointer-events: none; z-index: 100;
|
|
56
|
+
display: flex; align-items: center; gap: 8px;
|
|
57
|
+
}
|
|
58
|
+
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
59
|
+
.toast.success { border-color: var(--green); color: var(--green); }
|
|
60
|
+
.toast.error { border-color: var(--red); color: var(--red); }
|
|
50
61
|
</style>
|
|
51
62
|
</head>
|
|
52
63
|
<body>
|
|
@@ -54,7 +65,7 @@
|
|
|
54
65
|
<h1>Welcome to Wall-E</h1>
|
|
55
66
|
<p class="subtitle">Let's get you set up. This takes about 30 seconds.</p>
|
|
56
67
|
|
|
57
|
-
<!--
|
|
68
|
+
<!-- Owner -->
|
|
58
69
|
<div class="card">
|
|
59
70
|
<h2><span class="status-dot ok" id="owner-dot"></span> Owner</h2>
|
|
60
71
|
<p>Auto-detected from your system.</p>
|
|
@@ -64,7 +75,7 @@
|
|
|
64
75
|
</div>
|
|
65
76
|
</div>
|
|
66
77
|
|
|
67
|
-
<!--
|
|
78
|
+
<!-- API Key -->
|
|
68
79
|
<div class="card">
|
|
69
80
|
<h2><span class="status-dot missing" id="api-dot"></span> Anthropic API Key</h2>
|
|
70
81
|
<p>Required for AI features (chat, think, reflect). Get one at <a href="https://console.anthropic.com/settings/keys" target="_blank" style="color:var(--accent)">console.anthropic.com</a></p>
|
|
@@ -73,18 +84,16 @@
|
|
|
73
84
|
<input type="password" id="api-key" placeholder="sk-ant-...">
|
|
74
85
|
</div>
|
|
75
86
|
<div class="btn-row">
|
|
76
|
-
<button class="btn btn-primary" id="save-btn" onclick="saveConfig()">Save
|
|
77
|
-
<button class="btn btn-secondary" id="detect-btn" onclick="detectKey()"
|
|
78
|
-
<span class="success-msg" id="save-ok">Saved!</span>
|
|
87
|
+
<button class="btn btn-primary" id="save-btn" onclick="saveConfig()">Save</button>
|
|
88
|
+
<button class="btn btn-secondary" id="detect-btn" onclick="detectKey()">Detect from environment</button>
|
|
79
89
|
<span class="error-msg" id="save-err"></span>
|
|
80
90
|
</div>
|
|
81
91
|
</div>
|
|
82
92
|
|
|
83
|
-
<!--
|
|
93
|
+
<!-- Integrations -->
|
|
84
94
|
<div class="card">
|
|
85
95
|
<h2>Integrations</h2>
|
|
86
96
|
<p>Optional — connect these anytime from the Wall-E settings.</p>
|
|
87
|
-
|
|
88
97
|
<div class="integration" id="int-slack">
|
|
89
98
|
<div class="integration-info">
|
|
90
99
|
<div class="integration-icon">💬</div>
|
|
@@ -95,7 +104,6 @@
|
|
|
95
104
|
</div>
|
|
96
105
|
<button class="btn btn-secondary" id="slack-btn" onclick="connectSlack()">Connect</button>
|
|
97
106
|
</div>
|
|
98
|
-
|
|
99
107
|
<div class="integration">
|
|
100
108
|
<div class="integration-info">
|
|
101
109
|
<div class="integration-icon">📧</div>
|
|
@@ -106,7 +114,6 @@
|
|
|
106
114
|
</div>
|
|
107
115
|
<span class="badge badge-ready">Auto</span>
|
|
108
116
|
</div>
|
|
109
|
-
|
|
110
117
|
<div class="integration">
|
|
111
118
|
<div class="integration-info">
|
|
112
119
|
<div class="integration-icon">📅</div>
|
|
@@ -127,7 +134,19 @@
|
|
|
127
134
|
</div>
|
|
128
135
|
</div>
|
|
129
136
|
|
|
137
|
+
<!-- Toast -->
|
|
138
|
+
<div class="toast" id="toast"></div>
|
|
139
|
+
|
|
130
140
|
<script>
|
|
141
|
+
function showToast(msg, type) {
|
|
142
|
+
const t = document.getElementById('toast');
|
|
143
|
+
t.className = 'toast ' + (type || 'success');
|
|
144
|
+
t.textContent = type === 'error' ? msg : '✓ ' + msg;
|
|
145
|
+
t.classList.add('show');
|
|
146
|
+
clearTimeout(t._timer);
|
|
147
|
+
t._timer = setTimeout(() => t.classList.remove('show'), 3000);
|
|
148
|
+
}
|
|
149
|
+
|
|
131
150
|
async function loadStatus() {
|
|
132
151
|
try {
|
|
133
152
|
const r = await fetch('/api/setup/status');
|
|
@@ -138,7 +157,7 @@
|
|
|
138
157
|
document.getElementById('api-key').placeholder = '••••••••••••••• (configured)';
|
|
139
158
|
}
|
|
140
159
|
if (d.slack_connected) {
|
|
141
|
-
|
|
160
|
+
_showSlackConnected(d.slack_team);
|
|
142
161
|
}
|
|
143
162
|
if (d.version) {
|
|
144
163
|
document.getElementById('version-label').textContent = 'Wall-E v' + d.version;
|
|
@@ -148,9 +167,7 @@
|
|
|
148
167
|
|
|
149
168
|
async function saveConfig() {
|
|
150
169
|
const btn = document.getElementById('save-btn');
|
|
151
|
-
const okMsg = document.getElementById('save-ok');
|
|
152
170
|
const errMsg = document.getElementById('save-err');
|
|
153
|
-
okMsg.style.display = 'none';
|
|
154
171
|
errMsg.style.display = 'none';
|
|
155
172
|
btn.disabled = true;
|
|
156
173
|
|
|
@@ -163,19 +180,18 @@
|
|
|
163
180
|
btn.disabled = false;
|
|
164
181
|
return;
|
|
165
182
|
}
|
|
166
|
-
const body = { owner_name: ownerVal, api_key: apiVal };
|
|
167
183
|
|
|
168
184
|
try {
|
|
169
185
|
const r = await fetch('/api/setup/save', {
|
|
170
186
|
method: 'POST',
|
|
171
187
|
headers: { 'Content-Type': 'application/json' },
|
|
172
|
-
body: JSON.stringify(
|
|
188
|
+
body: JSON.stringify({ owner_name: ownerVal, api_key: apiVal }),
|
|
173
189
|
});
|
|
174
190
|
const d = await r.json();
|
|
175
191
|
if (d.ok) {
|
|
176
|
-
okMsg.style.display = 'inline';
|
|
177
|
-
document.getElementById('api-dot').className = 'status-dot ok';
|
|
178
192
|
document.getElementById('owner-dot').className = 'status-dot ok';
|
|
193
|
+
if (apiVal) document.getElementById('api-dot').className = 'status-dot ok';
|
|
194
|
+
showToast('Settings saved');
|
|
179
195
|
} else {
|
|
180
196
|
errMsg.textContent = d.error || 'Save failed';
|
|
181
197
|
errMsg.style.display = 'inline';
|
|
@@ -187,6 +203,16 @@
|
|
|
187
203
|
btn.disabled = false;
|
|
188
204
|
}
|
|
189
205
|
|
|
206
|
+
function _showSlackConnected(team) {
|
|
207
|
+
const el = document.getElementById('int-slack');
|
|
208
|
+
if (!el) return;
|
|
209
|
+
const badge = team
|
|
210
|
+
? '<span class="badge badge-connected">Connected to ' + team + '</span>'
|
|
211
|
+
: '<span class="badge badge-connected">Connected</span>';
|
|
212
|
+
const btn = el.querySelector('#slack-btn');
|
|
213
|
+
if (btn) btn.outerHTML = badge;
|
|
214
|
+
}
|
|
215
|
+
|
|
190
216
|
async function connectSlack() {
|
|
191
217
|
const btn = document.getElementById('slack-btn');
|
|
192
218
|
try {
|
|
@@ -195,10 +221,10 @@
|
|
|
195
221
|
const r = await fetch('/api/wall-e/slack/auth', { method: 'POST' });
|
|
196
222
|
const d = await r.json();
|
|
197
223
|
if (d.ok && d.already) {
|
|
198
|
-
|
|
224
|
+
_showSlackConnected('');
|
|
225
|
+
showToast('Slack already connected');
|
|
199
226
|
} else if (d.ok) {
|
|
200
|
-
btn.textContent = '
|
|
201
|
-
// Poll for completion (OAuth callback happens server-side)
|
|
227
|
+
btn.textContent = 'Waiting for browser...';
|
|
202
228
|
let attempts = 0;
|
|
203
229
|
const poll = setInterval(async () => {
|
|
204
230
|
attempts++;
|
|
@@ -207,10 +233,11 @@
|
|
|
207
233
|
const sd = await sr.json();
|
|
208
234
|
if (sd.slack_connected) {
|
|
209
235
|
clearInterval(poll);
|
|
210
|
-
|
|
211
|
-
|
|
236
|
+
_showSlackConnected(sd.slack_team);
|
|
237
|
+
showToast('Slack connected' + (sd.slack_team ? ' to ' + sd.slack_team : ''));
|
|
238
|
+
} else if (attempts > 150) {
|
|
212
239
|
clearInterval(poll);
|
|
213
|
-
btn.textContent = 'Timed out';
|
|
240
|
+
btn.textContent = 'Timed out — try again';
|
|
214
241
|
btn.disabled = false;
|
|
215
242
|
}
|
|
216
243
|
} catch {}
|
|
@@ -218,6 +245,7 @@
|
|
|
218
245
|
} else {
|
|
219
246
|
btn.textContent = d.error || 'Failed';
|
|
220
247
|
btn.disabled = false;
|
|
248
|
+
showToast(d.error || 'Slack connection failed', 'error');
|
|
221
249
|
}
|
|
222
250
|
} catch (e) {
|
|
223
251
|
btn.textContent = 'Connect';
|
|
@@ -228,9 +256,7 @@
|
|
|
228
256
|
async function detectKey() {
|
|
229
257
|
const btn = document.getElementById('detect-btn');
|
|
230
258
|
const errMsg = document.getElementById('save-err');
|
|
231
|
-
const okMsg = document.getElementById('save-ok');
|
|
232
259
|
errMsg.style.display = 'none';
|
|
233
|
-
okMsg.style.display = 'none';
|
|
234
260
|
btn.disabled = true;
|
|
235
261
|
btn.textContent = 'Checking...';
|
|
236
262
|
try {
|
|
@@ -240,8 +266,6 @@
|
|
|
240
266
|
document.getElementById('api-key').value = '';
|
|
241
267
|
document.getElementById('api-key').placeholder = '••••••••••••••• (from ' + (d.source || 'environment') + ')';
|
|
242
268
|
document.getElementById('api-dot').className = 'status-dot ok';
|
|
243
|
-
okMsg.textContent = 'Detected: ' + (d.source || 'environment') + '!';
|
|
244
|
-
okMsg.style.display = 'inline';
|
|
245
269
|
const ownerVal = document.getElementById('owner-name').value.trim();
|
|
246
270
|
const saveBody = { owner_name: ownerVal };
|
|
247
271
|
if (d.gateway) saveBody.gateway = d.gateway;
|
|
@@ -251,6 +275,7 @@
|
|
|
251
275
|
headers: { 'Content-Type': 'application/json' },
|
|
252
276
|
body: JSON.stringify(saveBody),
|
|
253
277
|
});
|
|
278
|
+
showToast('Detected and saved: ' + (d.source || 'environment'));
|
|
254
279
|
} else {
|
|
255
280
|
errMsg.textContent = d.hint || 'No API key found. Enter one manually.';
|
|
256
281
|
errMsg.style.display = 'inline';
|
|
@@ -197,14 +197,19 @@ function handleApi(req, res, url) {
|
|
|
197
197
|
} catch {}
|
|
198
198
|
}
|
|
199
199
|
let slackConnected = false;
|
|
200
|
+
let slackTeam = '';
|
|
200
201
|
try {
|
|
201
|
-
const tokPath = path.join(process.env.HOME, '.
|
|
202
|
-
|
|
202
|
+
const tokPath = path.join(process.env.HOME, '.claude', 'wall-e-slack-token.json');
|
|
203
|
+
if (fs.existsSync(tokPath)) {
|
|
204
|
+
const tok = JSON.parse(fs.readFileSync(tokPath, 'utf8'));
|
|
205
|
+
slackConnected = !!(tok.access_token);
|
|
206
|
+
slackTeam = tok.team_name || '';
|
|
207
|
+
}
|
|
203
208
|
} catch {}
|
|
204
209
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
205
210
|
let version = '';
|
|
206
211
|
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 }));
|
|
212
|
+
res.end(JSON.stringify({ owner_name: ownerName, has_api_key: hasApiKey, slack_connected: slackConnected, slack_team: slackTeam, needs_setup: setup.needsSetup(), version }));
|
|
208
213
|
return;
|
|
209
214
|
}
|
|
210
215
|
if (url.pathname === '/api/setup/detect-key' && req.method === 'GET') {
|
|
@@ -313,16 +318,21 @@ function handleApi(req, res, url) {
|
|
|
313
318
|
// Accept any non-empty key (Anthropic, Portkey, or other providers)
|
|
314
319
|
const envPath = path.resolve(__dirname, '..', '.env');
|
|
315
320
|
const lines = [];
|
|
316
|
-
// Build set of keys
|
|
321
|
+
// Build set of keys to strip — API key and gateway are mutually exclusive
|
|
317
322
|
const keysToReplace = new Set();
|
|
318
323
|
if (ownerName) keysToReplace.add('WALLE_OWNER_NAME');
|
|
319
|
-
if (apiKey)
|
|
320
|
-
|
|
324
|
+
if (apiKey || gw) {
|
|
325
|
+
// Always strip both — they're mutually exclusive
|
|
326
|
+
keysToReplace.add('ANTHROPIC_API_KEY');
|
|
327
|
+
keysToReplace.add('ANTHROPIC_BASE_URL');
|
|
328
|
+
keysToReplace.add('ANTHROPIC_AUTH_TOKEN');
|
|
329
|
+
keysToReplace.add('ANTHROPIC_CUSTOM_HEADERS_B64');
|
|
330
|
+
}
|
|
321
331
|
// Read existing .env, keep lines that aren't being replaced
|
|
322
332
|
try {
|
|
323
333
|
const existing = fs.readFileSync(envPath, 'utf8');
|
|
324
334
|
for (const line of existing.split('\n')) {
|
|
325
|
-
const m = line.match(/^\s*#?\s*([A-
|
|
335
|
+
const m = line.match(/^\s*#?\s*([A-Z0-9_]+)\s*=/);
|
|
326
336
|
if (m && keysToReplace.has(m[1])) continue; // skip — will re-add below
|
|
327
337
|
lines.push(line);
|
|
328
338
|
}
|
|
@@ -336,16 +346,21 @@ function handleApi(req, res, url) {
|
|
|
336
346
|
process.env.WALLE_OWNER_NAME = ownerName;
|
|
337
347
|
}
|
|
338
348
|
if (gw) {
|
|
339
|
-
// Gateway setup: save
|
|
349
|
+
// Gateway setup: save gateway vars, clear direct API key
|
|
340
350
|
lines.push(`ANTHROPIC_BASE_URL=${gw.base_url}`);
|
|
341
351
|
lines.push(`ANTHROPIC_AUTH_TOKEN=${gw.auth_token}`);
|
|
342
352
|
lines.push(`ANTHROPIC_CUSTOM_HEADERS_B64=${gw.custom_headers_b64}`);
|
|
343
353
|
process.env.ANTHROPIC_BASE_URL = gw.base_url;
|
|
344
354
|
process.env.ANTHROPIC_AUTH_TOKEN = gw.auth_token;
|
|
345
355
|
process.env.ANTHROPIC_CUSTOM_HEADERS_B64 = gw.custom_headers_b64;
|
|
356
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
346
357
|
} else if (apiKey) {
|
|
358
|
+
// Direct API key: save key, clear gateway vars
|
|
347
359
|
lines.push(`ANTHROPIC_API_KEY=${apiKey}`);
|
|
348
360
|
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
361
|
+
delete process.env.ANTHROPIC_BASE_URL;
|
|
362
|
+
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
363
|
+
delete process.env.ANTHROPIC_CUSTOM_HEADERS_B64;
|
|
349
364
|
}
|
|
350
365
|
fs.writeFileSync(envPath, lines.join('\n') + '\n', { mode: 0o600 });
|
|
351
366
|
setup.clearSetupCache(); // so next / request goes to dashboard
|