glad-web 1.0.45 → 2.0.1

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 (68) hide show
  1. package/README.md +4 -192
  2. package/THIRD_PARTY_NOTICES.md +27 -0
  3. package/bin/glad.cjs +56 -0
  4. package/package.json +19 -58
  5. package/README.zh-CN.md +0 -198
  6. package/assets/logo.svg +0 -43
  7. package/bin/cli.js +0 -65
  8. package/lib/ai-tools/demo/enhanced-demo.js +0 -625
  9. package/lib/ai-tools/demo/index.js +0 -24
  10. package/lib/ai-tools/demo/responses.js +0 -88
  11. package/lib/ai-tools/detector.js +0 -76
  12. package/lib/ai-tools/registry.js +0 -300
  13. package/lib/claude/cli-usage.js +0 -95
  14. package/lib/claude/config.js +0 -82
  15. package/lib/claude/structured-session.js +0 -884
  16. package/lib/claude/transcript-repository.js +0 -216
  17. package/lib/codex/image-store.js +0 -174
  18. package/lib/codex/structured-session.js +0 -1578
  19. package/lib/commands/config.js +0 -78
  20. package/lib/commands/tools.js +0 -128
  21. package/lib/commands/web.js +0 -586
  22. package/lib/config/constants.js +0 -17
  23. package/lib/config/manager.js +0 -89
  24. package/lib/git/service.js +0 -83
  25. package/lib/notifications/message-formatter.js +0 -94
  26. package/lib/notifications/notification-service.js +0 -143
  27. package/lib/notifications/serverchan-client.js +0 -58
  28. package/lib/notifications/serverchan-settings-store.js +0 -115
  29. package/lib/schedule/job-runner.js +0 -162
  30. package/lib/schedule/job-store.js +0 -167
  31. package/lib/schedule/key-sequences.js +0 -49
  32. package/lib/schedule/scheduler-service.js +0 -39
  33. package/lib/server/routes/notifications.js +0 -52
  34. package/lib/server/routes/providers.js +0 -114
  35. package/lib/server/routes/schedules.js +0 -54
  36. package/lib/server/routes/usage.js +0 -23
  37. package/lib/server/routes/workspace.js +0 -77
  38. package/lib/session/buffer.js +0 -102
  39. package/lib/session/file-attachment-store.js +0 -168
  40. package/lib/session/pty-manager.js +0 -255
  41. package/lib/session/rendered-history.js +0 -225
  42. package/lib/session/session-manager.js +0 -1001
  43. package/lib/session/text-history.js +0 -274
  44. package/lib/usage/ccusage-runner.js +0 -128
  45. package/lib/usage/source-catalog.js +0 -26
  46. package/lib/usage/usage-service.js +0 -226
  47. package/lib/utils/logger.js +0 -74
  48. package/lib/utils/pid.js +0 -67
  49. package/lib/utils/validation.js +0 -53
  50. package/lib/web/claude.js +0 -1129
  51. package/lib/web/codex.js +0 -1042
  52. package/lib/web/composer.js +0 -463
  53. package/lib/web/core.js +0 -373
  54. package/lib/web/git.js +0 -535
  55. package/lib/web/gitgraph.js +0 -293
  56. package/lib/web/index.html +0 -516
  57. package/lib/web/layout.js +0 -72
  58. package/lib/web/notifications.js +0 -163
  59. package/lib/web/schedules.js +0 -245
  60. package/lib/web/session.js +0 -360
  61. package/lib/web/shell.js +0 -59
  62. package/lib/web/styles.css +0 -905
  63. package/lib/web/terminal-scroll.js +0 -81
  64. package/lib/web/theme.js +0 -60
  65. package/lib/web/timed-inputs.js +0 -216
  66. package/lib/web/usage.js +0 -323
  67. package/lib/workspace/service.js +0 -77
  68. package/scripts/check-syntax.js +0 -26
