phewsh 0.15.63 → 0.15.65
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/LICENSE +21 -0
- package/README.md +16 -1
- package/SECURITY.md +97 -0
- package/commands/ambient.js +37 -0
- package/commands/gate.js +29 -0
- package/commands/hook.js +53 -0
- package/commands/mbhd.js +5 -4
- package/commands/mcp.js +1 -1
- package/commands/sequence.js +14 -0
- package/commands/session.js +55 -13
- package/lib/gate-policy.js +117 -0
- package/lib/selfheal.js +1 -0
- package/lib/sequencer/discover.js +30 -2
- package/lib/sequencer/index.js +8 -4
- package/package.json +8 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 phewsh (hello@phewsh.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,9 +5,24 @@ Turn intent into action. Structure your thinking, execute your next step.
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
curl -fsSL https://phewsh.com/install.sh | sh
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
No sudo, nvm-aware. (Prefer a package manager? `npm install -g phewsh` —
|
|
12
|
+
same artifact, built from this repo.)
|
|
13
|
+
|
|
14
|
+
Then let it improve the AI tools you already have — without launching anything:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
phewsh ambient on # your .intent/ + a verified brief reach Claude Code, Codex,
|
|
18
|
+
# Gemini, Cursor at the start of every session (reversible)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
phewsh isn't just a CLI — `ambient` makes every supported tool start aligned to
|
|
22
|
+
what you're building. What it reads, writes, and sends is documented, factually,
|
|
23
|
+
in [SECURITY.md](./SECURITY.md). It's local-first and MIT; this repo is an exact
|
|
24
|
+
mirror of the published npm package.
|
|
25
|
+
|
|
11
26
|
## Quick Start
|
|
12
27
|
|
|
13
28
|
```bash
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Security & Data Flow — phewsh CLI
|
|
2
|
+
|
|
3
|
+
phewsh is a **local-first** tool. It runs on your machine, reads your project's
|
|
4
|
+
`.intent/` and your installed AI tools' config, and helps you carry context
|
|
5
|
+
between those tools. This document states — factually, traceable to source —
|
|
6
|
+
what it reads, writes, and sends. No vague claims; if something here is wrong,
|
|
7
|
+
it's a bug: **hello@phewsh.com**.
|
|
8
|
+
|
|
9
|
+
> Status: this is an honest self-documented inventory, **not** an independent
|
|
10
|
+
> audit. The CLI source is **public and MIT-licensed** at
|
|
11
|
+
> **github.com/cleverIdeaz/phewsh-cli** — an exact mirror of the published npm
|
|
12
|
+
> package, so you can read exactly what you install (`bin/ commands/ lib/ mcp/`).
|
|
13
|
+
> An independent review is still planned (see "Roadmap").
|
|
14
|
+
|
|
15
|
+
## What phewsh reads
|
|
16
|
+
- **`.intent/`** in your project (`vision.md`, `plan.md`, `status.md`, `next.md`,
|
|
17
|
+
`project.json`, `next.json`). This is the source of truth it projects.
|
|
18
|
+
- **Your tools' context files** when present: `CLAUDE.md`, `AGENTS.md`,
|
|
19
|
+
`GEMINI.md`, `.cursorrules`, `.github/copilot-instructions.md`, `README.md`.
|
|
20
|
+
- **Your Claude project memory** (`~/.claude/projects/<cwd>/memory/MEMORY.md` and
|
|
21
|
+
files it links) and **global per-user memory** (`~/.claude/CLAUDE.md`,
|
|
22
|
+
`~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md`) — read-only, used to enrich the
|
|
23
|
+
`phewsh seq` summary. Global memory is **never** written into a project file
|
|
24
|
+
unless you pass `--include-global` (`lib/sequencer/index.js`).
|
|
25
|
+
- **Git status** of the current repo (read-only: `git status`, HEAD, diff stats).
|
|
26
|
+
|
|
27
|
+
## What phewsh writes (and where)
|
|
28
|
+
Every generated block is wrapped in `<!-- PHEWSH:START -->` / `<!-- PHEWSH:END -->`
|
|
29
|
+
markers; **content outside the markers is preserved byte-for-byte** (tested).
|
|
30
|
+
- **Project context files** — the canonical `.intent/` projection into
|
|
31
|
+
`CLAUDE.md` / `AGENTS.md` / `GEMINI.md` / `.cursorrules` (only files that
|
|
32
|
+
already exist, unless you create them). One canonical projector
|
|
33
|
+
(`lib/selfheal.js`) is the single writer.
|
|
34
|
+
- **`~/.phewsh/`** — `config.json` (your settings/keys, mode **0600**, dir 0700),
|
|
35
|
+
`briefs/` (saved handoff briefs), `sessions/`, `receipts/`, ledger files.
|
|
36
|
+
- **Shell rc** — only if you run `phewsh shim on` (adds one PATH line; reversible
|
|
37
|
+
with `phewsh shim off`).
|
|
38
|
+
- **Global base files** — only if you run `phewsh ambient on`: a marker-wrapped
|
|
39
|
+
block in `~/.claude/CLAUDE.md` etc. for tools you already have. Reversible with
|
|
40
|
+
`phewsh ambient off`.
|
|
41
|
+
- **Claude Code hook** — `phewsh ambient on` registers `SessionStart`/`SessionEnd`
|
|
42
|
+
hooks (context injection + a metadata breadcrumb; **never your transcript**).
|
|
43
|
+
|
|
44
|
+
## Network calls (exactly these)
|
|
45
|
+
phewsh makes **no network calls for its core local features** (sequencing,
|
|
46
|
+
projection, briefs, status). Calls happen only for these named actions:
|
|
47
|
+
- **`api.anthropic.com` / `openrouter.ai` / `api.together.xyz`** — only if you set
|
|
48
|
+
a BYOK key and use `/run` or the API route. Your key goes **directly** to the
|
|
49
|
+
provider; it is **not** proxied through phewsh servers.
|
|
50
|
+
- **`registry.npmjs.org`** — version check for `phewsh update` (notify-only by
|
|
51
|
+
default).
|
|
52
|
+
- **`<project>.supabase.co`** — only if you `/login`: auth + optional cloud sync
|
|
53
|
+
of your `.intent/` (you initiate `push`/`pull`).
|
|
54
|
+
- **`phewsh.com`** — only for `phewsh serve`/cloud-bridge actions you start.
|
|
55
|
+
- **Sustainability telemetry** (`trackSap`, `lib/supabase.js`) — sends **model
|
|
56
|
+
name, token counts, and a kWh estimate only**. No prompts, no responses, no
|
|
57
|
+
file contents. Tied to your account id only when logged in.
|
|
58
|
+
|
|
59
|
+
## Credentials
|
|
60
|
+
- A bring-your-own key lives in `~/.phewsh/config.json` at mode **0600**
|
|
61
|
+
(`lib/config-file.js`) and is sent **directly to the provider**, never to us.
|
|
62
|
+
- phewsh routes through your installed tools on **their own login** (your Claude
|
|
63
|
+
subscription, your ChatGPT plan) — it does not see or store those credentials.
|
|
64
|
+
|
|
65
|
+
## The local bridge
|
|
66
|
+
`phewsh serve` binds **`127.0.0.1` only** (loopback), with an origin allowlist,
|
|
67
|
+
and runs only while you run it.
|
|
68
|
+
|
|
69
|
+
## Known issues / hardening backlog (honest)
|
|
70
|
+
- **Local shell-injection surface (medium, local-only):** a few call sites build
|
|
71
|
+
shell strings from REPL input via `execSync` (e.g. `commands/session.js` gate /
|
|
72
|
+
outcomes arg passthrough). Inputs are your own typed/pasted text on your own
|
|
73
|
+
machine, but crafted args could execute. Fix: convert to argument-safe
|
|
74
|
+
`execFileSync`/`spawnSync`. Tracked as a Next item.
|
|
75
|
+
- **No release provenance yet** — npm provenance, signed tags, and a public
|
|
76
|
+
source repo are planned (Roadmap) so the published artifact is verifiable.
|
|
77
|
+
|
|
78
|
+
## Disable / uninstall
|
|
79
|
+
- `phewsh ambient off` — removes injected hooks and global base files; restores
|
|
80
|
+
your files.
|
|
81
|
+
- `phewsh shim off` — removes the PATH line.
|
|
82
|
+
- `npm uninstall -g phewsh` — removes the CLI. `rm -rf ~/.phewsh` removes local
|
|
83
|
+
state. Generated `PHEWSH:START/END` blocks can be deleted by hand; everything
|
|
84
|
+
outside them is yours and untouched.
|
|
85
|
+
|
|
86
|
+
## Roadmap to verifiable trust
|
|
87
|
+
Done: this doc, exec hardening, telemetry stays counts-only and explicit, and a
|
|
88
|
+
**public MIT source repo** (`cleverIdeaz/phewsh-cli`) mirroring the npm package.
|
|
89
|
+
Before broad promotion: npm provenance, signed tags, and dependency/secret
|
|
90
|
+
scanning so the published artifact is cryptographically verifiable from the
|
|
91
|
+
public source. For enterprise: a documented threat model and an independent
|
|
92
|
+
security assessment. We will not call phewsh "audited" until an independent
|
|
93
|
+
audit has actually occurred.
|
|
94
|
+
|
|
95
|
+
## Reporting
|
|
96
|
+
Security issues: **hello@phewsh.com**. Please allow reasonable time to remediate
|
|
97
|
+
before public disclosure.
|
package/commands/ambient.js
CHANGED
|
@@ -27,6 +27,11 @@ const CLAUDE_SETTINGS = path.join(os.homedir(), '.claude', 'settings.json');
|
|
|
27
27
|
|
|
28
28
|
const HOOK_START = { type: 'command', command: 'phewsh hook session-start' };
|
|
29
29
|
const HOOK_END = { type: 'command', command: 'phewsh hook session-end' };
|
|
30
|
+
// Opt-in Decision Gate enforcement (separate from ambient context sync). The
|
|
31
|
+
// matcher scopes the hook to the tools the policy actually judges, so it never
|
|
32
|
+
// fires on harmless reads.
|
|
33
|
+
const HOOK_PRETOOL = { type: 'command', command: 'phewsh hook pre-tool' };
|
|
34
|
+
const PRETOOL_MATCHER = 'Write|Edit|MultiEdit|NotebookEdit|Bash';
|
|
30
35
|
|
|
31
36
|
// ANSI helpers (256-color per cli/lib/ui.js palette rules)
|
|
32
37
|
const b = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
@@ -97,6 +102,35 @@ function removeClaudeHooks() {
|
|
|
97
102
|
return removed;
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
// ── Decision Gate enforcement (PreToolUse) — opt-in, reversible ──────────────
|
|
106
|
+
function preToolGateApplied() {
|
|
107
|
+
const s = loadClaudeSettings();
|
|
108
|
+
return !!s && hasHook(s, 'PreToolUse', HOOK_PRETOOL.command);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function enablePreToolGate() {
|
|
112
|
+
const settings = loadClaudeSettings() || {};
|
|
113
|
+
settings.hooks = settings.hooks || {};
|
|
114
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
|
|
115
|
+
if (hasHook(settings, 'PreToolUse', HOOK_PRETOOL.command)) return false;
|
|
116
|
+
settings.hooks.PreToolUse.push({ matcher: PRETOOL_MATCHER, hooks: [HOOK_PRETOOL] });
|
|
117
|
+
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function disablePreToolGate() {
|
|
122
|
+
const settings = loadClaudeSettings();
|
|
123
|
+
if (!settings?.hooks?.PreToolUse) return false;
|
|
124
|
+
const before = settings.hooks.PreToolUse.length;
|
|
125
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse
|
|
126
|
+
.map(e => ({ ...e, hooks: (e.hooks || []).filter(h => h.command !== HOOK_PRETOOL.command) }))
|
|
127
|
+
.filter(e => e.hooks.length > 0);
|
|
128
|
+
if ((settings.hooks.PreToolUse || []).length === 0) delete settings.hooks.PreToolUse;
|
|
129
|
+
const changed = before !== (settings.hooks.PreToolUse?.length ?? 0);
|
|
130
|
+
if (changed) fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
131
|
+
return changed;
|
|
132
|
+
}
|
|
133
|
+
|
|
100
134
|
function showConsentScreen(harnesses) {
|
|
101
135
|
console.log('');
|
|
102
136
|
console.log(` ${b(cream('PHEWSH ambient'))} ${sage('— continuity without launching phewsh')}`);
|
|
@@ -365,3 +399,6 @@ async function main() {
|
|
|
365
399
|
|
|
366
400
|
module.exports = main;
|
|
367
401
|
module.exports.ensureAuto = ensureAuto;
|
|
402
|
+
module.exports.enablePreToolGate = enablePreToolGate;
|
|
403
|
+
module.exports.disablePreToolGate = disablePreToolGate;
|
|
404
|
+
module.exports.preToolGateApplied = preToolGateApplied;
|
package/commands/gate.js
CHANGED
|
@@ -373,6 +373,7 @@ async function main() {
|
|
|
373
373
|
if (subcommand === 'update' || subcommand === 'edit') { await update(); return; }
|
|
374
374
|
if (subcommand === 'pause') { pause(); return; }
|
|
375
375
|
if (subcommand === 'resume') { resume(); return; }
|
|
376
|
+
if (subcommand === 'enforce') { enforce(args[1]); return; }
|
|
376
377
|
if (subcommand === 'reset') {
|
|
377
378
|
let removed = false;
|
|
378
379
|
const project = loadProjectJson();
|
|
@@ -391,6 +392,34 @@ async function main() {
|
|
|
391
392
|
console.log(`\n Unknown: ${subcommand}. Run \`phewsh gate --help\`.\n`);
|
|
392
393
|
}
|
|
393
394
|
|
|
395
|
+
// Opt-in deterministic enforcement: register/unregister the Claude Code
|
|
396
|
+
// PreToolUse hook that makes the Decision Gate act BEFORE a tool runs. Reversible,
|
|
397
|
+
// Claude-Code-only for now (it's the one provider with a real veto hook).
|
|
398
|
+
function enforce(action) {
|
|
399
|
+
let amb;
|
|
400
|
+
try { amb = require('./ambient'); } catch { console.log('\n Enforcement unavailable.\n'); return; }
|
|
401
|
+
const on = (action || 'status').toLowerCase();
|
|
402
|
+
if (on === 'on' || on === 'enable') {
|
|
403
|
+
const changed = amb.enablePreToolGate();
|
|
404
|
+
console.log(changed
|
|
405
|
+
? `\n ${green('●')} Gate enforcement ${green('ON')} — Claude Code asks/denies on protected-path writes and high-blast-radius commands before they run.\n ${g('Reversible:')} phewsh gate enforce off\n`
|
|
406
|
+
: `\n Gate enforcement already on.\n`);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (on === 'off' || on === 'disable') {
|
|
410
|
+
const changed = amb.disablePreToolGate();
|
|
411
|
+
console.log(changed ? `\n Gate enforcement ${yellow('OFF')} — PreToolUse hook removed.\n` : `\n Gate enforcement was not on.\n`);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
// status
|
|
415
|
+
const applied = amb.preToolGateApplied();
|
|
416
|
+
console.log(`\n Gate enforcement: ${applied ? green('ON') : g('off')} ${g('(Claude Code PreToolUse)')}`);
|
|
417
|
+
console.log(` ${g('Turn on:')} phewsh gate enforce on ${g('· off:')} phewsh gate enforce off`);
|
|
418
|
+
console.log(` ${g('What it does: deny writes to protected paths (.env, keys, .git/…),')}`);
|
|
419
|
+
console.log(` ${g('ask before high-blast-radius shell (rm -rf, force-push, sudo…).')}`);
|
|
420
|
+
console.log(` ${g('Opt-in, local-only, fail-open. Other tools: advisory only for now.')}\n`);
|
|
421
|
+
}
|
|
422
|
+
|
|
394
423
|
module.exports = main;
|
|
395
424
|
|
|
396
425
|
main().catch(err => {
|
package/commands/hook.js
CHANGED
|
@@ -163,10 +163,63 @@ function sessionEnd() {
|
|
|
163
163
|
setTimeout(() => { appendBreadcrumb('session-end'); process.exit(0); }, 1500);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
// Load the Decision Gate context (constraints + protected files) for the project
|
|
167
|
+
// being worked in. Best-effort: missing/garbled → empty context (gate allows).
|
|
168
|
+
function loadGateContext(cwd) {
|
|
169
|
+
try {
|
|
170
|
+
const pj = JSON.parse(fs.readFileSync(path.join(cwd, '.intent', 'project.json'), 'utf-8'));
|
|
171
|
+
const dg = pj.decisionGate || {};
|
|
172
|
+
return {
|
|
173
|
+
constraints: dg.constraints || {},
|
|
174
|
+
protectedFiles: dg.protectedFiles || pj.protectedFiles || [],
|
|
175
|
+
};
|
|
176
|
+
} catch { return { constraints: {}, protectedFiles: [] }; }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// PreToolUse adapter — the Decision Gate acting BEFORE a tool runs. Reads Claude
|
|
180
|
+
// Code's PreToolUse JSON from stdin, evaluates against the project's constraints
|
|
181
|
+
// + protected files, and emits a permission decision. FAIL-SAFE: any error, no
|
|
182
|
+
// stdin, or no policy hit → exit 0 (allow). A broken gate must never trap you.
|
|
183
|
+
// It logs only a redacted decision line (never the tool payload).
|
|
184
|
+
function preTool() {
|
|
185
|
+
try {
|
|
186
|
+
if (process.stdin.isTTY) process.exit(0); // not a real hook invocation
|
|
187
|
+
const { evaluateAction, auditLine } = require('../lib/gate-policy');
|
|
188
|
+
let raw = '';
|
|
189
|
+
try { raw = fs.readFileSync(0, 'utf-8'); } catch { process.exit(0); }
|
|
190
|
+
let payload = {};
|
|
191
|
+
try { payload = JSON.parse(raw || '{}'); } catch { process.exit(0); }
|
|
192
|
+
|
|
193
|
+
const cwd = payload.cwd || process.cwd();
|
|
194
|
+
const envelope = {
|
|
195
|
+
toolName: payload.tool_name || payload.toolName,
|
|
196
|
+
toolInput: payload.tool_input || payload.toolInput || {},
|
|
197
|
+
...loadGateContext(cwd),
|
|
198
|
+
};
|
|
199
|
+
let result = { decision: 'allow', reason: '' };
|
|
200
|
+
try { result = evaluateAction(envelope); } catch { process.exit(0); }
|
|
201
|
+
|
|
202
|
+
if (result.decision !== 'allow') {
|
|
203
|
+
try { appendBreadcrumb('pre-tool', { decision: result.decision, action: auditLine(envelope, result) }); } catch { /* audit is best-effort */ }
|
|
204
|
+
process.stdout.write(JSON.stringify({
|
|
205
|
+
hookSpecificOutput: {
|
|
206
|
+
hookEventName: 'PreToolUse',
|
|
207
|
+
permissionDecision: result.decision, // 'deny' | 'ask'
|
|
208
|
+
permissionDecisionReason: result.reason,
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
process.exit(0);
|
|
213
|
+
} catch {
|
|
214
|
+
process.exit(0); // fail open, always
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
166
218
|
function main() {
|
|
167
219
|
const event = process.argv[3];
|
|
168
220
|
if (event === 'session-start') return sessionStart();
|
|
169
221
|
if (event === 'session-end') return sessionEnd();
|
|
222
|
+
if (event === 'pre-tool') return preTool();
|
|
170
223
|
// Unknown event: exit silently — hooks must never error the host tool.
|
|
171
224
|
process.exit(0);
|
|
172
225
|
}
|
package/commands/mbhd.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { execFileSync } = require('child_process');
|
|
2
2
|
|
|
3
3
|
const WEB_URL = 'https://phewsh.com/mbhd';
|
|
4
4
|
|
|
5
5
|
console.log(`\n 🎵 Opening MBHD Music Engine...\n`);
|
|
6
6
|
|
|
7
7
|
try {
|
|
8
|
+
// execFileSync (no shell) — argument-safe even though the URL is constant.
|
|
8
9
|
if (process.platform === 'darwin') {
|
|
9
|
-
|
|
10
|
+
execFileSync('open', [WEB_URL]);
|
|
10
11
|
} else if (process.platform === 'win32') {
|
|
11
|
-
|
|
12
|
+
execFileSync('cmd', ['/c', 'start', '', WEB_URL]);
|
|
12
13
|
} else {
|
|
13
|
-
|
|
14
|
+
execFileSync('xdg-open', [WEB_URL]);
|
|
14
15
|
}
|
|
15
16
|
} catch {
|
|
16
17
|
console.log(` Could not open browser. Visit: ${WEB_URL}\n`);
|
package/commands/mcp.js
CHANGED
|
@@ -48,7 +48,7 @@ function loadLocalProject() {
|
|
|
48
48
|
decisionGate: null,
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
const files = ['vision.md', 'plan.md', 'next.md', 'status.md'
|
|
51
|
+
const files = ['vision.md', 'plan.md', 'next.md', 'status.md'];
|
|
52
52
|
for (const file of files) {
|
|
53
53
|
const filePath = path.join(INTENT_DIR, file);
|
|
54
54
|
if (fs.existsSync(filePath)) {
|
package/commands/sequence.js
CHANGED
|
@@ -117,6 +117,20 @@ async function main() {
|
|
|
117
117
|
// Default behavior: no target = stdout summary
|
|
118
118
|
const target = flags.target || 'stdout';
|
|
119
119
|
|
|
120
|
+
// CANONICAL WRITE. Writing a native projection (e.g. `phewsh seq claude -w`)
|
|
121
|
+
// goes through the one canonical projector (self-heal), NOT the broad
|
|
122
|
+
// sequencer — identical block, identical source policy, no stale clobber.
|
|
123
|
+
if (flags.write && target === 'claude-md') {
|
|
124
|
+
const selfheal = require('../lib/selfheal');
|
|
125
|
+
const res = selfheal.syncContextFiles({ targets: ['CLAUDE.md'], createMissing: true });
|
|
126
|
+
if (res.synced && res.synced.length) {
|
|
127
|
+
console.log(`\n ${green('✓')} ${sage('CLAUDE.md — canonical .intent/ projection (same block self-heal & watch write)')}\n`);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(`\n ${slate('CLAUDE.md already current' + (res.reason ? ' (' + res.reason + ')' : ''))}\n`);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
120
134
|
try {
|
|
121
135
|
const result = sequence({
|
|
122
136
|
target,
|
package/commands/session.js
CHANGED
|
@@ -1714,7 +1714,6 @@ async function main() {
|
|
|
1714
1714
|
console.log(` ${teal('/key')} ${sage('Set API key (optional — harnesses need none)')}`);
|
|
1715
1715
|
console.log(` ${teal('/login')} ${sage('Identity + cloud sync')}`);
|
|
1716
1716
|
console.log(` ${teal('/model')} ${slate('<name>')} ${sage('Switch model — passed through, the provider validates')}`);
|
|
1717
|
-
console.log(` ${teal('/width')} ${slate('[n]')} ${sage('Fix mid-word wrapping if your terminal misreports its size')}`);
|
|
1718
1717
|
console.log(` ${teal('/update')} ${sage('Update phewsh')}`);
|
|
1719
1718
|
console.log(` ${teal('/tour')} ${sage('Quick walkthrough')}`);
|
|
1720
1719
|
console.log('');
|
|
@@ -1927,9 +1926,11 @@ async function main() {
|
|
|
1927
1926
|
|
|
1928
1927
|
if (cmd === 'gate') {
|
|
1929
1928
|
try {
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1929
|
+
// execFileSync with an argv array — no shell, so REPL input can never
|
|
1930
|
+
// be interpreted as shell metacharacters (hardening: was execSync).
|
|
1931
|
+
const { execFileSync } = require('child_process');
|
|
1932
|
+
const gateArgs = (cmdArg || 'status').split(/\s+/).filter(Boolean);
|
|
1933
|
+
execFileSync(process.execPath, [path.join(__dirname, 'gate.js'), ...gateArgs], { stdio: 'inherit' });
|
|
1933
1934
|
intentFiles = loadIntentContext();
|
|
1934
1935
|
systemPrompt = buildSystemPrompt(intentFiles);
|
|
1935
1936
|
} catch (err) {
|
|
@@ -1942,17 +1943,43 @@ async function main() {
|
|
|
1942
1943
|
if (cmd === 'seq' || cmd === 'sequence') {
|
|
1943
1944
|
try {
|
|
1944
1945
|
const { sequence } = require('../lib/sequencer');
|
|
1946
|
+
const { resolveProjectRoot, discover } = require('../lib/sequencer/discover');
|
|
1945
1947
|
const target = cmdArg?.split(/\s+/)[0];
|
|
1946
1948
|
const explain = cmdArg?.includes('explain') || cmdArg?.includes('-e');
|
|
1947
|
-
const write = cmdArg?.includes('-w') || cmdArg?.includes('write');
|
|
1948
1949
|
|
|
1949
1950
|
if (target === 'claude' || target === 'claude-md') {
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1951
|
+
// CANONICAL WRITE. /seq claude writes the SAME deterministic block as
|
|
1952
|
+
// self-heal and watch — one source policy (.intent canonical), one
|
|
1953
|
+
// renderer — so a manual sequence can never produce a divergent or
|
|
1954
|
+
// stale CLAUDE.md. (Previously this called the broad sequencer, which
|
|
1955
|
+
// ingested narrative.md + memory logs and clobbered the canonical block.)
|
|
1956
|
+
const res = selfheal.syncContextFiles({ targets: ['CLAUDE.md'], createMissing: true });
|
|
1957
|
+
if (res.synced && res.synced.length) {
|
|
1958
|
+
console.log(`\n ${teal('●')} ${sage('CLAUDE.md updated — canonical .intent/ projection')} ${slate('(same block self-heal & watch write)')}\n`);
|
|
1959
|
+
} else {
|
|
1960
|
+
console.log(`\n ${slate('CLAUDE.md already current' + (res.reason ? ' (' + res.reason + ')' : ''))}\n`);
|
|
1961
|
+
}
|
|
1962
|
+
} else if (explain || target === 'sources' || target === 'root') {
|
|
1963
|
+
// Diagnostic: show what the sequencer sees — resolved root, selected
|
|
1964
|
+
// sources (with scope), and the canonical projection's restricted set.
|
|
1965
|
+
const root = resolveProjectRoot(process.cwd());
|
|
1966
|
+
const found = discover(process.cwd());
|
|
1967
|
+
console.log('');
|
|
1968
|
+
console.log(` ${b(cream('Sequence diagnostics'))}`);
|
|
1969
|
+
ui.divider('line');
|
|
1970
|
+
console.log(` ${sage('resolved root')} ${cream(root)}${root !== process.cwd() ? slate(' (walked up from ' + process.cwd() + ')') : ''}`);
|
|
1971
|
+
console.log(` ${sage('canonical write set')} ${slate('vision.md · project.json · status.md · next.md · next.json')}`);
|
|
1972
|
+
console.log(` ${sage('broad read (this preview)')}`);
|
|
1973
|
+
for (const s of found) {
|
|
1974
|
+
const tag = s.scope === 'global' ? slate('global ') : sage('project');
|
|
1975
|
+
const canon = ['vision.md','project.json','status.md','next.md','next.json'].includes(s.name) ? teal(' ✓ canonical') : slate(' · preview-only');
|
|
1976
|
+
console.log(` ${tag} ${cream(s.name.padEnd(20))}${canon}`);
|
|
1953
1977
|
}
|
|
1978
|
+
ui.divider('line');
|
|
1979
|
+
console.log(` ${slate('write the canonical projection with')} ${cream('/seq claude')}`);
|
|
1980
|
+
console.log('');
|
|
1954
1981
|
} else {
|
|
1955
|
-
// Default: stdout
|
|
1982
|
+
// Default: broad, READ-ONLY synthesis to stdout — never writes a file.
|
|
1956
1983
|
sequence({ target: 'stdout', explain });
|
|
1957
1984
|
}
|
|
1958
1985
|
} catch (err) {
|
|
@@ -2546,10 +2573,11 @@ async function main() {
|
|
|
2546
2573
|
|
|
2547
2574
|
if (cmd === 'outcomes') {
|
|
2548
2575
|
try {
|
|
2549
|
-
//
|
|
2550
|
-
|
|
2551
|
-
const
|
|
2552
|
-
|
|
2576
|
+
// execFileSync (argv array, no shell) so the labeling prompt owns
|
|
2577
|
+
// stdin AND REPL input can't be shell-interpreted (hardening).
|
|
2578
|
+
const { execFileSync } = require('child_process');
|
|
2579
|
+
const outcomesArgs = cmdArg ? cmdArg.split(/\s+/).filter(Boolean) : [];
|
|
2580
|
+
execFileSync(process.execPath, [path.join(__dirname, '..', 'bin', 'phewsh.js'), 'outcomes', ...outcomesArgs], { stdio: 'inherit' });
|
|
2553
2581
|
} catch { /* user quit mid-labeling — fine */ }
|
|
2554
2582
|
rl.prompt();
|
|
2555
2583
|
return;
|
|
@@ -2727,6 +2755,20 @@ async function main() {
|
|
|
2727
2755
|
console.log(` ${teal('●')} ${sage('Back in phewsh.')} ${slate('No changes detected. 1 kept · 2 undid · 3 redid · 4 flopped · /reconcile · /switch <tool>')}`);
|
|
2728
2756
|
}
|
|
2729
2757
|
console.log(` ${slate('continuing elsewhere?')} ${cream('/handoff')} ${slate('shows the updated brief the next tool inherits')}`);
|
|
2758
|
+
// Auto-reconcile OFFER (human-gated, never auto-writes). Only when the
|
|
2759
|
+
// work left project UNDERSTANDING stale — the docs now disagree with the
|
|
2760
|
+
// code or a projection drifted. Ordinary implementation diffs and a merely
|
|
2761
|
+
// dirty tree do NOT trigger this; that would be noise.
|
|
2762
|
+
try {
|
|
2763
|
+
const truthDrift = (postflight.conflicts || []).filter(c =>
|
|
2764
|
+
/current-state intent disagrees|current-state claims may be stale|should be regenerated|before authoritative intent updated/i.test(c));
|
|
2765
|
+
if (truthDrift.length) {
|
|
2766
|
+
console.log('');
|
|
2767
|
+
console.log(` ${ember('⚠')} ${sage('Your project understanding may now be stale:')}`);
|
|
2768
|
+
console.log(` ${slate(truthDrift[0])}`);
|
|
2769
|
+
console.log(` ${sage('Bring the record up to date so the next tool inherits the truth →')} ${cream('/reconcile')} ${slate('(proposes an exact diff; writes only after you approve)')}`);
|
|
2770
|
+
}
|
|
2771
|
+
} catch { /* the offer is a nicety, never a blocker */ }
|
|
2730
2772
|
console.log('');
|
|
2731
2773
|
rl.prompt();
|
|
2732
2774
|
return;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Deterministic pre-action policy — the enforcement core of the Decision Gate.
|
|
4
|
+
//
|
|
5
|
+
// Pure and provider-neutral: given an action envelope it returns a decision.
|
|
6
|
+
// No I/O, no provider coupling — the hook adapter (commands/hook.js pre-tool)
|
|
7
|
+
// feeds it the action and renders the provider-specific response. This is the
|
|
8
|
+
// "deterministic enforcement" layer from cli/docs/pre-action-architecture.md;
|
|
9
|
+
// it does NOT replace the Decision Gate, it is how the gate acts before a tool
|
|
10
|
+
// runs. Decisions: 'allow' | 'ask' (require human) | 'deny' (block).
|
|
11
|
+
//
|
|
12
|
+
// Design rules: fail OPEN (an empty/garbled envelope returns allow — a policy
|
|
13
|
+
// must never trap the user), and never inspect file CONTENTS (we judge the
|
|
14
|
+
// action's target/shape, not your code or secrets).
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// Tools that write/modify the filesystem — common names across providers.
|
|
19
|
+
const WRITE_TOOLS = new Set([
|
|
20
|
+
'Write', 'Edit', 'MultiEdit', 'NotebookEdit',
|
|
21
|
+
'create_file', 'apply_patch', 'str_replace_editor', 'edit_file',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
// Shell/command tools.
|
|
25
|
+
const SHELL_TOOLS = new Set(['Bash', 'shell', 'run_command', 'run_terminal_cmd']);
|
|
26
|
+
|
|
27
|
+
// Paths an agent should never silently write — credentials, keys, VCS internals.
|
|
28
|
+
const DEFAULT_PROTECTED = [
|
|
29
|
+
'.env', '.env.*', '*.pem', '*.key', 'id_rsa', 'id_ed25519',
|
|
30
|
+
'.git/', '.npmrc', '.aws/', '.ssh/', '.phewsh/config.json',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// High-blast-radius shell patterns that warrant a human OK.
|
|
34
|
+
const DESTRUCTIVE = [
|
|
35
|
+
{ re: /\brm\s+-[a-z]*r[a-z]*f|\brm\s+-[a-z]*f[a-z]*r/, label: 'recursive force delete' },
|
|
36
|
+
{ re: /\bgit\s+push\b[^\n]*--force/, label: 'git force-push' },
|
|
37
|
+
{ re: /\bgit\s+reset\s+--hard\b/, label: 'git hard reset' },
|
|
38
|
+
{ re: /\bDROP\s+(TABLE|DATABASE)\b/i, label: 'SQL DROP' },
|
|
39
|
+
{ re: /\bsudo\b/, label: 'sudo' },
|
|
40
|
+
{ re: /\bchmod\s+-R\b/, label: 'recursive chmod' },
|
|
41
|
+
{ re: /\bmkfs\b|\bdd\s+if=/, label: 'disk write' },
|
|
42
|
+
{ re: /\bcurl\b[^\n]*\|\s*(sh|bash)\b|\bwget\b[^\n]*\|\s*(sh|bash)\b/, label: 'pipe-to-shell' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
function globToRegExp(glob) {
|
|
46
|
+
const esc = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
47
|
+
return new RegExp('^' + esc + '$');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Does `target` match any protected entry? Directory entries (trailing '/')
|
|
51
|
+
// match anywhere in the path; file globs match the basename or full path.
|
|
52
|
+
function isProtected(target, protectedList) {
|
|
53
|
+
if (!target || typeof target !== 'string') return false;
|
|
54
|
+
const norm = target.replace(/^\.\//, '');
|
|
55
|
+
const base = path.basename(norm);
|
|
56
|
+
return protectedList.some(p => {
|
|
57
|
+
if (p.endsWith('/')) return norm.split('/').includes(p.slice(0, -1)) || norm.startsWith(p);
|
|
58
|
+
const re = globToRegExp(p);
|
|
59
|
+
return re.test(base) || re.test(norm);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeTarget(toolInput = {}) {
|
|
64
|
+
return toolInput.file_path || toolInput.path || toolInput.notebook_path
|
|
65
|
+
|| toolInput.filename || toolInput.target_file || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// envelope: { toolName, toolInput, constraints, protectedFiles }
|
|
69
|
+
function evaluateAction(envelope = {}) {
|
|
70
|
+
try {
|
|
71
|
+
const { toolName, toolInput = {}, constraints = {}, protectedFiles = [] } = envelope;
|
|
72
|
+
if (!toolName) return { decision: 'allow', reason: '' };
|
|
73
|
+
const protectedList = [...DEFAULT_PROTECTED, ...(Array.isArray(protectedFiles) ? protectedFiles : [])];
|
|
74
|
+
|
|
75
|
+
// 1. Writes to a protected path → deny (only a deliberate human act should).
|
|
76
|
+
if (WRITE_TOOLS.has(toolName)) {
|
|
77
|
+
const target = writeTarget(toolInput);
|
|
78
|
+
if (isProtected(target, protectedList)) {
|
|
79
|
+
return { decision: 'deny', reason: `phewsh gate: ${toolName} targets a protected path (${target}). Credentials/keys/VCS internals are off-limits to automated edits.` };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 2. High-blast-radius shell → ask (require human confirmation).
|
|
84
|
+
if (SHELL_TOOLS.has(toolName)) {
|
|
85
|
+
const cmd = String(toolInput.command || toolInput.cmd || '');
|
|
86
|
+
const hit = DESTRUCTIVE.find(d => d.re.test(cmd));
|
|
87
|
+
if (hit) {
|
|
88
|
+
return { decision: 'ask', reason: `phewsh gate: high-blast-radius command (${hit.label}) — confirm before it runs.` };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 3. Strict autonomy asks before any write. "delegated"/"guided" do not —
|
|
93
|
+
// that would turn every edit into ceremony.
|
|
94
|
+
const autonomy = String(constraints.autonomy || '').toLowerCase();
|
|
95
|
+
if ((autonomy === 'manual' || autonomy === 'review') && WRITE_TOOLS.has(toolName)) {
|
|
96
|
+
return { decision: 'ask', reason: `phewsh gate: your autonomy is "${autonomy}" — confirm this ${toolName} before it runs.` };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { decision: 'allow', reason: '' };
|
|
100
|
+
} catch {
|
|
101
|
+
return { decision: 'allow', reason: '' }; // fail OPEN: never trap the user
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A redacted one-line audit record — decision + target shape only, never the
|
|
106
|
+
// tool payload (which may contain code or secrets).
|
|
107
|
+
function auditLine(envelope = {}, result = {}) {
|
|
108
|
+
const target = WRITE_TOOLS.has(envelope.toolName)
|
|
109
|
+
? writeTarget(envelope.toolInput || {})
|
|
110
|
+
: (SHELL_TOOLS.has(envelope.toolName) ? '<command>' : '');
|
|
111
|
+
return `${result.decision || 'allow'} ${envelope.toolName || '?'}${target ? ' ' + target : ''}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
evaluateAction, isProtected, auditLine, writeTarget,
|
|
116
|
+
WRITE_TOOLS, SHELL_TOOLS, DEFAULT_PROTECTED, DESTRUCTIVE,
|
|
117
|
+
};
|
package/lib/selfheal.js
CHANGED
|
@@ -205,6 +205,7 @@ function projectionStatus({ cwd = process.cwd(), targets = TARGET_FILES } = {})
|
|
|
205
205
|
// `seq`), where the user is actually working in the project.
|
|
206
206
|
function syncContextFiles({ cwd = process.cwd(), targets = TARGET_FILES, createMissing = true } = {}) {
|
|
207
207
|
try {
|
|
208
|
+
cwd = require('./sequencer/discover').resolveProjectRoot(cwd);
|
|
208
209
|
const intentDir = path.join(cwd, '.intent');
|
|
209
210
|
if (!fs.existsSync(intentDir)) return { synced: [], reason: 'no-intent' };
|
|
210
211
|
const core = buildContextCore(cwd);
|
|
@@ -15,10 +15,38 @@ const fs = require('fs');
|
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const os = require('os');
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// narrative.md is intentionally NOT a source: it is a deprecated artifact that
|
|
19
|
+
// predates the four-word model (Project · Next · Work · Record) and is no longer
|
|
20
|
+
// authoritative project truth. It must never enter a native projection. See
|
|
21
|
+
// resolveProjectRoot + the canonical projection in selfheal.
|
|
22
|
+
const INTENT_FILES = ['vision.md', 'plan.md', 'status.md', 'next.md'];
|
|
19
23
|
const INTENT_JSON = ['project.json', 'next.json', 'pps.json', 'gate.json'];
|
|
20
24
|
|
|
25
|
+
// Resolve the project root by walking UP from `start` to the nearest ancestor
|
|
26
|
+
// (including start) that contains a `.intent/` directory — bounded by the home
|
|
27
|
+
// dir and the filesystem root. This stops a nested dir (e.g. an app folder under
|
|
28
|
+
// a monorepo) from being treated as its own project, and makes every
|
|
29
|
+
// context-generating path agree on the same root. Returns `start` unchanged when
|
|
30
|
+
// no `.intent/` exists anywhere upward (so bare-directory behavior is untouched).
|
|
31
|
+
function resolveProjectRoot(start = process.cwd(), home = os.homedir()) {
|
|
32
|
+
let dir;
|
|
33
|
+
try { dir = path.resolve(start); } catch { return start; }
|
|
34
|
+
const fsRoot = path.parse(dir).root;
|
|
35
|
+
const homeReal = (() => { try { return fs.realpathSync(home); } catch { return home; } })();
|
|
36
|
+
while (true) {
|
|
37
|
+
if (fs.existsSync(path.join(dir, '.intent'))) return dir;
|
|
38
|
+
if (dir === fsRoot) break;
|
|
39
|
+
let real; try { real = fs.realpathSync(dir); } catch { real = dir; }
|
|
40
|
+
if (real === homeReal) break; // don't escape above the user's home
|
|
41
|
+
const parent = path.dirname(dir);
|
|
42
|
+
if (parent === dir) break;
|
|
43
|
+
dir = parent;
|
|
44
|
+
}
|
|
45
|
+
return path.resolve(start);
|
|
46
|
+
}
|
|
47
|
+
|
|
21
48
|
function discover(cwd = process.cwd(), home = os.homedir()) {
|
|
49
|
+
cwd = resolveProjectRoot(cwd, home);
|
|
22
50
|
const sources = [];
|
|
23
51
|
const seenPaths = new Set();
|
|
24
52
|
|
|
@@ -119,4 +147,4 @@ function discover(cwd = process.cwd(), home = os.homedir()) {
|
|
|
119
147
|
return sources;
|
|
120
148
|
}
|
|
121
149
|
|
|
122
|
-
module.exports = { discover };
|
|
150
|
+
module.exports = { discover, resolveProjectRoot };
|
package/lib/sequencer/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// The core pipeline: discover → parse → rank → compress → emit
|
|
4
4
|
// One function. N inputs. 1 optimal output per target format.
|
|
5
5
|
|
|
6
|
-
const { discover } = require('./discover');
|
|
6
|
+
const { discover, resolveProjectRoot } = require('./discover');
|
|
7
7
|
const { rank } = require('./ranker');
|
|
8
8
|
const { compress } = require('./compressor');
|
|
9
9
|
|
|
@@ -53,8 +53,12 @@ function sequence(options = {}) {
|
|
|
53
53
|
cwd = process.cwd(),
|
|
54
54
|
} = options;
|
|
55
55
|
|
|
56
|
+
// Resolve to the canonical project root once, so discovery, naming, and any
|
|
57
|
+
// file write all agree on the same project (a nested dir never self-resolves).
|
|
58
|
+
const root = resolveProjectRoot(cwd);
|
|
59
|
+
|
|
56
60
|
// 1. Discover all source files (project + global)
|
|
57
|
-
let sources = discover(
|
|
61
|
+
let sources = discover(root);
|
|
58
62
|
|
|
59
63
|
// Privacy guard: global per-user memory enriches the summary (stdout), but
|
|
60
64
|
// never bleeds into a project-file target (CLAUDE.md) unless asked for —
|
|
@@ -112,12 +116,12 @@ function sequence(options = {}) {
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
const output = emitter.emit(chunks, {
|
|
115
|
-
projectName: getProjectName(chunks,
|
|
119
|
+
projectName: getProjectName(chunks, root),
|
|
116
120
|
});
|
|
117
121
|
|
|
118
122
|
// Write to file if requested
|
|
119
123
|
if (write && emitter.writeToFile) {
|
|
120
|
-
const result = emitter.writeToFile(output,
|
|
124
|
+
const result = emitter.writeToFile(output, root);
|
|
121
125
|
return { chunks, output, sources, writeResult: result };
|
|
122
126
|
}
|
|
123
127
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phewsh",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.65",
|
|
4
4
|
"description": "Turn intent into action. Structure your thinking, execute your next step.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"phewsh": "bin/phewsh.js"
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"commands/",
|
|
11
11
|
"lib/",
|
|
12
12
|
"mcp/",
|
|
13
|
-
"README.md"
|
|
13
|
+
"README.md",
|
|
14
|
+
"SECURITY.md",
|
|
15
|
+
"LICENSE"
|
|
14
16
|
],
|
|
15
17
|
"keywords": [
|
|
16
18
|
"intent",
|
|
@@ -29,7 +31,10 @@
|
|
|
29
31
|
"license": "MIT",
|
|
30
32
|
"repository": {
|
|
31
33
|
"type": "git",
|
|
32
|
-
"url": "git+https://github.com/cleverIdeaz/phewsh.git"
|
|
34
|
+
"url": "git+https://github.com/cleverIdeaz/phewsh-cli.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/cleverIdeaz/phewsh-cli/issues"
|
|
33
38
|
},
|
|
34
39
|
"homepage": "https://phewsh.com",
|
|
35
40
|
"engines": {
|