codemem 0.43.2 → 0.44.0-beta.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/README.md +20 -0
- package/dist/index.js +366 -128
- package/dist/index.js.map +1 -1
- package/package.json +17 -13
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, DeviceIdentityError, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, 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, 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, isEmbeddingDisabled,
|
|
2
|
+
import { DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, 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, 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, isEmbeddingDisabled, 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, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, 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";
|
|
@@ -91,7 +91,7 @@ function emitJsonError(errorCode, message, exitCode = 1) {
|
|
|
91
91
|
* and empty) are treated as toggles, not paths, so the default is used.
|
|
92
92
|
* - All I/O is best-effort: failures are swallowed.
|
|
93
93
|
*/
|
|
94
|
-
var BOOLEAN_TOGGLE_VALUES = new Set([
|
|
94
|
+
var BOOLEAN_TOGGLE_VALUES = /* @__PURE__ */ new Set([
|
|
95
95
|
"",
|
|
96
96
|
"0",
|
|
97
97
|
"false",
|
|
@@ -131,7 +131,7 @@ function logHookEvent(message) {
|
|
|
131
131
|
var FILE_GATE_MIN_BYTES = 1500;
|
|
132
132
|
var FETCH_LIMIT = 40;
|
|
133
133
|
var DISPLAY_LIMIT = 15;
|
|
134
|
-
var MTIME_FRESH_TOLERANCE_MS =
|
|
134
|
+
var MTIME_FRESH_TOLERANCE_MS = 3e5;
|
|
135
135
|
var SMALL_FILE_BYPASS_PATTERNS = [
|
|
136
136
|
/\.(json|jsonc|toml|ya?ml)$/i,
|
|
137
137
|
/\.env(\.|$)/i,
|
|
@@ -286,7 +286,8 @@ function queryByFile(dbPath, relativePath, project, limit) {
|
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
function resolveProject$1(payload) {
|
|
289
|
-
|
|
289
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
|
|
290
|
+
return resolveHookProject(cwd, payload.project);
|
|
290
291
|
}
|
|
291
292
|
function sourceSessionId(payload) {
|
|
292
293
|
const value = payload.session_id;
|
|
@@ -758,14 +759,16 @@ function createHookIngestSpool(cfg) {
|
|
|
758
759
|
return false;
|
|
759
760
|
}
|
|
760
761
|
const payloadText = JSON.stringify(payload);
|
|
761
|
-
const
|
|
762
|
+
const tmpName = `.hook-tmp-${process.pid}-${Date.now()}-${randomInt(1e3, 1e4)}.json`;
|
|
763
|
+
const tmpPath = join(dir, tmpName);
|
|
762
764
|
try {
|
|
763
765
|
writeFileSync(tmpPath, payloadText, { encoding: "utf8" });
|
|
764
766
|
} catch {
|
|
765
767
|
logHookEvent(`${cfg.logPrefix} failed to allocate spool temp file`);
|
|
766
768
|
return false;
|
|
767
769
|
}
|
|
768
|
-
const
|
|
770
|
+
const finalName = `hook-${Math.floor(Date.now() / 1e3)}-${process.pid}-${randomInt(1e3, 1e4)}.json`;
|
|
771
|
+
const finalPath = join(dir, finalName);
|
|
769
772
|
try {
|
|
770
773
|
renameSync(tmpPath, finalPath);
|
|
771
774
|
} catch {
|
|
@@ -809,7 +812,8 @@ function createHookIngestSpool(cfg) {
|
|
|
809
812
|
continue;
|
|
810
813
|
}
|
|
811
814
|
if (nowS - mtimeS <= ttlSeconds) continue;
|
|
812
|
-
const
|
|
815
|
+
const recoveredName = `hook-recovered-${Math.floor(nowS)}-${process.pid}-${randomInt(1e3, 1e4)}.json`;
|
|
816
|
+
const recoveredPath = join(dir, recoveredName);
|
|
813
817
|
try {
|
|
814
818
|
renameSync(tmpPath, recoveredPath);
|
|
815
819
|
logHookEvent(`${cfg.logPrefix} recovered stale temp spool payload: ${recoveredPath}`);
|
|
@@ -1049,7 +1053,8 @@ function nowIso() {
|
|
|
1049
1053
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1050
1054
|
}
|
|
1051
1055
|
function saveSessionState(sessionId, state) {
|
|
1052
|
-
|
|
1056
|
+
const dir = contextDir();
|
|
1057
|
+
mkdirSync(dir, { recursive: true });
|
|
1053
1058
|
const path = statePathForSession(sessionId);
|
|
1054
1059
|
const tmpPath = `${path}.tmp`;
|
|
1055
1060
|
const payload = {
|
|
@@ -1571,7 +1576,8 @@ function extractInjectContext(payload) {
|
|
|
1571
1576
|
return normalizePromptText(payload.prompt) || null;
|
|
1572
1577
|
}
|
|
1573
1578
|
function resolveInjectProject$1(payload) {
|
|
1574
|
-
|
|
1579
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
|
|
1580
|
+
return resolveHookProject(cwd, payload.project);
|
|
1575
1581
|
}
|
|
1576
1582
|
async function buildLocalPack$1(context, project, dbPath, workingSetPaths = []) {
|
|
1577
1583
|
const store = new MemoryStore(dbPath);
|
|
@@ -2194,7 +2200,8 @@ function formatCodexAdditionalContext(packText, maxChars) {
|
|
|
2194
2200
|
return `${CODEMEM_CONTEXT_HEADER}${truncateAdditionalContext(normalized, bodyMaxChars)}`;
|
|
2195
2201
|
}
|
|
2196
2202
|
function resolveInjectProject(payload) {
|
|
2197
|
-
|
|
2203
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
|
|
2204
|
+
return resolveHookProject(cwd, payload.project);
|
|
2198
2205
|
}
|
|
2199
2206
|
function buildCodexInjectQuery(prompt, project) {
|
|
2200
2207
|
return [prompt, project ?? ""].filter((part) => part.trim().length > 0).join(" ").slice(0, 500) || "recent work";
|
|
@@ -3232,8 +3239,8 @@ function escapeSqlLikePattern(value) {
|
|
|
3232
3239
|
}
|
|
3233
3240
|
function formatBytes(bytes) {
|
|
3234
3241
|
if (bytes < 1024) return `${bytes} B`;
|
|
3235
|
-
if (bytes <
|
|
3236
|
-
return `${(bytes /
|
|
3242
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3243
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
3237
3244
|
}
|
|
3238
3245
|
function parseOptionalPositiveInt$1(value) {
|
|
3239
3246
|
if (!value) return void 0;
|
|
@@ -3922,8 +3929,9 @@ function resolveCandidateTarget(candidate) {
|
|
|
3922
3929
|
path: join(homedir(), ".config", "opencode", "AGENTS.md"),
|
|
3923
3930
|
display: "~/.config/opencode/AGENTS.md"
|
|
3924
3931
|
};
|
|
3932
|
+
const root = resolveProjectRoot(process.cwd()) ?? process.cwd();
|
|
3925
3933
|
return {
|
|
3926
|
-
path: join(
|
|
3934
|
+
path: join(root, "AGENTS.md"),
|
|
3927
3935
|
display: "AGENTS.md"
|
|
3928
3936
|
};
|
|
3929
3937
|
}
|
|
@@ -4164,7 +4172,8 @@ function emitStructuredError(errorCode, message) {
|
|
|
4164
4172
|
async function runEnqueueRawEvent(opts, dependencies = {}) {
|
|
4165
4173
|
try {
|
|
4166
4174
|
const payload = await (dependencies.readPayload ?? readStdinJson)();
|
|
4167
|
-
const
|
|
4175
|
+
const dbPath = resolveDbPath(resolveDbOpt(opts));
|
|
4176
|
+
const store = new MemoryStore(dbPath);
|
|
4168
4177
|
try {
|
|
4169
4178
|
ingestRawEvents(store, payload);
|
|
4170
4179
|
} finally {
|
|
@@ -4286,7 +4295,8 @@ var defaultLogger = {
|
|
|
4286
4295
|
error: (message) => console.error(`Error: ${message}`)
|
|
4287
4296
|
};
|
|
4288
4297
|
function readWorkerSyncConfig(configPath) {
|
|
4289
|
-
|
|
4298
|
+
const config = configPath ? readCodememConfigFileAtPath(configPath) : readCodememConfigFile();
|
|
4299
|
+
return readCoordinatorSyncConfig(config);
|
|
4290
4300
|
}
|
|
4291
4301
|
function createSequentialBackfillCoordinator(store, jobPlans, options) {
|
|
4292
4302
|
const pollIntervalMs = 1e3;
|
|
@@ -4381,13 +4391,13 @@ function startMaintenanceWorkerRuntime(options = {}) {
|
|
|
4381
4391
|
} catch (error) {
|
|
4382
4392
|
logger.warn(`WAL checkpoint failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4383
4393
|
}
|
|
4384
|
-
},
|
|
4394
|
+
}, 3e5);
|
|
4385
4395
|
walCheckpointTimer.unref();
|
|
4386
4396
|
if (!isEmbeddingDisabled()) runners.push(new VectorModelMigrationRunner({
|
|
4387
4397
|
dbPath,
|
|
4388
4398
|
signal: options.signal,
|
|
4389
4399
|
batchSize: 10,
|
|
4390
|
-
idleIntervalMs:
|
|
4400
|
+
idleIntervalMs: 6e4
|
|
4391
4401
|
}));
|
|
4392
4402
|
runners.push(createSequentialBackfillCoordinator(store, [
|
|
4393
4403
|
{
|
|
@@ -5125,7 +5135,7 @@ function createMemoryExtractionReplayCommand() {
|
|
|
5125
5135
|
observerReasoningEffort: opts.reasoningEffort === void 0 ? observerConfig.observerReasoningEffort : opts.reasoningEffort.trim() || null,
|
|
5126
5136
|
observerReasoningSummary: opts.reasoningSummary === void 0 ? observerConfig.observerReasoningSummary : opts.reasoningSummary.trim() || null,
|
|
5127
5137
|
observerMaxOutputTokens: maxOutputTokens ?? observerConfig.observerMaxOutputTokens ?? observerConfig.observerMaxTokens,
|
|
5128
|
-
observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [
|
|
5138
|
+
observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [.../* @__PURE__ */ new Set([...observerConfig.observerExplicitConfigKeys ?? [], "observerMaxOutputTokens"])]
|
|
5129
5139
|
};
|
|
5130
5140
|
const observer = new ObserverClient(observerConfigWithOverrides);
|
|
5131
5141
|
const result = opts.observerTierRouting === true ? await replayBatchExtractionWithTierRouting(resolveDbOpt(opts), observerConfigWithOverrides, {
|
|
@@ -5242,7 +5252,7 @@ function createMemoryExtractionBenchmarkCommand() {
|
|
|
5242
5252
|
observerReasoningEffort: opts.reasoningEffort === void 0 ? observerConfig.observerReasoningEffort : opts.reasoningEffort.trim() || null,
|
|
5243
5253
|
observerReasoningSummary: opts.reasoningSummary === void 0 ? observerConfig.observerReasoningSummary : opts.reasoningSummary.trim() || null,
|
|
5244
5254
|
observerMaxOutputTokens: maxOutputTokens ?? observerConfig.observerMaxOutputTokens ?? observerConfig.observerMaxTokens,
|
|
5245
|
-
observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [
|
|
5255
|
+
observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [.../* @__PURE__ */ new Set([...observerConfig.observerExplicitConfigKeys ?? [], "observerMaxOutputTokens"])]
|
|
5246
5256
|
};
|
|
5247
5257
|
const observer = new ObserverClient(observerConfigWithOverrides);
|
|
5248
5258
|
const runs = [];
|
|
@@ -5584,9 +5594,9 @@ memoryCommand.addCommand(createMemoryRelinkReportCommand());
|
|
|
5584
5594
|
memoryCommand.addCommand(createMemoryRelinkPlanCommand());
|
|
5585
5595
|
//#endregion
|
|
5586
5596
|
//#region src/commands/pack.ts
|
|
5587
|
-
var MAX_INTERNAL_LEDGER_INPUT_BYTES =
|
|
5597
|
+
var MAX_INTERNAL_LEDGER_INPUT_BYTES = 16384;
|
|
5588
5598
|
var INTERNAL_LEDGER_INPUT_TIMEOUT_MS = 1e3;
|
|
5589
|
-
var FORBIDDEN_INTERNAL_KEYS = new Set([
|
|
5599
|
+
var FORBIDDEN_INTERNAL_KEYS = /* @__PURE__ */ new Set([
|
|
5590
5600
|
"body",
|
|
5591
5601
|
"context",
|
|
5592
5602
|
"pack",
|
|
@@ -5598,7 +5608,7 @@ var FORBIDDEN_INTERNAL_KEYS = new Set([
|
|
|
5598
5608
|
"raw_prompt",
|
|
5599
5609
|
"title"
|
|
5600
5610
|
]);
|
|
5601
|
-
var INTERNAL_LEDGER_KEYS = new Set([
|
|
5611
|
+
var INTERNAL_LEDGER_KEYS = /* @__PURE__ */ new Set([
|
|
5602
5612
|
"action",
|
|
5603
5613
|
"attempt_id",
|
|
5604
5614
|
"started_at",
|
|
@@ -5613,13 +5623,13 @@ var INTERNAL_LEDGER_KEYS = new Set([
|
|
|
5613
5623
|
"failure_stage",
|
|
5614
5624
|
"original_attempt_id"
|
|
5615
5625
|
]);
|
|
5616
|
-
var INTERNAL_LEDGER_ACTIONS = new Set([
|
|
5626
|
+
var INTERNAL_LEDGER_ACTIONS = /* @__PURE__ */ new Set([
|
|
5617
5627
|
"record",
|
|
5618
5628
|
"delivery",
|
|
5619
5629
|
"cache_reuse"
|
|
5620
5630
|
]);
|
|
5621
|
-
var INTERNAL_RETRIEVAL_STATUSES = new Set(["skipped", "failed"]);
|
|
5622
|
-
var INTERNAL_DELIVERY_STATUSES = new Set([
|
|
5631
|
+
var INTERNAL_RETRIEVAL_STATUSES = /* @__PURE__ */ new Set(["skipped", "failed"]);
|
|
5632
|
+
var INTERNAL_DELIVERY_STATUSES = /* @__PURE__ */ new Set([
|
|
5623
5633
|
"handed_off",
|
|
5624
5634
|
"failed",
|
|
5625
5635
|
"unknown"
|
|
@@ -6450,6 +6460,51 @@ function sqliteVecFailureDiagnostics(error, dbPath) {
|
|
|
6450
6460
|
`error=${message}`
|
|
6451
6461
|
];
|
|
6452
6462
|
}
|
|
6463
|
+
function requiredNativeRuntimeFailureDiagnostics(error, context = {
|
|
6464
|
+
version: VERSION,
|
|
6465
|
+
nodeVersion: process.version,
|
|
6466
|
+
platform: process.platform,
|
|
6467
|
+
arch: process.arch
|
|
6468
|
+
}) {
|
|
6469
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
6470
|
+
return [
|
|
6471
|
+
"Required SQLite native binding is unavailable.",
|
|
6472
|
+
`Runtime: Node ${context.nodeVersion} on ${context.platform}/${context.arch}.`,
|
|
6473
|
+
`Details: ${detail}`,
|
|
6474
|
+
`Reinstall this exact codemem version: npm install -g codemem@${context.version}`,
|
|
6475
|
+
"Use Node 24.15+ on a supported 64-bit macOS, Linux, or Windows target; see the native install matrix for current platform coverage.",
|
|
6476
|
+
"This required better-sqlite3 failure is separate from optional sqlite-vec loading, which can fall back to lexical search."
|
|
6477
|
+
];
|
|
6478
|
+
}
|
|
6479
|
+
async function preflightServeNativeRuntime(invocation, dependencies = {
|
|
6480
|
+
isPortOpen,
|
|
6481
|
+
probe: probeRequiredNativeRuntime
|
|
6482
|
+
}) {
|
|
6483
|
+
if (invocation.mode === "start" && await dependencies.isPortOpen(invocation.host, invocation.port)) return { state: "already_running" };
|
|
6484
|
+
try {
|
|
6485
|
+
dependencies.probe();
|
|
6486
|
+
return { state: "ready" };
|
|
6487
|
+
} catch (error) {
|
|
6488
|
+
return {
|
|
6489
|
+
state: "unavailable",
|
|
6490
|
+
diagnostics: requiredNativeRuntimeFailureDiagnostics(error)
|
|
6491
|
+
};
|
|
6492
|
+
}
|
|
6493
|
+
}
|
|
6494
|
+
async function nativeRuntimeAllowsServeStart(invocation) {
|
|
6495
|
+
if (invocation.mode !== "start" && invocation.mode !== "restart") return true;
|
|
6496
|
+
const preflight = await preflightServeNativeRuntime(invocation);
|
|
6497
|
+
if (preflight.state === "already_running") {
|
|
6498
|
+
p.log.warn(`Viewer already running at http://${invocation.host}:${invocation.port}`);
|
|
6499
|
+
if (!invocation.background) process.exitCode = 1;
|
|
6500
|
+
return false;
|
|
6501
|
+
}
|
|
6502
|
+
if (preflight.state === "ready") return true;
|
|
6503
|
+
p.intro("codemem viewer");
|
|
6504
|
+
for (const line of preflight.diagnostics) p.log.error(line);
|
|
6505
|
+
process.exitCode = 1;
|
|
6506
|
+
return false;
|
|
6507
|
+
}
|
|
6453
6508
|
async function runServeCoordinatorMaintenance(store, dependencies) {
|
|
6454
6509
|
const projectShares = await dependencies.advancePendingProjectShares(store, { limit: 3 });
|
|
6455
6510
|
const coordinatorEnrollment = await dependencies.reconcileConfiguredCoordinatorEnrollment(store);
|
|
@@ -6508,11 +6563,11 @@ async function startForegroundViewer(invocation) {
|
|
|
6508
6563
|
if (invocation.dbPath) process.env.CODEMEM_DB = invocation.dbPath;
|
|
6509
6564
|
if (invocation.configPath) process.env.CODEMEM_CONFIG = invocation.configPath;
|
|
6510
6565
|
warnIfViewerExposed(invocation.host, invocation.port);
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
}
|
|
6566
|
+
const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
|
|
6567
|
+
if (!await terminateTrustedMaintenanceWorker(dbPath, {
|
|
6568
|
+
gracefulMs: 1e3,
|
|
6569
|
+
forceMs: 5e3
|
|
6570
|
+
})) p.log.warn("Existing maintenance worker is not trusted or did not stop; starting viewer anyway");
|
|
6516
6571
|
const preparedDb = prepareViewerDatabase(invocation.dbPath);
|
|
6517
6572
|
const observer = new ObserverClient();
|
|
6518
6573
|
let store;
|
|
@@ -6530,13 +6585,9 @@ async function startForegroundViewer(invocation) {
|
|
|
6530
6585
|
const sweeper = new RawEventSweeper(store, { observer });
|
|
6531
6586
|
sweeper.start();
|
|
6532
6587
|
const syncAbort = new AbortController();
|
|
6533
|
-
const
|
|
6588
|
+
const config = invocation.configPath ? readCodememConfigFileAtPath(invocation.configPath) : readCodememConfigFile();
|
|
6589
|
+
const syncConfig = readCoordinatorSyncConfig(config);
|
|
6534
6590
|
const syncEnabled = syncConfig.syncEnabled;
|
|
6535
|
-
const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
|
|
6536
|
-
if (!await terminateTrustedMaintenanceWorker(dbPath, {
|
|
6537
|
-
gracefulMs: 1e3,
|
|
6538
|
-
forceMs: 5e3
|
|
6539
|
-
})) p.log.warn("Existing maintenance worker is not trusted or did not stop; starting viewer anyway");
|
|
6540
6591
|
const syncRuntimeStatus = {
|
|
6541
6592
|
phase: syncEnabled ? "starting" : "disabled",
|
|
6542
6593
|
detail: syncEnabled ? "Waiting for viewer startup to finish" : "Sync is disabled"
|
|
@@ -6672,7 +6723,10 @@ async function startForegroundViewer(invocation) {
|
|
|
6672
6723
|
shutdown();
|
|
6673
6724
|
});
|
|
6674
6725
|
}
|
|
6675
|
-
async function runServeInvocation(invocation
|
|
6726
|
+
async function runServeInvocation(invocation, dependencies = {
|
|
6727
|
+
nativeRuntimeAllowsServeStart,
|
|
6728
|
+
stopExistingViewer
|
|
6729
|
+
}) {
|
|
6676
6730
|
const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
|
|
6677
6731
|
const runtimeConflict = await findRuntimeViewerConflict(dbPath, {
|
|
6678
6732
|
host: invocation.host,
|
|
@@ -6685,8 +6739,9 @@ async function runServeInvocation(invocation) {
|
|
|
6685
6739
|
process.exitCode = 1;
|
|
6686
6740
|
return;
|
|
6687
6741
|
}
|
|
6742
|
+
if (!await dependencies.nativeRuntimeAllowsServeStart(invocation)) return;
|
|
6688
6743
|
if (invocation.mode === "stop" || invocation.mode === "restart") {
|
|
6689
|
-
const result = await stopExistingViewer(dbPath, {
|
|
6744
|
+
const result = await dependencies.stopExistingViewer(dbPath, {
|
|
6690
6745
|
host: invocation.host,
|
|
6691
6746
|
port: invocation.port
|
|
6692
6747
|
});
|
|
@@ -6826,29 +6881,39 @@ function migrateLegacyOpencodePlugin() {
|
|
|
6826
6881
|
p.log.step("Removed legacy compat lib: ~/.config/opencode/lib/compat.js");
|
|
6827
6882
|
} catch {}
|
|
6828
6883
|
}
|
|
6829
|
-
|
|
6884
|
+
function isExactStringArray(value, expected) {
|
|
6885
|
+
return Array.isArray(value) && value.length === expected.length && value.every((entry, index) => entry === expected[index]);
|
|
6886
|
+
}
|
|
6887
|
+
/** Detect and upgrade legacy uvx/uv or managed single-package MCP entries in OpenCode config. */
|
|
6830
6888
|
function migrateLegacyOpencodeMcp(config) {
|
|
6831
6889
|
const mcpConfig = config.mcp;
|
|
6832
6890
|
if (!mcpConfig || typeof mcpConfig !== "object") return false;
|
|
6833
6891
|
const entry = mcpConfig.codemem;
|
|
6834
6892
|
if (!entry || typeof entry !== "object") return false;
|
|
6835
6893
|
const command = entry.command;
|
|
6836
|
-
|
|
6837
|
-
|
|
6894
|
+
const isLegacyUv = Array.isArray(command) && command.some((arg) => typeof arg === "string" && (arg === "uvx" || arg === "uv")) || typeof command === "string" && (command === "uvx" || command === "uv");
|
|
6895
|
+
const isManagedSinglePackageNpx = isExactStringArray(command, [
|
|
6896
|
+
"npx",
|
|
6897
|
+
"-y",
|
|
6898
|
+
"codemem",
|
|
6899
|
+
"mcp"
|
|
6900
|
+
]) || isExactStringArray(command, [
|
|
6901
|
+
"npx",
|
|
6902
|
+
"codemem",
|
|
6903
|
+
"mcp"
|
|
6904
|
+
]);
|
|
6905
|
+
if (isLegacyUv || isManagedSinglePackageNpx) {
|
|
6906
|
+
p.log.step("Upgrading managed MCP entry to the current npm launcher");
|
|
6907
|
+
const launcher = codememMcpLauncher();
|
|
6838
6908
|
mcpConfig.codemem = {
|
|
6839
|
-
|
|
6840
|
-
command: [
|
|
6841
|
-
"npx",
|
|
6842
|
-
"codemem",
|
|
6843
|
-
"mcp"
|
|
6844
|
-
],
|
|
6845
|
-
enabled: true
|
|
6909
|
+
...entry,
|
|
6910
|
+
command: [launcher.command, ...launcher.args]
|
|
6846
6911
|
};
|
|
6847
6912
|
return true;
|
|
6848
6913
|
}
|
|
6849
6914
|
return false;
|
|
6850
6915
|
}
|
|
6851
|
-
/** Detect and upgrade legacy uvx-
|
|
6916
|
+
/** Detect and upgrade legacy uvx or managed single-package MCP entries in Claude settings. */
|
|
6852
6917
|
function migrateLegacyClaudeMcp(settings) {
|
|
6853
6918
|
const mcpServers = settings.mcpServers;
|
|
6854
6919
|
if (!mcpServers || typeof mcpServers !== "object") return false;
|
|
@@ -6856,15 +6921,19 @@ function migrateLegacyClaudeMcp(settings) {
|
|
|
6856
6921
|
if (!entry || typeof entry !== "object") return false;
|
|
6857
6922
|
const command = entry.command;
|
|
6858
6923
|
const args = entry.args;
|
|
6859
|
-
|
|
6860
|
-
|
|
6924
|
+
const isLegacyUv = typeof command === "string" && (command === "uvx" || command === "uv") || Array.isArray(args) && args.some((arg) => typeof arg === "string" && (arg.startsWith("codemem==") || arg === "uvx"));
|
|
6925
|
+
const isManagedSinglePackageNpx = command === "npx" && isExactStringArray(args, [
|
|
6926
|
+
"-y",
|
|
6927
|
+
"codemem",
|
|
6928
|
+
"mcp"
|
|
6929
|
+
]);
|
|
6930
|
+
if (isLegacyUv || isManagedSinglePackageNpx) {
|
|
6931
|
+
p.log.step("Upgrading managed Claude MCP entry to the current npm launcher");
|
|
6932
|
+
const launcher = codememMcpLauncher();
|
|
6861
6933
|
mcpServers.codemem = {
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
"codemem",
|
|
6866
|
-
"mcp"
|
|
6867
|
-
]
|
|
6934
|
+
...entry,
|
|
6935
|
+
command: launcher.command,
|
|
6936
|
+
args: launcher.args
|
|
6868
6937
|
};
|
|
6869
6938
|
return true;
|
|
6870
6939
|
}
|
|
@@ -6919,13 +6988,10 @@ function installMcp(force) {
|
|
|
6919
6988
|
return true;
|
|
6920
6989
|
}
|
|
6921
6990
|
if (!migrated) {
|
|
6991
|
+
const launcher = codememMcpLauncher();
|
|
6922
6992
|
mcpConfig.codemem = {
|
|
6923
6993
|
type: "local",
|
|
6924
|
-
command: [
|
|
6925
|
-
"npx",
|
|
6926
|
-
"codemem",
|
|
6927
|
-
"mcp"
|
|
6928
|
-
],
|
|
6994
|
+
command: [launcher.command, ...launcher.args],
|
|
6929
6995
|
enabled: true
|
|
6930
6996
|
};
|
|
6931
6997
|
config.mcp = mcpConfig;
|
|
@@ -6942,7 +7008,8 @@ function installMcp(force) {
|
|
|
6942
7008
|
function isClaudeHooksPluginInstalled() {
|
|
6943
7009
|
const pluginDir = join(claudeConfigDir(), "plugins", "codemem");
|
|
6944
7010
|
if (existsSync(pluginDir)) return true;
|
|
6945
|
-
|
|
7011
|
+
const hooksJson = join(pluginDir, "hooks", "hooks.json");
|
|
7012
|
+
if (existsSync(hooksJson)) return true;
|
|
6946
7013
|
return false;
|
|
6947
7014
|
}
|
|
6948
7015
|
function installClaudeMcp(force) {
|
|
@@ -6959,13 +7026,10 @@ function installClaudeMcp(force) {
|
|
|
6959
7026
|
if ("codemem" in mcpServers && !force && !migrated) p.log.info(`Claude MCP entry already exists in ${settingsPath}`);
|
|
6960
7027
|
else {
|
|
6961
7028
|
if (!migrated) {
|
|
7029
|
+
const launcher = codememMcpLauncher();
|
|
6962
7030
|
mcpServers.codemem = {
|
|
6963
|
-
command:
|
|
6964
|
-
args:
|
|
6965
|
-
"-y",
|
|
6966
|
-
"codemem",
|
|
6967
|
-
"mcp"
|
|
6968
|
-
]
|
|
7031
|
+
command: launcher.command,
|
|
7032
|
+
args: launcher.args
|
|
6969
7033
|
};
|
|
6970
7034
|
settings.mcpServers = mcpServers;
|
|
6971
7035
|
}
|
|
@@ -6988,25 +7052,47 @@ function installClaudeMcp(force) {
|
|
|
6988
7052
|
return true;
|
|
6989
7053
|
}
|
|
6990
7054
|
/** The MCP server table appended to Codex config.toml. */
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
]
|
|
7055
|
+
function codexMcpBlock() {
|
|
7056
|
+
const launcher = codememMcpLauncher();
|
|
7057
|
+
const args = launcher.args.map((arg) => JSON.stringify(arg)).join(", ");
|
|
7058
|
+
return [
|
|
7059
|
+
"[mcp_servers.codemem]",
|
|
7060
|
+
`command = ${JSON.stringify(launcher.command)}`,
|
|
7061
|
+
`args = [${args}]`,
|
|
7062
|
+
"startup_timeout_sec = 30",
|
|
7063
|
+
"tool_timeout_sec = 60"
|
|
7064
|
+
].join("\n");
|
|
7065
|
+
}
|
|
6998
7066
|
var CODEX_MCP_TABLE_RE = /^[ \t]*\[[ \t]*mcp_servers[ \t]*\.[ \t]*("?)codemem\1[ \t]*\]/m;
|
|
7067
|
+
function migrateManagedCodexMcp(existing) {
|
|
7068
|
+
const tableMatch = CODEX_MCP_TABLE_RE.exec(existing);
|
|
7069
|
+
if (!tableMatch || tableMatch.index == null) return null;
|
|
7070
|
+
const blockStart = tableMatch.index;
|
|
7071
|
+
const nextTable = /^[ \t]*\[/gm;
|
|
7072
|
+
nextTable.lastIndex = blockStart + tableMatch[0].length;
|
|
7073
|
+
const blockEnd = nextTable.exec(existing)?.index ?? existing.length;
|
|
7074
|
+
const block = existing.slice(blockStart, blockEnd);
|
|
7075
|
+
const commandPattern = /^([ \t]*command[ \t]*=[ \t]*)"npx"([ \t]*(?:#.*)?)$/m;
|
|
7076
|
+
const argsPattern = /^([ \t]*args[ \t]*=[ \t]*)\[[ \t]*"-y"[ \t]*,[ \t]*"codemem"[ \t]*,[ \t]*"mcp"[ \t]*\]([ \t]*(?:#.*)?)$/m;
|
|
7077
|
+
if (!commandPattern.test(block) || !argsPattern.test(block)) return null;
|
|
7078
|
+
const launcher = codememMcpLauncher();
|
|
7079
|
+
const args = launcher.args.map((arg) => JSON.stringify(arg)).join(", ");
|
|
7080
|
+
const migrated = block.replace(commandPattern, `$1${JSON.stringify(launcher.command)}$2`).replace(argsPattern, `$1[${args}]$2`);
|
|
7081
|
+
return `${existing.slice(0, blockStart)}${migrated}${existing.slice(blockEnd)}`;
|
|
7082
|
+
}
|
|
6999
7083
|
/** Marker substring identifying codemem-owned hook commands. */
|
|
7000
7084
|
var CODEMEM_HOOK_MARKER = "codemem codex-hook-";
|
|
7001
7085
|
/**
|
|
7002
7086
|
* Resolve how Codex hooks should invoke codemem. Prefer a direct `codemem` call
|
|
7003
|
-
* when it's on PATH (fast — no per-hook resolution); fall back to `npx
|
|
7004
|
-
*
|
|
7005
|
-
*
|
|
7006
|
-
*
|
|
7087
|
+
* when it's on PATH (fast — no per-hook resolution); fall back to an `npx`
|
|
7088
|
+
* invocation that requests both `codemem` and `@codemem/embeddings` when codemem
|
|
7089
|
+
* isn't installed (e.g. setup was run via `npx codemem setup`). The paired
|
|
7090
|
+
* runtime keeps the local-store inject path (codex-hook-inject) able to embed
|
|
7091
|
+
* instead of degrading to FTS. Mirrors the MCP launcher's two-package model.
|
|
7007
7092
|
*/
|
|
7008
7093
|
function codememCodexHookBase() {
|
|
7009
|
-
|
|
7094
|
+
if (codememOnPath(true)) return "codemem";
|
|
7095
|
+
return "npx -y --package codemem --package @codemem/embeddings codemem";
|
|
7010
7096
|
}
|
|
7011
7097
|
/**
|
|
7012
7098
|
* Build the codemem-owned hook groups keyed by Codex event name, given the
|
|
@@ -7048,28 +7134,127 @@ function isCodememHookGroup(group) {
|
|
|
7048
7134
|
return hooks.some((h) => h != null && typeof h === "object" && typeof h.command === "string" && h.command.includes(CODEMEM_HOOK_MARKER));
|
|
7049
7135
|
}
|
|
7050
7136
|
/**
|
|
7051
|
-
* True if a resolved bin path is a transient
|
|
7052
|
-
*
|
|
7053
|
-
*
|
|
7054
|
-
*
|
|
7137
|
+
* True if a resolved bin path is a transient/project-local bin that will not be
|
|
7138
|
+
* on PATH for the globally-configured MCP hosts (OpenCode/Claude/Codex).
|
|
7139
|
+
*
|
|
7140
|
+
* - `_npx` / `.pnpm/dlx`: npx/dlx caches exposed only for the duration of a
|
|
7141
|
+
* `npx -y codemem setup` run, then removed.
|
|
7142
|
+
* - `node_modules/.bin`: a project-local install exposed only while setup runs
|
|
7143
|
+
* through `npm exec` / `pnpm exec` / a package script. The global hosts do not
|
|
7144
|
+
* inherit that project-local PATH, so baking a bare `codemem` command would
|
|
7145
|
+
* later fail to resolve.
|
|
7146
|
+
*
|
|
7147
|
+
* Such paths must NOT count as "on PATH" for launcher/hook command baking.
|
|
7055
7148
|
*/
|
|
7056
7149
|
function isTransientNpxBinPath(resolved) {
|
|
7057
|
-
return /[/\\]_npx[/\\]/.test(resolved) || /[/\\]\.pnpm[/\\]dlx[/\\]/.test(resolved);
|
|
7150
|
+
return /[/\\]_npx[/\\]/.test(resolved) || /[/\\]\.pnpm[/\\]dlx[/\\]/.test(resolved) || /[/\\]node_modules[/\\]\.bin[/\\]/.test(resolved);
|
|
7151
|
+
}
|
|
7152
|
+
/** Parse a semver core (major.minor.patch) plus optional prerelease tail. */
|
|
7153
|
+
function parseSetupSemver(version) {
|
|
7154
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-.+)?$/.exec(version.trim());
|
|
7155
|
+
if (!match) return null;
|
|
7156
|
+
return {
|
|
7157
|
+
core: [
|
|
7158
|
+
Number(match[1]),
|
|
7159
|
+
Number(match[2]),
|
|
7160
|
+
Number(match[3])
|
|
7161
|
+
],
|
|
7162
|
+
prerelease: version.includes("-")
|
|
7163
|
+
};
|
|
7164
|
+
}
|
|
7165
|
+
/**
|
|
7166
|
+
* True when a durable `codemem --version` output should be trusted to launch
|
|
7167
|
+
* `codemem mcp` directly. Mirrors the OpenCode plugin's runner check: accept the
|
|
7168
|
+
* exact build version, or a clean (non-prerelease) release at least as new as
|
|
7169
|
+
* this build. This prevents baking a bare launcher that later resolves to an
|
|
7170
|
+
* older global install sitting alongside the transient npx bin.
|
|
7171
|
+
*/
|
|
7172
|
+
function isDurableCodememVersionTrusted(versionOutput) {
|
|
7173
|
+
if (versionOutput === VERSION) return true;
|
|
7174
|
+
const candidate = parseSetupSemver(versionOutput);
|
|
7175
|
+
const current = parseSetupSemver(VERSION);
|
|
7176
|
+
if (!candidate || !current || candidate.prerelease) return false;
|
|
7177
|
+
for (let i = 0; i < 3; i++) {
|
|
7178
|
+
const c = candidate.core[i] ?? 0;
|
|
7179
|
+
const v = current.core[i] ?? 0;
|
|
7180
|
+
if (c > v) return true;
|
|
7181
|
+
if (c < v) return false;
|
|
7182
|
+
}
|
|
7183
|
+
return true;
|
|
7058
7184
|
}
|
|
7059
7185
|
/**
|
|
7060
7186
|
* Detect whether a durable `codemem` resolves on PATH (excluding a transient
|
|
7061
|
-
* npx/dlx bin that vanishes after this process exits).
|
|
7187
|
+
* npx/dlx bin that vanishes after this process exits). When `requireTrustedVersion`
|
|
7188
|
+
* is set, the durable binary must also report a version compatible with this
|
|
7189
|
+
* build before it is trusted, so setup never bakes a bare launcher that resolves
|
|
7190
|
+
* to a stale global install.
|
|
7062
7191
|
*/
|
|
7063
|
-
function codememOnPath() {
|
|
7192
|
+
function codememOnPath(requireTrustedVersion = false) {
|
|
7064
7193
|
try {
|
|
7065
|
-
const
|
|
7066
|
-
|
|
7067
|
-
|
|
7194
|
+
const command = process.platform === "win32" ? "where" : "which";
|
|
7195
|
+
const args = process.platform === "win32" ? ["codemem"] : ["-a", "codemem"];
|
|
7196
|
+
const durable = execFileSync(command, args, { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).filter((candidate) => !isTransientNpxBinPath(candidate));
|
|
7197
|
+
if (durable.length === 0) return false;
|
|
7198
|
+
if (!requireTrustedVersion) return true;
|
|
7199
|
+
const [firstDurable] = durable;
|
|
7200
|
+
if (firstDurable == null) return false;
|
|
7201
|
+
const version = probeDurableCodememVersion(firstDurable);
|
|
7202
|
+
return version != null && isDurableCodememVersionTrusted(version);
|
|
7068
7203
|
} catch {
|
|
7069
7204
|
return false;
|
|
7070
7205
|
}
|
|
7071
7206
|
}
|
|
7072
7207
|
/**
|
|
7208
|
+
* Run `<binary> --version`, returning trimmed stdout or null on failure. On
|
|
7209
|
+
* Windows a global npm install resolves to a `.cmd`/`.bat` shim that Node's
|
|
7210
|
+
* shell-less execFileSync cannot execute directly, so invoke it through
|
|
7211
|
+
* `cmd.exe /c` there; POSIX binaries run directly.
|
|
7212
|
+
*/
|
|
7213
|
+
function probeDurableCodememVersion(binaryPath) {
|
|
7214
|
+
try {
|
|
7215
|
+
return (process.platform === "win32" ? execFileSync("cmd.exe", [
|
|
7216
|
+
"/c",
|
|
7217
|
+
binaryPath,
|
|
7218
|
+
"--version"
|
|
7219
|
+
], {
|
|
7220
|
+
encoding: "utf-8",
|
|
7221
|
+
timeout: 3e3,
|
|
7222
|
+
stdio: [
|
|
7223
|
+
"ignore",
|
|
7224
|
+
"pipe",
|
|
7225
|
+
"ignore"
|
|
7226
|
+
]
|
|
7227
|
+
}) : execFileSync(binaryPath, ["--version"], {
|
|
7228
|
+
encoding: "utf-8",
|
|
7229
|
+
timeout: 3e3,
|
|
7230
|
+
stdio: [
|
|
7231
|
+
"ignore",
|
|
7232
|
+
"pipe",
|
|
7233
|
+
"ignore"
|
|
7234
|
+
]
|
|
7235
|
+
})).trim();
|
|
7236
|
+
} catch {
|
|
7237
|
+
return null;
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
function codememMcpLauncher(onPath = codememOnPath(true)) {
|
|
7241
|
+
return onPath ? {
|
|
7242
|
+
command: "codemem",
|
|
7243
|
+
args: ["mcp"]
|
|
7244
|
+
} : {
|
|
7245
|
+
command: "npx",
|
|
7246
|
+
args: [
|
|
7247
|
+
"-y",
|
|
7248
|
+
"--package",
|
|
7249
|
+
"codemem",
|
|
7250
|
+
"--package",
|
|
7251
|
+
"@codemem/embeddings",
|
|
7252
|
+
"codemem",
|
|
7253
|
+
"mcp"
|
|
7254
|
+
]
|
|
7255
|
+
};
|
|
7256
|
+
}
|
|
7257
|
+
/**
|
|
7073
7258
|
* Append the codemem MCP server table to Codex config.toml without rewriting
|
|
7074
7259
|
* unrelated content. Returns true on success.
|
|
7075
7260
|
*/
|
|
@@ -7077,6 +7262,20 @@ function installCodexMcp(codexHome, force) {
|
|
|
7077
7262
|
const configPath = join(codexHome, "config.toml");
|
|
7078
7263
|
const existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
|
|
7079
7264
|
if (CODEX_MCP_TABLE_RE.test(existing)) {
|
|
7265
|
+
const migrated = migrateManagedCodexMcp(existing);
|
|
7266
|
+
if (migrated != null) {
|
|
7267
|
+
try {
|
|
7268
|
+
copyFileSync(configPath, `${configPath}.codemem.bak`);
|
|
7269
|
+
} catch {}
|
|
7270
|
+
try {
|
|
7271
|
+
writeFileSync(configPath, migrated, "utf-8");
|
|
7272
|
+
p.log.success(`Codex MCP entry upgraded: ${configPath}`);
|
|
7273
|
+
return true;
|
|
7274
|
+
} catch (err) {
|
|
7275
|
+
p.log.error(`Failed to write ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7276
|
+
return false;
|
|
7277
|
+
}
|
|
7278
|
+
}
|
|
7080
7279
|
if (force) p.log.info(`Codex MCP entry already exists in ${configPath} — left as-is (TOML is not rewritten in place)`);
|
|
7081
7280
|
else p.log.info(`Codex MCP entry already exists in ${configPath}`);
|
|
7082
7281
|
return true;
|
|
@@ -7086,7 +7285,7 @@ function installCodexMcp(codexHome, force) {
|
|
|
7086
7285
|
} catch {}
|
|
7087
7286
|
let next = existing;
|
|
7088
7287
|
if (next.length > 0 && !next.endsWith("\n\n")) next += next.endsWith("\n") ? "\n" : "\n\n";
|
|
7089
|
-
next += `${
|
|
7288
|
+
next += `${codexMcpBlock()}\n`;
|
|
7090
7289
|
try {
|
|
7091
7290
|
writeFileSync(configPath, next, "utf-8");
|
|
7092
7291
|
p.log.success(`Codex MCP entry installed: ${configPath}`);
|
|
@@ -7113,12 +7312,38 @@ function installCodexHooks(codexHome, force) {
|
|
|
7113
7312
|
let hooks = config.hooks;
|
|
7114
7313
|
if (hooks == null || typeof hooks !== "object" || Array.isArray(hooks)) hooks = {};
|
|
7115
7314
|
const ours = buildCodememCodexHookGroups(codememCodexHookBase());
|
|
7315
|
+
const currentCommands = new Set(Object.values(ours).flatMap((groups) => groups.flatMap((group) => group.hooks.map((hook) => hook.command))));
|
|
7316
|
+
const groupNeedsMigration = (group) => {
|
|
7317
|
+
if (!isCodememHookGroup(group)) return false;
|
|
7318
|
+
const hooksList = group.hooks;
|
|
7319
|
+
if (!Array.isArray(hooksList)) return false;
|
|
7320
|
+
return hooksList.some((hook) => {
|
|
7321
|
+
if (hook == null || typeof hook !== "object") return false;
|
|
7322
|
+
const command = hook.command;
|
|
7323
|
+
if (typeof command !== "string") return false;
|
|
7324
|
+
return command.includes(CODEMEM_HOOK_MARKER) && !currentCommands.has(command);
|
|
7325
|
+
});
|
|
7326
|
+
};
|
|
7116
7327
|
let changed = false;
|
|
7328
|
+
const isCodememOwnedHook = (hook) => hook != null && typeof hook === "object" && typeof hook.command === "string" && hook.command.includes(CODEMEM_HOOK_MARKER);
|
|
7329
|
+
const stripCodememHooks = (group) => {
|
|
7330
|
+
if (!isCodememHookGroup(group)) return group;
|
|
7331
|
+
const hooksList = group.hooks;
|
|
7332
|
+
if (!Array.isArray(hooksList)) return null;
|
|
7333
|
+
const preservedHooks = hooksList.filter((hook) => !isCodememOwnedHook(hook));
|
|
7334
|
+
if (preservedHooks.length === 0) return null;
|
|
7335
|
+
return {
|
|
7336
|
+
...group,
|
|
7337
|
+
hooks: preservedHooks
|
|
7338
|
+
};
|
|
7339
|
+
};
|
|
7117
7340
|
for (const [event, ourGroups] of Object.entries(ours)) {
|
|
7118
7341
|
const current = hooks[event];
|
|
7119
7342
|
const existingGroups = Array.isArray(current) ? [...current] : [];
|
|
7120
|
-
|
|
7121
|
-
const
|
|
7343
|
+
const hasCodemem = existingGroups.some(isCodememHookGroup);
|
|
7344
|
+
const needsMigration = existingGroups.some(groupNeedsMigration);
|
|
7345
|
+
if (hasCodemem && !force && !needsMigration) continue;
|
|
7346
|
+
const preserved = existingGroups.map((group) => stripCodememHooks(group)).filter((group) => group != null);
|
|
7122
7347
|
hooks[event] = [...preserved, ...ourGroups];
|
|
7123
7348
|
changed = true;
|
|
7124
7349
|
}
|
|
@@ -7154,8 +7379,11 @@ function installCodex(force) {
|
|
|
7154
7379
|
p.log.error(`Failed to create Codex home ${codexHome}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7155
7380
|
return false;
|
|
7156
7381
|
}
|
|
7157
|
-
if (codememOnPath()) p.log.info("Codex hooks will call `codemem` directly (found on PATH).");
|
|
7158
|
-
else
|
|
7382
|
+
if (codememOnPath(true)) p.log.info("Codex hooks will call `codemem` directly (found on PATH).");
|
|
7383
|
+
else {
|
|
7384
|
+
const globalInstallCommand = process.platform === "linux" ? "env ONNXRUNTIME_NODE_INSTALL=skip npm install -g codemem" : "npm install -g codemem";
|
|
7385
|
+
p.log.info(`\`codemem\` is not on PATH, so Codex hooks will run via \`npx -y --package codemem --package @codemem/embeddings codemem\` (works without a global install). For lower hook latency: ${globalInstallCommand}`);
|
|
7386
|
+
}
|
|
7159
7387
|
let ok = true;
|
|
7160
7388
|
ok = installCodexMcp(codexHome, force) && ok;
|
|
7161
7389
|
ok = installCodexHooks(codexHome, force) && ok;
|
|
@@ -7285,7 +7513,7 @@ var statsCommand = statsCmd.action((opts) => {
|
|
|
7285
7513
|
//#region src/commands/status.ts
|
|
7286
7514
|
var MAX_ATTENTION = 20;
|
|
7287
7515
|
var DEFAULT_VIEWER_PORT = 38888;
|
|
7288
|
-
var RECENT_FAILURE_WINDOW_MS =
|
|
7516
|
+
var RECENT_FAILURE_WINDOW_MS = 864e5;
|
|
7289
7517
|
var OBSERVER_IDENTITY_KEYS = [
|
|
7290
7518
|
"observer_provider",
|
|
7291
7519
|
"observer_model",
|
|
@@ -8432,7 +8660,8 @@ bootstrapCmd.action(async (peerArg, opts) => {
|
|
|
8432
8660
|
process.exitCode = 2;
|
|
8433
8661
|
return;
|
|
8434
8662
|
}
|
|
8435
|
-
const
|
|
8663
|
+
const dbPath = resolveDbPath(resolveDbOpt(opts));
|
|
8664
|
+
const store = new MemoryStore(dbPath);
|
|
8436
8665
|
try {
|
|
8437
8666
|
const pageSize = Math.max(1, Number.parseInt(opts.pageSize, 10) || 2e3);
|
|
8438
8667
|
const keysDir = opts.keysDir ?? void 0;
|
|
@@ -8603,7 +8832,7 @@ syncCoordinatorAlias.hook("preAction", (_thisCmd, actionCmd) => {
|
|
|
8603
8832
|
syncCommand.addCommand(syncCoordinatorAlias, { hidden: true });
|
|
8604
8833
|
//#endregion
|
|
8605
8834
|
//#region src/commands/update.ts
|
|
8606
|
-
var INSTALL_TIMEOUT_MS =
|
|
8835
|
+
var INSTALL_TIMEOUT_MS = 42e4;
|
|
8607
8836
|
var VERIFY_TIMEOUT_MS = 3e4;
|
|
8608
8837
|
var PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
8609
8838
|
var UPDATE_LOCK_FILE = "update-install.lock";
|
|
@@ -8625,7 +8854,7 @@ async function removeStaleInstallLock(lockPath) {
|
|
|
8625
8854
|
const pid = Number.parseInt(rawPid.trim(), 10);
|
|
8626
8855
|
if (Number.isSafeInteger(pid) && pid > 0) {
|
|
8627
8856
|
if (isProcessAlive(pid)) return false;
|
|
8628
|
-
} else if (Date.now() - lockStat.mtimeMs <=
|
|
8857
|
+
} else if (Date.now() - lockStat.mtimeMs <= 45e4) return false;
|
|
8629
8858
|
await unlink(lockPath);
|
|
8630
8859
|
return true;
|
|
8631
8860
|
} catch {
|
|
@@ -8656,6 +8885,7 @@ function runCommand(command, args, timeoutMs, options = {}) {
|
|
|
8656
8885
|
const child = spawn(command, args, {
|
|
8657
8886
|
cwd: options.cwd,
|
|
8658
8887
|
detached: process.platform !== "win32",
|
|
8888
|
+
env: options.env,
|
|
8659
8889
|
shell: false,
|
|
8660
8890
|
stdio: [
|
|
8661
8891
|
"ignore",
|
|
@@ -8784,17 +9014,18 @@ function renderStatusWarning(error) {
|
|
|
8784
9014
|
function renderHumanStatus(status) {
|
|
8785
9015
|
const warning = renderStatusWarning(status.error);
|
|
8786
9016
|
if (status.update_available) return `Update available${cacheQualifier(status.stale)}: ${status.current_version} → ${status.latest_version}. ${status.recommended_action}${warning}`;
|
|
8787
|
-
if (!
|
|
9017
|
+
if (!status.channel || !isReleaseVersionForChannel(status.current_version, status.channel)) return `Unable to compare current version ${status.current_version} with ${status.latest_version}. ${status.recommended_action}${warning}`;
|
|
8788
9018
|
return `${status.current_version} is up to date${cacheQualifier(status.stale)}.${warning}`;
|
|
8789
9019
|
}
|
|
8790
|
-
var checkCommand = addJsonOption(new Command("check").description("Check for a newer
|
|
9020
|
+
var checkCommand = addJsonOption(new Command("check").description("Check for a newer codemem release on the installed channel")).addOption(new Option("-r, --refresh", "bypass the six-hour release cache")).configureHelp(helpStyle).action(async (options) => {
|
|
8791
9021
|
try {
|
|
9022
|
+
const installKind = detectInstallKind({
|
|
9023
|
+
entryPath: process.argv[1] ?? "",
|
|
9024
|
+
env: process.env
|
|
9025
|
+
});
|
|
8792
9026
|
const status = await getUpdateStatus({
|
|
8793
9027
|
currentVersion: VERSION,
|
|
8794
|
-
installKind
|
|
8795
|
-
entryPath: process.argv[1] ?? "",
|
|
8796
|
-
env: process.env
|
|
8797
|
-
}),
|
|
9028
|
+
installKind,
|
|
8798
9029
|
refresh: options.refresh
|
|
8799
9030
|
});
|
|
8800
9031
|
if (status.latest_version === null) {
|
|
@@ -8820,15 +9051,29 @@ var checkCommand = addJsonOption(new Command("check").description("Check for a n
|
|
|
8820
9051
|
}
|
|
8821
9052
|
}
|
|
8822
9053
|
});
|
|
8823
|
-
|
|
9054
|
+
function updateInstallArgs(npmArgs, targetVersion) {
|
|
9055
|
+
const installArgs = [
|
|
9056
|
+
"install",
|
|
9057
|
+
"-g",
|
|
9058
|
+
"--registry",
|
|
9059
|
+
PUBLIC_NPM_REGISTRY,
|
|
9060
|
+
`--@codemem:registry=${PUBLIC_NPM_REGISTRY}`,
|
|
9061
|
+
`codemem@${targetVersion}`,
|
|
9062
|
+
`@codemem/embeddings@${targetVersion}`
|
|
9063
|
+
];
|
|
9064
|
+
if (process.platform !== "win32") return installArgs;
|
|
9065
|
+
return [...npmArgs.slice(0, 3), windowsCommandLine(npmArgs[3] ?? "", installArgs)];
|
|
9066
|
+
}
|
|
9067
|
+
async function installUpdate(options) {
|
|
8824
9068
|
let releaseInstallLock = null;
|
|
8825
9069
|
try {
|
|
9070
|
+
const installKind = detectInstallKind({
|
|
9071
|
+
entryPath: process.argv[1] ?? "",
|
|
9072
|
+
env: process.env
|
|
9073
|
+
});
|
|
8826
9074
|
const status = await getUpdateStatus({
|
|
8827
9075
|
currentVersion: VERSION,
|
|
8828
|
-
installKind
|
|
8829
|
-
entryPath: process.argv[1] ?? "",
|
|
8830
|
-
env: process.env
|
|
8831
|
-
}),
|
|
9076
|
+
installKind,
|
|
8832
9077
|
refresh: true
|
|
8833
9078
|
});
|
|
8834
9079
|
if (!status.auto_update_eligible || !status.latest_version) {
|
|
@@ -8836,26 +9081,18 @@ var installCommand = addJsonOption(new Command("install").description("Install a
|
|
|
8836
9081
|
return;
|
|
8837
9082
|
}
|
|
8838
9083
|
const targetVersion = status.latest_version;
|
|
8839
|
-
if (!
|
|
8840
|
-
failInstall(options, "update_install_refused", "release version
|
|
9084
|
+
if (!status.channel || !isReleaseVersionForChannel(targetVersion, status.channel)) {
|
|
9085
|
+
failInstall(options, "update_install_refused", "release version does not match the installed channel");
|
|
8841
9086
|
return;
|
|
8842
9087
|
}
|
|
8843
9088
|
releaseInstallLock = await acquireInstallLock();
|
|
8844
9089
|
const npm = await resolveInstallCommand();
|
|
8845
|
-
const installation = await runCommand(npm.command,
|
|
8846
|
-
"install",
|
|
8847
|
-
"-g",
|
|
8848
|
-
"--registry",
|
|
8849
|
-
PUBLIC_NPM_REGISTRY,
|
|
8850
|
-
`codemem@${targetVersion}`
|
|
8851
|
-
])] : [
|
|
8852
|
-
"install",
|
|
8853
|
-
"-g",
|
|
8854
|
-
"--registry",
|
|
8855
|
-
PUBLIC_NPM_REGISTRY,
|
|
8856
|
-
`codemem@${targetVersion}`
|
|
8857
|
-
], INSTALL_TIMEOUT_MS, {
|
|
9090
|
+
const installation = await runCommand(npm.command, updateInstallArgs(npm.args, targetVersion), INSTALL_TIMEOUT_MS, {
|
|
8858
9091
|
cwd: npm.cwd,
|
|
9092
|
+
env: process.platform === "linux" ? {
|
|
9093
|
+
...process.env,
|
|
9094
|
+
ONNXRUNTIME_NODE_INSTALL: "skip"
|
|
9095
|
+
} : void 0,
|
|
8859
9096
|
windowsVerbatimArguments: process.platform === "win32"
|
|
8860
9097
|
});
|
|
8861
9098
|
if (installation.exitCode !== 0) {
|
|
@@ -8882,8 +9119,9 @@ var installCommand = addJsonOption(new Command("install").description("Install a
|
|
|
8882
9119
|
} finally {
|
|
8883
9120
|
await releaseInstallLock?.();
|
|
8884
9121
|
}
|
|
8885
|
-
}
|
|
8886
|
-
var
|
|
9122
|
+
}
|
|
9123
|
+
var installCommand = addJsonOption(new Command("install").description("Install an eligible codemem update on the installed channel")).configureHelp(helpStyle).action(installUpdate);
|
|
9124
|
+
var updateCommand = new Command("update").description("Inspect and manage codemem updates").enablePositionalOptions().configureHelp(helpStyle).addCommand(checkCommand).addCommand(installCommand);
|
|
8887
9125
|
//#endregion
|
|
8888
9126
|
//#region src/commands/version.ts
|
|
8889
9127
|
var versionCommand = new Command("version").configureHelp(helpStyle).description("Print codemem version").action(() => {
|