phewsh 0.15.5 → 0.15.6
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/session.js +102 -12
- package/package.json +1 -1
package/commands/session.js
CHANGED
|
@@ -648,16 +648,22 @@ async function main() {
|
|
|
648
648
|
return estimateTokens(`${systemPrompt}\n${conversation}`);
|
|
649
649
|
}
|
|
650
650
|
|
|
651
|
+
// One quiet line, Claude Code-bar style: route + model · context gauge ·
|
|
652
|
+
// mode. Hints live in /help — the rail is glanceable state, not a manual.
|
|
651
653
|
function renderStatusRail() {
|
|
652
654
|
if (!process.stdout.isTTY) return;
|
|
653
655
|
const folder = relativeFolder(process.cwd(), os.homedir());
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const tokens =
|
|
659
|
-
|
|
660
|
-
|
|
656
|
+
const routeName = route?.type === 'harness' ? HARNESSES[route.id].label : routeLabel(route, config);
|
|
657
|
+
const model = route?.type === 'harness'
|
|
658
|
+
? (harnessModel || 'default model')
|
|
659
|
+
: modelName(currentModel);
|
|
660
|
+
const tokens = currentContextTokens();
|
|
661
|
+
const pct = Math.min(99, Math.round((tokens / 200000) * 100));
|
|
662
|
+
const bar = '█'.repeat(Math.max(1, Math.round(pct / 10))) + '░'.repeat(10 - Math.max(1, Math.round(pct / 10)));
|
|
663
|
+
const modeLabel = sessionMode
|
|
664
|
+
? Object.values(INTENT_MODES).find(m => m.id === sessionMode)?.label.toLowerCase()
|
|
665
|
+
: 'open';
|
|
666
|
+
console.log(` ${slate(folder)} ${slate('│')} ${cream(routeName)} ${slate(model)} ${slate('│')} ${slate(bar)} ${slate(pct + '%')} ${slate('│')} ${sage('⏵ ' + modeLabel)} ${slate('(shift+tab)')}`);
|
|
661
667
|
}
|
|
662
668
|
|
|
663
669
|
const readlinePrompt = rl.prompt.bind(rl);
|
|
@@ -715,9 +721,72 @@ async function main() {
|
|
|
715
721
|
};
|
|
716
722
|
}
|
|
717
723
|
|
|
724
|
+
// ── Bracketed paste: like Claude Code, a paste lands in the input line as
|
|
725
|
+
// a collapsed placeholder and NEVER auto-submits — Enter sends it. The
|
|
726
|
+
// terminal marks paste boundaries (\x1b[200~ … \x1b[201~); Node's keypress
|
|
727
|
+
// decoder surfaces them as paste-start/paste-end. While pasting we detach
|
|
728
|
+
// readline so embedded newlines can't fire 'line' events.
|
|
729
|
+
const PASTE_ON = '\x1b[?2004h';
|
|
730
|
+
const PASTE_OFF = '\x1b[?2004l';
|
|
731
|
+
let pasting = false;
|
|
732
|
+
let pasteChunks = [];
|
|
733
|
+
let detachedListeners = null;
|
|
734
|
+
let pasteCounter = 0;
|
|
735
|
+
const pendingPastes = new Map();
|
|
736
|
+
|
|
737
|
+
function pasteMode(on) {
|
|
738
|
+
if (process.stdout.isTTY) process.stdout.write(on ? PASTE_ON : PASTE_OFF);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Substitute placeholders back to the real pasted text at submit time.
|
|
742
|
+
function expandPastes(input) {
|
|
743
|
+
let out = input;
|
|
744
|
+
for (const [tag, text] of pendingPastes) {
|
|
745
|
+
if (out.includes(tag)) {
|
|
746
|
+
out = out.split(tag).join(text);
|
|
747
|
+
pendingPastes.delete(tag);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return out;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
let wasSpecialInput = false; // recolor must also fire when the token STOPS matching
|
|
754
|
+
|
|
718
755
|
if (process.stdin.isTTY) {
|
|
719
|
-
|
|
756
|
+
const phewshKeypress = (str, key) => {
|
|
720
757
|
try {
|
|
758
|
+
// Paste interception comes first — everything inside the paste is data.
|
|
759
|
+
if (key && key.name === 'paste-start') {
|
|
760
|
+
pasting = true;
|
|
761
|
+
pasteChunks = [];
|
|
762
|
+
detachedListeners = process.stdin.listeners('keypress').filter(l => l !== phewshKeypress);
|
|
763
|
+
for (const l of detachedListeners) process.stdin.removeListener('keypress', l);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (pasting) {
|
|
767
|
+
if (key && key.name === 'paste-end') {
|
|
768
|
+
pasting = false;
|
|
769
|
+
for (const l of detachedListeners || []) process.stdin.on('keypress', l);
|
|
770
|
+
detachedListeners = null;
|
|
771
|
+
const text = pasteChunks.join('');
|
|
772
|
+
pasteChunks = [];
|
|
773
|
+
if (!text) return;
|
|
774
|
+
const lineCount = text.split('\n').length;
|
|
775
|
+
if (lineCount > 1 || text.length > 200) {
|
|
776
|
+
pasteCounter++;
|
|
777
|
+
const tag = `[paste #${pasteCounter}: ${text.length.toLocaleString('en-US')} chars, ${lineCount} lines]`;
|
|
778
|
+
pendingPastes.set(tag, text);
|
|
779
|
+
lastPaste = text;
|
|
780
|
+
rl.write(tag);
|
|
781
|
+
} else {
|
|
782
|
+
rl.write(text);
|
|
783
|
+
}
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
pasteChunks.push(str !== undefined && str !== null ? String(str) : (key && key.sequence) || '');
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
721
790
|
if (key?.ctrl && key.name === 'o' && lastPaste) {
|
|
722
791
|
setImmediate(() => {
|
|
723
792
|
rl.line = '';
|
|
@@ -730,6 +799,17 @@ async function main() {
|
|
|
730
799
|
});
|
|
731
800
|
return;
|
|
732
801
|
}
|
|
802
|
+
// shift+tab cycles the session mode — open → build → research → decide → review
|
|
803
|
+
if (key && key.name === 'tab' && key.shift) {
|
|
804
|
+
const ids = [null, ...Object.values(INTENT_MODES).map(m => m.id)];
|
|
805
|
+
const next = ids[(ids.indexOf(sessionMode) + 1) % ids.length];
|
|
806
|
+
sessionMode = next;
|
|
807
|
+
const label = next ? Object.values(INTENT_MODES).find(m => m.id === next).label : 'Open';
|
|
808
|
+
process.stdout.write('\x1b[2K\r');
|
|
809
|
+
console.log(` ${teal('⏵')} ${sage('mode:')} ${cream(label.toLowerCase())}${next ? slate(' — ' + 'shapes how routes respond') : slate(' — no slant')}`);
|
|
810
|
+
rl.prompt();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
733
813
|
// ESC: cancel an in-flight turn, or clear the input line.
|
|
734
814
|
if (key && key.name === 'escape') {
|
|
735
815
|
if (turnInFlight) {
|
|
@@ -744,17 +824,23 @@ async function main() {
|
|
|
744
824
|
}
|
|
745
825
|
return;
|
|
746
826
|
}
|
|
747
|
-
// Re-render so token coloring tracks edits
|
|
748
|
-
// stops matching
|
|
827
|
+
// Re-render so token coloring tracks edits — including the keystroke
|
|
828
|
+
// where the token stops matching and must un-color.
|
|
749
829
|
const cur = rl.line || '';
|
|
750
|
-
|
|
830
|
+
const special = cur[0] === '/' || cur[0] === '@';
|
|
831
|
+
if (special || wasSpecialInput) rl._refreshLine();
|
|
832
|
+
wasSpecialInput = special;
|
|
751
833
|
} catch { /* never break input */ }
|
|
752
|
-
}
|
|
834
|
+
};
|
|
835
|
+
process.stdin.prependListener('keypress', phewshKeypress);
|
|
836
|
+
pasteMode(true);
|
|
837
|
+
process.on('exit', () => pasteMode(false)); // never leave the terminal in paste mode
|
|
753
838
|
}
|
|
754
839
|
|
|
755
840
|
rl.prompt();
|
|
756
841
|
|
|
757
842
|
async function handleInput(input) {
|
|
843
|
+
input = expandPastes(input);
|
|
758
844
|
|
|
759
845
|
// A bare number right after a route failure picks the fallback
|
|
760
846
|
if (awaitingFallback) {
|
|
@@ -1452,10 +1538,12 @@ async function main() {
|
|
|
1452
1538
|
console.log('');
|
|
1453
1539
|
console.log(` ${teal('●')} ${sage('Handing you to the guided update')} ${slate('— exit to come back to phewsh')}`);
|
|
1454
1540
|
console.log('');
|
|
1541
|
+
pasteMode(false);
|
|
1455
1542
|
rl.pause();
|
|
1456
1543
|
const { spawnSync } = require('child_process');
|
|
1457
1544
|
spawnSync(process.execPath, [path.join(__dirname, '..', 'bin', 'phewsh.js'), 'clarify'], { stdio: 'inherit' });
|
|
1458
1545
|
rl.resume();
|
|
1546
|
+
pasteMode(true);
|
|
1459
1547
|
console.log('');
|
|
1460
1548
|
console.log(` ${teal('●')} ${sage('Back in phewsh.')} ${slate('/intent view to see the result — agents pick it up automatically')}`);
|
|
1461
1549
|
console.log('');
|
|
@@ -1764,10 +1852,12 @@ async function main() {
|
|
|
1764
1852
|
console.log(` ${slate('your .intent/ context rides along via CLAUDE.md')}`);
|
|
1765
1853
|
}
|
|
1766
1854
|
console.log('');
|
|
1855
|
+
pasteMode(false);
|
|
1767
1856
|
rl.pause();
|
|
1768
1857
|
const { spawnSync } = require('child_process');
|
|
1769
1858
|
const res = spawnSync(h.bin, [], { stdio: 'inherit' });
|
|
1770
1859
|
rl.resume();
|
|
1860
|
+
pasteMode(true);
|
|
1771
1861
|
recordSessionEvent(target, projectName, 'task_complete', {
|
|
1772
1862
|
taskId: decisionId, success: res.status === 0, summary: `interactive ${h.label} session`,
|
|
1773
1863
|
});
|