@phren/cli 0.0.26 → 0.0.28

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.
@@ -325,6 +325,7 @@ export async function runTopLevelCommand(argv) {
325
325
  console.error(`Invalid --task-proactivity value "${taskArg}". Use one of: high, medium, low.`);
326
326
  return finish(1);
327
327
  }
328
+ const cloneUrl = getOptionValue(initArgs, "--clone-url");
328
329
  await runInit({
329
330
  mode: modeArg,
330
331
  machine: machineIdx !== -1 ? initArgs[machineIdx + 1] : undefined,
@@ -338,6 +339,7 @@ export async function runTopLevelCommand(argv) {
338
339
  applyStarterUpdate: initArgs.includes("--apply-starter-update"),
339
340
  dryRun: initArgs.includes("--dry-run"),
340
341
  yes: initArgs.includes("--yes") || initArgs.includes("-y"),
342
+ _walkthroughCloneUrl: cloneUrl,
341
343
  });
342
344
  return finish();
343
345
  }
package/mcp/dist/init.js CHANGED
@@ -85,19 +85,16 @@ function runSyncCommand(command, args) {
85
85
  }
86
86
  }
87
87
  function shouldUninstallCurrentGlobalPackage() {
88
- const entryScript = process.argv[1];
89
- if (!entryScript)
90
- return false;
88
+ // Always attempt to remove the global package if it exists, regardless of
89
+ // whether the uninstaller was invoked from the global install or a local repo.
91
90
  const npmRootResult = runSyncCommand(getNpmCommand(), ["root", "-g"]);
92
91
  if (!npmRootResult.ok)
93
92
  return false;
94
93
  const npmRoot = npmRootResult.stdout.trim();
95
94
  if (!npmRoot)
96
95
  return false;
97
- const resolvedEntryScript = path.resolve(entryScript);
98
- const resolvedGlobalPackageRoot = path.resolve(path.join(npmRoot, PHREN_NPM_PACKAGE_NAME));
99
- return resolvedEntryScript === resolvedGlobalPackageRoot
100
- || resolvedEntryScript.startsWith(`${resolvedGlobalPackageRoot}${path.sep}`);
96
+ const globalPkgPath = path.join(npmRoot, PHREN_NPM_PACKAGE_NAME);
97
+ return fs.existsSync(globalPkgPath);
101
98
  }
102
99
  function uninstallCurrentGlobalPackage() {
103
100
  const result = runSyncCommand(getNpmCommand(), ["uninstall", "-g", PHREN_NPM_PACKAGE_NAME]);
@@ -1682,8 +1679,29 @@ function sweepSkillSymlinks(phrenPath) {
1682
1679
  log(` Removed skill symlink: ${fullPath}`);
1683
1680
  }
1684
1681
  }
1682
+ catch {
1683
+ // Broken symlink (target no longer exists) — clean it up
1684
+ try {
1685
+ fs.unlinkSync(fullPath);
1686
+ log(` Removed broken skill symlink: ${fullPath}`);
1687
+ }
1688
+ catch (err2) {
1689
+ debugLog(`sweepSkillSymlinks: could not remove broken symlink ${fullPath}: ${errorMessage(err2)}`);
1690
+ }
1691
+ }
1692
+ }
1693
+ // Remove phren-generated manifest files from the skills parent directory
1694
+ const parentDir = path.dirname(dir);
1695
+ for (const manifestFile of ["skill-manifest.json", "skill-commands.json"]) {
1696
+ const manifestPath = path.join(parentDir, manifestFile);
1697
+ try {
1698
+ if (fs.existsSync(manifestPath)) {
1699
+ fs.unlinkSync(manifestPath);
1700
+ log(` Removed ${manifestFile} (${manifestPath})`);
1701
+ }
1702
+ }
1685
1703
  catch (err) {
1686
- debugLog(`sweepSkillSymlinks: could not check/remove ${fullPath}: ${errorMessage(err)}`);
1704
+ debugLog(`sweepSkillSymlinks: could not remove ${manifestPath}: ${errorMessage(err)}`);
1687
1705
  }
1688
1706
  }
