codemem 0.45.0-rc.2 → 0.46.0-alpha.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { ContextOnlyReplayError, DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, EXTRACTION_BENCHMARK_QUALITY_WEIGHTS, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, ObserverOutputError, ObserverOutputTransportError, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, detectInstallKind, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, extractionReplayObserverIdentity, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, ingestRawEvents, initDatabase, isAutomaticRecallMeasurement, isEmbeddingDisabled, isExplicitUpdateInstallEligible, isReleaseVersionForChannel, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, normalizePromptTransportProtocolRange, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordAutomaticRecall, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveComparablePath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, stripTrailingCommas, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, vacuumDatabase, viewerUrl, writeCodememConfigFile } from "@codemem/core";
2
+ import { ContextOnlyReplayError, DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, EXTRACTION_BENCHMARK_QUALITY_WEIGHTS, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, ObserverOutputError, ObserverOutputTransportError, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, detectInstallKind, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, extractionReplayObserverIdentity, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getCodememConfigPath, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, ingestRawEvents, initDatabase, isAutomaticRecallMeasurement, isEmbeddingDisabled, isExplicitUpdateInstallEligible, isReleaseVersionForChannel, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, mutateCodememConfigFile, normalizePromptTransportProtocolRange, observerBaseUrlForProviderOverride, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordAutomaticRecall, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveComparablePath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, stripTrailingCommas, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, vacuumDatabase, viewerUrl } from "@codemem/core";
3
3
  import { Command, Option } from "commander";
4
4
  import omelette from "omelette";
5
5
  import { randomInt, randomUUID } from "node:crypto";
@@ -2343,7 +2343,6 @@ function runWorkspaceConfigCommand(opts) {
2343
2343
  const workspaceId = opts.workspaceId;
2344
2344
  if (!workspaceId) throw new Error("workspace-id is required");
2345
2345
  const configPath = getWorkspaceCodememConfigPath(workspaceId);
2346
- const existingConfig = existsSync(configPath) ? readCodememConfigFileAtPath(configPath) : readCodememConfigFile();
2347
2346
  const patch = buildWorkspaceConfigPatch({
2348
2347
  workspaceId,
2349
2348
  syncEnabled: opts.enableSync ? true : opts.disableSync ? false : void 0,
@@ -2355,10 +2354,11 @@ function runWorkspaceConfigCommand(opts) {
2355
2354
  json: opts.json
2356
2355
  });
2357
2356
  if (Object.keys(patch).length === 0) throw new Error("Provide at least one config field to update");
2358
- const nextConfig = mergeWorkspaceConfig(existingConfig, patch);
2357
+ const result = mutateCodememConfigFile((existingConfig) => mergeWorkspaceConfig(existingConfig, patch), configPath, { fallbackReadPath: getCodememConfigPath() });
2358
+ const nextConfig = result.data;
2359
2359
  return {
2360
2360
  workspace_id: workspaceId,
2361
- config_path: writeCodememConfigFile(nextConfig, configPath),
2361
+ config_path: result.path,
2362
2362
  updated_keys: Object.keys(patch).sort(),
2363
2363
  config: nextConfig
2364
2364
  };
@@ -4187,6 +4187,21 @@ var enqueueCmd = new Command("enqueue-raw-event").configureHelp(helpStyle).descr
4187
4187
  addDbOption(enqueueCmd);
4188
4188
  var enqueueRawEventCommand = enqueueCmd.action((opts) => runEnqueueRawEvent(opts));
4189
4189
  //#endregion
4190
+ //#region src/top-level-alias.ts
4191
+ /**
4192
+ * True when the CLI process was invoked through the named top-level
4193
+ * compatibility alias (first non-flag token of the real argv). Forwarding
4194
+ * wrappers such as `memory export` re-parse the alias command with synthetic
4195
+ * argv, so this stays false for canonical invocations.
4196
+ */
4197
+ function invokedAsTopLevelAlias(name, argv = process.argv.slice(2)) {
4198
+ for (const token of argv) {
4199
+ if (token === "--") return false;
4200
+ if (!token.startsWith("-")) return token === name;
4201
+ }
4202
+ return false;
4203
+ }
4204
+ //#endregion
4190
4205
  //#region src/commands/export-memories.ts
4191
4206
  function expandUserPath(value) {
4192
4207
  return value.startsWith("~/") ? join(homedir(), value.slice(2)) : value;
@@ -5395,6 +5410,13 @@ function summarizeExtractionBenchmarkAttempts(attempts, batches) {
5395
5410
  })
5396
5411
  };
5397
5412
  }
