@codemem/server 0.22.0 → 0.22.1

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.
@@ -0,0 +1,8 @@
1
+ export interface InMemoryRequestRateLimiter {
2
+ check(key: string, limit: number): {
3
+ allowed: boolean;
4
+ retryAfterS: number;
5
+ };
6
+ }
7
+ export declare function createInMemoryRequestRateLimiter(windowMs?: number): InMemoryRequestRateLimiter;
8
+ //# sourceMappingURL=request-rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-rate-limit.d.ts","sourceRoot":"","sources":["../src/request-rate-limit.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,0BAA0B;IAC1C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7E;AAED,wBAAgB,gCAAgC,CAAC,QAAQ,SAAS,GAAG,0BAA0B,CA2C9F"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAKN,KAAK,eAAe,EAKpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA6D5B,MAAM,WAAW,kBAAkB;IAClC,UAAU,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CAC1C;AAgLD,wBAAgB,YAAY,CAAC,IAAI,GAAE,kBAAuB,8EAkGzD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAKN,KAAK,eAAe,EAKpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA6E5B,MAAM,WAAW,kBAAkB;IAClC,UAAU,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CAC1C;AAmOD,wBAAgB,YAAY,CAAC,IAAI,GAAE,kBAAuB,8EA6GzD"}
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Ports Python's viewer_routes/stats.py.
5
5
  */
6
- import type { MemoryStore } from "@codemem/core";
6
+ import { type MemoryStore } from "@codemem/core";
7
7
  import { Hono } from "hono";
8
8
  /**
9
9
  * Create stats routes. The store factory is called per-request to get a
@@ -1 +1 @@
1
- {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/routes/stats.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,WAAW,8EAqHtD"}
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/routes/stats.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAW,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,WAAW,8EA2HtD"}
@@ -3,11 +3,21 @@
3
3
  */
4
4
  import type { MemoryStore } from "@codemem/core";
5
5
  import { Hono } from "hono";
6
+ import type { InMemoryRequestRateLimiter } from "../request-rate-limit.js";
6
7
  type StoreFactory = () => MemoryStore;
7
8
  type SyncRuntimeStatus = {
8
9
  phase: "starting" | "running" | "stopping" | "error" | "disabled" | "rebootstrapping" | "needs_attention" | null;
9
10
  detail?: string | null;
10
11
  };
12
+ interface SyncProtocolRouteOptions {
13
+ routeRateLimit?: {
14
+ limiter: InMemoryRequestRateLimiter;
15
+ readLimit: number;
16
+ mutationLimit: number;
17
+ unauthenticatedReadLimit: number;
18
+ unauthenticatedMutationLimit: number;
19
+ };
20
+ }
11
21
  /**
12
22
  * Peer-to-peer sync protocol routes (/v1/*).
13
23
  *
@@ -15,7 +25,7 @@ type SyncRuntimeStatus = {
15
25
  * network-accessible. All requests are auth-gated via signature
16
26
  * verification so unauthenticated callers are rejected.
17
27
  */