1689
1707
  }
@@ -2008,6 +2026,33 @@ export async function runUninstall(opts = {}) {
2008
2026
  if (shouldRemoveGlobalPackage) {
2009
2027
  uninstallCurrentGlobalPackage();
2010
2028
  }
2029
+ // Remove VS Code extension if installed
2030
+ try {
2031
+ const codeResult = execFileSync("code", ["--list-extensions"], {
2032
+ encoding: "utf8",
2033
+ stdio: ["ignore", "pipe", "ignore"],
2034
+ timeout: 10_000,
2035
+ });
2036
+ const phrenExts = codeResult.split("\n").filter((ext) => ext.toLowerCase().includes("phren"));
2037
+ for (const ext of phrenExts) {
2038
+ const trimmed = ext.trim();
2039
+ if (!trimmed)
2040
+ continue;
2041
+ try {
2042
+ execFileSync("code", ["--uninstall-extension", trimmed], {
2043
+ stdio: ["ignore", "pipe", "ignore"],
2044
+ timeout: 15_000,
2045
+ });
2046
+ log(` Removed VS Code extension (${trimmed})`);
2047
+ }
2048
+ catch (err) {
2049
+ debugLog(`uninstall: VS Code extension removal failed for ${trimmed}: ${errorMessage(err)}`);
2050
+ }
2051
+ }
2052
+ }
2053
+ catch {
2054
+ // code CLI not available — skip
2055
+ }
2011
2056
  log(`\nPhren config, hooks, and installed data removed.`);
2012
2057
  log(`Restart your agent(s) to apply changes.\n`);
2013
2058
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phren/cli",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "Knowledge layer for AI agents. Phren learns and recalls.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,8 @@
10
10
  "mcp/dist",
11
11
  "icon.svg",
12
12
  "starter",
13
- "skills"
13
+ "skills",
14
+ "scripts/preuninstall.mjs"
14
15
  ],
