@oooooooooo/ch0de 1.9.56 → 1.9.57

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 CHANGED
@@ -7278,8 +7278,10 @@ function cmdServe(argv) {
7278
7278
  if (!portVal) portVal = '4097'; // default unlikely to conflict with OpenCode (4096)
7279
7279
  var PORT = parseInt(portVal, 10);
7280
7280
  var SESSIONS_DIR = path.join(ROOT, '.chode', 'sessions');
7281
+ var CONFIG_PATH = path.join(ROOT, '.chode', 'config.json');
7281
7282
 
7282
7283
  if (!fs.existsSync(SESSIONS_DIR)) fs.mkdirSync(SESSIONS_DIR, {recursive:true});
7284
+ if (!fs.existsSync(path.dirname(CONFIG_PATH))) fs.mkdirSync(path.dirname(CONFIG_PATH), {recursive:true});
7283
7285
 
7284
7286
  var sessions = {};
7285
7287
  try {
@@ -7296,6 +7298,16 @@ function cmdServe(argv) {
7296
7298
  return 'ses_' + crypto.randomBytes(8).toString('hex');
7297
7299
  }
7298
7300
 
7301
+ function loadServeConfig() {
7302
+ try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); } catch(e) { return { freeOnly: true, defaultProvider: 'auto', providers: {}, connections: {} }; }
7303
+ }
7304
+
7305
+ function saveServeConfig(cfg) {
7306
+ try { fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf8'); } catch(e) {}
7307
+ }
7308
+
7309
+ var serveState = { provider: 'auto', mode: 'execute', theme: 'dark', details: false, timeoutSeconds: null, acceptanceTest: null, yolo: false };
7310
+
7299
7311
  function json(res, code, obj) {
7300
7312
  res.writeHead(code, {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'});
7301
7313
  res.end(JSON.stringify(obj));
@@ -7393,6 +7405,52 @@ function cmdServe(argv) {
7393
7405
  }
7394
7406
  }
7395
7407
 
7408
+ if (pathname === '/api/settings' && req.method === 'GET') {
7409
+ var cfg = loadServeConfig();
7410
+ json(res, 200, {ok: true, config: cfg, state: serveState});
7411
+ return;
7412
+ }
7413
+
7414
+ if (pathname === '/api/settings' && req.method === 'POST') {
7415
+ readBody(req).then(function(body){
7416
+ var parsed;
7417
+ try { parsed = JSON.parse(body || '{}'); } catch(e) { json(res, 400, {ok:false, error:'invalid json'}); return; }
7418
+ var cfg = loadServeConfig();
7419
+ if (parsed.freeOnly !== undefined) cfg.freeOnly = !!parsed.freeOnly;
7420
+ if (parsed.defaultProvider !== undefined) cfg.defaultProvider = parsed.defaultProvider;
7421
+ if (parsed.mode !== undefined) serveState.mode = parsed.mode;
7422
+ if (parsed.theme !== undefined) serveState.theme = parsed.theme;
7423
+ if (parsed.details !== undefined) serveState.details = !!parsed.details;
7424
+ if (parsed.timeoutSeconds !== undefined) serveState.timeoutSeconds = parsed.timeoutSeconds ? Number(parsed.timeoutSeconds) : null;
7425
+ if (parsed.acceptanceTest !== undefined) serveState.acceptanceTest = parsed.acceptanceTest || null;
7426
+ if (parsed.yolo !== undefined) serveState.yolo = !!parsed.yolo;
7427
+ if (parsed.provider !== undefined) serveState.provider = parsed.provider;
7428
+ saveServeConfig(cfg);
7429
+ json(res, 200, {ok: true, config: cfg, state: serveState});
7430
+ }).catch(function(e){ json(res, 500, {ok:false, error: e.message}); });
7431
+ return;
7432
+ }
7433
+
7434
+ if (pathname === '/api/providers' && req.method === 'GET') {
7435
+ var out = [];
7436
+ try {
7437
+ for (var id in PROVIDERS) {
7438
+ 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});
7440
+ }
7441
+ var dir = require('./provider-directory');
7442
+ var listed = dir.list();
7443
+ var ids = {};
7444
+ out.forEach(function(x){ids[x.id]=true;});
7445
+ listed.forEach(function(p){
7446
+ 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});
7448
+ }
7449
+ });
7450
+ } catch(e) {}
7451
+ json(res, 200, {ok: true, providers: out});
7452
+ }
7453
+
7396
7454
  res.writeHead(404, {'Content-Type': 'text/plain'});