18
- export declare function syncProtocolRoutes(getStore: StoreFactory): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
28
+ export declare function syncProtocolRoutes(getStore: StoreFactory, opts?: SyncProtocolRouteOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
19
29
  /**
20
30
  * Viewer-facing sync management routes (/api/sync/*).
21
31
  *
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AA2BhE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AACtC,KAAK,iBAAiB,GAAG;IACxB,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;IACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAkoBF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,8EAwMxD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,YAAY,EACtB,oBAAoB,CAAC,EAAE,MAAM,iBAAiB,GAAG,IAAI,8EAyzBrD"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AA2BhE,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAE3E,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AACtC,KAAK,iBAAiB,GAAG;IACxB,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;IACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,UAAU,wBAAwB;IACjC,cAAc,CAAC,EAAE;QAChB,OAAO,EAAE,0BAA0B,CAAC;QACpC,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,wBAAwB,EAAE,MAAM,CAAC;QACjC,4BAA4B,EAAE,MAAM,CAAC;KACrC,CAAC;CACF;AA8pBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,GAAE,wBAA6B,8EA+O7F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,YAAY,EACtB,oBAAoB,CAAC,EAAE,MAAM,iBAAiB,GAAG,IAAI,8EAyzBrD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemem/server",
3
- "version": "0.22.0",
3
+ "version": "0.22.1",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {
@@ -18,7 +18,8 @@
18
18
  "@hono/node-server": "^1.14.3",
19
19
  "drizzle-orm": "^0.45.1",
20
20
  "hono": "^4.7.10",
21
- "@codemem/core": "^0.22.0"
21
+ "limiter": "^3.0.0",
22
+ "@codemem/core": "^0.22.1"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/better-sqlite3": "^7.6.13",
package/static/app.js CHANGED
@@ -252,6 +252,9 @@
252
252
  async function loadStats() {
253
253
  return fetchJson("/api/stats");
254
254
  }
255
+ async function loadRuntimeInfo() {
256
+ return fetchJson("/api/runtime");
257
+ }
255
258
  async function loadUsage(project) {
256
259
  return fetchJson(`/api/usage?project=${encodeURIComponent(project)}`);
257
260
  }
@@ -2366,6 +2369,10 @@
2366
2369
  if (peer?.status?.fresh) return "connected";
2367
2370
  return "waiting";
2368
2371
  }
2372
+ function isOfflineTeamDevice(device) {
2373
+ if (!device.discovered?.stale) return false;
2374
+ return device.peer ? derivePeerUiStatus(device.peer) !== "connected" : true;
2375
+ }
2369
2376
  function peerErrorText(peer) {
2370
2377
  return cleanText(peer?.last_error).toLowerCase();
2371
2378
  }
@@ -2627,7 +2634,7 @@
2627
2634
  summary: {
2628
2635
  connectedDeviceCount: peers.filter((peer) => derivePeerUiStatus(peer) === "connected").length,
2629
2636
  seenOnTeamCount: discoveredDevices.length,
2630
- offlineTeamDeviceCount: discoveredDevices.filter((device) => Boolean(device?.stale)).length
2637
+ offlineTeamDeviceCount: mergedDevices.filter((device) => isOfflineTeamDevice(device)).length
2631
2638
  },
2632
2639
  duplicatePeople,
2633
2640
  attentionItems: attentionItems.sort((a, b) => a.priority - b.priority || a.title.localeCompare(b.title))
@@ -4145,11 +4152,6 @@
4145
4152
  function assignedActorCount(actorId) {
4146
4153
  return (Array.isArray(state.lastSyncPeers) ? state.lastSyncPeers : []).filter((peer) => String(peer?.actor_id || "") === actorId).length;
4147
4154
  }
4148
- function assignmentNote(actorId) {
4149
- if (!actorId) return "Unassigned devices keep legacy fallback attribution until you choose a person.";
4150
- if ((Array.isArray(state.lastSyncActors) ? state.lastSyncActors : []).find((item) => String(item?.actor_id || "") === actorId)?.is_local) return "Assigning this device to you keeps it in your identity across your devices, including private sync.";
4151
- return "This person receives memories from allowed projects by default. Use Only me on a memory when it should stay local.";
4152
- }
4153
4155
  function visibleSyncActors() {
4154
4156
  return deriveVisiblePeopleActors({
4155
4157
  actors: state.lastSyncActors,
@@ -4763,7 +4765,10 @@
4763
4765
  children: [/* @__PURE__ */ u("div", {
4764
4766
  className: "actor-title",
4765
4767
  children: [
4766
- /* @__PURE__ */ u("strong", { children: row.displayName }),
4768
+ /* @__PURE__ */ u("strong", {
4769
+ title: row.displayTitle || void 0,
4770
+ children: row.displayName
4771
+ }),
4767
4772
  /* @__PURE__ */ u("span", {
4768
4773
  className: `badge actor-badge${row.availabilityLabel === "Offline" ? "" : " local"}`,
4769
4774
  children: row.availabilityLabel
@@ -9957,7 +9962,12 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
9957
9962
  ensureJoinPanelInSetupSection();
9958
9963
  const coordinator = state.lastSyncCoordinator;
9959
9964
  const syncView = state.lastSyncViewModel || {
9960
- summary: {},
9965
+ summary: {
9966
+ connectedDeviceCount: 0,
9967
+ seenOnTeamCount: 0,
9968
+ offlineTeamDeviceCount: 0
9969
+ },
9970
+ duplicatePeople: [],
9961
9971
  attentionItems: []
9962
9972
  };
9963
9973
  const focusAttentionTarget = (item) => {
@@ -10080,7 +10090,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
10080
10090
  const attentionItems = Array.isArray(syncView.attentionItems) ? syncView.attentionItems : [];
10081
10091
  const discoveredRows = (Array.isArray(coordinator.discovered_devices) ? coordinator.discovered_devices : []).map((device) => {
10082
10092
  const deviceId = String(device.device_id || "").trim();
10083
- const displayName = String(device.display_name || "").trim() || deviceId || "Discovered device";
10093
+ const displayName = resolveFriendlyDeviceName({
10094
+ coordinatorName: String(device.display_name || "").trim(),
10095
+ deviceId
10096
+ }) || "Discovered device";
10097
+ const displayTitle = deviceId && displayName !== deviceId ? deviceId : null;
10084
10098
  const fingerprint = String(device.fingerprint || "").trim();
10085
10099
  const hasAmbiguousCoordinatorGroup = (Array.isArray(device.groups) ? device.groups.map((value) => String(value || "").trim()).filter(Boolean) : []).length > 1;
10086
10100
  const pairedPeer = localPeers.find((peer) => String(peer?.peer_device_id || "") === deviceId);
@@ -10092,7 +10106,8 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
10092
10106
  const hasConflict = Boolean(pairedPeer) && Boolean(fingerprint) && Boolean(pairedFingerprint) && pairedFingerprint !== fingerprint;
10093
10107
  const canAccept = Boolean(deviceId) && Boolean(fingerprint) && !pairedPeer && !device.stale && !hasAmbiguousCoordinatorGroup;
10094
10108
  const addresses = Array.isArray(device.addresses) ? device.addresses : [];
10095
- const noteParts = [deviceId, addresses.length ? addresses.map((address) => isSyncRedactionEnabled() ? redactAddress(String(address || "")) : String(address || "")).filter(Boolean).join(" · ") : "No fresh addresses"];
10109
+ const noteParts = [addresses.length ? addresses.map((address) => isSyncRedactionEnabled() ? redactAddress(String(address || "")) : String(address || "")).filter(Boolean).join(" · ") : "No fresh addresses"];
10110
+ if (displayTitle) noteParts.push(`device id: ${deviceId}`);
10096
10111
  let actionMessage = null;
10097
10112
  let mode = canAccept ? "accept" : "none";
10098
10113
  let pairedMessage = null;
@@ -10127,6 +10142,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
10127
10142
  connectionLabel: hasConflict ? SYNC_TERMINOLOGY.conflicts : pairedPeer ? SYNC_TERMINOLOGY.pairedLocally : "Not connected on this device",
10128
10143
  deviceId,
10129
10144
  displayName,
10145
+ displayTitle,
10130
10146
  fingerprint,
10131
10147
  mode,
10132
10148
  note: noteParts.join(" · "),
@@ -10544,9 +10560,6 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
10544
10560
  function listText(value) {
10545
10561
  return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
10546
10562
  }
10547
- function summaryText(prefix, values, emptyLabel) {
10548
- return `${prefix}: ${values.join(", ") || emptyLabel}`;
10549
- }
10550
10563
  function ExistingElementSlot({ element }) {
10551
10564
  const hostRef = A$1(null);
10552
10565
  _(() => {
@@ -10569,9 +10582,9 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
10569
10582
  const scope = peer.project_scope || {};
10570
10583
  const includeList = listText(scope.include);
10571
10584
  const excludeList = listText(scope.exclude);
10572
- const effectiveInclude = listText(scope.effective_include);
10573
- const effectiveExclude = listText(scope.effective_exclude);
10574
- const inheritsGlobal = Boolean(scope.inherits_global);
10585
+ listText(scope.effective_include);
10586
+ listText(scope.effective_exclude);
10587
+ Boolean(scope.inherits_global);
10575
10588
  const primaryAddress = pickPrimaryAddress(peer.addresses);
10576
10589
  const peerAddresses = Array.isArray(peer.addresses) ? Array.from(new Set(peer.addresses.filter(Boolean).map((value) => String(value)))) : [];
10577
10590
  const addressLine = peerAddresses.length ? peerAddresses.map((address) => isSyncRedactionEnabled() ? redactAddress(address) : address).join(" · ") : "No addresses";
@@ -10876,18 +10889,6 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
10876
10889
  children: applyActorLabel
10877
10890
  })]
10878
10891
  }),
10879
- /* @__PURE__ */ u("div", {
10880
- className: "peer-scope-effective",
10881
- children: assignmentNote(selectedActorId)
10882
- }),
10883
- /* @__PURE__ */ u("div", {
10884
- className: "peer-scope-summary",
10885
- children: inheritsGlobal ? "Using global sync scope" : `Device override · include: ${includeList.join(", ") || "all"} · exclude: ${excludeList.join(", ") || "none"}`
10886
- }),
10887
- /* @__PURE__ */ u("div", {
10888
- className: "peer-scope-effective",
10889
- children: `Effective scope · ${summaryText("include", effectiveInclude, "all")} · ${summaryText("exclude", effectiveExclude, "none")}`
10890
- }),
10891
10892
  /* @__PURE__ */ u("div", { ref: setScopeHost })
10892
10893
  ]
10893
10894
  })
@@ -11157,6 +11158,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11157
11158
  //#region src/tabs/sync/index.ts
11158
11159
  var lastSyncHash = "";
11159
11160
  var cachedSyncStatus = null;
11161
+ var latestSyncLoadRequestId = 0;
11160
11162
  var HEALTH_SYNC_STATUS_CACHE_TTL_MS = 15e3;
11161
11163
  function syncStatusCacheKey(project) {
11162
11164
  return `project:${project || ""}|includeJoinRequests:false`;
@@ -11183,20 +11185,22 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11183
11185
  };
11184
11186
  }
11185
11187
  async function loadSyncData() {
11188
+ const requestId = ++latestSyncLoadRequestId;
11186
11189
  try {
11187
11190
  const project = state.currentProject || "";
11188
11191
  const includeJoinRequests = state.activeTab === "sync";
11189
11192
  const useCache = state.activeTab === "health";
11193
+ let fetchedFreshSyncStatus = false;
11190
11194
  let payload;
11191
11195
  if (useCache) {
11192
11196
  payload = readCachedSyncStatus(project);
11193
11197
  if (!payload) {
11194
11198
  payload = await loadSyncStatus(true, project, { includeJoinRequests: false });
11195
- writeCachedSyncStatus(project, normalizeSyncStatusForCache(payload));
11199
+ fetchedFreshSyncStatus = true;
11196
11200
  }
11197
11201
  } else {
11198
11202
  payload = await loadSyncStatus(true, project, { includeJoinRequests });
11199
- writeCachedSyncStatus(project, normalizeSyncStatusForCache(payload));
11203
+ fetchedFreshSyncStatus = true;
11200
11204
  }
11201
11205
  let actorsPayload = null;
11202
11206
  let actorLoadError = false;
@@ -11206,6 +11210,8 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11206
11210
  } catch {
11207
11211
  actorLoadError = true;
11208
11212
  }
11213
+ if (requestId !== latestSyncLoadRequestId) return;
11214
+ if (fetchedFreshSyncStatus) writeCachedSyncStatus(project, normalizeSyncStatusForCache(payload));
11209
11215
  const hash = JSON.stringify([
11210
11216
  payload,
11211
11217
  actorsPayload,
@@ -11250,6 +11256,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11250
11256
  if (actorMeta) actorMeta.textContent = "People controls are temporarily unavailable. Device status and sync health still loaded.";
11251
11257
  }
11252
11258
  } catch {
11259
+ if (requestId !== latestSyncLoadRequestId) return;
11253
11260
  hideSkeleton("syncTeamSkeleton");
11254
11261
  hideSkeleton("syncActorsSkeleton");
11255
11262
  hideSkeleton("syncPeersSkeleton");
@@ -11285,6 +11292,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11285
11292
  var settingsEnvOverrides = {};
11286
11293
  var settingsTouchedKeys = /* @__PURE__ */ new Set();
11287
11294
  var settingsShellMounted = false;
11295
+ var settingsProtectedKeys = /* @__PURE__ */ new Set();
11288
11296
  var settingsStartPolling = null;
11289
11297
  var settingsRefresh = null;
11290
11298
  var SETTINGS_ADVANCED_KEY = "codemem-settings-advanced";
@@ -11317,6 +11325,14 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11317
11325
  syncCoordinatorTimeout: "sync_coordinator_timeout_s",
11318
11326
  syncCoordinatorPresenceTtl: "sync_coordinator_presence_ttl_s"
11319
11327
  };
11328
+ var PROTECTED_VIEWER_CONFIG_KEYS = new Set([
11329
+ "claude_command",
11330
+ "observer_base_url",
11331
+ "observer_auth_file",
11332
+ "observer_auth_command",
11333
+ "observer_headers",
11334
+ "sync_coordinator_url"
11335
+ ]);
11320
11336
  var settingsRenderState = {
11321
11337
  effectiveText: "",
11322
11338
  isSaving: false,
@@ -11802,6 +11818,12 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11802
11818
  type: hasWarning ? "warning" : "success"
11803
11819
  };
11804
11820
  }
11821
+ function isProtectedConfigKey(key) {
11822
+ return settingsProtectedKeys.has(key) || PROTECTED_VIEWER_CONFIG_KEYS.has(key);
11823
+ }
11824
+ function protectedConfigHelp(key) {
11825
+ return `${key} is read-only in the viewer for security. Edit the config file or environment instead.`;
11826
+ }
11805
11827
  function formStateFromPayload(payload) {
11806
11828
  const config = payload.config || {};
11807
11829
  const effective = payload.effective || {};
@@ -11842,8 +11864,10 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11842
11864
  const defaults = payload.defaults || {};
11843
11865
  const config = payload.config || {};
11844
11866
  const envOverrides = payload.env_overrides && typeof payload.env_overrides === "object" ? payload.env_overrides : {};
11867
+ const protectedKeys = Array.isArray(payload.protected_keys) ? payload.protected_keys.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
11845
11868
  const values = formStateFromPayload(payload);
11846
11869
  settingsEnvOverrides = envOverrides;
11870
+ settingsProtectedKeys = new Set(protectedKeys);
11847
11871
  state.configDefaults = defaults;
11848
11872
  state.configPath = payload.path || "";
11849
11873
  updateRenderState({
@@ -11996,6 +12020,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
11996
12020
  const current = collectSettingsPayload({ allowUntouchedParseErrors: true });
11997
12021
  const changed = {};
11998
12022
  Object.entries(current).forEach(([key, value]) => {
12023
+ if (isProtectedConfigKey(key)) return;
11999
12024
  if (hasOwn(settingsEnvOverrides, key) && !settingsTouchedKeys.has(key)) return;
12000
12025
  if (!isEqualValue(value, settingsBaseline[key])) changed[key] = value;
12001
12026
  });
@@ -12398,15 +12423,15 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
12398
12423
  children: "Claude command (JSON argv)"
12399
12424
  }),
12400
12425
  /* @__PURE__ */ u("textarea", {
12426
+ disabled: true,
12401
12427
  id: "claudeCommand",
12402
- onInput: onTextInput("claudeCommand"),
12403
12428
  placeholder: "[\"claude\"]",
12404
12429
  rows: 2,
12405
12430
  value: values.claudeCommand
12406
12431
  }),
12407
12432
  /* @__PURE__ */ u("div", {
12408
12433
  className: "small",
12409
- children: "Used by `claude_sidecar` runtime. Example wrapper: `[\"wrapper\", \"claude\", \"--\"]`."
12434
+ children: protectedConfigHelp("claude_command")
12410
12435
  })
12411
12436
  ]
12412
12437
  }),
@@ -12496,14 +12521,14 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
12496
12521
  children: "Token file path"
12497
12522
  }),