15
16
  "dependencies": {
16
17
  "@modelcontextprotocol/sdk": "^1.27.1",
@@ -19,7 +20,7 @@
19
20
  "glob": "^13.0.6",
20
21
  "graphology": "^0.26.0",
21
22
  "graphology-layout-forceatlas2": "^0.10.1",
22
- "inquirer": "^12.10.0",
23
+ "inquirer": "^13.3.2",
23
24
  "js-yaml": "^4.1.1",
24
25
  "sigma": "^3.0.2",
25
26
  "sql.js-fts5": "^1.4.0",
@@ -28,14 +29,14 @@
28
29
  "devDependencies": {
29
30
  "@playwright/test": "^1.58.2",
30
31
  "@types/js-yaml": "^4.0.9",
31
- "@types/node": "^25.3.5",
32
- "@typescript-eslint/eslint-plugin": "^8.56.1",
33
- "@typescript-eslint/parser": "^8.56.1",
34
- "@vitest/coverage-v8": "^4.0.18",
35
- "eslint": "^10.0.3",
32
+ "@types/node": "^25.5.0",
33
+ "@typescript-eslint/eslint-plugin": "^8.57.1",
34
+ "@typescript-eslint/parser": "^8.57.1",
35
+ "@vitest/coverage-v8": "^4.1.0",
36
+ "eslint": "^10.1.0",
36
37
  "tsx": "^4.21.0",
37
38
  "typescript": "^5.9.3",
38
- "vitest": "^4.0.18"
39
+ "vitest": "^4.1.0"
39
40
  },
40
41
  "scripts": {
41
42
  "build": "node scripts/build.mjs",
@@ -49,6 +50,7 @@
49
50
  "bench": "tsx mcp/bench/locomo-runner.ts --sessions 3",
50
51
  "bench:retrieval": "tsx scripts/bench-retrieval-modes.ts",
51
52
  "bench:retrieval:synthetic": "tsx scripts/bench-retrieval-synthetic.ts",
53
+ "preuninstall": "node scripts/preuninstall.mjs",
52
54
  "prepublishOnly": "npm run build && npm test"
53
55
  },
54
56
  "engines": {
@@ -67,5 +69,9 @@
67
69
  "type": "git",
68
70
  "url": "git+https://github.com/alaarab/phren.git"
69
71
  },
70
- "homepage": "https://github.com/alaarab/phren#readme"
72
+ "homepage": "https://github.com/alaarab/phren#readme",
73
+ "overrides": {
74
+ "flatted": "^3.4.2",
75
+ "undici": "^7.10.0"
76
+ }
71
77
  }
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * npm preuninstall hook — clean up hooks and MCP server entries from agent
5
+ * config files so they don't become orphaned when the package is removed
6
+ * via `npm uninstall -g @phren/cli` (bypassing `phren uninstall`).
7
+ *
8
+ * Only removes config/hooks — does NOT touch ~/.phren data.
9
+ */
10
+
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
16
+ const homePath = (...parts) => path.join(home, ...parts);
17
+
18
+ /** Read JSON, apply mutator, write back. */
19
+ function patchJson(filePath, mutator) {
20
+ if (!fs.existsSync(filePath)) return;
21
+ try {
22
+ const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
23
+ mutator(data);
24
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
25
+ } catch {
26
+ // best-effort — don't fail the uninstall
27
+ }
28
+ }
29
+
30
+ /** Check if a hook command string belongs to phren. */
31
+ function isPhrenCommand(cmd) {
32
+ return (
33
+ cmd.includes("@phren/cli") ||
34
+ cmd.includes("phren/cli") ||
35
+ /\bhook-(prompt|stop|session-start|tool|context)\b/.test(cmd)
36
+ );
37
+ }
38
+
39
+ // ── Claude Code settings.json: remove hooks + MCP server ──
40
+ const claudeSettings = homePath(".claude", "settings.json");
41
+ patchJson(claudeSettings, (data) => {
42
+ // Remove MCP server
43
+ if (data.mcpServers?.phren) delete data.mcpServers.phren;
44
+
45
+ // Remove phren hooks
46
+ if (data.hooks && typeof data.hooks === "object") {
47
+ for (const event of ["UserPromptSubmit", "Stop", "SessionStart", "PostToolUse"]) {
48
+ const entries = data.hooks[event];
49
+ if (!Array.isArray(entries)) continue;
50
+ data.hooks[event] = entries.filter(
51
+ (entry) => !entry.hooks?.some((h) => typeof h.command === "string" && isPhrenCommand(h.command))
52
+ );
53
+ if (data.hooks[event].length === 0) delete data.hooks[event];
54
+ }
55
+ if (Object.keys(data.hooks).length === 0) delete data.hooks;
56
+ }
57
+ });
58
+
59
+ // ── Claude Code ~/.claude.json: remove MCP server ──
60
+ const claudeJson = homePath(".claude.json");
61
+ patchJson(claudeJson, (data) => {
62
+ if (data.mcpServers?.phren) delete data.mcpServers.phren;
63
+ });
64
+
65
+ // ── VS Code MCP configs ──
66
+ const vscodeMcpCandidates = [
67
+ homePath(".vscode-server", "data", "User", "mcp.json"),
68
+ homePath("AppData", "Roaming", "Code", "User", "mcp.json"),
69
+ homePath(".config", "Code", "User", "mcp.json"),
70
+ ];
71
+ for (const mcpFile of vscodeMcpCandidates) {
72
+ patchJson(mcpFile, (data) => {
73
+ // servers array format
74
+ if (Array.isArray(data.servers)) {
75
+ data.servers = data.servers.filter((s) => s.name !== "phren");
76
+ }
77
+ // mcpServers object format
78
+ if (data.mcpServers?.phren) delete data.mcpServers.phren;
79
+ });
80
+ }
81
+
82
+ // ── Cursor MCP config ──
83
+ const cursorMcpCandidates = [
84
+ homePath(".cursor", "mcp.json"),
85
+ ];
86
+ for (const mcpFile of cursorMcpCandidates) {
87
+ patchJson(mcpFile, (data) => {
88
+ if (data.mcpServers?.phren) delete data.mcpServers.phren;
89
+ });
90
+ }
91
+
92
+ // ── Cursor hooks ──
93
+ const cursorHooks = homePath(".cursor", "hooks.json");
94
+ patchJson(cursorHooks, (data) => {
95
+ for (const key of ["sessionStart", "beforeSubmitPrompt", "stop"]) {
96
+ if (data[key]?.command && typeof data[key].command === "string" && isPhrenCommand(data[key].command)) {
97
+ delete data[key];
98
+ }
99
+ }
100
+ });
101
+
102
+ // ── Copilot hooks ──
103
+ const copilotHooks = homePath(".github", "hooks", "phren.json");
104
+ if (fs.existsSync(copilotHooks)) {
105
+ try { fs.unlinkSync(copilotHooks); } catch { /* best-effort */ }
106
+ }
107
+
108
+ console.log("phren: cleaned up hooks and MCP config from agent settings.");
@@ -1,35 +0,0 @@
1
- ---
2
- name: pipeline
3
- description: Tell Claude which stage of development you're in and get guidance on what to focus on next.
4
- ---
5
- # /pipeline - Where am I in the workflow
6
-
7
- Tell Claude which stage of development you're in, and get guidance on what to focus on next.
8
-
9
- ## Stages
10
-
11
- 1. **planning** - sketching ideas, designing, deciding what to build
12
- 2. **coding** - writing implementation, building features
13
- 3. **testing** - running tests, verifying behavior, catching bugs
14
- 4. **review** - code review, feedback, making sure it's ready
15
- 5. **shipped** - released, deployed, users have it
16
-
17
- ## What to do
18
-
19
- 1. Look at recent git commits to understand what's been done recently.
20
- 2. Check open files in the editor to see what's actively being worked on.
21
- 3. Ask the user: "What stage are you in?" if unclear.
22
- 4. Tell the user:
23
- - **Current stage**: e.g. "You're in the coding stage"
24
- - **What to focus on next**: e.g. "Once you finish this component, write tests for it. Don't move to review until tests pass."
25
-
26
- ## Examples
27
-
28
- - "You've got 10 failing tests. You're in testing. Run tests again after each fix, don't batch them."
29
- - "You've got code ready but no CHANGELOG entry. That's review stage. Update the changelog, then you're ready to ship."
30
- - "You've got 3 open PRs awaiting feedback. You're in review. Respond to comments or start something new while you wait."
31
-
32
- ## Customize
33
-
34
- - Adjust the stages to match your workflow (you might have a "documentation" or "deployment" stage)
35
- - Add what "done" means for each stage in your project
@@ -1,35 +0,0 @@
1
- ---
2
- name: release
3
- description: Get from "code is ready" to "users have it" with a version bump, changelog, tag, and publish checklist.
4
- ---
5
- # /release - Ship a version
6
-
7
- Get from "code is ready" to "users have it" with a checklist.
8
-
9
- ## Steps
10
-
11
- 1. **Confirm version bump**: Ask for patch, minor, or major (if not specified). Auto-detect from git history or ask the user.
12
- 2. **Update CHANGELOG.md**: List what changed in this release. Format varies (keep-changelog, conventional commits, etc).
13
- 3. **Bump version**: Update package.json, Cargo.toml, pyproject.toml, or whatever your project uses.
14
- 4. **Git tag**: Create a git tag: `git tag vX.Y.Z` and push it.
15
- 5. **Publish**: Run your publish command (`npm publish`, `cargo publish`, `poetry publish`, etc).
16
-
17
- ## Example flow
18
-
19
- ```
20
- You: "/release"
21
- Claude: "What version bump? (patch/minor/major)"
22
- You: "minor"
23
- Claude: "Got it. You're going from 1.2.3 to 1.3.0.
24
- - Updated CHANGELOG.md with new features
25
- - Bumped version in package.json
26
- - Created tag v1.3.0
27
- - Published to npm
28
- Ready to verify it's live?"
29
- ```
30
-
31
- ## Customize
32
-
33
- - Update the publish command for your stack (e.g. `cargo publish` for Rust, `twine upload` for Python)
34
- - Adjust changelog format to match your conventions
35
- - Add any pre-publish checks you need (tests, linting, etc)