@pharaoh-so/mcp 0.2.4 → 0.2.5

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pharaoh",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Codebase knowledge graph with 23 development workflow skills. Query architecture, dependencies, blast radius, and more instead of reading files one at a time.",
5
5
  "author": {
6
6
  "name": "Pharaoh",
package/dist/index.js CHANGED
@@ -10,10 +10,11 @@
10
10
  * - Interactive (TTY): authenticate, print setup instructions, exit.
11
11
  * - Proxy (pipe): require pre-existing credentials, bridge stdio ↔ SSE.
12
12
  */
13
- import { printActivationPrompt, printAuthSuccess, pollForToken, requestDeviceCode, } from "./auth.js";
13
+ import { pollForToken, printActivationPrompt, printAuthSuccess, requestDeviceCode, } from "./auth.js";
14
14
  import { deleteCredentials, isExpired, readCredentials, writeCredentials } from "./credentials.js";
15
15
  import { formatIdentity, formatTtl, parseArgs, printLines, printSetupInstructions, printUsage, resolveSseUrl, tokenToCredentials, } from "./helpers.js";
16
- import { TokenExpiredError, TenantSuspendedError, startProxy } from "./proxy.js";
16
+ import { runInstallSkills } from "./install-skills.js";
17
+ import { startProxy, TenantSuspendedError, TokenExpiredError } from "./proxy.js";
17
18
  async function main() {
18
19
  const args = process.argv.slice(2);
19
20
  if (args.includes("--help") || args.includes("-h")) {
@@ -46,6 +47,8 @@ async function main() {
46
47
  if (isInteractive) {
47
48
  if (creds && !isExpired(creds)) {
48
49
  printLines(`Pharaoh: authenticated as ${formatIdentity(creds)} — token valid for ${formatTtl(creds.expires_at)}, ${creds.repos.length} repo${creds.repos.length === 1 ? "" : "s"} connected`);
50
+ // Ensure skills are installed/up-to-date on every interactive run
51
+ runInstallSkills();
49
52
  printSetupInstructions();
50
53
  process.exit(0);
51
54
  }
@@ -61,6 +64,8 @@ async function main() {
61
64
  const newCreds = tokenToCredentials(token, sseUrl);
62
65
  writeCredentials(newCreds);
63
66
  printAuthSuccess(token.github_login ?? null, token.tenant_name ?? null, token.repos?.length ?? 0);
67
+ // Auto-install skills to detected platforms (Claude Code, OpenClaw)
68
+ runInstallSkills();
64
69
  printSetupInstructions();
65
70
  process.exit(0);
66
71
  }
@@ -70,7 +75,14 @@ async function main() {
70
75
  printLines("Pharaoh: no valid credentials — cannot start proxy.", "Run this command first to authenticate:", " npx @pharaoh-so/mcp", "");
71
76
  process.exit(1);
72
77
  }
73
- // Valid credentials — start the proxy
78
+ // Valid credentials — ensure skills are installed before starting proxy
79
+ // Silently installs/updates on first run or package update; fast no-op if current.
80
+ try {
81
+ runInstallSkills();
82
+ }
83
+ catch {
84
+ // ALLOWED-CATCH: skill install failure must not block MCP proxy startup
85
+ }
74
86
  printLines(`Pharaoh: token valid for ${formatTtl(creds.expires_at)} — connecting`);
75
87
  try {
76
88
  await startProxy(creds.sse_url, creds.access_token);
@@ -14,6 +14,16 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
14
14
  const PKG_ROOT = join(__dirname, "..");
15
15
  /** Path to the bundled skills directory. */
16
16
  const BUNDLED_SKILLS_DIR = join(PKG_ROOT, "skills");
17
+ /** Read the package version from package.json (single source of truth). */
18
+ function getPackageVersion() {
19
+ try {
20
+ const pkg = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf-8"));
21
+ return pkg.version ?? "0.0.0";
22
+ }
23
+ catch {
24
+ return "0.0.0";
25
+ }
26
+ }
17
27
  // ── Detection ───────────────────────────────────────────────────────
18
28
  /**
19
29
  * Detect whether Claude Code is installed by checking for ~/.claude/.
@@ -72,37 +82,61 @@ function installClaudeCodePlugin(home = homedir()) {
72
82
  return entries.filter((e) => e.isDirectory()).length;
73
83
  }
74
84
  /**
75
- * Register Pharaoh in Claude Code's installed_plugins.json.
76
- * Does NOT overwrite an existing entry.
85
+ * Register Pharaoh in Claude Code's installed_plugins.json (v2 format).
86
+ * Adds a new entry or updates the version/lastUpdated of an existing one.
87
+ * Preserves all other plugin entries.
77
88
  *
78
89
  * @param home - Home directory override (defaults to os.homedir()).
79
- * @returns True if the entry was added, false if already present.
90
+ * @returns "added" if new entry created, "updated" if version bumped, "current" if already up-to-date, "error" on failure.
80
91
  */
81
92
  function registerClaudeCodePlugin(home = homedir()) {
82
93
  const registryPath = join(home, ".claude", "plugins", "installed_plugins.json");
83
94
  const pluginKey = "pharaoh@pharaoh-so";
84
- let registry = {};
95
+ const pluginDir = join(home, ".claude", "plugins", "data", "pharaoh");
96
+ const version = getPackageVersion();
97
+ const now = new Date().toISOString();
98
+ let registry = { version: 2, plugins: {} };
85
99
  if (existsSync(registryPath)) {
86
100
  try {
87
- registry = JSON.parse(readFileSync(registryPath, "utf-8"));
101
+ const raw = JSON.parse(readFileSync(registryPath, "utf-8"));
102
+ // Handle both v2 format (with plugins key) and any other shape
103
+ if (raw.version === 2 && raw.plugins && typeof raw.plugins === "object") {
104
+ registry = raw;
105
+ }
106
+ else {
107
+ // Non-v2 file — preserve as-is, add v2 structure around it
108
+ registry = { version: 2, plugins: {}, ...raw };
109
+ if (!registry.plugins)
110
+ registry.plugins = {};
111
+ }
88
112
  }
89
113
  catch {
90
114
  process.stderr.write("Pharaoh: installed_plugins.json exists but is not valid JSON — skipping registration.\n");
91
- return false;
115
+ return "error";
92
116
  }
93
117
  }
94
- if (registry[pluginKey]) {
95
- return false;
96
- }
97
- registry[pluginKey] = {
98
- name: "pharaoh",
99
- version: "0.2.0",
100
- source: "npm:@pharaoh-so/mcp",
101
- pluginRoot: join(home, ".claude", "plugins", "data", "pharaoh"),
102
- installedAt: new Date().toISOString(),
118
+ const existing = registry.plugins[pluginKey];
119
+ const entry = {
120
+ scope: "user",
121
+ installPath: pluginDir,
122
+ version,
123
+ installedAt: now,
124
+ lastUpdated: now,
103
125
  };
126
+ if (existing && existing.length > 0) {
127
+ const current = existing[0];
128
+ if (current.version === version) {
129
+ return "current";
130
+ }
131
+ // Version changed — update in place, preserve original installedAt
132
+ entry.installedAt = current.installedAt || now;
133
+ registry.plugins[pluginKey] = [entry];
134
+ writeFileSync(registryPath, JSON.stringify(registry, null, "\t"));
135
+ return "updated";
136
+ }
137
+ registry.plugins[pluginKey] = [entry];
104
138
  writeFileSync(registryPath, JSON.stringify(registry, null, "\t"));
105
- return true;
139
+ return "added";
106
140
  }
107
141
  // ── OpenClaw installer ──────────────────────────────────────────────
108
142
  /**
@@ -181,17 +215,19 @@ export function runInstallSkills(home = homedir()) {
181
215
  if (hasClaudeCode) {
182
216
  const count = installClaudeCodePlugin(home);
183
217
  if (count >= 0) {
184
- const registered = registerClaudeCodePlugin(home);
185
- const regMsg = registered
186
- ? "Plugin registered in installed_plugins.json"
187
- : "Plugin already registered skipped";
218
+ const regResult = registerClaudeCodePlugin(home);
219
+ const regMessages = {
220
+ added: "Plugin registered in installed_plugins.json",
221
+ updated: "Plugin version updated in installed_plugins.json",
222
+ current: "Plugin already registered and up-to-date",
223
+ error: "Could not update installed_plugins.json",
224
+ };
188
225
  process.stderr.write([
189
226
  `Pharaoh: installed ${count} skills as Claude Code plugin`,
190
227
  ` → ~/.claude/plugins/data/pharaoh/`,
191
- ` → ${regMsg}`,
192
- "",
193
- "Restart Claude Code to pick up the new skills.",
228
+ ` → ${regMessages[regResult]}`,
194
229
  "",
230
+ ...(regResult === "added" ? ["Restart Claude Code to pick up the new skills.", ""] : []),
195
231
  ].join("\n"));
196
232
  installed = true;
197
233
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pharaoh-so/mcp",
3
3
  "mcpName": "so.pharaoh/pharaoh",
4
- "version": "0.2.4",
4
+ "version": "0.2.5",
5
5
  "description": "MCP proxy for Pharaoh — maps codebases into queryable knowledge graphs for AI agents. Enables Claude Code in headless environments (VPS, SSH, CI) via device flow auth.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",