5413
+ function cliObserverProviderOverride(cfg, providerFlag) {
5414
+ const observerProvider = providerFlag?.trim() || cfg.observerProvider;
5415
+ return {
5416
+ observerProvider,
5417
+ observerBaseUrl: observerBaseUrlForProviderOverride(cfg, observerProvider || "")
5418
+ };
5419
+ }
5398
5420
  function createMemoryExtractionBenchmarkCommand() {
5399
5421
  const cmd = new Command("extraction-benchmark").configureHelp(helpStyle).description("Run the formal extraction replay benchmark set and print a cost/quality scoreboard").requiredOption("--benchmark <id>", "benchmark profile id").option("--observer-provider <provider>", "override observer provider for this benchmark run").option("--observer-model <model>", "override observer model for this benchmark run").option("--observer-tier-routing", "use replay-only benchmark-backed observer tier routing").option("--openai-responses", "use OpenAI Responses API for this benchmark run").option("--reasoning-effort <level>", "set OpenAI reasoning.effort for this benchmark run (responses path)").option("--reasoning-summary <mode>", "set OpenAI reasoning.summary for this benchmark run (responses path)").option("--max-output-tokens <n>", "override OpenAI max_output_tokens for this benchmark run (responses path)").option("--observer-temperature <value>", "override observer temperature for this benchmark run").option("--transcript-budget <chars>", "override replay transcript budget in characters for this benchmark run").option("--repetitions <n>", "run every benchmark batch 1-10 times to measure model stability", "1");
5400
5422
  addDbOption(cmd);
@@ -5423,7 +5445,7 @@ function createMemoryExtractionBenchmarkCommand() {
5423
5445
  const observerConfig = loadObserverConfig();
5424
5446
  const observerConfigWithOverrides = {
5425
5447
  ...observerConfig,
5426
- observerProvider: opts.observerProvider?.trim() || observerConfig.observerProvider,
5448
+ ...cliObserverProviderOverride(observerConfig, opts.observerProvider),
5427
5449
  observerModel: opts.observerModel?.trim() || observerConfig.observerModel,
5428
5450
  observerTemperature: observerTemperature ?? observerConfig.observerTemperature,
5429
5451
  observerOpenAIUseResponses: resolveOpenAIResponsesOverride(opts.openaiResponses, observerConfig.observerOpenAIUseResponses),
@@ -6792,6 +6814,19 @@ async function nativeRuntimeAllowsServeStart(invocation) {
6792
6814
  process.exitCode = 1;
6793
6815
  return false;
6794
6816
  }
6817
+ function createServeCoordinatorMaintenanceDependencies(server) {
6818
+ return {
6819
+ advancePendingProjectShares: (store, options) => server.advancePendingProjectSharesOperation(store, {
6820
+ ...options,
6821
+ advanceOperation: server.advanceProjectShareOperation
6822
+ }),
6823
+ reconcileConfiguredCoordinatorEnrollment: server.reconcileConfiguredCoordinatorEnrollment,
6824
+ reconcileRecipientPolicyProjects: (store, options) => server.reconcileRecipientPolicyProjectsOperation(store, {
6825
+ ...options,
6826
+ effects: server.createRecipientPolicyReconcilerEffects(store)
6827
+ })
6828
+ };
6829
+ }
6795
6830
  async function runServeCoordinatorMaintenance(store, dependencies) {
6796
6831
  const projectShares = await dependencies.advancePendingProjectShares(store, { limit: 3 });
6797
6832
  const coordinatorEnrollment = await dependencies.reconcileConfiguredCoordinatorEnrollment(store);
@@ -6844,8 +6879,77 @@ async function startBackgroundViewer(invocation) {
6844
6879
  p.intro("codemem viewer");
6845
6880
  p.outro(`Viewer started in background (pid ${child.pid}) at http://${invocation.host}:${invocation.port}`);
6846
6881
  }
6882
+ function startRawEventProcessing(dbPath, store, observer, createInbox) {
6883
+ const sweeper = new RawEventSweeper(store, { observer });
6884
+ sweeper.start();
6885
+ const queue = createInbox({
6886
+ dbPath,
6887
+ sweeper,
6888
+ onDrainError: () => {
6889
+ p.log.warn("Raw-event database ingestion is delayed; accepted events remain queued");
6890
+ },
6891
+ onDrainRecovered: () => {
6892
+ p.log.info("Raw-event database ingestion recovered");
6893
+ },
6894
+ onCorruptEntries: (count) => {
6895
+ p.log.error(`Raw-event queue contains ${count} corrupt retained entries`);
6896
+ },
6897
+ onBacklog: (pending, oldestAgeMs) => {
6898
+ p.log.warn(`Raw-event queue backlog is sustained (${pending} pending, oldest ${Math.ceil(oldestAgeMs / 1e3)}s)`);
6899
+ },
6900
+ onBacklogRecovered: () => {
6901
+ p.log.info("Raw-event queue backlog recovered");
6902
+ }
6903
+ });
6904
+ queue.inbox.start();
6905
+ const rawEventTarget = createRawEventTargetState(store);
6906
+ return {
6907
+ appOptions: {
6908
+ rawEventInbox: queue.inbox,
6909
+ rawEventTarget,
6910
+ sweeper,
6911
+ observer
6912
+ },
6913
+ stop: async () => {
6914
+ rawEventTarget.stop();
6915
+ await queue.stop();
6916
+ await sweeper.stop();
6917
+ }
6918
+ };
6919
+ }
6920
+ function createRawEventTargetState(store) {
6921
+ let hasCurrentIdentity = false;
6922
+ let actorId = store.actorId;
6923
+ let deviceId = store.deviceId;
6924
+ const refreshCurrentIdentity = () => {
6925
+ try {
6926
+ hasCurrentIdentity = store.hasCurrentIdentity();
6927
+ } catch {
6928
+ hasCurrentIdentity = false;
6929
+ }
6930
+ actorId = store.actorId;
6931
+ deviceId = store.deviceId;
6932
+ };
6933
+ refreshCurrentIdentity();
6934
+ const stop = store.onIdentityChanged(refreshCurrentIdentity);
6935
+ return {
6936
+ dbPath: store.dbPath,
6937
+ hasCurrentIdentity: () => {
6938
+ if (!hasCurrentIdentity || actorId !== store.actorId || deviceId !== store.deviceId) return false;
6939
+ try {
6940
+ return store.hasCurrentConfiguredIdentity();
6941
+ } catch {
6942
+ return false;
6943
+ }
6944
+ },
6945
+ stop
6946
+ };
6947
+ }
6847
6948
  async function startForegroundViewer(invocation) {
6848
- const { advancePendingProjectShares, createApp, createSyncApp, closeStore, getStore, reconcileConfiguredCoordinatorEnrollment, reconcileRecipientPolicyProjects } = await import("@codemem/server");
6949
+ const serverModule = await import("@codemem/server");
6950
+ const { createApp, createSyncApp, closeStore, createViewerRawEventInbox, getStore } = serverModule;
6951
+ const createRawEventInbox = createViewerRawEventInbox;
6952
+ const coordinatorMaintenanceDependencies = createServeCoordinatorMaintenanceDependencies(serverModule);
6849
6953
  const { serve } = await import("@hono/node-server");
6850
6954
  if (invocation.dbPath) process.env.CODEMEM_DB = invocation.dbPath;
6851
6955
  if (invocation.configPath) process.env.CODEMEM_CONFIG = invocation.configPath;
@@ -6869,8 +6973,7 @@ async function startForegroundViewer(invocation) {
6869
6973
  store = getStore();
6870
6974
  p.log.warn("Embeddings disabled for this viewer process; raw-event ingestion remains active.");
6871
6975
  }
6872
- const sweeper = new RawEventSweeper(store, { observer });
6873
- sweeper.start();
6976
+ const rawEventProcessing = startRawEventProcessing(dbPath, store, observer, createRawEventInbox);
6874
6977
  const syncAbort = new AbortController();
6875
6978
  const config = invocation.configPath ? readCodememConfigFileAtPath(invocation.configPath) : readCodememConfigFile();
6876
6979
  const syncConfig = readCoordinatorSyncConfig(config);
@@ -6881,8 +6984,7 @@ async function startForegroundViewer(invocation) {
6881
6984
  };
6882
6985
  const appOpts = {
6883
6986
  storeFactory: () => store,
6884
- sweeper,
6885
- observer,
6987
+ ...rawEventProcessing.appOptions,
6886
6988
  getSyncRuntimeStatus: () => syncRuntimeStatus
6887
6989
  };
6888
6990
  const app = createApp(appOpts);
@@ -6933,11 +7035,7 @@ async function startForegroundViewer(invocation) {
6933
7035
  signal: syncAbort.signal,
6934
7036
  scanner: store.scanner,
6935
7037
  onAfterCoordinatorRefresh: async () => {
6936
- await runServeCoordinatorMaintenance(store, {
6937
- advancePendingProjectShares,
6938
- reconcileConfiguredCoordinatorEnrollment,
6939
- reconcileRecipientPolicyProjects
6940
- });
7038
+ await runServeCoordinatorMaintenance(store, coordinatorMaintenanceDependencies);
6941
7039
  },
6942
7040
  onPhaseChange: (phase) => {
6943
7041
  if (phase === "running") {
@@ -6979,7 +7077,7 @@ async function startForegroundViewer(invocation) {
6979
7077
  syncServer?.close(done);
6980
7078
  server.close(done);
6981
7079
  }).catch(() => {});
6982
- await sweeper.stop();
7080
+ await rawEventProcessing.stop();
6983
7081
  try {
6984
7082
  rmSync(pidPath);
6985
7083
  } catch {}
@@ -7735,6 +7833,17 @@ function fmtTokens(n) {
7735
7833
  if (n >= 1e3) return `~${(n / 1e3).toFixed(0)}K`;
7736
7834
  return `${n}`;
7737
7835
  }
7836
+ function formatUsageEvent(event, provenance) {
7837
+ const parts = [`${event.event}: ${event.count.toLocaleString()}`];
7838
+ if (event.tokens_read > 0) parts.push(`read ${fmtTokens(event.tokens_read)} tokens`);
7839
+ if (event.tokens_saved > 0) parts.push(`est. saved ${fmtTokens(event.tokens_saved)} tokens`);
7840
+ if (provenance?.measured_count) parts.push(`${provenance.measured_count} provider-measured`);
7841
+ if (provenance?.estimated_count) parts.push(`${provenance.estimated_count} estimated`);
7842
+ if (provenance?.unavailable_count) parts.push(`${provenance.unavailable_count} unavailable`);
7843
+ if (provenance?.legacy_text_length_count) parts.push(`${provenance.legacy_text_length_count} legacy length excluded`);
7844
+ if (provenance?.legacy_unclassified_count) parts.push(`${provenance.legacy_unclassified_count} legacy unclassified`);
7845
+ return ` ${parts.join(", ")}`;
7846
+ }
7738
7847
  var statsCmd = new Command("stats").configureHelp(helpStyle).description("Show database statistics");
7739
7848
  addDbOption(statsCmd);
7740
7849
  addJsonOption(statsCmd);
@@ -7788,12 +7897,8 @@ var statsCommand = statsCmd.action((opts) => {
7788
7897
  `Raw events: ${db.raw_events.toLocaleString()}`
7789
7898
  ].join("\n"));
7790
7899
  if (result.usage.events.length > 0) {
7791
- const lines = result.usage.events.map((e) => {
7792
- const parts = [`${e.event}: ${e.count.toLocaleString()}`];
7793
- if (e.tokens_read > 0) parts.push(`read ${fmtTokens(e.tokens_read)} tokens`);
7794
- if (e.tokens_saved > 0) parts.push(`est. saved ${fmtTokens(e.tokens_saved)} tokens`);
7795
- return ` ${parts.join(", ")}`;
7796
- });
7900
+ const provenanceByEvent = new Map(result.usage.provenance.events.map((event) => [event.event, event]));
7901
+ const lines = result.usage.events.map((event) => formatUsageEvent(event, provenanceByEvent.get(event.event)));
7797
7902
  const t = result.usage.totals;
7798
7903
  lines.push("");
7799
7904
  lines.push(` Total: ${t.events.toLocaleString()} events, read ${fmtTokens(t.tokens_read)} tokens, est. saved ${fmtTokens(t.tokens_saved)} tokens`);
@@ -8194,8 +8299,8 @@ function collectAdvertiseAddresses(explicitAddress, configuredHost, port, interf
8194
8299
  function readCliConfig(configPath) {
8195
8300
  return configPath ? readCodememConfigFileAtPath(configPath) : readCodememConfigFile();
8196
8301
  }
8197
- function writeCliConfig(config, configPath) {
8198
- return writeCodememConfigFile(config, configPath || void 0);
8302
+ function mutateCliConfig(mutator, configPath) {
8303
+ return mutateCodememConfigFile(mutator, configPath || void 0).data;
8199
8304
  }
8200
8305
  function parseAttemptsLimit(value) {
8201
8306
  if (!/^\d+$/.test(value.trim())) throw new Error(`Invalid --limit: ${value}`);
@@ -8819,14 +8924,15 @@ enableCmd.action((opts) => {
8819
8924
  try {
8820
8925
  const keysDir = process.env.CODEMEM_KEYS_DIR?.trim() || void 0;
8821
8926
  const [deviceId, fingerprint] = ensureDeviceIdentity(store.db, { keysDir });
8822
- const config = readCliConfig(opts.config);
8823
- config.sync_enabled = true;
8824
- if (effectiveHost) config.sync_host = effectiveHost;
8825
8927
  const syncPort = parsePositiveIntegerOption(effectivePort, "--sync-port");
8826
8928
  const syncInterval = parsePositiveIntegerOption(opts.interval, "--interval");
8827
- if (syncPort != null) config.sync_port = syncPort;
8828
- if (syncInterval != null) config.sync_interval_s = syncInterval;
8829
- writeCliConfig(config, opts.config);
8929
+ const config = mutateCliConfig((current) => {
8930
+ current.sync_enabled = true;
8931
+ if (effectiveHost) current.sync_host = effectiveHost;
8932
+ if (syncPort != null) current.sync_port = syncPort;
8933
+ if (syncInterval != null) current.sync_interval_s = syncInterval;
8934
+ return current;
8935
+ }, opts.config);
8830
8936
  if (opts.json) {
8831
8937
  console.log(JSON.stringify({
8832
8938
  ok: true,
@@ -8863,9 +8969,17 @@ syncCommand.addCommand(enableCmd);
8863
8969
  var disableCmd = new Command("disable").configureHelp(helpStyle).description("Disable sync without deleting keys or peers");
8864
8970
  addConfigOption(disableCmd);
8865
8971
  disableCmd.action((opts) => {
8866
- const config = readCliConfig(opts.config);
8867
- config.sync_enabled = false;
8868
- writeCliConfig(config, opts.config);
8972
+ try {
8973
+ mutateCliConfig((config) => ({
8974
+ ...config,
8975
+ sync_enabled: false
8976
+ }), opts.config);
8977
+ } catch (error) {
8978
+ const message = error instanceof Error ? error.message : "Failed to disable sync";
8979
+ p.log.error(message);
8980
+ process.exitCode = 1;
8981
+ return;
8982
+ }
8869
8983
  p.intro("codemem sync disable");
8870
8984
  p.outro("Sync disabled — restart `codemem serve` to take effect");
8871
8985
  });
@@ -9117,10 +9231,18 @@ syncCommand.addCommand(bootstrapCmd);
9117
9231
  var connectCmd = new Command("connect").configureHelp(helpStyle).description("Configure coordinator URL for cloud sync").argument("<url>", "coordinator URL (e.g. https://coordinator.example.com)").option("--group <group>", "sync group ID");
9118
9232
  addConfigOption(connectCmd);
9119
9233
  connectCmd.action((url, opts) => {
9120
- const config = readCliConfig(opts.config);
9121
- config.sync_coordinator_url = url.trim();
9122
- if (opts.group) config.sync_coordinator_group = opts.group.trim();
9123
- writeCliConfig(config, opts.config);
9234
+ try {
9235
+ mutateCliConfig((config) => {
9236
+ config.sync_coordinator_url = url.trim();
9237
+ if (opts.group) config.sync_coordinator_group = opts.group.trim();
9238
+ return config;
9239
+ }, opts.config);
9240
+ } catch (error) {
9241
+ const message = error instanceof Error ? error.message : "Failed to configure coordinator sync";
9242
+ p.log.error(message);
9243
+ process.exitCode = 1;
9244
+ return;
9245
+ }
9124
9246
  p.intro("codemem sync connect");
9125
9247
  p.log.success(`Coordinator: ${url.trim()}`);
9126
9248
  if (opts.group) p.log.info(`Group: ${opts.group.trim()}`);
@@ -9966,19 +10088,6 @@ var ROOT_COMPLETION_COMMANDS = [
9966
10088
  "--help",
9967
10089
  "--version"
9968
10090
  ];
9969
- /**
9970
- * True when the CLI process was invoked through the named top-level
9971
- * compatibility alias (first non-flag token of the real argv). Forwarding
9972
- * wrappers such as `memory export` re-parse the alias command with synthetic
9973
- * argv, so this stays false for canonical invocations.
9974
- */
9975
- function invokedAsTopLevelAlias(name, argv = process.argv.slice(2)) {
9976
- for (const token of argv) {
9977
- if (token === "--") return false;
9978
- if (!token.startsWith("-")) return token === name;
9979
- }
9980
- return false;
9981
- }
9982
10091
  function registerMemoryGroupWrappers() {
9983
10092
  if (memoryCommand.commands.some((command) => command.name() === "export")) return;
9984
10093
  const forwardedTail = (subcommand) => {