phewsh 0.15.72 → 0.15.74
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/commands/ambient.js +18 -0
- package/commands/clarify.js +27 -3
- package/commands/hook.js +30 -1
- package/commands/intent.js +6 -1
- package/commands/pack.js +34 -1
- package/commands/session.js +53 -36
- package/lib/cors.js +6 -0
- package/lib/packs.js +4 -4
- package/package.json +1 -1
package/commands/ambient.js
CHANGED
|
@@ -262,6 +262,10 @@ async function turnOn(skipConfirm) {
|
|
|
262
262
|
|
|
263
263
|
function turnOff() {
|
|
264
264
|
const removed = removeClaudeHooks();
|
|
265
|
+
// Full reversibility: ambient off takes the lifecycle hooks with it too.
|
|
266
|
+
let gateRemoved = false;
|
|
267
|
+
try { gateRemoved = disablePreToolGate(); } catch { /* best-effort */ }
|
|
268
|
+
if (gateRemoved) removed.push('lifecycle hooks (PreToolUse gate + PostToolUse receipt)');
|
|
265
269
|
const { removed: removedGlobal } = selfheal.removeGlobalBaseFiles();
|
|
266
270
|
const { removed: removedProject } = selfheal.removeProjectContextFiles();
|
|
267
271
|
const { removed: removedSlash } = slash.removeSlashCommands();
|
|
@@ -269,6 +273,7 @@ function turnOff() {
|
|
|
269
273
|
delete ledger.applied['claude-code'];
|
|
270
274
|
delete ledger.applied.globalBase;
|
|
271
275
|
delete ledger.applied.slashCommands;
|
|
276
|
+
delete ledger.applied.lifecycle;
|
|
272
277
|
delete ledger.autoEnabledAt;
|
|
273
278
|
ledger.disabled = true; // sticky opt-out — first-run auto-enable must respect this
|
|
274
279
|
saveLedger(ledger);
|
|
@@ -365,6 +370,11 @@ async function ensureAuto() {
|
|
|
365
370
|
|
|
366
371
|
const hasClaude = installed.some(h => h.id === 'claude-code');
|
|
367
372
|
const changes = hasClaude ? applyClaudeHooks() : [];
|
|
373
|
+
// Neal's ruling (Jul 5): the lifecycle hooks (PreToolUse gate + PostToolUse
|
|
374
|
+
// receipt) install with first-run auto-enable too — they're fail-open,
|
|
375
|
+
// redacted, and removed by the same `phewsh ambient off` / `gate enforce off`.
|
|
376
|
+
let gateApplied = false;
|
|
377
|
+
if (hasClaude) { try { gateApplied = enablePreToolGate(); } catch { /* best-effort */ } }
|
|
368
378
|
const { written } = selfheal.syncGlobalBaseFiles();
|
|
369
379
|
const slashWritten = slash.installSlashCommands().written;
|
|
370
380
|
// Refresh existing project files only — first-run auto-enable must not dump
|
|
@@ -378,6 +388,13 @@ async function ensureAuto() {
|
|
|
378
388
|
captures: '~/.phewsh/ambient-sessions.jsonl — timestamp, project, cwd only',
|
|
379
389
|
undo: 'phewsh ambient off',
|
|
380
390
|
};
|
|
391
|
+
if (gateApplied) {
|
|
392
|
+
ledger.applied.lifecycle = {
|
|
393
|
+
at: now, file: CLAUDE_SETTINGS,
|
|
394
|
+
what: 'PreToolUse gate (deny protected paths, ask on high blast radius) + PostToolUse redacted receipt',
|
|
395
|
+
undo: 'phewsh gate enforce off (or phewsh ambient off)',
|
|
396
|
+
};
|
|
397
|
+
}
|
|
381
398
|
}
|
|
382
399
|
if (written.length > 0) {
|
|
383
400
|
ledger.applied.globalBase = { at: now, files: written, undo: 'phewsh ambient off' };
|
|
@@ -394,6 +411,7 @@ async function ensureAuto() {
|
|
|
394
411
|
console.log('');
|
|
395
412
|
console.log(` ${b(cream('phewsh set itself up across your AI tools'))} ${sage('— so they stay in sync with your project intent.')}`);
|
|
396
413
|
if (hasClaude) console.log(` ${teal('+')} ${slate('Claude Code gets a brief from a project\'s .intent/ at session start')}`);
|
|
414
|
+
if (gateApplied) console.log(` ${teal('+')} ${slate('guardrails: protected-path writes gated + a redacted receipt of what ran')}`);
|
|
397
415
|
written.forEach(f => console.log(` ${teal('+')} ${slate('environment note added to ' + f)}`));
|
|
398
416
|
if (slashWritten.length) console.log(` ${teal('+')} ${cream('/intent')} ${slate('command added to ' + slashWritten.join(', '))}`);
|
|
399
417
|
console.log(` ${sage('In any project with')} ${cream('.intent/')}${sage(', your tools now read its real intent. Reversible:')} ${cream('phewsh ambient off')}`);
|
package/commands/clarify.js
CHANGED
|
@@ -81,6 +81,26 @@ function assembleRaw(answers) {
|
|
|
81
81
|
return answers.map((a) => `${a.title} (${a.directive}): ${a.answer}`).join('\n');
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// No-AI spec from the user's own words: the first answer line becomes the
|
|
85
|
+
// goal (node label stripped), the raw text survives verbatim in pps.intent.raw,
|
|
86
|
+
// and the first task points back at the AI compile.
|
|
87
|
+
function fallbackSpec(raw) {
|
|
88
|
+
const first = String(raw).split('\n').find(l => l.trim()) || "Capture this project's intent";
|
|
89
|
+
const goal = first.replace(/^[A-Za-z]+ \([^)]*\):\s*/, '').trim().slice(0, 200);
|
|
90
|
+
return {
|
|
91
|
+
goal,
|
|
92
|
+
success_criteria: [],
|
|
93
|
+
constraints: [],
|
|
94
|
+
inputs: [],
|
|
95
|
+
outputs: [],
|
|
96
|
+
tasks: [
|
|
97
|
+
{ text: 'Re-run `phewsh clarify --update` to compile this spec with AI', type: 'copy' },
|
|
98
|
+
{ text: 'Refine the vision — complete vision.md', type: 'do' },
|
|
99
|
+
{ text: 'Define Phase 1 — what is the smallest thing to ship?', type: 'do' },
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
84
104
|
function buildClarifySystemPrompt(existing) {
|
|
85
105
|
const isRefine = !!(existing?.intent?.goal);
|
|
86
106
|
return `You are a project compiler. Your job is to extract clean, structured intent from messy human input.
|
|
@@ -264,8 +284,12 @@ async function main() {
|
|
|
264
284
|
? await callClarifyViaHarness(harnessId, raw, existing)
|
|
265
285
|
: await callClarifyAPI(config.apiKey, raw, existing);
|
|
266
286
|
} catch (err) {
|
|
267
|
-
|
|
268
|
-
|
|
287
|
+
// A failed compile must NEVER eat the user's answers. They just walked
|
|
288
|
+
// the compass — write a plain no-AI spec from what they typed, and let
|
|
289
|
+
// `clarify --update` enrich it when a route works again.
|
|
290
|
+
console.log(`\n AI compile unavailable (${err.message}).`);
|
|
291
|
+
console.log(' Saving your answers as a plain spec instead — nothing you typed is lost.');
|
|
292
|
+
extracted = fallbackSpec(raw);
|
|
269
293
|
}
|
|
270
294
|
|
|
271
295
|
const entity = getProjectName();
|
|
@@ -333,4 +357,4 @@ if (require.main === module) {
|
|
|
333
357
|
});
|
|
334
358
|
}
|
|
335
359
|
|
|
336
|
-
module.exports = { run: main, GUIDE_NODES, INTENT_NODES, assembleRaw, askGuided, extractJson };
|
|
360
|
+
module.exports = { run: main, GUIDE_NODES, INTENT_NODES, assembleRaw, askGuided, extractJson, fallbackSpec };
|
package/commands/hook.js
CHANGED
|
@@ -74,10 +74,39 @@ function firstLines(text, n) {
|
|
|
74
74
|
.join('\n');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// The accountability invitation. A real project (it has .git) with no
|
|
78
|
+
// .intent/ gets ONE gentle note injected — telling the agent to offer
|
|
79
|
+
// grounding once, and to drop it if declined. Once per project, ever
|
|
80
|
+
// (tracked in ~/.phewsh/intent-nudges.json); every later session is
|
|
81
|
+
// silent again. Invite, never nag — the recorded stance (decisions.md).
|
|
82
|
+
function maybeIntentInvitation() {
|
|
83
|
+
try {
|
|
84
|
+
if (!fs.existsSync(path.join(process.cwd(), '.git'))) return; // not a real project — cost nothing
|
|
85
|
+
const nudgeFile = path.join(PHEWSH_DIR, 'intent-nudges.json');
|
|
86
|
+
let seen = {};
|
|
87
|
+
try { seen = JSON.parse(fs.readFileSync(nudgeFile, 'utf-8')); } catch { /* first nudge ever */ }
|
|
88
|
+
if (seen && seen[process.cwd()]) return; // already invited here — never again
|
|
89
|
+
seen = (seen && typeof seen === 'object') ? seen : {};
|
|
90
|
+
seen[process.cwd()] = new Date().toISOString();
|
|
91
|
+
fs.mkdirSync(PHEWSH_DIR, { recursive: true });
|
|
92
|
+
fs.writeFileSync(nudgeFile, JSON.stringify(seen, null, 2));
|
|
93
|
+
process.stdout.write([
|
|
94
|
+
'# phewsh — one-time note for this project',
|
|
95
|
+
'This repo has no `.intent/` yet — no durable project truth that travels between AI tools.',
|
|
96
|
+
'If the user starts substantial (non-trivial) work here, offer ONCE, in plain language, to ground the project:',
|
|
97
|
+
'`phewsh clarify` (guided walk) or `phewsh init` (two questions) creates `.intent/` — vision, plan, and next steps that every AI tool reads, so the next session picks up where this one left off.',
|
|
98
|
+
'If they decline, or the work is trivial/exploratory, drop it and never raise it again. Do not create `.intent/` yourself uninvited.',
|
|
99
|
+
].join('\n') + '\n');
|
|
100
|
+
appendBreadcrumb('intent-invite');
|
|
101
|
+
} catch { /* the invitation is a nicety — never break the host */ }
|
|
102
|
+
}
|
|
103
|
+
|
|
77
104
|
function sessionStart() {
|
|
78
105
|
if (!fs.existsSync(path.join(INTENT_DIR, 'vision.md')) &&
|
|
79
106
|
!fs.existsSync(path.join(INTENT_DIR, 'plan.md'))) {
|
|
80
|
-
// No .intent/ here —
|
|
107
|
+
// No .intent/ here — one gentle invitation if this is a real project,
|
|
108
|
+
// then silence forever.
|
|
109
|
+
maybeIntentInvitation();
|
|
81
110
|
process.exit(0);
|
|
82
111
|
}
|
|
83
112
|
|
package/commands/intent.js
CHANGED
|
@@ -6,7 +6,12 @@ const { createPPS, writeGuardedViews } = require('../lib/pps');
|
|
|
6
6
|
|
|
7
7
|
const os = require('os');
|
|
8
8
|
const configFile = require('../lib/config-file');
|
|
9
|
-
|
|
9
|
+
// Flags may arrive as `phewsh intent --init` / `phewsh init --init` (flag at
|
|
10
|
+
// argv[3]) or as a direct `node intent.js --init` (flag at argv[2]) — the
|
|
11
|
+
// session used the direct form and every flag was silently missed, so /init
|
|
12
|
+
// printed help instead of initializing. Accept both shapes.
|
|
13
|
+
const rawArgs = process.argv.slice(2);
|
|
14
|
+
const args = (rawArgs[0] === 'intent' || rawArgs[0] === 'init') ? rawArgs.slice(1) : rawArgs;
|
|
10
15
|
const INTENT_DIR = path.join(process.cwd(), '.intent');
|
|
11
16
|
const CONFIG_PATH = path.join(os.homedir(), '.phewsh', 'config.json');
|
|
12
17
|
const WEB_URL = 'https://phewsh.com/intent';
|
package/commands/pack.js
CHANGED
|
@@ -36,7 +36,39 @@ function list() {
|
|
|
36
36
|
console.log(` ${slate('source: ' + p.source)}`);
|
|
37
37
|
}
|
|
38
38
|
console.log('');
|
|
39
|
-
console.log(` ${sage('Install:')} ${cream('phewsh pack install <name>')} ${sage('
|
|
39
|
+
console.log(` ${sage('Install:')} ${cream('phewsh pack install <name>')} ${sage('All official packs at once:')} ${cream('phewsh pack install all')}`);
|
|
40
|
+
console.log(` ${sage('Remove:')} ${cream('phewsh pack remove <name>')} ${sage('Read about every pack:')} ${cream('phewsh.com/cli#packs')}`);
|
|
41
|
+
console.log('');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// `phewsh pack install all` — every official (vendored) pack in one confirmed
|
|
45
|
+
// pass. Linked packs stay pointers to their upstream source; we list them so
|
|
46
|
+
// nothing external ever installs silently.
|
|
47
|
+
async function installAll() {
|
|
48
|
+
const vendored = Object.entries(packs.PACKS).filter(([, p]) => p.kind !== 'linked');
|
|
49
|
+
const linked = Object.entries(packs.PACKS).filter(([, p]) => p.kind === 'linked');
|
|
50
|
+
const pending = vendored.filter(([name]) => !packs.isInstalled(name));
|
|
51
|
+
|
|
52
|
+
console.log('');
|
|
53
|
+
if (pending.length === 0) {
|
|
54
|
+
console.log(` ${sage('All official packs are already installed here.')}`);
|
|
55
|
+
} else {
|
|
56
|
+
console.log(` ${b(cream('Official phewsh packs'))} ${sage('— ' + pending.length + ' to install:')}`);
|
|
57
|
+
pending.forEach(([name, p]) => console.log(` ${cream(name.padEnd(16))} ${slate(p.desc)}`));
|
|
58
|
+
console.log('');
|
|
59
|
+
const ok = await confirm(` ${b('Install ' + (pending.length === 1 ? 'it' : 'all ' + pending.length) + ' here?')} ${slate('[y/N] ')}`);
|
|
60
|
+
if (!ok) { console.log(` ${sage('Nothing changed.')}\n`); return; }
|
|
61
|
+
for (const [name] of pending) {
|
|
62
|
+
const { written } = packs.install(name);
|
|
63
|
+
console.log(` ${green('●')} ${cream(name)} ${slate('→ ' + written.join(', '))}`);
|
|
64
|
+
}
|
|
65
|
+
console.log(` ${sage('Remove any:')} ${cream('phewsh pack remove <name>')}`);
|
|
66
|
+
}
|
|
67
|
+
if (linked.length > 0) {
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log(` ${sage(linked.length + ' more are linked packs — separate tools phewsh points at but never auto-installs:')}`);
|
|
70
|
+
console.log(` ${cream('phewsh pack')} ${sage('lists them ·')} ${cream('phewsh.com/cli#packs')} ${sage('tells their stories')}`);
|
|
71
|
+
}
|
|
40
72
|
console.log('');
|
|
41
73
|
}
|
|
42
74
|
|
|
@@ -89,6 +121,7 @@ function remove(name) {
|
|
|
89
121
|
async function main() {
|
|
90
122
|
const sub = process.argv[3];
|
|
91
123
|
const name = process.argv[4];
|
|
124
|
+
if (sub === 'install' && name === 'all') return installAll();
|
|
92
125
|
if (sub === 'install' && name) return install(name);
|
|
93
126
|
if (sub === 'remove' && name) return remove(name);
|
|
94
127
|
return list();
|
package/commands/session.js
CHANGED
|
@@ -733,6 +733,39 @@ async function main() {
|
|
|
733
733
|
console.log('');
|
|
734
734
|
}
|
|
735
735
|
|
|
736
|
+
// The project scanner — bootstrap option AND /scan slash command. Lists
|
|
737
|
+
// .intent/ projects plus likely candidates (git, no .intent yet, reason
|
|
738
|
+
// shown) from the usual folders, numbered so a bare digit opens one.
|
|
739
|
+
function runScanMenu() {
|
|
740
|
+
const spin = ui.spinner('scanning your usual folders');
|
|
741
|
+
const found = scanForProjects();
|
|
742
|
+
let candidates = [];
|
|
743
|
+
try { candidates = scanForCandidates(); } catch { /* advisory — scan still useful without it */ }
|
|
744
|
+
spin.stop();
|
|
745
|
+
if (found.length === 0 && candidates.length === 0) {
|
|
746
|
+
bootstrapChoices = null;
|
|
747
|
+
console.log(` ${sage('No .intent/ projects found in the usual folders.')}`);
|
|
748
|
+
console.log(` ${slate('cd into a project and run phewsh, or /init to start one here.')}`);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
bootstrapChoices = [];
|
|
752
|
+
if (found.length > 0) {
|
|
753
|
+
console.log(` ${teal('●')} ${sage(`Found ${found.length} project${found.length !== 1 ? 's' : ''} with .intent/:`)}`);
|
|
754
|
+
for (const p of found) {
|
|
755
|
+
bootstrapChoices.push({ kind: 'open', path: p.path });
|
|
756
|
+
console.log(` ${teal(String(bootstrapChoices.length))} ${cream(p.name)} ${slate('· ' + tildify(p.path))}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (candidates.length > 0) {
|
|
760
|
+
console.log(` ${teal('●')} ${sage(`${candidates.length} likely candidate${candidates.length !== 1 ? 's' : ''} — no shared memory yet:`)}`);
|
|
761
|
+
for (const p of candidates) {
|
|
762
|
+
bootstrapChoices.push({ kind: 'open', path: p.path });
|
|
763
|
+
console.log(` ${teal(String(bootstrapChoices.length))} ${cream(p.name)} ${slate('· ' + tildify(p.path) + ' · ' + p.reason)}`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
console.log(` ${slate('pick a number to open it')}`);
|
|
767
|
+
}
|
|
768
|
+
|
|
736
769
|
function showBootstrapMenu(projects) {
|
|
737
770
|
console.log(` ${b(cream('Where do you want to work?'))}`);
|
|
738
771
|
bootstrapChoices = [];
|
|
@@ -976,7 +1009,7 @@ async function main() {
|
|
|
976
1009
|
// RECOGNIZED leading /command (or @harness) token turns teal (peach for @)
|
|
977
1010
|
// so you know it registered. Arguments stay plain. TTY-only, fail-soft.
|
|
978
1011
|
const KNOWN_COMMANDS = new Set([
|
|
979
|
-
'quit', 'exit', 'q', 'help', 'h', 'init', 'intent', 'clarify', 'model',
|
|
1012
|
+
'quit', 'exit', 'q', 'help', 'h', 'init', 'intent', 'clarify', 'scan', 'model',
|
|
980
1013
|
'models', 'council', 'all', 'provider', 'route', 'use', 'work', 'switch', 'run',
|
|
981
1014
|
'clear', 'status', 'key', 'login', 'export', 'push', 'pull', 'serve',
|
|
982
1015
|
'sync', 'harnesses', 'fallback', 'outcomes', 'tour', 'update', 'upgrade',
|
|
@@ -1277,8 +1310,11 @@ async function main() {
|
|
|
1277
1310
|
return;
|
|
1278
1311
|
}
|
|
1279
1312
|
|
|
1280
|
-
//
|
|
1281
|
-
|
|
1313
|
+
// Scan/bootstrap menu: a bare number opens a project, inits, or scans.
|
|
1314
|
+
// Any other input means the user moved on — drop the menu so it never
|
|
1315
|
+
// shadows a later digit (mirrors the nextChoices rule above).
|
|
1316
|
+
if (bootstrapChoices && !/^[0-9]{1,2}$/.test(input)) bootstrapChoices = null;
|
|
1317
|
+
if (bootstrapChoices && /^[0-9]{1,2}$/.test(input)) {
|
|
1282
1318
|
const choice = bootstrapChoices[parseInt(input, 10) - 1];
|
|
1283
1319
|
if (!choice) {
|
|
1284
1320
|
console.log(` ${sage('Pick 1-' + bootstrapChoices.length)}`);
|
|
@@ -1293,8 +1329,8 @@ async function main() {
|
|
|
1293
1329
|
if (choice.kind === 'init') {
|
|
1294
1330
|
bootstrapChoices = null;
|
|
1295
1331
|
try {
|
|
1296
|
-
const {
|
|
1297
|
-
|
|
1332
|
+
const { execFileSync } = require('child_process');
|
|
1333
|
+
execFileSync(process.execPath, [path.join(__dirname, '..', 'bin', 'phewsh.js'), 'intent', '--init'], { stdio: 'inherit' });
|
|
1298
1334
|
intentFiles = loadIntentContext();
|
|
1299
1335
|
systemPrompt = buildSystemPrompt(intentFiles);
|
|
1300
1336
|
if (intentFiles.length > 0) {
|
|
@@ -1309,35 +1345,7 @@ async function main() {
|
|
|
1309
1345
|
return;
|
|
1310
1346
|
}
|
|
1311
1347
|
if (choice.kind === 'scan') {
|
|
1312
|
-
|
|
1313
|
-
const found = scanForProjects();
|
|
1314
|
-
// Likely candidates: real repos with no .intent/ yet — the projects
|
|
1315
|
-
// most worth grounding. Same shallow scan; shown with their reason.
|
|
1316
|
-
let candidates = [];
|
|
1317
|
-
try { candidates = scanForCandidates(); } catch { /* advisory — scan still useful without it */ }
|
|
1318
|
-
spin.stop();
|
|
1319
|
-
if (found.length === 0 && candidates.length === 0) {
|
|
1320
|
-
bootstrapChoices = null;
|
|
1321
|
-
console.log(` ${sage('No .intent/ projects found in the usual folders.')}`);
|
|
1322
|
-
console.log(` ${slate('cd into a project and run phewsh, or /init to start one here.')}`);
|
|
1323
|
-
} else {
|
|
1324
|
-
bootstrapChoices = [];
|
|
1325
|
-
if (found.length > 0) {
|
|
1326
|
-
console.log(` ${teal('●')} ${sage(`Found ${found.length} project${found.length !== 1 ? 's' : ''} with .intent/:`)}`);
|
|
1327
|
-
for (const p of found) {
|
|
1328
|
-
bootstrapChoices.push({ kind: 'open', path: p.path });
|
|
1329
|
-
console.log(` ${teal(String(bootstrapChoices.length))} ${cream(p.name)} ${slate('· ' + tildify(p.path))}`);
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
if (candidates.length > 0) {
|
|
1333
|
-
console.log(` ${teal('●')} ${sage(`${candidates.length} likely candidate${candidates.length !== 1 ? 's' : ''} — no shared memory yet:`)}`);
|
|
1334
|
-
for (const p of candidates) {
|
|
1335
|
-
bootstrapChoices.push({ kind: 'open', path: p.path });
|
|
1336
|
-
console.log(` ${teal(String(bootstrapChoices.length))} ${cream(p.name)} ${slate('· ' + tildify(p.path) + ' · ' + p.reason)}`);
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
console.log(` ${slate('pick a number to open it')}`);
|
|
1340
|
-
}
|
|
1348
|
+
runScanMenu();
|
|
1341
1349
|
console.log('');
|
|
1342
1350
|
rl.prompt();
|
|
1343
1351
|
return;
|
|
@@ -1671,6 +1679,7 @@ async function main() {
|
|
|
1671
1679
|
console.log(` ${teal('@name')} ${slate('<msg>')} ${sage('one message to one tool — @codex review this')}`);
|
|
1672
1680
|
console.log(` ${teal('/work')} ${slate('[tool]')} ${sage('hand off to the full interactive tool, outcome on return')}`);
|
|
1673
1681
|
console.log(` ${teal('/clarify')} ${sage('turn messy thoughts into .intent/ artifacts')}`);
|
|
1682
|
+
console.log(` ${teal('/scan')} ${sage('find your projects — and repos that need shared memory')}`);
|
|
1674
1683
|
console.log(` ${teal('/outcomes')} ${sage('label what you kept — the record that gets smarter')}`);
|
|
1675
1684
|
console.log('');
|
|
1676
1685
|
console.log(` ${slate('/help all')} ${sage('everything')} ${slate('·')} ${slate('/tour')} ${sage('walkthrough')} ${slate('·')} ${slate('/quit')} ${sage('exit')}`);
|
|
@@ -1688,6 +1697,7 @@ async function main() {
|
|
|
1688
1697
|
console.log(` ${teal('/learn')} ${sage('what your record taught — which tool keeps best, by kind of work')}`);
|
|
1689
1698
|
console.log('');
|
|
1690
1699
|
console.log(` ${cream('author .intent/')}`);
|
|
1700
|
+
console.log(` ${teal('/scan')} ${sage('Find your projects — and likely candidates with no .intent/ yet')}`);
|
|
1691
1701
|
console.log(` ${teal('/init')} ${sage('Create .intent/ for this project')}`);
|
|
1692
1702
|
console.log(` ${teal('/intent')} ${sage('Pause and reflect — view or update .intent/ before moving on')}`);
|
|
1693
1703
|
console.log(` ${teal('/remember')} ${sage('Jot a decision to .intent/decisions.md — every tool inherits it')}`);
|
|
@@ -1906,8 +1916,8 @@ async function main() {
|
|
|
1906
1916
|
console.log(` ${sage('Use /reload to refresh context')}\n`);
|
|
1907
1917
|
} else {
|
|
1908
1918
|
try {
|
|
1909
|
-
const {
|
|
1910
|
-
|
|
1919
|
+
const { execFileSync } = require('child_process');
|
|
1920
|
+
execFileSync(process.execPath, [path.join(__dirname, '..', 'bin', 'phewsh.js'), 'intent', '--init'], { stdio: 'inherit' });
|
|
1911
1921
|
intentFiles = loadIntentContext();
|
|
1912
1922
|
systemPrompt = buildSystemPrompt(intentFiles);
|
|
1913
1923
|
if (intentFiles.length > 0) {
|
|
@@ -1922,6 +1932,13 @@ async function main() {
|
|
|
1922
1932
|
return;
|
|
1923
1933
|
}
|
|
1924
1934
|
|
|
1935
|
+
if (cmd === 'scan') {
|
|
1936
|
+
runScanMenu();
|
|
1937
|
+
console.log('');
|
|
1938
|
+
rl.prompt();
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1925
1942
|
if (cmd === 'clarify') {
|
|
1926
1943
|
try {
|
|
1927
1944
|
const { spawnSync } = require('child_process');
|
package/lib/cors.js
CHANGED
|
@@ -37,6 +37,12 @@ function corsHeaders(req) {
|
|
|
37
37
|
'Access-Control-Allow-Origin': origin,
|
|
38
38
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
39
39
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Phewsh-Runtime',
|
|
40
|
+
// Chrome Local/Private Network Access: an HTTPS page (phewsh.com) fetching
|
|
41
|
+
// plain-http loopback gets a preflight carrying Access-Control-Request-
|
|
42
|
+
// Private-Network; without this answer Chrome kills the request before it
|
|
43
|
+
// is ever sent — the cockpit shows "bridge offline" while serve is
|
|
44
|
+
// demonstrably up. Loopback-only server, allowlisted origins only.
|
|
45
|
+
'Access-Control-Allow-Private-Network': 'true',
|
|
40
46
|
'Access-Control-Max-Age': '600',
|
|
41
47
|
Vary: 'Origin',
|
|
42
48
|
};
|
package/lib/packs.js
CHANGED
|
@@ -54,11 +54,11 @@ const PACKS = {
|
|
|
54
54
|
},
|
|
55
55
|
'loop-library': {
|
|
56
56
|
kind: 'linked',
|
|
57
|
-
title: 'Loop Library',
|
|
58
|
-
desc: '
|
|
59
|
-
source: '
|
|
57
|
+
title: 'Loop Library / Loopy',
|
|
58
|
+
desc: 'Forward Future\'s practical agent-loop library and Loopy skill, with checks and stopping conditions. Good Work-layer inspiration; phewsh links to it without becoming a scheduler or runner.',
|
|
59
|
+
source: 'signals.forwardfuture.com/loop-library · github.com/Forward-Future/loop-library',
|
|
60
60
|
license: 'MIT',
|
|
61
|
-
install: 'npx skills add Forward-Future/loop-library --skill
|
|
61
|
+
install: 'npx skills add Forward-Future/loop-library --skill loopy -g # install Loopy through upstream skills tooling',
|
|
62
62
|
},
|
|
63
63
|
'matt-skills': {
|
|
64
64
|
kind: 'linked',
|