@pharaoh-so/mcp 0.3.54 → 0.3.57

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 +36 -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 +1 -1
  12. package/dist/helpers.js +15 -2
  13. package/dist/index.js +47 -28
  14. package/dist/install-skills.d.ts +4 -3
  15. package/dist/install-skills.js +12 -8
  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 +269 -0
  32. package/project-memory/hook-config.mjs +91 -0
  33. package/project-memory/hook-events.mjs +117 -0
  34. package/project-memory/local.mjs +480 -0
  35. package/project-memory/memory-api.mjs +200 -0
  36. package/project-memory/pharaoh-memory-hook.mjs +47 -0
  37. package/project-memory/pharaoh-memory.mjs +60 -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 +235 -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.54"
6
+ "@pharaoh-so/mcp@0.3.57"
7
7
  ]
8
8
  }
9
9
  }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,42 @@ 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.57] - 2026-11-04
30
+
31
+ ### Changed
32
+ - Project memory uses compact recall by default. The agent can save one candidate during the task, and completion ends without a memory follow-up.
33
+
34
+ ## [0.3.56] - 2026-11-04
35
+
36
+ ### Fixed
37
+ - A connected project that is still mapping during normal setup now starts memory on an ordinary prompt after its mapped revision is verified.
38
+
39
+ ## [0.3.55] - 2026-11-04
40
+
41
+ ### Added
42
+ - Normal interactive setup and upgrades configure local project-memory hooks for accounts with an existing pilot grant. Codex uses its native hook review.
43
+
8
44
  ## [0.3.54] - 2026-10-24
9
45
 
10
46
  ### Added
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
@@ -38,7 +38,7 @@ export declare function resolveSseUrl(tokenSseUrl: string | undefined, server: s
38
38
  * will eventually hit the same silent-expiry cliff we're trying to eliminate.
39
39
  * Fail loud so the server regression surfaces instead of being absorbed.
40
40
  */
41
- export declare function tokenToCredentials(token: TokenResponse, sseUrl: string): Credentials;
41
+ export declare function tokenToCredentials(token: TokenResponse, sseUrl: string, server?: string): Credentials;
42
42
  /** Format remaining TTL as human-readable string (e.g. "5d 12h"). */
43
43
  export declare function formatTtl(expiresAt: string): string;
44
44
  /** Format a credential identity string (e.g. "alice (my-org)"). */
package/dist/helpers.js CHANGED
@@ -2,6 +2,7 @@
2
2
  * Pure helper functions for the mcp-proxy CLI — no side effects on import.
3
3
  * Separated from index.ts so tests can import without triggering main().
4
4
  */
5
+ import { randomUUID } from "node:crypto";
5
6
  import { createInterface } from "node:readline/promises";
6
7
  import { isExpired } from "./credentials.js";
7
8
  const DEFAULT_SERVER = "https://mcp.pharaoh.so";
@@ -95,7 +96,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
95
96
  return { server, logout };
96
97
  }
97
98
  export function printUsage() {
98
- 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's MCP entries, plugin and CLAUDE.md line from Claude Code", " --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}`, "");
99
100
  }
100
101
  /**
101
102
  * Validate that a server-supplied SSE URL shares the same origin as the configured server.
@@ -127,12 +128,20 @@ export function resolveSseUrl(tokenSseUrl, server) {
127
128
  * will eventually hit the same silent-expiry cliff we're trying to eliminate.
128
129
  * Fail loud so the server regression surfaces instead of being absorbed.
129
130
  */
