@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,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap-current-project prompting and execution for init.
|
|
3
|
+
*/
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { debugLog } from "./shared.js";
|
|
6
|
+
import { bootstrapFromExisting, } from "./init/setup.js";
|
|
7
|
+
import { PROJECT_OWNERSHIP_MODES, } from "./project-config.js";
|
|
8
|
+
import { createWalkthroughPrompts, createWalkthroughStyle, } from "./init-walkthrough.js";
|
|
9
|
+
import { getPendingBootstrapTarget } from "./init-detect.js";
|
|
10
|
+
import { log } from "./init/shared.js";
|
|
11
|
+
/**
|
|
12
|
+
* Decide whether to bootstrap the CWD project and with what ownership.
|
|
13
|
+
* May prompt the user interactively.
|
|
14
|
+
*/
|
|
15
|
+
export async function resolveBootstrapDecision(phrenPath, opts, ownershipDefault, dryRun) {
|
|
16
|
+
const pendingBootstrap = getPendingBootstrapTarget(phrenPath, opts);
|
|
17
|
+
let shouldBootstrap = opts._walkthroughBootstrapCurrentProject === true;
|
|
18
|
+
let ownership = opts._walkthroughBootstrapOwnership ?? ownershipDefault;
|
|
19
|
+
if (pendingBootstrap && !dryRun) {
|
|
20
|
+
const walkthroughAlreadyHandled = opts._walkthroughBootstrapCurrentProject !== undefined;
|
|
21
|
+
if (walkthroughAlreadyHandled) {
|
|
22
|
+
shouldBootstrap = opts._walkthroughBootstrapCurrentProject === true;
|
|
23
|
+
ownership = opts._walkthroughBootstrapOwnership ?? ownershipDefault;
|
|
24
|
+
}
|
|
25
|
+
else if (opts.yes || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
26
|
+
shouldBootstrap = true;
|
|
27
|
+
ownership = ownershipDefault;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const prompts = await createWalkthroughPrompts();
|
|
31
|
+
const style = await createWalkthroughStyle();
|
|
32
|
+
const detectedProjectName = path.basename(pendingBootstrap.path);
|
|
33
|
+
log("");
|
|
34
|
+
log(style.header("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
|
|
35
|
+
log(style.header("Current Project"));
|
|
36
|
+
log(style.header("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
|
|
37
|
+
log(`Detected project: ${detectedProjectName}`);
|
|
38
|
+
shouldBootstrap = await prompts.confirm("Add this project to phren now?", true);
|
|
39
|
+
if (!shouldBootstrap) {
|
|
40
|
+
log(style.warning(` Skipped. Later: cd ${pendingBootstrap.path} && npx phren add`));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
ownership = await prompts.select("Ownership for detected project", [
|
|
44
|
+
{ value: ownershipDefault, name: `${ownershipDefault} (default)` },
|
|
45
|
+
...PROJECT_OWNERSHIP_MODES
|
|
46
|
+
.filter((mode) => mode !== ownershipDefault)
|
|
47
|
+
.map((mode) => ({ value: mode, name: mode })),
|
|
48
|
+
], ownershipDefault);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { shouldBootstrap, ownership };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Bootstrap a project from an existing directory into phren.
|
|
56
|
+
*/
|
|
57
|
+
export function bootstrapProject(phrenPath, projectPath, profile, ownership, label) {
|
|
58
|
+
try {
|
|
59
|
+
const created = bootstrapFromExisting(phrenPath, projectPath, {
|
|
60
|
+
profile,
|
|
61
|
+
ownership,
|
|
62
|
+
});
|
|
63
|
+
log(`\n${label} "${created.project}" (${created.ownership})`);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
debugLog(`Bootstrap from CWD failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection helpers for init: bootstrap target, install markers, path resolution.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { expandHomePath } from "./shared.js";
|
|
7
|
+
import { detectProjectDir, isProjectTracked } from "./init/setup.js";
|
|
8
|
+
import { DEFAULT_PHREN_PATH } from "./init/shared.js";
|
|
9
|
+
export function normalizedBootstrapProjectName(projectPath) {
|
|
10
|
+
return path.basename(projectPath).toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
11
|
+
}
|
|
12
|
+
export function getPendingBootstrapTarget(phrenPath, _opts) {
|
|
13
|
+
const cwdProject = detectProjectDir(process.cwd(), phrenPath);
|
|
14
|
+
if (!cwdProject)
|
|
15
|
+
return null;
|
|
16
|
+
const projectName = normalizedBootstrapProjectName(cwdProject);
|
|
17
|
+
if (isProjectTracked(phrenPath, projectName))
|
|
18
|
+
return null;
|
|
19
|
+
return { path: cwdProject, mode: "detected" };
|
|
20
|
+
}
|
|
21
|
+
export function hasInstallMarkers(phrenPath) {
|
|
22
|
+
// Require at least two markers to consider this a real install.
|
|
23
|
+
// A partial clone or failed init may create one directory but not finish.
|
|
24
|
+
if (!fs.existsSync(phrenPath))
|
|
25
|
+
return false;
|
|
26
|
+
let found = 0;
|
|
27
|
+
if (fs.existsSync(path.join(phrenPath, "machines.yaml")))
|
|
28
|
+
found++;
|
|
29
|
+
if (fs.existsSync(path.join(phrenPath, ".config")))
|
|
30
|
+
found++;
|
|
31
|
+
if (fs.existsSync(path.join(phrenPath, "global")))
|
|
32
|
+
found++;
|
|
33
|
+
return found >= 2;
|
|
34
|
+
}
|
|
35
|
+
export function resolveInitPhrenPath(opts) {
|
|
36
|
+
const raw = opts._walkthroughStoragePath || process.env.PHREN_PATH || DEFAULT_PHREN_PATH;
|
|
37
|
+
return path.resolve(expandHomePath(raw));
|
|
38
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dry-run output for init.
|
|
3
|
+
*/
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { getMachineName } from "./machine-identity.js";
|
|
6
|
+
import { getPendingBootstrapTarget } from "./init-detect.js";
|
|
7
|
+
import { log } from "./init/shared.js";
|
|
8
|
+
export function printDryRun(phrenPath, opts, params) {
|
|
9
|
+
const { hasExistingInstall, mcpLabel, hooksLabel, hooksEnabled, storageChoice, storageRepoRoot } = params;
|
|
10
|
+
const pendingBootstrap = getPendingBootstrapTarget(phrenPath, opts);
|
|
11
|
+
log("\nInit dry run. No files will be written.\n");
|
|
12
|
+
if (storageChoice) {
|
|
13
|
+
log(`Storage location: ${storageChoice} (${phrenPath})`);
|
|
14
|
+
if (storageChoice === "project" && storageRepoRoot) {
|
|
15
|
+
log(` Would update ${path.join(storageRepoRoot, ".gitignore")} with .phren/`);
|
|
16
|
+
log(` Would set PHREN_PATH in ${path.join(storageRepoRoot, ".env")}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (hasExistingInstall) {
|
|
20
|
+
log(`phren install detected at ${phrenPath}`);
|
|
21
|
+
log(`Would update configuration for the existing install:\n`);
|
|
22
|
+
log(` MCP mode: ${mcpLabel}`);
|
|
23
|
+
log(` Hooks mode: ${hooksLabel}`);
|
|
24
|
+
log(` Reconfigure Claude Code MCP/hooks`);
|
|
25
|
+
log(` Reconfigure VS Code, Cursor, Copilot CLI, and Codex MCP targets`);
|
|
26
|
+
if (hooksEnabled) {
|
|
27
|
+
log(` Reconfigure lifecycle hooks for detected tools`);
|
|
28
|
+
}
|
|
29
|
+
if (pendingBootstrap?.mode === "detected") {
|
|
30
|
+
log(` Would offer to add current project directory (${pendingBootstrap.path})`);
|
|
31
|
+
}
|
|
32
|
+
if (opts.applyStarterUpdate) {
|
|
33
|
+
log(` Apply starter template updates to global/CLAUDE.md and global skills`);
|
|
34
|
+
}
|
|
35
|
+
log(` Run post-init verification checks`);
|
|
36
|
+
log(`\nDry run complete.\n`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
log(`No existing phren install found at ${phrenPath}`);
|
|
40
|
+
log(`Would create a new phren install:\n`);
|
|
41
|
+
log(` Copy starter files to ${phrenPath} (or create minimal structure)`);
|
|
42
|
+
log(` Update machines.yaml for machine "${opts.machine || getMachineName()}"`);
|
|
43
|
+
log(` Create/update config files`);
|
|
44
|
+
log(` MCP mode: ${mcpLabel}`);
|
|
45
|
+
log(` Hooks mode: ${hooksLabel}`);
|
|
46
|
+
log(` Configure Claude Code plus detected MCP targets (VS Code/Cursor/Copilot/Codex)`);
|
|
47
|
+
if (hooksEnabled) {
|
|
48
|
+
log(` Configure lifecycle hooks for detected tools`);
|
|
49
|
+
}
|
|
50
|
+
if (pendingBootstrap?.mode === "detected") {
|
|
51
|
+
log(` Would offer to add current project directory (${pendingBootstrap.path})`);
|
|
52
|
+
}
|
|
53
|
+
log(` Write install preferences and run post-init verification checks`);
|
|
54
|
+
log(`\nDry run complete.\n`);
|
|
55
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment flags, onboarding preferences, and repair asset helpers for init.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as crypto from "crypto";
|
|
7
|
+
import { writeInstallPreferences, writeGovernanceInstallPreferences, } from "./init/preferences.js";
|
|
8
|
+
import { updateWorkflowPolicy } from "./shared/governance.js";
|
|
9
|
+
import { ensureGitignoreEntry, upsertProjectEnvVar, } from "./init/setup.js";
|
|
10
|
+
export function applyOnboardingPreferences(phrenPath, opts) {
|
|
11
|
+
if (opts.projectOwnershipDefault) {
|
|
12
|
+
writeInstallPreferences(phrenPath, { projectOwnershipDefault: opts.projectOwnershipDefault });
|
|
13
|
+
}
|
|
14
|
+
const runtimePatch = {};
|
|
15
|
+
if (opts.findingsProactivity)
|
|
16
|
+
runtimePatch.proactivityFindings = opts.findingsProactivity;
|
|
17
|
+
if (opts.taskProactivity)
|
|
18
|
+
runtimePatch.proactivityTask = opts.taskProactivity;
|
|
19
|
+
if (Object.keys(runtimePatch).length > 0) {
|
|
20
|
+
writeInstallPreferences(phrenPath, runtimePatch);
|
|
21
|
+
}
|
|
22
|
+
const governancePatch = {};
|
|
23
|
+
if (opts.findingsProactivity)
|
|
24
|
+
governancePatch.proactivityFindings = opts.findingsProactivity;
|
|
25
|
+
if (opts.taskProactivity)
|
|
26
|
+
governancePatch.proactivityTask = opts.taskProactivity;
|
|
27
|
+
if (Object.keys(governancePatch).length > 0) {
|
|
28
|
+
writeGovernanceInstallPreferences(phrenPath, governancePatch);
|
|
29
|
+
}
|
|
30
|
+
const workflowPatch = {};
|
|
31
|
+
if (typeof opts.lowConfidenceThreshold === "number")
|
|
32
|
+
workflowPatch.lowConfidenceThreshold = opts.lowConfidenceThreshold;
|
|
33
|
+
if (Array.isArray(opts.riskySections))
|
|
34
|
+
workflowPatch.riskySections = opts.riskySections;
|
|
35
|
+
if (opts.taskMode)
|
|
36
|
+
workflowPatch.taskMode = opts.taskMode;
|
|
37
|
+
if (opts.findingSensitivity)
|
|
38
|
+
workflowPatch.findingSensitivity = opts.findingSensitivity;
|
|
39
|
+
if (Object.keys(workflowPatch).length > 0) {
|
|
40
|
+
updateWorkflowPolicy(phrenPath, workflowPatch);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function writeWalkthroughEnvDefaults(phrenPath, opts) {
|
|
44
|
+
const envFile = path.join(phrenPath, ".env");
|
|
45
|
+
let envContent = fs.existsSync(envFile) ? fs.readFileSync(envFile, "utf8") : "# phren feature flags — generated by init\n";
|
|
46
|
+
const envFlags = [];
|
|
47
|
+
const autoCaptureChoice = opts._walkthroughAutoCapture;
|
|
48
|
+
const hasAutoCaptureFlag = /^\s*PHREN_FEATURE_AUTO_CAPTURE=.*$/m.test(envContent);
|
|
49
|
+
if (typeof autoCaptureChoice === "boolean") {
|
|
50
|
+
envFlags.push({
|
|
51
|
+
flag: `PHREN_FEATURE_AUTO_CAPTURE=${autoCaptureChoice ? "1" : "0"}`,
|
|
52
|
+
label: `Auto-capture ${autoCaptureChoice ? "enabled" : "disabled"}`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else if (!hasAutoCaptureFlag) {
|
|
56
|
+
// Default to enabled on fresh installs and non-walkthrough init.
|
|
57
|
+
envFlags.push({ flag: "PHREN_FEATURE_AUTO_CAPTURE=1", label: "Auto-capture enabled" });
|
|
58
|
+
}
|
|
59
|
+
if (opts._walkthroughSemanticDedup)
|
|
60
|
+
envFlags.push({ flag: "PHREN_FEATURE_SEMANTIC_DEDUP=1", label: "Semantic dedup" });
|
|
61
|
+
if (opts._walkthroughSemanticConflict)
|
|
62
|
+
envFlags.push({ flag: "PHREN_FEATURE_SEMANTIC_CONFLICT=1", label: "Conflict detection" });
|
|
63
|
+
if (envFlags.length === 0)
|
|
64
|
+
return [];
|
|
65
|
+
let changed = false;
|
|
66
|
+
const enabledLabels = [];
|
|
67
|
+
for (const { flag, label } of envFlags) {
|
|
68
|
+
const key = flag.split("=")[0];
|
|
69
|
+
const lineRe = new RegExp(`^\\s*${key}=.*$`, "m");
|
|
70
|
+
if (lineRe.test(envContent)) {
|
|
71
|
+
const before = envContent;
|
|
72
|
+
envContent = envContent.replace(lineRe, flag);
|
|
73
|
+
if (envContent !== before) {
|
|
74
|
+
changed = true;
|
|
75
|
+
enabledLabels.push(label);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (!envContent.endsWith("\n"))
|
|
80
|
+
envContent += "\n";
|
|
81
|
+
envContent += `${flag}\n`;
|
|
82
|
+
changed = true;
|
|
83
|
+
enabledLabels.push(label);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (changed) {
|
|
87
|
+
const tmpPath = `${envFile}.tmp-${crypto.randomUUID()}`;
|
|
88
|
+
fs.writeFileSync(tmpPath, envContent);
|
|
89
|
+
fs.renameSync(tmpPath, envFile);
|
|
90
|
+
}
|
|
91
|
+
return enabledLabels.map((label) => `${label} (${envFile})`);
|
|
92
|
+
}
|
|
93
|
+
export function collectRepairedAssetLabels(repaired) {
|
|
94
|
+
const repairedAssets = [];
|
|
95
|
+
if (repaired.createdContextFile)
|
|
96
|
+
repairedAssets.push("~/.phren-context.md");
|
|
97
|
+
if (repaired.createdRootMemory)
|
|
98
|
+
repairedAssets.push("generated MEMORY.md");
|
|
99
|
+
repairedAssets.push(...repaired.createdGlobalAssets);
|
|
100
|
+
repairedAssets.push(...repaired.createdRuntimeAssets);
|
|
101
|
+
repairedAssets.push(...repaired.createdFeatureDefaults);
|
|
102
|
+
repairedAssets.push(...repaired.createdSkillArtifacts);
|
|
103
|
+
return repairedAssets;
|
|
104
|
+
}
|
|
105
|
+
export function applyProjectStorageBindings(repoRoot, phrenPath) {
|
|
106
|
+
const updates = [];
|
|
107
|
+
if (ensureGitignoreEntry(repoRoot, ".phren/")) {
|
|
108
|
+
updates.push(`${path.join(repoRoot, ".gitignore")} (.phren/)`);
|
|
109
|
+
}
|
|
110
|
+
if (upsertProjectEnvVar(repoRoot, "PHREN_PATH", phrenPath)) {
|
|
111
|
+
updates.push(`${path.join(repoRoot, ".env")} (PHREN_PATH=${phrenPath})`);
|
|
112
|
+
}
|
|
113
|
+
return updates;
|
|
114
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fresh install logic: copy starter, scaffold project, write initial config.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { atomicWriteText, debugLog, hookConfigPath, writeRootManifest, } from "./shared.js";
|
|
7
|
+
import { isValidProjectName, errorMessage } from "./utils.js";
|
|
8
|
+
import { getMachineName, persistMachineName } from "./machine-identity.js";
|
|
9
|
+
import { writeInstallPreferences, } from "./init/preferences.js";
|
|
10
|
+
import { ensureGovernanceFiles, repairPreexistingInstall, runPostInitVerify, listTemplates, applyTemplate, ensureProjectScaffold, ensureLocalGitRepo, updateMachinesYaml, detectProjectDir, } from "./init/setup.js";
|
|
11
|
+
import { getWorkflowPolicy } from "./shared/governance.js";
|
|
12
|
+
import { addProjectToProfile } from "./profile-store.js";
|
|
13
|
+
import { STARTER_DIR, VERSION, log, confirmPrompt } from "./init/shared.js";
|
|
14
|
+
import { configureMcpTargets } from "./init-mcp.js";
|
|
15
|
+
import { configureHooksIfEnabled } from "./init-hooks.js";
|
|
16
|
+
import { applyOnboardingPreferences, writeWalkthroughEnvDefaults, collectRepairedAssetLabels, } from "./init-env.js";
|
|
17
|
+
import { warmSemanticSearch } from "./init-semantic.js";
|
|
18
|
+
import { bootstrapProject } from "./init-bootstrap.js";
|
|
19
|
+
import { logger } from "./logger.js";
|
|
20
|
+
function copyDir(src, dest) {
|
|
21
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
22
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
23
|
+
if (src === STARTER_DIR && entry.isDirectory() && ["my-api", "my-frontend", "my-first-project"].includes(entry.name)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const srcPath = path.join(src, entry.name);
|
|
27
|
+
const destPath = path.join(dest, entry.name);
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
copyDir(srcPath, destPath);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
fs.copyFileSync(srcPath, destPath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function runFreshInstall(phrenPath, opts, params) {
|
|
37
|
+
const { mcpEnabled, hooksEnabled, skillsScope, ownershipDefault, syncIntent, shouldBootstrapCurrentProject, bootstrapOwnership, } = params;
|
|
38
|
+
log("\nSetting up phren...\n");
|
|
39
|
+
const walkthroughProject = opts._walkthroughProject;
|
|
40
|
+
if (walkthroughProject) {
|
|
41
|
+
if (!walkthroughProject.trim()) {
|
|
42
|
+
console.error("Error: project name cannot be empty.");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (walkthroughProject.length > 100) {
|
|
46
|
+
console.error("Error: project name must be 100 characters or fewer.");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (!isValidProjectName(walkthroughProject)) {
|
|
50
|
+
console.error(`Error: invalid project name "${walkthroughProject}". Use lowercase letters, numbers, and hyphens.`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const cwdProjectPath = !walkthroughProject ? detectProjectDir(process.cwd(), phrenPath) : null;
|
|
55
|
+
const useTemplateProject = Boolean(walkthroughProject) || Boolean(opts.template);
|
|
56
|
+
const firstProjectName = walkthroughProject || "my-first-project";
|
|
57
|
+
const firstProjectDomain = opts._walkthroughDomain ?? "software";
|
|
58
|
+
if (fs.existsSync(STARTER_DIR)) {
|
|
59
|
+
copyDir(STARTER_DIR, phrenPath);
|
|
60
|
+
writeRootManifest(phrenPath, {
|
|
61
|
+
version: 1,
|
|
62
|
+
installMode: "shared",
|
|
63
|
+
syncMode: "managed-git",
|
|
64
|
+
});
|
|
65
|
+
if (useTemplateProject) {
|
|
66
|
+
const targetProject = walkthroughProject || firstProjectName;
|
|
67
|
+
const projectDir = path.join(phrenPath, targetProject);
|
|
68
|
+
const templateApplied = Boolean(opts.template && applyTemplate(projectDir, opts.template, targetProject));
|
|
69
|
+
if (templateApplied) {
|
|
70
|
+
log(` Applied "${opts.template}" template to ${targetProject}`);
|
|
71
|
+
}
|
|
72
|
+
ensureProjectScaffold(projectDir, targetProject, firstProjectDomain, opts._walkthroughInferredScaffold);
|
|
73
|
+
const targetProfile = opts.profile || "default";
|
|
74
|
+
const addToProfile = addProjectToProfile(phrenPath, targetProfile, targetProject);
|
|
75
|
+
if (!addToProfile.ok) {
|
|
76
|
+
debugLog(`fresh init addProjectToProfile failed for ${targetProfile}/${targetProject}: ${addToProfile.error}`);
|
|
77
|
+
}
|
|
78
|
+
if (opts.template && !templateApplied) {
|
|
79
|
+
log(` Template "${opts.template}" not found. Available: ${listTemplates().join(", ") || "none"}`);
|
|
80
|
+
}
|
|
81
|
+
log(` Seeded project "${targetProject}"`);
|
|
82
|
+
}
|
|
83
|
+
log(` Created phren v${VERSION} \u2192 ${phrenPath}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
log(` Starter not found in package, creating minimal structure...`);
|
|
87
|
+
writeRootManifest(phrenPath, {
|
|
88
|
+
version: 1,
|
|
89
|
+
installMode: "shared",
|
|
90
|
+
syncMode: "managed-git",
|
|
91
|
+
});
|
|
92
|
+
fs.mkdirSync(path.join(phrenPath, "global", "skills"), { recursive: true });
|
|
93
|
+
fs.mkdirSync(path.join(phrenPath, "profiles"), { recursive: true });
|
|
94
|
+
atomicWriteText(path.join(phrenPath, "global", "CLAUDE.md"), `# Global Context\n\nThis file is loaded in every project.\n\n## General preferences\n\n<!-- Your coding style, preferred tools, things Claude should always know -->\n`);
|
|
95
|
+
if (useTemplateProject) {
|
|
96
|
+
const projectDir = path.join(phrenPath, firstProjectName);
|
|
97
|
+
if (opts.template && applyTemplate(projectDir, opts.template, firstProjectName)) {
|
|
98
|
+
log(` Applied "${opts.template}" template to ${firstProjectName}`);
|
|
99
|
+
}
|
|
100
|
+
ensureProjectScaffold(projectDir, firstProjectName, firstProjectDomain, opts._walkthroughInferredScaffold);
|
|
101
|
+
}
|
|
102
|
+
const profileName = opts.profile || "default";
|
|
103
|
+
const profileProjects = useTemplateProject
|
|
104
|
+
? ` - global\n - ${firstProjectName}`
|
|
105
|
+
: ` - global`;
|
|
106
|
+
atomicWriteText(path.join(phrenPath, "profiles", `${profileName}.yaml`), `name: ${profileName}\ndescription: Default profile\nprojects:\n${profileProjects}\n`);
|
|
107
|
+
}
|
|
108
|
+
// Bootstrap CWD project if opted in
|
|
109
|
+
if (cwdProjectPath && shouldBootstrapCurrentProject) {
|
|
110
|
+
bootstrapProject(phrenPath, cwdProjectPath, opts.profile, bootstrapOwnership, "Added current project");
|
|
111
|
+
}
|
|
112
|
+
// Persist machine and config
|
|
113
|
+
const effectiveMachine = opts.machine?.trim() || getMachineName();
|
|
114
|
+
persistMachineName(effectiveMachine);
|
|
115
|
+
updateMachinesYaml(phrenPath, effectiveMachine, opts.profile);
|
|
116
|
+
ensureGovernanceFiles(phrenPath);
|
|
117
|
+
const repaired = repairPreexistingInstall(phrenPath);
|
|
118
|
+
applyOnboardingPreferences(phrenPath, opts);
|
|
119
|
+
const localGitRepo = ensureLocalGitRepo(phrenPath);
|
|
120
|
+
log(` Updated machines.yaml with machine "${effectiveMachine}"`);
|
|
121
|
+
const mcpLabel = mcpEnabled ? "ON (recommended)" : "OFF (hooks-only fallback)";
|
|
122
|
+
const hooksLabel = hooksEnabled ? "ON (active)" : "OFF (disabled)";
|
|
123
|
+
log(` MCP mode: ${mcpLabel}`);
|
|
124
|
+
log(` Hooks mode: ${hooksLabel}`);
|
|
125
|
+
log(` Default project ownership: ${ownershipDefault}`);
|
|
126
|
+
log(` Task mode: ${getWorkflowPolicy(phrenPath).taskMode}`);
|
|
127
|
+
log(` Git repo: ${localGitRepo.detail}`);
|
|
128
|
+
if (repaired.removedLegacyProjects > 0) {
|
|
129
|
+
log(` Removed ${repaired.removedLegacyProjects} legacy starter project entr${repaired.removedLegacyProjects === 1 ? "y" : "ies"} from profiles.`);
|
|
130
|
+
}
|
|
131
|
+
const repairedAssets = collectRepairedAssetLabels(repaired);
|
|
132
|
+
if (repairedAssets.length > 0) {
|
|
133
|
+
log(` Recreated missing generated assets: ${repairedAssets.join(", ")}`);
|
|
134
|
+
}
|
|
135
|
+
// Confirmation prompt before writing agent config
|
|
136
|
+
if (!opts.yes) {
|
|
137
|
+
const settingsPath = hookConfigPath("claude");
|
|
138
|
+
log(`\nWill modify:`);
|
|
139
|
+
log(` ${settingsPath} (add MCP server + hooks)`);
|
|
140
|
+
const confirmed = await confirmPrompt("\nProceed?");
|
|
141
|
+
if (!confirmed) {
|
|
142
|
+
log("Aborted.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Configure MCP and hooks
|
|
147
|
+
configureMcpTargets(phrenPath, { mcpEnabled, hooksEnabled }, "Configured");
|
|
148
|
+
configureHooksIfEnabled(phrenPath, hooksEnabled, "Configured");
|
|
149
|
+
writeInstallPreferences(phrenPath, { mcpEnabled, hooksEnabled, skillsScope, installedVersion: VERSION, syncIntent });
|
|
150
|
+
// Post-init verification
|
|
151
|
+
log(`\nVerifying setup...`);
|
|
152
|
+
const verify = runPostInitVerify(phrenPath);
|
|
153
|
+
for (const check of verify.checks) {
|
|
154
|
+
log(` ${check.ok ? "pass" : "FAIL"} ${check.name}: ${check.detail}`);
|
|
155
|
+
}
|
|
156
|
+
log(`\nWhat was created:`);
|
|
157
|
+
log(` ${phrenPath}/global/CLAUDE.md Global instructions loaded in every session`);
|
|
158
|
+
log(` ${phrenPath}/global/skills/ Phren slash commands`);
|
|
159
|
+
log(` ${phrenPath}/profiles/ Machine-to-project mappings`);
|
|
160
|
+
log(` ${phrenPath}/.config/ Memory quality settings and config`);
|
|
161
|
+
// Ollama status summary (skip if already covered in walkthrough)
|
|
162
|
+
const walkthroughCoveredOllama = Boolean(process.env._PHREN_WALKTHROUGH_OLLAMA_SKIP) || !opts.yes;
|
|
163
|
+
if (!walkthroughCoveredOllama) {
|
|
164
|
+
try {
|
|
165
|
+
const { checkOllamaAvailable, checkModelAvailable, getOllamaUrl } = await import("./shared/ollama.js");
|
|
166
|
+
if (getOllamaUrl()) {
|
|
167
|
+
const ollamaUp = await checkOllamaAvailable();
|
|
168
|
+
if (ollamaUp) {
|
|
169
|
+
const modelReady = await checkModelAvailable();
|
|
170
|
+
if (modelReady) {
|
|
171
|
+
log("\n Semantic search: Ollama + nomic-embed-text ready.");
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
log("\n Semantic search: Ollama running, but nomic-embed-text not pulled.");
|
|
175
|
+
log(" Run: ollama pull nomic-embed-text");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
log("\n Tip: Install Ollama for semantic search (optional).");
|
|
180
|
+
log(" https://ollama.com → then: ollama pull nomic-embed-text");
|
|
181
|
+
log(" (Set PHREN_OLLAMA_URL=off to hide this message)");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
logger.debug("init ollamaInstallHint", errorMessage(err));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const envLabel of writeWalkthroughEnvDefaults(phrenPath, opts)) {
|
|
190
|
+
log(` ${envLabel}`);
|
|
191
|
+
}
|
|
192
|
+
if (opts._walkthroughSemanticSearch) {
|
|
193
|
+
log(`\nWarming semantic search...`);
|
|
194
|
+
try {
|
|
195
|
+
log(` ${await warmSemanticSearch(phrenPath, opts.profile)}`);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
log(` Semantic search warmup failed: ${errorMessage(err)}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
log(`\n\x1b[95m◆\x1b[0m phren initialized`);
|
|
202
|
+
log(`\nNext steps:`);
|
|
203
|
+
let step = 1;
|
|
204
|
+
log(` ${step++}. Start a new Claude session in your project directory — phren injects context automatically`);
|
|
205
|
+
log(` ${step++}. Run \`npx phren doctor\` to verify everything is wired correctly`);
|
|
206
|
+
log(` ${step++}. Change defaults anytime: \`npx phren config project-ownership\`, \`npx phren config workflow\`, \`npx phren config proactivity.findings\`, \`npx phren config proactivity.tasks\``);
|
|
207
|
+
const gh = opts._walkthroughGithub;
|
|
208
|
+
if (gh) {
|
|
209
|
+
const remote = gh.username
|
|
210
|
+
? `git@github.com:${gh.username}/${gh.repo}.git`
|
|
211
|
+
: `git@github.com:YOUR_USERNAME/${gh.repo}.git`;
|
|
212
|
+
log(` ${step++}. Push your phren to GitHub (private repo recommended):`);
|
|
213
|
+
log(` cd ${phrenPath}`);
|
|
214
|
+
log(` git add . && git commit -m "Initial phren setup"`);
|
|
215
|
+
if (gh.username) {
|
|
216
|
+
log(` gh repo create ${gh.username}/${gh.repo} --private --source=. --push`);
|
|
217
|
+
log(` # or manually: git remote add origin ${remote} && git push -u origin main`);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
log(` git remote add origin ${remote}`);
|
|
221
|
+
log(` git push -u origin main`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
log(` ${step++}. Push to GitHub for cross-machine sync (private repo recommended):`);
|
|
226
|
+
log(` cd ${phrenPath}`);
|
|
227
|
+
log(` git add . && git commit -m "Initial phren setup"`);
|
|
228
|
+
log(` git remote add origin git@github.com:YOUR_USERNAME/my-phren.git`);
|
|
229
|
+
log(` git push -u origin main`);
|
|
230
|
+
}
|
|
231
|
+
log(` ${step++}. Add more projects: cd ~/your-project && npx phren add`);
|
|
232
|
+
if (!mcpEnabled) {
|
|
233
|
+
log(` ${step++}. Turn MCP on: npx phren mcp-mode on`);
|
|
234
|
+
}
|
|
235
|
+
log(` ${step++}. After your first week, run phren-discover to surface gaps in your project knowledge`);
|
|
236
|
+
log(` ${step++}. After working across projects, run phren-consolidate to find cross-project patterns`);
|
|
237
|
+
log(`\n Read ${phrenPath}/README.md for a guided tour of each file.`);
|
|
238
|
+
log(``);
|
|
239
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook configuration helper for init.
|
|
3
|
+
*/
|
|
4
|
+
import { configureAllHooks } from "./hooks.js";
|
|
5
|
+
import { debugLog } from "./shared.js";
|
|
6
|
+
import { errorMessage } from "./utils.js";
|
|
7
|
+
import { log } from "./init/shared.js";
|
|
8
|
+
/**
|
|
9
|
+
* Configure hooks if enabled, or log a disabled message.
|
|
10
|
+
* @param verb - label used in log messages, e.g. "Updated" or "Configured"
|
|
11
|
+
*/
|
|
12
|
+
export function configureHooksIfEnabled(phrenPath, hooksEnabled, verb) {
|
|
13
|
+
if (hooksEnabled) {
|
|
14
|
+
try {
|
|
15
|
+
const hooked = configureAllHooks(phrenPath, { allTools: true });
|
|
16
|
+
if (hooked.length)
|
|
17
|
+
log(` ${verb} hooks: ${hooked.join(", ")}`);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
debugLog(`configureAllHooks failed: ${errorMessage(err)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
log(` Hooks are disabled by preference (run: npx phren hooks-mode on)`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP target configuration for all detected AI coding tools.
|
|
3
|
+
*/
|
|
4
|
+
import { configureClaude, configureVSCode, configureCursorMcp, configureCopilotMcp, configureCodexMcp, logMcpTargetStatus, } from "./init/config.js";
|
|
5
|
+
import { debugLog } from "./shared.js";
|
|
6
|
+
import { errorMessage } from "./utils.js";
|
|
7
|
+
import { log } from "./init/shared.js";
|
|
8
|
+
/**
|
|
9
|
+
* Configure MCP for all detected AI coding tools (Claude, VS Code, Cursor, Copilot, Codex).
|
|
10
|
+
* @param verb - label used in log messages, e.g. "Updated" or "Configured"
|
|
11
|
+
*/
|
|
12
|
+
export function configureMcpTargets(phrenPath, opts, verb = "Configured") {
|
|
13
|
+
let claudeStatus = "no_settings";
|
|
14
|
+
try {
|
|
15
|
+
const status = configureClaude(phrenPath, { mcpEnabled: opts.mcpEnabled, hooksEnabled: opts.hooksEnabled });
|
|
16
|
+
claudeStatus = status ?? "installed";
|
|
17
|
+
if (status === "disabled" || status === "already_disabled") {
|
|
18
|
+
log(` ${verb} Claude Code hooks (MCP disabled)`);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
log(` ${verb} Claude Code MCP + hooks`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
log(` Could not configure Claude Code settings (${e}), add manually`);
|
|
26
|
+
}
|
|
27
|
+
let vsStatus = "no_vscode";
|
|
28
|
+
try {
|
|
29
|
+
vsStatus = configureVSCode(phrenPath, { mcpEnabled: opts.mcpEnabled }) ?? "no_vscode";
|
|
30
|
+
logMcpTargetStatus("VS Code", vsStatus, verb);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
debugLog(`configureVSCode failed: ${errorMessage(err)}`);
|
|
34
|
+
}
|
|
35
|
+
let cursorStatus = "no_cursor";
|
|
36
|
+
try {
|
|
37
|
+
cursorStatus = configureCursorMcp(phrenPath, { mcpEnabled: opts.mcpEnabled }) ?? "no_cursor";
|
|
38
|
+
logMcpTargetStatus("Cursor", cursorStatus, verb);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
debugLog(`configureCursorMcp failed: ${errorMessage(err)}`);
|
|
42
|
+
}
|
|
43
|
+
let copilotStatus = "no_copilot";
|
|
44
|
+
try {
|
|
45
|
+
copilotStatus = configureCopilotMcp(phrenPath, { mcpEnabled: opts.mcpEnabled }) ?? "no_copilot";
|
|
46
|
+
logMcpTargetStatus("Copilot CLI", copilotStatus, verb);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
debugLog(`configureCopilotMcp failed: ${errorMessage(err)}`);
|
|
50
|
+
}
|
|
51
|
+
let codexStatus = "no_codex";
|
|
52
|
+
try {
|
|
53
|
+
codexStatus = configureCodexMcp(phrenPath, { mcpEnabled: opts.mcpEnabled }) ?? "no_codex";
|
|
54
|
+
logMcpTargetStatus("Codex", codexStatus, verb);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
debugLog(`configureCodexMcp failed: ${errorMessage(err)}`);
|
|
58
|
+
}
|
|
59
|
+
const allStatuses = [claudeStatus, vsStatus, cursorStatus, copilotStatus, codexStatus];
|
|
60
|
+
if (allStatuses.some((s) => s === "installed" || s === "already_configured"))
|
|
61
|
+
return "installed";
|
|
62
|
+
if (allStatuses.some((s) => s === "disabled" || s === "already_disabled"))
|
|
63
|
+
return "disabled";
|
|
64
|
+
return claudeStatus;
|
|
65
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy migration helpers for init: directory rename, skill rename.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { homePath } from "./shared.js";
|
|
7
|
+
import { hasInstallMarkers } from "./init-detect.js";
|
|
8
|
+
/**
|
|
9
|
+
* Migrate the legacy hidden store directory into ~/.phren when upgrading
|
|
10
|
+
* from the previous product name.
|
|
11
|
+
* @returns true if migration occurred
|
|
12
|
+
*/
|
|
13
|
+
export function migrateLegacyStore(phrenPath, dryRun) {
|
|
14
|
+
const legacyPath = path.resolve(homePath(".cortex"));
|
|
15
|
+
if (legacyPath === phrenPath || !fs.existsSync(legacyPath) || !hasInstallMarkers(legacyPath)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (!dryRun) {
|
|
19
|
+
fs.renameSync(legacyPath, phrenPath);
|
|
20
|
+
}
|
|
21
|
+
console.log(`Migrated legacy store → ~/.phren`);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Rename stale legacy skill names left over from the rebrand.
|
|
26
|
+
* Runs on every init so users who already migrated the directory still get the fix.
|
|
27
|
+
*/
|
|
28
|
+
export function migrateLegacySkills(phrenPath) {
|
|
29
|
+
const skillsMigrateDir = path.join(phrenPath, "global", "skills");
|
|
30
|
+
if (!fs.existsSync(skillsMigrateDir))
|
|
31
|
+
return;
|
|
32
|
+
const legacySkillName = "cortex.md";
|
|
33
|
+
const legacySkillPrefix = "cortex-";
|
|
34
|
+
for (const entry of fs.readdirSync(skillsMigrateDir)) {
|
|
35
|
+
if (!entry.endsWith(".md"))
|
|
36
|
+
continue;
|
|
37
|
+
if (entry === legacySkillName) {
|
|
38
|
+
const dest = path.join(skillsMigrateDir, "phren.md");
|
|
39
|
+
if (!fs.existsSync(dest)) {
|
|
40
|
+
fs.renameSync(path.join(skillsMigrateDir, entry), dest);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (entry.startsWith(legacySkillPrefix)) {
|
|
44
|
+
const newName = `phren-${entry.slice(legacySkillPrefix.length)}`;
|
|
45
|
+
const dest = path.join(skillsMigrateDir, newName);
|
|
46
|
+
if (!fs.existsSync(dest)) {
|
|
47
|
+
fs.renameSync(path.join(skillsMigrateDir, entry), dest);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|