phewsh 0.11.9 → 0.11.11
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 +51 -36
- package/commands/session.js +156 -76
- package/lib/ui.js +252 -0
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -8,6 +8,8 @@ const b = (s) => `\x1b[1m${s}\x1b[0m`; // bold
|
|
|
8
8
|
const d = (s) => `\x1b[2m${s}\x1b[0m`; // dim
|
|
9
9
|
const w = (s) => `\x1b[97m${s}\x1b[0m`; // bright white
|
|
10
10
|
const g = (s) => `\x1b[90m${s}\x1b[0m`; // dark gray
|
|
11
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
12
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
11
13
|
|
|
12
14
|
function showBrand() {
|
|
13
15
|
const fs = require('fs');
|
|
@@ -15,24 +17,32 @@ function showBrand() {
|
|
|
15
17
|
const os = require('os');
|
|
16
18
|
const hasIntent = fs.existsSync(path.join(process.cwd(), '.intent', 'vision.md'));
|
|
17
19
|
const configPath = path.join(os.homedir(), '.phewsh', 'config.json');
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} catch { /* no config */ }
|
|
26
|
-
}
|
|
20
|
+
let hasKey = false;
|
|
21
|
+
let email = null;
|
|
22
|
+
try {
|
|
23
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
24
|
+
hasKey = !!config?.apiKey;
|
|
25
|
+
email = config?.email;
|
|
26
|
+
} catch { /* no config */ }
|
|
27
27
|
|
|
28
28
|
console.log('');
|
|
29
29
|
console.log(` ${d('😮\u200d💨')} ${d('🤫')}`);
|
|
30
30
|
console.log('');
|
|
31
31
|
console.log(` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`);
|
|
32
32
|
console.log(` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`);
|
|
33
|
-
console.log(` ${g('
|
|
33
|
+
console.log(` ${g('Your project identity for every AI tool.')}`);
|
|
34
34
|
console.log('');
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
// Context-aware hint
|
|
37
|
+
if (!hasKey) {
|
|
38
|
+
console.log(` ${g('Get started:')} ${w('phewsh')} ${g('(guided setup, takes 60 seconds)')}`);
|
|
39
|
+
} else if (hasIntent) {
|
|
40
|
+
console.log(` ${green('●')} .intent/ loaded ${g('·')} ${w('phewsh')} ${g('to chat ·')} ${w('phewsh watch')} ${g('to sync')}`);
|
|
41
|
+
} else if (email) {
|
|
42
|
+
console.log(` ${g('logged in as')} ${email} ${g('·')} ${w('phewsh')} ${g('to start')}`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(` ${g('Ready.')} ${w('phewsh')} ${g('to start a session.')}`);
|
|
45
|
+
}
|
|
36
46
|
console.log('');
|
|
37
47
|
}
|
|
38
48
|
|
|
@@ -64,33 +74,38 @@ function showVersion() {
|
|
|
64
74
|
function showHelp() {
|
|
65
75
|
const pkg = require('../package.json');
|
|
66
76
|
showBrand();
|
|
67
|
-
console.log(` ${g('v' + pkg.version)} · ${g('phewsh.com')}\n`);
|
|
68
|
-
console.log(` ${
|
|
69
|
-
console.log(` ${
|
|
77
|
+
console.log(` ${g('v' + pkg.version)} · ${g('phewsh.com/cli')}\n`);
|
|
78
|
+
console.log(` ${g('─'.repeat(48))}`);
|
|
79
|
+
console.log(` ${b('Just type')} ${w('phewsh')} ${b('to start.')} ${g('Everything else is optional.')}`);
|
|
80
|
+
console.log(` ${g('─'.repeat(48))}`);
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(` ${b(w('start here'))}`);
|
|
83
|
+
console.log(` ${cyan('phewsh')} Open AI session — type naturally, get guided`);
|
|
84
|
+
console.log(` ${cyan('phewsh clarify')} Turn a messy idea into a structured spec`);
|
|
85
|
+
console.log(` ${cyan('phewsh login')} Set up identity + API key`);
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(` ${b(w('project'))}`);
|
|
88
|
+
console.log(` ${cyan('intent')} ${g('Manage .intent/ artifacts — status, open, evolve')}`);
|
|
89
|
+
console.log(` ${cyan('gate')} ${g('Declare constraints (budget, time, skill, urgency)')}`);
|
|
90
|
+
console.log(` ${cyan('context')} ${g('Export portable context for any AI tool')}`);
|
|
91
|
+
console.log(` ${cyan('ai')} ${g('One-shot AI prompt with .intent/ context')}`);
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(` ${b(w('sync'))}`);
|
|
94
|
+
console.log(` ${cyan('push')} ${g('Push .intent/ to cloud')}`);
|
|
95
|
+
console.log(` ${cyan('pull')} ${g('Pull from cloud to .intent/')}`);
|
|
96
|
+
console.log(` ${cyan('watch')} ${g('Live sync — CLAUDE.md + web dashboard auto-update')}`);
|
|
97
|
+
console.log(` ${cyan('serve')} ${g('Execution bridge for the web app')}`);
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(` ${b(w('connect'))}`);
|
|
100
|
+
console.log(` ${cyan('mcp')} ${g('Connect AI agents via MCP protocol')}`);
|
|
101
|
+
console.log(` ${cyan('link')} ${g('Link local .intent/ to cloud project')}`);
|
|
70
102
|
console.log('');
|
|
71
|
-
console.log(` ${b('
|
|
72
|
-
console.log(` ${
|
|
73
|
-
console.log(` ${
|
|
74
|
-
console.log(` ${
|
|
75
|
-
console.log(` ${w('pull')} Pull project from cloud to .intent/`);
|
|
76
|
-
console.log(` ${w('link')} Link local .intent/ to a cloud project`);
|
|
77
|
-
console.log(` ${w('intent')} Manage .intent/ artifacts — status, open, evolve`);
|
|
78
|
-
console.log(` ${w('ai')} One-shot AI prompt (reads .intent/)`);
|
|
79
|
-
console.log(` ${w('gate')} Declare operational constraints (budget, time, skill)`);
|
|
80
|
-
console.log(` ${w('context')} Export portable context for any AI tool`);
|
|
81
|
-
console.log(` ${w('login')} Set up identity, API key, and cloud sync`);
|
|
82
|
-
console.log(` ${w('watch')} Live sync — .intent/ changes push to cloud + CLAUDE.md`);
|
|
83
|
-
console.log(` ${w('serve')} Start live execution bridge for the web app`);
|
|
84
|
-
console.log(` ${w('mcp')} Connect AI agents — setup, sync, status`);
|
|
85
|
-
console.log(` ${w('sap')} Sustainable AI Protocol — usage and accountability`);
|
|
86
|
-
console.log(` ${w('style')} Build your style identity — ingest, profile, sync`);
|
|
87
|
-
console.log(` ${w('mbhd')} MBHD music engine`);
|
|
103
|
+
console.log(` ${b(w('more'))}`);
|
|
104
|
+
console.log(` ${cyan('sap')} ${g('Sustainable AI Protocol — usage tracking')}`);
|
|
105
|
+
console.log(` ${cyan('style')} ${g('Style identity — ingest, profile, sync')}`);
|
|
106
|
+
console.log(` ${cyan('mbhd')} ${g('MBHD music engine')}`);
|
|
88
107
|
console.log('');
|
|
89
|
-
console.log(` ${
|
|
90
|
-
console.log(` ${g('phewsh login')} Set up identity + API key`);
|
|
91
|
-
console.log(` ${g('phewsh')} Open AI session (with .intent/ context)`);
|
|
92
|
-
console.log(` ${g('phewsh clarify')} Compile messy intent → structured spec`);
|
|
93
|
-
console.log(` ${g('phewsh ai run "what\'s next?"')} One-shot prompt`);
|
|
108
|
+
console.log(` ${g('Works standalone · Inside Claude Code · Inside Cursor · With any MCP agent')}`);
|
|
94
109
|
console.log('');
|
|
95
110
|
}
|
|
96
111
|
|
package/commands/session.js
CHANGED
|
@@ -7,6 +7,7 @@ const path = require('path');
|
|
|
7
7
|
const os = require('os');
|
|
8
8
|
const readline = require('readline');
|
|
9
9
|
const { trackSap } = require('../lib/supabase');
|
|
10
|
+
const ui = require('../lib/ui');
|
|
10
11
|
|
|
11
12
|
const CONFIG_DIR = path.join(os.homedir(), '.phewsh');
|
|
12
13
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
@@ -16,13 +17,7 @@ const { select, refreshSession: refreshSess } = require('../lib/supabase');
|
|
|
16
17
|
const { readPPS } = require('../lib/pps');
|
|
17
18
|
const { push, pull, ensureValidToken } = require('./sync');
|
|
18
19
|
|
|
19
|
-
const b
|
|
20
|
-
const d = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
21
|
-
const w = (s) => `\x1b[97m${s}\x1b[0m`;
|
|
22
|
-
const g = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
23
|
-
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
24
|
-
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
25
|
-
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
20
|
+
const { b, d, w, g, green, cyan, yellow } = ui;
|
|
26
21
|
|
|
27
22
|
// Sync awareness: compare local .intent/ timestamps with cloud updated_at
|
|
28
23
|
async function checkSyncStatus(config) {
|
|
@@ -145,6 +140,9 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
145
140
|
const body = { model: modelId, max_tokens: 2048, messages, stream: true };
|
|
146
141
|
if (systemPrompt) body.system = systemPrompt;
|
|
147
142
|
|
|
143
|
+
// Start spinner while waiting for first token
|
|
144
|
+
const spin = ui.spinner('thinking');
|
|
145
|
+
|
|
148
146
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
149
147
|
method: 'POST',
|
|
150
148
|
headers: {
|
|
@@ -156,6 +154,7 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
156
154
|
});
|
|
157
155
|
|
|
158
156
|
if (!response.ok) {
|
|
157
|
+
spin.stop();
|
|
159
158
|
const err = await response.json().catch(() => ({}));
|
|
160
159
|
const msg = err.error?.message || `API error ${response.status}`;
|
|
161
160
|
if (response.status === 401 || msg.includes('invalid')) {
|
|
@@ -167,6 +166,7 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
167
166
|
let fullResponse = '';
|
|
168
167
|
let promptTokens = null;
|
|
169
168
|
let completionTokens = null;
|
|
169
|
+
let firstToken = true;
|
|
170
170
|
|
|
171
171
|
for await (const chunk of response.body) {
|
|
172
172
|
const text = Buffer.from(chunk).toString('utf-8');
|
|
@@ -177,6 +177,10 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
177
177
|
try {
|
|
178
178
|
const parsed = JSON.parse(data);
|
|
179
179
|
if (parsed.type === 'content_block_delta' && parsed.delta?.text) {
|
|
180
|
+
if (firstToken) {
|
|
181
|
+
spin.stop(); // Clear spinner before first output
|
|
182
|
+
firstToken = false;
|
|
183
|
+
}
|
|
180
184
|
process.stdout.write(parsed.delta.text);
|
|
181
185
|
fullResponse += parsed.delta.text;
|
|
182
186
|
}
|
|
@@ -190,6 +194,7 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
196
|
|
|
197
|
+
if (firstToken) spin.stop(); // In case we got no tokens
|
|
193
198
|
process.stdout.write('\n');
|
|
194
199
|
|
|
195
200
|
return { content: fullResponse, promptTokens, completionTokens, model: modelId };
|
|
@@ -205,23 +210,20 @@ async function main() {
|
|
|
205
210
|
let totalPromptTokens = 0;
|
|
206
211
|
let totalCompletionTokens = 0;
|
|
207
212
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
console.log(` ${d('😮💨')} ${d('🤫')}`);
|
|
211
|
-
console.log('');
|
|
212
|
-
console.log(` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`);
|
|
213
|
-
console.log(` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`);
|
|
214
|
-
console.log('');
|
|
213
|
+
// ── Animated brand reveal ──────────────────────────────
|
|
214
|
+
await ui.brandReveal();
|
|
215
215
|
|
|
216
|
+
// ── First-run welcome or status panel ──────────────────
|
|
216
217
|
if (!config?.apiKey) {
|
|
217
218
|
console.log(` ${b(w('Welcome to PHEWSH.'))}`);
|
|
218
219
|
console.log(` ${g('Your AI knows your project. No more re-explaining.')}`);
|
|
219
220
|
console.log('');
|
|
220
|
-
console.log(` ${w('To
|
|
221
|
+
console.log(` ${w('To chat, you need an API key.')} ${g('(not a subscription)')}`);
|
|
222
|
+
console.log(` ${g('ChatGPT Plus / Claude Pro are separate — API keys are pay-as-you-go.')}`);
|
|
221
223
|
console.log('');
|
|
222
224
|
console.log(` ${cyan('1')} ${b('Anthropic')} ${g('(recommended)')}`);
|
|
223
225
|
console.log(` ${g('console.anthropic.com/settings/keys')}`);
|
|
224
|
-
console.log(` ${g('Direct access to Claude. Best quality.')}`);
|
|
226
|
+
console.log(` ${g('Direct access to Claude. Best quality. ~$0.01/message.')}`);
|
|
225
227
|
console.log('');
|
|
226
228
|
console.log(` ${cyan('2')} ${b('OpenRouter')}`);
|
|
227
229
|
console.log(` ${g('openrouter.ai/keys')}`);
|
|
@@ -229,6 +231,7 @@ async function main() {
|
|
|
229
231
|
console.log('');
|
|
230
232
|
console.log(` ${g('Got a key? Type')} /key ${g('to paste it in.')}`);
|
|
231
233
|
console.log(` ${g('Want cloud sync too?')} /login ${g('connects your identity.')}`);
|
|
234
|
+
console.log(` ${g('Curious?')} /tour ${g('to see what PHEWSH can do (no key needed).')}`);
|
|
232
235
|
console.log('');
|
|
233
236
|
} else if (!config.apiKey.startsWith('sk-')) {
|
|
234
237
|
console.log(` ${yellow('!')} Stored API key looks invalid.`);
|
|
@@ -237,6 +240,7 @@ async function main() {
|
|
|
237
240
|
config.apiKey = null; // Don't try to use a bad key
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
// ── Project status ─────────────────────────────────────
|
|
240
244
|
if (intentFiles.length > 0) {
|
|
241
245
|
console.log(` ${green('●')} ${cyan(projectName)} ${g('·')} ${intentFiles.map(f => f.file).join(', ')}`);
|
|
242
246
|
} else {
|
|
@@ -248,6 +252,9 @@ async function main() {
|
|
|
248
252
|
console.log(` ${g(' user:')} ${config.email}`);
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
// ── Interop line ───────────────────────────────────────
|
|
256
|
+
ui.interopLine(config, intentFiles);
|
|
257
|
+
|
|
251
258
|
// Sync status check (non-blocking, 3s timeout)
|
|
252
259
|
if (config?.supabaseUserId && intentFiles.length > 0) {
|
|
253
260
|
const syncResult = await Promise.race([
|
|
@@ -268,17 +275,20 @@ async function main() {
|
|
|
268
275
|
}
|
|
269
276
|
|
|
270
277
|
console.log('');
|
|
278
|
+
ui.divider('─');
|
|
271
279
|
if (!config?.apiKey) {
|
|
272
|
-
console.log(` ${g('type')} /key ${g('to get started · /
|
|
280
|
+
console.log(` ${g('type')} /key ${g('to get started ·')} /tour ${g('to explore ·')} /help ${g('for all commands')}`);
|
|
273
281
|
} else {
|
|
282
|
+
console.log(` ${ui.randomTip()}`);
|
|
274
283
|
console.log(` ${g('type naturally · /help for commands · /quit to exit')}`);
|
|
275
284
|
}
|
|
285
|
+
ui.divider('─');
|
|
276
286
|
console.log('');
|
|
277
287
|
|
|
278
288
|
const rl = readline.createInterface({
|
|
279
289
|
input: process.stdin,
|
|
280
290
|
output: process.stdout,
|
|
281
|
-
prompt: ` ${green('>')} `,
|
|
291
|
+
prompt: ` ${cyan('phewsh')} ${green('>')} `,
|
|
282
292
|
historySize: 100,
|
|
283
293
|
});
|
|
284
294
|
|
|
@@ -300,47 +310,77 @@ async function main() {
|
|
|
300
310
|
|
|
301
311
|
if (cmd === 'quit' || cmd === 'exit' || cmd === 'q') {
|
|
302
312
|
const turns = messages.length / 2;
|
|
303
|
-
console.log(
|
|
313
|
+
console.log('');
|
|
314
|
+
ui.divider('─');
|
|
315
|
+
console.log(` ${g('session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}`);
|
|
316
|
+
ui.divider('─');
|
|
317
|
+
console.log('');
|
|
304
318
|
process.exit(0);
|
|
305
319
|
}
|
|
306
320
|
|
|
307
321
|
if (cmd === 'help' || cmd === 'h') {
|
|
308
322
|
console.log(`
|
|
309
|
-
${b('Session commands')}
|
|
323
|
+
${b(w('Session commands'))}
|
|
310
324
|
|
|
311
325
|
${w('conversation')}
|
|
312
|
-
${
|
|
313
|
-
${
|
|
314
|
-
${
|
|
326
|
+
${cyan('/clear')} Clear conversation history
|
|
327
|
+
${cyan('/run')} ${d('<prompt>')} One-shot prompt (doesn't add to conversation)
|
|
328
|
+
${cyan('/quit')} End session
|
|
315
329
|
|
|
316
330
|
${w('project')}
|
|
317
|
-
${
|
|
318
|
-
${
|
|
319
|
-
${
|
|
320
|
-
${
|
|
321
|
-
${
|
|
322
|
-
${
|
|
323
|
-
${
|
|
331
|
+
${cyan('/init')} Create .intent/ artifacts in this directory
|
|
332
|
+
${cyan('/clarify')} AI-assisted artifact generation
|
|
333
|
+
${cyan('/gate')} Declare operational constraints (budget, time, skill)
|
|
334
|
+
${cyan('/export')} Export portable context for other AI tools
|
|
335
|
+
${cyan('/context')} Show loaded .intent/ files
|
|
336
|
+
${cyan('/reload')} Reload .intent/ context from disk
|
|
337
|
+
${cyan('/status')} Show session stats
|
|
324
338
|
|
|
325
339
|
${w('sync')}
|
|
326
|
-
${
|
|
327
|
-
${
|
|
328
|
-
${
|
|
340
|
+
${cyan('/push')} Push .intent/ to cloud
|
|
341
|
+
${cyan('/pull')} Pull .intent/ from cloud (reloads context)
|
|
342
|
+
${cyan('/sync')} Check sync status
|
|
329
343
|
|
|
330
344
|
${w('configuration')}
|
|
331
|
-
${
|
|
332
|
-
${
|
|
333
|
-
${
|
|
334
|
-
${
|
|
335
|
-
${
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
345
|
+
${cyan('/login')} Set up identity + cloud sync
|
|
346
|
+
${cyan('/key')} Set or update your API key
|
|
347
|
+
${cyan('/model')} ${d('<name>')} Switch model (sonnet, opus, haiku)
|
|
348
|
+
${cyan('/models')} List available models
|
|
349
|
+
${cyan('/provider')} Show current provider info
|
|
350
|
+
${cyan('/update')} Update phewsh to the latest version
|
|
351
|
+
|
|
352
|
+
${w('explore')}
|
|
353
|
+
${cyan('/tour')} Guided walkthrough of everything PHEWSH can do
|
|
354
|
+
${cyan('/system')} Show current system prompt
|
|
339
355
|
`);
|
|
340
356
|
rl.prompt();
|
|
341
357
|
return;
|
|
342
358
|
}
|
|
343
359
|
|
|
360
|
+
// ── /tour — guided walkthrough ─────────────────────
|
|
361
|
+
if (cmd === 'tour') {
|
|
362
|
+
const pages = ui.TOUR_PAGES;
|
|
363
|
+
let pageIdx = cmdArg ? parseInt(cmdArg) - 1 : 0;
|
|
364
|
+
if (isNaN(pageIdx) || pageIdx < 0) pageIdx = 0;
|
|
365
|
+
if (pageIdx >= pages.length) pageIdx = pages.length - 1;
|
|
366
|
+
|
|
367
|
+
const page = pages[pageIdx];
|
|
368
|
+
console.log('');
|
|
369
|
+
ui.divider('─');
|
|
370
|
+
console.log(` ${b(w(page.title))} ${g(`(${pageIdx + 1}/${pages.length})`)}`);
|
|
371
|
+
ui.divider('─');
|
|
372
|
+
page.body.forEach(line => console.log(line));
|
|
373
|
+
console.log('');
|
|
374
|
+
if (pageIdx < pages.length - 1) {
|
|
375
|
+
console.log(` ${g('next:')} /tour ${pageIdx + 2} ${g('·')} ${g('or /tour 1-' + pages.length + ' to jump')}`);
|
|
376
|
+
} else {
|
|
377
|
+
console.log(` ${green('●')} ${g('End of tour. You\'re ready to go.')}`);
|
|
378
|
+
}
|
|
379
|
+
console.log('');
|
|
380
|
+
rl.prompt();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
344
384
|
if (cmd === 'clear') {
|
|
345
385
|
messages.length = 0;
|
|
346
386
|
console.log(` ${g('conversation cleared')}`);
|
|
@@ -350,8 +390,11 @@ async function main() {
|
|
|
350
390
|
|
|
351
391
|
if (cmd === 'context') {
|
|
352
392
|
if (intentFiles.length > 0) {
|
|
353
|
-
console.log(
|
|
354
|
-
|
|
393
|
+
console.log('');
|
|
394
|
+
console.log(` ${b('Loaded from')} ${cyan('.intent/')}`);
|
|
395
|
+
ui.divider('─');
|
|
396
|
+
intentFiles.forEach(f => console.log(` ${green('●')} ${w(f.file)} ${g('(' + f.content.length + ' chars)')}`));
|
|
397
|
+
ui.divider('─');
|
|
355
398
|
} else {
|
|
356
399
|
console.log(`\n ${g('No .intent/ context found in')} ${process.cwd()}`);
|
|
357
400
|
console.log(` ${g('Run')} /init ${g('to create one')}`);
|
|
@@ -364,16 +407,16 @@ async function main() {
|
|
|
364
407
|
if (cmd === 'status') {
|
|
365
408
|
const turns = messages.length / 2;
|
|
366
409
|
config = loadConfig(); // refresh
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
410
|
+
ui.statusPanel('Session', [
|
|
411
|
+
['Turns', String(turns)],
|
|
412
|
+
['Tokens', `${totalPromptTokens} in → ${totalCompletionTokens} out`],
|
|
413
|
+
['Project', projectName, 'cyan'],
|
|
414
|
+
['Context', intentFiles.length > 0 ? intentFiles.map(f => f.file).join(', ') : 'none', intentFiles.length > 0 ? 'green' : 'yellow'],
|
|
415
|
+
['Model', MODELS[currentModel].name],
|
|
416
|
+
['Provider', MODELS[currentModel].provider],
|
|
417
|
+
['User', config?.email || g('not logged in')],
|
|
418
|
+
['API key', config?.apiKey ? config.apiKey.slice(0, 8) + '...' : 'not set', config?.apiKey ? 'green' : 'yellow'],
|
|
419
|
+
]);
|
|
377
420
|
rl.prompt();
|
|
378
421
|
return;
|
|
379
422
|
}
|
|
@@ -418,7 +461,7 @@ async function main() {
|
|
|
418
461
|
|
|
419
462
|
if (cmd === 'clarify') {
|
|
420
463
|
if (!config?.apiKey) {
|
|
421
|
-
console.log(`\n ${yellow('
|
|
464
|
+
console.log(`\n ${yellow('!')} No API key. Run /key to set one.\n`);
|
|
422
465
|
rl.prompt();
|
|
423
466
|
return;
|
|
424
467
|
}
|
|
@@ -462,7 +505,7 @@ async function main() {
|
|
|
462
505
|
if (content) {
|
|
463
506
|
const outPath = path.join(process.cwd(), '.phewsh.context');
|
|
464
507
|
fs.writeFileSync(outPath, content);
|
|
465
|
-
console.log(`\n ${green('
|
|
508
|
+
console.log(`\n ${green('●')} Written to ${outPath}\n`);
|
|
466
509
|
} else {
|
|
467
510
|
console.log(`\n ${g('No artifacts to export')}\n`);
|
|
468
511
|
}
|
|
@@ -475,16 +518,16 @@ async function main() {
|
|
|
475
518
|
|
|
476
519
|
if (cmd === 'push') {
|
|
477
520
|
if (!config?.supabaseUserId) {
|
|
478
|
-
console.log(`\n ${yellow('
|
|
521
|
+
console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
|
|
479
522
|
rl.prompt();
|
|
480
523
|
return;
|
|
481
524
|
}
|
|
482
525
|
try {
|
|
483
526
|
const token = await ensureValidToken(config);
|
|
484
|
-
if (!token) { console.log(`\n ${yellow('
|
|
527
|
+
if (!token) { console.log(`\n ${yellow('!')} Session expired. Run /login.\n`); rl.prompt(); return; }
|
|
485
528
|
await push(config, token);
|
|
486
529
|
} catch (err) {
|
|
487
|
-
console.error(` ${yellow('
|
|
530
|
+
console.error(` ${yellow('!')} Push failed: ${err.message}\n`);
|
|
488
531
|
}
|
|
489
532
|
rl.prompt();
|
|
490
533
|
return;
|
|
@@ -492,13 +535,13 @@ async function main() {
|
|
|
492
535
|
|
|
493
536
|
if (cmd === 'pull') {
|
|
494
537
|
if (!config?.supabaseUserId) {
|
|
495
|
-
console.log(`\n ${yellow('
|
|
538
|
+
console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
|
|
496
539
|
rl.prompt();
|
|
497
540
|
return;
|
|
498
541
|
}
|
|
499
542
|
try {
|
|
500
543
|
const token = await ensureValidToken(config);
|
|
501
|
-
if (!token) { console.log(`\n ${yellow('
|
|
544
|
+
if (!token) { console.log(`\n ${yellow('!')} Session expired. Run /login.\n`); rl.prompt(); return; }
|
|
502
545
|
await pull(config, token);
|
|
503
546
|
// Reload context after pull
|
|
504
547
|
intentFiles = loadIntentContext();
|
|
@@ -507,7 +550,7 @@ async function main() {
|
|
|
507
550
|
console.log(` ${green('●')} Context reloaded: ${intentFiles.map(f => f.file).join(', ')}`);
|
|
508
551
|
}
|
|
509
552
|
} catch (err) {
|
|
510
|
-
console.error(` ${yellow('
|
|
553
|
+
console.error(` ${yellow('!')} Pull failed: ${err.message}\n`);
|
|
511
554
|
}
|
|
512
555
|
console.log('');
|
|
513
556
|
rl.prompt();
|
|
@@ -517,22 +560,24 @@ async function main() {
|
|
|
517
560
|
if (cmd === 'sync') {
|
|
518
561
|
// Show sync status
|
|
519
562
|
if (!config?.supabaseUserId) {
|
|
520
|
-
console.log(`\n ${yellow('
|
|
563
|
+
console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
|
|
521
564
|
rl.prompt();
|
|
522
565
|
return;
|
|
523
566
|
}
|
|
567
|
+
const syncSpin = ui.spinner('checking sync');
|
|
524
568
|
const syncResult = await checkSyncStatus(config);
|
|
525
569
|
if (!syncResult) {
|
|
526
|
-
|
|
570
|
+
syncSpin.stop(`${g('Could not check sync status')}`);
|
|
527
571
|
} else if (syncResult.status === 'cloud-newer') {
|
|
528
|
-
|
|
572
|
+
syncSpin.stop(`${yellow('↓')} Cloud is newer (${syncResult.ago}) — run /pull`);
|
|
529
573
|
} else if (syncResult.status === 'local-newer') {
|
|
530
|
-
|
|
574
|
+
syncSpin.stop(`${yellow('↑')} Local changes not pushed (${syncResult.ago}) — run /push`);
|
|
531
575
|
} else if (syncResult.status === 'synced') {
|
|
532
|
-
|
|
576
|
+
syncSpin.stop(`${green('↕')} In sync`);
|
|
533
577
|
} else if (syncResult.status === 'local-only') {
|
|
534
|
-
|
|
578
|
+
syncSpin.stop(`${g('↕ Not linked to cloud — run /push to sync')}`);
|
|
535
579
|
}
|
|
580
|
+
console.log('');
|
|
536
581
|
rl.prompt();
|
|
537
582
|
return;
|
|
538
583
|
}
|
|
@@ -573,7 +618,9 @@ async function main() {
|
|
|
573
618
|
return;
|
|
574
619
|
}
|
|
575
620
|
console.log('');
|
|
576
|
-
|
|
621
|
+
ui.divider('─');
|
|
622
|
+
console.log(` ${b(w('Where to get an API key'))}`);
|
|
623
|
+
ui.divider('─');
|
|
577
624
|
console.log('');
|
|
578
625
|
console.log(` ${cyan('Anthropic')} ${g('(recommended)')}`);
|
|
579
626
|
console.log(` ${g('1.')} Go to ${w('console.anthropic.com/settings/keys')}`);
|
|
@@ -583,6 +630,9 @@ async function main() {
|
|
|
583
630
|
console.log(` ${g('1.')} Go to ${w('openrouter.ai/keys')}`);
|
|
584
631
|
console.log(` ${g('2.')} Create key → copy it (starts with sk-or-)`)
|
|
585
632
|
console.log('');
|
|
633
|
+
console.log(` ${g('Note: API keys are separate from ChatGPT Plus / Claude Pro subscriptions.')}`);
|
|
634
|
+
console.log(` ${g('Both providers offer free credits to get started.')}`);
|
|
635
|
+
console.log('');
|
|
586
636
|
const keyRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
587
637
|
keyRl.question(` Paste your API key\n > `, (apiKey) => {
|
|
588
638
|
keyRl.close();
|
|
@@ -603,7 +653,10 @@ async function main() {
|
|
|
603
653
|
}
|
|
604
654
|
|
|
605
655
|
if (cmd === 'models') {
|
|
606
|
-
console.log(
|
|
656
|
+
console.log('');
|
|
657
|
+
ui.divider('─');
|
|
658
|
+
console.log(` ${b(w('Available models'))}`);
|
|
659
|
+
ui.divider('─');
|
|
607
660
|
for (const [key, model] of Object.entries(MODELS)) {
|
|
608
661
|
const active = key === currentModel ? ` ${green('●')}` : '';
|
|
609
662
|
console.log(` ${w(key.padEnd(16))} ${g(model.name)}${active}`);
|
|
@@ -637,12 +690,38 @@ async function main() {
|
|
|
637
690
|
|
|
638
691
|
if (cmd === 'provider') {
|
|
639
692
|
const model = MODELS[currentModel];
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
693
|
+
ui.statusPanel('Provider', [
|
|
694
|
+
['API', 'Anthropic (direct)'],
|
|
695
|
+
['Model', model.name, 'cyan'],
|
|
696
|
+
['Endpoint', 'api.anthropic.com/v1/messages'],
|
|
697
|
+
['Key', config?.apiKey ? config.apiKey.slice(0, 8) + '...' : 'not set', config?.apiKey ? 'green' : 'yellow'],
|
|
698
|
+
]);
|
|
699
|
+
rl.prompt();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (cmd === 'update' || cmd === 'upgrade') {
|
|
704
|
+
const updateSpin = ui.spinner('checking for updates');
|
|
705
|
+
try {
|
|
706
|
+
const pkg = require('../../package.json');
|
|
707
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(5000) });
|
|
708
|
+
const data = await res.json();
|
|
709
|
+
if (!data.version || data.version === pkg.version) {
|
|
710
|
+
updateSpin.stop(`${green('●')} Already on the latest version (${pkg.version})`);
|
|
711
|
+
console.log('');
|
|
712
|
+
rl.prompt();
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
updateSpin.stop(`${cyan(pkg.version)} → ${cyan(data.version)}`);
|
|
716
|
+
console.log(` ${g('Installing...')}\n`);
|
|
717
|
+
const { execSync } = require('child_process');
|
|
718
|
+
execSync(`npm install -g ${pkg.name}@latest`, { stdio: 'inherit' });
|
|
719
|
+
console.log(`\n ${green('●')} Updated to ${data.version}`);
|
|
720
|
+
console.log(` ${g('Restart phewsh to use the new version.')}\n`);
|
|
721
|
+
} catch (err) {
|
|
722
|
+
updateSpin.stop(`${yellow('!')} Update failed: ${err.message}`);
|
|
723
|
+
console.log(` ${g('You can update manually:')} npm install -g phewsh\n`);
|
|
724
|
+
}
|
|
646
725
|
rl.prompt();
|
|
647
726
|
return;
|
|
648
727
|
}
|
|
@@ -654,7 +733,7 @@ async function main() {
|
|
|
654
733
|
return;
|
|
655
734
|
}
|
|
656
735
|
if (!config?.apiKey) {
|
|
657
|
-
console.log(` ${yellow('
|
|
736
|
+
console.log(` ${yellow('!')} No API key. Run /key to set one.`);
|
|
658
737
|
rl.prompt();
|
|
659
738
|
return;
|
|
660
739
|
}
|
|
@@ -700,6 +779,7 @@ async function main() {
|
|
|
700
779
|
console.log('');
|
|
701
780
|
console.log(` ${g('Get a key:')} ${cyan('console.anthropic.com/settings/keys')}`);
|
|
702
781
|
console.log(` ${g('Or try:')} ${cyan('openrouter.ai/keys')}`);
|
|
782
|
+
console.log(` ${g('Explore:')} ${cyan('/tour')} ${g('to see what PHEWSH does (no key needed)')}`);
|
|
703
783
|
console.log('');
|
|
704
784
|
rl.prompt();
|
|
705
785
|
return;
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// phewsh ui — zero-dependency terminal animations and visual helpers
|
|
2
|
+
// Pure ANSI escape sequences. No chalk, no ora, no bloat.
|
|
3
|
+
|
|
4
|
+
const b = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
5
|
+
const d = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
6
|
+
const w = (s) => `\x1b[97m${s}\x1b[0m`;
|
|
7
|
+
const g = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
8
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
9
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
10
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
11
|
+
const magenta = (s) => `\x1b[35m${s}\x1b[0m`;
|
|
12
|
+
const blue = (s) => `\x1b[34m${s}\x1b[0m`;
|
|
13
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
14
|
+
|
|
15
|
+
// ANSI cursor control
|
|
16
|
+
const hide = '\x1b[?25l';
|
|
17
|
+
const show = '\x1b[?25h';
|
|
18
|
+
const up = (n = 1) => `\x1b[${n}A`;
|
|
19
|
+
const clearLine = '\x1b[2K\r';
|
|
20
|
+
|
|
21
|
+
// ── Spinner ──────────────────────────────────────────────
|
|
22
|
+
// Returns { stop } — call stop() when work is done.
|
|
23
|
+
const SPINNER_FRAMES = [' ·', ' · ·', ' · · ·', ' · · · ·', '· · · · ·', ' · · · ·', ' · · ·', ' · ·'];
|
|
24
|
+
const BRAILLE_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
25
|
+
const BREATH_FRAMES = ['░', '▒', '▓', '█', '▓', '▒', '░', ' '];
|
|
26
|
+
const PULSE_FRAMES = ['·', '•', '●', '•'];
|
|
27
|
+
|
|
28
|
+
function spinner(text = 'thinking', style = 'braille') {
|
|
29
|
+
const frames = style === 'dots' ? SPINNER_FRAMES
|
|
30
|
+
: style === 'breath' ? BREATH_FRAMES
|
|
31
|
+
: style === 'pulse' ? PULSE_FRAMES
|
|
32
|
+
: BRAILLE_FRAMES;
|
|
33
|
+
let i = 0;
|
|
34
|
+
let stopped = false;
|
|
35
|
+
process.stdout.write(hide);
|
|
36
|
+
const interval = setInterval(() => {
|
|
37
|
+
if (stopped) return;
|
|
38
|
+
const frame = frames[i % frames.length];
|
|
39
|
+
process.stdout.write(`${clearLine} ${cyan(frame)} ${g(text)}`);
|
|
40
|
+
i++;
|
|
41
|
+
}, style === 'breath' ? 120 : 80);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
update(newText) { text = newText; },
|
|
45
|
+
stop(finalText) {
|
|
46
|
+
stopped = true;
|
|
47
|
+
clearInterval(interval);
|
|
48
|
+
process.stdout.write(`${clearLine}${show}`);
|
|
49
|
+
if (finalText) process.stdout.write(` ${finalText}\n`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Animated brand reveal ────────────────────────────────
|
|
55
|
+
// Draws the PHEWSH logo line by line with a cascading effect.
|
|
56
|
+
function brandReveal(fast = false) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const lines = [
|
|
59
|
+
'',
|
|
60
|
+
` ${d('😮\u200d💨')} ${d('🤫')}`,
|
|
61
|
+
'',
|
|
62
|
+
` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`,
|
|
63
|
+
` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`,
|
|
64
|
+
'',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
if (fast) {
|
|
68
|
+
lines.forEach(l => console.log(l));
|
|
69
|
+
resolve();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let idx = 0;
|
|
74
|
+
const interval = setInterval(() => {
|
|
75
|
+
if (idx >= lines.length) {
|
|
76
|
+
clearInterval(interval);
|
|
77
|
+
resolve();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log(lines[idx]);
|
|
81
|
+
idx++;
|
|
82
|
+
}, 60);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Status panel ─────────────────────────────────────────
|
|
87
|
+
// Draws a bordered status box with labeled rows.
|
|
88
|
+
function statusPanel(title, rows) {
|
|
89
|
+
const maxLabel = Math.max(...rows.map(r => r[0].length));
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(` ${b(w(title))}`);
|
|
92
|
+
console.log(` ${g('─'.repeat(48))}`);
|
|
93
|
+
for (const [label, value, color] of rows) {
|
|
94
|
+
const colorFn = color === 'green' ? green
|
|
95
|
+
: color === 'yellow' ? yellow
|
|
96
|
+
: color === 'cyan' ? cyan
|
|
97
|
+
: color === 'red' ? red
|
|
98
|
+
: (s) => s;
|
|
99
|
+
console.log(` ${g(label.padEnd(maxLabel + 2))} ${colorFn(value)}`);
|
|
100
|
+
}
|
|
101
|
+
console.log(` ${g('─'.repeat(48))}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Interop badge line ───────────────────────────────────
|
|
106
|
+
// Shows where phewsh is connected / can connect.
|
|
107
|
+
function interopLine(config, intentFiles) {
|
|
108
|
+
const parts = [];
|
|
109
|
+
if (intentFiles.length > 0) parts.push(green('●') + ' .intent/');
|
|
110
|
+
if (config?.apiKey) parts.push(green('●') + ' AI');
|
|
111
|
+
if (config?.supabaseUserId) parts.push(green('●') + ' cloud');
|
|
112
|
+
|
|
113
|
+
// Always show what's available
|
|
114
|
+
const available = [];
|
|
115
|
+
available.push(g('Claude Code'));
|
|
116
|
+
available.push(g('Cursor'));
|
|
117
|
+
available.push(g('ChatGPT'));
|
|
118
|
+
available.push(g('MCP'));
|
|
119
|
+
|
|
120
|
+
if (parts.length > 0) {
|
|
121
|
+
console.log(` ${g('connected')} ${parts.join(g(' · '))}`);
|
|
122
|
+
}
|
|
123
|
+
console.log(` ${g('works with')} ${available.join(g(' · '))}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Divider ──────────────────────────────────────────────
|
|
127
|
+
function divider(char = '·', width = 48) {
|
|
128
|
+
console.log(` ${g(char.repeat(width))}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Typewriter ───────────────────────────────────────────
|
|
132
|
+
// Writes text character by character. Returns a promise.
|
|
133
|
+
function typewrite(text, speed = 20) {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
let i = 0;
|
|
136
|
+
const interval = setInterval(() => {
|
|
137
|
+
if (i >= text.length) {
|
|
138
|
+
clearInterval(interval);
|
|
139
|
+
process.stdout.write('\n');
|
|
140
|
+
resolve();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
process.stdout.write(text[i]);
|
|
144
|
+
i++;
|
|
145
|
+
}, speed);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Welcome tips (rotates each session) ──────────────────
|
|
150
|
+
const TIPS = [
|
|
151
|
+
`${g('tip:')} Type ${w('/clarify')} to turn a messy idea into a structured spec`,
|
|
152
|
+
`${g('tip:')} ${w('/gate')} lets you set budget, time, and skill constraints`,
|
|
153
|
+
`${g('tip:')} Run ${w('phewsh watch')} in another tab for live sync to CLAUDE.md`,
|
|
154
|
+
`${g('tip:')} ${w('/export')} creates a portable context file for any AI tool`,
|
|
155
|
+
`${g('tip:')} ${w('/model opus')} switches to Claude Opus for complex reasoning`,
|
|
156
|
+
`${g('tip:')} Your ${w('.intent/')} files are plain markdown — edit them anytime`,
|
|
157
|
+
`${g('tip:')} ${w('phewsh context --copy')} puts your project context on the clipboard`,
|
|
158
|
+
`${g('tip:')} ${w('/tour')} walks you through everything PHEWSH can do`,
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
function randomTip() {
|
|
162
|
+
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Tour content ─────────────────────────────────────────
|
|
166
|
+
const TOUR_PAGES = [
|
|
167
|
+
{
|
|
168
|
+
title: 'What is PHEWSH?',
|
|
169
|
+
body: [
|
|
170
|
+
` PHEWSH gives your project a ${b('portable identity')}.`,
|
|
171
|
+
` Define what you're building once — every AI tool reads it.`,
|
|
172
|
+
'',
|
|
173
|
+
` It works ${w('standalone')} as its own AI shell,`,
|
|
174
|
+
` and ${w('inside')} Claude Code, Cursor, ChatGPT, and any MCP agent.`,
|
|
175
|
+
'',
|
|
176
|
+
` ${g('Your project context lives in')} ${cyan('.intent/')} ${g('— plain markdown files.')}`,
|
|
177
|
+
` ${g('You own them. They travel with your code.')}`,
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
title: 'The .intent/ directory',
|
|
182
|
+
body: [
|
|
183
|
+
` ${cyan('.intent/')}`,
|
|
184
|
+
` ${green('vision.md')} ${g('What this project is and why it exists')}`,
|
|
185
|
+
` ${green('plan.md')} ${g('Strategy, phases, milestones')}`,
|
|
186
|
+
` ${green('next.md')} ${g('Current tasks and what to do right now')}`,
|
|
187
|
+
` ${yellow('gate.json')} ${g('Your constraints (budget, time, skill)')}`,
|
|
188
|
+
'',
|
|
189
|
+
` ${g('Create these with')} /init ${g('or')} /clarify`,
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
title: 'Standalone mode',
|
|
194
|
+
body: [
|
|
195
|
+
` When you run ${w('phewsh')} on its own, you get an AI shell`,
|
|
196
|
+
` that knows your project inside and out.`,
|
|
197
|
+
'',
|
|
198
|
+
` Just type naturally:`,
|
|
199
|
+
` ${cyan('>')} what should I focus on today?`,
|
|
200
|
+
` ${cyan('>')} is my plan realistic given my budget?`,
|
|
201
|
+
` ${cyan('>')} break this feature into tasks`,
|
|
202
|
+
'',
|
|
203
|
+
` ${g('Every message includes your vision, plan, and constraints.')}`,
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
title: 'Inside other tools',
|
|
208
|
+
body: [
|
|
209
|
+
` ${b('Claude Code')} ${g('phewsh watch → auto-updates CLAUDE.md')}`,
|
|
210
|
+
` ${b('Cursor')} ${g('phewsh context --file → .phewsh.context')}`,
|
|
211
|
+
` ${b('ChatGPT')} ${g('phewsh context --copy → paste into Custom Instructions')}`,
|
|
212
|
+
` ${b('MCP agents')} ${g('phewsh mcp setup → agents pull tasks automatically')}`,
|
|
213
|
+
'',
|
|
214
|
+
` ${g('Same project identity, every tool. No re-explaining.')}`,
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
title: 'The Decision Gate',
|
|
219
|
+
body: [
|
|
220
|
+
` Before you build, decide ${b('whether')} to build.`,
|
|
221
|
+
` The gate captures what you can actually spend:`,
|
|
222
|
+
'',
|
|
223
|
+
` ${g('Budget')} ${w('$50')} ${g('Skill')} ${w('expert')}`,
|
|
224
|
+
` ${g('Time')} ${w('15 hrs/week')} ${g('Urgency')} ${w('high')}`,
|
|
225
|
+
'',
|
|
226
|
+
` ${g('These constraints shape every AI response.')}`,
|
|
227
|
+
` ${g('Run')} /gate activate ${g('to set yours.')}`,
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
title: 'You\'re ready',
|
|
232
|
+
body: [
|
|
233
|
+
` ${green('●')} Type naturally to chat with your project context`,
|
|
234
|
+
` ${green('●')} ${w('/init')} or ${w('/clarify')} to create .intent/ artifacts`,
|
|
235
|
+
` ${green('●')} ${w('/gate')} to set your constraints`,
|
|
236
|
+
` ${green('●')} ${w('/help')} for all commands`,
|
|
237
|
+
'',
|
|
238
|
+
` ${g('PHEWSH is your project\'s home base.')}`,
|
|
239
|
+
` ${g('The AI that knows you doesn\'t need to be re-taught.')}`,
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
// Colors
|
|
246
|
+
b, d, w, g, green, cyan, yellow, magenta, blue, red,
|
|
247
|
+
// Components
|
|
248
|
+
spinner, brandReveal, statusPanel, interopLine, divider, typewrite,
|
|
249
|
+
randomTip, TOUR_PAGES,
|
|
250
|
+
// ANSI helpers
|
|
251
|
+
hide, show, up, clearLine,
|
|
252
|
+
};
|