@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 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
  }
@@ -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
- const remote = execFileSync("git", ["-C", phrenPath, "remote", "get-url", "origin"], {
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 gitRemote = gitRemoteStatus(phrenPath);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phren/cli",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Knowledge layer for AI agents. Phren learns and recalls.",
5
5
  "type": "module",
6
6
  "bin": {