@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.
@@ -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);
@@ -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
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phren/cli",
3
- "version": "0.0.53",
3
+ "version": "0.0.55",
4
4
  "description": "Knowledge layer for AI agents. Phren learns and recalls.",
5
5
  "type": "module",
6
6
  "bin": {