phewsh 0.7.0 → 0.8.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 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')} Run context-aware AI prompts (reads .intent/)`);
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 push')} Sync to cloud`);
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,25 @@ function exitAfterUpdate(code = 0) {
111
116
  setTimeout(() => process.exit(code), 2000);
112
117
  }
113
118
 
114
- if (!command || command === 'help' || command === '--help' || command === '-h') {
119
+ if (!command) {
120
+ // Bare `phewsh` — drop into persistent session
121
+ // If no API key, fall back to help
122
+ const fs = require('fs');
123
+ const path = require('path');
124
+ const os = require('os');
125
+ const configPath = path.join(os.homedir(), '.phewsh', 'config.json');
126
+ let hasKey = false;
127
+ try {
128
+ const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
129
+ hasKey = !!cfg.apiKey;
130
+ } catch {}
131
+ if (hasKey) {
132
+ COMMANDS.session();
133
+ } else {
134
+ showHelp();
135
+ exitAfterUpdate(0);
136
+ }
137
+ } else if (command === 'help' || command === '--help' || command === '-h') {
115
138
  showHelp();
116
139
  exitAfterUpdate(0);
117
140
  } else if (command === 'version' || command === '--version' || command === '-v') {
@@ -0,0 +1,262 @@
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_PATH = path.join(os.homedir(), '.phewsh', 'config.json');
12
+ const INTENT_DIR = path.join(process.cwd(), '.intent');
13
+ const HISTORY_PATH = path.join(os.homedir(), '.phewsh', 'session_history.json');
14
+
15
+ const b = (s) => `\x1b[1m${s}\x1b[0m`;
16
+ const d = (s) => `\x1b[2m${s}\x1b[0m`;
17
+ const g = (s) => `\x1b[90m${s}\x1b[0m`;
18
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
19
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
20
+
21
+ function loadConfig() {
22
+ if (!fs.existsSync(CONFIG_PATH)) return null;
23
+ try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); } catch { return null; }
24
+ }
25
+
26
+ function loadIntentContext() {
27
+ const files = ['vision.md', 'plan.md', 'next.md'];
28
+ const loaded = [];
29
+ for (const file of files) {
30
+ const p = path.join(INTENT_DIR, file);
31
+ if (fs.existsSync(p)) {
32
+ loaded.push({ file, content: fs.readFileSync(p, 'utf-8') });
33
+ }
34
+ }
35
+ return loaded;
36
+ }
37
+
38
+ function buildSystemPrompt(intentFiles) {
39
+ 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.`;
40
+
41
+ if (intentFiles.length === 0) {
42
+ 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.`;
43
+ }
44
+
45
+ const sections = intentFiles.map(({ file, content }) =>
46
+ `## ${file}\n\n${content.trim()}`
47
+ ).join('\n\n---\n\n');
48
+
49
+ 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}`;
50
+ }
51
+
52
+ async function streamChat(apiKey, messages, systemPrompt, config) {
53
+ const model = 'claude-sonnet-4-6';
54
+ const body = { model, max_tokens: 2048, messages, stream: true };
55
+ if (systemPrompt) body.system = systemPrompt;
56
+
57
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
58
+ method: 'POST',
59
+ headers: {
60
+ 'x-api-key': apiKey,
61
+ 'anthropic-version': '2023-06-01',
62
+ 'content-type': 'application/json',
63
+ },
64
+ body: JSON.stringify(body),
65
+ });
66
+
67
+ if (!response.ok) {
68
+ const err = await response.json().catch(() => ({}));
69
+ throw new Error(err.error?.message || `API error ${response.status}`);
70
+ }
71
+
72
+ let fullResponse = '';
73
+ let promptTokens = null;
74
+ let completionTokens = null;
75
+
76
+ for await (const chunk of response.body) {
77
+ const text = Buffer.from(chunk).toString('utf-8');
78
+ const lines = text.split('\n').filter(l => l.startsWith('data: '));
79
+ for (const line of lines) {
80
+ const data = line.slice(6);
81
+ if (data === '[DONE]') continue;
82
+ try {
83
+ const parsed = JSON.parse(data);
84
+ if (parsed.type === 'content_block_delta' && parsed.delta?.text) {
85
+ process.stdout.write(parsed.delta.text);
86
+ fullResponse += parsed.delta.text;
87
+ }
88
+ if (parsed.type === 'message_start' && parsed.message?.usage) {
89
+ promptTokens = parsed.message.usage.input_tokens;
90
+ }
91
+ if (parsed.type === 'message_delta' && parsed.usage) {
92
+ completionTokens = parsed.usage.output_tokens;
93
+ }
94
+ } catch { /* skip */ }
95
+ }
96
+ }
97
+
98
+ process.stdout.write('\n');
99
+
100
+ // SAP tracking (fire-and-forget)
101
+ trackSap({
102
+ userId: config.supabaseUserId,
103
+ source: 'cli',
104
+ model,
105
+ promptTokens,
106
+ completionTokens,
107
+ accessToken: config.supabaseAccessToken,
108
+ });
109
+
110
+ return { content: fullResponse, promptTokens, completionTokens };
111
+ }
112
+
113
+ async function main() {
114
+ const config = loadConfig();
115
+
116
+ if (!config?.apiKey) {
117
+ console.log('\n No API key found. Run `phewsh login --set-key` first.');
118
+ console.log(' Or start at: `phewsh login`\n');
119
+ process.exit(1);
120
+ }
121
+
122
+ const intentFiles = loadIntentContext();
123
+ const systemPrompt = buildSystemPrompt(intentFiles);
124
+ const messages = []; // conversation history
125
+ const projectName = path.basename(process.cwd());
126
+
127
+ // Session banner
128
+ console.log('');
129
+ console.log(` ${d('😮‍💨')} ${d('🤫')}`);
130
+ console.log('');
131
+ if (intentFiles.length > 0) {
132
+ console.log(` ${green('●')} Session started ${g('·')} ${cyan(projectName)} ${g('·')} ${intentFiles.map(f => f.file).join(', ')}`);
133
+ } else {
134
+ console.log(` ${green('●')} Session started ${g('·')} ${cyan(projectName)} ${g('·')} no .intent/ context`);
135
+ }
136
+ console.log(` ${g('type naturally · /help for commands · /quit to exit')}`);
137
+ console.log('');
138
+
139
+ const rl = readline.createInterface({
140
+ input: process.stdin,
141
+ output: process.stdout,
142
+ prompt: ` ${green('>')} `,
143
+ historySize: 100,
144
+ });
145
+
146
+ rl.prompt();
147
+
148
+ rl.on('line', async (line) => {
149
+ const input = line.trim();
150
+
151
+ if (!input) {
152
+ rl.prompt();
153
+ return;
154
+ }
155
+
156
+ // Slash commands
157
+ if (input.startsWith('/')) {
158
+ const cmd = input.slice(1).split(' ')[0].toLowerCase();
159
+
160
+ if (cmd === 'quit' || cmd === 'exit' || cmd === 'q') {
161
+ console.log(`\n ${g('Session ended · ' + messages.length / 2 + ' exchanges')}\n`);
162
+ process.exit(0);
163
+ }
164
+
165
+ if (cmd === 'help') {
166
+ console.log(`
167
+ ${b('Session commands')}
168
+
169
+ ${g('/quit')} End session
170
+ ${g('/clear')} Clear conversation history
171
+ ${g('/context')} Show loaded .intent/ files
172
+ ${g('/status')} Show session stats
173
+ ${g('/reload')} Reload .intent/ context
174
+ ${g('/system')} Show system prompt (debug)
175
+ `);
176
+ rl.prompt();
177
+ return;
178
+ }
179
+
180
+ if (cmd === 'clear') {
181
+ messages.length = 0;
182
+ console.log(` ${g('conversation cleared')}`);
183
+ rl.prompt();
184
+ return;
185
+ }
186
+
187
+ if (cmd === 'context') {
188
+ if (intentFiles.length > 0) {
189
+ console.log(`\n Loaded from ${cyan('.intent/')}:`);
190
+ intentFiles.forEach(f => console.log(` ${green('●')} ${f.file} ${g('(' + f.content.length + ' chars)')}`));
191
+ } else {
192
+ console.log(`\n ${g('No .intent/ context found in')} ${process.cwd()}`);
193
+ console.log(` ${g('Run')} phewsh clarify ${g('to create one')}`);
194
+ }
195
+ console.log('');
196
+ rl.prompt();
197
+ return;
198
+ }
199
+
200
+ if (cmd === 'status') {
201
+ const turns = messages.length / 2;
202
+ console.log(`\n ${b('Session')}`);
203
+ console.log(` Turns ${turns}`);
204
+ console.log(` Project ${projectName}`);
205
+ console.log(` Context ${intentFiles.length > 0 ? intentFiles.map(f => f.file).join(', ') : 'none'}`);
206
+ console.log(` Provider anthropic (claude-sonnet-4-6)`);
207
+ console.log('');
208
+ rl.prompt();
209
+ return;
210
+ }
211
+
212
+ if (cmd === 'reload') {
213
+ const reloaded = loadIntentContext();
214
+ intentFiles.length = 0;
215
+ intentFiles.push(...reloaded);
216
+ console.log(` ${green('●')} Reloaded ${reloaded.length} artifact${reloaded.length !== 1 ? 's' : ''}`);
217
+ rl.prompt();
218
+ return;
219
+ }
220
+
221
+ if (cmd === 'system') {
222
+ console.log(`\n${g(systemPrompt)}\n`);
223
+ rl.prompt();
224
+ return;
225
+ }
226
+
227
+ // Unknown slash command — treat as normal input
228
+ }
229
+
230
+ // Regular input → send to AI
231
+ messages.push({ role: 'user', content: input });
232
+
233
+ console.log('');
234
+
235
+ try {
236
+ const result = await streamChat(config.apiKey, messages, systemPrompt, config);
237
+ messages.push({ role: 'assistant', content: result.content });
238
+
239
+ // Token count footer
240
+ if (result.promptTokens || result.completionTokens) {
241
+ console.log(g(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens`));
242
+ }
243
+ } catch (err) {
244
+ console.error(`\n ${err.message}\n`);
245
+ // Remove the failed user message
246
+ messages.pop();
247
+ }
248
+
249
+ console.log('');
250
+ rl.prompt();
251
+ });
252
+
253
+ rl.on('close', () => {
254
+ console.log(`\n ${g('Session ended')}\n`);
255
+ process.exit(0);
256
+ });
257
+ }
258
+
259
+ main().catch(err => {
260
+ console.error('\n Error:', err.message);
261
+ process.exit(1);
262
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"