@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/mcp/dist/ui/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as path from "path";
|
|
|
6
6
|
import * as querystring from "querystring";
|
|
7
7
|
import { spawn, execFileSync } from "child_process";
|
|
8
8
|
import { computePhrenLiveStateToken, getProjectDirs, } from "../shared.js";
|
|
9
|
-
import { getNonPrimaryStores } from "../store-registry.js";
|
|
9
|
+
import { getNonPrimaryStores, subscribeStoreProjects, unsubscribeStoreProjects } from "../store-registry.js";
|
|
10
10
|
import { editFinding, readReviewQueue, removeFinding, readFindings, addFinding as addFindingStore, readTasksAcrossProjects, addTask as addTaskStore, completeTask as completeTaskStore, removeTask as removeTaskStore, updateTask as updateTaskStore, TASKS_FILENAME, } from "../data/access.js";
|
|
11
11
|
import { isValidProjectName, errorMessage, queueFilePath, safeProjectPath } from "../utils.js";
|
|
12
12
|
import { readInstallPreferences, writeInstallPreferences, writeGovernanceInstallPreferences } from "../init/preferences.js";
|
|
@@ -339,6 +339,28 @@ function handleGetProjects(res, ctx) {
|
|
|
339
339
|
function handleGetChangeToken(res, ctx) {
|
|
340
340
|
jsonOk(res, { token: computePhrenLiveStateToken(ctx.phrenPath) });
|
|
341
341
|
}
|
|
342
|
+
function handleGetStores(res, ctx) {
|
|
343
|
+
try {
|
|
344
|
+
const stores = getNonPrimaryStores(ctx.phrenPath);
|
|
345
|
+
const storeData = stores.map((store) => {
|
|
346
|
+
const availableProjects = getProjectDirs(store.path)
|
|
347
|
+
.map((dir) => path.basename(dir))
|
|
348
|
+
.filter((p) => p !== "global");
|
|
349
|
+
const subscribedProjects = store.projects || [];
|
|
350
|
+
return {
|
|
351
|
+
name: store.name,
|
|
352
|
+
role: store.role,
|
|
353
|
+
path: store.path,
|
|
354
|
+
availableProjects,
|
|
355
|
+
subscribedProjects,
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
jsonOk(res, { ok: true, stores: storeData });
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
jsonErr(res, errorMessage(err), 500);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
342
364
|
function handleGetRuntimeHealth(res, ctx) {
|
|
343
365
|
jsonOk(res, readSyncSnapshot(ctx.phrenPath));
|
|
344
366
|
}
|
|
@@ -816,12 +838,116 @@ function handlePostSettingsMcpEnabled(req, res, url, ctx) {
|
|
|
816
838
|
jsonOk(res, { ok: true, mcpEnabled: enabled });
|
|
817
839
|
});
|
|
818
840
|
}
|
|
841
|
+
function handleGetProfiles(res, ctx) {
|
|
842
|
+
try {
|
|
843
|
+
const { listProfiles } = require("../profile-store.js");
|
|
844
|
+
const profileResult = listProfiles(ctx.phrenPath);
|
|
845
|
+
if (!profileResult.ok) {
|
|
846
|
+
return jsonOk(res, { ok: false, error: profileResult.error, profiles: [] });
|
|
847
|
+
}
|
|
848
|
+
const profiles = profileResult.data || [];
|
|
849
|
+
jsonOk(res, { ok: true, profiles, activeProfile: ctx.profile || undefined });
|
|
850
|
+
}
|
|
851
|
+
catch (err) {
|
|
852
|
+
jsonOk(res, { ok: false, error: errorMessage(err), profiles: [] });
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function handlePostProfile(req, res, url, ctx) {
|
|
856
|
+
withPostBody(req, res, url, ctx, (parsed) => {
|
|
857
|
+
const profileName = String(parsed.profile || "").trim();
|
|
858
|
+
if (!profileName)
|
|
859
|
+
return jsonErr(res, "Profile name required", 400);
|
|
860
|
+
try {
|
|
861
|
+
const { setMachineProfile, getDefaultMachineAlias } = require("../profile-store.js");
|
|
862
|
+
const machineAlias = getDefaultMachineAlias();
|
|
863
|
+
const result = setMachineProfile(ctx.phrenPath, machineAlias, profileName);
|
|
864
|
+
if (!result.ok)
|
|
865
|
+
return jsonErr(res, result.error || "Failed to switch profile", 500);
|
|
866
|
+
jsonOk(res, { ok: true, message: `Switched to profile: ${profileName}`, profile: profileName });
|
|
867
|
+
}
|
|
868
|
+
catch (err) {
|
|
869
|
+
jsonErr(res, errorMessage(err), 500);
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
function handlePostStoreSubscribe(req, res, url, ctx) {
|
|
874
|
+
withPostBody(req, res, url, ctx, (parsed) => {
|
|
875
|
+
const storeName = String(parsed.store || "");
|
|
876
|
+
const projects = Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
877
|
+
if (!storeName)
|
|
878
|
+
return jsonErr(res, "Missing store name", 400);
|
|
879
|
+
try {
|
|
880
|
+
subscribeStoreProjects(ctx.phrenPath, storeName, projects);
|
|
881
|
+
jsonOk(res, { ok: true });
|
|
882
|
+
}
|
|
883
|
+
catch (err) {
|
|
884
|
+
jsonErr(res, errorMessage(err), 500);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
function handlePostStoreUnsubscribe(req, res, url, ctx) {
|
|
889
|
+
withPostBody(req, res, url, ctx, (parsed) => {
|
|
890
|
+
const storeName = String(parsed.store || "");
|
|
891
|
+
const projects = Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
892
|
+
if (!storeName)
|
|
893
|
+
return jsonErr(res, "Missing store name", 400);
|
|
894
|
+
try {
|
|
895
|
+
unsubscribeStoreProjects(ctx.phrenPath, storeName, projects);
|
|
896
|
+
jsonOk(res, { ok: true });
|
|
897
|
+
}
|
|
898
|
+
catch (err) {
|
|
899
|
+
jsonErr(res, errorMessage(err), 500);
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
}
|
|
819
903
|
function handlePostSettingsProjectOverrides(req, res, url, ctx) {
|
|
820
904
|
withPostBody(req, res, url, ctx, (parsed) => {
|
|
905
|
+
const globalUpdate = String(parsed.globalUpdate || "") === "true";
|
|
821
906
|
const project = String(parsed.project || "");
|
|
822
907
|
const field = String(parsed.field || "");
|
|
823
908
|
const value = String(parsed.value || "");
|
|
824
909
|
const clearField = String(parsed.clear || "") === "true";
|
|
910
|
+
const NUMERIC_RETENTION_FIELDS = ["ttlDays", "retentionDays", "autoAcceptThreshold", "minInjectConfidence"];
|
|
911
|
+
const NUMERIC_WORKFLOW_FIELDS = ["lowConfidenceThreshold"];
|
|
912
|
+
// Handle global retention/workflow policy updates
|
|
913
|
+
if (globalUpdate) {
|
|
914
|
+
try {
|
|
915
|
+
if (NUMERIC_RETENTION_FIELDS.includes(field)) {
|
|
916
|
+
const { updateRetentionPolicy } = require("../governance/policy.js");
|
|
917
|
+
let updateData = {};
|
|
918
|
+
if (!clearField) {
|
|
919
|
+
const num = parseFloat(value);
|
|
920
|
+
if (!Number.isFinite(num) || num < 0)
|
|
921
|
+
throw new Error("Invalid numeric value for " + field);
|
|
922
|
+
updateData[field] = num;
|
|
923
|
+
}
|
|
924
|
+
const result = updateRetentionPolicy(ctx.phrenPath, updateData);
|
|
925
|
+
if (!result.ok)
|
|
926
|
+
return jsonErr(res, result.error || "Failed to update retention policy", 500);
|
|
927
|
+
return jsonOk(res, { ok: true, retentionPolicy: result.data });
|
|
928
|
+
}
|
|
929
|
+
else if (NUMERIC_WORKFLOW_FIELDS.includes(field)) {
|
|
930
|
+
const { updateWorkflowPolicy } = require("../governance/policy.js");
|
|
931
|
+
let updateData = {};
|
|
932
|
+
if (!clearField) {
|
|
933
|
+
const num = parseFloat(value);
|
|
934
|
+
if (!Number.isFinite(num) || num < 0 || num > 1)
|
|
935
|
+
throw new Error("Invalid value for " + field + " (must be 0-1)");
|
|
936
|
+
updateData[field] = num;
|
|
937
|
+
}
|
|
938
|
+
const result = updateWorkflowPolicy(ctx.phrenPath, updateData);
|
|
939
|
+
if (!result.ok)
|
|
940
|
+
return jsonErr(res, result.error || "Failed to update workflow policy", 500);
|
|
941
|
+
return jsonOk(res, { ok: true, workflowPolicy: result.data });
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
return jsonErr(res, "Unknown config field: " + field, 400);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
catch (err) {
|
|
948
|
+
return jsonErr(res, errorMessage(err));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
825
951
|
if (!project || !isValidProjectName(project))
|
|
826
952
|
return jsonErr(res, "Invalid project name", 400);
|
|
827
953
|
const registeredProjects = getProjectDirs(ctx.phrenPath, ctx.profile).map((d) => path.basename(d)).filter((p) => p !== "global");
|
|
@@ -831,8 +957,6 @@ function handlePostSettingsProjectOverrides(req, res, url, ctx) {
|
|
|
831
957
|
proactivity: ["high", "medium", "low"], proactivityFindings: ["high", "medium", "low"],
|
|
832
958
|
proactivityTask: ["high", "medium", "low"], taskMode: ["off", "manual", "suggest", "auto"],
|
|
833
959
|
};
|
|
834
|
-
const NUMERIC_RETENTION_FIELDS = ["ttlDays", "retentionDays", "autoAcceptThreshold", "minInjectConfidence"];
|
|
835
|
-
const NUMERIC_WORKFLOW_FIELDS = ["lowConfidenceThreshold"];
|
|
836
960
|
try {
|
|
837
961
|
updateProjectConfigOverrides(ctx.phrenPath, project, (current) => {
|
|
838
962
|
const next = { ...current };
|
|
@@ -944,6 +1068,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
944
1068
|
switch (pathname) {
|
|
945
1069
|
case "/api/projects": return handleGetProjects(res, ctx);
|
|
946
1070
|
case "/api/change-token": return handleGetChangeToken(res, ctx);
|
|
1071
|
+
case "/api/stores": return handleGetStores(res, ctx);
|
|
947
1072
|
case "/api/runtime-health": return handleGetRuntimeHealth(res, ctx);
|
|
948
1073
|
case "/api/review-queue": return handleGetReviewQueue(res, ctx);
|
|
949
1074
|
case "/api/review-activity": return handleGetReviewActivity(res, ctx);
|
|
@@ -957,6 +1082,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
957
1082
|
case "/api/settings": return handleGetSettings(res, url, ctx);
|
|
958
1083
|
case "/api/config": return handleGetConfig(res, url, ctx);
|
|
959
1084
|
case "/api/csrf-token": return handleGetCsrfToken(res, ctx);
|
|
1085
|
+
case "/api/profiles": return handleGetProfiles(res, ctx);
|
|
960
1086
|
case "/api/search": return await handleGetSearch(res, url, ctx);
|
|
961
1087
|
}
|
|
962
1088
|
// Prefix-matched GET routes
|
|
@@ -979,6 +1105,9 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
979
1105
|
case "/api/skill-save": return handlePostSkillSave(req, res, url, ctx);
|
|
980
1106
|
case "/api/skill-toggle": return handlePostSkillToggle(req, res, url, ctx);
|
|
981
1107
|
case "/api/hook-toggle": return handlePostHookToggle(req, res, url, ctx);
|
|
1108
|
+
case "/api/profile": return handlePostProfile(req, res, url, ctx);
|
|
1109
|
+
case "/api/stores/subscribe": return handlePostStoreSubscribe(req, res, url, ctx);
|
|
1110
|
+
case "/api/stores/unsubscribe": return handlePostStoreUnsubscribe(req, res, url, ctx);
|
|
982
1111
|
case "/api/project-topics/save": return handlePostTopicsSave(req, res, url, ctx);
|
|
983
1112
|
case "/api/project-topics/reclassify": return handlePostTopicsReclassify(req, res, url, ctx);
|
|
984
1113
|
case "/api/project-topics/pin": return handlePostTopicsPin(req, res, url, ctx);
|
package/mcp/dist/ui/styles.js
CHANGED
|
@@ -655,4 +655,10 @@ export const REVIEW_UI_STYLES = `
|
|
|
655
655
|
.review-sync-indicator.error {
|
|
656
656
|
background: var(--danger);
|
|
657
657
|
}
|
|
658
|
+
/* ── Review Card Highlight (keyboard shortcuts) ──── */
|
|
659
|
+
.review-card-highlight {
|
|
660
|
+
border: 2px solid var(--blue) !important;
|
|
661
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.08), var(--shadow) !important;
|
|
662
|
+
background: color-mix(in srgb, var(--surface) 98%, var(--blue)) !important;
|
|
663
|
+
}
|
|
658
664
|
`;
|