phewsh 0.15.40 → 0.15.41
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 +14 -10
- package/commands/ambient.js +63 -1
- package/lib/selfheal.js +9 -2
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -189,13 +189,22 @@ async function maybeFirstRunIntro() {
|
|
|
189
189
|
} catch { /* the intro is a nicety — never let it block the session */ }
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
// Always-on, frictionless: on the first interactive use, auto-enable ambient
|
|
193
|
+
// across every installed harness (unless the user opted out); on every run
|
|
194
|
+
// after, self-heal so each tool's context files are present and fresh. All
|
|
195
|
+
// best-effort and non-blocking — a launch must never wait on or fail from this.
|
|
196
|
+
async function ambientSelfHeal() {
|
|
197
|
+
try { await require('../commands/ambient').ensureAuto(); } catch { /* never block launch */ }
|
|
198
|
+
// Refresh-only: keep already-present context files fresh, but never CREATE
|
|
199
|
+
// them just because phewsh was opened here — that would dirty a clean repo.
|
|
200
|
+
try { require('../lib/selfheal').syncContextFiles({ createMissing: false }); } catch { /* never block launch */ }
|
|
201
|
+
try { require('../lib/selfheal').refreshGlobalBaseFilesIfApplied(); } catch { /* never block launch */ }
|
|
202
|
+
}
|
|
203
|
+
|
|
192
204
|
if (!command) {
|
|
193
205
|
// Bare `phewsh` — first run gets the intro, then drop into the session.
|
|
194
206
|
// Session handles missing API key gracefully with /login and /key commands.
|
|
195
|
-
|
|
196
|
-
// (silent, idempotent, only if ambient was already applied).
|
|
197
|
-
try { require('../lib/selfheal').refreshGlobalBaseFilesIfApplied(); } catch { /* never block launch */ }
|
|
198
|
-
maybeFirstRunIntro().then(() => COMMANDS.session());
|
|
207
|
+
ambientSelfHeal().finally(() => maybeFirstRunIntro().then(() => COMMANDS.session()));
|
|
199
208
|
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
200
209
|
showHelp();
|
|
201
210
|
exitAfterUpdate(0);
|
|
@@ -209,12 +218,7 @@ if (!command) {
|
|
|
209
218
|
// auto-run /work for that harness (preflight → brief → native handoff →
|
|
210
219
|
// postflight). This is what the phewsh.com doorways copy as a single command.
|
|
211
220
|
process.env.PHEWSH_AUTOWORK = command;
|
|
212
|
-
|
|
213
|
-
// only ever launches (say) Codex via phewsh still has a fresh AGENTS.md, even
|
|
214
|
-
// though Codex has no SessionEnd hook to trigger the sync. Silent, best-effort.
|
|
215
|
-
try { require('../lib/selfheal').syncContextFiles(); } catch { /* never block launch */ }
|
|
216
|
-
try { require('../lib/selfheal').refreshGlobalBaseFilesIfApplied(); } catch { /* never block launch */ }
|
|
217
|
-
maybeFirstRunIntro().then(() => COMMANDS.session());
|
|
221
|
+
ambientSelfHeal().finally(() => maybeFirstRunIntro().then(() => COMMANDS.session()));
|
|
218
222
|
} else {
|
|
219
223
|
console.error(`\n Unknown command: ${command}\n Run 'phewsh help' for available commands.\n`);
|
|
220
224
|
process.exit(1);
|
package/commands/ambient.js
CHANGED
|
@@ -156,7 +156,12 @@ async function turnOn(skipConfirm) {
|
|
|
156
156
|
if (!skipConfirm) {
|
|
157
157
|
const ok = await confirm(` ${b('Apply?')} ${slate('[y/N] ')}`);
|
|
158
158
|
if (!ok) {
|
|
159
|
-
|
|
159
|
+
// A declined prompt is a deliberate opt-out — record it so the first-run
|
|
160
|
+
// auto-enable never overrides the user's "no" on a later command.
|
|
161
|
+
const led = loadLedger();
|
|
162
|
+
led.disabled = true;
|
|
163
|
+
saveLedger(led);
|
|
164
|
+
console.log(` ${sage('Nothing changed.')} ${slate('(phewsh won\'t auto-enable; run `phewsh ambient on` anytime.)')}`);
|
|
160
165
|
console.log('');
|
|
161
166
|
return;
|
|
162
167
|
}
|
|
@@ -180,6 +185,7 @@ async function turnOn(skipConfirm) {
|
|
|
180
185
|
};
|
|
181
186
|
}
|
|
182
187
|
ledger.seenHarnesses = harnesses.filter(h => h.installed).map(h => h.id);
|
|
188
|
+
ledger.disabled = false; // explicit enable clears any prior opt-out
|
|
183
189
|
saveLedger(ledger);
|
|
184
190
|
|
|
185
191
|
console.log('');
|
|
@@ -201,6 +207,8 @@ function turnOff() {
|
|
|
201
207
|
const ledger = loadLedger();
|
|
202
208
|
delete ledger.applied['claude-code'];
|
|
203
209
|
delete ledger.applied.globalBase;
|
|
210
|
+
delete ledger.autoEnabledAt;
|
|
211
|
+
ledger.disabled = true; // sticky opt-out — first-run auto-enable must respect this
|
|
204
212
|
saveLedger(ledger);
|
|
205
213
|
console.log('');
|
|
206
214
|
if (removed.length > 0 || removedGlobal.length > 0) {
|
|
@@ -271,6 +279,59 @@ function status() {
|
|
|
271
279
|
console.log('');
|
|
272
280
|
}
|
|
273
281
|
|
|
282
|
+
// First-run / always-on. The first time phewsh is used interactively, wire
|
|
283
|
+
// ambient automatically across every installed harness — so the user never has
|
|
284
|
+
// to run `phewsh ambient on`. Idempotent and self-limiting:
|
|
285
|
+
// • respects an explicit opt-out (ledger.disabled — set by `ambient off` or
|
|
286
|
+
// declining the consent prompt); never re-enables behind the user's back.
|
|
287
|
+
// • no-op once it has run (autoEnabledAt) — the per-run self-heal keeps files
|
|
288
|
+
// fresh after that.
|
|
289
|
+
// • if NO harness is installed yet, does nothing and re-checks next run.
|
|
290
|
+
// Transparent: prints a one-time notice naming what changed and how to undo —
|
|
291
|
+
// informed consent after the fact, fully reversible. Never throws/blocks.
|
|
292
|
+
async function ensureAuto() {
|
|
293
|
+
const ledger = loadLedger();
|
|
294
|
+
const interacted = ledger.disabled || ledger.autoEnabledAt ||
|
|
295
|
+
(ledger.applied && (ledger.applied['claude-code'] || ledger.applied.globalBase));
|
|
296
|
+
if (interacted) return;
|
|
297
|
+
|
|
298
|
+
const harnesses = listHarnesses();
|
|
299
|
+
const installed = harnesses.filter(h => h.installed);
|
|
300
|
+
if (installed.length === 0) return; // nothing to enhance yet — try again next run
|
|
301
|
+
|
|
302
|
+
const hasClaude = installed.some(h => h.id === 'claude-code');
|
|
303
|
+
const changes = hasClaude ? applyClaudeHooks() : [];
|
|
304
|
+
const { written } = selfheal.syncGlobalBaseFiles();
|
|
305
|
+
// Refresh existing project files only — first-run auto-enable must not dump
|
|
306
|
+
// context files into whatever repo the user happens to be standing in.
|
|
307
|
+
try { selfheal.syncContextFiles({ createMissing: false }); } catch { /* best-effort */ }
|
|
308
|
+
|
|
309
|
+
const now = new Date().toISOString();
|
|
310
|
+
if (hasClaude) {
|
|
311
|
+
ledger.applied['claude-code'] = {
|
|
312
|
+
at: now, file: CLAUDE_SETTINGS, changes,
|
|
313
|
+
captures: '~/.phewsh/ambient-sessions.jsonl — timestamp, project, cwd only',
|
|
314
|
+
undo: 'phewsh ambient off',
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (written.length > 0) {
|
|
318
|
+
ledger.applied.globalBase = { at: now, files: written, undo: 'phewsh ambient off' };
|
|
319
|
+
}
|
|
320
|
+
ledger.disabled = false;
|
|
321
|
+
ledger.autoEnabledAt = now;
|
|
322
|
+
ledger.seenHarnesses = installed.map(h => h.id);
|
|
323
|
+
saveLedger(ledger);
|
|
324
|
+
|
|
325
|
+
if ((changes.length || written.length) && process.stdout.isTTY) {
|
|
326
|
+
console.log('');
|
|
327
|
+
console.log(` ${b(cream('phewsh set itself up across your AI tools'))} ${sage('— so they stay in sync with your project intent.')}`);
|
|
328
|
+
if (hasClaude) console.log(` ${teal('+')} ${slate('Claude Code gets a brief from a project\'s .intent/ at session start')}`);
|
|
329
|
+
written.forEach(f => console.log(` ${teal('+')} ${slate('environment note added to ' + f)}`));
|
|
330
|
+
console.log(` ${sage('In any project with')} ${cream('.intent/')}${sage(', your tools now read its real intent. This is reversible:')} ${cream('phewsh ambient off')}`);
|
|
331
|
+
console.log('');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
274
335
|
async function main() {
|
|
275
336
|
const sub = process.argv[3] || 'status';
|
|
276
337
|
const skipConfirm = process.argv.includes('--yes');
|
|
@@ -280,3 +341,4 @@ async function main() {
|
|
|
280
341
|
}
|
|
281
342
|
|
|
282
343
|
module.exports = main;
|
|
344
|
+
module.exports.ensureAuto = ensureAuto;
|
package/lib/selfheal.js
CHANGED
|
@@ -151,7 +151,12 @@ function removeBlock(filePath) {
|
|
|
151
151
|
|
|
152
152
|
// Build the phewsh block once (from .intent/ via the sequencer) and write it
|
|
153
153
|
// into every target tool's context file with a signed, timestamped footer.
|
|
154
|
-
|
|
154
|
+
// createMissing:false = refresh-only. Used by per-launch self-heal so merely
|
|
155
|
+
// opening phewsh in a repo never CREATES context files (which would dirty a
|
|
156
|
+
// clean tree unexpectedly) — it only keeps already-present ones fresh. File
|
|
157
|
+
// creation stays with deliberate acts (a real session's SessionEnd, /reconcile,
|
|
158
|
+
// `seq`), where the user is actually working in the project.
|
|
159
|
+
function syncContextFiles({ cwd = process.cwd(), targets = TARGET_FILES, createMissing = true } = {}) {
|
|
155
160
|
try {
|
|
156
161
|
const intentDir = path.join(cwd, '.intent');
|
|
157
162
|
if (!fs.existsSync(intentDir)) return { synced: [], reason: 'no-intent' };
|
|
@@ -181,7 +186,9 @@ function syncContextFiles({ cwd = process.cwd(), targets = TARGET_FILES } = {})
|
|
|
181
186
|
const footer = `> — synced by phewsh 😮💨🤫 · ${stamp}${head ? ' · ' + head : ''}`;
|
|
182
187
|
const synced = [];
|
|
183
188
|
for (const file of targets) {
|
|
184
|
-
|
|
189
|
+
const fp = path.join(cwd, file);
|
|
190
|
+
if (!createMissing && !fs.existsSync(fp)) continue; // refresh-only: don't seed a clean repo
|
|
191
|
+
if (upsertBlock(fp, core, footer, file)) synced.push(file);
|
|
185
192
|
}
|
|
186
193
|
return { synced };
|
|
187
194
|
} catch (err) { return { synced: [], reason: err && err.message ? err.message : 'error' }; }
|