@@ -1,88 +0,0 @@
1
- // 50 randomized responses for demo mode
2
- // Topics: AI coding, mobile development, remote terminals, vibe coding, tech wisdom
3
-
4
- const RESPONSES = [
5
- // AI Coding Assistant vibes
6
- "AI-powered coding is like having a genius pair programmer who never sleeps!",
7
- "The future of development is collaborative - humans and AI working together.",
8
- "Code faster, debug smarter, ship sooner. That's the AI advantage.",
9
- "Sometimes the best code is written when you're away from your desk.",
10
- "AI assistants don't replace developers, they amplify their potential.",
11
-
12
- // Mobile development
13
- "Mobile-first isn't just design philosophy - it's a lifestyle.",
14
- "Your next breakthrough idea might come while you're on the go.",
15
- "The best development environment is the one you have with you.",
16
- "Coding from your phone? Welcome to the future!",
17
- "Distance is no barrier when your terminal fits in your pocket.",
18
-
19
- // Remote terminal access
20
- "SSH has evolved - now it's in your hand.",
21
- "Terminal access from anywhere opens infinite possibilities.",
22
- "Your development machine, always within reach.",
23
- "The cloud isn't just for deployment anymore.",
24
- "Remote doesn't mean disconnected.",
25
-
26
- // Vibe coding
27
- "Great code comes from great vibes.",
28
- "The best debugging happens with a clear mind.",
29
- "Inspiration strikes at unexpected moments - be ready!",
30
- "Code flows better when you're in the zone.",
31
- "Sometimes you need to step away to see the solution clearly.",
32
-
33
- // Tech wisdom
34
- "The right tool at the right time makes all the difference.",
35
- "Automation isn't lazy - it's efficient.",
36
- "Every line of code is a conversation with the future.",
37
- "Simplicity is the ultimate sophistication.",
38
- "Build tools that make you excited to work.",
39
-
40
- // AI tools ecosystem
41
- "Claude, Aider, Copilot - choose your AI companion wisely.",
42
- "The best AI tool is the one that matches your workflow.",
43
- "Multiple AI assistants? Why choose when you can have them all?",
44
- "Each AI has its strengths - know when to use which.",
45
- "The AI coding revolution is here, and it's spectacular.",
46
-
47
- // Developer productivity
48
- "Context switching costs time - keep your flow intact.",
49
- "The fastest code is the code you don't have to write.",
50
- "Good tools get out of your way. Great tools elevate your work.",
51
- "Productivity isn't about hours - it's about focus.",
52
- "Work smarter, not just harder.",
53
-
54
- // Terminal culture
55
- "The terminal is where magic happens.",
56
- "Command line mastery is a superpower.",
57
- "GUI is great, but CLI is poetry.",
58
- "Real developers aren't afraid of the terminal.",
59
- "There's something beautiful about green text on black.",
60
-
61
- // Innovation & future
62
- "The tools we build today shape tomorrow's possibilities.",
63
- "Innovation happens when convenience meets capability.",
64
- "The future of coding is more accessible than ever.",
65
- "Technology should adapt to you, not the other way around.",
66
- "We're living in the golden age of developer tools.",
67
-
68
- // Collaboration
69
- "The best solutions come from diverse perspectives.",
70
- "Share your workflow, inspire others.",
71
- "Community-driven development moves faster.",
72
- "Open source is the rising tide that lifts all boats.",
73
- "Collaboration multiplies creativity.",
74
- ];
75
-
76
- /**
77
- * Get a random response from the pool
78
- * @returns {string} Random response
79
- */
80
- function getRandomResponse() {
81
- const index = Math.floor(Math.random() * RESPONSES.length);
82
- return RESPONSES[index];
83
- }
84
-
85
- module.exports = {
86
- RESPONSES,
87
- getRandomResponse
88
- };
@@ -1,76 +0,0 @@
1
- const { getAllTools, getToolVersion } = require('./registry');
2
- const logger = require('../utils/logger');
3
-
4
- // Detect all installed AI tools
5
- async function detectInstalledTools() {
6
- const allTools = getAllTools();
7
- const installedTools = [];
8
-
9
- logger.debug('Detecting installed AI tools...');
10
-
11
- for (const tool of allTools) {
12
- // Skip demo mode from auto-detection (only available via --ai demo)
13
- if (tool.key === 'demo') {
14
- continue;
15
- }
16
-
17
- try {
18
- const isInstalled = await tool.checkInstalled();
19
-
20
- if (isInstalled) {
21
- const version = await getToolVersion(tool);
22
-
23
- // Always use short command name - PTY will have npm bin in PATH
24
- installedTools.push({
25
- ...tool,
26
- version,
27
- installed: true
28
- });
29
-
30
- logger.debug(`Found: ${tool.displayName} v${version} (command: ${tool.command})`);
31
- }
32
- } catch (err) {
33
- logger.debug(`Check failed for ${tool.displayName}: ${err.message}`);
34
- }
35
- }
36
-
37
- logger.debug(`Detected ${installedTools.length} installed AI tools`);
38
-
39
- return installedTools;
40
- }
41
-
42
- // Check if specific tool is installed
43
- async function isToolInstalled(toolKey) {
44
- const { getToolByKey } = require('./registry');
45
- const tool = getToolByKey(toolKey);
46
-
47
- if (!tool) {
48
- return { installed: false, error: 'Unknown tool' };
49
- }
50
-
51
- try {
52
- const isInstalled = await tool.checkInstalled();
53
-
54
- if (isInstalled) {
55
- const version = await getToolVersion(tool);
56
-
57
- // Always use short command name - PTY will have npm bin in PATH
58
- return {
59
- installed: true,
60
- tool: {
61
- ...tool,
62
- version
63
- }
64
- };
65
- }
66
-
67
- return { installed: false, tool };
68
- } catch (err) {
69
- return { installed: false, tool, error: err.message };
70
- }
71
- }
72
-
73
- module.exports = {
74
- detectInstalledTools,
75
- isToolInstalled
76
- };
@@ -1,300 +0,0 @@
1
- const { exec } = require('child_process');
2
- const { promisify } = require('util');
3
- const execAsync = promisify(exec);
4
- const logger = require('../utils/logger');
5
-
6
- function preview(text, maxChars = 500) {
7
- const value = String(text || '')
8
- .replace(/\r/g, '\\r')
9
- .replace(/\n/g, '\\n')
10
- .replace(/\t/g, '\\t')
11
- .replace(/\x1b/g, '\\x1b');
12
- return value.length > maxChars ? value.slice(0, maxChars) + '...' : value;
13
- }
14
-
15
- // AI Tools Registry
16
- const AI_TOOLS = {
17
- 'claude-code': {
18
- key: 'claude-code',
19
- command: 'claude',
20
- args: [],
21
- displayName: 'Claude',
22
- description: 'Anthropic\'s AI coding assistant',
23
- website: 'https://docs.claude.com',
24
- checkInstalled: async () => await commandExists('claude')
25
- },
26
- 'aider': {
27
- key: 'aider',
28
- command: 'aider',
29
- args: [],
30
- displayName: 'Aider',
31
- description: 'AI pair programming in your terminal',
32
- website: 'https://aider.chat',
33
- checkInstalled: async () => await commandExists('aider')
34
- },
35
- 'codex': {
36
- key: 'codex',
37
- command: 'codex',
38
- args: [],
39
- displayName: 'Codex',
40
- description: 'Official OpenAI Codex CLI (launched April 2025)',
41
- website: 'https://openai.com/codex',
42
- checkInstalled: async () => await commandExists('codex')
43
- },
44
- 'github-copilot': {
45
- key: 'github-copilot',
46
- command: 'copilot',
47
- args: [],
48
- displayName: 'Copilot',
49
- description: 'GitHub\'s command line AI',
50
- website: 'https://github.com/features/copilot',
51
- checkInstalled: async () => await commandExists('copilot')
52
- },
53
- 'cody': {
54
- key: 'cody',
55
- command: 'cody',
56
- args: ['chat'],
57
- displayName: 'Cody',
58
- description: 'Sourcegraph\'s AI assistant (Beta)',
59
- website: 'https://sourcegraph.com/cody',
60
- checkInstalled: async () => await commandExists('cody')
61
- },
62
- 'antigravity': {
63
- key: 'antigravity',
64
- command: 'agy',
65
- args: [],
66
- displayName: 'Antigravity',
67
- description: 'Google Antigravity CLI for local agentic coding workflows',
68
- website: 'https://antigravity.google/docs/cli-overview',
69
- checkInstalled: async () => await commandExists('agy')
70
- },
71
- 'continue': {
72
- key: 'continue',
73
- command: 'cn',
74
- args: [],
75
- displayName: 'Continue',
76
- description: 'Open-source modular AI coding assistant',
77
- website: 'https://continue.dev',
78
- checkInstalled: async () => await commandExists('cn')
79
- },
80
- 'cursor': {
81
- key: 'cursor',
82
- command: 'cursor-agent',
83
- args: [],
84
- displayName: 'Cursor',
85
- description: 'Cursor\'s AI coding assistant CLI (Beta)',
86
- website: 'https://cursor.com/blog/cli',
87
- checkInstalled: async () => await commandExists('cursor-agent')
88
- },
89
- 'chatgpt': {
90
- key: 'chatgpt',
91
- command: 'chatgpt',
92
- args: [],
93
- displayName: 'ChatGPT',
94
- description: 'ChatGPT in your terminal (Go implementation)',
95
- website: 'https://github.com/j178/chatgpt',
96
- checkInstalled: async () => await commandExists('chatgpt')
97
- },
98
- 'sgpt': {
99
- key: 'sgpt',
100
- command: 'sgpt',
101
- args: ['--repl', 'temp'],
102
- displayName: 'ShellGPT',
103
- description: 'ChatGPT-powered shell assistant with REPL mode',
104
- website: 'https://github.com/TheR1D/shell_gpt',
105
- checkInstalled: async () => await commandExists('sgpt')
106
- },
107
- 'mentat': {
108
- key: 'mentat',
109
- command: 'mentat',
110
- args: [],
111
- displayName: 'Mentat',
112
- description: 'AI coding assistant with Git integration',
113
- website: 'https://www.mentat.ai',
114
- checkInstalled: async () => await commandExists('mentat')
115
- },
116
- 'grok': {
117
- key: 'grok',
118
- command: 'grok',
119
- args: [],
120
- displayName: 'Grok',
121
- description: 'xAI\'s Grok AI assistant (by Elon Musk)',
122
- website: 'https://grok.x.ai',
123
- checkInstalled: async () => await commandExists('grok')
124
- },
125
- 'ollama': {
126
- key: 'ollama',
127
- command: 'ollama',
128
- args: ['run', 'codellama'],
129
- displayName: 'Ollama',
130
- description: 'Run LLMs locally (CodeLlama, Llama, etc)',
131
- website: 'https://ollama.ai',
132
- checkInstalled: async () => await commandExists('ollama')
133
- },
134
- 'openhands': {
135
- key: 'openhands',
136
- command: 'openhands',
137
- args: [],
138
- displayName: 'OpenHands',
139
- description: 'Open-source AI software engineer (formerly OpenDevin)',
140
- website: 'https://github.com/All-Hands-AI/OpenHands',
141
- checkInstalled: async () => await commandExists('openhands')
142
- },
143
- 'opencode': {
144
- key: 'opencode',
145
- command: 'opencode',
146
- args: [],
147
- displayName: 'OpenCode',
148
- description: 'Open-source AI coding agent with LSP integration and 75+ LLM providers',
149
- website: 'https://opencode.ai',
150
- checkInstalled: async () => await commandExists('opencode')
151
- },
152
- 'blackbox': {
153
- key: 'blackbox',
154
- command: 'blackboxai',
155
- args: [],
156
- displayName: 'Blackbox AI',
157
- description: 'AI coding assistant with debugging & file editing',
158
- website: 'https://blackbox.ai',
159
- checkInstalled: async () => await commandExists('blackboxai')
160
- },
161
- 'amazon-q': {
162
- key: 'amazon-q',
163
- command: 'q',
164
- args: [],
165
- displayName: 'Amazon Q',
166
- description: 'AWS\'s AI coding companion with free tier',
167
- website: 'https://aws.amazon.com/q/developer',
168
- checkInstalled: async () => await commandExists('q')
169
- },
170
- 'pi': {
171
- key: 'pi',
172
- command: 'pi',
173
- args: [],
174
- displayName: 'Pi',
175
- description: 'Minimal AI coding agent with extensions, skills, and 15+ LLM providers',
176
- website: 'https://shittycodingagent.ai',
177
- checkInstalled: async () => await commandExists('pi')
178
- },
179
- 'kilo': {
180
- key: 'kilo',
181
- command: 'kilo',
182
- args: [],
183
- displayName: 'Kilo',
184
- description: 'Agentic engineering CLI with 500+ models and parallel mode',
185
- website: 'https://kilo.ai',
186
- checkInstalled: async () => await commandExists('kilo')
187
- },
188
- 'qodercli': {
189
- key: 'qodercli',
190
- command: 'qodercli',
191
- args: [],
192
- displayName: 'Qoder',
193
- description: 'Qoder AI coding assistant with interactive CLI',
194
- website: 'https://qoder.com',
195
- checkInstalled: async () => await commandExists('qodercli')
196
- },
197
- 'demo': {
198
- key: 'demo',
199
- command: 'node',
200
- args: [require('path').join(__dirname, 'demo', 'index.js')],
201
- displayName: 'Demo',
202
- description: 'Interactive demo for testing (no AI installation required)',
203
- website: 'https://gitee.com/anonymous/glad',
204
- checkInstalled: async () => true // Always available
205
- }
206
- };
207
-
208
- // Check if command exists
209
- async function commandExists(command) {
210
- try {
211
- const isWindows = process.platform === 'win32';
212
- const checkCommand = isWindows ? `where ${command}` : `command -v ${command}`;
213
- const { stdout, stderr } = await execAsync(checkCommand, { timeout: 5000 });
214
- logger.debugInfo(`[tool-detect] command exists: ${command}; stdout="${preview(stdout)}"; stderr="${preview(stderr)}"`);
215
- return true;
216
- } catch (err) {
217
- logger.debugInfo(`[tool-detect] command missing: ${command}; error="${preview(err.message)}"; stdout="${preview(err.stdout)}"; stderr="${preview(err.stderr)}"`);
218
- return false;
219
- }
220
- }
221
-
222
- // Get tool version
223
- async function getToolVersion(tool) {
224
- // Try --version first
225
- try {
226
- const { stdout, stderr } = await execAsync(`${tool.command} --version 2>&1`, { timeout: 8000 });
227
- logger.debugInfo(`[tool-version] ${tool.displayName} --version stdout="${preview(stdout)}"; stderr="${preview(stderr)}"`);
228
- const version = parseVersion(stdout);
229
- if (version !== 'unknown') {
230
- return version;
231
- }
232
- } catch (err) {
233
- logger.debugInfo(`[tool-version] ${tool.displayName} --version failed: ${preview(err.message)}; stdout="${preview(err.stdout)}"; stderr="${preview(err.stderr)}"`);
234
- // Ignore error, will try -v next
235
- }
236
-
237
- // Try -v as fallback
238
- try {
239
- const { stdout, stderr } = await execAsync(`${tool.command} -v 2>&1`, { timeout: 8000 });
240
- logger.debugInfo(`[tool-version] ${tool.displayName} -v stdout="${preview(stdout)}"; stderr="${preview(stderr)}"`);
241
- const version = parseVersion(stdout);
242
- logger.debugInfo(`[tool-version] ${tool.displayName} parsed version="${version}"`);
243
- return version;
244
- } catch (err) {
245
- logger.debugInfo(`[tool-version] ${tool.displayName} -v failed: ${preview(err.message)}; stdout="${preview(err.stdout)}"; stderr="${preview(err.stderr)}"`);
246
- return 'unknown';
247
- }
248
- }
249
-
250
- // Parse version from output
251
- function parseVersion(output) {
252
- const versionMatch = output.match(/(\d+\.\d+\.\d+)/);
253
- if (versionMatch) {
254
- return versionMatch[1];
255
- }
256
-
257
- const simpleMatch = output.match(/(\d+\.\d+)/);
258
- if (simpleMatch) {
259
- return simpleMatch[1];
260
- }
261
-
262
- return 'unknown';
263
- }
264
-
265
- // Get tool by key
266
- function getToolByKey(key) {
267
- // Normalize key
268
- const normalizedKey = key.toLowerCase().replace(/\s+/g, '-');
269
-
270
- // Try exact match
271
- if (AI_TOOLS[normalizedKey]) {
272
- return AI_TOOLS[normalizedKey];
273
- }
274
-
275
- // Try fuzzy match
276
- for (const [toolKey, tool] of Object.entries(AI_TOOLS)) {
277
- if (tool.displayName.toLowerCase() === key.toLowerCase()) {
278
- return tool;
279
- }
280
- if (tool.command === key) {
281
- return tool;
282
- }
283
- }
284
-
285
- return null;
286
- }
287
-
288
- // Get all tools
289
- function getAllTools() {
290
- return Object.values(AI_TOOLS);
291
- }
292
-
293
- module.exports = {
294
- AI_TOOLS,
295
- commandExists,
296
- getToolVersion,
297
- parseVersion,
298
- getToolByKey,
299
- getAllTools
300
- };
@@ -1,95 +0,0 @@
1
- function stripTerminalFormatting(value) {
2
- return String(value || '')
3
- .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
4
- .replace(/\r/g, '');
5
- }
6
-
7
- function parseTokenCount(value) {
8
- const text = String(value || '').trim().replace(/,/g, '');
9
- const match = text.match(/^([\d.]+)\s*([kmb])?$/i);
10
- if (!match) return null;
11
- const amount = Number(match[1]);
12
- if (!Number.isFinite(amount)) return null;
13
- const multiplier = { k: 1_000, m: 1_000_000, b: 1_000_000_000 }[String(match[2] || '').toLowerCase()] || 1;
14
- return Math.round(amount * multiplier);
15
- }
16
-
17
- function parseClaudeUsageOutput(output) {
18
- const text = stripTerminalFormatting(output);
19
- const cost = text.match(/Total cost:\s*\$([\d,.]+)/i);
20
- const apiDuration = text.match(/Total duration \(API\):\s*([^\n]+)/i);
21
- const wallDuration = text.match(/Total duration \(wall\):\s*([^\n]+)/i);
22
- const codeChanges = text.match(/Total code changes:\s*([\d,]+) lines added,\s*([\d,]+) lines removed/i);
23
- const tokens = text.match(/Usage:\s*([\d,.kmb]+) input,\s*([\d,.kmb]+) output,\s*([\d,.kmb]+) cache read,\s*([\d,.kmb]+) cache write/i);
24
- const models = [];
25
- const modelPattern = /^\s*(.+?):\s*([\d,.kmb]+) input,\s*([\d,.kmb]+) output,\s*([\d,.kmb]+) cache read,\s*([\d,.kmb]+) cache write(?:\s*\(\$([\d,.]+)\))?\s*$/gim;
26
- for (const match of text.matchAll(modelPattern)) {
27
- if (/^Usage$/i.test(match[1].trim())) continue;
28
- models.push({
29
- model: match[1].trim(),
30
- inputTokens: parseTokenCount(match[2]),
31
- outputTokens: parseTokenCount(match[3]),
32
- cacheReadTokens: parseTokenCount(match[4]),
33
- cacheWriteTokens: parseTokenCount(match[5]),
34
- costUsd: match[6] ? Number(match[6].replace(/,/g, '')) : null
35
- });
36
- }
37
-
38
- if (!cost && !apiDuration && !wallDuration && !codeChanges && !tokens && models.length === 0) {
39
- throw new Error('Claude CLI returned an unrecognized /usage response');
40
- }
41
-
42
- const totalModelValue = key => models.length > 0
43
- ? models.reduce((sum, model) => sum + Number(model[key] || 0), 0)
44
- : null;
45
-
46
- return {
47
- totalCostUsd: cost ? Number(cost[1].replace(/,/g, '')) : null,
48
- apiDuration: apiDuration ? apiDuration[1].trim() : null,
49
- wallDuration: wallDuration ? wallDuration[1].trim() : null,
50
- linesAdded: codeChanges ? Number(codeChanges[1].replace(/,/g, '')) : null,
51
- linesRemoved: codeChanges ? Number(codeChanges[2].replace(/,/g, '')) : null,
52
- inputTokens: tokens ? parseTokenCount(tokens[1]) : totalModelValue('inputTokens'),
53
- outputTokens: tokens ? parseTokenCount(tokens[2]) : totalModelValue('outputTokens'),
54
- cacheReadTokens: tokens ? parseTokenCount(tokens[3]) : totalModelValue('cacheReadTokens'),
55
- cacheWriteTokens: tokens ? parseTokenCount(tokens[4]) : totalModelValue('cacheWriteTokens'),
56
- models
57
- };
58
- }
59
-
60
- function parseClaudeContextOutput(output) {
61
- const text = stripTerminalFormatting(output);
62
- const model = text.match(/^\*\*Model:\*\*\s*(.+?)\s*$/mi);
63
- const tokens = text.match(/^\*\*Tokens:\*\*\s*([\d,.]+\s*[kmb]?)\s*\/\s*([\d,.]+\s*[kmb]?)\s*\(([\d.]+)%\)/mi);
64
- if (!tokens) throw new Error('Claude CLI returned an unrecognized /context response');
65
-
66
- const usedTokens = parseTokenCount(tokens[1]);
67
- const maxTokens = parseTokenCount(tokens[2]);
68
- const categories = [];
69
- for (const line of text.split('\n')) {
70
- const columns = line.split('|').slice(1, -1).map(value => value.trim());
71
- if (columns.length < 3 || !columns[0] || /^category$/i.test(columns[0]) || /^-+$/.test(columns[0])) continue;
72
- const categoryTokens = parseTokenCount(columns[1]);
73
- if (categoryTokens == null || !/^<?[\d.]+%$/.test(columns[2])) continue;
74
- categories.push({
75
- label: columns[0].replace(/\*\*/g, ''),
76
- tokens: categoryTokens,
77
- percent: columns[2]
78
- });
79
- }
80
-
81
- return {
82
- model: model ? model[1].trim() : null,
83
- usedTokens,
84
- maxTokens,
85
- usedPercent: Number(tokens[3]),
86
- remainingTokens: usedTokens == null || maxTokens == null ? null : Math.max(0, maxTokens - usedTokens),
87
- categories
88
- };
89
- }
90
-
91
- module.exports = {
92
- parseClaudeContextOutput,
93
- parseClaudeUsageOutput,
94
- parseTokenCount
95
- };
@@ -1,82 +0,0 @@
1
- const MODEL_ALIASES = new Set(['default', 'env', 'sonnet', 'opus', 'haiku']);
2
- const EFFORT_LEVELS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
3
-
4
- function cleanEnvValue(value) {
5
- const text = String(value || '').trim();
6
- return text || null;
7
- }
8
-
9
- function readClaudeEnv(env = process.env) {
10
- return {
11
- anthropicModel: cleanEnvValue(env.ANTHROPIC_MODEL),
12
- sonnetModel: cleanEnvValue(env.ANTHROPIC_DEFAULT_SONNET_MODEL),
13
- opusModel: cleanEnvValue(env.ANTHROPIC_DEFAULT_OPUS_MODEL),
14
- haikuModel: cleanEnvValue(env.ANTHROPIC_DEFAULT_HAIKU_MODEL),
15
- subagentModel: cleanEnvValue(env.CLAUDE_CODE_SUBAGENT_MODEL),
16
- effort: normalizeEffort(env.CLAUDE_CODE_EFFORT_LEVEL)
17
- };
18
- }
19
-
20
- function normalizeModel(value, env = process.env) {
21
- const model = String(value || '').trim();
22
- if (model) return model;
23
- return readClaudeEnv(env).anthropicModel ? 'env' : 'sonnet';
24
- }
25
-
26
- function normalizeEffort(value, env = process.env) {
27
- const effort = String(value || '').trim().toLowerCase();
28
- if (EFFORT_LEVELS.has(effort)) return effort;
29
- const envEffort = String(env.CLAUDE_CODE_EFFORT_LEVEL || '').trim().toLowerCase();
30
- return EFFORT_LEVELS.has(envEffort) ? envEffort : 'medium';
31
- }
32
-
33
- function resolveClaudeModel(value, env = process.env) {
34
- const selected = normalizeModel(value, env);
35
- const config = readClaudeEnv(env);
36
- if (selected === 'default') return undefined;
37
- if (selected === 'env') return config.anthropicModel || undefined;
38
- if (selected === 'sonnet') return config.sonnetModel || 'sonnet';
39
- if (selected === 'opus') return config.opusModel || 'opus';
40
- if (selected === 'haiku') return config.haikuModel || 'haiku';
41
- return selected;
42
- }
43
-
44
- function modelOption(value, label, resolved, source) {
45
- return {
46
- value,
47
- label,
48
- resolved: resolved || null,
49
- source
50
- };
51
- }
52
-
53
- function getClaudeRuntimeConfig(env = process.env) {
54
- const config = readClaudeEnv(env);
55
- const options = [
56
- modelOption('default', 'Default', null, 'Claude default')
57
- ];
58
- if (config.anthropicModel) {
59
- options.push(modelOption('env', 'Env', config.anthropicModel, 'ANTHROPIC_MODEL'));
60
- }
61
- options.push(
62
- modelOption('sonnet', 'Sonnet', config.sonnetModel || 'sonnet', 'ANTHROPIC_DEFAULT_SONNET_MODEL'),
63
- modelOption('opus', 'Opus', config.opusModel || 'opus', 'ANTHROPIC_DEFAULT_OPUS_MODEL'),
64
- modelOption('haiku', 'Haiku', config.haikuModel || 'haiku', 'ANTHROPIC_DEFAULT_HAIKU_MODEL')
65
- );
66
-
67
- return {
68
- defaultModel: config.anthropicModel ? 'env' : 'sonnet',
69
- defaultEffort: config.effort,
70
- models: options,
71
- env: config
72
- };
73
- }
74
-
75
- module.exports = {
76
- EFFORT_LEVELS,
77
- MODEL_ALIASES,
78
- getClaudeRuntimeConfig,
79
- normalizeEffort,
80
- normalizeModel,
81
- resolveClaudeModel
82
- };