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 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
- // Propagate any global-base-file guidance fixes for users who opted in
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
- // Keep every tool's context file current before handing off — so a user who
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);
@@ -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
- console.log(` ${sage('Nothing changed.')}`);
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
- function syncContextFiles({ cwd = process.cwd(), targets = TARGET_FILES } = {}) {
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
- if (upsertBlock(path.join(cwd, file), core, footer, file)) synced.push(file);
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' }; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.15.40",
3
+ "version": "0.15.41",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"