claude-simple 0.1.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.
Files changed (50) hide show
  1. package/README.md +225 -0
  2. package/client.js +139 -0
  3. package/dist/addon-fit.min.js +2 -0
  4. package/dist/app.0.1.0.min.js +825 -0
  5. package/dist/highlight.min.css +10 -0
  6. package/dist/highlight.min.js +1244 -0
  7. package/dist/index.html +16 -0
  8. package/dist/marked.min.js +6 -0
  9. package/dist/style.0.1.0.min.css +1050 -0
  10. package/dist/xterm.min.css +209 -0
  11. package/dist/xterm.min.js +2 -0
  12. package/gateway.js +349 -0
  13. package/package.json +41 -0
  14. package/public/addon-fit.min.js +2 -0
  15. package/public/agent-panel.js +1079 -0
  16. package/public/api.js +78 -0
  17. package/public/app.js +95 -0
  18. package/public/base.css +4 -0
  19. package/public/cache.js +27 -0
  20. package/public/chat.js +1087 -0
  21. package/public/files.js +476 -0
  22. package/public/git.js +130 -0
  23. package/public/index.html +61 -0
  24. package/public/lib.js +2 -0
  25. package/public/router.js +71 -0
  26. package/public/settings.js +90 -0
  27. package/public/shell/boot.js +159 -0
  28. package/public/shell/dashboard.js +269 -0
  29. package/public/shell/hub.js +192 -0
  30. package/public/shell/layout.js +195 -0
  31. package/public/shell/notes.js +323 -0
  32. package/public/shell/session-context.js +19 -0
  33. package/public/shell/session-list.js +442 -0
  34. package/public/shell/session-nav.js +299 -0
  35. package/public/shell/templates.js +342 -0
  36. package/public/shell/util.js +63 -0
  37. package/public/shell.js +589 -0
  38. package/public/state.js +362 -0
  39. package/public/style.css +1048 -0
  40. package/public/terminal.js +177 -0
  41. package/public/xterm.min.css +209 -0
  42. package/public/xterm.min.js +2 -0
  43. package/server.js +12 -0
  44. package/src/agents/claude.js +58 -0
  45. package/src/agents/codex.js +284 -0
  46. package/src/agents/grok.js +318 -0
  47. package/src/agents/meta-agent.js +1952 -0
  48. package/src/agents/models.js +339 -0
  49. package/src/agents/sessions.js +1508 -0
  50. package/src/app.js +1282 -0
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Discover available models for Claude / Codex / Grok from local CLI homes.
3
+ * Never returns secrets (API keys, tokens, base URLs with credentials).
4
+ */
5
+ import { promises as fs } from 'fs';
6
+ import path from 'path';
7
+ import os from 'os';
8
+
9
+ /** Static fallbacks when local cache/config is missing. */
10
+ export const FALLBACK_MODELS = {
11
+ claude: {
12
+ model: 'claude-sonnet-4-5',
13
+ models: [
14
+ { value: 'claude-sonnet-4-5', label: 'sonnet-4-5' },
15
+ { value: 'claude-sonnet-4-6', label: 'sonnet-4-6' },
16
+ { value: 'claude-opus-4-5', label: 'opus-4-5' },
17
+ { value: 'claude-haiku-4-5', label: 'haiku-4-5' },
18
+ ],
19
+ },
20
+ codex: {
21
+ model: 'gpt-5.4',
22
+ models: [
23
+ { value: 'gpt-5.4', label: 'gpt-5.4' },
24
+ { value: 'gpt-5.3', label: 'gpt-5.3' },
25
+ { value: 'gpt-5.2', label: 'gpt-5.2' },
26
+ { value: 'o3', label: 'o3' },
27
+ { value: 'o4-mini', label: 'o4-mini' },
28
+ { value: 'codex-mini-latest', label: 'codex-mini' },
29
+ ],
30
+ },
31
+ grok: {
32
+ model: 'grok-4.5',
33
+ models: [
34
+ { value: 'grok-4.5', label: 'grok-4.5' },
35
+ { value: 'grok-4', label: 'grok-4' },
36
+ { value: 'grok-3', label: 'grok-3' },
37
+ { value: 'grok-3-mini', label: 'grok-3-mini' },
38
+ ],
39
+ },
40
+ };
41
+
42
+ const DEFAULT_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
43
+
44
+ function shortLabel(id) {
45
+ if (!id) return '';
46
+ // claude-sonnet-4-5 → sonnet-4-5 when long
47
+ const m = String(id).match(/^claude-(.+)$/i);
48
+ if (m) return m[1];
49
+ return String(id);
50
+ }
51
+
52
+ function uniqModels(list) {
53
+ const seen = new Set();
54
+ const out = [];
55
+ for (const m of list) {
56
+ if (!m?.value) continue;
57
+ const v = String(m.value).trim();
58
+ if (!v || seen.has(v)) continue;
59
+ seen.add(v);
60
+ out.push({
61
+ value: v,
62
+ label: m.label || shortLabel(v),
63
+ ...(m.efforts?.length ? { efforts: m.efforts } : {}),
64
+ ...(m.defaultEffort ? { defaultEffort: m.defaultEffort } : {}),
65
+ });
66
+ }
67
+ return out;
68
+ }
69
+
70
+ function ensureDefaultInList(models, model) {
71
+ if (!model) return models;
72
+ if (models.some(m => m.value === model)) return models;
73
+ return [{ value: model, label: shortLabel(model) }, ...models];
74
+ }
75
+
76
+ async function readText(file) {
77
+ try {
78
+ return await fs.readFile(file, 'utf8');
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ async function readJson(file) {
85
+ const t = await readText(file);
86
+ if (!t) return null;
87
+ try {
88
+ return JSON.parse(t);
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ /** Minimal TOML string-value reader (no full parser). */
95
+ function tomlTopLevelString(content, key) {
96
+ if (!content) return null;
97
+ const re = new RegExp(`^\\s*${key}\\s*=\\s*"([^"]+)"\\s*$`, 'm');
98
+ const m = content.match(re);
99
+ return m ? m[1] : null;
100
+ }
101
+
102
+ function tomlSectionString(content, section, key) {
103
+ if (!content) return null;
104
+ const secRe = new RegExp(`\\[${section.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\]([\\s\\S]*?)(?=\\n\\[|$)`);
105
+ const sm = content.match(secRe);
106
+ if (!sm) return null;
107
+ const body = sm[1];
108
+ const re = new RegExp(`^\\s*${key}\\s*=\\s*"([^"]+)"\\s*$`, 'm');
109
+ const m = body.match(re);
110
+ return m ? m[1] : null;
111
+ }
112
+
113
+ function effortsFromCodexModel(entry) {
114
+ const levels = entry?.supported_reasoning_levels;
115
+ if (!Array.isArray(levels) || !levels.length) return null;
116
+ const efforts = levels
117
+ .map(l => (typeof l === 'string' ? l : l?.effort || l?.id || l?.value))
118
+ .filter(Boolean)
119
+ .map(String);
120
+ return efforts.length ? efforts : null;
121
+ }
122
+
123
+ function effortsFromGrokInfo(info) {
124
+ const list = info?.reasoning_efforts;
125
+ if (Array.isArray(list) && list.length) {
126
+ const efforts = list
127
+ .map(e => e?.value || e?.id || e?.effort)
128
+ .filter(Boolean)
129
+ .map(String);
130
+ if (efforts.length) return efforts;
131
+ }
132
+ if (info?.supports_reasoning_effort) return ['low', 'medium', 'high'];
133
+ return null;
134
+ }
135
+
136
+ async function discoverClaude(claudeConfigDirs = []) {
137
+ const dirs = claudeConfigDirs?.length
138
+ ? claudeConfigDirs
139
+ : [path.join(os.homedir(), '.claude')];
140
+
141
+ const found = [];
142
+ let defaultModel = null;
143
+ const sources = [];
144
+
145
+ for (const dir of dirs) {
146
+ for (const name of ['settings.json', 'settings.local.json']) {
147
+ const data = await readJson(path.join(dir, name));
148
+ if (!data || typeof data !== 'object') continue;
149
+ const env = data.env && typeof data.env === 'object' ? data.env : {};
150
+ // Only model-related keys — never auth tokens / base URLs
151
+ const keys = [
152
+ 'ANTHROPIC_MODEL',
153
+ 'ANTHROPIC_DEFAULT_SONNET_MODEL',
154
+ 'ANTHROPIC_DEFAULT_OPUS_MODEL',
155
+ 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
156
+ 'ANTHROPIC_SMALL_FAST_MODEL',
157
+ 'CLAUDE_MODEL',
158
+ ];
159
+ for (const k of keys) {
160
+ const v = env[k];
161
+ if (typeof v === 'string' && v.trim()) {
162
+ found.push({ value: v.trim(), label: shortLabel(v.trim()) });
163
+ if (k === 'ANTHROPIC_MODEL' || k === 'CLAUDE_MODEL') {
164
+ if (!defaultModel) defaultModel = v.trim();
165
+ }
166
+ }
167
+ }
168
+ if (typeof data.model === 'string' && data.model.trim()) {
169
+ found.push({ value: data.model.trim(), label: shortLabel(data.model.trim()) });
170
+ if (!defaultModel) defaultModel = data.model.trim();
171
+ }
172
+ if (found.length) sources.push(path.join(dir, name));
173
+ }
174
+ }
175
+
176
+ let models = uniqModels(found);
177
+ let source = 'fallback';
178
+ if (models.length) {
179
+ source = 'settings';
180
+ // Custom gateway / third-party models: list only what is configured
181
+ } else {
182
+ models = FALLBACK_MODELS.claude.models.map(m => ({ ...m }));
183
+ defaultModel = FALLBACK_MODELS.claude.model;
184
+ }
185
+
186
+ if (!defaultModel) defaultModel = models[0]?.value || FALLBACK_MODELS.claude.model;
187
+ models = ensureDefaultInList(models, defaultModel);
188
+
189
+ return {
190
+ model: defaultModel,
191
+ models,
192
+ efforts: DEFAULT_EFFORTS,
193
+ source,
194
+ sources: sources.slice(0, 4),
195
+ };
196
+ }
197
+
198
+ async function discoverCodex(codexHome) {
199
+ const home = codexHome || path.join(os.homedir(), '.codex');
200
+ const cache = await readJson(path.join(home, 'models_cache.json'));
201
+ const cfgText = await readText(path.join(home, 'config.toml'));
202
+ const cfgModel = tomlTopLevelString(cfgText, 'model');
203
+ const cfgEffort = tomlTopLevelString(cfgText, 'model_reasoning_effort');
204
+
205
+ let models = [];
206
+ let source = 'fallback';
207
+
208
+ if (cache && Array.isArray(cache.models) && cache.models.length) {
209
+ source = 'cache';
210
+ models = cache.models
211
+ .filter(m => m && (m.visibility == null || m.visibility === 'list' || m.visibility === 'visible'))
212
+ .map(m => {
213
+ const value = m.slug || m.id || m.model;
214
+ if (!value) return null;
215
+ const efforts = effortsFromCodexModel(m);
216
+ return {
217
+ value: String(value),
218
+ label: m.display_name || shortLabel(value),
219
+ ...(efforts ? { efforts } : {}),
220
+ ...(m.default_reasoning_level ? { defaultEffort: String(m.default_reasoning_level) } : {}),
221
+ };
222
+ })
223
+ .filter(Boolean);
224
+ // Prefer priority order if present
225
+ if (cache.models.some(m => m?.priority != null)) {
226
+ const prio = new Map(cache.models.map(m => [m.slug || m.id, m.priority ?? 999]));
227
+ models.sort((a, b) => (prio.get(a.value) ?? 999) - (prio.get(b.value) ?? 999));
228
+ }
229
+ }
230
+
231
+ if (!models.length) {
232
+ models = FALLBACK_MODELS.codex.models.map(m => ({ ...m }));
233
+ source = 'fallback';
234
+ }
235
+
236
+ let defaultModel = cfgModel || models[0]?.value || FALLBACK_MODELS.codex.model;
237
+ if (cfgModel) source = source === 'fallback' ? 'config' : `${source}+config`;
238
+ models = uniqModels(ensureDefaultInList(models, defaultModel));
239
+
240
+ const active = models.find(m => m.value === defaultModel);
241
+ const efforts = active?.efforts || DEFAULT_EFFORTS;
242
+ const effortDefault = cfgEffort || active?.defaultEffort || null;
243
+
244
+ return {
245
+ model: defaultModel,
246
+ models,
247
+ efforts,
248
+ effortDefault,
249
+ source,
250
+ fetchedAt: cache?.fetched_at || null,
251
+ clientVersion: cache?.client_version || null,
252
+ };
253
+ }
254
+
255
+ async function discoverGrok(grokHome) {
256
+ const home = grokHome || path.join(os.homedir(), '.grok');
257
+ const cache = await readJson(path.join(home, 'models_cache.json'));
258
+ const cfgText = await readText(path.join(home, 'config.toml'));
259
+ const cfgDefault = tomlSectionString(cfgText, 'models', 'default');
260
+
261
+ let models = [];
262
+ let source = 'fallback';
263
+
264
+ if (cache?.models && typeof cache.models === 'object') {
265
+ source = 'cache';
266
+ if (Array.isArray(cache.models)) {
267
+ models = cache.models.map(m => {
268
+ if (typeof m === 'string') return { value: m, label: shortLabel(m) };
269
+ const value = m.id || m.model || m.slug || m.name;
270
+ if (!value) return null;
271
+ const info = m.info || m;
272
+ const efforts = effortsFromGrokInfo(info);
273
+ return {
274
+ value: String(value),
275
+ label: info?.name || m.display_name || shortLabel(value),
276
+ ...(efforts ? { efforts } : {}),
277
+ ...(info?.reasoning_effort ? { defaultEffort: String(info.reasoning_effort) } : {}),
278
+ };
279
+ }).filter(Boolean);
280
+ } else {
281
+ models = Object.entries(cache.models).map(([id, entry]) => {
282
+ const info = entry?.info || entry || {};
283
+ if (info.hidden === true) return null;
284
+ const value = info.id || info.model || id;
285
+ const efforts = effortsFromGrokInfo(info);
286
+ return {
287
+ value: String(value),
288
+ label: info.name || info.system_prompt_label || shortLabel(value),
289
+ ...(efforts ? { efforts } : {}),
290
+ ...(info.reasoning_effort ? { defaultEffort: String(info.reasoning_effort) } : {}),
291
+ };
292
+ }).filter(Boolean);
293
+ }
294
+ }
295
+
296
+ if (!models.length) {
297
+ models = FALLBACK_MODELS.grok.models.map(m => ({ ...m }));
298
+ source = 'fallback';
299
+ }
300
+
301
+ let defaultModel = cfgDefault || models[0]?.value || FALLBACK_MODELS.grok.model;
302
+ if (cfgDefault) source = source === 'fallback' ? 'config' : `${source}+config`;
303
+ models = uniqModels(ensureDefaultInList(models, defaultModel));
304
+
305
+ const active = models.find(m => m.value === defaultModel);
306
+ const efforts = active?.efforts || DEFAULT_EFFORTS;
307
+ const effortDefault = active?.defaultEffort || tomlSectionString(cfgText, 'models', 'default_reasoning_effort') || null;
308
+
309
+ return {
310
+ model: defaultModel,
311
+ models,
312
+ efforts,
313
+ effortDefault,
314
+ source,
315
+ fetchedAt: cache?.fetched_at || null,
316
+ clientVersion: cache?.grok_version || null,
317
+ };
318
+ }
319
+
320
+ /**
321
+ * @param {{ claudeConfigDirs?: string[], codexHome?: string, grokHome?: string, agents?: string[] }} opts
322
+ */
323
+ export async function discoverAllAgentModels(opts = {}) {
324
+ const agents = opts.agents?.length ? opts.agents : ['claude', 'codex', 'grok'];
325
+ const want = new Set(agents);
326
+ const defaults = {};
327
+
328
+ if (want.has('claude')) {
329
+ defaults.claude = await discoverClaude(opts.claudeConfigDirs);
330
+ }
331
+ if (want.has('codex')) {
332
+ defaults.codex = await discoverCodex(opts.codexHome);
333
+ }
334
+ if (want.has('grok')) {
335
+ defaults.grok = await discoverGrok(opts.grokHome);
336
+ }
337
+
338
+ return defaults;
339
+ }