@oooooooooo/ch0de 1.9.57 → 1.9.59
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/chode.js +92 -3
- package/package.json +2 -2
- package/serve-frontend.html +118 -3
package/chode.js
CHANGED
|
@@ -3840,7 +3840,12 @@ async function runAgentLoop(prompt, sessionId, force, callOptions, aiCall, onToo
|
|
|
3840
3840
|
mutating.push(executable[mi].args.file);
|
|
3841
3841
|
}
|
|
3842
3842
|
}
|
|
3843
|
-
if (mutating.length)
|
|
3843
|
+
if (mutating.length) {
|
|
3844
|
+
// Auto-commit checkpoint before mutating agent turn so work can be rolled back.
|
|
3845
|
+
try { run('git add -A', { silent: true, cwd: ROOT }); } catch (e) {}
|
|
3846
|
+
try { run('git commit --allow-empty -m "chode checkpoint: agent turn ' + steps + '"', { silent: true, cwd: ROOT }); } catch (e) {}
|
|
3847
|
+
takeSnapshot('agent turn ' + steps, mutating, sessionId);
|
|
3848
|
+
}
|
|
3844
3849
|
var toolCtx = { yolo:callOptions.yolo===true,onChildAuthors:ids=>{for(const id of ids)draftAuthors.add(String(id).toLowerCase());},taskGroup,mode:callOptions.mode,acceptanceTest:callOptions.acceptanceTest,sessionId: sessionId, actions: actions, aiCall: aiCall, onTool: onTool, onAsk: onAsk, planTargets: effTargets, overwriteTargets: effOverwrites, scopeTargets: callOptions.scopeTargets || null, inspectedUrls: taskContext.inspectedUrls };
|
|
3845
3850
|
var acceptanceChangedThisTurn=false;
|
|
3846
3851
|
async function execOne(call) {
|
|
@@ -7298,6 +7303,40 @@ function cmdServe(argv) {
|
|
|
7298
7303
|
return 'ses_' + crypto.randomBytes(8).toString('hex');
|
|
7299
7304
|
}
|
|
7300
7305
|
|
|
7306
|
+
function verifyProviderKey(baseUrl, key) {
|
|
7307
|
+
return new Promise(function(resolve){
|
|
7308
|
+
try {
|
|
7309
|
+
var u = new URL(baseUrl + '/models');
|
|
7310
|
+
var req = http.request(u, {
|
|
7311
|
+
method: 'GET',
|
|
7312
|
+
headers: {'Authorization': 'Bearer ' + key, 'Accept': 'application/json'},
|
|
7313
|
+
timeout: 8000
|
|
7314
|
+
}, function(resp){
|
|
7315
|
+
var working = resp.statusCode >= 200 && resp.statusCode < 400;
|
|
7316
|
+
resp.resume();
|
|
7317
|
+
resolve(working);
|
|
7318
|
+
});
|
|
7319
|
+
req.on('error', function(){ resolve(false); });
|
|
7320
|
+
req.on('timeout', function(){ req.destroy(); resolve(false); });
|
|
7321
|
+
req.end();
|
|
7322
|
+
} catch(e) { resolve(false); }
|
|
7323
|
+
});
|
|
7324
|
+
}
|
|
7325
|
+
|
|
7326
|
+
function providerInfo(pid) {
|
|
7327
|
+
var info = null;
|
|
7328
|
+
try {
|
|
7329
|
+
for (var id in PROVIDERS) { if (id === pid) { info = PROVIDERS[id]; break; } }
|
|
7330
|
+
} catch(e) {}
|
|
7331
|
+
if (info) return info;
|
|
7332
|
+
try {
|
|
7333
|
+
var dir = require('./provider-directory');
|
|
7334
|
+
var listed = dir.list();
|
|
7335
|
+
for (var i = 0; i < listed.length; i++) { if (listed[i].id === pid) { info = listed[i]; break; } }
|
|
7336
|
+
} catch(e) {}
|
|
7337
|
+
return info;
|
|
7338
|
+
}
|
|
7339
|
+
|
|
7301
7340
|
function loadServeConfig() {
|
|
7302
7341
|
try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); } catch(e) { return { freeOnly: true, defaultProvider: 'auto', providers: {}, connections: {} }; }
|
|
7303
7342
|
}
|
|
@@ -7434,9 +7473,11 @@ function cmdServe(argv) {
|
|
|
7434
7473
|
if (pathname === '/api/providers' && req.method === 'GET') {
|
|
7435
7474
|
var out = [];
|
|
7436
7475
|
try {
|
|
7476
|
+
var cfg = loadServeConfig();
|
|
7477
|
+
var keys = cfg.providerKeys || {};
|
|
7437
7478
|
for (var id in PROVIDERS) {
|
|
7438
7479
|
var p = PROVIDERS[id];
|
|
7439
|
-
out.push({id: id, name: p.name || id, requiresKey: !!p.requiresKey, free: require('./provider-capacity').free ? require('./provider-capacity').free(p) : true, category: p.category || '', builtIn: true});
|
|
7480
|
+
out.push({id: id, name: p.name || id, requiresKey: !!p.requiresKey, free: require('./provider-capacity').free ? require('./provider-capacity').free(p) : true, category: p.category || '', builtIn: true, hasKey: !!keys[id]});
|
|
7440
7481
|
}
|
|
7441
7482
|
var dir = require('./provider-directory');
|
|
7442
7483
|
var listed = dir.list();
|
|
@@ -7444,11 +7485,59 @@ function cmdServe(argv) {
|
|
|
7444
7485
|
out.forEach(function(x){ids[x.id]=true;});
|
|
7445
7486
|
listed.forEach(function(p){
|
|
7446
7487
|
if (!ids[p.id]) {
|
|
7447
|
-
out.push({id: p.id, name: p.name, requiresKey: !!p.env, free: !p.env, category: 'directory', builtIn: false, baseUrl: p.baseUrl, models: p.models.length, doc: p.doc, verification: p.verification});
|
|
7488
|
+
out.push({id: p.id, name: p.name, requiresKey: !!p.env, free: !p.env, category: 'directory', builtIn: false, baseUrl: p.baseUrl, models: p.models.length, doc: p.doc, verification: p.verification, hasKey: !!keys[p.id]});
|
|
7448
7489
|
}
|
|
7449
7490
|
});
|
|
7450
7491
|
} catch(e) {}
|
|
7451
7492
|
json(res, 200, {ok: true, providers: out});
|
|
7493
|
+
return;
|
|
7494
|
+
}
|
|
7495
|
+
|
|
7496
|
+
var keyM = pathname.match(/^\/api\/provider\/([^\/]+)\/key(\/test)?$/);
|
|
7497
|
+
if (keyM) {
|
|
7498
|
+
var pid = keyM[1];
|
|
7499
|
+
var isTest = !!keyM[2];
|
|
7500
|
+
if (req.method === 'GET' && !isTest) {
|
|
7501
|
+
var cfg2 = loadServeConfig();
|
|
7502
|
+
var keys2 = cfg2.providerKeys || {};
|
|
7503
|
+
json(res, 200, {ok: true, hasKey: !!keys2[pid]});
|
|
7504
|
+
return;
|
|
7505
|
+
}
|
|
7506
|
+
if (req.method === 'POST' && !isTest) {
|
|
7507
|
+
readBody(req).then(function(body){
|
|
7508
|
+
var parsed;
|
|
7509
|
+
try { parsed = JSON.parse(body || '{}'); } catch(e) { json(res, 400, {ok:false, error:'invalid json'}); return; }
|
|
7510
|
+
var key = String(parsed.key || '');
|
|
7511
|
+
var cfg3 = loadServeConfig();
|
|
7512
|
+
if (!cfg3.providerKeys) cfg3.providerKeys = {};
|
|
7513
|
+
cfg3.providerKeys[pid] = key;
|
|
7514
|
+
saveServeConfig(cfg3);
|
|
7515
|
+
var pinfo = providerInfo(pid);
|
|
7516
|
+
if (pinfo && pinfo.baseUrl) {
|
|
7517
|
+
verifyProviderKey(pinfo.baseUrl, key).then(function(working){
|
|
7518
|
+
json(res, 200, {ok: true, working: working});
|
|
7519
|
+
}).catch(function(){ json(res, 200, {ok: true, working: false}); });
|
|
7520
|
+
} else {
|
|
7521
|
+
json(res, 200, {ok: true, working: false});
|
|
7522
|
+
}
|
|
7523
|
+
}).catch(function(e){ json(res, 500, {ok:false, error: e.message}); });
|
|
7524
|
+
return;
|
|
7525
|
+
}
|
|
7526
|
+
if (req.method === 'GET' && isTest) {
|
|
7527
|
+
var cfg4 = loadServeConfig();
|
|
7528
|
+
var keys4 = cfg4.providerKeys || {};
|
|
7529
|
+
var key = String(keys4[pid] || '');
|
|
7530
|
+
if (!key) { json(res, 200, {ok: true, working: false, hasKey: false}); return; }
|
|
7531
|
+
var pinfo2 = providerInfo(pid);
|
|
7532
|
+
if (pinfo2 && pinfo2.baseUrl) {
|
|
7533
|
+
verifyProviderKey(pinfo2.baseUrl, key).then(function(working){
|
|
7534
|
+
json(res, 200, {ok: true, working: working, hasKey: true});
|
|
7535
|
+
}).catch(function(){ json(res, 200, {ok: true, working: false, hasKey: true}); });
|
|
7536
|
+
} else {
|
|
7537
|
+
json(res, 200, {ok: true, working: false, hasKey: true});
|
|
7538
|
+
}
|
|
7539
|
+
return;
|
|
7540
|
+
}
|
|
7452
7541
|
}
|
|
7453
7542
|
|
|
7454
7543
|
res.writeHead(404, {'Content-Type': 'text/plain'});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oooooooooo/ch0de",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.59",
|
|
4
4
|
"description": "The self-healing AI coding harness. Free tiers. HEMO auto-provisioning. Zero lock-in. Canonical brand: ch0de.",
|
|
5
5
|
"main": "chode.js",
|
|
6
6
|
"bin": {
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
},
|
|
152
152
|
"dependencies": {
|
|
153
153
|
"@modelcontextprotocol/sdk": "1.32.2",
|
|
154
|
-
"@oooooooooo/ch0de": "^1.9.
|
|
154
|
+
"@oooooooooo/ch0de": "^1.9.58",
|
|
155
155
|
"lean": "^0.1.1"
|
|
156
156
|
},
|
|
157
157
|
"devDependencies": {
|
package/serve-frontend.html
CHANGED
|
@@ -58,8 +58,9 @@ body { background: #000; color: #e0e0e0; font-family: "IBM Plex Mono", monospace
|
|
|
58
58
|
.setting-select, .setting-input { background: #000; border: 1px solid #22dd5533; color: #e0e0e0; padding: 6px 10px; border-radius: 6px; font-family: inherit; font-size: 12px; outline: none; }
|
|
59
59
|
.setting-select:focus, .setting-input:focus { border-color: #22dd55; }
|
|
60
60
|
.setting-checkbox { width: 18px; height: 18px; accent-color: #22dd55; background: #000; border: 1px solid #22dd5533; cursor: pointer; }
|
|
61
|
-
.provider-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #141414; font-size: 12px; }
|
|
61
|
+
.provider-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #141414; font-size: 12px; cursor: pointer; }
|
|
62
62
|
.provider-row:last-child { border-bottom: none; }
|
|
63
|
+
.provider-row:hover { background: #141414; }
|
|
63
64
|
#providersList { max-height: 340px; overflow-y: auto; border: 1px solid #141414; border-radius: 6px; padding: 10px; background: #000; margin-top: 8px; }
|
|
64
65
|
.provider-info { display: flex; align-items: center; gap: 10px; flex: 1; }
|
|
65
66
|
.provider-name { font-weight: bold; color: #e0e0e0; }
|
|
@@ -67,9 +68,27 @@ body { background: #000; color: #e0e0e0; font-family: "IBM Plex Mono", monospace
|
|
|
67
68
|
.provider-status { font-size: 11px; padding: 2px 8px; border-radius: 4px; }
|
|
68
69
|
.provider-status.free { background: #1a1a1a; color: #22dd55; border: 1px solid #22dd5533; }
|
|
69
70
|
.provider-status.keyed { background: #1a1a1a; color: #ff9900; border: 1px solid #ff990033; }
|
|
71
|
+
.provider-status.haskey { background: #1a1a1a; color: #22dd55; border: 1px solid #22dd5533; }
|
|
72
|
+
.provider-status.working { background: #1a1a1a; color: #22dd55; border: 1px solid #22dd55; }
|
|
73
|
+
.provider-status.badkey { background: #1a1a1a; color: #ff4444; border: 1px solid #ff444433; }
|
|
70
74
|
.btn-save { background: #22dd55; color: #000; border: none; border-radius: 6px; padding: 8px 16px; font-family: inherit; font-weight: bold; cursor: pointer; margin-top: 20px; }
|
|
71
75
|
.btn-save:hover { background: #3dffa0; }
|
|
72
76
|
.btn-save:disabled { background: #1a1a1a; color: #666; cursor: not-allowed; }
|
|
77
|
+
|
|
78
|
+
/* Key input modal */
|
|
79
|
+
.key-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 100; display: none; }
|
|
80
|
+
.key-modal { position: fixed; top: 20%; left: 25%; right: 25%; background: #0a0a0a; border: 1px solid #22dd5533; border-radius: 12px; z-index: 110; padding: 24px; display: none; box-shadow: 0 4px 20px rgba(0,0,0,0.4); }
|
|
81
|
+
.key-modal.open, .key-modal-overlay.open { display: block; }
|
|
82
|
+
.key-modal-title { color: #22dd55; font-weight: bold; font-size: 1.1rem; margin-bottom: 16px; }
|
|
83
|
+
.key-modal-field { margin-bottom: 16px; }
|
|
84
|
+
.key-modal-label { display: block; color: #888; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; }
|
|
85
|
+
.key-modal-input { width: 100%; background: #000; border: 1px solid #22dd5533; color: #e0e0e0; padding: 10px 14px; border-radius: 6px; font-family: inherit; font-size: 13px; outline: none; }
|
|
86
|
+
.key-modal-input:focus { border-color: #22dd55; }
|
|
87
|
+
.key-modal-actions { display: flex; gap: 10px; margin-top: 20px; }
|
|
88
|
+
.key-status { margin-top: 12px; padding: 8px 12px; border-radius: 6px; font-size: 12px; display: none; }
|
|
89
|
+
.key-status.ok { background: #141414; color: #22dd55; border: 1px solid #22dd5533; display: block; }
|
|
90
|
+
.key-status.err { background: #141414; color: #ff4444; border: 1px solid #ff444433; display: block; }
|
|
91
|
+
.key-status.saving { background: #141414; color: #ff9900; border: 1px solid #ff990033; display: block; }
|
|
73
92
|
</style>
|
|
74
93
|
</head>
|
|
75
94
|
<body>
|
|
@@ -175,6 +194,19 @@ body { background: #000; color: #e0e0e0; font-family: "IBM Plex Mono", monospace
|
|
|
175
194
|
</div>
|
|
176
195
|
</div>
|
|
177
196
|
</div>
|
|
197
|
+
<div class="key-modal-overlay" id="keyModalOverlay"></div>
|
|
198
|
+
<div class="key-modal" id="keyModal">
|
|
199
|
+
<div class="key-modal-title" id="keyModalTitle">API Key</div>
|
|
200
|
+
<div class="key-modal-field">
|
|
201
|
+
<span class="key-modal-label" id="keyModalLabel">API Key</span>
|
|
202
|
+
<input type="password" id="keyModalInput" class="key-modal-input" placeholder="Enter API key">
|
|
203
|
+
</div>
|
|
204
|
+
<div class="key-modal-actions">
|
|
205
|
+
<button class="btn-save" id="keyCancelBtn" style="background:#141414;color:#e0e0e0;border:1px solid #22dd5533;">Cancel</button>
|
|
206
|
+
<button class="btn-save" id="keySubmitBtn">Save & Test</button>
|
|
207
|
+
</div>
|
|
208
|
+
<div class="key-status" id="keyStatus"></div>
|
|
209
|
+
</div>
|
|
178
210
|
<script>
|
|
179
211
|
const API = "http://127.0.0.1:" + (window.location.port || "4096");
|
|
180
212
|
let currentSessionId = null;
|
|
@@ -309,8 +341,10 @@ async function loadProviders() {
|
|
|
309
341
|
select.appendChild(opt);
|
|
310
342
|
var row = document.createElement("div");
|
|
311
343
|
row.className = "provider-row";
|
|
312
|
-
var statusCls
|
|
313
|
-
|
|
344
|
+
var statusCls, statusText;
|
|
345
|
+
if (!p.requiresKey) { statusCls = "free"; statusText = "free"; }
|
|
346
|
+
else if (p.hasKey) { statusCls = "working"; statusText = "key saved"; }
|
|
347
|
+
else { statusCls = "keyed"; statusText = "needs API key"; }
|
|
314
348
|
var extra = p.builtIn ? "" : ' <span style="font-size:10px;color:#888;margin-left:6px;">(' + (p.models||0) + ' models)</span>';
|
|
315
349
|
row.innerHTML =
|
|
316
350
|
'<div class="provider-info">' +
|
|
@@ -318,6 +352,7 @@ async function loadProviders() {
|
|
|
318
352
|
'<div class="provider-id">' + escapeHtml(p.id) + (p.baseUrl ? ' · ' + escapeHtml(String(p.baseUrl).slice(0,40)) : '') + '</div>' +
|
|
319
353
|
'</div>' +
|
|
320
354
|
'<div class="provider-status ' + statusCls + '">' + statusText + '</div>';
|
|
355
|
+
row.onclick = function() { openKeyModal(p); };
|
|
321
356
|
list.appendChild(row);
|
|
322
357
|
});
|
|
323
358
|
} catch(e) {}
|
|
@@ -366,6 +401,80 @@ function closeSettings() {
|
|
|
366
401
|
settingsOpen = false;
|
|
367
402
|
}
|
|
368
403
|
|
|
404
|
+
var keyModalProvider = null;
|
|
405
|
+
function openKeyModal(p) {
|
|
406
|
+
keyModalProvider = p;
|
|
407
|
+
document.getElementById("keyModalTitle").textContent = "API Key · " + (p.name || p.id);
|
|
408
|
+
document.getElementById("keyModalLabel").textContent = p.env ? ("API Key (" + p.env + ")") : "API Key";
|
|
409
|
+
document.getElementById("keyModalInput").value = "";
|
|
410
|
+
document.getElementById("keyStatus").style.display = "none";
|
|
411
|
+
document.getElementById("keyModalOverlay").classList.add("open");
|
|
412
|
+
document.getElementById("keyModal").classList.add("open");
|
|
413
|
+
document.getElementById("keyModalInput").focus();
|
|
414
|
+
}
|
|
415
|
+
function closeKeyModal() {
|
|
416
|
+
document.getElementById("keyModalOverlay").classList.remove("open");
|
|
417
|
+
document.getElementById("keyModal").classList.remove("open");
|
|
418
|
+
keyModalProvider = null;
|
|
419
|
+
}
|
|
420
|
+
async function submitKey() {
|
|
421
|
+
if (!keyModalProvider) return;
|
|
422
|
+
var input = document.getElementById("keyModalInput");
|
|
423
|
+
var key = input.value.trim();
|
|
424
|
+
if (!key) return;
|
|
425
|
+
var status = document.getElementById("keyStatus");
|
|
426
|
+
var btn = document.getElementById("keySubmitBtn");
|
|
427
|
+
status.textContent = "Saving and testing key…";
|
|
428
|
+
status.className = "key-status saving";
|
|
429
|
+
status.style.display = "block";
|
|
430
|
+
btn.disabled = true;
|
|
431
|
+
try {
|
|
432
|
+
const res = await fetch(API + "/api/provider/" + encodeURIComponent(keyModalProvider.id) + "/key", {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers: { "Content-Type": "application/json" },
|
|
435
|
+
body: JSON.stringify({ key: key })
|
|
436
|
+
});
|
|
437
|
+
const data = await res.json();
|
|
438
|
+
if (data.ok && data.working) {
|
|
439
|
+
status.textContent = "Key is working ✓";
|
|
440
|
+
status.className = "key-status ok";
|
|
441
|
+
// Refresh provider list to show updated status
|
|
442
|
+
await loadProviders();
|
|
443
|
+
setTimeout(closeKeyModal, 800);
|
|
444
|
+
} else {
|
|
445
|
+
status.textContent = "Key saved but could not be verified";
|
|
446
|
+
status.className = "key-status err";
|
|
447
|
+
}
|
|
448
|
+
} catch(e) {
|
|
449
|
+
status.textContent = "Error: " + e.message;
|
|
450
|
+
status.className = "key-status err";
|
|
451
|
+
} finally {
|
|
452
|
+
btn.disabled = false;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async function testKey() {
|
|
456
|
+
if (!keyModalProvider) return;
|
|
457
|
+
var status = document.getElementById("keyStatus");
|
|
458
|
+
status.textContent = "Testing key…";
|
|
459
|
+
status.className = "key-status saving";
|
|
460
|
+
status.style.display = "block";
|
|
461
|
+
try {
|
|
462
|
+
const res = await fetch(API + "/api/provider/" + encodeURIComponent(keyModalProvider.id) + "/key/test");
|
|
463
|
+
const data = await res.json();
|
|
464
|
+
if (data.ok && data.working) {
|
|
465
|
+
status.textContent = "Key is working ✓";
|
|
466
|
+
status.className = "key-status ok";
|
|
467
|
+
await loadProviders();
|
|
468
|
+
} else {
|
|
469
|
+
status.textContent = "Key verification failed";
|
|
470
|
+
status.className = "key-status err";
|
|
471
|
+
}
|
|
472
|
+
} catch(e) {
|
|
473
|
+
status.textContent = "Error: " + e.message;
|
|
474
|
+
status.className = "key-status err";
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
369
478
|
document.getElementById("newSessionBtn").onclick = createSession;
|
|
370
479
|
document.getElementById("sendBtn").onclick = sendMessage;
|
|
371
480
|
document.getElementById("messageInput").addEventListener("keypress", function(e) {
|
|
@@ -375,6 +484,12 @@ document.getElementById("settingsBtn").onclick = openSettings;
|
|
|
375
484
|
document.getElementById("settingsClose").onclick = closeSettings;
|
|
376
485
|
document.getElementById("settingsOverlay").onclick = function(e) { if (e.target === this) closeSettings(); };
|
|
377
486
|
document.getElementById("settingsSave").onclick = function() { saveSettings(); closeSettings(); };
|
|
487
|
+
document.getElementById("keyCancelBtn").onclick = closeKeyModal;
|
|
488
|
+
document.getElementById("keySubmitBtn").onclick = submitKey;
|
|
489
|
+
document.getElementById("keyModalOverlay").onclick = function(e) { if (e.target === this) closeKeyModal(); };
|
|
490
|
+
document.getElementById("keyModalInput").addEventListener("keypress", function(e) {
|
|
491
|
+
if (e.key === "Enter") submitKey();
|
|
492
|
+
});
|
|
378
493
|
|
|
379
494
|
document.querySelectorAll(".settings-tab").forEach(function(tab) {
|
|
380
495
|
tab.onclick = function() {
|