phewsh 0.11.16 → 0.12.0
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 +17 -0
- package/bin/phewsh.js +11 -2
- package/commands/ai.js +147 -44
- package/commands/browse.js +390 -0
- package/commands/context.js +11 -3
- package/commands/gate.js +72 -13
- package/commands/intent.js +8 -0
- package/commands/login.js +62 -7
- package/commands/mcp.js +88 -3
- package/commands/receipts.js +135 -0
- package/commands/serve.js +34 -0
- package/lib/anthropic-oauth.js +145 -0
- package/lib/providers.js +265 -0
- package/lib/receipts-data.js +151 -0
- package/lib/sequencer/emitters/claude-md.js +19 -5
- package/lib/ui.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,9 +65,26 @@ phewsh login # Authenticate + set API key
|
|
|
65
65
|
phewsh push # Sync local .intent/ to cloud
|
|
66
66
|
phewsh pull # Sync cloud to local
|
|
67
67
|
phewsh mcp setup # Configure MCP server for agent connectivity
|
|
68
|
+
phewsh mcp serve # Coordination bridge — route work to live agents
|
|
69
|
+
phewsh receipts # Proof trail — what agents actually did, with evidence
|
|
68
70
|
phewsh style # Build your style identity
|
|
69
71
|
```
|
|
70
72
|
|
|
73
|
+
## Receipts
|
|
74
|
+
|
|
75
|
+
Every task that runs through PHEWSH leaves evidence on your machine —
|
|
76
|
+
sessions, results, blockers, gate checks, spend — in `~/.phewsh/`. See it:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
phewsh receipts # merged timeline, newest first
|
|
80
|
+
phewsh receipts --json # machine-readable, for agents and scripts
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The same trail is visible at [phewsh.com/intent/receipts](https://phewsh.com/intent/receipts)
|
|
84
|
+
while a local bridge is running (`phewsh serve` or `phewsh mcp serve` — the
|
|
85
|
+
first executes tasks directly via Claude Code, the second routes them to
|
|
86
|
+
live connected agents; both record identical receipts).
|
|
87
|
+
|
|
71
88
|
## Sync
|
|
72
89
|
|
|
73
90
|
CLI and web ([phewsh.com/intent](https://phewsh.com/intent)) share the same cloud via Supabase.
|
package/bin/phewsh.js
CHANGED
|
@@ -60,8 +60,10 @@ const COMMANDS = {
|
|
|
60
60
|
context: () => require('../commands/context'),
|
|
61
61
|
gate: () => require('../commands/gate'),
|
|
62
62
|
sap: () => require('../commands/sap'),
|
|
63
|
+
browse: () => require('../commands/browse'),
|
|
63
64
|
watch: () => require('../commands/watch')(),
|
|
64
65
|
mcp: () => require('../commands/mcp')(),
|
|
66
|
+
receipts: () => require('../commands/receipts')(),
|
|
65
67
|
serve: () => require('../commands/serve')(),
|
|
66
68
|
sequence: () => require('../commands/sequence')(),
|
|
67
69
|
seq: () => require('../commands/sequence')(),
|
|
@@ -87,7 +89,8 @@ function showHelp() {
|
|
|
87
89
|
console.log(` ${cyan('intent')} ${g('Create, view, evolve .intent/ artifacts')}`);
|
|
88
90
|
console.log(` ${cyan('gate')} ${g('Set constraints (budget, time, skill, urgency)')}`);
|
|
89
91
|
console.log(` ${cyan('context')} ${g('Export .intent/ for any AI tool')}`);
|
|
90
|
-
console.log(` ${cyan('ai')} ${g('One-shot prompt with .intent/ context')}
|
|
92
|
+
console.log(` ${cyan('ai')} ${g('One-shot prompt with .intent/ context')}
|
|
93
|
+
${cyan('browse')} ${g('Read any URL — AI summary in your terminal')}`);
|
|
91
94
|
console.log('');
|
|
92
95
|
console.log(` ${b(w('sync everywhere'))}`);
|
|
93
96
|
console.log(` ${cyan('seq')} ${g('Sequence all memory → optimal context for any agent')}`);
|
|
@@ -95,6 +98,7 @@ function showHelp() {
|
|
|
95
98
|
console.log(` ${cyan('push/pull')} ${g('Manual sync to/from phewsh.com/intent')}`);
|
|
96
99
|
console.log(` ${cyan('serve')} ${g('Execution bridge — run from phewsh.com/intent')}`);
|
|
97
100
|
console.log(` ${cyan('mcp')} ${g('Connect AI agents via MCP protocol')}`);
|
|
101
|
+
console.log(` ${cyan('receipts')} ${g('Proof trail — what agents actually did, with evidence')}`);
|
|
98
102
|
console.log('');
|
|
99
103
|
console.log(` ${b(w('configure'))}`);
|
|
100
104
|
console.log(` ${cyan('login')} ${g('Identity + API key + cloud sync')}`);
|
|
@@ -115,7 +119,12 @@ function checkForUpdates() {
|
|
|
115
119
|
if (data.version && data.version !== pkg.version) {
|
|
116
120
|
const newer = data.version.split('.').map(Number);
|
|
117
121
|
const current = pkg.version.split('.').map(Number);
|
|
118
|
-
|
|
122
|
+
// Compare major, then minor, then patch — a later slot only matters
|
|
123
|
+
// when all earlier slots are equal (0.11.16 is NOT newer than 0.12.0).
|
|
124
|
+
const isNewer =
|
|
125
|
+
newer[0] !== current[0] ? newer[0] > current[0] :
|
|
126
|
+
newer[1] !== current[1] ? newer[1] > current[1] :
|
|
127
|
+
newer[2] > current[2];
|
|
119
128
|
if (isNewer) {
|
|
120
129
|
console.log(g(`\n Update available: ${pkg.version} → ${data.version}`));
|
|
121
130
|
console.log(g(` Run: npm install -g phewsh\n`));
|
package/commands/ai.js
CHANGED
|
@@ -2,6 +2,10 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const { trackSap } = require('../lib/supabase');
|
|
5
|
+
const {
|
|
6
|
+
getProvider, listProviders, detectProvider,
|
|
7
|
+
buildHeaders, buildBody, getUrl, streamParser,
|
|
8
|
+
} = require('../lib/providers');
|
|
5
9
|
|
|
6
10
|
const CONFIG_PATH = path.join(os.homedir(), '.phewsh', 'config.json');
|
|
7
11
|
const INTENT_DIR = path.join(process.cwd(), '.intent');
|
|
@@ -36,91 +40,183 @@ function buildSystemPrompt(intentFiles) {
|
|
|
36
40
|
return `You are a focused execution assistant. The user has structured intent artifacts that define what they are building. Use these as your primary context for every response — stay aligned with their vision, plan, and next actions.\n\n${sections}`;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
function parseFlags(args) {
|
|
44
|
+
const flags = {};
|
|
45
|
+
for (let i = 0; i < args.length; i++) {
|
|
46
|
+
if (args[i] === '--provider' || args[i] === '-p') {
|
|
47
|
+
flags.provider = args[i + 1];
|
|
48
|
+
i++;
|
|
49
|
+
} else if (args[i] === '--model' || args[i] === '-m') {
|
|
50
|
+
flags.model = args[i + 1];
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Strip flags from args to get the prompt
|
|
55
|
+
const clean = [];
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
if ((args[i] === '--provider' || args[i] === '-p' || args[i] === '--model' || args[i] === '-m') && args[i + 1]) {
|
|
58
|
+
i++; // skip value
|
|
59
|
+
} else {
|
|
60
|
+
clean.push(args[i]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { flags, clean };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveProvider(config, flagProvider) {
|
|
67
|
+
const providerName = flagProvider || config.defaultProvider || 'anthropic';
|
|
68
|
+
return getProvider(providerName);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readProjectId() {
|
|
72
|
+
try {
|
|
73
|
+
const p = path.join(INTENT_DIR, 'project.json');
|
|
74
|
+
if (!fs.existsSync(p)) return null;
|
|
75
|
+
const j = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
76
|
+
return j.id || null;
|
|
77
|
+
} catch { return null; }
|
|
78
|
+
}
|
|
44
79
|
|
|
45
|
-
|
|
80
|
+
function resolveApiKey(config, provider) {
|
|
81
|
+
// PHEWSH gateway authenticates with your phewsh JWT (phewsh login --token),
|
|
82
|
+
// not a BYOK provider key.
|
|
83
|
+
if (provider.id === 'phewsh') return config.supabaseAccessToken || null;
|
|
84
|
+
// Check provider-specific key first, then fall back to generic apiKey
|
|
85
|
+
const providerKey = config.providerKeys?.[provider.id];
|
|
86
|
+
if (providerKey) return providerKey;
|
|
87
|
+
// Check env var
|
|
88
|
+
if (provider.keyEnvVar && process.env[provider.keyEnvVar]) return process.env[provider.keyEnvVar];
|
|
89
|
+
// Fall back to generic key if provider matches or can be auto-detected
|
|
90
|
+
if (config.apiKey) {
|
|
91
|
+
const detected = detectProvider(config.apiKey);
|
|
92
|
+
if (!detected || detected === provider.id) return config.apiKey;
|
|
93
|
+
}
|
|
94
|
+
if (provider.noKey) return null; // ollama doesn't need a key
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function streamResponse(config, provider, model, systemPrompt, userPrompt) {
|
|
99
|
+
const apiKey = resolveApiKey(config, provider);
|
|
100
|
+
if (!apiKey && !provider.noKey) {
|
|
101
|
+
if (provider.id === 'phewsh') {
|
|
102
|
+
throw new Error('Not logged in. Run `phewsh login --token <jwt>` (get it at phewsh.com/intent → Settings → CLI Access) to use pooled credits.');
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`No API key for ${provider.name}. Run \`phewsh login --set-key\` or set ${provider.keyEnvVar}.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const url = getUrl(provider, config);
|
|
108
|
+
const headers = buildHeaders(provider, apiKey);
|
|
109
|
+
// Route through the Decision Gate: tell the gateway which project this spend
|
|
110
|
+
// belongs to, so a budget set on that project is enforced server-side.
|
|
111
|
+
if (provider.id === 'phewsh') {
|
|
112
|
+
const projectId = readProjectId();
|
|
113
|
+
if (projectId) headers['x-phewsh-project'] = projectId;
|
|
114
|
+
}
|
|
115
|
+
const body = buildBody(provider, model, systemPrompt, userPrompt);
|
|
116
|
+
|
|
117
|
+
const response = await fetch(url, {
|
|
46
118
|
method: 'POST',
|
|
47
|
-
headers
|
|
48
|
-
'x-api-key': apiKey,
|
|
49
|
-
'anthropic-version': '2023-06-01',
|
|
50
|
-
'content-type': 'application/json',
|
|
51
|
-
},
|
|
119
|
+
headers,
|
|
52
120
|
body: JSON.stringify(body),
|
|
53
121
|
});
|
|
54
122
|
|
|
55
123
|
if (!response.ok) {
|
|
56
124
|
const err = await response.json().catch(() => ({}));
|
|
57
|
-
|
|
125
|
+
const msg = err.error?.message || err.message || `API error ${response.status}`;
|
|
126
|
+
throw new Error(`${provider.name}: ${msg}`);
|
|
58
127
|
}
|
|
59
128
|
|
|
60
129
|
process.stdout.write('\n');
|
|
61
130
|
|
|
131
|
+
const parse = streamParser(provider);
|
|
62
132
|
let promptTokens = null;
|
|
63
133
|
let completionTokens = null;
|
|
64
134
|
|
|
65
|
-
for await (const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
const parsed = JSON.parse(data);
|
|
73
|
-
if (parsed.type === 'content_block_delta' && parsed.delta?.text) {
|
|
74
|
-
process.stdout.write(parsed.delta.text);
|
|
75
|
-
}
|
|
76
|
-
// Capture token counts from Anthropic stream
|
|
77
|
-
if (parsed.type === 'message_start' && parsed.message?.usage) {
|
|
78
|
-
promptTokens = parsed.message.usage.input_tokens;
|
|
79
|
-
}
|
|
80
|
-
if (parsed.type === 'message_delta' && parsed.usage) {
|
|
81
|
-
completionTokens = parsed.usage.output_tokens;
|
|
82
|
-
}
|
|
83
|
-
} catch { /* skip malformed chunks */ }
|
|
135
|
+
for await (const event of parse(response)) {
|
|
136
|
+
if (event.type === 'text') {
|
|
137
|
+
process.stdout.write(event.text);
|
|
138
|
+
} else if (event.type === 'usage') {
|
|
139
|
+
promptTokens = event.promptTokens;
|
|
140
|
+
completionTokens = event.completionTokens;
|
|
84
141
|
}
|
|
85
142
|
}
|
|
86
143
|
|
|
87
144
|
process.stdout.write('\n\n');
|
|
88
145
|
|
|
89
|
-
// SAP
|
|
90
|
-
trackSap({
|
|
146
|
+
// SAP tracking
|
|
147
|
+
trackSap({
|
|
148
|
+
userId: config.supabaseUserId,
|
|
149
|
+
source: 'cli',
|
|
150
|
+
model,
|
|
151
|
+
promptTokens,
|
|
152
|
+
completionTokens,
|
|
153
|
+
accessToken: config.supabaseAccessToken,
|
|
154
|
+
});
|
|
91
155
|
}
|
|
92
156
|
|
|
93
157
|
async function main() {
|
|
94
158
|
if (!subcommand || subcommand === '--help' || subcommand === '-h') {
|
|
95
159
|
console.log(`
|
|
96
|
-
|
|
160
|
+
phewsh ai
|
|
97
161
|
|
|
98
162
|
Usage:
|
|
99
163
|
phewsh ai run "<prompt>" Run a prompt with .intent/ context injected
|
|
100
164
|
phewsh ai status Show current config and context
|
|
165
|
+
phewsh ai providers List available AI providers
|
|
166
|
+
|
|
167
|
+
Options:
|
|
168
|
+
-p, --provider <name> Override provider (e.g. openrouter, groq, ollama)
|
|
169
|
+
-m, --model <model> Override model
|
|
101
170
|
|
|
102
171
|
Examples:
|
|
103
172
|
phewsh ai run "what should I build next?"
|
|
104
|
-
phewsh ai run
|
|
105
|
-
phewsh ai run
|
|
173
|
+
phewsh ai run -p openrouter "compare these approaches"
|
|
174
|
+
phewsh ai run -p groq -m llama-3.3-70b-versatile "quick question"
|
|
175
|
+
phewsh ai run -p ollama "local inference, no API key needed"
|
|
106
176
|
`);
|
|
107
177
|
return;
|
|
108
178
|
}
|
|
109
179
|
|
|
180
|
+
if (subcommand === 'providers') {
|
|
181
|
+
const config = loadConfig() || {};
|
|
182
|
+
console.log('\n Available providers:\n');
|
|
183
|
+
for (const p of listProviders()) {
|
|
184
|
+
const isDefault = (config.defaultProvider || 'anthropic') === p.id;
|
|
185
|
+
const hasKey = !!(config.providerKeys?.[p.id] || (p.keyEnvVar && process.env[p.keyEnvVar]));
|
|
186
|
+
const keyStatus = p.id === 'phewsh'
|
|
187
|
+
? (config.supabaseAccessToken ? 'logged in' : 'run: phewsh login --token')
|
|
188
|
+
: (p.noKey ? 'no key needed' : (hasKey ? 'key set' : 'no key'));
|
|
189
|
+
const marker = isDefault ? ' (default)' : '';
|
|
190
|
+
console.log(` ${p.id.padEnd(12)} ${p.name.padEnd(26)} ${keyStatus}${marker}`);
|
|
191
|
+
console.log(` ${''.padEnd(12)} model: ${p.defaultModel || '(configure)'}`);
|
|
192
|
+
if (p.docs) console.log(` ${''.padEnd(12)} ${p.docs}`);
|
|
193
|
+
console.log('');
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
110
198
|
if (subcommand === 'status') {
|
|
111
199
|
const config = loadConfig();
|
|
112
200
|
const intentFiles = loadIntentContext();
|
|
201
|
+
const providerName = config?.defaultProvider || 'anthropic';
|
|
202
|
+
let provider;
|
|
203
|
+
try { provider = getProvider(providerName); } catch { provider = { name: providerName }; }
|
|
113
204
|
console.log('\n phewsh ai — status\n');
|
|
114
|
-
console.log(` Config ${config ? '
|
|
115
|
-
console.log(`
|
|
116
|
-
console.log(`
|
|
117
|
-
|
|
205
|
+
console.log(` Config ${config ? 'found' : 'not found — run \`phewsh login\`'}`);
|
|
206
|
+
console.log(` Provider ${provider.name} (${providerName})`);
|
|
207
|
+
console.log(` API key ${config?.apiKey || config?.providerKeys?.[providerName] ? 'set' : 'not set'}`);
|
|
208
|
+
if (config?.providerKeys) {
|
|
209
|
+
const configured = Object.keys(config.providerKeys).filter(k => config.providerKeys[k]);
|
|
210
|
+
if (configured.length > 0) console.log(` Keys for ${configured.join(', ')}`);
|
|
211
|
+
}
|
|
212
|
+
console.log(` .intent/ ${intentFiles.length > 0 ? intentFiles.map(f => f.file).join(', ') : 'none found'}`);
|
|
118
213
|
console.log('');
|
|
119
214
|
return;
|
|
120
215
|
}
|
|
121
216
|
|
|
122
217
|
if (subcommand === 'run') {
|
|
123
|
-
const
|
|
218
|
+
const { flags, clean } = parseFlags(args.slice(1));
|
|
219
|
+
const prompt = clean.join(' ');
|
|
124
220
|
if (!prompt) {
|
|
125
221
|
console.error('\n Usage: phewsh ai run "<your prompt>"\n');
|
|
126
222
|
process.exit(1);
|
|
@@ -131,8 +227,14 @@ async function main() {
|
|
|
131
227
|
console.error('\n Not logged in. Run `phewsh login` first.\n');
|
|
132
228
|
process.exit(1);
|
|
133
229
|
}
|
|
134
|
-
|
|
135
|
-
|
|
230
|
+
|
|
231
|
+
const provider = resolveProvider(config, flags.provider);
|
|
232
|
+
const model = flags.model || config.providerModels?.[provider.id] || provider.defaultModel;
|
|
233
|
+
|
|
234
|
+
const apiKey = resolveApiKey(config, provider);
|
|
235
|
+
if (!apiKey && !provider.noKey) {
|
|
236
|
+
console.error(`\n No API key for ${provider.name}.`);
|
|
237
|
+
console.error(` Run \`phewsh login --set-key\` or set ${provider.keyEnvVar}.\n`);
|
|
136
238
|
process.exit(1);
|
|
137
239
|
}
|
|
138
240
|
|
|
@@ -144,8 +246,9 @@ async function main() {
|
|
|
144
246
|
} else {
|
|
145
247
|
console.log('\n No .intent/ found — running without project context');
|
|
146
248
|
}
|
|
249
|
+
console.log(` Provider: ${provider.name} | Model: ${model}`);
|
|
147
250
|
|
|
148
|
-
await streamResponse(config
|
|
251
|
+
await streamResponse(config, provider, model, systemPrompt, prompt);
|
|
149
252
|
return;
|
|
150
253
|
}
|
|
151
254
|
|