phewsh 0.15.15 → 0.15.19
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 +2 -2
- package/commands/clarify.js +116 -19
- package/commands/serve.js +7 -0
- package/commands/sync.js +33 -11
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -30,7 +30,7 @@ function showBrand() {
|
|
|
30
30
|
console.log('');
|
|
31
31
|
console.log(` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`);
|
|
32
32
|
console.log(` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`);
|
|
33
|
-
console.log(` ${g('
|
|
33
|
+
console.log(` ${g('Keep all your AI tools. phewsh is the one memory they share.')}`);
|
|
34
34
|
console.log('');
|
|
35
35
|
|
|
36
36
|
// Context-aware hint
|
|
@@ -49,7 +49,7 @@ function showBrand() {
|
|
|
49
49
|
const COMMANDS = {
|
|
50
50
|
session: () => require('../commands/session'),
|
|
51
51
|
intent: () => require('../commands/intent'),
|
|
52
|
-
clarify: () => require('../commands/clarify'),
|
|
52
|
+
clarify: () => require('../commands/clarify').run(),
|
|
53
53
|
push: () => require('../commands/push'),
|
|
54
54
|
pull: () => require('../commands/pull'),
|
|
55
55
|
link: () => require('../commands/link'),
|
package/commands/clarify.js
CHANGED
|
@@ -45,10 +45,52 @@ async function askForInput() {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
// The guided walk — the five strongest nodes of the web's 12-node Intent
|
|
49
|
+
// Compass, asked one at a time. The web compass helps the user *see* what
|
|
50
|
+
// they're building; this brings that to the terminal. Not a form: every
|
|
51
|
+
// question is skippable, and the point is to help you think, not interrogate.
|
|
52
|
+
const GUIDE_NODES = [
|
|
53
|
+
{ id: 'purpose', title: 'Purpose', directive: 'the core reason this exists',
|
|
54
|
+
q: 'What outcome are you really after — and why does this need to exist?' },
|
|
55
|
+
{ id: 'audience', title: 'Audience', directive: 'the people this serves',
|
|
56
|
+
q: 'Who is this for? Who feels it most when it works?' },
|
|
57
|
+
{ id: 'method', title: 'Method', directive: 'the mechanism and approach',
|
|
58
|
+
q: 'How does it actually work — the core mechanism or approach?' },
|
|
59
|
+
{ id: 'scope', title: 'Scope', directive: 'boundaries, in and out',
|
|
60
|
+
q: "What's in — and just as important, what's deliberately out, for now?" },
|
|
61
|
+
{ id: 'differentiation', title: 'Edge', directive: 'what makes this yours',
|
|
62
|
+
q: 'What would be lost if someone else built this instead of you?' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
function ask(rl, question) {
|
|
66
|
+
return new Promise((resolve) => rl.question(question, (a) => resolve((a || '').trim())));
|
|
67
|
+
}
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
// rl is injectable so the walk can be driven deterministically in tests.
|
|
70
|
+
async function askGuided(rl = readline.createInterface({ input: process.stdin, output: process.stdout })) {
|
|
71
|
+
console.log('\n Five quick questions to align your own thinking first —');
|
|
72
|
+
console.log(' a sentence or two each. Blank skips. (esc stops, nothing saved.)\n');
|
|
73
|
+
const answers = [];
|
|
74
|
+
for (let i = 0; i < GUIDE_NODES.length; i++) {
|
|
75
|
+
const n = GUIDE_NODES[i];
|
|
76
|
+
console.log(` ${i + 1}/${GUIDE_NODES.length} ${n.title} — ${n.directive}`);
|
|
77
|
+
const a = await ask(rl, ` ${n.q}\n > `);
|
|
78
|
+
if (a) answers.push({ ...n, answer: a });
|
|
79
|
+
console.log('');
|
|
80
|
+
}
|
|
81
|
+
rl.close();
|
|
82
|
+
return answers;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Label each answer by its node so the compiler keeps the structure the walk
|
|
86
|
+
// surfaced (a Purpose answer informs the goal, Scope informs constraints, etc.)
|
|
87
|
+
function assembleRaw(answers) {
|
|
88
|
+
return answers.map((a) => `${a.title} (${a.directive}): ${a.answer}`).join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildClarifySystemPrompt(existing) {
|
|
92
|
+
const isRefine = !!(existing?.intent?.goal);
|
|
93
|
+
return `You are a project compiler. Your job is to extract clean, structured intent from messy human input.
|
|
52
94
|
|
|
53
95
|
Return ONLY valid JSON — no markdown, no explanation, no commentary. The JSON must match this exact schema:
|
|
54
96
|
|
|
@@ -73,7 +115,26 @@ Rules:
|
|
|
73
115
|
- tasks: 3-7 concrete next actions, specific enough to act on immediately
|
|
74
116
|
- type options: "do" (manual action), "copy" (command to run), "open" (URL to visit), "install" (package to install)
|
|
75
117
|
${isRefine ? '\nThis is a refinement of existing intent. The previous goal was: ' + existing.intent.goal : ''}`;
|
|
118
|
+
}
|
|
76
119
|
|
|
120
|
+
// Pull the first valid JSON object out of model output — harnesses may wrap it
|
|
121
|
+
// in prose or code fences; the API returns it clean. Throws if none parses.
|
|
122
|
+
function extractJson(text) {
|
|
123
|
+
const candidates = [];
|
|
124
|
+
const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
125
|
+
if (fence) candidates.push(fence[1].trim());
|
|
126
|
+
const first = text.indexOf('{');
|
|
127
|
+
const last = text.lastIndexOf('}');
|
|
128
|
+
if (first !== -1 && last > first) candidates.push(text.slice(first, last + 1));
|
|
129
|
+
candidates.push(text.trim());
|
|
130
|
+
for (const c of candidates) {
|
|
131
|
+
try { return JSON.parse(c); } catch { /* try next candidate */ }
|
|
132
|
+
}
|
|
133
|
+
throw new Error('could not parse a project spec from the model output');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function callClarifyAPI(apiKey, raw, existing) {
|
|
137
|
+
const systemPrompt = buildClarifySystemPrompt(existing);
|
|
77
138
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
78
139
|
method: 'POST',
|
|
79
140
|
headers: {
|
|
@@ -95,11 +156,17 @@ ${isRefine ? '\nThis is a refinement of existing intent. The previous goal was:
|
|
|
95
156
|
}
|
|
96
157
|
|
|
97
158
|
const data = await response.json();
|
|
98
|
-
|
|
159
|
+
return extractJson(data.content?.[0]?.text || '');
|
|
160
|
+
}
|
|
99
161
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
162
|
+
// No API key? Compile through an installed harness instead — pass-through, so
|
|
163
|
+
// clarify works for anyone with Claude Code / Codex / etc., no key required.
|
|
164
|
+
async function callClarifyViaHarness(harnessId, raw, existing) {
|
|
165
|
+
const { runViaHarness } = require('../lib/harnesses');
|
|
166
|
+
const systemPrompt = buildClarifySystemPrompt(existing) +
|
|
167
|
+
'\n\nReturn ONLY the JSON object — no prose, no code fences, before or after.';
|
|
168
|
+
const out = await runViaHarness(harnessId, systemPrompt, raw, { quiet: true });
|
|
169
|
+
return extractJson(out || '');
|
|
103
170
|
}
|
|
104
171
|
|
|
105
172
|
function writeViews(intentDir, pps) {
|
|
@@ -126,17 +193,22 @@ async function main() {
|
|
|
126
193
|
😮💨🤫 phewsh clarify
|
|
127
194
|
|
|
128
195
|
Usage:
|
|
129
|
-
phewsh clarify
|
|
196
|
+
phewsh clarify Guided: a 5-question walk that aligns your thinking, then compiles
|
|
197
|
+
phewsh clarify --freeform Free-form: describe it all in one messy blob
|
|
130
198
|
phewsh clarify --text "..." Inline: pass raw text directly
|
|
131
199
|
phewsh clarify --update Refine existing PPS with new input
|
|
132
200
|
|
|
133
201
|
What it does:
|
|
134
|
-
|
|
202
|
+
Walks you through the five strongest nodes of the Intent Compass —
|
|
203
|
+
Purpose, Audience, Method, Scope, Edge — one question at a time, so the
|
|
204
|
+
terminal helps you *think*, not just compile. Then turns your answers
|
|
205
|
+
into a structured project spec (PPS):
|
|
135
206
|
Writes .intent/pps.json as the source of truth.
|
|
136
207
|
Generates vision.md, plan.md, next.md as human-readable views.
|
|
137
208
|
|
|
138
209
|
Requires:
|
|
139
|
-
|
|
210
|
+
An installed agent CLI (Claude Code, Codex, Gemini…) — phewsh uses its
|
|
211
|
+
login, no key. Or run "phewsh login --set-key" to use an Anthropic API key.
|
|
140
212
|
|
|
141
213
|
Examples:
|
|
142
214
|
phewsh clarify
|
|
@@ -147,8 +219,13 @@ async function main() {
|
|
|
147
219
|
}
|
|
148
220
|
|
|
149
221
|
const config = loadConfig();
|
|
150
|
-
|
|
151
|
-
|
|
222
|
+
// Pass-through: with no API key, compile through an installed harness
|
|
223
|
+
// (Claude Code, Codex, …) — the same login the rest of phewsh rides on.
|
|
224
|
+
const harnessId = config?.apiKey ? null : require('../lib/harnesses').detectInstalled();
|
|
225
|
+
if (!config?.apiKey && !harnessId) {
|
|
226
|
+
console.log('\n Nothing to compile with yet. Either:');
|
|
227
|
+
console.log(' • install an agent CLI (Claude Code, Codex, Gemini…) — phewsh uses its login, or');
|
|
228
|
+
console.log(' • run `phewsh login --set-key` to add an API key.\n');
|
|
152
229
|
process.exit(1);
|
|
153
230
|
}
|
|
154
231
|
|
|
@@ -161,13 +238,25 @@ async function main() {
|
|
|
161
238
|
|
|
162
239
|
console.log('\n 😮💨🤫 phewsh clarify\n');
|
|
163
240
|
|
|
241
|
+
const freeform = args.includes('--freeform') || args.includes('-f');
|
|
164
242
|
let raw = rawFromFlag;
|
|
165
243
|
if (!raw) {
|
|
166
244
|
if (!process.stdin.isTTY) {
|
|
167
245
|
console.error('\n Pipe input or use --text "your description"\n');
|
|
168
246
|
process.exit(1);
|
|
169
247
|
}
|
|
170
|
-
|
|
248
|
+
if (freeform) {
|
|
249
|
+
raw = await askForInput();
|
|
250
|
+
} else {
|
|
251
|
+
// Guided is the default interactive path: help the user think first.
|
|
252
|
+
const answers = await askGuided();
|
|
253
|
+
raw = assembleRaw(answers);
|
|
254
|
+
if (!raw) {
|
|
255
|
+
// Skipped every question — fall back to a single free-form description.
|
|
256
|
+
console.log(' No problem — describe it your own way instead.');
|
|
257
|
+
raw = await askForInput();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
171
260
|
}
|
|
172
261
|
|
|
173
262
|
if (!raw) {
|
|
@@ -175,11 +264,15 @@ async function main() {
|
|
|
175
264
|
return;
|
|
176
265
|
}
|
|
177
266
|
|
|
178
|
-
|
|
267
|
+
const { HARNESSES } = require('../lib/harnesses');
|
|
268
|
+
const via = harnessId ? ` via ${HARNESSES[harnessId]?.label || harnessId}` : '';
|
|
269
|
+
console.log(`\n Compiling your intent into a spec${via}...\n`);
|
|
179
270
|
|
|
180
271
|
let extracted;
|
|
181
272
|
try {
|
|
182
|
-
extracted =
|
|
273
|
+
extracted = harnessId
|
|
274
|
+
? await callClarifyViaHarness(harnessId, raw, existing)
|
|
275
|
+
: await callClarifyAPI(config.apiKey, raw, existing);
|
|
183
276
|
} catch (err) {
|
|
184
277
|
console.error('\n Clarify failed:', err.message, '\n');
|
|
185
278
|
process.exit(1);
|
|
@@ -238,7 +331,11 @@ async function main() {
|
|
|
238
331
|
`);
|
|
239
332
|
}
|
|
240
333
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
334
|
+
if (require.main === module) {
|
|
335
|
+
main().catch(err => {
|
|
336
|
+
console.error('\n Error:', err.message);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = { run: main, GUIDE_NODES, assembleRaw, askGuided, extractJson };
|
package/commands/serve.js
CHANGED
|
@@ -219,6 +219,13 @@ function json(req, res, data, status = 200) {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
function main() {
|
|
222
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
223
|
+
console.log('\n phewsh serve — local execution bridge for phewsh.com/intent');
|
|
224
|
+
console.log(' Runs a loopback server so the web workspace can dispatch to your');
|
|
225
|
+
console.log(' installed agents. Stays running until you stop it (ctrl+c).');
|
|
226
|
+
console.log('\n Usage: phewsh serve [--port <n>] (default 7483)\n');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
222
229
|
const port = getPort();
|
|
223
230
|
const runtimes = detectRuntimes();
|
|
224
231
|
const hasClaudeCode = runtimes.find(r => r.id === 'claude-code')?.connected;
|
package/commands/sync.js
CHANGED
|
@@ -256,7 +256,19 @@ async function link(config, token, cloudId) {
|
|
|
256
256
|
console.log(' Run `phewsh push` to sync.\n');
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
function isAuthError(err) {
|
|
260
|
+
const m = (err && err.message || '').toLowerCase();
|
|
261
|
+
return m.includes('jwt') || m.includes('expired') || m.includes('401') || m.includes('unauthorized');
|
|
262
|
+
}
|
|
263
|
+
|
|
259
264
|
async function main(direction = 'push') {
|
|
265
|
+
const argv = process.argv.slice(3);
|
|
266
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
267
|
+
console.log(`\n phewsh ${direction === 'pull' ? 'pull' : direction === 'link' ? 'link <cloud-id>' : 'push'} — sync .intent/ with phewsh.com/intent`);
|
|
268
|
+
console.log(` push .intent/ → cloud pull cloud → .intent/ link adopt a cloud project\n`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
260
272
|
const config = loadConfig();
|
|
261
273
|
if (!config?.supabaseUserId) {
|
|
262
274
|
console.log('\n Not logged in. Run `phewsh login` first.\n');
|
|
@@ -269,18 +281,28 @@ async function main(direction = 'push') {
|
|
|
269
281
|
process.exit(1);
|
|
270
282
|
}
|
|
271
283
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
284
|
+
// The token can still be rejected server-side (refresh token also expired).
|
|
285
|
+
// Convert that into the same friendly nudge instead of a raw stack trace.
|
|
286
|
+
try {
|
|
287
|
+
if (direction === 'pull') {
|
|
288
|
+
await pull(config, token);
|
|
289
|
+
} else if (direction === 'link') {
|
|
290
|
+
const cloudId = argv[0] && !argv[0].startsWith('-') ? argv[0] : process.argv[4];
|
|
291
|
+
if (!cloudId) {
|
|
292
|
+
console.log('\n Usage: phewsh link <cloud-project-id>\n');
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
await link(config, token, cloudId);
|
|
296
|
+
} else {
|
|
297
|
+
await push(config, token);
|
|
280
298
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
299
|
+
} catch (err) {
|
|
300
|
+
if (isAuthError(err)) {
|
|
301
|
+
console.log('\n Session expired. Run `phewsh login` to re-authenticate.\n');
|
|
302
|
+
} else {
|
|
303
|
+
console.log(`\n ${direction} failed: ${err.message}\n`);
|
|
304
|
+
}
|
|
305
|
+
process.exit(1);
|
|
284
306
|
}
|
|
285
307
|
}
|
|
286
308
|
|