7397
7455
  res.end('Not found');
7398
7456
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oooooooooo/ch0de",
3
- "version": "1.9.56",
3
+ "version": "1.9.57",
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.55",
154
+ "@oooooooooo/ch0de": "^1.9.56",
155
155
  "lean": "^0.1.1"
156
156
  },
157
157
  "devDependencies": {
@@ -11,6 +11,7 @@ body { background: #000; color: #e0e0e0; font-family: "IBM Plex Mono", monospace
11
11
  .sidebar { width: 280px; background: #0a0a0a; border-right: 1px solid #1a1a1a; display: flex; flex-direction: column; }
12
12
  .main { flex: 1; display: flex; flex-direction: column; background: #000; }
13
13
  .header { height: 60px; background: #0a0a0a; border-bottom: 1px solid #1a1a1a; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; }
14
+ .header-right { display: flex; align-items: center; gap: 12px; }
14
15
  .logo { color: #22dd55; font-weight: bold; font-size: 1.25rem; letter-spacing: -0.5px; }
15
16
  .sessions { flex: 1; overflow-y: auto; padding: 12px; }
16
17
  .session-item { padding: 10px 12px; border-radius: 8px; cursor: pointer; margin-bottom: 6px; border: 1px solid transparent; transition: all 0.2s; }
@@ -18,6 +19,8 @@ body { background: #000; color: #e0e0e0; font-family: "IBM Plex Mono", monospace
18
19
  .session-item.active { background: #141414; border-color: #22dd55; }
19
20
  .new-session { padding: 10px 12px; background: #22dd55; color: #000; border: none; border-radius: 8px; font-family: inherit; font-weight: bold; cursor: pointer; width: 100%; margin-bottom: 12px; }
20
21
  .new-session:hover { background: #3dffa0; }
22
+ .settings-btn { padding: 8px 16px; background: #141414; color: #e0e0e0; border: 1px solid #22dd5533; border-radius: 8px; font-family: inherit; font-size: 12px; cursor: pointer; }
23
+ .settings-btn:hover { border-color: #22dd55; background: #1a1a1a; }
21
24
  .chat { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
22
25
  .messages { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 16px; }
23
26
  .message { max-width: 70%; padding: 12px 16px; border-radius: 12px; line-height: 1.5; }
@@ -33,6 +36,40 @@ body { background: #000; color: #e0e0e0; font-family: "IBM Plex Mono", monospace
33
36
  #sendBtn:disabled { background: #1a1a1a; color: #666; cursor: not-allowed; }
34
37
  .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; color: #666; }
35
38
  .status-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; background: #1a1a1a; border: 1px solid #22dd5533; color: #22dd55; font-size: 11px; margin-left: 8px; }
39
+ /* Settings panel */
40
+ .settings-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 50; display: none; }
41
+ .settings-panel { position: fixed; top: 10%; left: 10%; right: 10%; bottom: 10%; background: #0a0a0a; border: 1px solid #22dd5533; border-radius: 12px; z-index: 60; display: none; flex-direction: column; overflow: hidden; }
42
+ .settings-panel.open, .settings-overlay.open { display: block; }
43
+ .settings-header { height: 50px; background: #0a0a0a; border-bottom: 1px solid #1a1a1a; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; }
44
+ .settings-title { color: #22dd55; font-weight: bold; font-size: 1.1rem; }
45
+ .settings-close { background: transparent; border: 1px solid #22dd5533; color: #e0e0e0; border-radius: 6px; padding: 4px 12px; font-family: inherit; font-size: 12px; cursor: pointer; }
46
+ .settings-close:hover { border-color: #22dd55; }
47
+ .settings-tabs { display: flex; border-bottom: 1px solid #1a1a1a; }
48
+ .settings-tab { padding: 10px 16px; background: transparent; border: none; border-bottom: 2px solid transparent; color: #888; font-family: inherit; font-size: 12px; cursor: pointer; }
49
+ .settings-tab:hover { color: #e0e0e0; background: #141414; }
50
+ .settings-tab.active { color: #22dd55; border-bottom-color: #22dd55; }
51
+ .settings-content { flex: 1; overflow-y: auto; padding: 24px; }
52
+ .settings-section { margin-bottom: 30px; }
53
+ .settings-section-title { color: #888; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 14px; }
54
+ .setting-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #141414; }
55
+ .setting-row:last-child { border-bottom: none; }
56
+ .setting-label { color: #e0e0e0; font-size: 13px; }
57
+ .setting-value { color: #888; font-size: 12px; }
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
+ .setting-select:focus, .setting-input:focus { border-color: #22dd55; }
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; }
62
+ .provider-row:last-child { border-bottom: none; }
63
+ #providersList { max-height: 340px; overflow-y: auto; border: 1px solid #141414; border-radius: 6px; padding: 10px; background: #000; margin-top: 8px; }
64
+ .provider-info { display: flex; align-items: center; gap: 10px; flex: 1; }
65
+ .provider-name { font-weight: bold; color: #e0e0e0; }
66
+ .provider-id { color: #666; font-size: 11px; }
67
+ .provider-status { font-size: 11px; padding: 2px 8px; border-radius: 4px; }
68
+ .provider-status.free { background: #1a1a1a; color: #22dd55; border: 1px solid #22dd5533; }
69
+ .provider-status.keyed { background: #1a1a1a; color: #ff9900; border: 1px solid #ff990033; }
70
+ .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
+ .btn-save:hover { background: #3dffa0; }
72
+ .btn-save:disabled { background: #1a1a1a; color: #666; cursor: not-allowed; }
36
73
  </style>
37
74
  </head>
38
75
  <body>
@@ -40,6 +77,7 @@ body { background: #000; color: #e0e0e0; font-family: "IBM Plex Mono", monospace
40
77
  <div class="sidebar">
41
78
  <div class="header">
42
79
  <div class="logo">chode <span class="status-badge">serve</span></div>
80
+ <button class="settings-btn" id="settingsBtn">Settings</button>
43
81
  </div>
44
82
  <button class="new-session" id="newSessionBtn">New Session</button>
45
83
  <div class="sessions" id="sessionsList">
@@ -66,9 +104,81 @@ body { background: #000; color: #e0e0e0; font-family: "IBM Plex Mono", monospace
66
104
  </div>
67
105
  </div>
68
106
  </div>
107
+ <div class="settings-overlay" id="settingsOverlay"></div>
108
+ <div class="settings-panel" id="settingsPanel">
109
+ <div class="settings-header">
110
+ <div class="settings-title">chode settings</div>
111
+ <button class="settings-close" id="settingsClose">Close</button>
112
+ </div>
113
+ <div class="settings-tabs">
114
+ <button class="settings-tab active" data-tab="general">General</button>
115
+ <button class="settings-tab" data-tab="providers">Providers</button>
116
+ <button class="settings-tab" data-tab="session">Session</button>
117
+ </div>
118
+ <div class="settings-content">
119
+ <div class="settings-section" id="tab-general">
120
+ <div class="setting-row">
121
+ <span class="setting-label">Free mode</span>
122
+ <label style="display:flex;align-items:center;gap:8px;cursor:pointer;"><input type="checkbox" id="settingFreeOnly" class="setting-checkbox"><span style="color:#e0e0e0;font-size:12px;">Enable free (keyless) providers only</span></label>
123
+ </div>
124
+ <div class="setting-row">
125
+ <span class="setting-label">Default provider</span>
126
+ <select id="settingDefaultProvider" class="setting-select"></select>
127
+ </div>
128
+ <div class="setting-row">
129
+ <span class="setting-label">Mode</span>
130
+ <select id="settingMode" class="setting-select">
131
+ <option value="execute">Execute</option>
132
+ <option value="plan">Plan</option>
133
+ </select>
134
+ </div>
135
+ <div class="setting-row">
136
+ <span class="setting-label">Theme</span>
137
+ <select id="settingTheme" class="setting-select">
138
+ <option value="dark">Dark</option>
139
+ <option value="light">Light</option>
140
+ <option value="system">System</option>
141
+ </select>
142
+ </div>
143
+ <div class="setting-row">
144
+ <span class="setting-label">Details</span>
145
+ <label style="display:flex;align-items:center;gap:8px;cursor:pointer;"><input type="checkbox" id="settingDetails" class="setting-checkbox"><span style="color:#e0e0e0;font-size:12px;">Show tool and provider diagnostics</span></label>
146
+ </div>
147
+ <div class="setting-row">
148
+ <span class="setting-label">Timeout (seconds)</span>
149
+ <input type="number" id="settingTimeoutSeconds" class="setting-input" placeholder="e.g. 600">
150
+ </div>
151
+ <div class="setting-row">
152
+ <span class="setting-label">Acceptance test</span>
153
+ <input type="text" id="settingAcceptanceTest" class="setting-input" placeholder="path/to/test.cjs">
154
+ </div>
155
+ <div class="setting-row">
156
+ <span class="setting-label">Yolo mode</span>
157
+ <label style="display:flex;align-items:center;gap:8px;cursor:pointer;"><input type="checkbox" id="settingYolo" class="setting-checkbox"><span style="color:#e0e0e0;font-size:12px;">Skip confirmation prompts</span></label>
158
+ </div>
159
+ <button class="btn-save" id="settingsSave">Save settings</button>
160
+ </div>
161
+ <div class="settings-section" id="tab-providers" style="display:none;">
162
+ <div class="setting-section-title">Available providers</div>
163
+ <div id="providersList"></div>
164
+ </div>
165
+ <div class="settings-section" id="tab-session" style="display:none;">
166
+ <div class="setting-section-title">Current session</div>
167
+ <div class="setting-row">
168
+ <span class="setting-label">Session ID</span>
169
+ <span class="setting-value" id="settingSessionId">-</span>
170
+ </div>
171
+ <div class="setting-row">
172
+ <span class="setting-label">Messages</span>
173
+ <span class="setting-value" id="settingSessionMessages">0</span>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </div>
69
178
  <script>
70
179
  const API = "http://127.0.0.1:" + (window.location.port || "4096");
71
180
  let currentSessionId = null;
181
+ let settingsOpen = false;
72
182
 
73
183
  async function loadSessions() {
74
184
  const res = await fetch(API + "/api/sessions");
@@ -82,6 +192,7 @@ async function loadSessions() {
82
192
  div.onclick = function() { selectSession(s.id); };
83
193
  list.appendChild(div);
84
194
  });
195
+ updateSessionTab();
85
196
  }
86
197
 
87
198
  async function createSession() {
@@ -140,6 +251,92 @@ async function sendMessage() {
140
251
  }
141
252
  }
142
253
 
254
+ async function loadSettings() {
255
+ try {
256
+ const res = await fetch(API + "/api/settings");
257
+ const data = await res.json();
258
+ if (!data.ok) return;
259
+ var cfg = data.config || {};
260
+ var st = data.state || {};
261
+ document.getElementById("settingFreeOnly").checked = !!cfg.freeOnly;
262
+ document.getElementById("settingDefaultProvider").value = cfg.defaultProvider || "auto";
263
+ document.getElementById("settingMode").value = st.mode || "execute";
264
+ document.getElementById("settingTheme").value = st.theme || "dark";
265
+ document.getElementById("settingDetails").checked = !!st.details;
266
+ document.getElementById("settingTimeoutSeconds").value = st.timeoutSeconds || "";
267
+ document.getElementById("settingAcceptanceTest").value = st.acceptanceTest || "";
268
+ document.getElementById("settingYolo").checked = !!st.yolo;
269
+ } catch(e) {}
270
+ }
271
+
272
+ async function saveSettings() {
273
+ var body = {
274
+ freeOnly: document.getElementById("settingFreeOnly").checked,
275
+ defaultProvider: document.getElementById("settingDefaultProvider").value,
276
+ mode: document.getElementById("settingMode").value,
277
+ theme: document.getElementById("settingTheme").value,
278
+ details: document.getElementById("settingDetails").checked,
279
+ timeoutSeconds: document.getElementById("settingTimeoutSeconds").value || null,
280
+ acceptanceTest: document.getElementById("settingAcceptanceTest").value || null,
281
+ yolo: document.getElementById("settingYolo").checked,
282
+ };
283
+ try {
284
+ const res = await fetch(API + "/api/settings", {
285
+ method: "POST",
286
+ headers: { "Content-Type": "application/json" },
287
+ body: JSON.stringify(body)
288
+ });
289
+ const data = await res.json();
290
+ if (data.ok) {
291
+ // Refresh provider list for default provider select
292
+ await loadProviders();
293
+ }
294
+ } catch(e) {}
295
+ }
296
+
297
+ async function loadProviders() {
298
+ try {
299
+ const res = await fetch(API + "/api/providers");
300
+ const data = await res.json();
301
+ var select = document.getElementById("settingDefaultProvider");
302
+ var list = document.getElementById("providersList");
303
+ select.innerHTML = '<option value="auto">auto</option>';
304
+ list.innerHTML = '';
305
+ (data.providers || []).forEach(function(p) {
306
+ var opt = document.createElement("option");
307
+ opt.value = p.id;
308
+ opt.textContent = p.name || p.id;
309
+ select.appendChild(opt);
310
+ var row = document.createElement("div");
311
+ row.className = "provider-row";
312
+ var statusCls = p.requiresKey ? "keyed" : "free";
313
+ var statusText = p.requiresKey ? "needs API key" : "free";
314
+ var extra = p.builtIn ? "" : ' <span style="font-size:10px;color:#888;margin-left:6px;">(' + (p.models||0) + ' models)</span>';
315
+ row.innerHTML =
316
+ '<div class="provider-info">' +
317
+ '<div class="provider-name">' + escapeHtml(p.name || p.id) + extra + '</div>' +
318
+ '<div class="provider-id">' + escapeHtml(p.id) + (p.baseUrl ? ' · ' + escapeHtml(String(p.baseUrl).slice(0,40)) : '') + '</div>' +
319
+ '</div>' +
320
+ '<div class="provider-status ' + statusCls + '">' + statusText + '</div>';
321
+ list.appendChild(row);
322
+ });
323
+ } catch(e) {}
324
+ }
325
+
326
+ function updateSessionTab() {
327
+ var sidEl = document.getElementById("settingSessionId");
328
+ var msgEl = document.getElementById("settingSessionMessages");
329
+ if (currentSessionId) {
330
+ sidEl.textContent = currentSessionId;
331
+ // count messages from the session list item
332
+ var item = Array.from(document.querySelectorAll(".session-item")).find(function(el){ return el.textContent && el.textContent.includes(currentSessionId); });
333
+ msgEl.textContent = "loaded";
334
+ } else {
335
+ sidEl.textContent = "-";
336
+ msgEl.textContent = "0";
337
+ }
338
+ }
339
+
143
340
  function addMessage(role, content, scroll) {
144
341
  var messagesEl = document.getElementById("messages");
145
342
  var div = document.createElement("div");
@@ -155,11 +352,38 @@ function escapeHtml(text) {
155
352
  return div.innerHTML;
156
353
  }
157
354
 
355
+ function openSettings() {
356
+ document.getElementById("settingsOverlay").classList.add("open");
357
+ document.getElementById("settingsPanel").classList.add("open");
358
+ settingsOpen = true;
359
+ loadSettings();
360
+ loadProviders();
361
+ }
362
+
363
+ function closeSettings() {
364
+ document.getElementById("settingsOverlay").classList.remove("open");
365
+ document.getElementById("settingsPanel").classList.remove("open");
366
+ settingsOpen = false;
367
+ }
368
+
158
369
  document.getElementById("newSessionBtn").onclick = createSession;
159
370
  document.getElementById("sendBtn").onclick = sendMessage;
160
371
  document.getElementById("messageInput").addEventListener("keypress", function(e) {
161
372
  if (e.key === "Enter") sendMessage();
162
373
  });
374
+ document.getElementById("settingsBtn").onclick = openSettings;
375
+ document.getElementById("settingsClose").onclick = closeSettings;
376
+ document.getElementById("settingsOverlay").onclick = function(e) { if (e.target === this) closeSettings(); };
377
+ document.getElementById("settingsSave").onclick = function() { saveSettings(); closeSettings(); };
378
+
379
+ document.querySelectorAll(".settings-tab").forEach(function(tab) {
380
+ tab.onclick = function() {
381
+ document.querySelectorAll(".settings-tab").forEach(function(t){ t.classList.remove("active"); });
382
+ tab.classList.add("active");
383
+ document.querySelectorAll(".settings-section").forEach(function(s){ s.style.display = "none"; });
384
+ document.getElementById("tab-" + tab.getAttribute("data-tab")).style.display = "block";
385
+ };
386
+ });
163
387
 
164
388
  loadSessions();
165
389
  setTimeout(async function() {
@@ -169,4 +393,4 @@ setTimeout(async function() {
169
393
  }, 500);
170
394
  </script>
171
395
  </body>
172
- </html>
396
+ </html>