@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.
- package/mcp/dist/entrypoint.js +2 -0
- package/mcp/dist/init.js +53 -8
- package/package.json +16 -10
- package/scripts/preuninstall.mjs +108 -0
- package/starter/global/skills/pipeline.md +0 -35
- package/starter/global/skills/release.md +0 -35
package/mcp/dist/entrypoint.js
CHANGED
|
@@ -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
|
-
|
|
89
|
-
|
|
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
|
|
98
|
-
|
|
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
|
|
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.
|
|
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": "^
|
|
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.
|
|
32
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
33
|
-
"@typescript-eslint/parser": "^8.
|
|
34
|
-
"@vitest/coverage-v8": "^4.0
|
|
35
|
-
"eslint": "^10.0
|
|
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
|
|
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)
|