codemem 0.46.0-alpha.2 → 0.46.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/dist/index.js +403 -33
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ContextOnlyReplayError, DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, EXTRACTION_BENCHMARK_QUALITY_WEIGHTS, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, ObserverOutputError, ObserverOutputTransportError, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, atomicReplaceConfigFile, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, detectInstallKind, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, extractionReplayObserverIdentity, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getCodememConfigPath, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, ingestRawEvents, initDatabase, isAutomaticRecallMeasurement, isEmbeddingDisabled, isExplicitUpdateInstallEligible, isReleaseVersionForChannel, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, mutateCodememConfigFile, normalizePromptTransportProtocolRange, observerBaseUrlForProviderOverride, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordAutomaticRecall, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveComparablePath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, vacuumDatabase, viewerUrl } from "@codemem/core";
|
|
2
|
+
import { ContextOnlyReplayError, DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, EXTRACTION_BENCHMARK_QUALITY_WEIGHTS, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, ObserverOutputError, ObserverOutputTransportError, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, atomicReplaceConfigFile, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, 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, describePiObserverStatus, detectInstallKind, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, extractionReplayObserverIdentity, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getCodememConfigPath, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, ingestRawEvents, initDatabase, isAutomaticRecallMeasurement, isEmbeddingDisabled, isExplicitUpdateInstallEligible, isReleaseVersionForChannel, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, mutateCodememConfigFile, normalizePromptTransportProtocolRange, observerBaseUrlForProviderOverride, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordAutomaticRecall, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveComparablePath, resolveDbPath, resolveHookProject, resolvePiAgentDir, resolvePiObserverConfig, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, 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";
|
|
@@ -4949,6 +4949,11 @@ function forgetMemoryAction(idStr, opts) {
|
|
|
4949
4949
|
store.close();
|
|
4950
4950
|
}
|
|
4951
4951
|
}
|
|
4952
|
+
function parseConfidence(value) {
|
|
4953
|
+
const n = value == null || value.trim() === "" ? .5 : Number(value);
|
|
4954
|
+
if (!Number.isFinite(n)) return .5;
|
|
4955
|
+
return Math.min(1, Math.max(0, n));
|
|
4956
|
+
}
|
|
4952
4957
|
function rollbackManualMemory(store, sessionId, memoryId) {
|
|
4953
4958
|
store.db.transaction(() => {
|
|
4954
4959
|
const row = store.db.prepare("SELECT import_key FROM memory_items WHERE id = ?").get(memoryId);
|
|
@@ -4974,7 +4979,7 @@ async function rememberMemoryAction(opts) {
|
|
|
4974
4979
|
toolVersion: "manual",
|
|
4975
4980
|
metadata: { manual: true }
|
|
4976
4981
|
});
|
|
4977
|
-
const memId = store.remember(sessionId, opts.kind, opts.title, opts.body, .
|
|
4982
|
+
const memId = store.remember(sessionId, opts.kind, opts.title, opts.body, parseConfidence(opts.confidence), opts.tags);
|
|
4978
4983
|
if (!store.get(memId)) {
|
|
4979
4984
|
await store.flushPendingVectorWrites();
|
|
4980
4985
|
rollbackManualMemory(store, sessionId, memId);
|
|
@@ -5017,7 +5022,7 @@ function createForgetMemoryCommand() {
|
|
|
5017
5022
|
return cmd;
|
|
5018
5023
|
}
|
|
5019
5024
|
function createRememberMemoryCommand() {
|
|
5020
|
-
const cmd = new Command("remember").configureHelp(helpStyle).description("Manually add a memory item").requiredOption("-k, --kind <kind>", "memory kind (discovery, decision, feature, bugfix, etc.)").requiredOption("-t, --title <title>", "memory title").requiredOption("-b, --body <body>", "memory body text").option("--tags <tags...>", "tags (space-separated)").option("--project <project>", "project name (defaults to git repo root)");
|
|
5025
|
+
const cmd = new Command("remember").configureHelp(helpStyle).description("Manually add a memory item").requiredOption("-k, --kind <kind>", "memory kind (discovery, decision, feature, bugfix, etc.)").requiredOption("-t, --title <title>", "memory title").requiredOption("-b, --body <body>", "memory body text").option("--tags <tags...>", "tags (space-separated)").option("--project <project>", "project name (defaults to git repo root)").option("--confidence <n>", "confidence 0-1", "0.5");
|
|
5021
5026
|
addDbOption(cmd);
|
|
5022
5027
|
addJsonOption(cmd);
|
|
5023
5028
|
cmd.action(rememberMemoryAction);
|
|
@@ -8401,6 +8406,16 @@ function writeJsonConfig(path, data, { createBackup = true } = {}) {
|
|
|
8401
8406
|
atomicReplaceConfigFile(path, output, configMetadata(path));
|
|
8402
8407
|
return true;
|
|
8403
8408
|
}
|
|
8409
|
+
/**
|
|
8410
|
+
* Write JSON config after copying any existing file to `<path>.codemem.bak`.
|
|
8411
|
+
* Backup failure is non-fatal (write still proceeds).
|
|
8412
|
+
*/
|
|
8413
|
+
function writeJsonConfigWithBackup(path, data) {
|
|
8414
|
+
if (existsSync(path)) try {
|
|
8415
|
+
copyFileSync(path, `${path}.codemem.bak`);
|
|
8416
|
+
} catch {}
|
|
8417
|
+
writeJsonConfig(path, data);
|
|
8418
|
+
}
|
|
8404
8419
|
//#endregion
|
|
8405
8420
|
//#region src/commands/setup.ts
|
|
8406
8421
|
/**
|
|
@@ -8412,6 +8427,8 @@ function writeJsonConfig(path, data, { createBackup = true } = {}) {
|
|
|
8412
8427
|
* 1. Adds "@codemem/opencode-plugin" to the plugin array in ~/.config/opencode/opencode.jsonc
|
|
8413
8428
|
* 2. Adds/updates the MCP entry in ~/.config/opencode/opencode.jsonc
|
|
8414
8429
|
* 3. For Claude Code: installs MCP config and guides marketplace plugin install
|
|
8430
|
+
* 4. For Codex: MCP + hooks via CODEX_HOME
|
|
8431
|
+
* 5. For pi: packages entry + observer derivation + optional MCP adapter surface
|
|
8415
8432
|
*
|
|
8416
8433
|
* Designed to be safe to run repeatedly (idempotent unless --force).
|
|
8417
8434
|
*/
|
|
@@ -8425,6 +8442,23 @@ function claudeConfigDir() {
|
|
|
8425
8442
|
function codexConfigDir() {
|
|
8426
8443
|
return process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
|
|
8427
8444
|
}
|
|
8445
|
+
/** Resolve the pi agent directory, honoring PI_CODING_AGENT_DIR. */
|
|
8446
|
+
function piConfigDir() {
|
|
8447
|
+
return resolvePiAgentDir();
|
|
8448
|
+
}
|
|
8449
|
+
/** npm packages: entry prefix for the pi extension (version is appended). */
|
|
8450
|
+
var PI_EXTENSION_NPM_NAME = "@codemem/pi-extension";
|
|
8451
|
+
var PI_EXTENSION_NPM_PREFIX = `npm:${PI_EXTENSION_NPM_NAME}@`;
|
|
8452
|
+
var PI_MCP_ADAPTER_MARKER = "pi-mcp-adapter";
|
|
8453
|
+
/** Codemem stdio MCP server entry written to pi's mcp.json under --pi-mcp. */
|
|
8454
|
+
var PI_MCP_CODEMEM_ENTRY = {
|
|
8455
|
+
command: "npx",
|
|
8456
|
+
args: [
|
|
8457
|
+
"-y",
|
|
8458
|
+
"codemem",
|
|
8459
|
+
"mcp"
|
|
8460
|
+
]
|
|
8461
|
+
};
|
|
8428
8462
|
/** Remove legacy copied plugin JS file from ~/.config/opencode/plugins/codemem.js */
|
|
8429
8463
|
function migrateLegacyOpencodePlugin() {
|
|
8430
8464
|
const legacyPlugin = join(opencodeConfigDir(), "plugins", "codemem.js");
|
|
@@ -8949,38 +8983,374 @@ function installCodex(force, options = {}) {
|
|
|
8949
8983
|
ok = installCodexHooks(codexHome, force, hookBase) && ok;
|
|
8950
8984
|
return ok;
|
|
8951
8985
|
}
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8986
|
+
/** Build the packages: entry for the pi extension (npm pin or local path). */
|
|
8987
|
+
function buildPiExtensionPackageSpec(extensionPath, version = VERSION) {
|
|
8988
|
+
if (extensionPath?.trim()) {
|
|
8989
|
+
const raw = extensionPath.trim();
|
|
8990
|
+
return isAbsolute(raw) ? raw : resolve(raw);
|
|
8991
|
+
}
|
|
8992
|
+
return `${PI_EXTENSION_NPM_PREFIX}${version}`;
|
|
8993
|
+
}
|
|
8994
|
+
/** True when a packages: entry refers to @codemem/pi-extension (any version/path form). */
|
|
8995
|
+
function isPiExtensionPackageEntry(entry) {
|
|
8996
|
+
if (typeof entry !== "string" || !entry.trim()) return false;
|
|
8997
|
+
const value = entry.trim();
|
|
8998
|
+
if (value === `npm:${PI_EXTENSION_NPM_NAME}` || value.startsWith(PI_EXTENSION_NPM_PREFIX)) return true;
|
|
8999
|
+
if (value === PI_EXTENSION_NPM_NAME || value.startsWith(`${PI_EXTENSION_NPM_NAME}@`)) return true;
|
|
9000
|
+
return /(?:^|[/\\])@codemem[/\\]pi-extension(?:[/\\]|$)/.test(value) || /(?:^|[/\\])packages[/\\]pi-extension(?:[/\\]|$)/.test(value);
|
|
9001
|
+
}
|
|
9002
|
+
/** True when entry is an npm: pin for @codemem/pi-extension (versioned or bare). */
|
|
9003
|
+
function isNpmPiExtensionPackageEntry(entry) {
|
|
9004
|
+
if (typeof entry !== "string" || !entry.trim()) return false;
|
|
9005
|
+
const value = entry.trim();
|
|
9006
|
+
return value === `npm:${PI_EXTENSION_NPM_NAME}` || value.startsWith(PI_EXTENSION_NPM_PREFIX);
|
|
9007
|
+
}
|
|
9008
|
+
function piBinaryOnPath() {
|
|
9009
|
+
try {
|
|
9010
|
+
const out = execFileSync(process.platform === "win32" ? "where" : "which", ["pi"], { encoding: "utf-8" });
|
|
9011
|
+
return Boolean(out.split(/\r?\n/).map((line) => line.trim()).find(Boolean));
|
|
9012
|
+
} catch {
|
|
9013
|
+
return false;
|
|
9014
|
+
}
|
|
9015
|
+
}
|
|
9016
|
+
/** True when path exists and has non-whitespace content (real install marker). */
|
|
9017
|
+
function isNonEmptyFile(path) {
|
|
9018
|
+
try {
|
|
9019
|
+
if (!existsSync(path)) return false;
|
|
9020
|
+
return readFileSync(path, "utf-8").trim().length > 0;
|
|
9021
|
+
} catch {
|
|
9022
|
+
return false;
|
|
9023
|
+
}
|
|
9024
|
+
}
|
|
9025
|
+
/**
|
|
9026
|
+
* Detect pi via `pi` on PATH, or an agent dir that contains real install
|
|
9027
|
+
* markers (non-empty settings.json or auth.json). A bare empty ~/.pi/agent
|
|
9028
|
+
* directory must not count — users (and tests) create that path incidentally.
|
|
9029
|
+
* Honors PI_CODING_AGENT_DIR.
|
|
9030
|
+
*/
|
|
9031
|
+
function isPiDetected() {
|
|
9032
|
+
if (piBinaryOnPath()) return true;
|
|
9033
|
+
const dir = piConfigDir();
|
|
9034
|
+
if (!existsSync(dir)) return false;
|
|
9035
|
+
return isNonEmptyFile(join(dir, "settings.json")) || isNonEmptyFile(join(dir, "auth.json"));
|
|
9036
|
+
}
|
|
9037
|
+
/** Detect pi-mcp-adapter via packages: entry or extensions/ directory name. */
|
|
9038
|
+
function isPiMcpAdapterDetected(piDir = piConfigDir()) {
|
|
9039
|
+
const settingsPath = join(piDir, "settings.json");
|
|
9040
|
+
if (existsSync(settingsPath)) try {
|
|
9041
|
+
const packages = loadJsoncConfig(settingsPath).packages;
|
|
9042
|
+
if (Array.isArray(packages)) {
|
|
9043
|
+
if (packages.some((entry) => typeof entry === "string" && entry.includes(PI_MCP_ADAPTER_MARKER))) return true;
|
|
9044
|
+
}
|
|
9045
|
+
} catch {}
|
|
9046
|
+
const extensionsDir = join(piDir, "extensions");
|
|
9047
|
+
if (!existsSync(extensionsDir)) return false;
|
|
9048
|
+
try {
|
|
9049
|
+
return readdirSync(extensionsDir, { withFileTypes: true }).some((entry) => {
|
|
9050
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) return false;
|
|
9051
|
+
const name = entry.name.toLowerCase();
|
|
9052
|
+
return name.includes("mcp-adapter") || name.includes("mcp_adapter");
|
|
9053
|
+
});
|
|
9054
|
+
} catch {
|
|
9055
|
+
return false;
|
|
9056
|
+
}
|
|
9057
|
+
}
|
|
9058
|
+
function readPiJsoncOrAbort(path, untouchedHint) {
|
|
9059
|
+
if (!existsSync(path)) return {};
|
|
9060
|
+
try {
|
|
9061
|
+
return loadJsoncConfig(path);
|
|
9062
|
+
} catch (err) {
|
|
9063
|
+
p.log.error(`Failed to parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9064
|
+
p.log.info(`Leaving ${path} untouched. ${untouchedHint}`);
|
|
9065
|
+
return null;
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
function shouldKeepExistingPiExtension(existing, force, desired) {
|
|
9069
|
+
if (existing == null || force) return false;
|
|
9070
|
+
return !(isNpmPiExtensionPackageEntry(existing) && existing !== desired);
|
|
9071
|
+
}
|
|
9072
|
+
function writePiExtensionSettings(piDir, settingsPath, settings, existing, desired) {
|
|
9073
|
+
try {
|
|
9074
|
+
mkdirSync(piDir, { recursive: true });
|
|
9075
|
+
writeJsonConfigWithBackup(settingsPath, settings);
|
|
9076
|
+
p.log.success(existing != null ? `Pi extension package updated: ${desired}` : `Pi extension package added: ${desired}`);
|
|
9077
|
+
} catch (err) {
|
|
9078
|
+
p.log.error(`Failed to write ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9079
|
+
return false;
|
|
9080
|
+
}
|
|
9081
|
+
return true;
|
|
9082
|
+
}
|
|
9083
|
+
/**
|
|
9084
|
+
* Idempotently ensure the pi extension packages: entry is present in
|
|
9085
|
+
* settings.json. Backs up before write; aborts on parse failure.
|
|
9086
|
+
*/
|
|
9087
|
+
function installPiExtensionPackage(piDir, force, extensionPath) {
|
|
9088
|
+
const settingsPath = join(piDir, "settings.json");
|
|
9089
|
+
const desired = buildPiExtensionPackageSpec(extensionPath);
|
|
9090
|
+
const settings = readPiJsoncOrAbort(settingsPath, "Fix or remove the file, then re-run `codemem setup --pi-only`.");
|
|
9091
|
+
if (settings == null) return false;
|
|
9092
|
+
const list = Array.isArray(settings.packages) ? [...settings.packages] : [];
|
|
9093
|
+
const existing = list.find((entry) => isPiExtensionPackageEntry(entry));
|
|
9094
|
+
if (shouldKeepExistingPiExtension(existing, force, desired)) {
|
|
9095
|
+
p.log.info(`Pi extension package already configured in ${settingsPath}`);
|
|
9096
|
+
return true;
|
|
9097
|
+
}
|
|
9098
|
+
settings.packages = [...list.filter((entry) => !isPiExtensionPackageEntry(entry)), desired];
|
|
9099
|
+
return writePiExtensionSettings(piDir, settingsPath, settings, existing, desired);
|
|
9100
|
+
}
|
|
9101
|
+
function isOfficialObserverBaseUrl(url) {
|
|
9102
|
+
const normalized = url.trim().replace(/\/+$/, "").toLowerCase();
|
|
9103
|
+
return normalized === "https://api.openai.com" || normalized === "https://api.openai.com/v1" || normalized === "https://api.anthropic.com" || normalized === "https://api.anthropic.com/v1";
|
|
9104
|
+
}
|
|
9105
|
+
function nextPiToolsBlock(existing, toolsMode) {
|
|
9106
|
+
const piBlock = { ...existing.pi != null && typeof existing.pi === "object" && !Array.isArray(existing.pi) ? { ...existing.pi } : {} };
|
|
9107
|
+
if (toolsMode === "mcp-adapter") {
|
|
9108
|
+
if (piBlock.tools_mode === "mcp-adapter") return {
|
|
9109
|
+
piBlock,
|
|
9110
|
+
toolsModeUpdated: false
|
|
9111
|
+
};
|
|
9112
|
+
piBlock.tools_mode = "mcp-adapter";
|
|
9113
|
+
return {
|
|
9114
|
+
piBlock,
|
|
9115
|
+
toolsModeUpdated: true
|
|
9116
|
+
};
|
|
9117
|
+
}
|
|
9118
|
+
if (piBlock.tools_mode == null || piBlock.tools_mode === "") {
|
|
9119
|
+
piBlock.tools_mode = "native";
|
|
9120
|
+
return {
|
|
9121
|
+
piBlock,
|
|
9122
|
+
toolsModeUpdated: true
|
|
9123
|
+
};
|
|
9124
|
+
}
|
|
9125
|
+
return {
|
|
9126
|
+
piBlock,
|
|
9127
|
+
toolsModeUpdated: false
|
|
9128
|
+
};
|
|
9129
|
+
}
|
|
9130
|
+
function applyResolvedPiObserver(resolved, needs, next, updated) {
|
|
9131
|
+
if (needs.envProvider || needs.envModel) return;
|
|
9132
|
+
const explicitProvider = needs.fileProvider;
|
|
9133
|
+
if (explicitProvider) {
|
|
9134
|
+
if (needs.model && explicitProvider.toLowerCase() === String(resolved.provider).toLowerCase()) {
|
|
9135
|
+
next.observer_model = resolved.model;
|
|
9136
|
+
updated.push("observer_model");
|
|
9137
|
+
}
|
|
9138
|
+
return;
|
|
9139
|
+
}
|
|
9140
|
+
if (needs.fileModel) return;
|
|
9141
|
+
next.observer_provider = resolved.provider;
|
|
9142
|
+
updated.push("observer_provider");
|
|
9143
|
+
next.observer_model = resolved.model;
|
|
9144
|
+
updated.push("observer_model");
|
|
9145
|
+
const customBaseUrl = resolved.baseUrl && !isOfficialObserverBaseUrl(resolved.baseUrl) ? resolved.baseUrl : void 0;
|
|
9146
|
+
if (needs.baseUrl && customBaseUrl) {
|
|
9147
|
+
next.observer_base_url = customBaseUrl;
|
|
9148
|
+
updated.push("observer_base_url");
|
|
9149
|
+
}
|
|
9150
|
+
if (needs.useResponses && customBaseUrl) {
|
|
9151
|
+
next.observer_openai_use_responses = resolved.openAIUseResponses;
|
|
9152
|
+
updated.push("observer_openai_use_responses");
|
|
9153
|
+
}
|
|
9154
|
+
}
|
|
9155
|
+
function unsetObserverSlots(existing) {
|
|
9156
|
+
const envProvider = process.env.CODEMEM_OBSERVER_PROVIDER?.trim();
|
|
9157
|
+
const envModel = process.env.CODEMEM_OBSERVER_MODEL?.trim();
|
|
9158
|
+
const envBaseUrl = process.env.CODEMEM_OBSERVER_BASE_URL?.trim();
|
|
9159
|
+
const envUseResponses = process.env.CODEMEM_OBSERVER_OPENAI_USE_RESPONSES?.trim();
|
|
9160
|
+
const fileProvider = typeof existing.observer_provider === "string" ? existing.observer_provider.trim() : "";
|
|
9161
|
+
const fileModel = typeof existing.observer_model === "string" ? existing.observer_model.trim() : "";
|
|
9162
|
+
const fileBaseUrl = typeof existing.observer_base_url === "string" ? existing.observer_base_url.trim() : "";
|
|
9163
|
+
return {
|
|
9164
|
+
provider: !envProvider && !fileProvider,
|
|
9165
|
+
model: !envModel && !fileModel,
|
|
9166
|
+
baseUrl: !envBaseUrl && !fileBaseUrl,
|
|
9167
|
+
useResponses: !envUseResponses && existing.observer_openai_use_responses == null,
|
|
9168
|
+
fileProvider,
|
|
9169
|
+
fileModel,
|
|
9170
|
+
envProvider,
|
|
9171
|
+
envModel
|
|
9172
|
+
};
|
|
9173
|
+
}
|
|
9174
|
+
function fillUnsetObserverFromPi(existing, next, updated) {
|
|
9175
|
+
const needs = unsetObserverSlots(existing);
|
|
9176
|
+
if (!(needs.provider || needs.model || needs.baseUrl || needs.useResponses)) {
|
|
9177
|
+
p.log.info("Existing codemem observer config left unchanged");
|
|
9178
|
+
return;
|
|
9179
|
+
}
|
|
9180
|
+
const resolved = resolvePiObserverConfig();
|
|
9181
|
+
p.log.info(`Pi observer: ${describePiObserverStatus(resolved)}`);
|
|
9182
|
+
if (resolved.ok) {
|
|
9183
|
+
applyResolvedPiObserver(resolved, needs, next, updated);
|
|
9184
|
+
return;
|
|
9185
|
+
}
|
|
9186
|
+
if (!needs.fileProvider && !needs.fileModel && !needs.envProvider && !needs.envModel) p.log.info("Extraction model left unconfigured — set observer_provider/observer_model when ready.");
|
|
9187
|
+
}
|
|
9188
|
+
function writeUpdatedCodememConfig(next, updated) {
|
|
9189
|
+
if (updated.length === 0) return true;
|
|
9190
|
+
try {
|
|
9191
|
+
const saved = writeCodememConfigFile(next);
|
|
9192
|
+
p.log.success(`Codemem config updated (${updated.join(", ")}): ${saved}`);
|
|
9193
|
+
} catch (err) {
|
|
9194
|
+
p.log.error(`Failed to write codemem config: ${err instanceof Error ? err.message : String(err)}`);
|
|
9195
|
+
return false;
|
|
9196
|
+
}
|
|
9197
|
+
return true;
|
|
9198
|
+
}
|
|
9199
|
+
/**
|
|
9200
|
+
* Derive unset observer_* keys from pi config and ensure pi.tools_mode default.
|
|
9201
|
+
* Never persists API keys or tokens from pi auth.
|
|
9202
|
+
*/
|
|
9203
|
+
function wirePiCodememConfig(opts) {
|
|
9204
|
+
let existing;
|
|
9205
|
+
try {
|
|
9206
|
+
existing = readCodememConfigFile();
|
|
9207
|
+
} catch (err) {
|
|
9208
|
+
p.log.error(`Failed to read codemem config: ${err instanceof Error ? err.message : String(err)}`);
|
|
9209
|
+
return false;
|
|
9210
|
+
}
|
|
9211
|
+
const next = { ...existing };
|
|
9212
|
+
const updated = [];
|
|
9213
|
+
const { piBlock, toolsModeUpdated } = nextPiToolsBlock(existing, opts.toolsMode);
|
|
9214
|
+
if (toolsModeUpdated) updated.push("pi.tools_mode");
|
|
9215
|
+
next.pi = piBlock;
|
|
9216
|
+
fillUnsetObserverFromPi(existing, next, updated);
|
|
9217
|
+
return writeUpdatedCodememConfig(next, updated);
|
|
9218
|
+
}
|
|
9219
|
+
/**
|
|
9220
|
+
* Opt-in MCP surface for pi via pi-mcp-adapter. Writes mcp.json only when the
|
|
9221
|
+
* adapter is detected; otherwise explains the prerequisite and writes nothing.
|
|
9222
|
+
*/
|
|
9223
|
+
function installPiMcp(piDir, force) {
|
|
9224
|
+
if (!isPiMcpAdapterDetected(piDir)) {
|
|
9225
|
+
p.log.warn("MCP in pi requires the pi-mcp-adapter package. Install it (e.g. `pi install npm:pi-mcp-adapter`), then re-run `codemem setup --pi-only --pi-mcp`.");
|
|
9226
|
+
return {
|
|
9227
|
+
ok: true,
|
|
9228
|
+
wrote: false,
|
|
9229
|
+
adapterPresent: false
|
|
9230
|
+
};
|
|
9231
|
+
}
|
|
9232
|
+
const mcpPath = join(piDir, "mcp.json");
|
|
9233
|
+
let mcp;
|
|
9234
|
+
if (existsSync(mcpPath)) try {
|
|
9235
|
+
mcp = loadJsoncConfig(mcpPath);
|
|
9236
|
+
} catch (err) {
|
|
9237
|
+
p.log.error(`Failed to parse ${mcpPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9238
|
+
p.log.info(`Leaving ${mcpPath} untouched. Fix or remove the file, then re-run with --pi-mcp.`);
|
|
9239
|
+
return {
|
|
9240
|
+
ok: false,
|
|
9241
|
+
wrote: false,
|
|
9242
|
+
adapterPresent: true
|
|
9243
|
+
};
|
|
9244
|
+
}
|
|
9245
|
+
else mcp = {};
|
|
9246
|
+
let servers = mcp.mcpServers;
|
|
9247
|
+
if (servers == null || typeof servers !== "object" || Array.isArray(servers)) servers = {};
|
|
9248
|
+
if ("codemem" in servers && !force) {
|
|
9249
|
+
p.log.info(`Pi MCP entry already exists in ${mcpPath}`);
|
|
9250
|
+
return {
|
|
9251
|
+
ok: true,
|
|
9252
|
+
wrote: false,
|
|
9253
|
+
adapterPresent: true
|
|
9254
|
+
};
|
|
9255
|
+
}
|
|
9256
|
+
servers.codemem = { ...PI_MCP_CODEMEM_ENTRY };
|
|
9257
|
+
mcp.mcpServers = servers;
|
|
9258
|
+
try {
|
|
9259
|
+
mkdirSync(piDir, { recursive: true });
|
|
9260
|
+
writeJsonConfigWithBackup(mcpPath, mcp);
|
|
9261
|
+
p.log.success(`Pi MCP entry installed: ${mcpPath}`);
|
|
9262
|
+
} catch (err) {
|
|
9263
|
+
p.log.error(`Failed to write ${mcpPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9264
|
+
return {
|
|
9265
|
+
ok: false,
|
|
9266
|
+
wrote: false,
|
|
9267
|
+
adapterPresent: true
|
|
9268
|
+
};
|
|
9269
|
+
}
|
|
9270
|
+
return {
|
|
9271
|
+
ok: true,
|
|
9272
|
+
wrote: true,
|
|
9273
|
+
adapterPresent: true
|
|
9274
|
+
};
|
|
9275
|
+
}
|
|
9276
|
+
/**
|
|
9277
|
+
* Configure pi: packages entry, observer derivation, optional MCP adapter surface.
|
|
9278
|
+
* Idempotent; honors PI_CODING_AGENT_DIR. Returns true on success.
|
|
9279
|
+
*/
|
|
9280
|
+
function installPi(options = {}) {
|
|
9281
|
+
const force = options.force ?? false;
|
|
9282
|
+
const piDir = piConfigDir();
|
|
9283
|
+
try {
|
|
9284
|
+
mkdirSync(piDir, { recursive: true });
|
|
9285
|
+
} catch (err) {
|
|
9286
|
+
p.log.error(`Failed to create pi agent dir ${piDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9287
|
+
return false;
|
|
9288
|
+
}
|
|
9289
|
+
p.log.info(`Pi agent directory: ${piDir}`);
|
|
9290
|
+
if (!installPiExtensionPackage(piDir, force, options.piExtensionPath)) return false;
|
|
8955
9291
|
let ok = true;
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
p.log.
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
}
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
9292
|
+
let toolsMode = "native";
|
|
9293
|
+
if (options.piMcp) {
|
|
9294
|
+
const mcpResult = installPiMcp(piDir, force);
|
|
9295
|
+
ok = mcpResult.ok && ok;
|
|
9296
|
+
if (mcpResult.adapterPresent && mcpResult.ok) toolsMode = "mcp-adapter";
|
|
9297
|
+
}
|
|
9298
|
+
ok = wirePiCodememConfig({ toolsMode }) && ok;
|
|
9299
|
+
if (ok) {
|
|
9300
|
+
p.log.info("Pi next steps:");
|
|
9301
|
+
p.log.info(" - Start (or restart) pi to load the extension package");
|
|
9302
|
+
if (!options.piMcp) p.log.info(" - Optional MCP surface: install pi-mcp-adapter, then re-run with --pi-mcp");
|
|
9303
|
+
p.log.info(" - To disable: remove the @codemem/pi-extension entry from pi settings.json packages");
|
|
9304
|
+
}
|
|
9305
|
+
return ok;
|
|
9306
|
+
}
|
|
9307
|
+
function maybeInstallPi(opts, onlyFlag, force) {
|
|
9308
|
+
if (!(opts.piOnly || !onlyFlag && isPiDetected())) return true;
|
|
9309
|
+
p.log.step("Configuring pi (extension package + observer)...");
|
|
9310
|
+
return installPi({
|
|
9311
|
+
force,
|
|
9312
|
+
piMcp: opts.piMcp,
|
|
9313
|
+
piExtensionPath: opts.piExtensionPath
|
|
9314
|
+
});
|
|
9315
|
+
}
|
|
9316
|
+
function installOpencodeSetup(force) {
|
|
9317
|
+
p.log.step("Installing OpenCode plugin...");
|
|
9318
|
+
const pluginResult = installPlugin(force);
|
|
9319
|
+
p.log.step("Installing OpenCode MCP config...");
|
|
9320
|
+
return pluginResult.ok && installMcp(force, { createBackup: !pluginResult.wrote });
|
|
9321
|
+
}
|
|
9322
|
+
function installClaudeSetup(force) {
|
|
9323
|
+
p.log.step("Installing Claude Code MCP config...");
|
|
9324
|
+
return installClaudeMcp(force);
|
|
9325
|
+
}
|
|
9326
|
+
function installCodexSetup(force) {
|
|
9327
|
+
p.log.step("Configuring Codex (MCP + hooks)...");
|
|
9328
|
+
const ok = installCodex(force);
|
|
9329
|
+
p.log.info("Codex next steps:");
|
|
9330
|
+
p.log.info(" - Restart Codex to load the new configuration");
|
|
9331
|
+
p.log.info(" - On first run, approve the one-time prompt to trust the codemem hooks");
|
|
9332
|
+
p.log.info(" - MCP recall works immediately (no trust prompt required)");
|
|
9333
|
+
return ok;
|
|
9334
|
+
}
|
|
9335
|
+
function runSetupInstalls(opts, force) {
|
|
9336
|
+
const onlyFlag = Boolean(opts.opencodeOnly || opts.claudeOnly || opts.codexOnly || opts.piOnly);
|
|
9337
|
+
let ok = true;
|
|
9338
|
+
if (opts.opencodeOnly || !onlyFlag) ok = installOpencodeSetup(force) && ok;
|
|
9339
|
+
if (opts.claudeOnly || !onlyFlag) ok = installClaudeSetup(force) && ok;
|
|
9340
|
+
if (opts.codexOnly || !onlyFlag && existsSync(codexConfigDir())) ok = installCodexSetup(force) && ok;
|
|
9341
|
+
return maybeInstallPi(opts, onlyFlag, force) && ok;
|
|
9342
|
+
}
|
|
9343
|
+
function runSetup(opts) {
|
|
9344
|
+
p.intro(`codemem setup v${VERSION}`);
|
|
9345
|
+
if (runSetupInstalls(opts, opts.force ?? false)) {
|
|
9346
|
+
p.outro("Setup complete — restart your editor to load the plugin");
|
|
9347
|
+
return;
|
|
8983
9348
|
}
|
|
9349
|
+
p.outro("Setup completed with warnings");
|
|
9350
|
+
process.exitCode = 1;
|
|
9351
|
+
}
|
|
9352
|
+
var setupCommand = new Command("setup").configureHelp(helpStyle).description("Install codemem plugin + MCP config for OpenCode, Claude Code, Codex, and pi").option("--force", "overwrite existing installations").option("--opencode-only", "only install for OpenCode").option("--claude-only", "only install for Claude Code").option("--codex-only", "only install for Codex").option("--pi-only", "only install for pi").option("--pi-mcp", "opt into pi MCP adapter surface (requires pi-mcp-adapter)").option("--pi-extension-path <path>", "dev: write a local path packages entry instead of the npm pin").action((opts) => {
|
|
9353
|
+
runSetup(opts);
|
|
8984
9354
|
});
|
|
8985
9355
|
//#endregion
|
|
8986
9356
|
//#region src/commands/stats.ts
|