@pharaoh-so/mcp 0.3.53 → 0.3.56

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.
Files changed (43) hide show
  1. package/.mcp.json +1 -1
  2. package/CHANGELOG.md +41 -0
  3. package/LICENSE +1 -1
  4. package/NOTICE +28 -0
  5. package/README.md +5 -5
  6. package/commands/plan.md +11 -11
  7. package/commands/review.md +2 -2
  8. package/dist/auth.d.ts +5 -0
  9. package/dist/credentials.d.ts +8 -0
  10. package/dist/gateway.js +1 -1
  11. package/dist/helpers.d.ts +15 -5
  12. package/dist/helpers.js +71 -2
  13. package/dist/index.js +51 -26
  14. package/dist/install-skills.d.ts +30 -5
  15. package/dist/install-skills.js +210 -11
  16. package/dist/refresh.d.ts +8 -2
  17. package/dist/refresh.js +33 -6
  18. package/dist/setup.d.ts +5 -0
  19. package/dist/setup.js +66 -0
  20. package/inspect-tools.json +89 -36
  21. package/{skills → npx-skills}/brainstorm/SKILL.md +2 -1
  22. package/{skills → npx-skills}/debug/SKILL.md +2 -1
  23. package/{skills → npx-skills}/pharaoh/SKILL.md +13 -6
  24. package/{skills → npx-skills}/plan/SKILL.md +13 -14
  25. package/npx-skills/refactor/SKILL.md +38 -0
  26. package/{skills → npx-skills}/review/SKILL.md +4 -5
  27. package/{skills → npx-skills}/tdd/SKILL.md +2 -1
  28. package/{skills → npx-skills}/verify/SKILL.md +2 -1
  29. package/package.json +6 -4
  30. package/project-memory/bundle.mjs +137 -0
  31. package/project-memory/execute-turn.mjs +240 -0
  32. package/project-memory/hook-config.mjs +91 -0
  33. package/project-memory/hook-events.mjs +141 -0
  34. package/project-memory/local.mjs +463 -0
  35. package/project-memory/memory-api.mjs +182 -0
  36. package/project-memory/pharaoh-memory-hook.mjs +33 -0
  37. package/project-memory/pharaoh-memory.mjs +55 -0
  38. package/project-memory/setup.d.mts +14 -0
  39. package/project-memory/setup.mjs +151 -0
  40. package/project-memory/user-target.mjs +221 -0
  41. package/trace-app.html +222 -101
  42. package/skills/.gitkeep +0 -0
  43. package/skills/refactor/SKILL.md +0 -34
package/.mcp.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "pharaoh": {
4
4
  "command": "npx",
5
5
  "args": [
6
- "@pharaoh-so/mcp@0.3.53"
6
+ "@pharaoh-so/mcp@0.3.56"
7
7
  ]
8
8
  }
9
9
  }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,47 @@ All notable changes to `@pharaoh-so/mcp` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - `plugins/pharaoh/`: the Claude directory plugin, generated from the same skills (plan, review, refactor and pharaoh) with the hosted connector at `https://mcp.pharaoh.so/mcp`. It has no hooks or commands and isn't part of the npm package.