130
- export function tokenToCredentials(token, sseUrl) {
131
+ export function tokenToCredentials(token, sseUrl, server) {
131
132
  if (!token.refresh_token || !token.refresh_token_expires_in) {
132
133
  throw new Error("Pharaoh server response missing refresh_token — server is older than this client. " +
133
134
  "Update the server to a version that issues refresh tokens, or downgrade the client.");
134
135
  }
135
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
+ : [];
136
145
  return {
137
146
  version: 2,
138
147
  access_token: token.access_token,
@@ -144,6 +153,10 @@ export function tokenToCredentials(token, sseUrl) {
144
153
  github_login: token.github_login ?? null,
145
154
  tenant_name: token.tenant_name ?? null,
146
155
  repos: token.repos ?? [],
156
+ ...(memory.length ? { project_memory: memory, project_memory_session: randomUUID() } : {}),
157
+ ...(production && token.project_memory_status === "unavailable"
158
+ ? { project_memory_status: "unavailable" }
159
+ : {}),
147
160
  };
148
161
  }
149
162
  /** Format remaining TTL as human-readable string (e.g. "5d 12h"). */
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@
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";
@@ -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,22 +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: removed its MCP entries, plugin and ~/.claude/CLAUDE.md line from Claude Code");
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");
71
84
  // Credentials first: a cleanup step that fails must never leave the tokens behind.
72
85
  deleteCredentials();
73
86
  cleanSlate();
74
87
  uninstallClaudePlugin();
75
- printLines("Pharaoh: credentials, MCP config, the Claude Code plugin and the ~/.claude/CLAUDE.md line cleared");
88
+ cleanCodexSetup();
89
+ reportMemorySetup({ removeLegacy: true });
90
+ printLines("Pharaoh: credentials cleared and local setup cleanup finished.");
76
91
  process.exit(0);
77
92
  }
78
93
  const isInteractive = Boolean(process.stdin.isTTY);
@@ -81,10 +96,10 @@ async function main() {
81
96
  // Running `npx @pharaoh-so/mcp` is the only command a user needs.
82
97
  if (isInteractive) {
83
98
  process.stderr.write(`${BANNER}\n`);
84
- // Setup registers everything through the `claude` CLI on PATH. Without it,
85
- // stop before sign-in: nothing is written and saved credentials stay.
86
- const { claudeCliMissing, hasClaude, runSetup } = await import("./setup.js");
87
- 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) {
88
103
  printLines(...claudeCliMissing());
89
104
  process.exit(1);
90
105
  }
@@ -97,28 +112,32 @@ async function main() {
97
112
  printLines(`Pharaoh: provisional access — install the GitHub App to map your repos: ${token.install_url ?? ""}`);
98
113
  }
99
114
  const sseUrl = resolveSseUrl(token.sse_url, server);
100
- const newCreds = tokenToCredentials(token, sseUrl);
115
+ const newCreds = tokenToCredentials(token, sseUrl, server);
101
116
  writeCredentials(newCreds);
102
117
  printAuthSuccess(token.github_login ?? null, token.tenant_name ?? null, token.repos?.length ?? 0);
103
- // Register MCP server + install skills (silent — user doesn't need internals).
104
- // runSetup has printed the failure and the command to run by hand; no success line.
105
- 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)
106
127
  process.exit(1);
107
- process.stderr.write(" Setting up...");
108
- const { keptCommands } = runInstallSkills(undefined, { silent: true });
109
- process.stderr.write(" done.\n");
110
- // Two things the user must hear about: their settings.json (the only
111
- // place this setup edits permissions, never a silent start) and any
112
- // command file of theirs that was left alone.
113
- printLines(describeAllowReadTools(allowReadTools()));
114
- const kept = formatKeptCommands(keptCommands);
115
- if (kept)
116
- printLines(kept);
117
- // The one line agents act on (bench #1305) goes in the user's own
118
- // ~/.claude/CLAUDE.md, so it's asked about once, here, never on a start.
119
- const claudeMd = describeClaudeMdOutcome(await offerClaudeMdLine(() => askYesNo(CLAUDE_MD_QUESTION), Boolean(process.stdin.isTTY && process.stderr.isTTY)));
120
- if (claudeMd)
121
- printLines(claudeMd);
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 });
122
141
  // The registration is pinned to this version; upgrading is a deliberate
123
142
  // re-run. Interactive only, and quiet when the registry can't be reached.
124
143
  const latest = await fetchLatestVersion();
@@ -126,7 +145,7 @@ async function main() {
126
145
  if (latest && isNewerVersion(latest, running)) {
127
146
  printLines(`Pharaoh: ${latest} is available (this is ${running}). Upgrade: ${UPGRADE_COMMAND}`);
128
147
  }
129
- 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?"', "");
130
149
  process.exit(0);
131
150
  }
132
151
  // ── Proxy mode (Claude Code spawned us as a stdio MCP server) ──
@@ -56,10 +56,11 @@ export declare function installSkills(home?: string): number;
56
56
  export declare function mergeOpenClawConfig(home?: string): boolean;
57
57
  /**
58
58
  * Rules the interactive setup adds to `permissions.allow` in
59
- * ~/.claude/settings.json: the six read-only tools and `pharaoh_account`,
59
+ * ~/.claude/settings.json: the seven read-only tools (`readOnlyHint: true`),
60
60
  * each by full name. Never a prefix rule — `mcp__pharaoh` also auto-approves
61
- * account deletion and `pharaoh_admin`, so a prompt injection could run them
62
- * 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.
63
64
  */
64
65
  export declare const PHARAOH_ALLOW_RULES: readonly string[];
65
66
  /** What `allowReadTools` did to ~/.claude/settings.json. */
@@ -16,8 +16,11 @@ import { getPackageVersion, PACKAGE_NAME, pinnedPackageSpec } from "./version.js
16
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
17
  /** Path to the bundled package root (one level up from dist/). */
18
18
  const PKG_ROOT = join(__dirname, "..");
