@phren/cli 0.0.24 → 0.0.26
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/init.js +66 -1
- package/mcp/dist/link-doctor.js +29 -6
- package/package.json +1 -1
package/mcp/dist/init.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as fs from "fs";
|
|
6
6
|
import * as path from "path";
|
|
7
7
|
import * as crypto from "crypto";
|
|
8
|
-
import { execFileSync } from "child_process";
|
|
8
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
9
9
|
import { configureAllHooks } from "./hooks.js";
|
|
10
10
|
import { getMachineName, machineFilePath, persistMachineName } from "./machine-identity.js";
|
|
11
11
|
import { atomicWriteText, debugLog, isRecord, hookConfigPath, homeDir, homePath, expandHomePath, findPhrenPath, getProjectDirs, readRootManifest, writeRootManifest, } from "./shared.js";
|
|
@@ -24,6 +24,7 @@ import { DEFAULT_PHREN_PATH, STARTER_DIR, VERSION, log, confirmPrompt } from "./
|
|
|
24
24
|
import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, } from "./project-config.js";
|
|
25
25
|
import { getWorkflowPolicy, updateWorkflowPolicy } from "./shared-governance.js";
|
|
26
26
|
import { addProjectToProfile } from "./profile-store.js";
|
|
27
|
+
const PHREN_NPM_PACKAGE_NAME = "@phren/cli";
|
|
27
28
|
function parseVersion(version) {
|
|
28
29
|
const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?/);
|
|
29
30
|
if (!match)
|
|
@@ -58,6 +59,56 @@ export function isVersionNewer(current, previous) {
|
|
|
58
59
|
return true;
|
|
59
60
|
return c.pre > p.pre;
|
|
60
61
|
}
|
|
62
|
+
function getNpmCommand() {
|
|
63
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
64
|
+
}
|
|
65
|
+
function runSyncCommand(command, args) {
|
|
66
|
+
try {
|
|
67
|
+
const result = spawnSync(command, args, {
|
|
68
|
+
encoding: "utf8",
|
|
69
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
ok: result.status === 0,
|
|
73
|
+
status: result.status,
|
|
74
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
75
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
status: null,
|
|
82
|
+
stdout: "",
|
|
83
|
+
stderr: errorMessage(err),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function shouldUninstallCurrentGlobalPackage() {
|
|
88
|
+
const entryScript = process.argv[1];
|
|
89
|
+
if (!entryScript)
|
|
90
|
+
return false;
|
|
91
|
+
const npmRootResult = runSyncCommand(getNpmCommand(), ["root", "-g"]);
|
|
92
|
+
if (!npmRootResult.ok)
|
|
93
|
+
return false;
|
|
94
|
+
const npmRoot = npmRootResult.stdout.trim();
|
|
95
|
+
if (!npmRoot)
|
|
96
|
+
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}`);
|
|
101
|
+
}
|
|
102
|
+
function uninstallCurrentGlobalPackage() {
|
|
103
|
+
const result = runSyncCommand(getNpmCommand(), ["uninstall", "-g", PHREN_NPM_PACKAGE_NAME]);
|
|
104
|
+
if (result.ok) {
|
|
105
|
+
log(` Removed global npm package (${PHREN_NPM_PACKAGE_NAME})`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const detail = result.stderr.trim() || result.stdout.trim() || (result.status === null ? "failed to start command" : `exit code ${result.status}`);
|
|
109
|
+
log(` Warning: could not remove global npm package (${PHREN_NPM_PACKAGE_NAME})`);
|
|
110
|
+
debugLog(`uninstall: global npm cleanup failed: ${detail}`);
|
|
111
|
+
}
|
|
61
112
|
export function parseMcpMode(raw) {
|
|
62
113
|
if (!raw)
|
|
63
114
|
return undefined;
|
|
@@ -1732,6 +1783,7 @@ export async function runUninstall(opts = {}) {
|
|
|
1732
1783
|
return;
|
|
1733
1784
|
}
|
|
1734
1785
|
log("\nUninstalling phren...\n");
|
|
1786
|
+
const shouldRemoveGlobalPackage = shouldUninstallCurrentGlobalPackage();
|
|
1735
1787
|
// Confirmation prompt (shared-mode only — project-local is low-stakes)
|
|
1736
1788
|
if (!opts.yes) {
|
|
1737
1789
|
const confirmed = phrenPath
|
|
@@ -1924,6 +1976,16 @@ export async function runUninstall(opts = {}) {
|
|
|
1924
1976
|
catch (err) {
|
|
1925
1977
|
debugLog(`uninstall: cleanup failed for ${machineFile}: ${errorMessage(err)}`);
|
|
1926
1978
|
}
|
|
1979
|
+
const contextFile = homePath(".phren-context.md");
|
|
1980
|
+
try {
|
|
1981
|
+
if (fs.existsSync(contextFile)) {
|
|
1982
|
+
fs.unlinkSync(contextFile);
|
|
1983
|
+
log(` Removed machine context file (${contextFile})`);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
catch (err) {
|
|
1987
|
+
debugLog(`uninstall: cleanup failed for ${contextFile}: ${errorMessage(err)}`);
|
|
1988
|
+
}
|
|
1927
1989
|
// Sweep agent skill directories for symlinks pointing into the phren store
|
|
1928
1990
|
if (phrenPath) {
|
|
1929
1991
|
try {
|
|
@@ -1943,6 +2005,9 @@ export async function runUninstall(opts = {}) {
|
|
|
1943
2005
|
log(` Warning: could not remove phren root (${phrenPath})`);
|
|
1944
2006
|
}
|
|
1945
2007
|
}
|
|
2008
|
+
if (shouldRemoveGlobalPackage) {
|
|
2009
|
+
uninstallCurrentGlobalPackage();
|
|
2010
|
+
}
|
|
1946
2011
|
log(`\nPhren config, hooks, and installed data removed.`);
|
|
1947
2012
|
log(`Restart your agent(s) to apply changes.\n`);
|
|
1948
2013
|
}
|
package/mcp/dist/link-doctor.js
CHANGED
|
@@ -17,6 +17,7 @@ import { repairPreexistingInstall } from "./init-setup.js";
|
|
|
17
17
|
import { getMachineName, lookupProfile, findProfileFile, getProfileProjects, findProjectDir, } from "./link.js";
|
|
18
18
|
import { claudeProjectKey } from "./link-context.js";
|
|
19
19
|
import { getProjectOwnershipMode, readProjectConfig } from "./project-config.js";
|
|
20
|
+
import { readInstallPreferences } from "./init-preferences.js";
|
|
20
21
|
// ── Doctor ──────────────────────────────────────────────────────────────────
|
|
21
22
|
function isWrapperActive(tool) {
|
|
22
23
|
const wrapperPath = homePath(".local", "bin", tool);
|
|
@@ -35,7 +36,7 @@ function isWrapperActive(tool) {
|
|
|
35
36
|
return false;
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
|
-
function gitRemoteStatus(phrenPath) {
|
|
39
|
+
function gitRemoteStatus(phrenPath, syncIntent) {
|
|
39
40
|
try {
|
|
40
41
|
execFileSync("git", ["-C", phrenPath, "rev-parse", "--is-inside-work-tree"], {
|
|
41
42
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -45,19 +46,40 @@ function gitRemoteStatus(phrenPath) {
|
|
|
45
46
|
catch {
|
|
46
47
|
return { ok: false, detail: "phren path is not a git repository" };
|
|
47
48
|
}
|
|
49
|
+
let remote;
|
|
48
50
|
try {
|
|
49
|
-
|
|
51
|
+
remote = execFileSync("git", ["-C", phrenPath, "remote", "get-url", "origin"], {
|
|
50
52
|
encoding: "utf8",
|
|
51
53
|
stdio: ["ignore", "pipe", "ignore"],
|
|
52
54
|
timeout: EXEC_TIMEOUT_QUICK_MS,
|
|
53
55
|
}).trim();
|
|
54
|
-
return remote
|
|
55
|
-
? { ok: true, detail: `origin=${remote}` }
|
|
56
|
-
: { ok: true, detail: "no remote configured (local-only sync mode)" };
|
|
57
56
|
}
|
|
58
57
|
catch {
|
|
58
|
+
// no remote configured
|
|
59
|
+
}
|
|
60
|
+
if (!remote) {
|
|
61
|
+
if (syncIntent === "sync") {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
detail: "sync configured but no git remote found. Run: cd ~/.phren && git remote add origin <YOUR_REPO_URL> && git push -u origin main",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
59
67
|
return { ok: true, detail: "no remote configured (local-only sync mode)" };
|
|
60
68
|
}
|
|
69
|
+
// Remote exists — verify it's reachable
|
|
70
|
+
try {
|
|
71
|
+
execFileSync("git", ["-C", phrenPath, "ls-remote", "--exit-code", "origin"], {
|
|
72
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
73
|
+
timeout: 10_000,
|
|
74
|
+
});
|
|
75
|
+
return { ok: true, detail: `origin=${remote}` };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
if (syncIntent === "sync") {
|
|
79
|
+
return { ok: false, detail: `origin=${remote} (unreachable) — check your network or SSH keys` };
|
|
80
|
+
}
|
|
81
|
+
return { ok: true, detail: `origin=${remote} (unreachable, local-only mode)` };
|
|
82
|
+
}
|
|
61
83
|
}
|
|
62
84
|
function pushSkillMirrorChecks(checks, scope, manifest, destDir) {
|
|
63
85
|
const parentDir = path.dirname(destDir);
|
|
@@ -117,7 +139,8 @@ export async function runDoctor(phrenPath, fix = false, checkData = false) {
|
|
|
117
139
|
ok: versionAtLeast(nodeVersion, 20),
|
|
118
140
|
detail: nodeVersion || "node not found in PATH",
|
|
119
141
|
});
|
|
120
|
-
const
|
|
142
|
+
const prefs = readInstallPreferences(phrenPath);
|
|
143
|
+
const gitRemote = gitRemoteStatus(phrenPath, prefs.syncIntent);
|
|
121
144
|
checks.push({
|
|
122
145
|
name: "git-remote",
|
|
123
146
|
ok: gitRemote.ok,
|