phewsh 0.15.3 → 0.15.4
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/README.md +11 -0
- package/commands/session.js +55 -1
- package/lib/session-display.js +54 -0
- package/lib/session-input.js +2 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -84,6 +84,17 @@ Inside the shell:
|
|
|
84
84
|
/help All commands
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
The prompt includes a compact status rail with the current folder, route,
|
|
88
|
+
loaded `.intent/` files, and approximate context size. Multi-line pastes and
|
|
89
|
+
single-line pastes of 300+ characters collapse after submission:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
[pasted 1,284 chars · 12 lines · Ctrl+O to expand]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Press `Ctrl+O` to inspect the last submitted paste and `Esc` to clear input or
|
|
96
|
+
cancel an in-flight provider turn.
|
|
97
|
+
|
|
87
98
|
## What it does
|
|
88
99
|
|
|
89
100
|
Creates three structured artifacts in `.intent/`:
|
package/commands/session.js
CHANGED
|
@@ -22,6 +22,14 @@ const { recordDecision, labelOutcome, pendingDecisions, outcomeStats, OUTCOMES }
|
|
|
22
22
|
const { recordSessionEvent } = require('../lib/receipts-data');
|
|
23
23
|
const configFile = require('../lib/config-file');
|
|
24
24
|
const { createFailureTracker, createLineDispatcher } = require('../lib/session-input');
|
|
25
|
+
const {
|
|
26
|
+
echoedRows,
|
|
27
|
+
estimateTokens,
|
|
28
|
+
formatPasteSummary,
|
|
29
|
+
formatTokenCount,
|
|
30
|
+
relativeFolder,
|
|
31
|
+
shouldCollapsePaste,
|
|
32
|
+
} = require('../lib/session-display');
|
|
25
33
|
const { recordProject, listProjects, scanForProjects, fmtAgo } = require('../lib/projects-index');
|
|
26
34
|
|
|
27
35
|
// Brand palette shortcuts
|
|
@@ -631,6 +639,39 @@ async function main() {
|
|
|
631
639
|
prompt: ` ${teal('phewsh')} ${sage('>')} `,
|
|
632
640
|
historySize: 100,
|
|
633
641
|
});
|
|
642
|
+
const promptText = ` phewsh > `;
|
|
643
|
+
let lastPaste = null;
|
|
644
|
+
|
|
645
|
+
function currentContextTokens() {
|
|
646
|
+
const conversation = messages.map(message => message.content).join('\n');
|
|
647
|
+
return estimateTokens(`${systemPrompt}\n${conversation}`);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function renderStatusRail() {
|
|
651
|
+
if (!process.stdout.isTTY) return;
|
|
652
|
+
const folder = relativeFolder(process.cwd(), os.homedir());
|
|
653
|
+
const contextLabel = intentFiles.length > 0
|
|
654
|
+
? `${intentFiles.length} intent file${intentFiles.length === 1 ? '' : 's'}`
|
|
655
|
+
: 'no project context';
|
|
656
|
+
const routeName = routeLabel(route, config);
|
|
657
|
+
const tokens = formatTokenCount(currentContextTokens());
|
|
658
|
+
console.log(` ${slate(folder)} ${slate('·')} ${cream(routeName)} ${slate('·')} ${sage(contextLabel)} ${slate('·')} ${sage(`~${tokens} ctx tokens`)}`);
|
|
659
|
+
console.log(` ${slate('/help commands · /use route · /context memory · Ctrl+O last paste · Esc cancel')}`);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const readlinePrompt = rl.prompt.bind(rl);
|
|
663
|
+
rl.prompt = function promptWithStatusRail(preserveCursor) {
|
|
664
|
+
renderStatusRail();
|
|
665
|
+
return readlinePrompt(preserveCursor);
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
function collapsePastedEcho(lines, input) {
|
|
669
|
+
if (!process.stdout.isTTY || !shouldCollapsePaste(lines, input)) return;
|
|
670
|
+
const rows = echoedRows(lines, promptText, process.stdout.columns || 80);
|
|
671
|
+
for (let i = 0; i < rows; i++) process.stdout.write('\x1b[1A\x1b[2K\r');
|
|
672
|
+
lastPaste = input;
|
|
673
|
+
console.log(` ${peach(formatPasteSummary(input, lines.length))}`);
|
|
674
|
+
}
|
|
634
675
|
|
|
635
676
|
// Live input coloring — like Claude Code: text stays normal, and only a
|
|
636
677
|
// RECOGNIZED leading /command (or @harness) token turns teal (peach for @)
|
|
@@ -674,8 +715,20 @@ async function main() {
|
|
|
674
715
|
}
|
|
675
716
|
|
|
676
717
|
if (process.stdin.isTTY) {
|
|
677
|
-
process.stdin.
|
|
718
|
+
process.stdin.prependListener('keypress', (str, key) => {
|
|
678
719
|
try {
|
|
720
|
+
if (key?.ctrl && key.name === 'o' && lastPaste) {
|
|
721
|
+
setImmediate(() => {
|
|
722
|
+
rl.line = '';
|
|
723
|
+
rl.cursor = 0;
|
|
724
|
+
process.stdout.write('\x1b[2K\r');
|
|
725
|
+
console.log(` ${b(cream('Last paste'))} ${slate(`(${lastPaste.length.toLocaleString('en-US')} chars)`)}`);
|
|
726
|
+
console.log(lastPaste.split('\n').map(line => ` ${line}`).join('\n'));
|
|
727
|
+
console.log('');
|
|
728
|
+
rl.prompt();
|
|
729
|
+
});
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
679
732
|
// ESC: cancel an in-flight turn, or clear the input line.
|
|
680
733
|
if (key && key.name === 'escape') {
|
|
681
734
|
if (turnInFlight) {
|
|
@@ -1827,6 +1880,7 @@ async function main() {
|
|
|
1827
1880
|
}
|
|
1828
1881
|
|
|
1829
1882
|
const lineDispatcher = createLineDispatcher(handleInput, {
|
|
1883
|
+
onBatch: ({ input, lines }) => collapsePastedEcho(lines, input),
|
|
1830
1884
|
onNoop: () => rl.prompt(),
|
|
1831
1885
|
onError: (err) => {
|
|
1832
1886
|
console.error(`\n ${ember('!')} ${sage('Input failed:')} ${err.message}`);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
4
|
+
|
|
5
|
+
function visibleLength(value) {
|
|
6
|
+
return String(value || '').replace(ANSI_RE, '').length;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function estimateTokens(value) {
|
|
10
|
+
return Math.max(0, Math.ceil(String(value || '').length / 4));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function formatTokenCount(tokens) {
|
|
14
|
+
if (tokens < 1000) return String(tokens);
|
|
15
|
+
return `${(tokens / 1000).toFixed(tokens < 10000 ? 1 : 0)}k`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function shouldCollapsePaste(lines, input, threshold = 300) {
|
|
19
|
+
return lines.length > 1 || input.length >= threshold;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatPasteSummary(input, lineCount) {
|
|
23
|
+
const chars = input.length.toLocaleString('en-US');
|
|
24
|
+
const lines = lineCount > 1 ? ` · ${lineCount} lines` : '';
|
|
25
|
+
return `[pasted ${chars} chars${lines} · Ctrl+O to expand]`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function echoedRows(lines, prompt, columns = 80) {
|
|
29
|
+
const width = Math.max(20, columns || 80);
|
|
30
|
+
return lines.reduce((total, line, index) => {
|
|
31
|
+
const prefix = index === 0 ? visibleLength(prompt) : 0;
|
|
32
|
+
return total + Math.max(1, Math.ceil((prefix + visibleLength(line)) / width));
|
|
33
|
+
}, 0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function relativeFolder(cwd, home) {
|
|
37
|
+
if (!cwd) return '';
|
|
38
|
+
const relative = home ? path.relative(home, cwd) : cwd;
|
|
39
|
+
if (home && relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
40
|
+
return `~/${relative}`;
|
|
41
|
+
}
|
|
42
|
+
if (home && relative === '') return '~';
|
|
43
|
+
return cwd;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
echoedRows,
|
|
48
|
+
estimateTokens,
|
|
49
|
+
formatPasteSummary,
|
|
50
|
+
formatTokenCount,
|
|
51
|
+
relativeFolder,
|
|
52
|
+
shouldCollapsePaste,
|
|
53
|
+
visibleLength,
|
|
54
|
+
};
|
package/lib/session-input.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
function createLineDispatcher(handleInput, {
|
|
2
2
|
onError = (err) => { throw err; },
|
|
3
3
|
onNoop = () => {},
|
|
4
|
+
onBatch = () => {},
|
|
4
5
|
schedule = setImmediate,
|
|
5
6
|
} = {}) {
|
|
6
7
|
let pendingLines = [];
|
|
@@ -16,6 +17,7 @@ function createLineDispatcher(handleInput, {
|
|
|
16
17
|
onNoop();
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
20
|
+
onBatch({ input, lines });
|
|
19
21
|
chain = chain.then(() => handleInput(input)).catch(onError);
|
|
20
22
|
}
|
|
21
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phewsh",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.4",
|
|
4
4
|
"description": "Turn intent into action. Structure your thinking, execute your next step.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"phewsh": "bin/phewsh.js"
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|
|
37
37
|
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "node --test test/*.test.js"
|
|
40
|
+
},
|
|
38
41
|
"dependencies": {
|
|
39
42
|
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
40
43
|
}
|