@phren/cli 0.0.53 → 0.0.55
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/README.md +80 -35
- package/mcp/dist/cli/cli.js +5 -3
- package/mcp/dist/cli/namespaces.js +120 -0
- package/mcp/dist/generated/memory-ui-graph.browser.js +29 -26
- package/mcp/dist/memory-ui-graph.runtime.js +29 -26
- package/mcp/dist/profile-store.js +3 -0
- package/mcp/dist/ui/assets.js +1 -1
- package/mcp/dist/ui/data.js +8 -6
- package/mcp/dist/ui/page.js +26 -1
- package/mcp/dist/ui/scripts.js +305 -18
- package/mcp/dist/ui/server.js +132 -3
- package/mcp/dist/ui/styles.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
|
|
13
|
+
Persistent memory for AI agents. Findings, tasks, and patterns live in markdown files in a git repo you control. No database, no vendor lock-in. Works with Claude, Copilot, Cursor, and Codex.
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
---
|
|
@@ -21,63 +21,108 @@ Every time you start a new session, your AI agent forgets everything it learned.
|
|
|
21
21
|
npx @phren/cli init
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
One command. Sets up `~/.phren`, wires up MCP for your tools, installs hooks. Next time you open a project, context starts flowing automatically. On a new machine? Re-run init and you're back in sync.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
---
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
- **Fragments**: named concepts (auth, build, React) that connect findings across projects. Search for a topic and phren pulls in everything linked to that fragment
|
|
30
|
-
- **Tasks**: work items that persist across sessions with priority, pinning, and GitHub issue linking
|
|
31
|
-
- **Sessions**: conversation boundaries with summaries and checkpoints, so the next session picks up where the last one left off
|
|
32
|
-
- **Skills**: reusable slash commands you teach phren. Drop them in `~/.phren/global/skills/` and they work everywhere
|
|
28
|
+
## What actually happens
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
**When you open a prompt:**
|
|
31
|
+
- Hooks extract keywords from your question
|
|
32
|
+
- Phren searches findings across projects (FTS5 full-text with semantic fallback)
|
|
33
|
+
- Relevant snippets inject into your prompt before you hit send
|
|
34
|
+
- You ask; Claude already knows the gotchas
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- **Shell and web UI** for browsing, searching, and triaging (`phren` or `phren web-ui`)
|
|
36
|
+
**When you discover something:**
|
|
37
|
+
- `phren add-finding <project> "finding text"` captures it with optional tags (`[decision]`, `[pattern]`, `[pitfall]`, `[bug]`)
|
|
38
|
+
- Trust scores decay over time; decisions never do; observations expire in 14 days
|
|
39
|
+
- Findings link to fragments (named concepts like "auth" or "build") that connect knowledge across projects
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
**Sessions:**
|
|
42
|
+
- Mark boundaries with `session_start` / `session_end`
|
|
43
|
+
- Next session sees your prior summary, active tasks, recent findings, and where you left off
|
|
44
|
+
- Checkpoints track edited files and failing tests so you can resume exactly where you stopped
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
**Tasks:**
|
|
47
|
+
- Add with priority/section. Pin across sessions. Link to GitHub issues.
|
|
48
|
+
- Track completions and cross-project rollups.
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
---
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
## Key features
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
### Fragment graph
|
|
55
|
+
Explore connections visually. Drag nodes to reorganize; graph auto-settles. Click a fragment to see every finding linked to it across all projects.
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
### Finding lifecycle
|
|
58
|
+
- **Supersede**: "Finding X is obsoleted by finding Y"
|
|
59
|
+
- **Retract**: "We were wrong about this; here's why"
|
|
60
|
+
- **Contradict**: "We have two findings that conflict; this is why"
|
|
55
61
|
|
|
56
|
-
|
|
62
|
+
Helps you reason about contradictions instead of hiding them.
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
### Multi-agent support
|
|
65
|
+
Same store works with Claude Code, Copilot, Cursor, and Codex. Agents tag findings with their tool, so you see who discovered what.
|
|
66
|
+
|
|
67
|
+
### Review queue
|
|
68
|
+
Mark findings as needing review (`[Review]` section). Phren surfaces review items on every session start. Approve, reject, or edit in place.
|
|
62
69
|
|
|
63
|
-
|
|
70
|
+
### Governance & policies
|
|
71
|
+
Per-project retention policies. Confidence decay curves. Access control. Audit logs. Configure with `phren config` or the web UI.
|
|
64
72
|
|
|
73
|
+
### Store subscriptions
|
|
74
|
+
Subscribe to specific projects in a team store — others stay hidden from search and context injection:
|
|
65
75
|
```bash
|
|
66
|
-
phren team
|
|
76
|
+
phren store subscribe team-store arc intranet
|
|
77
|
+
phren store unsubscribe team-store legacy-projects
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
### Progressive disclosure
|
|
81
|
+
Enable `PHREN_FEATURE_PROGRESSIVE_DISCLOSURE=1` to get compact memory indices instead of full snippets. Call `get_memory_detail(id)` to expand only what you need.
|
|
82
|
+
|
|
83
|
+
### Semantic dedup & conflict detection
|
|
84
|
+
Optional: enable LLM-based duplicate detection and contradiction flagging on `add_finding`. Prevents near-duplicate entries and catches "always use X" vs "never use X" contradictions.
|
|
70
85
|
|
|
71
|
-
###
|
|
86
|
+
### Skills & hooks
|
|
87
|
+
Drop custom slash commands into `~/.phren/global/skills/`. Hooks run on user prompt, tool use, and session events — wire phren into your own workflows.
|
|
88
|
+
|
|
89
|
+
---
|
|
72
90
|
|
|
73
|
-
|
|
91
|
+
## CLI quick reference
|
|
74
92
|
|
|
75
93
|
```bash
|
|
76
|
-
phren
|
|
77
|
-
phren
|
|
94
|
+
phren Interactive shell (explore/search)
|
|
95
|
+
phren search <query> Full-text search with FTS5
|
|
96
|
+
phren add-finding <project> "insight" Capture a finding
|
|
97
|
+
phren task add <project> "item" Add a task
|
|
98
|
+
phren session_start <project> Start a session
|
|
99
|
+
phren store list List personal + team stores
|
|
100
|
+
phren team init <name> --remote <url> Create a team store
|
|
101
|
+
phren team join <url> Join a team store
|
|
102
|
+
phren web-ui [--port 3499] Launch the web UI
|
|
103
|
+
phren doctor Health check & auto-fix
|
|
78
104
|
```
|
|
79
105
|
|
|
80
|
-
|
|
106
|
+
See full CLI docs at [alaarab.github.io/phren](https://alaarab.github.io/phren/).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Team stores
|
|
111
|
+
|
|
112
|
+
Shared knowledge repos for teams. One person creates with `phren team init`, others join with `phren team join`. Findings, tasks, and skills sync across team members.
|
|
113
|
+
|
|
114
|
+
Each team store can be configured with per-project subscriptions so people only see what they care about.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Platforms
|
|
119
|
+
|
|
120
|
+
- **Claude Code** (VS Code, Web, Desktop) — MCP hooks + CLI
|
|
121
|
+
- **Copilot** (VS Code, GitHub.com) — MCP hooks
|
|
122
|
+
- **Cursor** (IDE) — MCP hooks + built-in skill system
|
|
123
|
+
- **Codex** (Claude Agent SDK) — MCP tools + hooks
|
|
124
|
+
|
|
125
|
+
All use the same phren store. No vendor lock-in.
|
|
81
126
|
|
|
82
127
|
---
|
|
83
128
|
|
package/mcp/dist/cli/cli.js
CHANGED
|
@@ -7,10 +7,10 @@ import { handleExtractMemories } from "./extract.js";
|
|
|
7
7
|
import { handleGovernMemories, handlePruneMemories, handleConsolidateMemories, handleMaintain, handleBackgroundMaintenance, } from "./govern.js";
|
|
8
8
|
import { handleConfig, handleIndexPolicy, handleRetentionPolicy, handleWorkflowPolicy, } from "./config.js";
|
|
9
9
|
import { parseSearchArgs } from "./search.js";
|
|
10
|
-
import { handleDetectSkills, handleFindingNamespace, handleHooksNamespace, handleProjectsNamespace, handleSkillsNamespace, handleSkillList, handlePromoteNamespace, handleStoreNamespace, handleTaskNamespace, } from "./namespaces.js";
|
|
10
|
+
import { handleDetectSkills, handleFindingNamespace, handleHooksNamespace, handleProfileNamespace, handleProjectsNamespace, handleSkillsNamespace, handleSkillList, handlePromoteNamespace, handleReviewNamespace, handleStoreNamespace, handleTaskNamespace, } from "./namespaces.js";
|
|
11
11
|
import { handleTeamNamespace } from "./team.js";
|
|
12
12
|
import { handleTaskView, handleSessionsView, handleQuickstart, handleDebugInjection, handleInspectIndex, } from "./ops.js";
|
|
13
|
-
import { handleAddFinding, handleDoctor, handleFragmentSearch, handleMemoryUi, handleTruths, handlePinCanonical, handleQualityFeedback, handleRelatedDocs,
|
|
13
|
+
import { handleAddFinding, handleDoctor, handleFragmentSearch, handleMemoryUi, handleTruths, handlePinCanonical, handleQualityFeedback, handleRelatedDocs, handleConsolidationStatus, handleSessionContext, handleSearch, handleShell, handleStatus, handleUpdate, } from "./actions.js";
|
|
14
14
|
import { handleGraphNamespace } from "./graph.js";
|
|
15
15
|
import { resolveRuntimeProfile } from "../runtime-profile.js";
|
|
16
16
|
// ── CLI router ───────────────────────────────────────────────────────────────
|
|
@@ -103,7 +103,7 @@ export async function runCliCommand(command, args) {
|
|
|
103
103
|
case "graph":
|
|
104
104
|
return handleGraphNamespace(args);
|
|
105
105
|
case "review":
|
|
106
|
-
return
|
|
106
|
+
return handleReviewNamespace(args);
|
|
107
107
|
case "consolidation-status":
|
|
108
108
|
return handleConsolidationStatus(args);
|
|
109
109
|
case "session-context":
|
|
@@ -112,6 +112,8 @@ export async function runCliCommand(command, args) {
|
|
|
112
112
|
return handleTruths(args[0]);
|
|
113
113
|
case "store":
|
|
114
114
|
return handleStoreNamespace(args);
|
|
115
|
+
case "profile":
|
|
116
|
+
return handleProfileNamespace(args);
|
|
115
117
|
case "team":
|
|
116
118
|
return handleTeamNamespace(args);
|
|
117
119
|
case "promote":
|
|
@@ -1771,6 +1771,82 @@ export async function handleStoreNamespace(args) {
|
|
|
1771
1771
|
printStoreUsage();
|
|
1772
1772
|
process.exit(1);
|
|
1773
1773
|
}
|
|
1774
|
+
// ── Profile namespace ────────────────────────────────────────────────────────
|
|
1775
|
+
function printProfileUsage() {
|
|
1776
|
+
console.log("Usage:");
|
|
1777
|
+
console.log(" phren profile list List all available profiles");
|
|
1778
|
+
console.log(" phren profile switch <name> Switch to an active profile");
|
|
1779
|
+
}
|
|
1780
|
+
export function handleProfileNamespace(args) {
|
|
1781
|
+
const subcommand = args[0];
|
|
1782
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
1783
|
+
printProfileUsage();
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
const phrenPath = getPhrenPath();
|
|
1787
|
+
if (subcommand === "list") {
|
|
1788
|
+
const { listProfiles } = require("../profile-store.js");
|
|
1789
|
+
const result = listProfiles(phrenPath);
|
|
1790
|
+
if (!result.ok) {
|
|
1791
|
+
console.error(`Failed to list profiles: ${result.error}`);
|
|
1792
|
+
process.exit(1);
|
|
1793
|
+
}
|
|
1794
|
+
const profiles = result.data || [];
|
|
1795
|
+
if (profiles.length === 0) {
|
|
1796
|
+
console.log("No profiles available.");
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
const { listMachines } = require("../profile-store.js");
|
|
1800
|
+
const machinesResult = listMachines(phrenPath);
|
|
1801
|
+
const machines = machinesResult.ok ? machinesResult.data : {};
|
|
1802
|
+
const { getMachineName } = require("../machine-identity.js");
|
|
1803
|
+
const currentMachine = getMachineName();
|
|
1804
|
+
const activeProfile = machines[currentMachine];
|
|
1805
|
+
console.log(`${profiles.length} profile(s):\n`);
|
|
1806
|
+
for (const profile of profiles) {
|
|
1807
|
+
const isCurrent = profile.name === activeProfile ? " (current)" : "";
|
|
1808
|
+
const projectCount = profile.projects?.length ?? 0;
|
|
1809
|
+
console.log(` ${profile.name}${isCurrent}`);
|
|
1810
|
+
console.log(` projects: ${projectCount}`);
|
|
1811
|
+
console.log();
|
|
1812
|
+
}
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
if (subcommand === "switch") {
|
|
1816
|
+
const profileName = args[1];
|
|
1817
|
+
if (!profileName) {
|
|
1818
|
+
console.error("Usage: phren profile switch <name>");
|
|
1819
|
+
process.exit(1);
|
|
1820
|
+
}
|
|
1821
|
+
const { setMachineProfile, getDefaultMachineAlias, listProfiles } = require("../profile-store.js");
|
|
1822
|
+
// Validate that profile exists
|
|
1823
|
+
const listResult = listProfiles(phrenPath);
|
|
1824
|
+
if (!listResult.ok) {
|
|
1825
|
+
console.error(`Failed to list profiles: ${listResult.error}`);
|
|
1826
|
+
process.exit(1);
|
|
1827
|
+
}
|
|
1828
|
+
const profiles = listResult.data || [];
|
|
1829
|
+
if (!profiles.some((p) => p.name === profileName)) {
|
|
1830
|
+
console.error(`Profile not found: "${profileName}"`);
|
|
1831
|
+
console.log("Available profiles:");
|
|
1832
|
+
for (const p of profiles) {
|
|
1833
|
+
console.log(` - ${p.name}`);
|
|
1834
|
+
}
|
|
1835
|
+
process.exit(1);
|
|
1836
|
+
}
|
|
1837
|
+
const machineAlias = getDefaultMachineAlias();
|
|
1838
|
+
const result = setMachineProfile(phrenPath, machineAlias, profileName);
|
|
1839
|
+
if (!result.ok) {
|
|
1840
|
+
console.error(`Failed to switch profile: ${result.error}`);
|
|
1841
|
+
process.exit(1);
|
|
1842
|
+
}
|
|
1843
|
+
console.log(`Switched to profile: ${profileName} (machine: ${machineAlias})`);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
console.error(`Unknown profile subcommand: ${subcommand}`);
|
|
1847
|
+
printProfileUsage();
|
|
1848
|
+
process.exit(1);
|
|
1849
|
+
}
|
|
1774
1850
|
// ── Promote namespace ────────────────────────────────────────────────────────
|
|
1775
1851
|
export async function handlePromoteNamespace(args) {
|
|
1776
1852
|
if (!args[0] || args[0] === "--help" || args[0] === "-h") {
|
|
@@ -1836,6 +1912,50 @@ export async function handlePromoteNamespace(args) {
|
|
|
1836
1912
|
console.log(`Promoted to ${toStore}/${project}:`);
|
|
1837
1913
|
console.log(` "${match.text.slice(0, 120)}${match.text.length > 120 ? "..." : ""}"`);
|
|
1838
1914
|
}
|
|
1915
|
+
// ── Review namespace ────────────────────────────────────────────────────────
|
|
1916
|
+
export async function handleReviewNamespace(args) {
|
|
1917
|
+
const subcommand = args[0];
|
|
1918
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
1919
|
+
console.log("Usage:");
|
|
1920
|
+
console.log(" phren review [project] Show review queue items");
|
|
1921
|
+
console.log(" phren review approve <project> <text> Approve and remove item");
|
|
1922
|
+
console.log(" phren review reject <project> <text> Reject and remove item");
|
|
1923
|
+
console.log("");
|
|
1924
|
+
console.log("Examples:");
|
|
1925
|
+
console.log(' phren review myproject');
|
|
1926
|
+
console.log(' phren review approve myproject "Always validate input"');
|
|
1927
|
+
console.log(' phren review reject myproject "Avoid async in loops"');
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
// Handle "approve" and "reject" subcommands
|
|
1931
|
+
if (subcommand === "approve" || subcommand === "reject") {
|
|
1932
|
+
const action = subcommand;
|
|
1933
|
+
const project = args[1];
|
|
1934
|
+
const lineText = args.slice(2).join(" ");
|
|
1935
|
+
if (!project || !lineText) {
|
|
1936
|
+
console.error(`Usage: phren review ${action} <project> <text>`);
|
|
1937
|
+
process.exit(1);
|
|
1938
|
+
}
|
|
1939
|
+
if (!isValidProjectName(project)) {
|
|
1940
|
+
console.error(`Invalid project name: "${project}".`);
|
|
1941
|
+
process.exit(1);
|
|
1942
|
+
}
|
|
1943
|
+
const phrenPath = getPhrenPath();
|
|
1944
|
+
const { approveQueueItem, rejectQueueItem } = await import("../data/access.js");
|
|
1945
|
+
const result = action === "approve"
|
|
1946
|
+
? approveQueueItem(phrenPath, project, lineText)
|
|
1947
|
+
: rejectQueueItem(phrenPath, project, lineText);
|
|
1948
|
+
if (!result.ok) {
|
|
1949
|
+
console.error(`Failed to ${action} item: ${result.error ?? "Unknown error"}`);
|
|
1950
|
+
process.exit(1);
|
|
1951
|
+
}
|
|
1952
|
+
console.log(`${action === "approve" ? "✓ Approved" : "✗ Rejected"}: ${lineText.slice(0, 100)}${lineText.length > 100 ? "..." : ""}`);
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
// Default: show review queue (first arg is project name if not a subcommand)
|
|
1956
|
+
const { handleReview } = await import("./actions.js");
|
|
1957
|
+
return handleReview(args);
|
|
1958
|
+
}
|
|
1839
1959
|
function countStoreProjects(store) {
|
|
1840
1960
|
if (!fs.existsSync(store.path))
|
|
1841
1961
|
return 0;
|