phewsh 0.15.5 → 0.15.7
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 +113 -13
- 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,75 @@ 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 chars = text.length.toLocaleString('en-US');
|
|
778
|
+
const tag = lineCount > 1
|
|
779
|
+
? `[paste #${pasteCounter}: ${chars} chars, ${lineCount} lines]`
|
|
780
|
+
: `[paste #${pasteCounter}: ${chars} chars]`;
|
|
781
|
+
pendingPastes.set(tag, text);
|
|
782
|
+
lastPaste = text;
|
|
783
|
+
rl.write(tag);
|
|
784
|
+
} else {
|
|
785
|
+
rl.write(text);
|
|
786
|
+
}
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
pasteChunks.push(str !== undefined && str !== null ? String(str) : (key && key.sequence) || '');
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
721
793
|
if (key?.ctrl && key.name === 'o' && lastPaste) {
|
|
722
794
|
setImmediate(() => {
|
|
723
795
|
rl.line = '';
|
|
@@ -730,6 +802,17 @@ async function main() {
|
|
|
730
802
|
});
|
|
731
803
|
return;
|
|
732
804
|
}
|
|
805
|
+
// shift+tab cycles the session mode — open → build → research → decide → review
|
|
806
|
+
if (key && key.name === 'tab' && key.shift) {
|
|
807
|
+
const ids = [null, ...Object.values(INTENT_MODES).map(m => m.id)];
|
|
808
|
+
const next = ids[(ids.indexOf(sessionMode) + 1) % ids.length];
|
|
809
|
+
sessionMode = next;
|
|
810
|
+
const label = next ? Object.values(INTENT_MODES).find(m => m.id === next).label : 'Open';
|
|
811
|
+
process.stdout.write('\x1b[2K\r');
|
|
812
|
+
console.log(` ${teal('⏵')} ${sage('mode:')} ${cream(label.toLowerCase())}${next ? slate(' — ' + 'shapes how routes respond') : slate(' — no slant')}`);
|
|
813
|
+
rl.prompt();
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
733
816
|
// ESC: cancel an in-flight turn, or clear the input line.
|
|
734
817
|
if (key && key.name === 'escape') {
|
|
735
818
|
if (turnInFlight) {
|
|
@@ -744,17 +827,30 @@ async function main() {
|
|
|
744
827
|
}
|
|
745
828
|
return;
|
|
746
829
|
}
|
|
747
|
-
// Re-render so token coloring tracks edits
|
|
748
|
-
// stops matching
|
|
749
|
-
|
|
750
|
-
|
|
830
|
+
// Re-render so token coloring tracks edits — including the keystroke
|
|
831
|
+
// where the token stops matching and must un-color. Deferred one tick:
|
|
832
|
+
// this is a prependListener, so readline hasn't appended the just-typed
|
|
833
|
+
// char yet; without the defer, rl.line is stale by one and /model only
|
|
834
|
+
// ever evaluates as /mode.
|
|
835
|
+
setImmediate(() => {
|
|
836
|
+
try {
|
|
837
|
+
const cur = rl.line || '';
|
|
838
|
+
const special = cur[0] === '/' || cur[0] === '@';
|
|
839
|
+
if (special || wasSpecialInput) rl._refreshLine();
|
|
840
|
+
wasSpecialInput = special;
|
|
841
|
+
} catch { /* never break input */ }
|
|
842
|
+
});
|
|
751
843
|
} catch { /* never break input */ }
|
|
752
|
-
}
|
|
844
|
+
};
|
|
845
|
+
process.stdin.prependListener('keypress', phewshKeypress);
|
|
846
|
+
pasteMode(true);
|
|
847
|
+
process.on('exit', () => pasteMode(false)); // never leave the terminal in paste mode
|
|
753
848
|
}
|
|
754
849
|
|
|
755
850
|
rl.prompt();
|
|
756
851
|
|
|
757
852
|
async function handleInput(input) {
|
|
853
|
+
input = expandPastes(input);
|
|
758
854
|
|
|
759
855
|
// A bare number right after a route failure picks the fallback
|
|
760
856
|
if (awaitingFallback) {
|
|
@@ -1452,10 +1548,12 @@ async function main() {
|
|
|
1452
1548
|
console.log('');
|
|
1453
1549
|
console.log(` ${teal('●')} ${sage('Handing you to the guided update')} ${slate('— exit to come back to phewsh')}`);
|
|
1454
1550
|
console.log('');
|
|
1551
|
+
pasteMode(false);
|
|
1455
1552
|
rl.pause();
|
|
1456
1553
|
const { spawnSync } = require('child_process');
|
|
1457
1554
|
spawnSync(process.execPath, [path.join(__dirname, '..', 'bin', 'phewsh.js'), 'clarify'], { stdio: 'inherit' });
|
|
1458
1555
|
rl.resume();
|
|
1556
|
+
pasteMode(true);
|
|
1459
1557
|
console.log('');
|
|
1460
1558
|
console.log(` ${teal('●')} ${sage('Back in phewsh.')} ${slate('/intent view to see the result — agents pick it up automatically')}`);
|
|
1461
1559
|
console.log('');
|
|
@@ -1764,10 +1862,12 @@ async function main() {
|
|
|
1764
1862
|
console.log(` ${slate('your .intent/ context rides along via CLAUDE.md')}`);
|
|
1765
1863
|
}
|
|
1766
1864
|
console.log('');
|
|
1865
|
+
pasteMode(false);
|
|
1767
1866
|
rl.pause();
|
|
1768
1867
|
const { spawnSync } = require('child_process');
|
|
1769
1868
|
const res = spawnSync(h.bin, [], { stdio: 'inherit' });
|
|
1770
1869
|
rl.resume();
|
|
1870
|
+
pasteMode(true);
|
|
1771
1871
|
recordSessionEvent(target, projectName, 'task_complete', {
|
|
1772
1872
|
taskId: decisionId, success: res.status === 0, summary: `interactive ${h.label} session`,
|
|
1773
1873
|
});
|