12
+ - `NOTICE`: the brainstorm, debug, tdd and verify skills are adapted from [superpowers](https://github.com/obra/superpowers) (MIT, Copyright (c) 2025 Jesse Vincent). Each skill's `license:` field says so too.
13
+
14
+ ### Changed
15
+ - Every tool declares `openWorldHint: false` (Pharaoh reaches only your own connected repos), and `pharaoh_account`'s description says it links the plans page.
16
+ - When a repo isn't live, and in `pharaoh_account` for free workspaces, replies link to the plans page on pharaoh.so instead of showing a price and a checkout link.
17
+ - `get_unused_code` says a Dead function's file has no import that could reach it, instead of "file not imported". An import that names other functions from the same file doesn't count, and never did.
18
+ - The Trace card draws its bars in block characters (`█ ▏ ░`) in your client's monospace font instead of solid CSS bars.
19
+ - `/review` works as before, second opinion from Codex or Claude included. It no longer calls itself "the definitive code review", and Phase 0 says that `gh pr view` asks GitHub for the open PR's target branch with your gh login.
20
+ - `/plan` says "This is a review: make no code changes during it" instead of claiming plan mode. A plain "yes" or "yes to all" still doesn't pick BIG or SMALL, so it asks once more; if the reply still doesn't pick one, or you tell it to choose, it picks and says which. `.claude/plan-review.md` adds review criteria, and your own instructions come first.
21
+ - `pharaoh:refactor` runs the six steps its description lists: module context, blast radius, reachability, dependencies, naming conflicts, and a search for the tests to change.
22
+ - Skills install from `npx-skills/`, this package's build of `skills/`.
23
+ - `pharaoh_account` only reads now: your plan, live repo and upgrade link. Switching the live repo and refreshing a repo moved to the new `pharaoh_manage_repo`, which Claude Code asks about before it runs. The interactive setup's allow rules need no change: they already list the read-only tools, `pharaoh_account` among them, and leave `pharaoh_manage_repo` out.
24
+ - Every tool has a title, and the tool descriptions say what each tool returns instead of which other tool to avoid.
25
+ - The steering that used to sit in the server instructions moved to the npx `pharaoh` skill. Registrations are pinned to a version, so it takes effect when you upgrade. Codex gets only the review skill, so it doesn't get this.
26
+ - The `pharaoh` skill's connection URL is `https://mcp.pharaoh.so/mcp` instead of `/sse`. The skill also carries the guidance that was only in the server instructions: one Pharaoh call answers what grep and file reads take many turns to piece together, load Pharaoh's tools before the first code search when your client defers them, summarize a block marked "Trace view available" instead of reproducing it, and check related repos when a change touches shared interfaces.
27
+ - The server instructions state facts: what Pharaoh maps, which tool answers which question, what the graph leaves to grep, how repos are mapped and what each account tool does. Asking Pharaoh first, not re-checking its answers with grep, surfacing and crediting its ★ blocks and the rest of that guidance now live in the `pharaoh` skill. Claude Code and OpenClaw list the skill in every session and load its body only when they use it. The Grep hook and the optional `~/.claude/CLAUDE.md` line are unchanged and apply from the start of a session.
28
+
29
+ ## [0.3.56] - 2026-11-04
30
+
31
+ ### Fixed
32
+ - A connected project that is still mapping during normal setup now starts memory on an ordinary prompt after its mapped revision is verified.
33
+
34
+ ## [0.3.55] - 2026-11-04
35
+
36
+ ### Added
37
+ - Normal interactive setup and upgrades configure local project-memory hooks for accounts with an existing pilot grant. Codex uses its native hook review.
38
+
39
+ ## [0.3.54] - 2026-10-24
40
+
41
+ ### Added
42
+ - The interactive setup (`npx @pharaoh-so/mcp` in a terminal) asks once whether to add one line to `~/.claude/CLAUDE.md`: "In repos Pharaoh has mapped, before searching or reading files, ask Pharaoh where the relevant code is, what calls it, and whether what you need already exists." Enter means yes. In Pharaoh's adoption benchmark, Claude Code called Pharaoh first when this line was in a CLAUDE.md, and rarely when the same sentence came from the server instructions or a plugin hook.
43
+ - The line sits between `<!-- pharaoh-mcp:start … -->` and `<!-- pharaoh-mcp:end -->` markers. Setup appends that block or replaces the line inside it, and nothing else in the file changes. A symlinked file is left alone, and a proxy start (Claude Code launching the server) never asks or writes. Your answer is saved in `~/.pharaoh/claude-md-answer`, so a no stands.
44
+
45
+ ### Changed
46
+ - `--clean` and `--logout` also remove that block, leaving the rest of the file byte for byte, and delete the saved answer so the next setup asks again.
47
+ - `--logout` deletes the saved credentials first, so a cleanup step that fails can't leave them behind.
48
+
8
49
  ## [0.3.53] - 2026-10-23
9
50
 
10
51
  ### Changed
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Pharaoh, Inc.
3
+ Copyright (c) 2026 2407066 Alberta Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/NOTICE ADDED
@@ -0,0 +1,28 @@
1
+ @pharaoh-so/mcp includes third-party material.
2
+
3
+ The brainstorm, debug, tdd and verify skills (skills/ and npx-skills/) are
4
+ adapted from the brainstorming, systematic-debugging, test-driven-development
5
+ and verification-before-completion skills of superpowers
6
+ (https://github.com/obra/superpowers), used under this license:
7
+
8
+ MIT License
9
+
10
+ Copyright (c) 2025 Jesse Vincent
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
package/README.md CHANGED
@@ -5,9 +5,9 @@
5
5
  [![node](https://img.shields.io/node/v/@pharaoh-so/mcp)](https://nodejs.org)
6
6
  [![pharaoh MCP server](https://glama.ai/mcp/servers/Pharaoh-so/pharaoh/badges/score.svg)](https://glama.ai/mcp/servers/Pharaoh-so/pharaoh)
7
7
 
8
- MCP proxy for [Pharaoh](https://pharaoh.so) — maps codebases into queryable knowledge graphs for AI agents.
8
+ MCP proxy for [Pharaoh](https://pharaoh.so) — maps codebases into queryable knowledge graphs for coding agents.
9
9
 
10
- Pharaoh gives AI coding assistants a complete architectural map of your codebase: every function, dependency, module, and connection. Instead of reading files one at a time, your AI agent queries the knowledge graph and gets instant answers about blast radius, unused code, dependency chains, and more.
10
+ Pharaoh gives coding agents such as Claude Code, Cursor, Codex and OpenClaw a complete architectural map of your codebase: every function, dependency, module, and connection. Instead of reading files one at a time, your agent queries the knowledge graph and gets instant answers about blast radius, unused code, dependency chains, and more.
11
11
 
12
12
  This package enables [Claude Code](https://docs.anthropic.com/en/docs/build-with-claude/claude-code) to connect to Pharaoh in **headless environments** (VPS, SSH, containers, CI) where a browser isn't available for OAuth. It acts as a stdio-to-SSE proxy, presenting itself as a local MCP server while relaying all communication to the remote Pharaoh server.
13
13
 
@@ -144,7 +144,7 @@ claude mcp add --scope user pharaoh -- npx @pharaoh-so/mcp@<version> --server ht
144
144
  ## Requirements
145
145
 
146
146
  - **Node.js** >= 18
147
- - **Claude Code** or any MCP-compatible AI client
147
+ - **Claude Code** or any MCP-compatible agent
148
148
  - A **Pharaoh account** — sign up at [pharaoh.so](https://pharaoh.so)
149
149
 
150
150
  ## Security
@@ -244,7 +244,7 @@ Pharaoh parses your repositories using [tree-sitter](https://tree-sitter.github.
244
244
  - **Dependencies** — import/export relationships, call chains, module connections
245
245
  - **Vision specs** — PRD/spec documents linked to implementation
246
246
 
247
- No source code is stored — only structural metadata. When an AI agent queries Pharaoh, it gets architectural facts in minimal tokens, not raw code dumps. This means your AI assistant can understand your entire codebase architecture without consuming its context window reading files one by one.
247
+ No source code is stored — only structural metadata. When an agent queries Pharaoh, it gets architectural facts in minimal tokens, not raw code dumps. This means your agent can understand your entire codebase architecture without consuming its context window reading files one by one.
248
248
 
249
249
  ## Supported Languages
250
250
 
@@ -264,7 +264,7 @@ Contributions are welcome. Please open an issue first to discuss what you'd like
264
264
 
265
265
  ## License
266
266
 
267
- [MIT](LICENSE) -- Pharaoh, Inc.
267
+ [MIT](LICENSE) -- 2407066 Alberta Inc.
268
268
 
269
269
  ## Links
270
270
 
package/commands/plan.md CHANGED
@@ -6,7 +6,7 @@ description: Architecture-aware planning with Pharaoh reconnaissance (adapted fr
6
6
 
7
7
  Architecture-aware plan review before implementation. Adapted from [Garry Tan's planning framework](https://www.youtube.com/watch?v=bMknfKXIFA8) for AI-assisted development.
8
8
 
9
- **You are now in plan mode. Do NOT make any code changes. Think, evaluate, and present decisions.**
9
+ **This is a review: make no code changes during it. Think, evaluate, and present decisions.**
10
10
 
11
11
  ## When to Use
12
12
 
@@ -20,7 +20,7 @@ Still run Step 1 (Reconnaissance) even when reviewing a document — always veri
20
20
 
21
21
  ## Project Overrides
22
22
 
23
- If a `.claude/plan-review.md` file exists in this project, read it now and apply those rules on top of this baseline. Project rules take precedence where they conflict.
23
+ If a `.claude/plan-review.md` file exists in this project, read it now as additional review criteria for this project. Instructions from the user take precedence over it, and it never changes what you do outside this review.
24
24
 
25
25
  ## Engineering Preferences (guide all recommendations)
26
26
 
@@ -56,14 +56,14 @@ Ground every recommendation in what actually exists. If you propose adding somet
56
56
 
57
57
  ## Step 2: Mode Selection (MANDATORY — ask before proceeding)
58
58
 
59
- **STOP and ask the user which mode before starting the review.** This is a hard gate — do not infer, assume, or skip this question even if the user says "yes", "go ahead", or "yes to all". Present both options and wait for an explicit choice:
59
+ **STOP and ask the user which mode before starting the review.** Present both options and wait for a choice:
60
60
 
61
61
  > **BIG CHANGE or SMALL CHANGE?**
62
62
  >
63
63
  > - **BIG CHANGE**: Full interactive review, all relevant sections, up to 4 top issues per section
64
64
  > - **SMALL CHANGE**: One question per section, only sections 2-4
65
65
 
66
- If the user's response is ambiguous (e.g. "just do it", "yes to all"), ask again: "I need to know — BIG or SMALL change?" Do not proceed to Step 3 without an answer.
66
+ A general "yes", "go ahead" or "yes to all" doesn't pick a mode, so ask once more: "BIG or SMALL change?" If the reply still doesn't pick one, or the user tells you to choose, pick the mode that fits the plan's size and say which you picked.
67
67
 
68
68
  ## Step 3: Review Sections
69
69
 
@@ -81,21 +81,21 @@ Adapt depth to change size. Skip sections that don't apply. **After each section
81
81
  - Data flow bottlenecks and single points of failure
82
82
  - Does this need new code at all, or can a human process / existing pattern solve it?
83
83
  - **If this plan introduces a new invariant**, call it out and include a
84
- step in Section 6 to add a `### assertion` to VISION.md with a pairing TEST.
84
+ step in the plan to add a `### assertion` to VISION.md with a pairing TEST.
85
85
 
86
86
  ### Section 2 — Code Quality (always)
87
87
 
88
88
  - Organization, module structure, DRY violations (be aggressive)
89
89
  - Error handling gaps and missing edge cases (call out explicitly)
90
90
  - Technical debt: shortcuts, hardcoded values, magic strings
91
- - Over-engineered or under-engineered relative to my preferences
91
+ - Over-engineered or under-engineered relative to the preferences above
92
92
  - Reuse: does code for this already exist somewhere?
93
93
 
94
94
  ### Section 3 — Wiring & Integration (always)
95
95
 
96
96
  - Are all new exports called from a production entry point?
97
- - Run `get_blast_radius` on any new/changed functions — zero callers = not done
98
- - Does the plan declare WHERE new code gets called from? If not, flag it
97
+ - Run `get_blast_radius` on changed functions that already exist
98
+ - The graph has no functions the plan adds, so does the plan declare WHERE new code gets called from? If not, flag it — no named caller = not done
99
99
  - Integration points: how does this connect to what already exists?
100
100
 
101
101
  ### Section 4 — Tests (always)
@@ -127,16 +127,16 @@ For every specific issue (bug, smell, design concern, risk, missing wiring):
127
127
  1. **Describe concretely** — file, line/function reference, what's wrong
128
128
  2. **Present 2-3 options** including "do nothing" where reasonable
129
129
  3. **For each option** — implementation effort, risk, blast radius, maintenance burden
130
- 4. **Recommend one** mapped to my preferences above, and say why
130
+ 4. **Recommend one** mapped to the preferences above, and say why
131
131
  5. **Ask** whether I agree or want a different direction
132
132
 
133
- Number each issue (1, 2, 3...) and letter each option (A, B, C...). Recommended option is always listed first. Use AskUserQuestion with clear labels like "Issue 1 Option A", "Issue 1 Option B".
133
+ Number each issue (1, 2, 3...) and letter each option (A, B, C...). Recommended option is always listed first. If your client has a question tool (AskUserQuestion in Claude Code), use it with clear labels like "Issue 1 Option A", "Issue 1 Option B".
134
134
 
135
135
  ## Pharaoh Checkpoints (use throughout, not just at the end)
136
136
 
137
137
  - **Before reviewing**: recon (Step 1 above)
138
138
  - **During review**: `get_blast_radius` when evaluating impact of changes; `search_functions` before suggesting new code
139
- - **Final sweep**: `get_blast_radius` on ALL new exports — zero callers on non-entry-points = plan is incomplete
139
+ - **Final sweep**: every new export has a caller named in the plan, and `get_blast_radius` covers every changed export that already exists — zero callers on non-entry-points = plan is incomplete
140
140
 
141
141
  ## Workflow Rules
142
142
 
@@ -4,7 +4,7 @@ description: Architecture-aware code review with Pharaoh
4
4
 
5
5
  # Review with Pharaoh
6
6
 
7
- The definitive code review, for any codebase. Architecture-aware, branch-aware, multi-agent, adversarial. Five phases that combine Pharaoh's knowledge graph with parallel specialized reviewers and an independent second opinion from a different model. Every phase works without Pharaoh too; Pharaoh makes the architecture picture exact and cheap where it maps the repo.
7
+ Architecture-aware, branch-aware, multi-agent, adversarial code review. Five phases that combine Pharaoh's knowledge graph with parallel specialized reviewers and an independent second opinion from a different model. Every phase works without Pharaoh too; Pharaoh makes the architecture picture exact and cheap where it maps the repo.
8
8
 
9
9
  A review always reaches a verdict. When a phase needs something your client doesn't have (Pharaoh, a shell, subagents, a second model), it says how to carry on without it: do that, and name what was missing in the review output. Ask the user only when someone is there to answer.
10
10
 
@@ -30,7 +30,7 @@ No shell, or no git? Review the diff you were given (a file, a pasted patch, a p
30
30
  git log --oneline -1 # latest commit
31
31
  ```
32
32
 
33
- 2. Determine the base (where this branch diverged from the branch it merges into). The target is the first of these that resolves:
33
+ 2. Determine the base (where this branch diverged from the branch it merges into). The target is the first of these that resolves (`gh pr view` asks GitHub, with your own gh login, for the open PR's target branch):
34
34
  ```bash
35
35
  TARGET=$(gh pr view --json baseRefName --jq '"origin/" + .baseRefName' 2>/dev/null) # the open PR's target branch
36
36
  [ -n "$TARGET" ] || TARGET=$(git symbolic-ref -q --short refs/remotes/origin/HEAD) # the default branch, set by git clone
package/dist/auth.d.ts CHANGED
@@ -25,6 +25,11 @@ export interface TokenResponse {
25
25
  github_login?: string;
26
26
  tenant_name?: string;
27
27
  repos?: string[];
28
+ project_memory?: Array<{
29
+ repo: string;
30
+ revision: string | null;
31
+ }>;
32
+ project_memory_status?: "unavailable";
28
33
  provisional?: boolean;
29
34
  install_url?: string;
30
35
  }
@@ -21,6 +21,14 @@ export interface Credentials {
21
21
  github_login: string | null;
22
22
  tenant_name: string | null;
23
23
  repos: string[];
24
+ /** Device sign-in hint only; the server authorizes every memory operation. */
25
+ project_memory?: Array<{
26
+ repo: string;
27
+ revision: string | null;
28
+ }>;
29
+ project_memory_status?: "unavailable";
30
+ /** Local readiness binding, stable only across refreshes of this sign-in. */
31
+ project_memory_session?: string;
24
32
  }
25
33
  /** Default credential file path (route-grep-hook.ts checks the same file to know Pharaoh is signed in). */
26
34
  export declare const CREDENTIALS_PATH: string;
package/dist/gateway.js CHANGED
@@ -105,7 +105,7 @@ export function createGateway(opts) {
105
105
  */
106
106
  function persistAuthorizedToken(token) {
107
107
  const sseUrl = resolveSseUrl(token.sse_url, opts.server);
108
- const creds = tokenToCredentials(token, sseUrl);
108
+ const creds = tokenToCredentials(token, sseUrl, opts.server);
109
109
  writeCredentials(creds, opts.credentialsPath);
110
110
  lastCreds = creds;
111
111
  pending = null;
package/dist/helpers.d.ts CHANGED
@@ -1,13 +1,23 @@
1
- /**
2
- * Pure helper functions for the mcp-proxy CLI — no side effects on import.
3
- * Separated from index.ts so tests can import without triggering main().
4
- */
5
1
  import type { TokenResponse } from "./auth.js";
6
2
  import type { Credentials } from "./credentials.js";
7
3
  /** The npx command users run to set up Pharaoh. Single source of truth for all output. */
8
4
  export declare const NPX_COMMAND = "npx @pharaoh-so/mcp";
9
5
  /** Write one or more lines to stderr. */
10
6
  export declare function printLines(...lines: string[]): void;
7
+ /**
8
+ * Ask a yes/no question that defaults to yes: Enter, `y` or `yes` is yes; `n`
9
+ * or `no` is no; anything else asks again. Ctrl-C exits (130). On a terminal,
10
+ * keys pressed before the question showed (an Enter while sign-in waited) are
11
+ * dropped, so only a key pressed after reading it answers.
12
+ *
13
+ * @param question - The prompt, ending in something like `[Y/n] `.
14
+ * @param input - Where the answer is read (defaults to stdin).
15
+ * @param output - Where the prompt is written (defaults to stderr, like every other line).
16
+ * @returns The answer, or null when input ended before one.
17
+ */
18
+ export declare function askYesNo(question: string, input?: NodeJS.ReadableStream & {
19
+ isTTY?: boolean;
20
+ }, output?: NodeJS.WritableStream): Promise<boolean | null>;
11
21
  /** Parse CLI arguments. */
12
22
  export declare function parseArgs(argv?: string[]): {
13
23
  server: string;
@@ -28,7 +38,7 @@ export declare function resolveSseUrl(tokenSseUrl: string | undefined, server: s
28
38
  * will eventually hit the same silent-expiry cliff we're trying to eliminate.
29
39
  * Fail loud so the server regression surfaces instead of being absorbed.
30
40
  */
31
- export declare function tokenToCredentials(token: TokenResponse, sseUrl: string): Credentials;
41
+ export declare function tokenToCredentials(token: TokenResponse, sseUrl: string, server?: string): Credentials;
32
42
  /** Format remaining TTL as human-readable string (e.g. "5d 12h"). */
33
43
  export declare function formatTtl(expiresAt: string): string;
34
44
  /** Format a credential identity string (e.g. "alice (my-org)"). */
package/dist/helpers.js CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Pure helper functions for the mcp-proxy CLI — no side effects on import.
3
+ * Separated from index.ts so tests can import without triggering main().
4
+ */
5
+ import { randomUUID } from "node:crypto";
6
+ import { createInterface } from "node:readline/promises";
1
7
  import { isExpired } from "./credentials.js";
2
8
  const DEFAULT_SERVER = "https://mcp.pharaoh.so";
3
9
  /** The npx command users run to set up Pharaoh. Single source of truth for all output. */
@@ -6,6 +12,54 @@ export const NPX_COMMAND = "npx @pharaoh-so/mcp";
6
12
  export function printLines(...lines) {
7
13
  process.stderr.write(`${lines.join("\n")}\n`);
8
14
  }
15
+ /** How long to read and drop input that was typed before a question showed. */
16
+ const TYPE_AHEAD_MS = 50;
17
+ /**
18
+ * Ask a yes/no question that defaults to yes: Enter, `y` or `yes` is yes; `n`
19
+ * or `no` is no; anything else asks again. Ctrl-C exits (130). On a terminal,
20
+ * keys pressed before the question showed (an Enter while sign-in waited) are
21
+ * dropped, so only a key pressed after reading it answers.
22
+ *
23
+ * @param question - The prompt, ending in something like `[Y/n] `.
24
+ * @param input - Where the answer is read (defaults to stdin).
25
+ * @param output - Where the prompt is written (defaults to stderr, like every other line).
26
+ * @returns The answer, or null when input ended before one.
27
+ */
28
+ export async function askYesNo(question, input = process.stdin, output = process.stderr) {
29
+ if (input.isTTY)
30
+ await dropTypedAhead(input);
31
+ const rl = createInterface({ input, output });
32
+ rl.on("SIGINT", () => {
33
+ output.write("\n");
34
+ process.exit(130);
35
+ });
36
+ rl.setPrompt(question);
37
+ rl.prompt();
38
+ try {
39
+ // The iterator ends on EOF; rl.question never settles then.
40
+ for await (const reply of rl) {
41
+ const answer = reply.trim().toLowerCase();
42
+ if (answer === "" || answer === "y" || answer === "yes")
43
+ return true;
44
+ if (answer === "n" || answer === "no")
45
+ return false;
46
+ rl.prompt();
47
+ }
48
+ return null;
49
+ }
50
+ finally {
51
+ rl.close();
52
+ }
53
+ }
54
+ /** Read and drop the input already waiting, in raw mode so a half-typed line goes too. */
55
+ async function dropTypedAhead(input) {
56
+ input.setRawMode?.(true);
57
+ const drop = () => { };
58
+ input.on("data", drop);
59
+ await new Promise((resolve) => setTimeout(resolve, TYPE_AHEAD_MS));
60
+ input.off("data", drop);
61
+ input.pause();
62
+ }
9
63
  /** Parse CLI arguments. */
10
64
  export function parseArgs(argv = process.argv.slice(2)) {
11
65
  let server = DEFAULT_SERVER;
@@ -42,7 +96,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
42
96
  return { server, logout };
43
97
  }
44
98
  export function printUsage() {
45
- printLines("Usage: pharaoh-mcp [options]", "", "Options:", " --server <url> Pharaoh server URL (default: https://mcp.pharaoh.so)", " --logout Clear credentials, MCP config and the Claude Code plugin, then exit", " --clean Remove Pharaoh's MCP entries and plugin from Claude Code config", " --install-skills Reinstall the Claude Code plugin and OpenClaw skills", " --version, -v Print the version", " --help, -h Show this help", "", "Get started:", ` ${NPX_COMMAND}`, "");
99
+ printLines("Usage: pharaoh-mcp [options]", "", "Options:", " --server <url> Pharaoh server URL (default: https://mcp.pharaoh.so)", " --logout Clear credentials, MCP config, the Claude Code plugin and the CLAUDE.md line, then exit", " --clean Remove Pharaoh setup, including managed local project memory hooks", " --install-skills Reinstall the Claude Code plugin and OpenClaw skills", " --version, -v Print the version", " --help, -h Show this help", "", "Get started:", ` ${NPX_COMMAND}`, "");
46
100
  }
47
101
  /**
48
102
  * Validate that a server-supplied SSE URL shares the same origin as the configured server.
@@ -74,12 +128,20 @@ export function resolveSseUrl(tokenSseUrl, server) {
74
128
  * will eventually hit the same silent-expiry cliff we're trying to eliminate.
75
129
  * Fail loud so the server regression surfaces instead of being absorbed.
76
130
  */
77
- export function tokenToCredentials(token, sseUrl) {
131
+ export function tokenToCredentials(token, sseUrl, server) {
78
132
  if (!token.refresh_token || !token.refresh_token_expires_in) {
79
133
  throw new Error("Pharaoh server response missing refresh_token — server is older than this client. " +
80
134
  "Update the server to a version that issues refresh tokens, or downgrade the client.");
81
135
  }
82
136
  const now = Date.now();
137
+ const production = server === DEFAULT_SERVER && new URL(sseUrl).origin === DEFAULT_SERVER;
138
+ const memory = production && Array.isArray(token.project_memory)
139
+ ? token.project_memory.filter((entry) => typeof entry?.repo === "string" &&
140
+ /^[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9_.-]{1,100}$/i.test(entry.repo) &&
141
+ (entry.revision === null ||
142
+ (typeof entry.revision === "string" &&
143
+ /^(?!0{40}$)[a-f0-9]{40}$/.test(entry.revision))))
144
+ : [];
83
145
  return {
84
146
  version: 2,
85
147
  access_token: token.access_token,
@@ -91,6 +153,13 @@ export function tokenToCredentials(token, sseUrl) {
91
153
  github_login: token.github_login ?? null,
92
154
  tenant_name: token.tenant_name ?? null,
93
155
  repos: token.repos ?? [],
156
+ ...(memory.length ? { project_memory: memory } : {}),
157
+ ...(memory.some((entry) => entry.revision === null)
158
+ ? { project_memory_session: randomUUID() }
159
+ : {}),
160
+ ...(production && token.project_memory_status === "unavailable"
161
+ ? { project_memory_status: "unavailable" }
162
+ : {}),
94
163
  };
95
164
  }
96
165
  /** Format remaining TTL as human-readable string (e.g. "5d 12h"). */
package/dist/index.js CHANGED
@@ -12,12 +12,13 @@
12
12
  * mode until device flow completes, then swap to SSE in
13
13
  * place — the stdio session never disconnects.
14
14
  */
15
+ import { syncMemoryHooks } from "../project-memory/setup.mjs";
15
16
  import { pollForToken, printActivationPrompt, printAuthSuccess, requestDeviceCode, } from "./auth.js";
16
17
  import { AuthRequiredBackend, createSseBackend } from "./backend.js";
17
18
  import { deleteCredentials, readCredentials, writeCredentials, } from "./credentials.js";
18
19
  import { createGateway } from "./gateway.js";
19
- import { formatTtl, parseArgs, printLines, printUsage, resolveSseUrl, tokenToCredentials, } from "./helpers.js";
20
- import { allowReadTools, describeAllowReadTools, formatKeptCommands, runInstallSkills, } from "./install-skills.js";
20
+ import { askYesNo, formatTtl, parseArgs, printLines, printUsage, resolveSseUrl, tokenToCredentials, } from "./helpers.js";
21
+ import { allowReadTools, CLAUDE_MD_QUESTION, describeAllowReadTools, describeClaudeMdOutcome, formatKeptCommands, offerClaudeMdLine, runInstallSkills, } from "./install-skills.js";
21
22
  import { handleRefreshFailure, tryMidSessionRefresh } from "./lifecycle.js";
22
23
  import { TenantSuspendedError, TokenExpiredError } from "./proxy.js";
23
24
  import { startInAuthRequiredMode, startInSseMode } from "./proxy-server.js";
@@ -31,6 +32,16 @@ const BANNER = [
31
32
  " |_| |_||_/_/ \\_\\_|_\\/_/ \\_\\\\___/|_||_|",
32
33
  "",
33
34
  ].join("\n");
35
+ function reportMemorySetup(options) {
36
+ try {
37
+ const notices = syncMemoryHooks(options);
38
+ if (notices.length)
39
+ printLines(...notices);
40
+ }
41
+ catch {
42
+ printLines("Pharaoh: project memory setup could not update local activation.");
43
+ }
44
+ }
34
45
  async function main() {
35
46
  const args = process.argv.slice(2);
36
47
  if (args.includes("--help") || args.includes("-h")) {
@@ -57,21 +68,26 @@ async function main() {
57
68
  // Clean mode — remove all pharaoh traces from Claude Code config (MCP entries,
58
69
  // disabled states, project-scoped configs). Useful for resetting broken installs.
59
70
  if (args.includes("--clean")) {
60
- const { cleanSlate } = await import("./setup.js");
71
+ const { cleanCodexSetup, cleanSlate } = await import("./setup.js");
61
72
  const { uninstallClaudePlugin } = await import("./install-skills.js");
62
73
  cleanSlate();
63
74
  uninstallClaudePlugin();
64
- printLines("Pharaoh: cleaned all MCP entries and the plugin from Claude Code config");
75
+ cleanCodexSetup();
76
+ reportMemorySetup({ removeLegacy: true });
77
+ printLines("Pharaoh: local setup cleanup finished.");
65
78
  process.exit(0);
66
79
  }
67
80
  const { server, logout } = parseArgs(args);
68
81
  if (logout) {
69
- const { cleanSlate } = await import("./setup.js");
82
+ const { cleanCodexSetup, cleanSlate } = await import("./setup.js");
70
83
  const { uninstallClaudePlugin } = await import("./install-skills.js");
84
+ // Credentials first: a cleanup step that fails must never leave the tokens behind.
85
+ deleteCredentials();
71
86
  cleanSlate();
72
87
  uninstallClaudePlugin();
73
- deleteCredentials();
74
- printLines("Pharaoh: credentials, MCP config and the Claude Code plugin cleared");
88
+ cleanCodexSetup();
89
+ reportMemorySetup({ removeLegacy: true });
90
+ printLines("Pharaoh: credentials cleared and local setup cleanup finished.");
75
91
  process.exit(0);
76
92
  }
77
93
  const isInteractive = Boolean(process.stdin.isTTY);
@@ -80,10 +96,10 @@ async function main() {
80
96
  // Running `npx @pharaoh-so/mcp` is the only command a user needs.
81
97
  if (isInteractive) {
82
98
  process.stderr.write(`${BANNER}\n`);
83
- // Setup registers everything through the `claude` CLI on PATH. Without it,
84
- // stop before sign-in: nothing is written and saved credentials stay.
85
- const { claudeCliMissing, hasClaude, runSetup } = await import("./setup.js");
86
- if (!hasClaude()) {
99
+ const { claudeCliMissing, hasClaude, hasCodex, runCodexSetup, runSetup } = await import("./setup.js");
100
+ const claude = hasClaude();
101
+ const codex = hasCodex();
102
+ if (!claude && !codex) {
87
103
  printLines(...claudeCliMissing());
88
104
  process.exit(1);
89
105
  }
@@ -96,23 +112,32 @@ async function main() {
96
112
  printLines(`Pharaoh: provisional access — install the GitHub App to map your repos: ${token.install_url ?? ""}`);
97
113
  }
98
114
  const sseUrl = resolveSseUrl(token.sse_url, server);
99
- const newCreds = tokenToCredentials(token, sseUrl);
115
+ const newCreds = tokenToCredentials(token, sseUrl, server);
100
116
  writeCredentials(newCreds);
101
117
  printAuthSuccess(token.github_login ?? null, token.tenant_name ?? null, token.repos?.length ?? 0);
102
- // Register MCP server + install skills (silent — user doesn't need internals).
103
- // runSetup has printed the failure and the command to run by hand; no success line.
104
- if (!runSetup({ silent: true }))
118
+ const clients = [];
119
+ if (claude && runSetup({ silent: true }))
120
+ clients.push("claude-code");
121
+ // Existing Claude-only installs stay unchanged for nonpilot accounts.
122
+ if (codex && (!claude || newCreds.project_memory?.length)) {
123
+ if (runCodexSetup())
124
+ clients.push("codex");
125
+ }
126
+ if (!clients.length)
105
127
  process.exit(1);
106
- process.stderr.write(" Setting up...");
107
- const { keptCommands } = runInstallSkills(undefined, { silent: true });
108
- process.stderr.write(" done.\n");
109
- // Two things the user must hear about: their settings.json (the only
110
- // place this setup edits permissions, never a silent start) and any
111
- // command file of theirs that was left alone.
112
- printLines(describeAllowReadTools(allowReadTools()));
113
- const kept = formatKeptCommands(keptCommands);
114
- if (kept)
115
- printLines(kept);
128
+ if (clients.includes("claude-code")) {
129
+ process.stderr.write(" Setting up...");
130
+ const { keptCommands } = runInstallSkills(undefined, { silent: true });
131
+ process.stderr.write(" done.\n");
132
+ printLines(describeAllowReadTools(allowReadTools()));
133
+ const kept = formatKeptCommands(keptCommands);
134
+ if (kept)
135
+ printLines(kept);
136
+ const claudeMd = describeClaudeMdOutcome(await offerClaudeMdLine(() => askYesNo(CLAUDE_MD_QUESTION), Boolean(process.stdin.isTTY && process.stderr.isTTY)));
137
+ if (claudeMd)
138
+ printLines(claudeMd);
139
+ }
140
+ reportMemorySetup({ credentials: newCreds, clients });
116
141
  // The registration is pinned to this version; upgrading is a deliberate
117
142
  // re-run. Interactive only, and quiet when the registry can't be reached.
118
143
  const latest = await fetchLatestVersion();
@@ -120,7 +145,7 @@ async function main() {
120
145
  if (latest && isNewerVersion(latest, running)) {
121
146
  printLines(`Pharaoh: ${latest} is available (this is ${running}). Upgrade: ${UPGRADE_COMMAND}`);
122
147
  }
123
- printLines("", "Pharaoh is ready. Try these in a new Claude Code conversation:", "", ' "What modules does this codebase have?"', ' "Search for functions related to authentication"', ' "What breaks if I change this file?"', "");
148
+ printLines("", "Pharaoh is ready. Try these in a new conversation:", "", ' "What modules does this codebase have?"', ' "Search for functions related to authentication"', ' "What breaks if I change this file?"', "");
124
149
  process.exit(0);
125
150
  }
126
151
  // ── Proxy mode (Claude Code spawned us as a stdio MCP server) ──
@@ -7,8 +7,9 @@ import { type ClaudeCli } from "./setup.js";
7
7
  */
8
8
  export declare function claudePluginState(home?: string): "enabled" | "disabled" | "absent";
9
9
  /**
10
- * Uninstall Pharaoh's Claude Code plugin: through the CLI, then the local
11
- * marketplace directory and any pre-marketplace install.
10
+ * Uninstall Pharaoh's Claude Code plugin (through the CLI, then the local
11
+ * marketplace directory and any pre-marketplace install) and its block in
12
+ * ~/.claude/CLAUDE.md. Run by --clean and --logout.
12
13
  *
13
14
  * @param home - Home directory override (defaults to os.homedir()).
14
15
  * @param claude - `claude` CLI runner (defaults to the real CLI for `home`).
@@ -55,10 +56,11 @@ export declare function installSkills(home?: string): number;
55
56
  export declare function mergeOpenClawConfig(home?: string): boolean;
56
57
  /**
57
58
  * Rules the interactive setup adds to `permissions.allow` in
58
- * ~/.claude/settings.json: the six read-only tools and `pharaoh_account`,
59
+ * ~/.claude/settings.json: the seven read-only tools (`readOnlyHint: true`),
59
60
  * each by full name. Never a prefix rule — `mcp__pharaoh` also auto-approves
60
- * account deletion and `pharaoh_admin`, so a prompt injection could run them
61
- * with no human in the loop (security review SC-6). Those tools keep prompting.
61
+ * the tools that change things (`pharaoh_manage_repo`, which queues a re-map,
62
+ * and the superadmin tools), so a prompt injection could run them with
63
+ * no human in the loop (security review SC-6). Those tools keep prompting.
62
64
  */
63
65
  export declare const PHARAOH_ALLOW_RULES: readonly string[];
64
66
  /** What `allowReadTools` did to ~/.claude/settings.json. */
@@ -83,6 +85,29 @@ export declare function allowReadTools(home?: string): AllowReadToolsResult;
83
85
  * @param result - What `allowReadTools` returned.
84
86
  */
85
87
  export declare function describeAllowReadTools(result: AllowReadToolsResult): string;
88
+ /** What the interactive setup asks before writing the line. */
89
+ export declare const CLAUDE_MD_QUESTION = "Pharaoh: add one line to ~/.claude/CLAUDE.md so Claude checks Pharaoh before searching your code?\n \"In repos Pharaoh has mapped, before searching or reading files, ask Pharaoh where the relevant code is, what calls it, and whether what you need already exists.\"\n[Y/n] ";
90
+ /** What `offerClaudeMdLine` did; `describeClaudeMdOutcome` words it. */
91
+ export type ClaudeMdOutcome = "added" | "updated" | "declined" | "kept" | "failed" | "current" | "answered" | "cancelled" | "no-terminal" | "no-claude";
92
+ /**
93
+ * Offer the Pharaoh-first line in ~/.claude/CLAUDE.md. Interactive setup only,
94
+ * never a proxy start. Asks only when the block is absent and the user hasn't
95
+ * answered before; a stale block (an earlier yes) is updated without asking.
96
+ * The line is appended inside markers, and nothing else in the file changes.
97
+ *
98
+ * @param ask - Asks the user: true for yes, false for no, null when input ended.
99
+ * @param isTerminal - Whether stdin and stderr are a terminal; false asks and writes nothing.
100
+ * @param home - Home directory override (defaults to os.homedir()).
101
+ * @returns What happened, for `describeClaudeMdOutcome`.
102
+ */
103
+ export declare function offerClaudeMdLine(ask: () => Promise<boolean | null>, isTerminal: boolean, home?: string): Promise<ClaudeMdOutcome>;
104
+ /**
105
+ * The line the interactive setup prints for an `offerClaudeMdLine` outcome, or
106
+ * null when there is nothing to say (already current, already answered, no terminal).
107
+ *
108
+ * @param outcome - What `offerClaudeMdLine` returned.
109
+ */
110
+ export declare function describeClaudeMdOutcome(outcome: ClaudeMdOutcome): string | null;
86
111
  /** What `runInstallSkills` wants the caller to tell the user. */
87
112
  export interface InstallResult {
88
113
  /** Files in ~/.claude/commands/ left as they were because they are not Pharaoh's. */