@phren/cli 0.0.23 → 0.0.25
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-setup.js
CHANGED
|
@@ -290,9 +290,10 @@ export function repairPreexistingInstall(phrenPath) {
|
|
|
290
290
|
function isExpectedVerifyFailure(phrenPath, check) {
|
|
291
291
|
if (check.ok)
|
|
292
292
|
return false;
|
|
293
|
-
if (check.name === "git-remote")
|
|
294
|
-
return true;
|
|
295
293
|
const prefs = readInstallPreferences(phrenPath);
|
|
294
|
+
// git-remote failure is only expected when the user chose local-only (no clone URL)
|
|
295
|
+
if (check.name === "git-remote")
|
|
296
|
+
return prefs.syncIntent !== "sync";
|
|
296
297
|
if (check.name === "mcp-config" && prefs.mcpEnabled === false)
|
|
297
298
|
return true;
|
|
298
299
|
if (check.name === "hooks-registered" && prefs.hooksEnabled === false)
|
|
@@ -339,17 +340,30 @@ function gitRemoteStatus(phrenPath) {
|
|
|
339
340
|
catch {
|
|
340
341
|
return { ok: false, detail: "phren path is not a git repository" };
|
|
341
342
|
}
|
|
343
|
+
let remote;
|
|
342
344
|
try {
|
|
343
|
-
|
|
345
|
+
remote = execFileSync("git", ["-C", phrenPath, "remote", "get-url", "origin"], {
|
|
344
346
|
encoding: "utf8",
|
|
345
347
|
stdio: ["ignore", "pipe", "ignore"],
|
|
346
348
|
timeout: EXEC_TIMEOUT_QUICK_MS,
|
|
347
349
|
}).trim();
|
|
348
|
-
|
|
350
|
+
if (!remote)
|
|
351
|
+
return { ok: false, detail: "git origin remote not configured" };
|
|
349
352
|
}
|
|
350
353
|
catch {
|
|
351
354
|
return { ok: false, detail: "git origin remote not configured" };
|
|
352
355
|
}
|
|
356
|
+
// Connectivity test: verify the remote is reachable (10s timeout)
|
|
357
|
+
try {
|
|
358
|
+
execFileSync("git", ["-C", phrenPath, "ls-remote", "--exit-code", "origin"], {
|
|
359
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
360
|
+
timeout: 10_000,
|
|
361
|
+
});
|
|
362
|
+
return { ok: true, detail: `origin=${remote}` };
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return { ok: false, detail: `origin=${remote} (configured but unreachable)` };
|
|
366
|
+
}
|
|
353
367
|
}
|
|
354
368
|
function copyStarterFile(phrenPath, src, dest) {
|
|
355
369
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
@@ -1147,14 +1161,22 @@ export function runPostInitVerify(phrenPath) {
|
|
|
1147
1161
|
}
|
|
1148
1162
|
else {
|
|
1149
1163
|
const gitRemote = gitRemoteStatus(phrenPath);
|
|
1164
|
+
const wantSync = prefs.syncIntent === "sync";
|
|
1150
1165
|
const gitRemoteDetail = gitRemote.ok
|
|
1151
1166
|
? gitRemote.detail
|
|
1152
|
-
:
|
|
1167
|
+
: wantSync
|
|
1168
|
+
? `${gitRemote.detail} — sync was configured but remote is missing or unreachable`
|
|
1169
|
+
: `${gitRemote.detail} (optional unless you want cross-machine sync)`;
|
|
1170
|
+
const gitRemoteFix = gitRemote.ok
|
|
1171
|
+
? undefined
|
|
1172
|
+
: wantSync
|
|
1173
|
+
? `Your clone URL didn't work. Fix: cd ${phrenPath} && git remote add origin <URL> && git push -u origin main`
|
|
1174
|
+
: "Optional: initialize a repo and add an origin remote for cross-machine sync.";
|
|
1153
1175
|
checks.push({
|
|
1154
1176
|
name: "git-remote",
|
|
1155
1177
|
ok: gitRemote.ok,
|
|
1156
1178
|
detail: gitRemoteDetail,
|
|
1157
|
-
fix:
|
|
1179
|
+
fix: gitRemoteFix,
|
|
1158
1180
|
});
|
|
1159
1181
|
const settingsPath = hookConfigPath("claude");
|
|
1160
1182
|
const configWritable = nearestWritableTarget(settingsPath);
|
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;
|
|
@@ -1037,9 +1088,23 @@ export async function runInit(opts = {}) {
|
|
|
1037
1088
|
}
|
|
1038
1089
|
catch (e) {
|
|
1039
1090
|
log(` Clone failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1040
|
-
log(
|
|
1091
|
+
log("");
|
|
1092
|
+
log(" ┌──────────────────────────────────────────────────────────────────┐");
|
|
1093
|
+
log(" │ WARNING: Sync is NOT configured. Your phren data is local-only. │");
|
|
1094
|
+
log(" │ │");
|
|
1095
|
+
log(" │ To fix later: │");
|
|
1096
|
+
log(` │ cd ${phrenPath}`);
|
|
1097
|
+
log(" │ git remote add origin <YOUR_REPO_URL> │");
|
|
1098
|
+
log(" │ git push -u origin main │");
|
|
1099
|
+
log(" └──────────────────────────────────────────────────────────────────┘");
|
|
1100
|
+
log("");
|
|
1101
|
+
log(` Continuing with fresh local-only install.`);
|
|
1041
1102
|
}
|
|
1042
1103
|
}
|
|
1104
|
+
// Record sync intent: "sync" if a clone URL was provided (regardless of success), "local" otherwise.
|
|
1105
|
+
// On re-runs of existing installs, preserve the existing syncIntent unless the user provided a new clone URL.
|
|
1106
|
+
const existingSyncIntent = hasExistingInstall ? readInstallPreferences(phrenPath).syncIntent : undefined;
|
|
1107
|
+
const syncIntent = opts._walkthroughCloneUrl ? "sync" : (existingSyncIntent ?? "local");
|
|
1043
1108
|
const mcpEnabled = opts.mcp ? opts.mcp === "on" : getMcpEnabledPreference(phrenPath);
|
|
1044
1109
|
const hooksEnabled = opts.hooks ? opts.hooks === "on" : getHooksEnabledPreference(phrenPath);
|
|
1045
1110
|
const skillsScope = opts.skillsScope ?? "global";
|
|
@@ -1194,7 +1259,7 @@ export async function runInit(opts = {}) {
|
|
|
1194
1259
|
log(` No starter template updates were applied (starter files not found).`);
|
|
1195
1260
|
}
|
|
1196
1261
|
}
|
|
1197
|
-
writeInstallPreferences(phrenPath, { mcpEnabled, hooksEnabled, skillsScope, installedVersion: VERSION });
|
|
1262
|
+
writeInstallPreferences(phrenPath, { mcpEnabled, hooksEnabled, skillsScope, installedVersion: VERSION, syncIntent });
|
|
1198
1263
|
if (repaired.removedLegacyProjects > 0) {
|
|
1199
1264
|
log(` Removed ${repaired.removedLegacyProjects} legacy starter project entr${repaired.removedLegacyProjects === 1 ? "y" : "ies"} from profiles.`);
|
|
1200
1265
|
}
|
|
@@ -1372,7 +1437,7 @@ export async function runInit(opts = {}) {
|
|
|
1372
1437
|
// Configure MCP for all detected AI coding tools and hooks
|
|
1373
1438
|
configureMcpTargets(phrenPath, { mcpEnabled, hooksEnabled }, "Configured");
|
|
1374
1439
|
configureHooksIfEnabled(phrenPath, hooksEnabled, "Configured");
|
|
1375
|
-
writeInstallPreferences(phrenPath, { mcpEnabled, hooksEnabled, skillsScope, installedVersion: VERSION });
|
|
1440
|
+
writeInstallPreferences(phrenPath, { mcpEnabled, hooksEnabled, skillsScope, installedVersion: VERSION, syncIntent });
|
|
1376
1441
|
// Post-init verification
|
|
1377
1442
|
log(`\nVerifying setup...`);
|
|
1378
1443
|
const verify = runPostInitVerify(phrenPath);
|
|
@@ -1718,6 +1783,7 @@ export async function runUninstall(opts = {}) {
|
|
|
1718
1783
|
return;
|
|
1719
1784
|
}
|
|
1720
1785
|
log("\nUninstalling phren...\n");
|
|
1786
|
+
const shouldRemoveGlobalPackage = shouldUninstallCurrentGlobalPackage();
|
|
1721
1787
|
// Confirmation prompt (shared-mode only — project-local is low-stakes)
|
|
1722
1788
|
if (!opts.yes) {
|
|
1723
1789
|
const confirmed = phrenPath
|
|
@@ -1910,6 +1976,16 @@ export async function runUninstall(opts = {}) {
|
|
|
1910
1976
|
catch (err) {
|
|
1911
1977
|
debugLog(`uninstall: cleanup failed for ${machineFile}: ${errorMessage(err)}`);
|
|
1912
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
|
+
}
|
|
1913
1989
|
// Sweep agent skill directories for symlinks pointing into the phren store
|
|
1914
1990
|
if (phrenPath) {
|
|
1915
1991
|
try {
|
|
@@ -1929,6 +2005,9 @@ export async function runUninstall(opts = {}) {
|
|
|
1929
2005
|
log(` Warning: could not remove phren root (${phrenPath})`);
|
|
1930
2006
|
}
|
|
1931
2007
|
}
|
|
2008
|
+
if (shouldRemoveGlobalPackage) {
|
|
2009
|
+
uninstallCurrentGlobalPackage();
|
|
2010
|
+
}
|
|
1932
2011
|
log(`\nPhren config, hooks, and installed data removed.`);
|
|
1933
2012
|
log(`Restart your agent(s) to apply changes.\n`);
|
|
1934
2013
|
}
|
package/mcp/dist/mcp-ops.js
CHANGED
|
@@ -163,15 +163,52 @@ export function register(server, ctx) {
|
|
|
163
163
|
if ((process.env.PHREN_DEBUG))
|
|
164
164
|
process.stderr.write(`[phren] healthCheck taskMode: ${errorMessage(err)}\n`);
|
|
165
165
|
}
|
|
166
|
+
let syncIntent;
|
|
166
167
|
try {
|
|
167
168
|
const { readInstallPreferences } = await import("./init-preferences.js");
|
|
168
169
|
const prefs = readInstallPreferences(phrenPath);
|
|
169
170
|
proactivity = prefs.proactivity || "high";
|
|
171
|
+
syncIntent = prefs.syncIntent;
|
|
170
172
|
}
|
|
171
173
|
catch (err) {
|
|
172
174
|
if ((process.env.PHREN_DEBUG))
|
|
173
175
|
process.stderr.write(`[phren] healthCheck proactivity: ${errorMessage(err)}\n`);
|
|
174
176
|
}
|
|
177
|
+
// Determine sync status from intent + git remote state
|
|
178
|
+
let syncStatus = "local-only";
|
|
179
|
+
let syncDetail = "no git remote configured";
|
|
180
|
+
try {
|
|
181
|
+
const { execFileSync } = await import("child_process");
|
|
182
|
+
const remote = execFileSync("git", ["-C", phrenPath, "remote", "get-url", "origin"], {
|
|
183
|
+
encoding: "utf8",
|
|
184
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
185
|
+
timeout: 5_000,
|
|
186
|
+
}).trim();
|
|
187
|
+
if (remote) {
|
|
188
|
+
try {
|
|
189
|
+
execFileSync("git", ["-C", phrenPath, "ls-remote", "--exit-code", "origin"], {
|
|
190
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
191
|
+
timeout: 10_000,
|
|
192
|
+
});
|
|
193
|
+
syncStatus = "synced";
|
|
194
|
+
syncDetail = `origin=${remote}`;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
syncStatus = syncIntent === "sync" ? "broken" : "local-only";
|
|
198
|
+
syncDetail = `origin=${remote} (unreachable)`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (syncIntent === "sync") {
|
|
202
|
+
syncStatus = "broken";
|
|
203
|
+
syncDetail = "sync was configured but no remote found";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
if (syncIntent === "sync") {
|
|
208
|
+
syncStatus = "broken";
|
|
209
|
+
syncDetail = "sync was configured but no remote found";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
175
212
|
const lines = [
|
|
176
213
|
`Phren v${version}`,
|
|
177
214
|
`Profile: ${activeProfile || "(default)"}`,
|
|
@@ -182,6 +219,7 @@ export function register(server, ctx) {
|
|
|
182
219
|
`Hooks: ${hooksEnabled ? "enabled" : "disabled"}`,
|
|
183
220
|
`Proactivity: ${proactivity}`,
|
|
184
221
|
`Task mode: ${taskMode}`,
|
|
222
|
+
`Sync: ${syncStatus}${syncStatus !== "synced" ? ` (${syncDetail})` : ""}`,
|
|
185
223
|
`Path: ${phrenPath}`,
|
|
186
224
|
].filter(Boolean);
|
|
187
225
|
return mcpResponse({
|
|
@@ -197,6 +235,8 @@ export function register(server, ctx) {
|
|
|
197
235
|
hooksEnabled,
|
|
198
236
|
proactivity,
|
|
199
237
|
taskMode,
|
|
238
|
+
syncStatus,
|
|
239
|
+
syncDetail,
|
|
200
240
|
phrenPath,
|
|
201
241
|
},
|
|
202
242
|
});
|