@phren/cli 0.0.28 → 0.0.32
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/capabilities/cli.js +2 -5
- package/mcp/dist/capabilities/mcp.js +5 -8
- package/mcp/dist/capabilities/types.js +2 -5
- package/mcp/dist/capabilities/vscode.js +2 -5
- package/mcp/dist/capabilities/web-ui.js +2 -5
- package/mcp/dist/{cli-actions.js → cli/actions.js} +22 -21
- package/mcp/dist/{cli.js → cli/cli.js} +13 -13
- package/mcp/dist/{cli-config.js → cli/config.js} +9 -9
- package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
- package/mcp/dist/{cli-govern.js → cli/govern.js} +10 -9
- package/mcp/dist/{cli-graph.js → cli/graph.js} +10 -9
- package/mcp/dist/{cli-hooks-citations.js → cli/hooks-citations.js} +2 -2
- package/mcp/dist/{cli-hooks-context.js → cli/hooks-context.js} +23 -23
- package/mcp/dist/{cli-hooks-globs.js → cli/hooks-globs.js} +4 -4
- package/mcp/dist/{cli-hooks-output.js → cli/hooks-output.js} +9 -10
- package/mcp/dist/{cli-hooks-session.js → cli/hooks-session.js} +42 -57
- package/mcp/dist/{cli-hooks.js → cli/hooks.js} +27 -26
- package/mcp/dist/{cli-namespaces.js → cli/namespaces.js} +25 -24
- package/mcp/dist/{cli-ops.js → cli/ops.js} +9 -9
- package/mcp/dist/{cli-search.js → cli/search.js} +8 -7
- package/mcp/dist/cli-hooks-git.js +243 -0
- package/mcp/dist/cli-hooks-prompt.js +319 -0
- package/mcp/dist/cli-hooks-session-handlers.js +349 -0
- package/mcp/dist/cli-hooks-stop.js +557 -0
- package/mcp/dist/{content-archive.js → content/archive.js} +8 -9
- package/mcp/dist/{content-citation.js → content/citation.js} +5 -5
- package/mcp/dist/{content-dedup.js → content/dedup.js} +9 -12
- package/mcp/dist/{content-learning.js → content/learning.js} +12 -12
- package/mcp/dist/{content-validate.js → content/validate.js} +5 -5
- package/mcp/dist/{core-finding.js → core/finding.js} +4 -4
- package/mcp/dist/{core-project.js → core/project.js} +4 -4
- package/mcp/dist/{core-search.js → core/search.js} +2 -2
- package/mcp/dist/{data-access.js → data/access.js} +131 -13
- package/mcp/dist/{data-tasks.js → data/tasks.js} +7 -5
- package/mcp/dist/embedding.js +9 -14
- package/mcp/dist/entrypoint.js +11 -11
- package/mcp/dist/{finding-context.js → finding/context.js} +2 -2
- package/mcp/dist/{finding-impact.js → finding/impact.js} +3 -3
- package/mcp/dist/{finding-journal.js → finding/journal.js} +4 -4
- package/mcp/dist/{finding-lifecycle.js → finding/lifecycle.js} +4 -4
- package/mcp/dist/{governance-audit.js → governance/audit.js} +2 -2
- package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
- package/mcp/dist/{governance-policy.js → governance/policy.js} +10 -12
- package/mcp/dist/{governance-rbac.js → governance/rbac.js} +3 -3
- package/mcp/dist/{governance-scores.js → governance/scores.js} +8 -10
- package/mcp/dist/hooks.js +39 -31
- package/mcp/dist/index-query.js +4 -1
- package/mcp/dist/index.js +53 -29
- package/mcp/dist/{init-config.js → init/config.js} +6 -6
- package/mcp/dist/{init.js → init/init.js} +28 -29
- package/mcp/dist/{init-preferences.js → init/preferences.js} +3 -3
- package/mcp/dist/{init-setup.js → init/setup.js} +17 -19
- package/mcp/dist/{init-shared.js → init/shared.js} +3 -3
- package/mcp/dist/init-bootstrap.js +68 -0
- package/mcp/dist/init-detect.js +38 -0
- package/mcp/dist/init-dryrun.js +55 -0
- package/mcp/dist/init-env.js +114 -0
- package/mcp/dist/init-fresh.js +239 -0
- package/mcp/dist/init-hooks.js +26 -0
- package/mcp/dist/init-mcp.js +65 -0
- package/mcp/dist/init-migrate.js +51 -0
- package/mcp/dist/init-modes.js +135 -0
- package/mcp/dist/init-npm.js +37 -0
- package/mcp/dist/init-project-local.js +99 -0
- package/mcp/dist/init-semantic.js +48 -0
- package/mcp/dist/init-types.js +1 -0
- package/mcp/dist/init-uninstall.js +482 -0
- package/mcp/dist/init-update.js +96 -0
- package/mcp/dist/init-walkthrough-merge.js +90 -0
- package/mcp/dist/init-walkthrough.js +529 -0
- package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
- package/mcp/dist/{link-context.js → link/context.js} +4 -4
- package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
- package/mcp/dist/{link.js → link/link.js} +26 -31
- package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
- package/mcp/dist/logger.js +11 -3
- package/mcp/dist/phren-art.js +0 -6
- package/mcp/dist/phren-paths.js +30 -12
- package/mcp/dist/proactivity.js +2 -2
- package/mcp/dist/profile-store.js +5 -6
- package/mcp/dist/project-config.js +2 -2
- package/mcp/dist/project-topics.js +1 -1
- package/mcp/dist/query-correlation.js +1 -1
- package/mcp/dist/{session-checkpoints.js → session/checkpoints.js} +3 -3
- package/mcp/dist/{session-utils.js → session/utils.js} +1 -1
- package/mcp/dist/{shared-content.js → shared/content.js} +7 -7
- package/mcp/dist/{shared-data-utils.js → shared/data-utils.js} +3 -3
- package/mcp/dist/{shared-embedding-cache.js → shared/embedding-cache.js} +3 -3
- package/mcp/dist/{shared-fragment-graph.js → shared/fragment-graph.js} +15 -24
- package/mcp/dist/shared/governance.js +4 -0
- package/mcp/dist/{shared-index.js → shared/index.js} +92 -123
- package/mcp/dist/{shared-ollama.js → shared/ollama.js} +2 -2
- package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +16 -21
- package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +17 -20
- package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
- package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
- package/mcp/dist/shared.js +4 -59
- package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
- package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
- package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
- package/mcp/dist/{shell-render.js → shell/render.js} +1 -1
- package/mcp/dist/{shell.js → shell/shell.js} +11 -11
- package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
- package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
- package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
- package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
- package/mcp/dist/{skill-registry.js → skill/registry.js} +4 -4
- package/mcp/dist/{skill-state.js → skill/state.js} +1 -1
- package/mcp/dist/startup-embedding.js +2 -2
- package/mcp/dist/status.js +15 -14
- package/mcp/dist/{tasks-github.js → task/github.js} +2 -2
- package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
- package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +7 -7
- package/mcp/dist/telemetry.js +3 -4
- package/mcp/dist/tool-registry.js +29 -17
- package/mcp/dist/tools/config.js +515 -0
- package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
- package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
- package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
- package/mcp/dist/{mcp-finding.js → tools/finding.js} +97 -124
- package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
- package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
- package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
- package/mcp/dist/{mcp-ops.js → tools/ops.js} +169 -71
- package/mcp/dist/{mcp-search.js → tools/search.js} +19 -23
- package/mcp/dist/{mcp-session.js → tools/session.js} +48 -23
- package/mcp/dist/{mcp-skills.js → tools/skills.js} +33 -35
- package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
- package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
- package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
- package/mcp/dist/{memory-ui-page.js → ui/page.js} +4 -6
- package/mcp/dist/{memory-ui-server.js → ui/server.js} +30 -22
- package/mcp/dist/update.js +2 -2
- package/mcp/dist/utils.js +51 -11
- package/package.json +2 -2
- package/scripts/preuninstall.mjs +31 -0
- package/starter/global/CLAUDE.md +3 -2
- package/mcp/dist/mcp-config.js +0 -551
- package/mcp/dist/shared-governance.js +0 -4
- /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
- /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
- /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
- /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
- /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
- /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
- /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
- /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uninstall logic for phren: removes MCP config, hooks, skill symlinks,
|
|
3
|
+
* and the phren data directory.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
8
|
+
import { isPhrenCommand, removeMcpServerAtPath, removeTomlMcpServer, patchJsonFile, } from "./init/config.js";
|
|
9
|
+
import { atomicWriteText, debugLog, isRecord, homeDir, homePath, findPhrenPath, getProjectDirs, readRootManifest, hookConfigPath, } from "./shared.js";
|
|
10
|
+
import { errorMessage } from "./utils.js";
|
|
11
|
+
import { machineFilePath } from "./machine-identity.js";
|
|
12
|
+
import { codexJsonCandidates, copilotMcpCandidates, cursorMcpCandidates, vscodeMcpCandidates, } from "./provider-adapters.js";
|
|
13
|
+
import { DEFAULT_PHREN_PATH, log } from "./init/shared.js";
|
|
14
|
+
const PHREN_NPM_PACKAGE_NAME = "@phren/cli";
|
|
15
|
+
function getNpmCommand() {
|
|
16
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
17
|
+
}
|
|
18
|
+
const UNINSTALL_TIMEOUT_MS = 30_000;
|
|
19
|
+
function runSyncCommand(command, args) {
|
|
20
|
+
try {
|
|
21
|
+
const result = spawnSync(command, args, {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
24
|
+
timeout: UNINSTALL_TIMEOUT_MS,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
ok: result.status === 0,
|
|
28
|
+
status: result.status,
|
|
29
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
30
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
status: null,
|
|
37
|
+
stdout: "",
|
|
38
|
+
stderr: errorMessage(err),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function shouldUninstallCurrentGlobalPackage() {
|
|
43
|
+
// Always attempt to remove the global package if it exists, regardless of
|
|
44
|
+
// whether the uninstaller was invoked from the global install or a local repo.
|
|
45
|
+
const npmRootResult = runSyncCommand(getNpmCommand(), ["root", "-g"]);
|
|
46
|
+
if (!npmRootResult.ok)
|
|
47
|
+
return false;
|
|
48
|
+
const npmRoot = npmRootResult.stdout.trim();
|
|
49
|
+
if (!npmRoot)
|
|
50
|
+
return false;
|
|
51
|
+
const globalPkgPath = path.join(npmRoot, PHREN_NPM_PACKAGE_NAME);
|
|
52
|
+
return fs.existsSync(globalPkgPath);
|
|
53
|
+
}
|
|
54
|
+
function uninstallCurrentGlobalPackage() {
|
|
55
|
+
const result = runSyncCommand(getNpmCommand(), ["uninstall", "-g", PHREN_NPM_PACKAGE_NAME]);
|
|
56
|
+
if (result.ok) {
|
|
57
|
+
log(` Removed global npm package (${PHREN_NPM_PACKAGE_NAME})`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const detail = result.stderr.trim() || result.stdout.trim() || (result.status === null ? "failed to start command" : `exit code ${result.status}`);
|
|
61
|
+
log(` Warning: could not remove global npm package (${PHREN_NPM_PACKAGE_NAME})`);
|
|
62
|
+
debugLog(`uninstall: global npm cleanup failed: ${detail}`);
|
|
63
|
+
}
|
|
64
|
+
// Agent skill directories to sweep for symlinks during uninstall
|
|
65
|
+
function agentSkillDirs() {
|
|
66
|
+
const home = homeDir();
|
|
67
|
+
return [
|
|
68
|
+
homePath(".claude", "skills"),
|
|
69
|
+
path.join(home, ".cursor", "skills"),
|
|
70
|
+
path.join(home, ".copilot", "skills"),
|
|
71
|
+
path.join(home, ".codex", "skills"),
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
// Remove skill symlinks that resolve inside phrenPath. Only touches symlinks, never regular files.
|
|
75
|
+
function sweepSkillSymlinks(phrenPath) {
|
|
76
|
+
const resolvedPhren = path.resolve(phrenPath);
|
|
77
|
+
for (const dir of agentSkillDirs()) {
|
|
78
|
+
if (!fs.existsSync(dir))
|
|
79
|
+
continue;
|
|
80
|
+
let entries;
|
|
81
|
+
try {
|
|
82
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
debugLog(`sweepSkillSymlinks: readdirSync failed for ${dir}: ${errorMessage(err)}`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (!entry.isSymbolicLink())
|
|
90
|
+
continue;
|
|
91
|
+
const fullPath = path.join(dir, entry.name);
|
|
92
|
+
try {
|
|
93
|
+
const target = fs.realpathSync(fullPath);
|
|
94
|
+
if (target.startsWith(resolvedPhren + path.sep) || target === resolvedPhren) {
|
|
95
|
+
fs.unlinkSync(fullPath);
|
|
96
|
+
log(` Removed skill symlink: ${fullPath}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Broken symlink (target no longer exists) — clean it up
|
|
101
|
+
try {
|
|
102
|
+
fs.unlinkSync(fullPath);
|
|
103
|
+
log(` Removed broken skill symlink: ${fullPath}`);
|
|
104
|
+
}
|
|
105
|
+
catch (err2) {
|
|
106
|
+
debugLog(`sweepSkillSymlinks: could not remove broken symlink ${fullPath}: ${errorMessage(err2)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Remove phren-generated manifest files from the skills parent directory
|
|
111
|
+
const parentDir = path.dirname(dir);
|
|
112
|
+
for (const manifestFile of ["skill-manifest.json", "skill-commands.json"]) {
|
|
113
|
+
const manifestPath = path.join(parentDir, manifestFile);
|
|
114
|
+
try {
|
|
115
|
+
if (fs.existsSync(manifestPath)) {
|
|
116
|
+
fs.unlinkSync(manifestPath);
|
|
117
|
+
log(` Removed ${manifestFile} (${manifestPath})`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
debugLog(`sweepSkillSymlinks: could not remove ${manifestPath}: ${errorMessage(err)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Filter phren hook entries from an agent hooks file. Returns true if the file was changed.
|
|
127
|
+
// Deletes the file if no hooks remain. `commandField` is the JSON key holding the command
|
|
128
|
+
// string in each hook entry (e.g. "bash" for Copilot, "command" for Codex).
|
|
129
|
+
function filterAgentHooks(filePath, commandField) {
|
|
130
|
+
if (!fs.existsSync(filePath))
|
|
131
|
+
return false;
|
|
132
|
+
try {
|
|
133
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
134
|
+
if (!isRecord(raw) || !isRecord(raw.hooks))
|
|
135
|
+
return false;
|
|
136
|
+
const hooks = raw.hooks;
|
|
137
|
+
let changed = false;
|
|
138
|
+
for (const event of Object.keys(hooks)) {
|
|
139
|
+
const entries = hooks[event];
|
|
140
|
+
if (!Array.isArray(entries))
|
|
141
|
+
continue;
|
|
142
|
+
const filtered = entries.filter((e) => !(isRecord(e) && typeof e[commandField] === "string" && isPhrenCommand(e[commandField])));
|
|
143
|
+
if (filtered.length !== entries.length) {
|
|
144
|
+
hooks[event] = filtered;
|
|
145
|
+
changed = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (!changed)
|
|
149
|
+
return false;
|
|
150
|
+
// Remove empty hook event keys
|
|
151
|
+
for (const event of Object.keys(hooks)) {
|
|
152
|
+
if (Array.isArray(hooks[event]) && hooks[event].length === 0) {
|
|
153
|
+
delete hooks[event];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (Object.keys(hooks).length === 0) {
|
|
157
|
+
// Hooks are empty but the config may contain other top-level keys (e.g. version).
|
|
158
|
+
// Only delete the file if the entire config is effectively empty.
|
|
159
|
+
const otherKeys = Object.keys(raw).filter((k) => k !== "hooks");
|
|
160
|
+
if (otherKeys.length === 0) {
|
|
161
|
+
fs.unlinkSync(filePath);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
atomicWriteText(filePath, JSON.stringify(raw, null, 2));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
atomicWriteText(filePath, JSON.stringify(raw, null, 2));
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
debugLog(`filterAgentHooks: failed for ${filePath}: ${errorMessage(err)}`);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function promptUninstallConfirm(phrenPath) {
|
|
178
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
179
|
+
return true;
|
|
180
|
+
// Show summary of what will be deleted
|
|
181
|
+
try {
|
|
182
|
+
const projectDirs = getProjectDirs(phrenPath);
|
|
183
|
+
const projectCount = projectDirs.length;
|
|
184
|
+
let findingCount = 0;
|
|
185
|
+
for (const dir of projectDirs) {
|
|
186
|
+
const findingsFile = path.join(dir, "FINDINGS.md");
|
|
187
|
+
if (fs.existsSync(findingsFile)) {
|
|
188
|
+
const content = fs.readFileSync(findingsFile, "utf8");
|
|
189
|
+
findingCount += content.split("\n").filter((l) => l.startsWith("- ")).length;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
log(`\n Will delete: ${phrenPath}`);
|
|
193
|
+
log(` Contains: ${projectCount} project(s), ~${findingCount} finding(s)`);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
debugLog(`promptUninstallConfirm: summary failed: ${errorMessage(err)}`);
|
|
197
|
+
log(`\n Will delete: ${phrenPath}`);
|
|
198
|
+
}
|
|
199
|
+
const readline = await import("readline");
|
|
200
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
201
|
+
return new Promise((resolve) => {
|
|
202
|
+
rl.question(`\nThis will permanently delete ${phrenPath} and all phren data. Type 'yes' to confirm: `, (answer) => {
|
|
203
|
+
rl.close();
|
|
204
|
+
resolve(answer.trim().toLowerCase() === "yes");
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
export async function runUninstall(opts = {}) {
|
|
209
|
+
const phrenPath = findPhrenPath();
|
|
210
|
+
const manifest = phrenPath ? readRootManifest(phrenPath) : null;
|
|
211
|
+
if (manifest?.installMode === "project-local" && phrenPath) {
|
|
212
|
+
log("\nUninstalling project-local phren...\n");
|
|
213
|
+
const workspaceRoot = manifest.workspaceRoot || path.dirname(phrenPath);
|
|
214
|
+
const workspaceMcp = path.join(workspaceRoot, ".vscode", "mcp.json");
|
|
215
|
+
try {
|
|
216
|
+
if (removeMcpServerAtPath(workspaceMcp)) {
|
|
217
|
+
log(` Removed phren from VS Code workspace MCP config (${workspaceMcp})`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
debugLog(`uninstall local vscode cleanup failed: ${errorMessage(err)}`);
|
|
222
|
+
}
|
|
223
|
+
fs.rmSync(phrenPath, { recursive: true, force: true });
|
|
224
|
+
log(` Removed ${phrenPath}`);
|
|
225
|
+
log("\nProject-local phren uninstalled.");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
log("\nUninstalling phren...\n");
|
|
229
|
+
const shouldRemoveGlobalPackage = shouldUninstallCurrentGlobalPackage();
|
|
230
|
+
// Confirmation prompt (shared-mode only — project-local is low-stakes)
|
|
231
|
+
if (!opts.yes) {
|
|
232
|
+
const confirmed = phrenPath
|
|
233
|
+
? await promptUninstallConfirm(phrenPath)
|
|
234
|
+
: (process.stdin.isTTY && process.stdout.isTTY
|
|
235
|
+
? await (async () => {
|
|
236
|
+
const readline = await import("readline");
|
|
237
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
238
|
+
return new Promise((resolve) => {
|
|
239
|
+
rl.question("This will remove all phren config and hooks. Type 'yes' to confirm: ", (answer) => {
|
|
240
|
+
rl.close();
|
|
241
|
+
resolve(answer.trim().toLowerCase() === "yes");
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
})()
|
|
245
|
+
: true);
|
|
246
|
+
if (!confirmed) {
|
|
247
|
+
log("Uninstall cancelled.");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const home = homeDir();
|
|
252
|
+
const machineFile = machineFilePath();
|
|
253
|
+
const settingsPath = hookConfigPath("claude");
|
|
254
|
+
// Remove from Claude Code ~/.claude.json (where MCP servers are actually read)
|
|
255
|
+
const claudeJsonPath = homePath(".claude.json");
|
|
256
|
+
if (fs.existsSync(claudeJsonPath)) {
|
|
257
|
+
try {
|
|
258
|
+
if (removeMcpServerAtPath(claudeJsonPath)) {
|
|
259
|
+
log(` Removed phren MCP server from ~/.claude.json`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
log(` Warning: could not update ~/.claude.json (${e})`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Remove from Claude Code settings.json
|
|
267
|
+
if (fs.existsSync(settingsPath)) {
|
|
268
|
+
try {
|
|
269
|
+
patchJsonFile(settingsPath, (data) => {
|
|
270
|
+
const hooksMap = isRecord(data.hooks) ? data.hooks : (data.hooks = {});
|
|
271
|
+
// Remove MCP server
|
|
272
|
+
if (data.mcpServers?.phren) {
|
|
273
|
+
delete data.mcpServers.phren;
|
|
274
|
+
log(` Removed phren MCP server from Claude Code settings`);
|
|
275
|
+
}
|
|
276
|
+
// Remove hooks containing phren references
|
|
277
|
+
for (const hookEvent of ["UserPromptSubmit", "Stop", "SessionStart", "PostToolUse"]) {
|
|
278
|
+
const hooks = hooksMap[hookEvent];
|
|
279
|
+
if (!Array.isArray(hooks))
|
|
280
|
+
continue;
|
|
281
|
+
const before = hooks.length;
|
|
282
|
+
hooksMap[hookEvent] = hooks.filter((h) => !h.hooks?.some((hook) => typeof hook.command === "string" && isPhrenCommand(hook.command)));
|
|
283
|
+
const removed = before - hooksMap[hookEvent].length;
|
|
284
|
+
if (removed > 0)
|
|
285
|
+
log(` Removed ${removed} phren hook(s) from ${hookEvent}`);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
log(` Warning: could not update Claude Code settings (${e})`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
log(` Claude Code settings not found at ${settingsPath} — skipping`);
|
|
295
|
+
}
|
|
296
|
+
// Remove from VS Code mcp.json
|
|
297
|
+
const vsCandidates = vscodeMcpCandidates().map((dir) => path.join(dir, "mcp.json"));
|
|
298
|
+
for (const mcpFile of vsCandidates) {
|
|
299
|
+
try {
|
|
300
|
+
if (removeMcpServerAtPath(mcpFile)) {
|
|
301
|
+
log(` Removed phren from VS Code MCP config (${mcpFile})`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
debugLog(`uninstall: cleanup failed for ${mcpFile}: ${errorMessage(err)}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Remove from Cursor MCP config
|
|
309
|
+
const cursorCandidates = cursorMcpCandidates();
|
|
310
|
+
for (const mcpFile of cursorCandidates) {
|
|
311
|
+
try {
|
|
312
|
+
if (removeMcpServerAtPath(mcpFile)) {
|
|
313
|
+
log(` Removed phren from Cursor MCP config (${mcpFile})`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
debugLog(`uninstall: cleanup failed for ${mcpFile}: ${errorMessage(err)}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Remove from Copilot CLI MCP config
|
|
321
|
+
const copilotCandidates = copilotMcpCandidates();
|
|
322
|
+
for (const mcpFile of copilotCandidates) {
|
|
323
|
+
try {
|
|
324
|
+
if (removeMcpServerAtPath(mcpFile)) {
|
|
325
|
+
log(` Removed phren from Copilot CLI MCP config (${mcpFile})`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
debugLog(`uninstall: cleanup failed for ${mcpFile}: ${errorMessage(err)}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Remove from Codex MCP config (TOML + JSON)
|
|
333
|
+
const codexToml = path.join(home, ".codex", "config.toml");
|
|
334
|
+
try {
|
|
335
|
+
if (removeTomlMcpServer(codexToml)) {
|
|
336
|
+
log(` Removed phren from Codex MCP config (${codexToml})`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
debugLog(`uninstall: cleanup failed for ${codexToml}: ${errorMessage(err)}`);
|
|
341
|
+
}
|
|
342
|
+
const codexCandidates = codexJsonCandidates(phrenPath || DEFAULT_PHREN_PATH);
|
|
343
|
+
for (const mcpFile of codexCandidates) {
|
|
344
|
+
try {
|
|
345
|
+
if (removeMcpServerAtPath(mcpFile)) {
|
|
346
|
+
log(` Removed phren from Codex MCP config (${mcpFile})`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
debugLog(`uninstall: cleanup failed for ${mcpFile}: ${errorMessage(err)}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Remove phren entries from Copilot hooks file (filter, don't bulk-delete)
|
|
354
|
+
const copilotHooksFile = hookConfigPath("copilot", phrenPath || DEFAULT_PHREN_PATH);
|
|
355
|
+
try {
|
|
356
|
+
if (filterAgentHooks(copilotHooksFile, "bash")) {
|
|
357
|
+
log(` Removed phren entries from Copilot hooks (${copilotHooksFile})`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
debugLog(`uninstall: cleanup failed for ${copilotHooksFile}: ${errorMessage(err)}`);
|
|
362
|
+
}
|
|
363
|
+
// Remove phren entries from Cursor hooks file (may contain non-phren entries)
|
|
364
|
+
const cursorHooksFile = hookConfigPath("cursor", phrenPath || DEFAULT_PHREN_PATH);
|
|
365
|
+
try {
|
|
366
|
+
if (fs.existsSync(cursorHooksFile)) {
|
|
367
|
+
const raw = JSON.parse(fs.readFileSync(cursorHooksFile, "utf8"));
|
|
368
|
+
let changed = false;
|
|
369
|
+
for (const key of ["sessionStart", "beforeSubmitPrompt", "stop"]) {
|
|
370
|
+
if (raw[key]?.command && typeof raw[key].command === "string" && isPhrenCommand(raw[key].command)) {
|
|
371
|
+
delete raw[key];
|
|
372
|
+
changed = true;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (changed) {
|
|
376
|
+
atomicWriteText(cursorHooksFile, JSON.stringify(raw, null, 2));
|
|
377
|
+
log(` Removed phren entries from Cursor hooks (${cursorHooksFile})`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
debugLog(`uninstall: cleanup failed for ${cursorHooksFile}: ${errorMessage(err)}`);
|
|
383
|
+
}
|
|
384
|
+
// Remove phren entries from Codex hooks file (filter, don't bulk-delete)
|
|
385
|
+
const codexHooksFile = hookConfigPath("codex", phrenPath || DEFAULT_PHREN_PATH);
|
|
386
|
+
try {
|
|
387
|
+
if (filterAgentHooks(codexHooksFile, "command")) {
|
|
388
|
+
log(` Removed phren entries from Codex hooks (${codexHooksFile})`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
debugLog(`uninstall: cleanup failed for ${codexHooksFile}: ${errorMessage(err)}`);
|
|
393
|
+
}
|
|
394
|
+
// Remove session wrapper scripts (written by installSessionWrapper)
|
|
395
|
+
const localBinDir = path.join(home, ".local", "bin");
|
|
396
|
+
for (const tool of ["copilot", "cursor", "codex"]) {
|
|
397
|
+
const wrapperPath = path.join(localBinDir, tool);
|
|
398
|
+
try {
|
|
399
|
+
if (fs.existsSync(wrapperPath)) {
|
|
400
|
+
// Only remove if it's a phren wrapper (check for PHREN_PATH marker)
|
|
401
|
+
const content = fs.readFileSync(wrapperPath, "utf8");
|
|
402
|
+
if (content.includes("PHREN_PATH") && content.includes("phren")) {
|
|
403
|
+
fs.unlinkSync(wrapperPath);
|
|
404
|
+
log(` Removed ${tool} session wrapper (${wrapperPath})`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
debugLog(`uninstall: cleanup failed for ${wrapperPath}: ${errorMessage(err)}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
if (fs.existsSync(machineFile)) {
|
|
414
|
+
fs.unlinkSync(machineFile);
|
|
415
|
+
log(` Removed machine alias (${machineFile})`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (err) {
|
|
419
|
+
debugLog(`uninstall: cleanup failed for ${machineFile}: ${errorMessage(err)}`);
|
|
420
|
+
}
|
|
421
|
+
const contextFile = homePath(".phren-context.md");
|
|
422
|
+
try {
|
|
423
|
+
if (fs.existsSync(contextFile)) {
|
|
424
|
+
fs.unlinkSync(contextFile);
|
|
425
|
+
log(` Removed machine context file (${contextFile})`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
debugLog(`uninstall: cleanup failed for ${contextFile}: ${errorMessage(err)}`);
|
|
430
|
+
}
|
|
431
|
+
// Sweep agent skill directories for symlinks pointing into the phren store
|
|
432
|
+
if (phrenPath) {
|
|
433
|
+
try {
|
|
434
|
+
sweepSkillSymlinks(phrenPath);
|
|
435
|
+
}
|
|
436
|
+
catch (err) {
|
|
437
|
+
debugLog(`uninstall: skill symlink sweep failed: ${errorMessage(err)}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (phrenPath && fs.existsSync(phrenPath)) {
|
|
441
|
+
try {
|
|
442
|
+
fs.rmSync(phrenPath, { recursive: true, force: true });
|
|
443
|
+
log(` Removed phren root (${phrenPath})`);
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
debugLog(`uninstall: cleanup failed for ${phrenPath}: ${errorMessage(err)}`);
|
|
447
|
+
log(` Warning: could not remove phren root (${phrenPath})`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (shouldRemoveGlobalPackage) {
|
|
451
|
+
uninstallCurrentGlobalPackage();
|
|
452
|
+
}
|
|
453
|
+
// Remove VS Code extension if installed
|
|
454
|
+
try {
|
|
455
|
+
const codeResult = execFileSync("code", ["--list-extensions"], {
|
|
456
|
+
encoding: "utf8",
|
|
457
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
458
|
+
timeout: 10_000,
|
|
459
|
+
});
|
|
460
|
+
const phrenExts = codeResult.split("\n").filter((ext) => ext.toLowerCase().includes("phren"));
|
|
461
|
+
for (const ext of phrenExts) {
|
|
462
|
+
const trimmed = ext.trim();
|
|
463
|
+
if (!trimmed)
|
|
464
|
+
continue;
|
|
465
|
+
try {
|
|
466
|
+
execFileSync("code", ["--uninstall-extension", trimmed], {
|
|
467
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
468
|
+
timeout: 15_000,
|
|
469
|
+
});
|
|
470
|
+
log(` Removed VS Code extension (${trimmed})`);
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
debugLog(`uninstall: VS Code extension removal failed for ${trimmed}: ${errorMessage(err)}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
// code CLI not available — skip
|
|
479
|
+
}
|
|
480
|
+
log(`\nPhren config, hooks, and installed data removed.`);
|
|
481
|
+
log(`Restart your agent(s) to apply changes.\n`);
|
|
482
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update logic for existing phren installs.
|
|
3
|
+
*/
|
|
4
|
+
import { hookConfigPath, writeRootManifest, } from "./shared.js";
|
|
5
|
+
import { isVersionNewer } from "./init-npm.js";
|
|
6
|
+
import { readInstallPreferences, writeInstallPreferences, } from "./init/preferences.js";
|
|
7
|
+
import { ensureGovernanceFiles, repairPreexistingInstall, runPostInitVerify, applyStarterTemplateUpdates, ensureLocalGitRepo, } from "./init/setup.js";
|
|
8
|
+
import { getWorkflowPolicy } from "./shared/governance.js";
|
|
9
|
+
import { VERSION, log, confirmPrompt } from "./init/shared.js";
|
|
10
|
+
import { configureMcpTargets } from "./init-mcp.js";
|
|
11
|
+
import { configureHooksIfEnabled } from "./init-hooks.js";
|
|
12
|
+
import { applyOnboardingPreferences, writeWalkthroughEnvDefaults, collectRepairedAssetLabels, } from "./init-env.js";
|
|
13
|
+
import { getPendingBootstrapTarget } from "./init-detect.js";
|
|
14
|
+
import { bootstrapProject } from "./init-bootstrap.js";
|
|
15
|
+
export async function runExistingInstallUpdate(phrenPath, opts, params) {
|
|
16
|
+
const { mcpEnabled, hooksEnabled, skillsScope, ownershipDefault, syncIntent, shouldBootstrapCurrentProject, bootstrapOwnership, } = params;
|
|
17
|
+
const mcpLabel = mcpEnabled ? "ON (recommended)" : "OFF (hooks-only fallback)";
|
|
18
|
+
const hooksLabel = hooksEnabled ? "ON (active)" : "OFF (disabled)";
|
|
19
|
+
writeRootManifest(phrenPath, {
|
|
20
|
+
version: 1,
|
|
21
|
+
installMode: "shared",
|
|
22
|
+
syncMode: "managed-git",
|
|
23
|
+
});
|
|
24
|
+
ensureGovernanceFiles(phrenPath);
|
|
25
|
+
const repaired = repairPreexistingInstall(phrenPath);
|
|
26
|
+
applyOnboardingPreferences(phrenPath, opts);
|
|
27
|
+
const existingGitRepo = ensureLocalGitRepo(phrenPath);
|
|
28
|
+
log(`\nphren already exists at ${phrenPath}`);
|
|
29
|
+
log(`Updating configuration...\n`);
|
|
30
|
+
log(` MCP mode: ${mcpLabel}`);
|
|
31
|
+
log(` Hooks mode: ${hooksLabel}`);
|
|
32
|
+
log(` Default project ownership: ${ownershipDefault}`);
|
|
33
|
+
log(` Task mode: ${getWorkflowPolicy(phrenPath).taskMode}`);
|
|
34
|
+
log(` Git repo: ${existingGitRepo.detail}`);
|
|
35
|
+
// Confirmation prompt before writing config
|
|
36
|
+
if (!opts.yes) {
|
|
37
|
+
const settingsPath = hookConfigPath("claude");
|
|
38
|
+
const modifications = [];
|
|
39
|
+
modifications.push(` ${settingsPath} (update MCP server + hooks)`);
|
|
40
|
+
log(`\nWill modify:`);
|
|
41
|
+
for (const mod of modifications)
|
|
42
|
+
log(mod);
|
|
43
|
+
const confirmed = await confirmPrompt("\nProceed?");
|
|
44
|
+
if (!confirmed) {
|
|
45
|
+
log("Aborted.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Always reconfigure MCP and hooks (picks up new features on upgrade)
|
|
50
|
+
configureMcpTargets(phrenPath, { mcpEnabled, hooksEnabled }, "Updated");
|
|
51
|
+
configureHooksIfEnabled(phrenPath, hooksEnabled, "Updated");
|
|
52
|
+
const prefs = readInstallPreferences(phrenPath);
|
|
53
|
+
const previousVersion = prefs.installedVersion;
|
|
54
|
+
if (isVersionNewer(VERSION, previousVersion)) {
|
|
55
|
+
log(`\n Starter template update available: v${previousVersion} -> v${VERSION}`);
|
|
56
|
+
log(` Run \`npx phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
|
|
57
|
+
}
|
|
58
|
+
if (opts.applyStarterUpdate) {
|
|
59
|
+
const updated = applyStarterTemplateUpdates(phrenPath);
|
|
60
|
+
if (updated.length) {
|
|
61
|
+
log(` Applied starter template updates (${updated.length} file${updated.length === 1 ? "" : "s"}).`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
log(` No starter template updates were applied (starter files not found).`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
writeInstallPreferences(phrenPath, { mcpEnabled, hooksEnabled, skillsScope, installedVersion: VERSION, syncIntent });
|
|
68
|
+
if (repaired.removedLegacyProjects > 0) {
|
|
69
|
+
log(` Removed ${repaired.removedLegacyProjects} legacy starter project entr${repaired.removedLegacyProjects === 1 ? "y" : "ies"} from profiles.`);
|
|
70
|
+
}
|
|
71
|
+
const repairedAssets = collectRepairedAssetLabels(repaired);
|
|
72
|
+
if (repairedAssets.length > 0) {
|
|
73
|
+
log(` Recreated missing generated assets: ${repairedAssets.join(", ")}`);
|
|
74
|
+
}
|
|
75
|
+
// Post-update verification
|
|
76
|
+
log(`\nVerifying setup...`);
|
|
77
|
+
const verify = runPostInitVerify(phrenPath);
|
|
78
|
+
for (const check of verify.checks) {
|
|
79
|
+
log(` ${check.ok ? "pass" : "FAIL"} ${check.name}: ${check.detail}`);
|
|
80
|
+
}
|
|
81
|
+
const pendingBootstrap = getPendingBootstrapTarget(phrenPath, opts);
|
|
82
|
+
if (pendingBootstrap && shouldBootstrapCurrentProject) {
|
|
83
|
+
bootstrapProject(phrenPath, pendingBootstrap.path, opts.profile, bootstrapOwnership, "Added current project");
|
|
84
|
+
}
|
|
85
|
+
for (const envLabel of writeWalkthroughEnvDefaults(phrenPath, opts)) {
|
|
86
|
+
log(` ${envLabel}`);
|
|
87
|
+
}
|
|
88
|
+
log(`\n\x1b[95m◆\x1b[0m phren updated successfully`);
|
|
89
|
+
log(`\nNext steps:`);
|
|
90
|
+
log(` 1. Start a new Claude session in your project directory — phren injects context automatically`);
|
|
91
|
+
log(` 2. Run \`npx phren doctor\` to verify everything is wired correctly`);
|
|
92
|
+
log(` 3. Change defaults anytime: \`npx phren config project-ownership\`, \`npx phren config workflow\`, \`npx phren config proactivity.findings\`, \`npx phren config proactivity.tasks\``);
|
|
93
|
+
log(` 4. After your first week, run phren-discover to surface gaps in your project knowledge`);
|
|
94
|
+
log(` 5. After working across projects, run phren-consolidate to find cross-project patterns`);
|
|
95
|
+
log(``);
|
|
96
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge walkthrough answers back into InitOptions.
|
|
3
|
+
*/
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
import { runWalkthrough } from "./init-walkthrough.js";
|
|
6
|
+
import { resolveInitPhrenPath, hasInstallMarkers } from "./init-detect.js";
|
|
7
|
+
import { log } from "./init/shared.js";
|
|
8
|
+
/**
|
|
9
|
+
* Run the interactive walkthrough for first-time installs, merging answers into opts.
|
|
10
|
+
* Mutates opts in-place and returns the (possibly updated) phrenPath and hasExistingInstall.
|
|
11
|
+
*/
|
|
12
|
+
export async function mergeWalkthroughAnswers(phrenPath, hasExisting, opts) {
|
|
13
|
+
const answers = await runWalkthrough(phrenPath);
|
|
14
|
+
opts._walkthroughStorageChoice = answers.storageChoice;
|
|
15
|
+
opts._walkthroughStoragePath = answers.storagePath;
|
|
16
|
+
opts._walkthroughStorageRepoRoot = answers.storageRepoRoot;
|
|
17
|
+
const newPhrenPath = resolveInitPhrenPath(opts);
|
|
18
|
+
const newHasExisting = hasInstallMarkers(newPhrenPath);
|
|
19
|
+
opts.machine = opts.machine || answers.machine;
|
|
20
|
+
opts.profile = opts.profile || answers.profile;
|
|
21
|
+
opts.mcp = opts.mcp || answers.mcp;
|
|
22
|
+
opts.hooks = opts.hooks || answers.hooks;
|
|
23
|
+
opts.projectOwnershipDefault = opts.projectOwnershipDefault || answers.projectOwnershipDefault;
|
|
24
|
+
opts.findingsProactivity = opts.findingsProactivity || answers.findingsProactivity;
|
|
25
|
+
opts.taskProactivity = opts.taskProactivity || answers.taskProactivity;
|
|
26
|
+
if (typeof opts.lowConfidenceThreshold !== "number")
|
|
27
|
+
opts.lowConfidenceThreshold = answers.lowConfidenceThreshold;
|
|
28
|
+
if (!Array.isArray(opts.riskySections))
|
|
29
|
+
opts.riskySections = answers.riskySections;
|
|
30
|
+
opts.taskMode = opts.taskMode || answers.taskMode;
|
|
31
|
+
if (answers.cloneUrl) {
|
|
32
|
+
opts._walkthroughCloneUrl = answers.cloneUrl;
|
|
33
|
+
}
|
|
34
|
+
if (answers.githubRepo) {
|
|
35
|
+
opts._walkthroughGithub = { username: answers.githubUsername, repo: answers.githubRepo };
|
|
36
|
+
}
|
|
37
|
+
opts._walkthroughDomain = answers.domain;
|
|
38
|
+
if (answers.inferredScaffold) {
|
|
39
|
+
opts._walkthroughInferredScaffold = answers.inferredScaffold;
|
|
40
|
+
}
|
|
41
|
+
if (!answers.ollamaEnabled) {
|
|
42
|
+
process.env._PHREN_WALKTHROUGH_OLLAMA_SKIP = "1";
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
opts._walkthroughSemanticSearch = true;
|
|
46
|
+
}
|
|
47
|
+
opts._walkthroughAutoCapture = answers.autoCaptureEnabled;
|
|
48
|
+
if (answers.semanticDedupEnabled) {
|
|
49
|
+
opts._walkthroughSemanticDedup = true;
|
|
50
|
+
}
|
|
51
|
+
if (answers.semanticConflictEnabled) {
|
|
52
|
+
opts._walkthroughSemanticConflict = true;
|
|
53
|
+
}
|
|
54
|
+
if (answers.findingSensitivity && answers.findingSensitivity !== "balanced") {
|
|
55
|
+
opts.findingSensitivity = answers.findingSensitivity;
|
|
56
|
+
}
|
|
57
|
+
opts._walkthroughBootstrapCurrentProject = answers.bootstrapCurrentProject;
|
|
58
|
+
opts._walkthroughBootstrapOwnership = answers.bootstrapOwnership;
|
|
59
|
+
return { phrenPath: newPhrenPath, hasExistingInstall: newHasExisting };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Clone an existing phren from a remote URL.
|
|
63
|
+
* @returns true if the clone succeeded (existing install), false otherwise
|
|
64
|
+
*/
|
|
65
|
+
export function cloneExistingPhren(phrenPath, cloneUrl) {
|
|
66
|
+
log(`\nCloning existing phren from ${cloneUrl}...`);
|
|
67
|
+
try {
|
|
68
|
+
execFileSync("git", ["clone", cloneUrl, phrenPath], {
|
|
69
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
70
|
+
timeout: 60_000,
|
|
71
|
+
});
|
|
72
|
+
log(` Cloned to ${phrenPath}`);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
log(` Clone failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
77
|
+
log("");
|
|
78
|
+
log(" ┌──────────────────────────────────────────────────────────────────┐");
|
|
79
|
+
log(" │ WARNING: Sync is NOT configured. Your phren data is local-only. │");
|
|
80
|
+
log(" │ │");
|
|
81
|
+
log(" │ To fix later: │");
|
|
82
|
+
log(` │ cd ${phrenPath}`);
|
|
83
|
+
log(" │ git remote add origin <YOUR_REPO_URL> │");
|
|
84
|
+
log(" │ git push -u origin main │");
|
|
85
|
+
log(" └──────────────────────────────────────────────────────────────────┘");
|
|
86
|
+
log("");
|
|
87
|
+
log(` Continuing with fresh local-only install.`);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|