codemem 0.40.2 → 0.41.0
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 +892 -122
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, REF_BACKFILL_JOB, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildAuthHeaders, buildBaseUrl, buildDistillReport, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, clonePromptPackAttempt, compareMemoryRoleReports, connect, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, initDatabase, isEmbeddingDisabled, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, planReplicationOpsAgePrune, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
|
|
2
|
+
import { DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, REF_BACKFILL_JOB, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildAuthHeaders, buildBaseUrl, buildDistillReport, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, 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, 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, initDatabase, isEmbeddingDisabled, isStableReleaseVersion, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, vacuumDatabase, viewerUrl, writeCodememConfigFile } from "@codemem/core";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import omelette from "omelette";
|
|
5
5
|
import { randomInt, randomUUID } from "node:crypto";
|
|
@@ -13,6 +13,7 @@ import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
|
13
13
|
import net from "node:net";
|
|
14
14
|
import { desc, eq } from "drizzle-orm";
|
|
15
15
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
16
|
+
import { mkdir, open, readFile, stat, unlink } from "node:fs/promises";
|
|
16
17
|
//#region src/help-style.ts
|
|
17
18
|
/**
|
|
18
19
|
* Shared Commander help style configuration.
|
|
@@ -1482,7 +1483,7 @@ var EMPTY_PACK$1 = {
|
|
|
1482
1483
|
packTokens: 0
|
|
1483
1484
|
};
|
|
1484
1485
|
var DEFAULT_VIEWER_HOST$1 = "127.0.0.1";
|
|
1485
|
-
var DEFAULT_VIEWER_PORT$
|
|
1486
|
+
var DEFAULT_VIEWER_PORT$2 = 38888;
|
|
1486
1487
|
var DEFAULT_MAX_CHARS$1 = 16e3;
|
|
1487
1488
|
var DEFAULT_HTTP_MAX_TIME_S$1 = 2;
|
|
1488
1489
|
function emitJson$1(value) {
|
|
@@ -1551,7 +1552,7 @@ async function tryHttpPack$1(context, project, maxTimeMs = DEFAULT_HTTP_MAX_TIME
|
|
|
1551
1552
|
packTokens: 0
|
|
1552
1553
|
};
|
|
1553
1554
|
const host = process.env.CODEMEM_VIEWER_HOST || DEFAULT_VIEWER_HOST$1;
|
|
1554
|
-
const port = parsePositiveInt$2(process.env.CODEMEM_VIEWER_PORT, DEFAULT_VIEWER_PORT$
|
|
1555
|
+
const port = parsePositiveInt$2(process.env.CODEMEM_VIEWER_PORT, DEFAULT_VIEWER_PORT$2);
|
|
1555
1556
|
const url = new URL(`http://${host}:${port}/api/pack`);
|
|
1556
1557
|
url.searchParams.set("context", context);
|
|
1557
1558
|
url.searchParams.set("limit", String(parsePositiveInt$2(process.env.CODEMEM_INJECT_LIMIT, 8)));
|
|
@@ -2182,7 +2183,7 @@ var EMPTY_PACK = {
|
|
|
2182
2183
|
packTokens: 0
|
|
2183
2184
|
};
|
|
2184
2185
|
var DEFAULT_VIEWER_HOST = "127.0.0.1";
|
|
2185
|
-
var DEFAULT_VIEWER_PORT = 38888;
|
|
2186
|
+
var DEFAULT_VIEWER_PORT$1 = 38888;
|
|
2186
2187
|
var DEFAULT_MAX_CHARS = 16e3;
|
|
2187
2188
|
var DEFAULT_HTTP_MAX_TIME_S = 2;
|
|
2188
2189
|
var CODEMEM_CONTEXT_HEADER = `## codemem memory context
|
|
@@ -2256,7 +2257,7 @@ async function buildLocalPack(context, project, dbPath) {
|
|
|
2256
2257
|
}
|
|
2257
2258
|
async function tryHttpPack(context, project, maxTimeMs = DEFAULT_HTTP_MAX_TIME_S * 1e3) {
|
|
2258
2259
|
const host = process.env.CODEMEM_VIEWER_HOST || DEFAULT_VIEWER_HOST;
|
|
2259
|
-
const port = parsePositiveInt$1(process.env.CODEMEM_VIEWER_PORT, DEFAULT_VIEWER_PORT);
|
|
2260
|
+
const port = parsePositiveInt$1(process.env.CODEMEM_VIEWER_PORT, DEFAULT_VIEWER_PORT$1);
|
|
2260
2261
|
const url = new URL(`http://${host}:${port}/api/pack`);
|
|
2261
2262
|
url.searchParams.set("context", context);
|
|
2262
2263
|
url.searchParams.set("limit", String(parsePositiveInt$1(process.env.CODEMEM_INJECT_LIMIT, 8)));
|
|
@@ -4267,6 +4268,7 @@ function expandUserPath(value) {
|
|
|
4267
4268
|
var cmd$2 = new Command("export-memories").configureHelp(helpStyle).description("Export memories to a JSON file for sharing or backup").argument("<output>", "output file path (use '-' for stdout)").option("--project <project>", "filter by project (defaults to git repo root)").option("--all-projects", "export all projects").option("--include-inactive", "include deactivated memories").option("--since <iso>", "only export memories created after this ISO timestamp");
|
|
4268
4269
|
addDbOption(cmd$2);
|
|
4269
4270
|
cmd$2.action((output, opts) => {
|
|
4271
|
+
if (output !== "-" && invokedAsTopLevelAlias("export-memories")) emitDeprecationWarning("codemem export-memories", "codemem memory export");
|
|
4270
4272
|
const payload = exportMemories({
|
|
4271
4273
|
dbPath: resolveDbPath(resolveDbOpt(opts)),
|
|
4272
4274
|
project: opts.project,
|
|
@@ -4298,6 +4300,7 @@ var cmd$1 = new Command("import-memories").configureHelp(helpStyle).description(
|
|
|
4298
4300
|
addDbOption(cmd$1);
|
|
4299
4301
|
addJsonOption(cmd$1);
|
|
4300
4302
|
cmd$1.action((inputFile, opts) => {
|
|
4303
|
+
if (!opts.json && invokedAsTopLevelAlias("import-memories")) emitDeprecationWarning("codemem import-memories", "codemem memory import");
|
|
4301
4304
|
let payload;
|
|
4302
4305
|
try {
|
|
4303
4306
|
payload = readImportPayload(inputFile);
|
|
@@ -6040,6 +6043,90 @@ function timeSince(isoDate) {
|
|
|
6040
6043
|
return `${Math.floor(days / 30)}mo ago`;
|
|
6041
6044
|
}
|
|
6042
6045
|
//#endregion
|
|
6046
|
+
//#region src/viewer-runtime.ts
|
|
6047
|
+
function isValidPid(value) {
|
|
6048
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
6049
|
+
}
|
|
6050
|
+
function isValidPort(value) {
|
|
6051
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535;
|
|
6052
|
+
}
|
|
6053
|
+
function parseViewerPidRecord(raw) {
|
|
6054
|
+
if (raw == null) return { state: "missing" };
|
|
6055
|
+
const trimmed = raw.trim();
|
|
6056
|
+
if (!trimmed) return { state: "malformed" };
|
|
6057
|
+
try {
|
|
6058
|
+
const parsed = JSON.parse(trimmed);
|
|
6059
|
+
if (isValidPid(parsed)) return {
|
|
6060
|
+
state: "legacy",
|
|
6061
|
+
pid: parsed
|
|
6062
|
+
};
|
|
6063
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { state: "malformed" };
|
|
6064
|
+
const candidate = parsed;
|
|
6065
|
+
if (!isValidPid(candidate.pid) || typeof candidate.host !== "string" || !candidate.host.trim() || !isValidPort(candidate.port)) return { state: "malformed" };
|
|
6066
|
+
return {
|
|
6067
|
+
state: "valid",
|
|
6068
|
+
record: {
|
|
6069
|
+
pid: candidate.pid,
|
|
6070
|
+
host: candidate.host.trim(),
|
|
6071
|
+
port: candidate.port
|
|
6072
|
+
}
|
|
6073
|
+
};
|
|
6074
|
+
} catch {
|
|
6075
|
+
if (!/^\d+$/.test(trimmed)) return { state: "malformed" };
|
|
6076
|
+
const pid = Number(trimmed);
|
|
6077
|
+
return isValidPid(pid) ? {
|
|
6078
|
+
state: "legacy",
|
|
6079
|
+
pid
|
|
6080
|
+
} : { state: "malformed" };
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
function isLoopbackHost(host) {
|
|
6084
|
+
const normalized = host.trim().toLowerCase().replace(/^\[(.*)\]$/, "$1");
|
|
6085
|
+
const ipv4Parts = normalized.split(".");
|
|
6086
|
+
const isIpv4Loopback = ipv4Parts.length >= 1 && ipv4Parts.length <= 4 && ipv4Parts[0] === "127" && ipv4Parts.every((part) => /^\d+$/.test(part) && Number(part) <= 255);
|
|
6087
|
+
return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || isIpv4Loopback;
|
|
6088
|
+
}
|
|
6089
|
+
async function observeViewerRuntime(parsed, deps, defaultTarget = {
|
|
6090
|
+
host: "127.0.0.1",
|
|
6091
|
+
port: 38888
|
|
6092
|
+
}) {
|
|
6093
|
+
if (parsed.state === "malformed") return {
|
|
6094
|
+
state: "unknown",
|
|
6095
|
+
attention_code: "viewer_pid_malformed"
|
|
6096
|
+
};
|
|
6097
|
+
const record = parsed.state === "valid" ? parsed.record : parsed.state === "legacy" ? {
|
|
6098
|
+
...defaultTarget,
|
|
6099
|
+
pid: parsed.pid
|
|
6100
|
+
} : defaultTarget;
|
|
6101
|
+
if (!isLoopbackHost(record.host)) return {
|
|
6102
|
+
state: "unknown",
|
|
6103
|
+
pid: record.pid,
|
|
6104
|
+
attention_code: "viewer_non_loopback"
|
|
6105
|
+
};
|
|
6106
|
+
if (record.pid && deps.isProcessRunning(record.pid) === false) return {
|
|
6107
|
+
state: "stopped",
|
|
6108
|
+
pid: record.pid
|
|
6109
|
+
};
|
|
6110
|
+
const probe = await probeCodememViewerLiveness(record, deps);
|
|
6111
|
+
if (probe.state === "live") return probe.degraded ? {
|
|
6112
|
+
state: "running",
|
|
6113
|
+
pid: record.pid,
|
|
6114
|
+
attention_code: "viewer_not_ready"
|
|
6115
|
+
} : record.pid ? {
|
|
6116
|
+
state: "running",
|
|
6117
|
+
pid: record.pid
|
|
6118
|
+
} : { state: "running" };
|
|
6119
|
+
if (probe.reason === "unreachable") return {
|
|
6120
|
+
state: "unreachable",
|
|
6121
|
+
pid: record.pid
|
|
6122
|
+
};
|
|
6123
|
+
return {
|
|
6124
|
+
state: "unknown",
|
|
6125
|
+
pid: record.pid,
|
|
6126
|
+
attention_code: probe.reason === "wrong_service" ? "viewer_wrong_service" : "viewer_unexpected_response"
|
|
6127
|
+
};
|
|
6128
|
+
}
|
|
6129
|
+
//#endregion
|
|
6043
6130
|
//#region src/commands/serve-invocation.ts
|
|
6044
6131
|
/**
|
|
6045
6132
|
* Parse and validate a port string. Throws a user-friendly message (no stack trace)
|
|
@@ -6102,8 +6189,7 @@ function isLocalHost(host) {
|
|
|
6102
6189
|
return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1" || normalized === "0.0.0.0" || normalized === "::";
|
|
6103
6190
|
}
|
|
6104
6191
|
function isLoopbackOnlyHost(host) {
|
|
6105
|
-
|
|
6106
|
-
return normalized === "localhost" || /^127(?:\.\d{1,3}){0,3}$/.test(normalized) || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1";
|
|
6192
|
+
return isLoopbackHost(host);
|
|
6107
6193
|
}
|
|
6108
6194
|
function warnIfViewerExposed(host, port) {
|
|
6109
6195
|
if (isLoopbackOnlyHost(host)) return;
|
|
@@ -6203,22 +6289,13 @@ function commandHasExactDbPath(command, dbPath) {
|
|
|
6203
6289
|
function readViewerPidRecord(dbPath) {
|
|
6204
6290
|
const pidPath = pidFilePath(dbPath);
|
|
6205
6291
|
if (!existsSync(pidPath)) return null;
|
|
6206
|
-
const
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
};
|
|
6214
|
-
} catch {
|
|
6215
|
-
const pid = Number.parseInt(raw, 10);
|
|
6216
|
-
if (Number.isFinite(pid) && pid > 0) return {
|
|
6217
|
-
pid,
|
|
6218
|
-
host: "127.0.0.1",
|
|
6219
|
-
port: 38888
|
|
6220
|
-
};
|
|
6221
|
-
}
|
|
6292
|
+
const parsed = parseViewerPidRecord(readFileSync(pidPath, "utf-8"));
|
|
6293
|
+
if (parsed.state === "valid") return parsed.record;
|
|
6294
|
+
if (parsed.state === "legacy") return {
|
|
6295
|
+
pid: parsed.pid,
|
|
6296
|
+
host: "127.0.0.1",
|
|
6297
|
+
port: 38888
|
|
6298
|
+
};
|
|
6222
6299
|
return null;
|
|
6223
6300
|
}
|
|
6224
6301
|
function normalizeViewerHost(host) {
|
|
@@ -6242,23 +6319,18 @@ function isProcessRunning(pid) {
|
|
|
6242
6319
|
return false;
|
|
6243
6320
|
}
|
|
6244
6321
|
}
|
|
6245
|
-
async function respondsLikeCodememViewer(record) {
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
clearTimeout(timer);
|
|
6251
|
-
return res.ok;
|
|
6252
|
-
} catch {
|
|
6253
|
-
return false;
|
|
6254
|
-
}
|
|
6322
|
+
async function respondsLikeCodememViewer(record, fetchImpl = fetch) {
|
|
6323
|
+
return (await probeCodememViewerLiveness$1(record, {
|
|
6324
|
+
fetch: fetchImpl,
|
|
6325
|
+
timeoutMs: 1e3
|
|
6326
|
+
})).state === "live";
|
|
6255
6327
|
}
|
|
6256
6328
|
async function lookupViewerPidFromStats(host, port) {
|
|
6257
6329
|
try {
|
|
6258
|
-
const
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6330
|
+
const res = await fetch(viewerUrl({
|
|
6331
|
+
host,
|
|
6332
|
+
port
|
|
6333
|
+
}, "/api/stats"), { signal: AbortSignal.timeout(1e3) });
|
|
6262
6334
|
if (!res.ok) return null;
|
|
6263
6335
|
return extractViewerPid(await res.json());
|
|
6264
6336
|
} catch {
|
|
@@ -6467,11 +6539,14 @@ async function runServeCoordinatorMaintenance(store, dependencies) {
|
|
|
6467
6539
|
const enrollmentIssues = coordinatorEnrollment.issues ?? 0;
|
|
6468
6540
|
const recipientPolicies = await dependencies.reconcileRecipientPolicyProjects(store, { limit: 3 });
|
|
6469
6541
|
if (projectShares.failed > 0) throw new Error(`share operation maintenance failed for ${projectShares.failed} of ${projectShares.processed} operations`);
|
|
6470
|
-
if (coordinatorEnrollment.failedGroups > 0
|
|
6542
|
+
if (coordinatorEnrollment.failedGroups > 0) {
|
|
6471
6543
|
const groupLabel = coordinatorEnrollment.failedGroups === 1 ? "group" : "groups";
|
|
6472
6544
|
const issueLabel = enrollmentIssues === 1 ? "issue" : "issues";
|
|
6473
|
-
|
|
6545
|
+
const failures = coordinatorEnrollment.failures ?? [];
|
|
6546
|
+
const failureDetail = failures.length ? ` [${failures.slice(0, 3).map((failure) => `${failure.groupId}:${failure.stage}:${failure.code}`).join(", ")}${failures.length > 3 ? `, +${failures.length - 3} more` : ""}]` : "";
|
|
6547
|
+
throw new Error(`coordinator enrollment maintenance failed for ${coordinatorEnrollment.failedGroups} ${groupLabel} with ${enrollmentIssues} reconciliation ${issueLabel}${failureDetail}`);
|
|
6474
6548
|
}
|
|
6549
|
+
if (enrollmentIssues > 0) throw new Error(`coordinator enrollment reconciliation found ${enrollmentIssues} ${enrollmentIssues === 1 ? "issue" : "issues"}`);
|
|
6475
6550
|
if (recipientPolicies.failed > 0) throw new Error(`recipient policy maintenance failed for ${recipientPolicies.failed} of ${recipientPolicies.processed} projects`);
|
|
6476
6551
|
return {
|
|
6477
6552
|
projectShares,
|
|
@@ -7290,6 +7365,341 @@ var statsCommand = statsCmd.action((opts) => {
|
|
|
7290
7365
|
}
|
|
7291
7366
|
});
|
|
7292
7367
|
//#endregion
|
|
7368
|
+
//#region src/commands/status.ts
|
|
7369
|
+
var MAX_ATTENTION = 20;
|
|
7370
|
+
var DEFAULT_VIEWER_PORT = 38888;
|
|
7371
|
+
var RECENT_FAILURE_WINDOW_MS = 1440 * 60 * 1e3;
|
|
7372
|
+
var OBSERVER_IDENTITY_KEYS = [
|
|
7373
|
+
"observer_provider",
|
|
7374
|
+
"observer_model",
|
|
7375
|
+
"observer_runtime",
|
|
7376
|
+
"observer_base_url",
|
|
7377
|
+
"observer_simple_provider",
|
|
7378
|
+
"observer_simple_model",
|
|
7379
|
+
"observer_rich_provider",
|
|
7380
|
+
"observer_rich_model"
|
|
7381
|
+
];
|
|
7382
|
+
var defaultDependencies = {
|
|
7383
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
7384
|
+
exists: existsSync,
|
|
7385
|
+
readText: (path) => {
|
|
7386
|
+
try {
|
|
7387
|
+
return readFileSync(path, "utf8");
|
|
7388
|
+
} catch {
|
|
7389
|
+
return null;
|
|
7390
|
+
}
|
|
7391
|
+
},
|
|
7392
|
+
readConfig: (path) => path ? readCodememConfigFileAtPath(path) : readCodememConfigFile(),
|
|
7393
|
+
resolveDbPath,
|
|
7394
|
+
connectReadOnly,
|
|
7395
|
+
collectDatabase: collectOperationalStatus,
|
|
7396
|
+
embeddingDisabled: isEmbeddingDisabled,
|
|
7397
|
+
fetch,
|
|
7398
|
+
isProcessRunning: (pid) => {
|
|
7399
|
+
try {
|
|
7400
|
+
process.kill(pid, 0);
|
|
7401
|
+
return true;
|
|
7402
|
+
} catch (error) {
|
|
7403
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "EPERM") return null;
|
|
7404
|
+
return false;
|
|
7405
|
+
}
|
|
7406
|
+
},
|
|
7407
|
+
env: process.env,
|
|
7408
|
+
writeStdout: (text) => console.log(text),
|
|
7409
|
+
writeStderr: (text) => console.error(text),
|
|
7410
|
+
setExitCode: (code) => {
|
|
7411
|
+
process.exitCode = code;
|
|
7412
|
+
}
|
|
7413
|
+
};
|
|
7414
|
+
function boolValue(value) {
|
|
7415
|
+
if (typeof value === "boolean") return value;
|
|
7416
|
+
if (typeof value !== "string") return null;
|
|
7417
|
+
const normalized = value.trim().toLowerCase();
|
|
7418
|
+
if ([
|
|
7419
|
+
"1",
|
|
7420
|
+
"true",
|
|
7421
|
+
"yes",
|
|
7422
|
+
"on"
|
|
7423
|
+
].includes(normalized)) return true;
|
|
7424
|
+
if ([
|
|
7425
|
+
"0",
|
|
7426
|
+
"false",
|
|
7427
|
+
"no",
|
|
7428
|
+
"off"
|
|
7429
|
+
].includes(normalized)) return false;
|
|
7430
|
+
return null;
|
|
7431
|
+
}
|
|
7432
|
+
function syncEnabled(config, env) {
|
|
7433
|
+
return boolValue(env.CODEMEM_SYNC_ENABLED) ?? boolValue(config.sync_enabled) ?? false;
|
|
7434
|
+
}
|
|
7435
|
+
function observerConfigured(config, env) {
|
|
7436
|
+
const fileConfigured = OBSERVER_IDENTITY_KEYS.some((key) => {
|
|
7437
|
+
const value = config[key];
|
|
7438
|
+
return typeof value === "string" && value.trim() !== "";
|
|
7439
|
+
});
|
|
7440
|
+
const envConfigured = OBSERVER_IDENTITY_KEYS.some((key) => {
|
|
7441
|
+
const value = env[`CODEMEM_${key.toUpperCase()}`];
|
|
7442
|
+
return typeof value === "string" && value.trim() !== "";
|
|
7443
|
+
});
|
|
7444
|
+
return fileConfigured || envConfigured;
|
|
7445
|
+
}
|
|
7446
|
+
function viewerDefaultTarget(env) {
|
|
7447
|
+
const host = env.CODEMEM_VIEWER_HOST?.trim() || "127.0.0.1";
|
|
7448
|
+
const parsedPort = Number.parseInt(env.CODEMEM_VIEWER_PORT ?? "", 10);
|
|
7449
|
+
return {
|
|
7450
|
+
host,
|
|
7451
|
+
port: Number.isFinite(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : DEFAULT_VIEWER_PORT
|
|
7452
|
+
};
|
|
7453
|
+
}
|
|
7454
|
+
function boundAttention(items) {
|
|
7455
|
+
return items.slice(0, MAX_ATTENTION).map((item) => ({
|
|
7456
|
+
code: item.code.replace(/[^a-z0-9_]/gi, "_").slice(0, 64),
|
|
7457
|
+
severity: item.severity,
|
|
7458
|
+
message: item.message.slice(0, 500)
|
|
7459
|
+
}));
|
|
7460
|
+
}
|
|
7461
|
+
function projectSync(enabled, snapshot, attention) {
|
|
7462
|
+
if (!enabled) return "disabled";
|
|
7463
|
+
if (!snapshot) return "unknown";
|
|
7464
|
+
if (snapshot.sync.daemon_error || snapshot.sync.needs_attention) {
|
|
7465
|
+
attention.push({
|
|
7466
|
+
code: "sync_daemon_error",
|
|
7467
|
+
severity: "error",
|
|
7468
|
+
message: "Sync requires attention; run `codemem sync doctor`"
|
|
7469
|
+
});
|
|
7470
|
+
return "error";
|
|
7471
|
+
}
|
|
7472
|
+
if (snapshot.sync.peer_errors > 0) {
|
|
7473
|
+
attention.push({
|
|
7474
|
+
code: "sync_degraded",
|
|
7475
|
+
severity: "warning",
|
|
7476
|
+
message: "Sync has recent peer failures; run `codemem sync status`"
|
|
7477
|
+
});
|
|
7478
|
+
return "degraded";
|
|
7479
|
+
}
|
|
7480
|
+
if (!snapshot.sync.available) return "unknown";
|
|
7481
|
+
return "healthy";
|
|
7482
|
+
}
|
|
7483
|
+
function addDatabaseAttention(state, attention) {
|
|
7484
|
+
if (state === "missing") attention.push({
|
|
7485
|
+
code: "database_missing",
|
|
7486
|
+
severity: "error",
|
|
7487
|
+
message: "Database does not exist"
|
|
7488
|
+
});
|
|
7489
|
+
else if (state === "unavailable") attention.push({
|
|
7490
|
+
code: "database_unavailable",
|
|
7491
|
+
severity: "error",
|
|
7492
|
+
message: "Database could not be read"
|
|
7493
|
+
});
|
|
7494
|
+
}
|
|
7495
|
+
function addRuntimeAttention(runtime, attention) {
|
|
7496
|
+
if (runtime.state === "stopped") {
|
|
7497
|
+
attention.push({
|
|
7498
|
+
code: "viewer_stopped",
|
|
7499
|
+
severity: "warning",
|
|
7500
|
+
message: "Viewer is stopped; run `codemem serve start` if needed"
|
|
7501
|
+
});
|
|
7502
|
+
return;
|
|
7503
|
+
}
|
|
7504
|
+
if (runtime.state === "unreachable") {
|
|
7505
|
+
attention.push({
|
|
7506
|
+
code: "viewer_unreachable",
|
|
7507
|
+
severity: "warning",
|
|
7508
|
+
message: "Viewer did not answer its local health check"
|
|
7509
|
+
});
|
|
7510
|
+
return;
|
|
7511
|
+
}
|
|
7512
|
+
if (runtime.attention_code) attention.push({
|
|
7513
|
+
code: runtime.attention_code,
|
|
7514
|
+
severity: "warning",
|
|
7515
|
+
message: {
|
|
7516
|
+
viewer_pid_malformed: "Viewer PID record is malformed",
|
|
7517
|
+
viewer_non_loopback: "Viewer PID record is not loopback; no request was made",
|
|
7518
|
+
viewer_not_ready: "Viewer is running but its database is not ready",
|
|
7519
|
+
viewer_unexpected_response: "Viewer returned an unexpected local health response",
|
|
7520
|
+
viewer_wrong_service: "Local health endpoint did not identify codemem viewer"
|
|
7521
|
+
}[runtime.attention_code]
|
|
7522
|
+
});
|
|
7523
|
+
}
|
|
7524
|
+
function projectDatabaseSubsystems(snapshot, configuredObserver, attention) {
|
|
7525
|
+
if (!snapshot) return {
|
|
7526
|
+
maintenance: { state: "unknown" },
|
|
7527
|
+
semantic_index: { state: "unknown" },
|
|
7528
|
+
raw_events: {
|
|
7529
|
+
state: "unknown",
|
|
7530
|
+
pending: 0
|
|
7531
|
+
},
|
|
7532
|
+
observer: { state: configuredObserver ? "unknown" : "unconfigured" }
|
|
7533
|
+
};
|
|
7534
|
+
if (snapshot.maintenance.state === "failed") attention.push({
|
|
7535
|
+
code: "maintenance_failed",
|
|
7536
|
+
severity: "error",
|
|
7537
|
+
message: "Maintenance has failed jobs; run `codemem maintenance status`"
|
|
7538
|
+
});
|
|
7539
|
+
else if (snapshot.maintenance.state === "running") attention.push({
|
|
7540
|
+
code: "maintenance_running",
|
|
7541
|
+
severity: "warning",
|
|
7542
|
+
message: "Maintenance work is in progress"
|
|
7543
|
+
});
|
|
7544
|
+
if (snapshot.semantic_index.state === "failed") attention.push({
|
|
7545
|
+
code: "semantic_index_failed",
|
|
7546
|
+
severity: "error",
|
|
7547
|
+
message: "Semantic index maintenance failed; run `codemem maintenance status`"
|
|
7548
|
+
});
|
|
7549
|
+
else if (snapshot.semantic_index.state === "pending") attention.push({
|
|
7550
|
+
code: "semantic_index_pending",
|
|
7551
|
+
severity: "warning",
|
|
7552
|
+
message: "Semantic index maintenance is pending"
|
|
7553
|
+
});
|
|
7554
|
+
else if (snapshot.semantic_index.state === "degraded") attention.push({
|
|
7555
|
+
code: "semantic_index_degraded",
|
|
7556
|
+
severity: "warning",
|
|
7557
|
+
message: "Semantic search is unavailable; keyword search remains available"
|
|
7558
|
+
});
|
|
7559
|
+
const pending = Math.max(0, Math.trunc(snapshot.raw_events.pending));
|
|
7560
|
+
let rawState = snapshot.raw_events.available ? "healthy" : "unknown";
|
|
7561
|
+
if (snapshot.raw_events.failed_batches > 0) {
|
|
7562
|
+
rawState = "failing";
|
|
7563
|
+
attention.push({
|
|
7564
|
+
code: "raw_events_failing",
|
|
7565
|
+
severity: "error",
|
|
7566
|
+
message: "Raw-event ingestion exhausted retries recently; run `codemem db raw-events-gate`"
|
|
7567
|
+
});
|
|
7568
|
+
} else if (pending > 0) {
|
|
7569
|
+
rawState = "backlogged";
|
|
7570
|
+
attention.push({
|
|
7571
|
+
code: "raw_events_backlogged",
|
|
7572
|
+
severity: "warning",
|
|
7573
|
+
message: `${pending} raw events pending; run \`codemem db raw-events-status\``
|
|
7574
|
+
});
|
|
7575
|
+
}
|
|
7576
|
+
let observerState = "unconfigured";
|
|
7577
|
+
if (configuredObserver) observerState = snapshot.observer.available ? "idle" : "unknown";
|
|
7578
|
+
if (configuredObserver && snapshot.observer.failed_batches > 0) {
|
|
7579
|
+
observerState = "failed";
|
|
7580
|
+
attention.push({
|
|
7581
|
+
code: "observer_failed",
|
|
7582
|
+
severity: "error",
|
|
7583
|
+
message: "Observer exhausted retries recently; run `codemem db raw-events-gate`"
|
|
7584
|
+
});
|
|
7585
|
+
} else if (configuredObserver && snapshot.observer.backoff_batches > 0) {
|
|
7586
|
+
observerState = "backoff";
|
|
7587
|
+
attention.push({
|
|
7588
|
+
code: "observer_backoff",
|
|
7589
|
+
severity: "warning",
|
|
7590
|
+
message: "Observer has retryable failures; run `codemem db raw-events-status`"
|
|
7591
|
+
});
|
|
7592
|
+
}
|
|
7593
|
+
return {
|
|
7594
|
+
maintenance: { state: snapshot.maintenance.state },
|
|
7595
|
+
semantic_index: { state: snapshot.semantic_index.state },
|
|
7596
|
+
raw_events: {
|
|
7597
|
+
state: rawState,
|
|
7598
|
+
pending
|
|
7599
|
+
},
|
|
7600
|
+
observer: { state: observerState }
|
|
7601
|
+
};
|
|
7602
|
+
}
|
|
7603
|
+
async function collectStatusReport(opts, deps = defaultDependencies) {
|
|
7604
|
+
const checkedAt = deps.now();
|
|
7605
|
+
const config = deps.readConfig(opts.config);
|
|
7606
|
+
const dbPath = deps.resolveDbPath(resolveDbOpt(opts));
|
|
7607
|
+
let databaseState = "missing";
|
|
7608
|
+
let snapshot = null;
|
|
7609
|
+
if (deps.exists(dbPath)) {
|
|
7610
|
+
let db = null;
|
|
7611
|
+
try {
|
|
7612
|
+
db = deps.connectReadOnly(dbPath, { warn: opts.json ? () => {} : deps.writeStderr });
|
|
7613
|
+
} catch {
|
|
7614
|
+
databaseState = "unavailable";
|
|
7615
|
+
}
|
|
7616
|
+
if (db) try {
|
|
7617
|
+
snapshot = deps.collectDatabase(db, {
|
|
7618
|
+
embeddingDisabled: deps.embeddingDisabled(),
|
|
7619
|
+
recentFailureCutoff: (/* @__PURE__ */ new Date(checkedAt.getTime() - RECENT_FAILURE_WINDOW_MS)).toISOString()
|
|
7620
|
+
});
|
|
7621
|
+
databaseState = "ready";
|
|
7622
|
+
} finally {
|
|
7623
|
+
db.close();
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7626
|
+
const pidPath = join(dirname(dbPath), "viewer.pid");
|
|
7627
|
+
const runtime = await observeViewerRuntime(parseViewerPidRecord(deps.exists(pidPath) ? deps.readText(pidPath) : null), {
|
|
7628
|
+
fetch: deps.fetch,
|
|
7629
|
+
isProcessRunning: deps.isProcessRunning
|
|
7630
|
+
}, viewerDefaultTarget(deps.env));
|
|
7631
|
+
const attention = [];
|
|
7632
|
+
addDatabaseAttention(databaseState, attention);
|
|
7633
|
+
addRuntimeAttention(runtime, attention);
|
|
7634
|
+
const sync = projectSync(syncEnabled(config, deps.env), snapshot, attention);
|
|
7635
|
+
const subsystems = projectDatabaseSubsystems(snapshot, observerConfigured(config, deps.env), attention);
|
|
7636
|
+
const ok = !attention.some((item) => item.severity === "error");
|
|
7637
|
+
const boundedAttention = boundAttention(attention);
|
|
7638
|
+
return {
|
|
7639
|
+
checked_at: checkedAt.toISOString(),
|
|
7640
|
+
ok,
|
|
7641
|
+
version: VERSION,
|
|
7642
|
+
database: { state: databaseState },
|
|
7643
|
+
runtime: runtime.pid ? {
|
|
7644
|
+
viewer: runtime.state,
|
|
7645
|
+
pid: runtime.pid
|
|
7646
|
+
} : { viewer: runtime.state },
|
|
7647
|
+
sync: { state: sync },
|
|
7648
|
+
...subsystems,
|
|
7649
|
+
attention: boundedAttention
|
|
7650
|
+
};
|
|
7651
|
+
}
|
|
7652
|
+
function renderStatusReport(report) {
|
|
7653
|
+
const lines = [
|
|
7654
|
+
`codemem status ${report.ok ? "OK" : "ATTENTION"}`,
|
|
7655
|
+
`Database: ${report.database.state}`,
|
|
7656
|
+
`Viewer: ${report.runtime.viewer}${report.runtime.pid ? ` (pid ${report.runtime.pid})` : ""}`,
|
|
7657
|
+
`Sync: ${report.sync.state}`,
|
|
7658
|
+
`Maintenance: ${report.maintenance.state}`,
|
|
7659
|
+
`Semantic index: ${report.semantic_index.state}`,
|
|
7660
|
+
`Raw events: ${report.raw_events.state}${report.raw_events.pending > 0 ? ` (${report.raw_events.pending} pending)` : ""}`,
|
|
7661
|
+
`Observer: ${report.observer.state}`
|
|
7662
|
+
];
|
|
7663
|
+
if (report.attention.length > 0) lines.push("Attention:", ...report.attention.map((item) => `- ${item.severity}: ${item.message}`));
|
|
7664
|
+
return lines.join("\n");
|
|
7665
|
+
}
|
|
7666
|
+
function createStatusCommand(overrides = {}) {
|
|
7667
|
+
const deps = {
|
|
7668
|
+
...defaultDependencies,
|
|
7669
|
+
...overrides
|
|
7670
|
+
};
|
|
7671
|
+
const command = new Command("status").configureHelp(helpStyle).description("Show local operational status").allowUnknownOption(true).allowExcessArguments(true);
|
|
7672
|
+
addDbOption(command);
|
|
7673
|
+
addConfigOption(command);
|
|
7674
|
+
addJsonOption(command);
|
|
7675
|
+
return command.action(async (opts, actionCommand) => {
|
|
7676
|
+
if (actionCommand.args.length > 0) {
|
|
7677
|
+
const message = "status accepts only documented options and no positional arguments";
|
|
7678
|
+
if (opts.json) deps.writeStdout(JSON.stringify({
|
|
7679
|
+
error: "usage_error",
|
|
7680
|
+
message
|
|
7681
|
+
}));
|
|
7682
|
+
else deps.writeStderr(message);
|
|
7683
|
+
deps.setExitCode(2);
|
|
7684
|
+
return;
|
|
7685
|
+
}
|
|
7686
|
+
try {
|
|
7687
|
+
const report = await collectStatusReport(opts, deps);
|
|
7688
|
+
deps.writeStdout(opts.json ? JSON.stringify(report) : renderStatusReport(report));
|
|
7689
|
+
deps.setExitCode(0);
|
|
7690
|
+
} catch {
|
|
7691
|
+
const error = {
|
|
7692
|
+
error: "status_failed",
|
|
7693
|
+
message: "Unable to collect operational status"
|
|
7694
|
+
};
|
|
7695
|
+
if (opts.json) deps.writeStdout(JSON.stringify(error));
|
|
7696
|
+
else deps.writeStderr(error.message);
|
|
7697
|
+
deps.setExitCode(1);
|
|
7698
|
+
}
|
|
7699
|
+
});
|
|
7700
|
+
}
|
|
7701
|
+
var statusCommand = createStatusCommand();
|
|
7702
|
+
//#endregion
|
|
7293
7703
|
//#region src/commands/sync-helpers.ts
|
|
7294
7704
|
function formatSyncAttempt(row) {
|
|
7295
7705
|
const status = row.ok ? "ok" : "error";
|
|
@@ -7523,6 +7933,7 @@ onceCmd.action(async (opts) => {
|
|
|
7523
7933
|
for (const row of rows) {
|
|
7524
7934
|
const result = await runSyncPass(store.db, row.peer_device_id, {
|
|
7525
7935
|
keysDir,
|
|
7936
|
+
dbPath: store.dbPath,
|
|
7526
7937
|
scanner: store.scanner
|
|
7527
7938
|
});
|
|
7528
7939
|
if (!result.ok) hadFailure = true;
|
|
@@ -7738,6 +8149,15 @@ pairCmd.action(async (opts) => {
|
|
|
7738
8149
|
console.log("For machine-friendly output next time, run:");
|
|
7739
8150
|
console.log(" codemem sync pair --payload-only");
|
|
7740
8151
|
console.log("On the accepting device, --include/--exclude control both what it sends and what it accepts from that peer.");
|
|
8152
|
+
} catch (error) {
|
|
8153
|
+
const code = error instanceof DeviceIdentityError ? error.code : "pairing_failed";
|
|
8154
|
+
const message = error instanceof Error ? error.message : "Pairing failed";
|
|
8155
|
+
if (opts.json) {
|
|
8156
|
+
emitJsonError(code, message);
|
|
8157
|
+
return;
|
|
8158
|
+
}
|
|
8159
|
+
p.log.error(message);
|
|
8160
|
+
process.exitCode = 1;
|
|
7741
8161
|
} finally {
|
|
7742
8162
|
store.close();
|
|
7743
8163
|
}
|
|
@@ -7762,12 +8182,19 @@ doctorCmd.action(async (opts) => {
|
|
|
7762
8182
|
public_key: schema.syncPeers.public_key
|
|
7763
8183
|
}).from(schema.syncPeers).all();
|
|
7764
8184
|
const issues = [];
|
|
8185
|
+
let identityError = null;
|
|
7765
8186
|
const syncHost = typeof config.sync_host === "string" ? config.sync_host : "0.0.0.0";
|
|
7766
8187
|
const syncPort = typeof config.sync_port === "number" ? config.sync_port : 7337;
|
|
7767
8188
|
const viewerBinding = readViewerBinding(dbPath);
|
|
7768
8189
|
const reachable = viewerBinding ? await portOpen(viewerBinding.host, viewerBinding.port) : false;
|
|
7769
8190
|
if (!reachable) issues.push("daemon not running");
|
|
7770
8191
|
if (!device) issues.push("identity missing");
|
|
8192
|
+
if (device) try {
|
|
8193
|
+
ensureDeviceIdentity(store.db, { keysDir: process.env.CODEMEM_KEYS_DIR?.trim() || void 0 });
|
|
8194
|
+
} catch (error) {
|
|
8195
|
+
identityError = error instanceof Error ? error.message : "device_identity_unavailable";
|
|
8196
|
+
issues.push(identityError);
|
|
8197
|
+
}
|
|
7771
8198
|
if (daemonState?.last_error && (!daemonState.last_ok_at || daemonState.last_ok_at < (daemonState.last_error_at ?? ""))) issues.push("daemon error");
|
|
7772
8199
|
if (peers.length === 0) issues.push("no peers");
|
|
7773
8200
|
if (!config.sync_enabled) issues.push("sync is disabled");
|
|
@@ -7799,6 +8226,7 @@ doctorCmd.action(async (opts) => {
|
|
|
7799
8226
|
mdns_source: mdnsSource,
|
|
7800
8227
|
daemon: reachable ? "running" : "not running",
|
|
7801
8228
|
identity: device?.device_id ?? null,
|
|
8229
|
+
identity_error: identityError,
|
|
7802
8230
|
daemon_error: daemonState?.last_error ?? null,
|
|
7803
8231
|
peers: peerDetails,
|
|
7804
8232
|
issues: [...new Set(issues)],
|
|
@@ -7812,7 +8240,10 @@ doctorCmd.action(async (opts) => {
|
|
|
7812
8240
|
console.log(`- mDNS: ${mdnsLabel}`);
|
|
7813
8241
|
console.log(`- Daemon: ${reachable ? "running" : "not running"}`);
|
|
7814
8242
|
if (!device) console.log("- Identity: missing (run `codemem sync enable`)");
|
|
7815
|
-
else
|
|
8243
|
+
else {
|
|
8244
|
+
console.log(`- Identity: ${device.device_id}`);
|
|
8245
|
+
if (identityError) console.log(`- Identity error: ${identityError}`);
|
|
8246
|
+
}
|
|
7816
8247
|
if (daemonState?.last_error && (!daemonState.last_ok_at || daemonState.last_ok_at < (daemonState.last_error_at ?? ""))) console.log(`- Daemon error: ${daemonState.last_error} (at ${daemonState.last_error_at ?? "unknown"})`);
|
|
7817
8248
|
if (peers.length === 0) console.log("- Peers: none (pair a device first)");
|
|
7818
8249
|
else {
|
|
@@ -7821,6 +8252,15 @@ doctorCmd.action(async (opts) => {
|
|
|
7821
8252
|
}
|
|
7822
8253
|
if (issues.length > 0) console.log(`WARN: ${[...new Set(issues)].slice(0, 3).join(", ")}`);
|
|
7823
8254
|
else console.log("OK: sync looks healthy");
|
|
8255
|
+
} catch (error) {
|
|
8256
|
+
const code = error instanceof DeviceIdentityError ? error.code : "doctor_failed";
|
|
8257
|
+
const message = error instanceof Error ? error.message : "Sync diagnostics failed";
|
|
8258
|
+
if (opts.json) {
|
|
8259
|
+
emitJsonError(code, message);
|
|
8260
|
+
return;
|
|
8261
|
+
}
|
|
8262
|
+
p.log.error(message);
|
|
8263
|
+
process.exitCode = 1;
|
|
7824
8264
|
} finally {
|
|
7825
8265
|
store.close();
|
|
7826
8266
|
}
|
|
@@ -8118,6 +8558,7 @@ bootstrapCmd.action(async (peerArg, opts) => {
|
|
|
8118
8558
|
const statusUrl = `${candidate}/v1/status`;
|
|
8119
8559
|
const headers = buildAuthHeaders({
|
|
8120
8560
|
deviceId,
|
|
8561
|
+
dbPath: store.dbPath,
|
|
8121
8562
|
method: "GET",
|
|
8122
8563
|
url: statusUrl,
|
|
8123
8564
|
bodyBytes: Buffer.alloc(0),
|
|
@@ -8191,6 +8632,7 @@ bootstrapCmd.action(async (peerArg, opts) => {
|
|
|
8191
8632
|
};
|
|
8192
8633
|
const { items } = await fetchAllSnapshotPages(baseUrl, resetInfo, deviceId, {
|
|
8193
8634
|
keysDir,
|
|
8635
|
+
dbPath: store.dbPath,
|
|
8194
8636
|
bootstrapGrantId: opts.bootstrapGrant,
|
|
8195
8637
|
pageSize
|
|
8196
8638
|
});
|
|
@@ -8207,6 +8649,15 @@ bootstrapCmd.action(async (peerArg, opts) => {
|
|
|
8207
8649
|
p.outro(result.ok ? "Bootstrap complete" : "Bootstrap failed");
|
|
8208
8650
|
}
|
|
8209
8651
|
if (!result.ok) process.exitCode = 1;
|
|
8652
|
+
} catch (error) {
|
|
8653
|
+
const code = error instanceof DeviceIdentityError ? error.code : "bootstrap_failed";
|
|
8654
|
+
const message = error instanceof Error ? error.message : "Bootstrap failed";
|
|
8655
|
+
if (opts.json) {
|
|
8656
|
+
emitJsonError(code, message);
|
|
8657
|
+
return;
|
|
8658
|
+
}
|
|
8659
|
+
p.log.error(message);
|
|
8660
|
+
process.exitCode = 1;
|
|
8210
8661
|
} finally {
|
|
8211
8662
|
store.close();
|
|
8212
8663
|
}
|
|
@@ -8230,13 +8681,411 @@ syncCoordinatorAlias.hook("preAction", (_thisCmd, actionCmd) => {
|
|
|
8230
8681
|
const subName = actionCmd.name();
|
|
8231
8682
|
emitDeprecationWarning(`codemem sync coordinator ${subName}`, `codemem coordinator ${subName}`);
|
|
8232
8683
|
});
|
|
8233
|
-
syncCommand.addCommand(syncCoordinatorAlias);
|
|
8684
|
+
syncCommand.addCommand(syncCoordinatorAlias, { hidden: true });
|
|
8685
|
+
//#endregion
|
|
8686
|
+
//#region src/commands/update.ts
|
|
8687
|
+
var INSTALL_TIMEOUT_MS = 300 * 1e3;
|
|
8688
|
+
var VERIFY_TIMEOUT_MS = 3e4;
|
|
8689
|
+
var PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
8690
|
+
var UPDATE_LOCK_FILE = "update-install.lock";
|
|
8691
|
+
var UpdateInstallLockedError = class extends Error {};
|
|
8692
|
+
function updateInstallLockPath() {
|
|
8693
|
+
return join(process.env.HOME?.trim() || homedir(), ".codemem", UPDATE_LOCK_FILE);
|
|
8694
|
+
}
|
|
8695
|
+
function isProcessAlive(pid) {
|
|
8696
|
+
try {
|
|
8697
|
+
process.kill(pid, 0);
|
|
8698
|
+
return true;
|
|
8699
|
+
} catch (error) {
|
|
8700
|
+
return error.code === "EPERM";
|
|
8701
|
+
}
|
|
8702
|
+
}
|
|
8703
|
+
async function removeStaleInstallLock(lockPath) {
|
|
8704
|
+
try {
|
|
8705
|
+
const [rawPid, lockStat] = await Promise.all([readFile(lockPath, "utf8"), stat(lockPath)]);
|
|
8706
|
+
const pid = Number.parseInt(rawPid.trim(), 10);
|
|
8707
|
+
if (Number.isSafeInteger(pid) && pid > 0) {
|
|
8708
|
+
if (isProcessAlive(pid)) return false;
|
|
8709
|
+
} else if (Date.now() - lockStat.mtimeMs <= 33e4) return false;
|
|
8710
|
+
await unlink(lockPath);
|
|
8711
|
+
return true;
|
|
8712
|
+
} catch {
|
|
8713
|
+
return false;
|
|
8714
|
+
}
|
|
8715
|
+
}
|
|
8716
|
+
async function acquireInstallLock() {
|
|
8717
|
+
const lockPath = updateInstallLockPath();
|
|
8718
|
+
await mkdir(dirname(lockPath), {
|
|
8719
|
+
recursive: true,
|
|
8720
|
+
mode: 448
|
|
8721
|
+
});
|
|
8722
|
+
for (let attempt = 0; attempt < 2; attempt += 1) try {
|
|
8723
|
+
const handle = await open(lockPath, "wx", 384);
|
|
8724
|
+
await handle.writeFile(`${process.pid}\n`, "utf8");
|
|
8725
|
+
return async () => {
|
|
8726
|
+
await handle.close().catch(() => void 0);
|
|
8727
|
+
await unlink(lockPath).catch(() => void 0);
|
|
8728
|
+
};
|
|
8729
|
+
} catch (error) {
|
|
8730
|
+
if (error.code !== "EEXIST") throw error;
|
|
8731
|
+
if (attempt > 0 || !await removeStaleInstallLock(lockPath)) throw new UpdateInstallLockedError("another codemem update installation is already running");
|
|
8732
|
+
}
|
|
8733
|
+
throw new UpdateInstallLockedError("another codemem update installation is already running");
|
|
8734
|
+
}
|
|
8735
|
+
function runCommand(command, args, timeoutMs, options = {}) {
|
|
8736
|
+
return new Promise((resolve, reject) => {
|
|
8737
|
+
const child = spawn(command, args, {
|
|
8738
|
+
cwd: options.cwd,
|
|
8739
|
+
detached: process.platform !== "win32",
|
|
8740
|
+
shell: false,
|
|
8741
|
+
stdio: [
|
|
8742
|
+
"ignore",
|
|
8743
|
+
"pipe",
|
|
8744
|
+
"pipe"
|
|
8745
|
+
],
|
|
8746
|
+
windowsVerbatimArguments: options.windowsVerbatimArguments
|
|
8747
|
+
});
|
|
8748
|
+
let settled = false;
|
|
8749
|
+
let timedOut = false;
|
|
8750
|
+
let stdout = "";
|
|
8751
|
+
let stderr = "";
|
|
8752
|
+
let timer;
|
|
8753
|
+
const finish = (result) => {
|
|
8754
|
+
if (settled) return;
|
|
8755
|
+
settled = true;
|
|
8756
|
+
clearTimeout(timer);
|
|
8757
|
+
resolve(result);
|
|
8758
|
+
};
|
|
8759
|
+
const terminate = (signal) => {
|
|
8760
|
+
if (process.platform === "win32" && child.pid) {
|
|
8761
|
+
const systemRoot = process.env.SystemRoot?.trim();
|
|
8762
|
+
if (systemRoot && isAbsolute(systemRoot)) {
|
|
8763
|
+
spawnSync(join(systemRoot, "System32", "taskkill.exe"), [
|
|
8764
|
+
"/PID",
|
|
8765
|
+
String(child.pid),
|
|
8766
|
+
"/T",
|
|
8767
|
+
"/F"
|
|
8768
|
+
], {
|
|
8769
|
+
stdio: "ignore",
|
|
8770
|
+
windowsHide: true
|
|
8771
|
+
});
|
|
8772
|
+
return;
|
|
8773
|
+
}
|
|
8774
|
+
}
|
|
8775
|
+
if (process.platform !== "win32" && child.pid) try {
|
|
8776
|
+
process.kill(-child.pid, signal);
|
|
8777
|
+
return;
|
|
8778
|
+
} catch {}
|
|
8779
|
+
child.kill(signal);
|
|
8780
|
+
};
|
|
8781
|
+
timer = setTimeout(() => {
|
|
8782
|
+
timedOut = true;
|
|
8783
|
+
terminate("SIGTERM");
|
|
8784
|
+
setTimeout(() => terminate("SIGKILL"), 5e3).unref();
|
|
8785
|
+
}, timeoutMs);
|
|
8786
|
+
child.stdout.setEncoding("utf8");
|
|
8787
|
+
child.stderr.setEncoding("utf8");
|
|
8788
|
+
child.stdout.on("data", (chunk) => {
|
|
8789
|
+
stdout += chunk;
|
|
8790
|
+
});
|
|
8791
|
+
child.stderr.on("data", (chunk) => {
|
|
8792
|
+
stderr += chunk;
|
|
8793
|
+
});
|
|
8794
|
+
child.once("error", (error) => {
|
|
8795
|
+
clearTimeout(timer);
|
|
8796
|
+
if (!settled) reject(error);
|
|
8797
|
+
});
|
|
8798
|
+
child.once("close", (code) => finish({
|
|
8799
|
+
exitCode: code ?? 1,
|
|
8800
|
+
stdout,
|
|
8801
|
+
stderr: timedOut ? "command timed out" : stderr
|
|
8802
|
+
}));
|
|
8803
|
+
});
|
|
8804
|
+
}
|
|
8805
|
+
async function resolveWindowsShim(name) {
|
|
8806
|
+
const systemRoot = process.env.SystemRoot?.trim();
|
|
8807
|
+
if (!systemRoot || !isAbsolute(systemRoot)) throw new Error("Windows SystemRoot is unavailable");
|
|
8808
|
+
const result = await runCommand(join(systemRoot, "System32", "where.exe"), [name], 1e4, { cwd: join(systemRoot, "System32") });
|
|
8809
|
+
const shim = result.stdout.split(/\r?\n/).map((value) => value.trim()).find((value) => isAbsolute(value));
|
|
8810
|
+
if (result.exitCode !== 0 || !shim) throw new Error(`unable to resolve ${name} from PATH`);
|
|
8811
|
+
return shim;
|
|
8812
|
+
}
|
|
8813
|
+
function windowsCommandLine(shim, args) {
|
|
8814
|
+
return `""${shim}" ${args.join(" ")}"`;
|
|
8815
|
+
}
|
|
8816
|
+
async function resolveInstallCommand() {
|
|
8817
|
+
if (process.platform !== "win32") return {
|
|
8818
|
+
command: "npm",
|
|
8819
|
+
args: []
|
|
8820
|
+
};
|
|
8821
|
+
const systemRoot = process.env.SystemRoot?.trim();
|
|
8822
|
+
if (!systemRoot || !isAbsolute(systemRoot)) throw new Error("Windows SystemRoot is unavailable");
|
|
8823
|
+
return {
|
|
8824
|
+
command: join(systemRoot, "System32", "cmd.exe"),
|
|
8825
|
+
args: [
|
|
8826
|
+
"/d",
|
|
8827
|
+
"/s",
|
|
8828
|
+
"/c",
|
|
8829
|
+
await resolveWindowsShim("npm.cmd")
|
|
8830
|
+
],
|
|
8831
|
+
cwd: join(systemRoot, "System32")
|
|
8832
|
+
};
|
|
8833
|
+
}
|
|
8834
|
+
async function resolveVerificationCommand() {
|
|
8835
|
+
if (process.platform !== "win32") return {
|
|
8836
|
+
command: "codemem",
|
|
8837
|
+
args: []
|
|
8838
|
+
};
|
|
8839
|
+
const systemRoot = process.env.SystemRoot?.trim();
|
|
8840
|
+
if (!systemRoot || !isAbsolute(systemRoot)) throw new Error("Windows SystemRoot is unavailable");
|
|
8841
|
+
return {
|
|
8842
|
+
command: join(systemRoot, "System32", "cmd.exe"),
|
|
8843
|
+
args: [
|
|
8844
|
+
"/d",
|
|
8845
|
+
"/s",
|
|
8846
|
+
"/c",
|
|
8847
|
+
await resolveWindowsShim("codemem.cmd")
|
|
8848
|
+
],
|
|
8849
|
+
cwd: join(systemRoot, "System32")
|
|
8850
|
+
};
|
|
8851
|
+
}
|
|
8852
|
+
function failInstall(options, code, message) {
|
|
8853
|
+
if (options.json) emitJsonError(code, message);
|
|
8854
|
+
else {
|
|
8855
|
+
console.error(message);
|
|
8856
|
+
process.exitCode = 1;
|
|
8857
|
+
}
|
|
8858
|
+
}
|
|
8859
|
+
function cacheQualifier(stale) {
|
|
8860
|
+
return stale ? " (cached result)" : "";
|
|
8861
|
+
}
|
|
8862
|
+
function renderStatusWarning(error) {
|
|
8863
|
+
return error ? ` Warning: ${error}` : "";
|
|
8864
|
+
}
|
|
8865
|
+
function renderHumanStatus(status) {
|
|
8866
|
+
const warning = renderStatusWarning(status.error);
|
|
8867
|
+
if (status.update_available) return `Update available${cacheQualifier(status.stale)}: ${status.current_version} → ${status.latest_version}. ${status.recommended_action}${warning}`;
|
|
8868
|
+
if (!isStableReleaseVersion(status.current_version)) return `Unable to compare current version ${status.current_version} with ${status.latest_version}. ${status.recommended_action}${warning}`;
|
|
8869
|
+
return `${status.current_version} is up to date${cacheQualifier(status.stale)}.${warning}`;
|
|
8870
|
+
}
|
|
8871
|
+
var checkCommand = addJsonOption(new Command("check").description("Check for a newer stable codemem release")).addOption(new Option("-r, --refresh", "bypass the six-hour release cache")).configureHelp(helpStyle).action(async (options) => {
|
|
8872
|
+
try {
|
|
8873
|
+
const status = await getUpdateStatus({
|
|
8874
|
+
currentVersion: VERSION,
|
|
8875
|
+
installKind: detectInstallKind({
|
|
8876
|
+
entryPath: process.argv[1] ?? "",
|
|
8877
|
+
env: process.env
|
|
8878
|
+
}),
|
|
8879
|
+
refresh: options.refresh
|
|
8880
|
+
});
|
|
8881
|
+
if (status.latest_version === null) {
|
|
8882
|
+
const message = status.error ?? "release status is unavailable";
|
|
8883
|
+
if (options.json) emitJsonError("update_check_unavailable", message);
|
|
8884
|
+
else {
|
|
8885
|
+
console.error(`Unable to check for updates: ${message}`);
|
|
8886
|
+
process.exitCode = 1;
|
|
8887
|
+
}
|
|
8888
|
+
return;
|
|
8889
|
+
}
|
|
8890
|
+
if (options.json) {
|
|
8891
|
+
console.log(JSON.stringify(status));
|
|
8892
|
+
return;
|
|
8893
|
+
}
|
|
8894
|
+
console.log(renderHumanStatus(status));
|
|
8895
|
+
} catch (error) {
|
|
8896
|
+
const message = error instanceof Error ? error.message : "release status is unavailable";
|
|
8897
|
+
if (options.json) emitJsonError("update_check_unavailable", message);
|
|
8898
|
+
else {
|
|
8899
|
+
console.error(`Unable to check for updates: ${message}`);
|
|
8900
|
+
process.exitCode = 1;
|
|
8901
|
+
}
|
|
8902
|
+
}
|
|
8903
|
+
});
|
|
8904
|
+
var installCommand = addJsonOption(new Command("install").description("Install an eligible stable codemem update")).configureHelp(helpStyle).action(async (options) => {
|
|
8905
|
+
let releaseInstallLock = null;
|
|
8906
|
+
try {
|
|
8907
|
+
const status = await getUpdateStatus({
|
|
8908
|
+
currentVersion: VERSION,
|
|
8909
|
+
installKind: detectInstallKind({
|
|
8910
|
+
entryPath: process.argv[1] ?? "",
|
|
8911
|
+
env: process.env
|
|
8912
|
+
}),
|
|
8913
|
+
refresh: true
|
|
8914
|
+
});
|
|
8915
|
+
if (!status.auto_update_eligible || !status.latest_version) {
|
|
8916
|
+
failInstall(options, "update_install_refused", status.recommended_action);
|
|
8917
|
+
return;
|
|
8918
|
+
}
|
|
8919
|
+
const targetVersion = status.latest_version;
|
|
8920
|
+
if (!isStableReleaseVersion(targetVersion)) {
|
|
8921
|
+
failInstall(options, "update_install_refused", "release version is not a stable semantic version");
|
|
8922
|
+
return;
|
|
8923
|
+
}
|
|
8924
|
+
releaseInstallLock = await acquireInstallLock();
|
|
8925
|
+
const npm = await resolveInstallCommand();
|
|
8926
|
+
const installation = await runCommand(npm.command, process.platform === "win32" ? [...npm.args.slice(0, 3), windowsCommandLine(npm.args[3] ?? "", [
|
|
8927
|
+
"install",
|
|
8928
|
+
"-g",
|
|
8929
|
+
"--registry",
|
|
8930
|
+
PUBLIC_NPM_REGISTRY,
|
|
8931
|
+
`codemem@${targetVersion}`
|
|
8932
|
+
])] : [
|
|
8933
|
+
"install",
|
|
8934
|
+
"-g",
|
|
8935
|
+
"--registry",
|
|
8936
|
+
PUBLIC_NPM_REGISTRY,
|
|
8937
|
+
`codemem@${targetVersion}`
|
|
8938
|
+
], INSTALL_TIMEOUT_MS, {
|
|
8939
|
+
cwd: npm.cwd,
|
|
8940
|
+
windowsVerbatimArguments: process.platform === "win32"
|
|
8941
|
+
});
|
|
8942
|
+
if (installation.exitCode !== 0) {
|
|
8943
|
+
failInstall(options, "update_install_failed", installation.stderr.trim() || "npm installation failed");
|
|
8944
|
+
return;
|
|
8945
|
+
}
|
|
8946
|
+
const codemem = await resolveVerificationCommand();
|
|
8947
|
+
const verification = await runCommand(codemem.command, process.platform === "win32" ? [...codemem.args.slice(0, 3), windowsCommandLine(codemem.args[3] ?? "", ["version"])] : ["version"], VERIFY_TIMEOUT_MS, {
|
|
8948
|
+
cwd: codemem.cwd,
|
|
8949
|
+
windowsVerbatimArguments: process.platform === "win32"
|
|
8950
|
+
});
|
|
8951
|
+
if (verification.exitCode !== 0 || verification.stdout.trim() !== targetVersion) {
|
|
8952
|
+
failInstall(options, "update_verification_failed", "installed version verification failed");
|
|
8953
|
+
return;
|
|
8954
|
+
}
|
|
8955
|
+
const result = {
|
|
8956
|
+
previous_version: VERSION,
|
|
8957
|
+
installed_version: targetVersion
|
|
8958
|
+
};
|
|
8959
|
+
if (options.json) console.log(JSON.stringify(result));
|
|
8960
|
+
else console.log(`Updated codemem from ${VERSION} to ${targetVersion}.`);
|
|
8961
|
+
} catch (error) {
|
|
8962
|
+
failInstall(options, error instanceof UpdateInstallLockedError ? "update_install_locked" : "update_install_failed", error instanceof Error ? error.message : "update installation failed");
|
|
8963
|
+
} finally {
|
|
8964
|
+
await releaseInstallLock?.();
|
|
8965
|
+
}
|
|
8966
|
+
});
|
|
8967
|
+
var updateCommand = new Command("update").description("Inspect and manage codemem updates").configureHelp(helpStyle).addCommand(checkCommand).addCommand(installCommand);
|
|
8234
8968
|
//#endregion
|
|
8235
8969
|
//#region src/commands/version.ts
|
|
8236
8970
|
var versionCommand = new Command("version").configureHelp(helpStyle).description("Print codemem version").action(() => {
|
|
8237
8971
|
console.log(VERSION);
|
|
8238
8972
|
});
|
|
8239
8973
|
//#endregion
|
|
8974
|
+
//#region src/command-tree.ts
|
|
8975
|
+
/**
|
|
8976
|
+
* Assembled root command tree — single source of truth shared by the runtime
|
|
8977
|
+
* entrypoint (`index.ts`) and the command-tree parity tests.
|
|
8978
|
+
*
|
|
8979
|
+
* `index.ts` stays the only module with import-time side effects (omelette
|
|
8980
|
+
* completion init, `program.parse()`); everything here is side-effect free
|
|
8981
|
+
* until `registerRootCommands` is called.
|
|
8982
|
+
*/
|
|
8983
|
+
var ROOT_COMPLETION_COMMANDS = [
|
|
8984
|
+
"claude-hook-file-context",
|
|
8985
|
+
"claude-hook-inject",
|
|
8986
|
+
"claude-hook-ingest",
|
|
8987
|
+
"codex-hook-inject",
|
|
8988
|
+
"codex-hook-ingest",
|
|
8989
|
+
"config",
|
|
8990
|
+
"coordinator",
|
|
8991
|
+
"db",
|
|
8992
|
+
"distill",
|
|
8993
|
+
"embed",
|
|
8994
|
+
"enqueue-raw-event",
|
|
8995
|
+
"export-memories",
|
|
8996
|
+
"import-memories",
|
|
8997
|
+
"maintenance",
|
|
8998
|
+
"mcp",
|
|
8999
|
+
"memory",
|
|
9000
|
+
"pack",
|
|
9001
|
+
"recent",
|
|
9002
|
+
"search",
|
|
9003
|
+
"serve",
|
|
9004
|
+
"setup",
|
|
9005
|
+
"stats",
|
|
9006
|
+
"status",
|
|
9007
|
+
"sync",
|
|
9008
|
+
"update",
|
|
9009
|
+
"version",
|
|
9010
|
+
"help",
|
|
9011
|
+
"--help",
|
|
9012
|
+
"--version"
|
|
9013
|
+
];
|
|
9014
|
+
/**
|
|
9015
|
+
* True when the CLI process was invoked through the named top-level
|
|
9016
|
+
* compatibility alias (first non-flag token of the real argv). Forwarding
|
|
9017
|
+
* wrappers such as `memory export` re-parse the alias command with synthetic
|
|
9018
|
+
* argv, so this stays false for canonical invocations.
|
|
9019
|
+
*/
|
|
9020
|
+
function invokedAsTopLevelAlias(name, argv = process.argv.slice(2)) {
|
|
9021
|
+
for (const token of argv) {
|
|
9022
|
+
if (token === "--") return false;
|
|
9023
|
+
if (!token.startsWith("-")) return token === name;
|
|
9024
|
+
}
|
|
9025
|
+
return false;
|
|
9026
|
+
}
|
|
9027
|
+
function registerMemoryGroupWrappers() {
|
|
9028
|
+
if (memoryCommand.commands.some((command) => command.name() === "export")) return;
|
|
9029
|
+
const forwardedTail = (subcommand) => {
|
|
9030
|
+
const argv = process.argv.slice(2);
|
|
9031
|
+
const groupIdx = argv.indexOf("memory");
|
|
9032
|
+
const searchFrom = groupIdx >= 0 ? groupIdx + 1 : 0;
|
|
9033
|
+
const subIdx = argv.indexOf(subcommand, searchFrom);
|
|
9034
|
+
return subIdx >= 0 ? argv.slice(subIdx + 1) : [];
|
|
9035
|
+
};
|
|
9036
|
+
const memExport = new Command("export").description("Export memories to a JSON file for sharing or backup").argument("<output>", "output file path (use '-' for stdout)").option("--db <path>", "database path").option("--db-path <path>", "database path").option("--project <project>", "filter by project").option("--all-projects", "export all projects").option("--include-inactive", "include deactivated memories").option("--since <iso>", "only export memories created after this ISO timestamp").configureHelp(helpStyle).allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
9037
|
+
await exportMemoriesCommand.parseAsync([
|
|
9038
|
+
"node",
|
|
9039
|
+
"export-memories",
|
|
9040
|
+
...forwardedTail("export")
|
|
9041
|
+
]);
|
|
9042
|
+
});
|
|
9043
|
+
const memImport = new Command("import").description("Import memories from an exported JSON file").argument("<inputFile>", "input JSON file (use '-' for stdin)").option("--db <path>", "database path").option("--db-path <path>", "database path").option("--remap-project <path>", "remap all projects to this path on import").option("--dry-run", "preview import without writing").configureHelp(helpStyle).allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
9044
|
+
await importMemoriesCommand.parseAsync([
|
|
9045
|
+
"node",
|
|
9046
|
+
"import-memories",
|
|
9047
|
+
...forwardedTail("import")
|
|
9048
|
+
]);
|
|
9049
|
+
});
|
|
9050
|
+
memoryCommand.addCommand(memExport);
|
|
9051
|
+
memoryCommand.addCommand(memImport);
|
|
9052
|
+
}
|
|
9053
|
+
/** Register every root command (visible and hidden) on the given program. */
|
|
9054
|
+
function registerRootCommands(program) {
|
|
9055
|
+
registerMemoryGroupWrappers();
|
|
9056
|
+
program.addCommand(serveCommand);
|
|
9057
|
+
program.addCommand(configCommand);
|
|
9058
|
+
program.addCommand(coordinatorCommand);
|
|
9059
|
+
program.addCommand(mcpCommand);
|
|
9060
|
+
program.addCommand(claudeHookInjectCommand);
|
|
9061
|
+
program.addCommand(claudeHookIngestCommand);
|
|
9062
|
+
program.addCommand(claudeHookFileContextCommand);
|
|
9063
|
+
program.addCommand(codexHookInjectCommand);
|
|
9064
|
+
program.addCommand(codexHookIngestCommand);
|
|
9065
|
+
program.addCommand(dbCommand);
|
|
9066
|
+
program.addCommand(distillCommand);
|
|
9067
|
+
program.addCommand(exportMemoriesCommand);
|
|
9068
|
+
program.addCommand(importMemoriesCommand);
|
|
9069
|
+
program.addCommand(statsCommand);
|
|
9070
|
+
program.addCommand(statusCommand);
|
|
9071
|
+
program.addCommand(maintenanceCommand);
|
|
9072
|
+
program.addCommand(embedCommand);
|
|
9073
|
+
program.addCommand(recentCommand);
|
|
9074
|
+
program.addCommand(searchCommand);
|
|
9075
|
+
program.addCommand(packCommand);
|
|
9076
|
+
program.addCommand(promptPackLedgerCommand, { hidden: true });
|
|
9077
|
+
program.addCommand(showMemoryCommand, { hidden: true });
|
|
9078
|
+
program.addCommand(forgetMemoryCommand, { hidden: true });
|
|
9079
|
+
program.addCommand(rememberMemoryCommand, { hidden: true });
|
|
9080
|
+
program.addCommand(memoryCommand);
|
|
9081
|
+
program.addCommand(syncCommand);
|
|
9082
|
+
program.addCommand(setupCommand);
|
|
9083
|
+
program.addCommand(enqueueRawEventCommand);
|
|
9084
|
+
program.addCommand(updateCommand);
|
|
9085
|
+
program.addCommand(versionCommand);
|
|
9086
|
+
return program;
|
|
9087
|
+
}
|
|
9088
|
+
//#endregion
|
|
8240
9089
|
//#region src/index.ts
|
|
8241
9090
|
/**
|
|
8242
9091
|
* @codemem/cli — CLI entry point.
|
|
@@ -8250,34 +9099,7 @@ var versionCommand = new Command("version").configureHelp(helpStyle).description
|
|
|
8250
9099
|
*/
|
|
8251
9100
|
var completion = omelette("codemem <command>");
|
|
8252
9101
|
completion.on("command", ({ reply }) => {
|
|
8253
|
-
reply([
|
|
8254
|
-
"claude-hook-file-context",
|
|
8255
|
-
"claude-hook-inject",
|
|
8256
|
-
"claude-hook-ingest",
|
|
8257
|
-
"codex-hook-inject",
|
|
8258
|
-
"codex-hook-ingest",
|
|
8259
|
-
"config",
|
|
8260
|
-
"coordinator",
|
|
8261
|
-
"db",
|
|
8262
|
-
"distill",
|
|
8263
|
-
"embed",
|
|
8264
|
-
"enqueue-raw-event",
|
|
8265
|
-
"export-memories",
|
|
8266
|
-
"import-memories",
|
|
8267
|
-
"mcp",
|
|
8268
|
-
"memory",
|
|
8269
|
-
"pack",
|
|
8270
|
-
"recent",
|
|
8271
|
-
"search",
|
|
8272
|
-
"serve",
|
|
8273
|
-
"setup",
|
|
8274
|
-
"stats",
|
|
8275
|
-
"sync",
|
|
8276
|
-
"version",
|
|
8277
|
-
"help",
|
|
8278
|
-
"--help",
|
|
8279
|
-
"--version"
|
|
8280
|
-
]);
|
|
9102
|
+
reply([...ROOT_COMPLETION_COMMANDS]);
|
|
8281
9103
|
});
|
|
8282
9104
|
completion.init();
|
|
8283
9105
|
function hasRootFlag(flag) {
|
|
@@ -8306,59 +9128,7 @@ if (hasRootFlag("--cleanup-completion")) {
|
|
|
8306
9128
|
completion.cleanupShellInitFile();
|
|
8307
9129
|
process.exit(0);
|
|
8308
9130
|
}
|
|
8309
|
-
|
|
8310
|
-
const forwardedTail = (subcommand) => {
|
|
8311
|
-
const argv = process.argv.slice(2);
|
|
8312
|
-
const groupIdx = argv.indexOf("memory");
|
|
8313
|
-
const searchFrom = groupIdx >= 0 ? groupIdx + 1 : 0;
|
|
8314
|
-
const subIdx = argv.indexOf(subcommand, searchFrom);
|
|
8315
|
-
return subIdx >= 0 ? argv.slice(subIdx + 1) : [];
|
|
8316
|
-
};
|
|
8317
|
-
const memExport = new Command("export").description("Export memories to a JSON file for sharing or backup").argument("<output>", "output file path (use '-' for stdout)").option("--db <path>", "database path").option("--db-path <path>", "database path").option("--project <project>", "filter by project").option("--all-projects", "export all projects").option("--include-inactive", "include deactivated memories").option("--since <iso>", "only export memories created after this ISO timestamp").configureHelp(helpStyle).allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
8318
|
-
await exportMemoriesCommand.parseAsync([
|
|
8319
|
-
"node",
|
|
8320
|
-
"export-memories",
|
|
8321
|
-
...forwardedTail("export")
|
|
8322
|
-
]);
|
|
8323
|
-
});
|
|
8324
|
-
const memImport = new Command("import").description("Import memories from an exported JSON file").argument("<inputFile>", "input JSON file (use '-' for stdin)").option("--db <path>", "database path").option("--db-path <path>", "database path").option("--remap-project <path>", "remap all projects to this path on import").option("--dry-run", "preview import without writing").configureHelp(helpStyle).allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
8325
|
-
await importMemoriesCommand.parseAsync([
|
|
8326
|
-
"node",
|
|
8327
|
-
"import-memories",
|
|
8328
|
-
...forwardedTail("import")
|
|
8329
|
-
]);
|
|
8330
|
-
});
|
|
8331
|
-
memoryCommand.addCommand(memExport);
|
|
8332
|
-
memoryCommand.addCommand(memImport);
|
|
8333
|
-
}
|
|
8334
|
-
program.addCommand(serveCommand);
|
|
8335
|
-
program.addCommand(configCommand);
|
|
8336
|
-
program.addCommand(coordinatorCommand);
|
|
8337
|
-
program.addCommand(mcpCommand);
|
|
8338
|
-
program.addCommand(claudeHookInjectCommand);
|
|
8339
|
-
program.addCommand(claudeHookIngestCommand);
|
|
8340
|
-
program.addCommand(claudeHookFileContextCommand);
|
|
8341
|
-
program.addCommand(codexHookInjectCommand);
|
|
8342
|
-
program.addCommand(codexHookIngestCommand);
|
|
8343
|
-
program.addCommand(dbCommand);
|
|
8344
|
-
program.addCommand(distillCommand);
|
|
8345
|
-
program.addCommand(exportMemoriesCommand);
|
|
8346
|
-
program.addCommand(importMemoriesCommand);
|
|
8347
|
-
program.addCommand(statsCommand);
|
|
8348
|
-
program.addCommand(maintenanceCommand);
|
|
8349
|
-
program.addCommand(embedCommand);
|
|
8350
|
-
program.addCommand(recentCommand);
|
|
8351
|
-
program.addCommand(searchCommand);
|
|
8352
|
-
program.addCommand(packCommand);
|
|
8353
|
-
program.addCommand(promptPackLedgerCommand, { hidden: true });
|
|
8354
|
-
program.addCommand(showMemoryCommand, { hidden: true });
|
|
8355
|
-
program.addCommand(forgetMemoryCommand, { hidden: true });
|
|
8356
|
-
program.addCommand(rememberMemoryCommand, { hidden: true });
|
|
8357
|
-
program.addCommand(memoryCommand);
|
|
8358
|
-
program.addCommand(syncCommand);
|
|
8359
|
-
program.addCommand(setupCommand);
|
|
8360
|
-
program.addCommand(enqueueRawEventCommand);
|
|
8361
|
-
program.addCommand(versionCommand);
|
|
9131
|
+
registerRootCommands(program);
|
|
8362
9132
|
program.parse();
|
|
8363
9133
|
//#endregion
|
|
8364
9134
|
export {};
|