phewsh 0.13.0 → 0.13.2
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 +11 -6
- package/commands/setup.js +28 -2
- package/lib/harnesses.js +13 -3
- package/package.json +1 -1
package/commands/session.js
CHANGED
|
@@ -107,14 +107,16 @@ const DEFAULT_MODEL = 'claude-sonnet';
|
|
|
107
107
|
// explicit config.defaultRoute → API key if set → first installed harness.
|
|
108
108
|
|
|
109
109
|
function resolveRoute(config, harnesses) {
|
|
110
|
-
|
|
110
|
+
// Chat routing needs a headless-capable harness; interactive-only ones
|
|
111
|
+
// (Hermes, Pi) are still detected and reachable via /work.
|
|
112
|
+
const chatCapable = harnesses.filter(h => h.installed && h.headless);
|
|
111
113
|
const preferred = config?.defaultRoute;
|
|
112
114
|
if (preferred === 'api' && config?.apiKey) return { type: 'api' };
|
|
113
|
-
if (preferred &&
|
|
115
|
+
if (preferred && chatCapable.some(h => h.id === preferred)) {
|
|
114
116
|
return { type: 'harness', id: preferred };
|
|
115
117
|
}
|
|
116
118
|
if (config?.apiKey) return { type: 'api' };
|
|
117
|
-
if (
|
|
119
|
+
if (chatCapable.length > 0) return { type: 'harness', id: chatCapable[0].id };
|
|
118
120
|
return null;
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -288,8 +290,8 @@ async function main() {
|
|
|
288
290
|
|
|
289
291
|
// Capabilities: what's installed on this machine, no setup required
|
|
290
292
|
if (installedHarnesses.length > 0) {
|
|
291
|
-
const caps =
|
|
292
|
-
|
|
293
|
+
const caps = installedHarnesses.map(h =>
|
|
294
|
+
`${teal('✓')} ${sage(h.label)}${h.headless ? '' : slate(' (/work)')}`
|
|
293
295
|
).join(slate(' · '));
|
|
294
296
|
console.log(` ${caps}`);
|
|
295
297
|
}
|
|
@@ -858,6 +860,8 @@ async function main() {
|
|
|
858
860
|
} else if (HARNESSES[target]) {
|
|
859
861
|
if (!harnesses.find(h => h.id === target)?.installed) {
|
|
860
862
|
console.log(` ${ember('!')} ${sage(HARNESSES[target].label + ' is not installed on this machine.')}`);
|
|
863
|
+
} else if (!HARNESSES[target].args) {
|
|
864
|
+
console.log(` ${sage(HARNESSES[target].label + ' is interactive-only — drop into it with')} ${cream('/work ' + target)} ${sage('(phewsh records the outcome when you return)')}`);
|
|
861
865
|
} else {
|
|
862
866
|
route = { type: 'harness', id: target };
|
|
863
867
|
console.log(` ${teal('●')} ${sage('Routing via')} ${cream(routeLabel(route, config))} ${slate('— no API key, your subscription')}`);
|
|
@@ -876,8 +880,9 @@ async function main() {
|
|
|
876
880
|
ui.divider('line');
|
|
877
881
|
for (const h of harnesses) {
|
|
878
882
|
const active = route?.type === 'harness' && route.id === h.id ? ` ${teal('● active')}` : '';
|
|
883
|
+
const mode = h.headless ? '' : slate(' · interactive (/work)');
|
|
879
884
|
const status = h.installed ? green('installed') : slate('not installed');
|
|
880
|
-
console.log(` ${cream(h.id.padEnd(12))} ${sage(h.label.padEnd(14))} ${status}${active}`);
|
|
885
|
+
console.log(` ${cream(h.id.padEnd(12))} ${sage(h.label.padEnd(14))} ${status}${mode}${active}`);
|
|
881
886
|
}
|
|
882
887
|
console.log('');
|
|
883
888
|
rl.prompt();
|
package/commands/setup.js
CHANGED
|
@@ -46,7 +46,8 @@ module.exports = async function setup() {
|
|
|
46
46
|
console.log(` ${b(cream('Detected on this machine'))}`);
|
|
47
47
|
for (const h of harnesses) {
|
|
48
48
|
const status = h.installed ? green('✓ installed') : slate('✗ not installed');
|
|
49
|
-
|
|
49
|
+
const mode = h.headless ? '' : slate(' · interactive — /work ' + h.id);
|
|
50
|
+
console.log(` ${cream(h.label.padEnd(14))} ${status} ${slate('(' + h.auth + ')')}${mode}`);
|
|
50
51
|
}
|
|
51
52
|
if (installed.length === 0) {
|
|
52
53
|
console.log('');
|
|
@@ -56,10 +57,35 @@ module.exports = async function setup() {
|
|
|
56
57
|
}
|
|
57
58
|
console.log('');
|
|
58
59
|
|
|
60
|
+
// Agent-run (no TTY): auto-configure instead of asking questions nobody
|
|
61
|
+
// can answer. Pick the first installed harness; humans can change it later.
|
|
62
|
+
const chatCapable = installed.filter(h => h.headless);
|
|
63
|
+
if (!process.stdin.isTTY) {
|
|
64
|
+
if (chatCapable.length > 0) {
|
|
65
|
+
config.defaultRoute = chatCapable[0].id;
|
|
66
|
+
saveConfig(config);
|
|
67
|
+
console.log(` ${teal('●')} ${sage('Auto-configured (non-interactive): default route =')} ${cream(chatCapable[0].label)} ${slate('— no API key needed')}`);
|
|
68
|
+
console.log(` ${slate('Change anytime: run `phewsh setup` in your own terminal, or /use inside a session.')}`);
|
|
69
|
+
} else if (config.apiKey) {
|
|
70
|
+
config.defaultRoute = 'api';
|
|
71
|
+
saveConfig(config);
|
|
72
|
+
console.log(` ${teal('●')} ${sage('Auto-configured: default route = API (existing key found)')}`);
|
|
73
|
+
} else {
|
|
74
|
+
console.log(` ${ember('!')} ${sage('Nothing to configure yet — no agent CLI installed and no API key.')}`);
|
|
75
|
+
console.log(` ${slate('Install Claude Code or Codex (or set a key), then rerun phewsh setup.')}`);
|
|
76
|
+
}
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(` ${sage('Start working:')} ${cream('phewsh')}`);
|
|
79
|
+
console.log('');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
60
84
|
|
|
61
85
|
// ── 2. Pick the default route ─────────────────────────
|
|
62
|
-
|
|
86
|
+
// Interactive-only harnesses (Hermes, Pi) can't take chat routing — they
|
|
87
|
+
// stay reachable via /work in a session, so they're not default options.
|
|
88
|
+
const options = chatCapable.map(h => ({ kind: 'harness', id: h.id, label: `${h.label} — your ${h.auth.split(' / ')[0].toLowerCase()}, no API key` }));
|
|
63
89
|
options.push({ kind: 'api', id: 'api', label: 'Direct API — bring your own Anthropic/OpenRouter key' });
|
|
64
90
|
|
|
65
91
|
console.log(` ${b(cream('Where should phewsh route your work by default?'))}`);
|
package/lib/harnesses.js
CHANGED
|
@@ -11,12 +11,21 @@
|
|
|
11
11
|
|
|
12
12
|
const { execSync, spawn } = require('child_process');
|
|
13
13
|
|
|
14
|
+
// args: how to run a one-shot prompt headlessly. args: null = we only know
|
|
15
|
+
// how to launch it interactively (detection + /work still fully supported —
|
|
16
|
+
// never guess flags; a wrong invocation looks like phewsh being broken).
|
|
14
17
|
const HARNESSES = {
|
|
15
18
|
'claude-code': { bin: 'claude', label: 'Claude Code', auth: 'Claude subscription / Console', args: (p) => ['-p', p, '--output-format', 'text'] },
|
|
16
19
|
'codex': { bin: 'codex', label: 'Codex CLI', auth: 'ChatGPT plan', args: (p) => ['exec', p] },
|
|
17
20
|
'gemini': { bin: 'gemini', label: 'Gemini CLI', auth: 'Google login', args: (p) => ['-p', p] },
|
|
18
21
|
'cursor': { bin: 'cursor-agent', label: 'Cursor Agent', auth: 'Cursor account', args: (p) => ['-p', p, '--output-format', 'text'] },
|
|
19
22
|
'opencode': { bin: 'opencode', label: 'OpenCode', auth: 'OpenCode Zen / configured', args: (p) => ['run', p] },
|
|
23
|
+
'hermes': { bin: 'hermes', label: 'Hermes', auth: 'Nous account', args: null },
|
|
24
|
+
'pi': { bin: 'pi', label: 'Pi', auth: 'Pi login', args: null },
|
|
25
|
+
'aider': { bin: 'aider', label: 'Aider', auth: 'configured keys', args: (p) => ['--message', p] },
|
|
26
|
+
'goose': { bin: 'goose', label: 'Goose', auth: 'Block / configured', args: (p) => ['run', '-t', p] },
|
|
27
|
+
'amp': { bin: 'amp', label: 'Amp', auth: 'Sourcegraph account', args: (p) => ['-x', p] },
|
|
28
|
+
'droid': { bin: 'droid', label: 'Droid', auth: 'Factory account', args: (p) => ['exec', p] },
|
|
20
29
|
};
|
|
21
30
|
|
|
22
31
|
function isInstalled(id) {
|
|
@@ -25,16 +34,16 @@ function isInstalled(id) {
|
|
|
25
34
|
try { execSync(`which ${h.bin}`, { stdio: 'pipe' }); return true; } catch { return false; }
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
/** First installed harness in preference order, or null. */
|
|
37
|
+
/** First installed chat-capable harness in preference order, or null. */
|
|
29
38
|
function detectInstalled() {
|
|
30
39
|
for (const id of Object.keys(HARNESSES)) {
|
|
31
|
-
if (isInstalled(id)) return id;
|
|
40
|
+
if (HARNESSES[id].args && isInstalled(id)) return id;
|
|
32
41
|
}
|
|
33
42
|
return null;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
function listHarnesses() {
|
|
37
|
-
return Object.entries(HARNESSES).map(([id, h]) => ({ id, ...h, installed: isInstalled(id) }));
|
|
46
|
+
return Object.entries(HARNESSES).map(([id, h]) => ({ id, ...h, headless: !!h.args, installed: isInstalled(id) }));
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
/**
|
|
@@ -45,6 +54,7 @@ function listHarnesses() {
|
|
|
45
54
|
function runViaHarness(id, systemPrompt, userPrompt) {
|
|
46
55
|
const h = HARNESSES[id];
|
|
47
56
|
if (!h) return Promise.reject(new Error(`Unknown harness: ${id}`));
|
|
57
|
+
if (!h.args) return Promise.reject(new Error(`${h.label} is interactive-only here — launch it with /work ${id}`));
|
|
48
58
|
const prompt = systemPrompt ? `${systemPrompt}\n\n---\n\n${userPrompt}` : userPrompt;
|
|
49
59
|
|
|
50
60
|
return new Promise((resolve, reject) => {
|