glad-web 1.0.46 → 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.
- package/README.md +4 -192
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/bin/glad.cjs +56 -0
- package/package.json +19 -61
- package/README.zh-CN.md +0 -198
- package/assets/logo.svg +0 -43
- package/bin/cli.js +0 -65
- package/lib/ai-tools/demo/enhanced-demo.js +0 -625
- package/lib/ai-tools/demo/index.js +0 -24
- package/lib/ai-tools/demo/responses.js +0 -88
- package/lib/ai-tools/detector.js +0 -76
- package/lib/ai-tools/registry.js +0 -300
- package/lib/claude/cli-usage.js +0 -95
- package/lib/claude/config.js +0 -82
- package/lib/claude/structured-session.js +0 -884
- package/lib/claude/transcript-repository.js +0 -216
- package/lib/codex/image-store.js +0 -174
- package/lib/codex/structured-session.js +0 -1590
- package/lib/commands/config.js +0 -78
- package/lib/commands/tools.js +0 -128
- package/lib/commands/web.js +0 -605
- package/lib/config/constants.js +0 -17
- package/lib/config/manager.js +0 -108
- package/lib/git/service.js +0 -83
- package/lib/notifications/message-formatter.js +0 -94
- package/lib/notifications/notification-service.js +0 -143
- package/lib/notifications/serverchan-client.js +0 -58
- package/lib/notifications/serverchan-settings-store.js +0 -115
- package/lib/schedule/job-runner.js +0 -162
- package/lib/schedule/job-store.js +0 -167
- package/lib/schedule/key-sequences.js +0 -49
- package/lib/schedule/scheduler-service.js +0 -39
- package/lib/server/routes/notifications.js +0 -52
- package/lib/server/routes/providers.js +0 -114
- package/lib/server/routes/schedules.js +0 -54
- package/lib/server/routes/skillhub.js +0 -104
- package/lib/server/routes/usage.js +0 -23
- package/lib/server/routes/workspace.js +0 -77
- package/lib/session/buffer.js +0 -102
- package/lib/session/file-attachment-store.js +0 -168
- package/lib/session/pty-manager.js +0 -255
- package/lib/session/rendered-history.js +0 -225
- package/lib/session/session-manager.js +0 -1032
- package/lib/session/text-history.js +0 -274
- package/lib/skillhub/client.js +0 -121
- package/lib/skillhub/settings-store.js +0 -168
- package/lib/skillhub/skill-installer.js +0 -320
- package/lib/usage/ccusage-runner.js +0 -128
- package/lib/usage/source-catalog.js +0 -26
- package/lib/usage/usage-service.js +0 -226
- package/lib/utils/logger.js +0 -74
- package/lib/utils/pid.js +0 -67
- package/lib/utils/validation.js +0 -53
- package/lib/web/bootstrap.js +0 -34
- package/lib/web/claude.js +0 -1150
- package/lib/web/codex.js +0 -1045
- package/lib/web/composer.js +0 -493
- package/lib/web/core.js +0 -385
- package/lib/web/git.js +0 -535
- package/lib/web/gitgraph.js +0 -293
- package/lib/web/index.html +0 -547
- package/lib/web/layout.js +0 -69
- package/lib/web/notifications.js +0 -164
- package/lib/web/schedules.js +0 -245
- package/lib/web/session.js +0 -361
- package/lib/web/shell.js +0 -74
- package/lib/web/skillhub.js +0 -197
- package/lib/web/styles.css +0 -932
- package/lib/web/terminal-scroll.js +0 -81
- package/lib/web/theme.js +0 -60
- package/lib/web/timed-inputs.js +0 -216
- package/lib/web/usage.js +0 -323
- package/lib/workspace/service.js +0 -77
- 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
|
-
};
|
package/lib/ai-tools/detector.js
DELETED
|
@@ -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
|
-
};
|
package/lib/ai-tools/registry.js
DELETED
|
@@ -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
|
-
};
|
package/lib/claude/cli-usage.js
DELETED
|
@@ -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
|
-
};
|
package/lib/claude/config.js
DELETED
|
@@ -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
|
-
};
|