19
- /** Path to the bundled skills directory. */
20
- const BUNDLED_SKILLS_DIR = join(PKG_ROOT, "skills");
19
+ /**
20
+ * Path to the bundled skills: npx's render of skills/ (written by the build's
21
+ * generate:commands), so installs copy them as-is and never see channel markers.
22
+ */
23
+ const BUNDLED_SKILLS_DIR = join(PKG_ROOT, "npx-skills");
21
24
  /** Path to the bundled slash commands directory. */
22
25
  const BUNDLED_COMMANDS_DIR = join(PKG_ROOT, "commands");
23
26
  /** Path to the bundled Claude Code hooks (hooks.json and the Grep routing hook). */
@@ -606,10 +609,11 @@ export function mergeOpenClawConfig(home = homedir()) {
606
609
  // ── Claude Code permissions ─────────────────────────────────────────
607
610
  /**
608
611
  * Rules the interactive setup adds to `permissions.allow` in
609
- * ~/.claude/settings.json: the six read-only tools and `pharaoh_account`,
612
+ * ~/.claude/settings.json: the seven read-only tools (`readOnlyHint: true`),
610
613
  * each by full name. Never a prefix rule — `mcp__pharaoh` also auto-approves
611
- * account deletion and `pharaoh_admin`, so a prompt injection could run them
612
- * with no human in the loop (security review SC-6). Those tools keep prompting.
614
+ * the tools that change things (`pharaoh_manage_repo`, which queues a re-map,
615
+ * and the superadmin tools), so a prompt injection could run them with
616
+ * no human in the loop (security review SC-6). Those tools keep prompting.
613
617
  */
614
618
  export const PHARAOH_ALLOW_RULES = [
615
619
  "mcp__pharaoh__get_codebase_map",
@@ -666,11 +670,11 @@ export function describeAllowReadTools(result) {
666
670
  const count = PHARAOH_ALLOW_RULES.length;
667
671
  switch (result) {
668
672
  case "added":
669
- return `Pharaoh: allowed ${count} tools by name in ~/.claude/settings.json (permissions.allow): the read-only tools and pharaoh_account. Account deletion and pharaoh_admin still prompt.`;
673
+ return `Pharaoh: allowed ${count} read-only tools by name in ~/.claude/settings.json (permissions.allow). pharaoh_manage_repo, which switches or refreshes a repo, still asks first.`;
670
674
  case "migrated":
671
- return `Pharaoh: replaced the blanket mcp__pharaoh rule in ~/.claude/settings.json with ${count} tools by name. Account deletion and pharaoh_admin prompt again.`;
675
+ return `Pharaoh: replaced the blanket mcp__pharaoh rule in ~/.claude/settings.json with ${count} read-only tools by name. pharaoh_manage_repo asks first again.`;
672
676
  case "current":
673
- return `Pharaoh: ~/.claude/settings.json already allows the ${count} read-only and account tools by name.`;
677
+ return `Pharaoh: ~/.claude/settings.json already allows the ${count} read-only tools by name.`;
674
678
  case "no-settings":
675
679
  return "Pharaoh: no ~/.claude/settings.json yet, so Pharaoh's tools ask before they run. The rules to allow the read-only tools are in the README (Security).";
676
680
  case "error":
package/dist/refresh.d.ts CHANGED
@@ -48,10 +48,11 @@ export type RefreshFailureAction = "reauth" | "preserve" | "transient";
48
48
  * @param server Pharaoh server base URL (e.g. "https://mcp.pharaoh.so")
49
49
  * @param path optional custom credentials path (for tests)
50
50
  * @param timeoutMs how long the refresh request may take (for tests)
51
+ * @param strictOrigin require credentials from this origin and refuse refresh redirects
51
52
  * @returns New or adopted credentials, or a classified failure. A lock held by
52
53
  * another proxy for too long is `transient` (credentials untouched).
53
54
  */
54
- export declare function refreshIfNeeded(creds: Credentials, server: string, path?: string, timeoutMs?: number): Promise<RefreshOutcome>;
55
+ export declare function refreshIfNeeded(creds: Credentials, server: string, path?: string, timeoutMs?: number, strictOrigin?: boolean): Promise<RefreshOutcome>;
55
56
  /**
56
57
  * Read credentials from disk and refresh if the access token is expired.
57
58
  *
@@ -63,4 +64,9 @@ export declare function refreshIfNeeded(creds: Credentials, server: string, path
63
64
  * Intended use: called at proxy startup to replace the current "die if expired"
64
65
  * branch in index.ts.
65
66
  */
66
- export declare function loadAndRefresh(server: string, path?: string): Promise<RefreshOutcome | null>;
67
+ export declare function loadAndRefresh(server: string, path?: string, strictOrigin?: boolean): Promise<RefreshOutcome | null>;
68
+ /** Refresh for a client that accepts credentials only from its configured origin. */
69
+ export declare function originBoundRefresh(server: string, path?: string): {
70
+ load: () => Promise<RefreshOutcome | null>;
71
+ renew: (creds: Credentials) => Promise<RefreshOutcome>;
72
+ };