phewsh 0.7.0 → 0.9.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.
- package/bin/phewsh.js +13 -4
- package/commands/session.js +460 -0
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -37,6 +37,7 @@ function showBrand() {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const COMMANDS = {
|
|
40
|
+
session: () => require('../commands/session'),
|
|
40
41
|
intent: () => require('../commands/intent'),
|
|
41
42
|
clarify: () => require('../commands/clarify'),
|
|
42
43
|
push: () => require('../commands/push'),
|
|
@@ -59,13 +60,17 @@ function showHelp() {
|
|
|
59
60
|
const pkg = require('../package.json');
|
|
60
61
|
showBrand();
|
|
61
62
|
console.log(` ${g('v' + pkg.version)} · ${g('phewsh.com')}\n`);
|
|
63
|
+
console.log(` ${b('Just type')} ${w('phewsh')} ${b('to start a session.')}`);
|
|
64
|
+
console.log(` ${g('Opens a persistent AI shell with your .intent/ context injected.')}`);
|
|
65
|
+
console.log('');
|
|
62
66
|
console.log(` ${b('Commands')}`);
|
|
67
|
+
console.log(` ${w('(bare)')} Open persistent AI session — just type naturally`);
|
|
63
68
|
console.log(` ${w('clarify')} Turn messy intent into a structured project spec`);
|
|
64
69
|
console.log(` ${w('push')} Push local .intent/ to cloud`);
|
|
65
70
|
console.log(` ${w('pull')} Pull project from cloud to .intent/`);
|
|
66
71
|
console.log(` ${w('link')} Link local .intent/ to a cloud project`);
|
|
67
72
|
console.log(` ${w('intent')} Manage .intent/ artifacts — status, open, evolve`);
|
|
68
|
-
console.log(` ${w('ai')}
|
|
73
|
+
console.log(` ${w('ai')} One-shot AI prompt (reads .intent/)`);
|
|
69
74
|
console.log(` ${w('login')} Set up identity, API key, and cloud sync`);
|
|
70
75
|
console.log(` ${w('sap')} Sustainable AI Protocol — usage and accountability`);
|
|
71
76
|
console.log(` ${w('style')} Build your style identity — ingest, profile, sync`);
|
|
@@ -73,9 +78,9 @@ function showHelp() {
|
|
|
73
78
|
console.log('');
|
|
74
79
|
console.log(` ${b('Quick start')}`);
|
|
75
80
|
console.log(` ${g('phewsh login')} Set up identity + API key`);
|
|
81
|
+
console.log(` ${g('phewsh')} Open AI session (with .intent/ context)`);
|
|
76
82
|
console.log(` ${g('phewsh clarify')} Compile messy intent → structured spec`);
|
|
77
|
-
console.log(` ${g('phewsh
|
|
78
|
-
console.log(` ${g('phewsh ai run "what\'s next?"')} AI with your project context`);
|
|
83
|
+
console.log(` ${g('phewsh ai run "what\'s next?"')} One-shot prompt`);
|
|
79
84
|
console.log('');
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -111,7 +116,11 @@ function exitAfterUpdate(code = 0) {
|
|
|
111
116
|
setTimeout(() => process.exit(code), 2000);
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
if (!command
|
|
119
|
+
if (!command) {
|
|
120
|
+
// Bare `phewsh` — always drop into persistent session
|
|
121
|
+
// Session handles missing API key gracefully with /login and /key commands
|
|
122
|
+
COMMANDS.session();
|
|
123
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
115
124
|
showHelp();
|
|
116
125
|
exitAfterUpdate(0);
|
|
117
126
|
} else if (command === 'version' || command === '--version' || command === '-v') {
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// phewsh session — persistent agent shell
|
|
2
|
+
// Drops you into a REPL where you type naturally.
|
|
3
|
+
// Under the hood: routes to Claude, injects .intent/ context, tracks SAP.
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const readline = require('readline');
|
|
9
|
+
const { trackSap } = require('../lib/supabase');
|
|
10
|
+
|
|
11
|
+
const CONFIG_DIR = path.join(os.homedir(), '.phewsh');
|
|
12
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
13
|
+
const INTENT_DIR = path.join(process.cwd(), '.intent');
|
|
14
|
+
|
|
15
|
+
const b = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
16
|
+
const d = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
17
|
+
const w = (s) => `\x1b[97m${s}\x1b[0m`;
|
|
18
|
+
const g = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
19
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
20
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
21
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
22
|
+
|
|
23
|
+
const MODELS = {
|
|
24
|
+
'claude-sonnet': { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', provider: 'anthropic' },
|
|
25
|
+
'claude-opus': { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', provider: 'anthropic' },
|
|
26
|
+
'claude-haiku': { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', provider: 'anthropic' },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DEFAULT_MODEL = 'claude-sonnet';
|
|
30
|
+
|
|
31
|
+
function loadConfig() {
|
|
32
|
+
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
33
|
+
try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); } catch { return null; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function saveConfig(config) {
|
|
37
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
38
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function loadIntentContext() {
|
|
42
|
+
const files = ['vision.md', 'plan.md', 'next.md'];
|
|
43
|
+
const loaded = [];
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const p = path.join(INTENT_DIR, file);
|
|
46
|
+
if (fs.existsSync(p)) {
|
|
47
|
+
loaded.push({ file, content: fs.readFileSync(p, 'utf-8') });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return loaded;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildSystemPrompt(intentFiles) {
|
|
54
|
+
const base = `You are PHEWSH — a focused execution assistant. You help the user think clearly, build intentionally, and ship without drift. Be concise, direct, and opinionated. Respond in plain text, not markdown, unless the user asks for formatted output.`;
|
|
55
|
+
|
|
56
|
+
if (intentFiles.length === 0) {
|
|
57
|
+
return base + `\n\nNo .intent/ artifacts found in the current directory. The user hasn't set up project context yet — help them think through what they're building if they ask.`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sections = intentFiles.map(({ file, content }) =>
|
|
61
|
+
`## ${file}\n\n${content.trim()}`
|
|
62
|
+
).join('\n\n---\n\n');
|
|
63
|
+
|
|
64
|
+
return `${base}\n\nThe user has structured intent artifacts for this project. Use them as primary context — stay aligned with their vision, plan, and next actions.\n\n${sections}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
68
|
+
const body = { model: modelId, max_tokens: 2048, messages, stream: true };
|
|
69
|
+
if (systemPrompt) body.system = systemPrompt;
|
|
70
|
+
|
|
71
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'x-api-key': apiKey,
|
|
75
|
+
'anthropic-version': '2023-06-01',
|
|
76
|
+
'content-type': 'application/json',
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify(body),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const err = await response.json().catch(() => ({}));
|
|
83
|
+
throw new Error(err.error?.message || `API error ${response.status}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let fullResponse = '';
|
|
87
|
+
let promptTokens = null;
|
|
88
|
+
let completionTokens = null;
|
|
89
|
+
|
|
90
|
+
for await (const chunk of response.body) {
|
|
91
|
+
const text = Buffer.from(chunk).toString('utf-8');
|
|
92
|
+
const lines = text.split('\n').filter(l => l.startsWith('data: '));
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const data = line.slice(6);
|
|
95
|
+
if (data === '[DONE]') continue;
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(data);
|
|
98
|
+
if (parsed.type === 'content_block_delta' && parsed.delta?.text) {
|
|
99
|
+
process.stdout.write(parsed.delta.text);
|
|
100
|
+
fullResponse += parsed.delta.text;
|
|
101
|
+
}
|
|
102
|
+
if (parsed.type === 'message_start' && parsed.message?.usage) {
|
|
103
|
+
promptTokens = parsed.message.usage.input_tokens;
|
|
104
|
+
}
|
|
105
|
+
if (parsed.type === 'message_delta' && parsed.usage) {
|
|
106
|
+
completionTokens = parsed.usage.output_tokens;
|
|
107
|
+
}
|
|
108
|
+
} catch { /* skip */ }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
process.stdout.write('\n');
|
|
113
|
+
|
|
114
|
+
return { content: fullResponse, promptTokens, completionTokens, model: modelId };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function main() {
|
|
118
|
+
let config = loadConfig();
|
|
119
|
+
let intentFiles = loadIntentContext();
|
|
120
|
+
let systemPrompt = buildSystemPrompt(intentFiles);
|
|
121
|
+
const messages = []; // conversation history
|
|
122
|
+
const projectName = path.basename(process.cwd());
|
|
123
|
+
let currentModel = DEFAULT_MODEL;
|
|
124
|
+
let totalPromptTokens = 0;
|
|
125
|
+
let totalCompletionTokens = 0;
|
|
126
|
+
|
|
127
|
+
// Session banner with PHEWSH branding
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log(` ${d('😮💨')} ${d('🤫')}`);
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log(` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`);
|
|
132
|
+
console.log(` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`);
|
|
133
|
+
console.log('');
|
|
134
|
+
|
|
135
|
+
if (!config?.apiKey) {
|
|
136
|
+
console.log(` ${yellow('⚠')} No API key configured.`);
|
|
137
|
+
console.log(` ${g('Run')} /login ${g('to set up identity + API key')}`);
|
|
138
|
+
console.log(` ${g('Or')} /key ${g('to add an API key directly')}`);
|
|
139
|
+
console.log('');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (intentFiles.length > 0) {
|
|
143
|
+
console.log(` ${green('●')} ${cyan(projectName)} ${g('·')} ${intentFiles.map(f => f.file).join(', ')}`);
|
|
144
|
+
} else {
|
|
145
|
+
console.log(` ${green('●')} ${cyan(projectName)} ${g('·')} no .intent/ context`);
|
|
146
|
+
console.log(` ${g(' run /init to create .intent/ artifacts')}`);
|
|
147
|
+
}
|
|
148
|
+
console.log(` ${g(' model:')} ${MODELS[currentModel].name}`);
|
|
149
|
+
if (config?.email) {
|
|
150
|
+
console.log(` ${g(' user:')} ${config.email}`);
|
|
151
|
+
}
|
|
152
|
+
console.log('');
|
|
153
|
+
console.log(` ${g('type naturally · /help for commands · /quit to exit')}`);
|
|
154
|
+
console.log('');
|
|
155
|
+
|
|
156
|
+
const rl = readline.createInterface({
|
|
157
|
+
input: process.stdin,
|
|
158
|
+
output: process.stdout,
|
|
159
|
+
prompt: ` ${green('>')} `,
|
|
160
|
+
historySize: 100,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
rl.prompt();
|
|
164
|
+
|
|
165
|
+
rl.on('line', async (line) => {
|
|
166
|
+
const input = line.trim();
|
|
167
|
+
|
|
168
|
+
if (!input) {
|
|
169
|
+
rl.prompt();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Slash commands
|
|
174
|
+
if (input.startsWith('/')) {
|
|
175
|
+
const parts = input.slice(1).split(/\s+/);
|
|
176
|
+
const cmd = parts[0].toLowerCase();
|
|
177
|
+
const cmdArg = parts.slice(1).join(' ');
|
|
178
|
+
|
|
179
|
+
if (cmd === 'quit' || cmd === 'exit' || cmd === 'q') {
|
|
180
|
+
const turns = messages.length / 2;
|
|
181
|
+
console.log(`\n ${g('Session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}\n`);
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (cmd === 'help' || cmd === 'h') {
|
|
186
|
+
console.log(`
|
|
187
|
+
${b('Session commands')}
|
|
188
|
+
|
|
189
|
+
${w('conversation')}
|
|
190
|
+
${g('/clear')} Clear conversation history
|
|
191
|
+
${g('/run')} ${d('<prompt>')} One-shot prompt (doesn't add to conversation)
|
|
192
|
+
${g('/quit')} End session
|
|
193
|
+
|
|
194
|
+
${w('project')}
|
|
195
|
+
${g('/init')} Create .intent/ artifacts in this directory
|
|
196
|
+
${g('/context')} Show loaded .intent/ files
|
|
197
|
+
${g('/reload')} Reload .intent/ context from disk
|
|
198
|
+
${g('/status')} Show session stats
|
|
199
|
+
|
|
200
|
+
${w('configuration')}
|
|
201
|
+
${g('/login')} Set up identity + cloud sync
|
|
202
|
+
${g('/key')} Set or update your API key
|
|
203
|
+
${g('/model')} ${d('<name>')} Switch model (sonnet, opus, haiku)
|
|
204
|
+
${g('/models')} List available models
|
|
205
|
+
${g('/provider')} Show current provider info
|
|
206
|
+
|
|
207
|
+
${w('debug')}
|
|
208
|
+
${g('/system')} Show current system prompt
|
|
209
|
+
`);
|
|
210
|
+
rl.prompt();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (cmd === 'clear') {
|
|
215
|
+
messages.length = 0;
|
|
216
|
+
console.log(` ${g('conversation cleared')}`);
|
|
217
|
+
rl.prompt();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (cmd === 'context') {
|
|
222
|
+
if (intentFiles.length > 0) {
|
|
223
|
+
console.log(`\n Loaded from ${cyan('.intent/')}:`);
|
|
224
|
+
intentFiles.forEach(f => console.log(` ${green('●')} ${f.file} ${g('(' + f.content.length + ' chars)')}`));
|
|
225
|
+
} else {
|
|
226
|
+
console.log(`\n ${g('No .intent/ context found in')} ${process.cwd()}`);
|
|
227
|
+
console.log(` ${g('Run')} /init ${g('to create one')}`);
|
|
228
|
+
}
|
|
229
|
+
console.log('');
|
|
230
|
+
rl.prompt();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (cmd === 'status') {
|
|
235
|
+
const turns = messages.length / 2;
|
|
236
|
+
config = loadConfig(); // refresh
|
|
237
|
+
console.log(`\n ${b('Session')}`);
|
|
238
|
+
console.log(` Turns ${turns}`);
|
|
239
|
+
console.log(` Tokens ${totalPromptTokens}→${totalCompletionTokens} (in→out)`);
|
|
240
|
+
console.log(` Project ${projectName}`);
|
|
241
|
+
console.log(` Context ${intentFiles.length > 0 ? intentFiles.map(f => f.file).join(', ') : 'none'}`);
|
|
242
|
+
console.log(` Model ${MODELS[currentModel].name}`);
|
|
243
|
+
console.log(` Provider ${MODELS[currentModel].provider}`);
|
|
244
|
+
if (config?.email) console.log(` User ${config.email}`);
|
|
245
|
+
console.log(` API key ${config?.apiKey ? config.apiKey.slice(0, 8) + '...' : yellow('not set')}`);
|
|
246
|
+
console.log('');
|
|
247
|
+
rl.prompt();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (cmd === 'reload') {
|
|
252
|
+
intentFiles = loadIntentContext();
|
|
253
|
+
systemPrompt = buildSystemPrompt(intentFiles);
|
|
254
|
+
console.log(` ${green('●')} Reloaded ${intentFiles.length} artifact${intentFiles.length !== 1 ? 's' : ''}`);
|
|
255
|
+
rl.prompt();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (cmd === 'system') {
|
|
260
|
+
console.log(`\n${g(systemPrompt)}\n`);
|
|
261
|
+
rl.prompt();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (cmd === 'init') {
|
|
266
|
+
if (fs.existsSync(path.join(INTENT_DIR, 'vision.md'))) {
|
|
267
|
+
console.log(`\n ${g('.intent/ already exists in')} ${process.cwd()}`);
|
|
268
|
+
console.log(` ${g('Use /reload to refresh context')}\n`);
|
|
269
|
+
} else {
|
|
270
|
+
try {
|
|
271
|
+
// Delegate to the intent --init command
|
|
272
|
+
const { execSync } = require('child_process');
|
|
273
|
+
execSync('node ' + path.join(__dirname, 'intent.js') + ' --init', { stdio: 'inherit' });
|
|
274
|
+
// Reload context after init
|
|
275
|
+
intentFiles = loadIntentContext();
|
|
276
|
+
systemPrompt = buildSystemPrompt(intentFiles);
|
|
277
|
+
if (intentFiles.length > 0) {
|
|
278
|
+
console.log(` ${green('●')} Context loaded: ${intentFiles.map(f => f.file).join(', ')}`);
|
|
279
|
+
}
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.error(` ${g('Init failed:')} ${err.message}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
console.log('');
|
|
285
|
+
rl.prompt();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (cmd === 'login') {
|
|
290
|
+
try {
|
|
291
|
+
const { execSync } = require('child_process');
|
|
292
|
+
execSync('node ' + path.join(__dirname, 'login.js'), { stdio: 'inherit' });
|
|
293
|
+
config = loadConfig(); // refresh after login
|
|
294
|
+
} catch (err) {
|
|
295
|
+
console.error(` ${g('Login failed:')} ${err.message}`);
|
|
296
|
+
}
|
|
297
|
+
rl.prompt();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (cmd === 'key') {
|
|
302
|
+
const keyRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
303
|
+
keyRl.question(`\n Anthropic API key\n > `, (apiKey) => {
|
|
304
|
+
keyRl.close();
|
|
305
|
+
apiKey = apiKey.trim();
|
|
306
|
+
if (apiKey) {
|
|
307
|
+
config = loadConfig() || {};
|
|
308
|
+
config.apiKey = apiKey;
|
|
309
|
+
saveConfig(config);
|
|
310
|
+
console.log(` ${green('●')} API key saved\n`);
|
|
311
|
+
} else {
|
|
312
|
+
console.log(` ${g('Cancelled')}\n`);
|
|
313
|
+
}
|
|
314
|
+
rl.prompt();
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (cmd === 'models') {
|
|
320
|
+
console.log(`\n ${b('Available models')}\n`);
|
|
321
|
+
for (const [key, model] of Object.entries(MODELS)) {
|
|
322
|
+
const active = key === currentModel ? ` ${green('●')}` : '';
|
|
323
|
+
console.log(` ${w(key.padEnd(16))} ${g(model.name)}${active}`);
|
|
324
|
+
}
|
|
325
|
+
console.log(`\n ${g('Switch with:')} /model <name>\n`);
|
|
326
|
+
rl.prompt();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (cmd === 'model') {
|
|
331
|
+
if (!cmdArg) {
|
|
332
|
+
console.log(` ${g('Current:')} ${MODELS[currentModel].name}`);
|
|
333
|
+
console.log(` ${g('Usage:')} /model <sonnet|opus|haiku>`);
|
|
334
|
+
rl.prompt();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// Fuzzy match model name
|
|
338
|
+
const query = cmdArg.toLowerCase().replace('claude-', '').replace('claude', '');
|
|
339
|
+
const match = Object.keys(MODELS).find(k =>
|
|
340
|
+
k.includes(query) || MODELS[k].name.toLowerCase().includes(query)
|
|
341
|
+
);
|
|
342
|
+
if (match) {
|
|
343
|
+
currentModel = match;
|
|
344
|
+
console.log(` ${green('●')} Switched to ${MODELS[match].name}`);
|
|
345
|
+
} else {
|
|
346
|
+
console.log(` ${g('Unknown model. Available:')} ${Object.keys(MODELS).join(', ')}`);
|
|
347
|
+
}
|
|
348
|
+
rl.prompt();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (cmd === 'provider') {
|
|
353
|
+
const model = MODELS[currentModel];
|
|
354
|
+
console.log(`\n ${b('Provider')}`);
|
|
355
|
+
console.log(` API Anthropic (direct)`);
|
|
356
|
+
console.log(` Model ${model.name}`);
|
|
357
|
+
console.log(` Endpoint api.anthropic.com/v1/messages`);
|
|
358
|
+
console.log(` Key ${config?.apiKey ? config.apiKey.slice(0, 8) + '...' : yellow('not set')}`);
|
|
359
|
+
console.log('');
|
|
360
|
+
rl.prompt();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (cmd === 'run') {
|
|
365
|
+
if (!cmdArg) {
|
|
366
|
+
console.log(` ${g('Usage:')} /run <prompt>`);
|
|
367
|
+
rl.prompt();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (!config?.apiKey) {
|
|
371
|
+
console.log(` ${yellow('⚠')} No API key. Run /key to set one.`);
|
|
372
|
+
rl.prompt();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
// One-shot: don't add to conversation history
|
|
376
|
+
console.log('');
|
|
377
|
+
try {
|
|
378
|
+
const result = await streamChat(
|
|
379
|
+
config.apiKey,
|
|
380
|
+
[{ role: 'user', content: cmdArg }],
|
|
381
|
+
systemPrompt,
|
|
382
|
+
MODELS[currentModel].id
|
|
383
|
+
);
|
|
384
|
+
if (result.promptTokens || result.completionTokens) {
|
|
385
|
+
console.log(g(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens`));
|
|
386
|
+
}
|
|
387
|
+
trackSap({
|
|
388
|
+
userId: config.supabaseUserId,
|
|
389
|
+
source: 'cli',
|
|
390
|
+
model: MODELS[currentModel].id,
|
|
391
|
+
promptTokens: result.promptTokens,
|
|
392
|
+
completionTokens: result.completionTokens,
|
|
393
|
+
accessToken: config.supabaseAccessToken,
|
|
394
|
+
});
|
|
395
|
+
} catch (err) {
|
|
396
|
+
console.error(`\n ${err.message}\n`);
|
|
397
|
+
}
|
|
398
|
+
console.log('');
|
|
399
|
+
rl.prompt();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Unknown slash command
|
|
404
|
+
console.log(` ${g('Unknown command:')} /${cmd} ${g('— type /help for available commands')}`);
|
|
405
|
+
rl.prompt();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Regular input → send to AI
|
|
410
|
+
if (!config?.apiKey) {
|
|
411
|
+
console.log(`\n ${yellow('⚠')} No API key configured. Run /key to set one.\n`);
|
|
412
|
+
rl.prompt();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
messages.push({ role: 'user', content: input });
|
|
417
|
+
console.log('');
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
const result = await streamChat(config.apiKey, messages, systemPrompt, MODELS[currentModel].id);
|
|
421
|
+
messages.push({ role: 'assistant', content: result.content });
|
|
422
|
+
|
|
423
|
+
// Track totals
|
|
424
|
+
if (result.promptTokens) totalPromptTokens += result.promptTokens;
|
|
425
|
+
if (result.completionTokens) totalCompletionTokens += result.completionTokens;
|
|
426
|
+
|
|
427
|
+
// Token count footer
|
|
428
|
+
if (result.promptTokens || result.completionTokens) {
|
|
429
|
+
console.log(g(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens · ${MODELS[currentModel].name}`));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// SAP tracking (fire-and-forget)
|
|
433
|
+
trackSap({
|
|
434
|
+
userId: config.supabaseUserId,
|
|
435
|
+
source: 'cli',
|
|
436
|
+
model: MODELS[currentModel].id,
|
|
437
|
+
promptTokens: result.promptTokens,
|
|
438
|
+
completionTokens: result.completionTokens,
|
|
439
|
+
accessToken: config.supabaseAccessToken,
|
|
440
|
+
});
|
|
441
|
+
} catch (err) {
|
|
442
|
+
console.error(`\n ${err.message}\n`);
|
|
443
|
+
// Remove the failed user message
|
|
444
|
+
messages.pop();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
console.log('');
|
|
448
|
+
rl.prompt();
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
rl.on('close', () => {
|
|
452
|
+
console.log(`\n ${g('Session ended')}\n`);
|
|
453
|
+
process.exit(0);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
main().catch(err => {
|
|
458
|
+
console.error('\n Error:', err.message);
|
|
459
|
+
process.exit(1);
|
|
460
|
+
});
|