phewsh 0.9.0 → 0.10.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/README.md +49 -9
- package/bin/phewsh.js +2 -2
- package/commands/{music.js → mbhd.js} +1 -1
- package/commands/session.js +191 -0
- package/commands/sync.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# phewsh
|
|
2
2
|
|
|
3
|
-
Turn intent into action.
|
|
3
|
+
Turn intent into action. Structure your thinking, execute your next step.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,24 +8,64 @@ Turn intent into action.
|
|
|
8
8
|
npm install -g phewsh
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Quick Start
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
phewsh
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
phewsh # enter the interactive shell
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Inside the shell:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
/init Create .intent/ artifacts
|
|
21
|
+
/clarify AI-assisted artifact generation
|
|
22
|
+
/push Sync local to cloud
|
|
23
|
+
/pull Sync cloud to local
|
|
24
|
+
/context View loaded artifacts
|
|
25
|
+
/sync Check sync status
|
|
26
|
+
/help All commands
|
|
18
27
|
```
|
|
19
28
|
|
|
20
29
|
## What it does
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
Creates three structured artifacts in `.intent/`:
|
|
23
32
|
|
|
24
33
|
- **vision.md** — The north star. Why this exists and where it's going.
|
|
25
34
|
- **plan.md** — The strategy. Phases, systems, sequence, constraints.
|
|
26
|
-
- **next.md** — Right now. Executable checklist with
|
|
35
|
+
- **next.md** — Right now. Executable checklist with next actions.
|
|
36
|
+
|
|
37
|
+
These artifacts become persistent context for AI conversations, both in the shell and across tools.
|
|
38
|
+
|
|
39
|
+
## Sync
|
|
40
|
+
|
|
41
|
+
CLI and web (phewsh.com/intent) share the same cloud via Supabase.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
phewsh login # authenticate
|
|
45
|
+
phewsh push # upload .intent/ to cloud
|
|
46
|
+
phewsh pull # download from cloud
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Sync is manual. The CLI shows status on startup:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
↓ Cloud is newer (2h ago) — run /pull
|
|
53
|
+
↑ Local changes not pushed (15m ago) — run /push
|
|
54
|
+
↕ synced
|
|
55
|
+
```
|
|
27
56
|
|
|
28
|
-
|
|
57
|
+
The `/intent` Claude Code skill writes local files only — run `phewsh push` after using it.
|
|
58
|
+
|
|
59
|
+
## Non-interactive mode
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
phewsh intent --init # Create .intent/ without entering the shell
|
|
63
|
+
phewsh intent --status # Check artifact state
|
|
64
|
+
phewsh clarify # AI-assisted artifact generation
|
|
65
|
+
phewsh ai run "prompt" # One-shot AI with .intent/ context
|
|
66
|
+
phewsh push # Sync to cloud
|
|
67
|
+
phewsh pull # Sync from cloud
|
|
68
|
+
```
|
|
29
69
|
|
|
30
70
|
## Web app
|
|
31
71
|
|
package/bin/phewsh.js
CHANGED
|
@@ -46,7 +46,7 @@ const COMMANDS = {
|
|
|
46
46
|
login: () => require('../commands/login'),
|
|
47
47
|
ai: () => require('../commands/ai'),
|
|
48
48
|
style: () => require('../commands/style'),
|
|
49
|
-
|
|
49
|
+
mbhd: () => require('../commands/mbhd'),
|
|
50
50
|
sap: () => require('../commands/sap'),
|
|
51
51
|
help: showHelp,
|
|
52
52
|
version: showVersion,
|
|
@@ -74,7 +74,7 @@ function showHelp() {
|
|
|
74
74
|
console.log(` ${w('login')} Set up identity, API key, and cloud sync`);
|
|
75
75
|
console.log(` ${w('sap')} Sustainable AI Protocol — usage and accountability`);
|
|
76
76
|
console.log(` ${w('style')} Build your style identity — ingest, profile, sync`);
|
|
77
|
-
console.log(` ${w('
|
|
77
|
+
console.log(` ${w('mbhd')} MBHD music engine`);
|
|
78
78
|
console.log('');
|
|
79
79
|
console.log(` ${b('Quick start')}`);
|
|
80
80
|
console.log(` ${g('phewsh login')} Set up identity + API key`);
|
package/commands/session.js
CHANGED
|
@@ -12,6 +12,10 @@ const CONFIG_DIR = path.join(os.homedir(), '.phewsh');
|
|
|
12
12
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
13
13
|
const INTENT_DIR = path.join(process.cwd(), '.intent');
|
|
14
14
|
|
|
15
|
+
const { select, refreshSession: refreshSess } = require('../lib/supabase');
|
|
16
|
+
const { readPPS } = require('../lib/pps');
|
|
17
|
+
const { push, pull, ensureValidToken } = require('./sync');
|
|
18
|
+
|
|
15
19
|
const b = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
16
20
|
const d = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
17
21
|
const w = (s) => `\x1b[97m${s}\x1b[0m`;
|
|
@@ -20,6 +24,79 @@ const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
|
20
24
|
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
21
25
|
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
22
26
|
|
|
27
|
+
// Sync awareness: compare local .intent/ timestamps with cloud updated_at
|
|
28
|
+
async function checkSyncStatus(config) {
|
|
29
|
+
if (!config?.supabaseUserId || !config?.supabaseAccessToken) return null;
|
|
30
|
+
if (!fs.existsSync(INTENT_DIR)) return null;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const token = await ensureValidToken(config);
|
|
34
|
+
if (!token) return null;
|
|
35
|
+
|
|
36
|
+
const pps = readPPS(INTENT_DIR);
|
|
37
|
+
const cloudId = pps?.adapters?.phewsh?.cloud_id;
|
|
38
|
+
const projectName = path.basename(process.cwd());
|
|
39
|
+
|
|
40
|
+
// Find cloud project
|
|
41
|
+
const query = cloudId
|
|
42
|
+
? `id=eq.${cloudId}&user_id=eq.${config.supabaseUserId}&select=id,updated_at`
|
|
43
|
+
: `name=eq.${encodeURIComponent(projectName)}&user_id=eq.${config.supabaseUserId}&select=id,updated_at`;
|
|
44
|
+
|
|
45
|
+
const projects = await select('projects', query, token);
|
|
46
|
+
if (projects.length === 0) return { status: 'local-only' };
|
|
47
|
+
|
|
48
|
+
const project = projects[0];
|
|
49
|
+
|
|
50
|
+
// Get latest cloud artifact updated_at
|
|
51
|
+
const artifacts = await select(
|
|
52
|
+
'artifacts',
|
|
53
|
+
`project_id=eq.${project.id}&user_id=eq.${config.supabaseUserId}&select=kind,updated_at&order=updated_at.desc&limit=1`,
|
|
54
|
+
token
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const cloudTime = artifacts.length > 0
|
|
58
|
+
? new Date(artifacts[0].updated_at).getTime()
|
|
59
|
+
: new Date(project.updated_at).getTime();
|
|
60
|
+
|
|
61
|
+
// Get latest local file mtime
|
|
62
|
+
const localFiles = ['vision.md', 'plan.md', 'next.md'];
|
|
63
|
+
let latestLocal = 0;
|
|
64
|
+
for (const file of localFiles) {
|
|
65
|
+
const filePath = path.join(INTENT_DIR, file);
|
|
66
|
+
if (fs.existsSync(filePath)) {
|
|
67
|
+
const mtime = fs.statSync(filePath).mtimeMs;
|
|
68
|
+
if (mtime > latestLocal) latestLocal = mtime;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (latestLocal === 0) return { status: 'local-only' };
|
|
73
|
+
|
|
74
|
+
const drift = Math.abs(cloudTime - latestLocal);
|
|
75
|
+
// Within 60 seconds = synced
|
|
76
|
+
if (drift < 60000) return { status: 'synced' };
|
|
77
|
+
|
|
78
|
+
if (cloudTime > latestLocal) {
|
|
79
|
+
const ago = formatAgo(Date.now() - cloudTime);
|
|
80
|
+
return { status: 'cloud-newer', ago };
|
|
81
|
+
} else {
|
|
82
|
+
const ago = formatAgo(Date.now() - latestLocal);
|
|
83
|
+
return { status: 'local-newer', ago };
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
return null; // Network error — silently skip
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatAgo(ms) {
|
|
91
|
+
const mins = Math.floor(ms / 60000);
|
|
92
|
+
if (mins < 1) return 'just now';
|
|
93
|
+
if (mins < 60) return `${mins}m ago`;
|
|
94
|
+
const hrs = Math.floor(mins / 60);
|
|
95
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
96
|
+
const days = Math.floor(hrs / 24);
|
|
97
|
+
return `${days}d ago`;
|
|
98
|
+
}
|
|
99
|
+
|
|
23
100
|
const MODELS = {
|
|
24
101
|
'claude-sonnet': { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', provider: 'anthropic' },
|
|
25
102
|
'claude-opus': { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', provider: 'anthropic' },
|
|
@@ -149,6 +226,26 @@ async function main() {
|
|
|
149
226
|
if (config?.email) {
|
|
150
227
|
console.log(` ${g(' user:')} ${config.email}`);
|
|
151
228
|
}
|
|
229
|
+
|
|
230
|
+
// Sync status check (non-blocking, 3s timeout)
|
|
231
|
+
if (config?.supabaseUserId && intentFiles.length > 0) {
|
|
232
|
+
const syncResult = await Promise.race([
|
|
233
|
+
checkSyncStatus(config),
|
|
234
|
+
new Promise(resolve => setTimeout(() => resolve(null), 3000)),
|
|
235
|
+
]);
|
|
236
|
+
if (syncResult) {
|
|
237
|
+
if (syncResult.status === 'cloud-newer') {
|
|
238
|
+
console.log(` ${yellow('↓')} Cloud is newer (${syncResult.ago}) — run /pull`);
|
|
239
|
+
} else if (syncResult.status === 'local-newer') {
|
|
240
|
+
console.log(` ${yellow('↑')} Local changes not pushed (${syncResult.ago}) — run /push`);
|
|
241
|
+
} else if (syncResult.status === 'synced') {
|
|
242
|
+
console.log(` ${green('↕')} ${g('synced')}`);
|
|
243
|
+
} else if (syncResult.status === 'local-only') {
|
|
244
|
+
console.log(` ${g('↕ not linked to cloud — run /push to sync')}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
152
249
|
console.log('');
|
|
153
250
|
console.log(` ${g('type naturally · /help for commands · /quit to exit')}`);
|
|
154
251
|
console.log('');
|
|
@@ -193,10 +290,16 @@ async function main() {
|
|
|
193
290
|
|
|
194
291
|
${w('project')}
|
|
195
292
|
${g('/init')} Create .intent/ artifacts in this directory
|
|
293
|
+
${g('/clarify')} AI-assisted artifact generation
|
|
196
294
|
${g('/context')} Show loaded .intent/ files
|
|
197
295
|
${g('/reload')} Reload .intent/ context from disk
|
|
198
296
|
${g('/status')} Show session stats
|
|
199
297
|
|
|
298
|
+
${w('sync')}
|
|
299
|
+
${g('/push')} Push .intent/ to cloud
|
|
300
|
+
${g('/pull')} Pull .intent/ from cloud (reloads context)
|
|
301
|
+
${g('/sync')} Check sync status
|
|
302
|
+
|
|
200
303
|
${w('configuration')}
|
|
201
304
|
${g('/login')} Set up identity + cloud sync
|
|
202
305
|
${g('/key')} Set or update your API key
|
|
@@ -286,6 +389,94 @@ async function main() {
|
|
|
286
389
|
return;
|
|
287
390
|
}
|
|
288
391
|
|
|
392
|
+
if (cmd === 'clarify') {
|
|
393
|
+
if (!config?.apiKey) {
|
|
394
|
+
console.log(`\n ${yellow('⚠')} No API key. Run /key to set one.\n`);
|
|
395
|
+
rl.prompt();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
const { execSync } = require('child_process');
|
|
400
|
+
const args = cmdArg ? `--text "${cmdArg.replace(/"/g, '\\"')}"` : '';
|
|
401
|
+
execSync(`node ${path.join(__dirname, 'clarify.js')} ${args}`, { stdio: 'inherit' });
|
|
402
|
+
// Reload context after clarify
|
|
403
|
+
intentFiles = loadIntentContext();
|
|
404
|
+
systemPrompt = buildSystemPrompt(intentFiles);
|
|
405
|
+
if (intentFiles.length > 0) {
|
|
406
|
+
console.log(` ${green('●')} Context loaded: ${intentFiles.map(f => f.file).join(', ')}`);
|
|
407
|
+
}
|
|
408
|
+
} catch (err) {
|
|
409
|
+
console.error(` ${g('Clarify failed:')} ${err.message}`);
|
|
410
|
+
}
|
|
411
|
+
console.log('');
|
|
412
|
+
rl.prompt();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (cmd === 'push') {
|
|
417
|
+
if (!config?.supabaseUserId) {
|
|
418
|
+
console.log(`\n ${yellow('⚠')} Not logged in. Run /login first.\n`);
|
|
419
|
+
rl.prompt();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
const token = await ensureValidToken(config);
|
|
424
|
+
if (!token) { console.log(`\n ${yellow('⚠')} Session expired. Run /login.\n`); rl.prompt(); return; }
|
|
425
|
+
await push(config, token);
|
|
426
|
+
} catch (err) {
|
|
427
|
+
console.error(` ${yellow('⚠')} Push failed: ${err.message}\n`);
|
|
428
|
+
}
|
|
429
|
+
rl.prompt();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (cmd === 'pull') {
|
|
434
|
+
if (!config?.supabaseUserId) {
|
|
435
|
+
console.log(`\n ${yellow('⚠')} Not logged in. Run /login first.\n`);
|
|
436
|
+
rl.prompt();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const token = await ensureValidToken(config);
|
|
441
|
+
if (!token) { console.log(`\n ${yellow('⚠')} Session expired. Run /login.\n`); rl.prompt(); return; }
|
|
442
|
+
await pull(config, token);
|
|
443
|
+
// Reload context after pull
|
|
444
|
+
intentFiles = loadIntentContext();
|
|
445
|
+
systemPrompt = buildSystemPrompt(intentFiles);
|
|
446
|
+
if (intentFiles.length > 0) {
|
|
447
|
+
console.log(` ${green('●')} Context reloaded: ${intentFiles.map(f => f.file).join(', ')}`);
|
|
448
|
+
}
|
|
449
|
+
} catch (err) {
|
|
450
|
+
console.error(` ${yellow('⚠')} Pull failed: ${err.message}\n`);
|
|
451
|
+
}
|
|
452
|
+
console.log('');
|
|
453
|
+
rl.prompt();
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (cmd === 'sync') {
|
|
458
|
+
// Show sync status
|
|
459
|
+
if (!config?.supabaseUserId) {
|
|
460
|
+
console.log(`\n ${yellow('⚠')} Not logged in. Run /login first.\n`);
|
|
461
|
+
rl.prompt();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const syncResult = await checkSyncStatus(config);
|
|
465
|
+
if (!syncResult) {
|
|
466
|
+
console.log(`\n ${g('Could not check sync status')}\n`);
|
|
467
|
+
} else if (syncResult.status === 'cloud-newer') {
|
|
468
|
+
console.log(`\n ${yellow('↓')} Cloud is newer (${syncResult.ago}) — run /pull\n`);
|
|
469
|
+
} else if (syncResult.status === 'local-newer') {
|
|
470
|
+
console.log(`\n ${yellow('↑')} Local changes not pushed (${syncResult.ago}) — run /push\n`);
|
|
471
|
+
} else if (syncResult.status === 'synced') {
|
|
472
|
+
console.log(`\n ${green('↕')} In sync\n`);
|
|
473
|
+
} else if (syncResult.status === 'local-only') {
|
|
474
|
+
console.log(`\n ${g('↕ Not linked to cloud — run /push to sync')}\n`);
|
|
475
|
+
}
|
|
476
|
+
rl.prompt();
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
289
480
|
if (cmd === 'login') {
|
|
290
481
|
try {
|
|
291
482
|
const { execSync } = require('child_process');
|
package/commands/sync.js
CHANGED
|
@@ -113,6 +113,7 @@ async function push(config, token) {
|
|
|
113
113
|
if (!localPPS.adapters.phewsh) localPPS.adapters.phewsh = {};
|
|
114
114
|
localPPS.adapters.phewsh.cloud_id = project.id;
|
|
115
115
|
localPPS.adapters.phewsh.last_synced = new Date().toISOString();
|
|
116
|
+
localPPS.adapters.phewsh.last_updated_by = 'cli';
|
|
116
117
|
writePPS(INTENT_DIR, localPPS);
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -164,7 +165,7 @@ async function pull(config, token, cloudId = null) {
|
|
|
164
165
|
// Keep any local adapter links
|
|
165
166
|
if (localPPS?.adapters) merged.adapters = { ...project.pps_json.adapters, ...localPPS.adapters };
|
|
166
167
|
merged.adapters = merged.adapters || {};
|
|
167
|
-
merged.adapters.phewsh = { cloud_id: project.id, last_synced: new Date().toISOString() };
|
|
168
|
+
merged.adapters.phewsh = { cloud_id: project.id, last_synced: new Date().toISOString(), last_updated_by: 'pull' };
|
|
168
169
|
writePPS(INTENT_DIR, merged);
|
|
169
170
|
pulled.push('pps.json');
|
|
170
171
|
}
|