12498
12523
  /* @__PURE__ */ u("input", {
12524
+ disabled: true,
12499
12525
  id: "observerAuthFile",
12500
- onInput: onTextInput("observerAuthFile"),
12501
12526
  placeholder: "~/.codemem/work-token.txt",
12502
12527
  value: values.observerAuthFile
12503
12528
  }),
12504
12529
  /* @__PURE__ */ u("div", {
12505
12530
  className: "small",
12506
- children: "Reads token text from this file when method is `file`."
12531
+ children: protectedConfigHelp("observer_auth_file")
12507
12532
  })
12508
12533
  ]
12509
12534
  }),
@@ -12525,15 +12550,15 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
12525
12550
  })]
12526
12551
  }),
12527
12552
  /* @__PURE__ */ u("textarea", {
12553
+ disabled: true,
12528
12554
  id: "observerAuthCommand",
12529
- onInput: onTextInput("observerAuthCommand"),
12530
12555
  placeholder: "[\"iap-auth\", \"--audience\", \"gateway\"]",
12531
12556
  rows: 3,
12532
12557
  value: values.observerAuthCommand
12533
12558
  }),
12534
12559
  /* @__PURE__ */ u("div", {
12535
12560
  className: "small",
12536
- children: "When method is `command`, command stdout is used as the token."
12561
+ children: protectedConfigHelp("observer_auth_command")
12537
12562
  })
