phewsh 0.12.1 → 0.12.3

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 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:
@@ -67,6 +95,7 @@ phewsh pull # Sync cloud to local
67
95
  phewsh mcp setup # Configure MCP server for agent connectivity
68
96
  phewsh mcp serve # Coordination bridge — route work to live agents
69
97
  phewsh receipts # Proof trail — what agents actually did, with evidence
98
+ phewsh update # Update phewsh to the latest version
70
99
  phewsh style # Build your style identity
71
100
  ```
72
101
 
package/bin/phewsh.js CHANGED
@@ -64,6 +64,7 @@ const COMMANDS = {
64
64
  watch: () => require('../commands/watch')(),
65
65
  mcp: () => require('../commands/mcp')(),
66
66
  receipts: () => require('../commands/receipts')(),
67
+ update: () => require('../commands/update')(),
67
68
  serve: () => require('../commands/serve')(),
68
69
  sequence: () => require('../commands/sequence')(),
69
70
  seq: () => require('../commands/sequence')(),
@@ -103,6 +104,7 @@ function showHelp() {
103
104
  console.log(` ${b(w('configure'))}`);
104
105
  console.log(` ${cyan('login')} ${g('Identity + API key + cloud sync')}`);
105
106
  console.log(` ${cyan('link')} ${g('Link local .intent/ to cloud project')}`);
107
+ console.log(` ${cyan('update')} ${g('Update phewsh to the latest version')}`);
106
108
  console.log('');
107
109
  console.log(` ${g('Works in: Claude Code · Cursor · ChatGPT · any MCP agent')}`);
108
110
  console.log(` ${g('No account needed. Account adds sync + sharing.')}`);
@@ -127,7 +129,7 @@ function checkForUpdates() {
127
129
  newer[2] > current[2];
128
130
  if (isNewer) {
129
131
  console.log(g(`\n Update available: ${pkg.version} → ${data.version}`));
130
- console.log(g(` Run: npm install -g phewsh\n`));
132
+ console.log(g(` Run: phewsh update\n`));
131
133
  }
132
134
  }
133
135
  })
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 Available providers:\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
- if (!config) {
227
- console.error('\n Not logged in. Run `phewsh login` first.\n');
228
- process.exit(1);
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\` or set ${provider.keyEnvVar}.\n`);
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
- const intentFiles = loadIntentContext();
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 — how to invoke each agent CLI headlessly. PHEWSH is not a
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
- let connected = false;
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
@@ -0,0 +1,79 @@
1
+ // phewsh update — update the CLI to the latest published version.
2
+ //
3
+ // Usage:
4
+ // phewsh update Check npm and install the latest version
5
+ // phewsh update --check Just compare versions, don't install
6
+
7
+ const { spawn } = require('child_process');
8
+
9
+ const b = (s) => `\x1b[1m${s}\x1b[0m`;
10
+ const w = (s) => `\x1b[97m${s}\x1b[0m`;
11
+ const g = (s) => `\x1b[38;2;130;142;138m${s}\x1b[0m`;
12
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
13
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
14
+
15
+ function isNewer(latest, current) {
16
+ const l = latest.split('.').map(Number);
17
+ const c = current.split('.').map(Number);
18
+ return l[0] !== c[0] ? l[0] > c[0] : l[1] !== c[1] ? l[1] > c[1] : l[2] > c[2];
19
+ }
20
+
21
+ async function main() {
22
+ const checkOnly = process.argv.includes('--check');
23
+ const pkg = require('../package.json');
24
+
25
+ console.log('');
26
+ console.log(` ${b(w('phewsh update'))}`);
27
+ console.log('');
28
+ console.log(` ${g('current:')} v${pkg.version}`);
29
+
30
+ let latest;
31
+ try {
32
+ const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, {
33
+ signal: AbortSignal.timeout(5000),
34
+ });
35
+ latest = (await res.json()).version;
36
+ } catch {
37
+ console.log(` ${yellow('Could not reach the npm registry. Try again later.')}`);
38
+ console.log('');
39
+ return;
40
+ }
41
+ console.log(` ${g('latest: ')} v${latest}`);
42
+ console.log('');
43
+
44
+ if (!latest || !isNewer(latest, pkg.version)) {
45
+ console.log(` ${green('Already up to date.')}`);
46
+ console.log('');
47
+ return;
48
+ }
49
+
50
+ if (checkOnly) {
51
+ console.log(` ${w(`v${latest} available.`)} Run ${w('phewsh update')} to install.`);
52
+ console.log('');
53
+ return;
54
+ }
55
+
56
+ console.log(` ${g('Installing')} ${w(`phewsh@${latest}`)} ${g('via npm…')}`);
57
+ console.log('');
58
+
59
+ const child = spawn('npm', ['install', '-g', `${pkg.name}@latest`], { stdio: 'inherit' });
60
+ child.on('close', (code) => {
61
+ console.log('');
62
+ if (code === 0) {
63
+ console.log(` ${green('✓')} Updated to v${latest}. New shells pick it up immediately.`);
64
+ } else {
65
+ console.log(` ${yellow('Update failed')} (npm exited ${code}).`);
66
+ console.log(` ${g('If it was a permissions error, fix your npm prefix or run:')}`);
67
+ console.log(` ${w(`sudo npm install -g ${pkg.name}@latest`)}`);
68
+ }
69
+ console.log('');
70
+ process.exit(code ?? 0);
71
+ });
72
+ child.on('error', (err) => {
73
+ console.log(` ${yellow('Could not run npm:')} ${err.message}`);
74
+ console.log('');
75
+ process.exit(1);
76
+ });
77
+ }
78
+
79
+ module.exports = main;
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"