phewsh 0.12.2 → 0.12.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 +28 -0
- package/commands/ai.js +44 -16
- package/commands/serve.js +3 -14
- package/lib/harnesses.js +67 -0
- package/lib/supabase.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,34 @@ phewsh # enter the interactive shell
|
|
|
15
15
|
phewsh serve # start live execution bridge for phewsh.com/intent
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## No API key needed
|
|
19
|
+
|
|
20
|
+
phewsh is not another agent — it's the layer that uses the ones you already
|
|
21
|
+
have. If Claude Code, Codex CLI, Gemini CLI, Cursor Agent, or OpenCode is
|
|
22
|
+
installed, phewsh runs through it on **its own login** (your Claude
|
|
23
|
+
subscription, ChatGPT plan, Google account):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
phewsh ai run "what should I build next?" # auto-uses an installed agent CLI
|
|
27
|
+
phewsh ai run -p codex "..." # pin one explicitly
|
|
28
|
+
phewsh ai providers # see what's installed
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
API keys (OpenRouter, Anthropic, Groq, …) and PHEWSH pooled credits remain
|
|
32
|
+
available as alternatives — `phewsh login --set-key`.
|
|
33
|
+
|
|
34
|
+
## Two packages, one system
|
|
35
|
+
|
|
36
|
+
- **`phewsh`** (this package) — the CLI: intent authoring, sync, bridges,
|
|
37
|
+
receipts, dispatch.
|
|
38
|
+
- **`phewsh-mcp-server`** — the MCP server that `phewsh mcp setup` wires into
|
|
39
|
+
Claude Code / Cursor / any MCP client. It gives an *interactive* agent
|
|
40
|
+
session your project's briefing, task queue, and enforcement gate over
|
|
41
|
+
stdio, and shares state with the bridges (`~/.phewsh/`).
|
|
42
|
+
|
|
43
|
+
Rule of thumb: `phewsh serve` = dispatch tasks *to* your agents.
|
|
44
|
+
`phewsh mcp setup` = your agents pull tasks *from* PHEWSH mid-session.
|
|
45
|
+
|
|
18
46
|
## Live Execution
|
|
19
47
|
|
|
20
48
|
Connect the web app to your local machine for real-time task execution:
|
package/commands/ai.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
getProvider, listProviders, detectProvider,
|
|
7
7
|
buildHeaders, buildBody, getUrl, streamParser,
|
|
8
8
|
} = require('../lib/providers');
|
|
9
|
+
const { HARNESSES, detectInstalled, listHarnesses, runViaHarness } = require('../lib/harnesses');
|
|
9
10
|
|
|
10
11
|
const CONFIG_PATH = path.join(os.homedir(), '.phewsh', 'config.json');
|
|
11
12
|
const INTENT_DIR = path.join(process.cwd(), '.intent');
|
|
@@ -170,16 +171,25 @@ async function main() {
|
|
|
170
171
|
|
|
171
172
|
Examples:
|
|
172
173
|
phewsh ai run "what should I build next?"
|
|
174
|
+
phewsh ai run -p claude-code "use my Claude subscription — no API key"
|
|
175
|
+
phewsh ai run -p codex "use my ChatGPT plan — no API key"
|
|
173
176
|
phewsh ai run -p openrouter "compare these approaches"
|
|
174
|
-
phewsh ai run -p groq -m llama-3.3-70b-versatile "quick question"
|
|
175
177
|
phewsh ai run -p ollama "local inference, no API key needed"
|
|
178
|
+
|
|
179
|
+
No API key? phewsh automatically runs through an installed agent CLI
|
|
180
|
+
(Claude Code, Codex, Gemini, Cursor, OpenCode) using its own login.
|
|
176
181
|
`);
|
|
177
182
|
return;
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
if (subcommand === 'providers') {
|
|
181
186
|
const config = loadConfig() || {};
|
|
182
|
-
console.log('\n
|
|
187
|
+
console.log('\n Subscription harnesses (no API key — they carry their own login):\n');
|
|
188
|
+
for (const h of listHarnesses()) {
|
|
189
|
+
const status = h.installed ? '\x1b[32minstalled\x1b[0m' : '\x1b[2mnot installed\x1b[0m';
|
|
190
|
+
console.log(` ${h.id.padEnd(12)} ${h.label.padEnd(26)} ${status} (${h.auth})`);
|
|
191
|
+
}
|
|
192
|
+
console.log('\n API providers:\n');
|
|
183
193
|
for (const p of listProviders()) {
|
|
184
194
|
const isDefault = (config.defaultProvider || 'anthropic') === p.id;
|
|
185
195
|
const hasKey = !!(config.providerKeys?.[p.id] || (p.keyEnvVar && process.env[p.keyEnvVar]));
|
|
@@ -222,30 +232,48 @@ async function main() {
|
|
|
222
232
|
process.exit(1);
|
|
223
233
|
}
|
|
224
234
|
|
|
225
|
-
const config = loadConfig();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
235
|
+
const config = loadConfig() || {};
|
|
236
|
+
const intentFiles = loadIntentContext();
|
|
237
|
+
const systemPrompt = buildSystemPrompt(intentFiles);
|
|
238
|
+
const contextLine = intentFiles.length > 0
|
|
239
|
+
? `\n Context: ${intentFiles.map(f => f.file).join(', ')}`
|
|
240
|
+
: '\n No .intent/ found — running without project context';
|
|
241
|
+
|
|
242
|
+
// Harnesses double as providers: they carry their own auth (your Claude /
|
|
243
|
+
// ChatGPT / Google subscription), so no API key is needed in phewsh.
|
|
244
|
+
const requested = flags.provider || config.defaultProvider;
|
|
245
|
+
if (requested && HARNESSES[requested]) {
|
|
246
|
+
console.log(contextLine);
|
|
247
|
+
console.log(` Provider: ${HARNESSES[requested].label} (your ${HARNESSES[requested].auth} — no API key)`);
|
|
248
|
+
await runViaHarness(requested, systemPrompt, prompt);
|
|
249
|
+
return;
|
|
229
250
|
}
|
|
230
251
|
|
|
231
252
|
const provider = resolveProvider(config, flags.provider);
|
|
232
253
|
const model = flags.model || config.providerModels?.[provider.id] || provider.defaultModel;
|
|
233
|
-
|
|
234
254
|
const apiKey = resolveApiKey(config, provider);
|
|
255
|
+
|
|
256
|
+
// No key? Fall back to an installed harness instead of erroring — that's
|
|
257
|
+
// what platform-agnostic means in practice.
|
|
258
|
+
if (!apiKey && !provider.noKey && !flags.provider) {
|
|
259
|
+
const harness = detectInstalled();
|
|
260
|
+
if (harness) {
|
|
261
|
+
console.log(contextLine);
|
|
262
|
+
console.log(` Provider: ${HARNESSES[harness].label} (your ${HARNESSES[harness].auth} — no API key)`);
|
|
263
|
+
console.log(` \x1b[2mTip: pin it with \`phewsh ai run -p ${harness}\` or add an API key via \`phewsh login --set-key\`\x1b[0m`);
|
|
264
|
+
await runViaHarness(harness, systemPrompt, prompt);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
235
269
|
if (!apiKey && !provider.noKey) {
|
|
236
270
|
console.error(`\n No API key for ${provider.name}.`);
|
|
237
|
-
console.error(` Run \`phewsh login --set-key
|
|
271
|
+
console.error(` Run \`phewsh login --set-key\`, set ${provider.keyEnvVar},`);
|
|
272
|
+
console.error(` or use an installed agent CLI: phewsh ai run -p claude-code|codex|gemini|cursor|opencode\n`);
|
|
238
273
|
process.exit(1);
|
|
239
274
|
}
|
|
240
275
|
|
|
241
|
-
|
|
242
|
-
const systemPrompt = buildSystemPrompt(intentFiles);
|
|
243
|
-
|
|
244
|
-
if (intentFiles.length > 0) {
|
|
245
|
-
console.log(`\n Context: ${intentFiles.map(f => f.file).join(', ')}`);
|
|
246
|
-
} else {
|
|
247
|
-
console.log('\n No .intent/ found — running without project context');
|
|
248
|
-
}
|
|
276
|
+
console.log(contextLine);
|
|
249
277
|
console.log(` Provider: ${provider.name} | Model: ${model}`);
|
|
250
278
|
|
|
251
279
|
await streamResponse(config, provider, model, systemPrompt, prompt);
|
package/commands/serve.js
CHANGED
|
@@ -35,27 +35,16 @@ function getPort() {
|
|
|
35
35
|
|
|
36
36
|
// ─── Runtime Detection ─────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
// Harness runners —
|
|
38
|
+
// Harness runners — shared table in lib/harnesses.js. PHEWSH is not a
|
|
39
39
|
// harness; it's the layer that dispatches to whichever harnesses you have.
|
|
40
40
|
// Detection is honest: a runtime is only "connected" if its binary is on PATH.
|
|
41
|
-
const RUNNERS =
|
|
42
|
-
'claude-code': { bin: 'claude', label: 'Claude Code', args: (p) => ['-p', p, '--output-format', 'text'] },
|
|
43
|
-
'codex': { bin: 'codex', label: 'Codex CLI', args: (p) => ['exec', p] },
|
|
44
|
-
'gemini': { bin: 'gemini', label: 'Gemini CLI', args: (p) => ['-p', p] },
|
|
45
|
-
'cursor': { bin: 'cursor-agent', label: 'Cursor Agent', args: (p) => ['-p', p, '--output-format', 'text'] },
|
|
46
|
-
'opencode': { bin: 'opencode', label: 'OpenCode', args: (p) => ['run', p] },
|
|
47
|
-
};
|
|
41
|
+
const { HARNESSES: RUNNERS, isInstalled } = require('../lib/harnesses');
|
|
48
42
|
|
|
49
43
|
function detectRuntimes() {
|
|
50
44
|
const runtimes = [];
|
|
51
45
|
|
|
52
46
|
for (const [id, r] of Object.entries(RUNNERS)) {
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
execSync(`which ${r.bin}`, { stdio: 'pipe' });
|
|
56
|
-
connected = true;
|
|
57
|
-
} catch { /* not installed */ }
|
|
58
|
-
runtimes.push({ id, label: r.label, connected });
|
|
47
|
+
runtimes.push({ id, label: r.label, connected: isInstalled(id) });
|
|
59
48
|
}
|
|
60
49
|
|
|
61
50
|
// Always report human as available
|
package/lib/harnesses.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Agent harnesses installed on this machine, and how to invoke them headlessly.
|
|
2
|
+
//
|
|
3
|
+
// PHEWSH is not a harness — it's the layer that uses the ones you already
|
|
4
|
+
// have. Each of these carries its OWN auth (Claude Code uses your Claude
|
|
5
|
+
// subscription, Codex your ChatGPT plan, Gemini your Google login), so going
|
|
6
|
+
// through them needs no API key in phewsh at all.
|
|
7
|
+
//
|
|
8
|
+
// Used by:
|
|
9
|
+
// phewsh serve — web-dispatched jobs execute through these
|
|
10
|
+
// phewsh ai — harnesses double as no-key providers
|
|
11
|
+
|
|
12
|
+
const { execSync, spawn } = require('child_process');
|
|
13
|
+
|
|
14
|
+
const HARNESSES = {
|
|
15
|
+
'claude-code': { bin: 'claude', label: 'Claude Code', auth: 'Claude subscription / Console', args: (p) => ['-p', p, '--output-format', 'text'] },
|
|
16
|
+
'codex': { bin: 'codex', label: 'Codex CLI', auth: 'ChatGPT plan', args: (p) => ['exec', p] },
|
|
17
|
+
'gemini': { bin: 'gemini', label: 'Gemini CLI', auth: 'Google login', args: (p) => ['-p', p] },
|
|
18
|
+
'cursor': { bin: 'cursor-agent', label: 'Cursor Agent', auth: 'Cursor account', args: (p) => ['-p', p, '--output-format', 'text'] },
|
|
19
|
+
'opencode': { bin: 'opencode', label: 'OpenCode', auth: 'OpenCode Zen / configured', args: (p) => ['run', p] },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function isInstalled(id) {
|
|
23
|
+
const h = HARNESSES[id];
|
|
24
|
+
if (!h) return false;
|
|
25
|
+
try { execSync(`which ${h.bin}`, { stdio: 'pipe' }); return true; } catch { return false; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** First installed harness in preference order, or null. */
|
|
29
|
+
function detectInstalled() {
|
|
30
|
+
for (const id of Object.keys(HARNESSES)) {
|
|
31
|
+
if (isInstalled(id)) return id;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function listHarnesses() {
|
|
37
|
+
return Object.entries(HARNESSES).map(([id, h]) => ({ id, ...h, installed: isInstalled(id) }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Run a prompt through a harness, streaming stdout to the terminal.
|
|
42
|
+
* stderr is buffered and only surfaced on failure (codex/gemini chat on it).
|
|
43
|
+
*/
|
|
44
|
+
function runViaHarness(id, systemPrompt, userPrompt) {
|
|
45
|
+
const h = HARNESSES[id];
|
|
46
|
+
if (!h) return Promise.reject(new Error(`Unknown harness: ${id}`));
|
|
47
|
+
const prompt = systemPrompt ? `${systemPrompt}\n\n---\n\n${userPrompt}` : userPrompt;
|
|
48
|
+
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const child = spawn(h.bin, h.args(prompt), { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
51
|
+
// Some harnesses (codex exec, gemini) wait for stdin EOF before running.
|
|
52
|
+
child.stdin.end();
|
|
53
|
+
|
|
54
|
+
let stderr = '';
|
|
55
|
+
process.stdout.write('\n');
|
|
56
|
+
child.stdout.on('data', (d) => process.stdout.write(d));
|
|
57
|
+
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
58
|
+
child.on('close', (code) => {
|
|
59
|
+
process.stdout.write('\n');
|
|
60
|
+
if (code === 0) resolve();
|
|
61
|
+
else reject(new Error(`${h.label} exited ${code}${stderr ? `\n ${stderr.trim().split('\n').slice(-3).join('\n ')}` : ''}`));
|
|
62
|
+
});
|
|
63
|
+
child.on('error', (e) => reject(new Error(`Could not run ${h.bin}: ${e.message}`)));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { HARNESSES, isInstalled, detectInstalled, listHarnesses, runViaHarness };
|
package/lib/supabase.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Supabase REST client for the CLI — no SDK, just fetch (Node 18+ built-in)
|
|
2
2
|
|
|
3
3
|
const SUPABASE_URL = 'https://fpnpfnahwaztdlxuayyv.supabase.co';
|
|
4
|
-
const SUPABASE_ANON_KEY = '
|
|
4
|
+
const SUPABASE_ANON_KEY = 'sb_publishable_sL3R5aB43Yo5Ct0NQwB4fg_je9ccSHY';
|
|
5
5
|
|
|
6
6
|
async function req(path, options = {}, accessToken = null) {
|
|
7
7
|
const headers = {
|