12538
12563
  ]
12539
12564
  }),
@@ -12589,15 +12614,15 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
12589
12614
  })]
12590
12615
  }),
12591
12616
  /* @__PURE__ */ u("textarea", {
12617
+ disabled: true,
12592
12618
  id: "observerHeaders",
12593
- onInput: onTextInput("observerHeaders"),
12594
12619
  placeholder: "{\"Authorization\":\"Bearer ${auth.token}\"}",
12595
12620
  rows: 4,
12596
12621
  value: values.observerHeaders
12597
12622
  }),
12598
12623
  /* @__PURE__ */ u("div", {
12599
12624
  className: "small",
12600
- children: ["Template variables: ", "`${auth.token}`, `${auth.type}`, `${auth.source}`."]
12625
+ children: protectedConfigHelp("observer_headers")
12601
12626
  })
12602
12627
  ]
12603
12628
  })
@@ -12781,14 +12806,14 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
12781
12806
  children: "Coordinator URL"
12782
12807
  }),
12783
12808
  /* @__PURE__ */ u("input", {
12809
+ disabled: true,
12784
12810
  id: "syncCoordinatorUrl",
12785
- onInput: onTextInput("syncCoordinatorUrl"),
12786
12811
  placeholder: "https://coord.example.com",
12787
12812
  value: values.syncCoordinatorUrl
12788
12813
  }),
12789
12814
  /* @__PURE__ */ u("div", {
12790
12815
  className: "small",
12791
- children: "Optional self-hosted/operator-run discovery service. Direct peer-to-peer sync remains the data path."
12816
+ children: protectedConfigHelp("sync_coordinator_url")
12792
12817
  })
12793
12818
  ]
12794
12819
  }),
@@ -12888,6 +12913,20 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
12888
12913
  }
12889
12914
  //#endregion
12890
12915
  //#region src/app.ts
12916
+ function setRuntimeLabel(version, commit) {
12917
+ const el = $$2("runtimeLabel");
12918
+ if (!el) return;
12919
+ el.textContent = commit ? `v${version} (${commit})` : `v${version}`;
12920
+ el.title = commit ? `codemem ${version} (${commit})` : `codemem ${version}`;
12921
+ el.hidden = false;
12922
+ }
12923
+ async function loadRuntimeLabel() {
12924
+ try {
12925
+ const runtime = await loadRuntimeInfo();
12926
+ if (!runtime?.version) return;
12927
+ setRuntimeLabel(runtime.version, "4a76585");
12928
+ } catch {}
12929
+ }
12891
12930
  var lastAnnouncedRefreshState = null;
12892
12931
  function setRefreshStatus(rs, detail) {
12893
12932
  state.refreshState = rs;
@@ -13022,6 +13061,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
13022
13061
  initSyncTab(() => refresh());
13023
13062
  initSettings(stopPolling, startPolling, () => refresh());
13024
13063
  loadProjects();
13064
+ loadRuntimeLabel();
13025
13065
  refresh();
13026
13066
  startPolling();
13027
13067
  //#endregion
package/static/index.html CHANGED
@@ -346,6 +346,13 @@
346
346
  max-width: 300px;
347
347
  }
348
348
 
349
+ .runtime-label {
350
+ font-size: 11px;
351
+ color: var(--text-tertiary);
352
+ opacity: 0.8;
353
+ white-space: nowrap;
354
+ }
355
+
349
356
  /* ── Tab bar ───────────────────────────────────────────── */
350
357
 
351
358
  .tab-bar {
@@ -1625,6 +1632,7 @@
1625
1632
  <span class="sr-only" id="refreshAnnouncer" aria-live="polite"></span>
1626
1633
  </div>
1627
1634
  <div class="header-right">
1635
+ <span class="runtime-label" id="runtimeLabel" hidden></span>
1628
1636
  <span class="meta-line" id="metaLine"></span>
1629
1637
  <select class="project-filter" id="projectFilter" aria-label="Project filter">
1630
1638
  <option value="">All Projects</option>