@staix/agent-mem 0.14.0 → 0.16.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/.claude-plugin/plugin.json +1 -1
- package/README.md +19 -3
- package/package.json +3 -2
- package/src/cli-agent-reads.ts +27 -13
- package/src/cli.ts +48 -9
- package/src/commands-table.ts +34 -7
- package/src/hook-spool.ts +12 -4
- package/src/hooks.ts +28 -5
- package/src/mcp-memory.ts +8 -5
- package/src/memory/candidate-overlaps.ts +87 -0
- package/src/memory/capture-input.ts +13 -1
- package/src/memory/capture.ts +29 -8
- package/src/memory/commands.ts +10 -1
- package/src/memory/consolidation-clusters.ts +6 -6
- package/src/memory/doctor.ts +11 -1
- package/src/memory/external-import.ts +15 -3
- package/src/memory/forget-nearby.ts +107 -0
- package/src/memory/search-fts.ts +4 -2
- package/src/memory/store.ts +8 -0
- package/src/session-brief.ts +2 -0
- package/src/setup-gemini.ts +31 -0
- package/src/setup-opencode.ts +15 -0
- package/src/setup-owned.ts +4 -0
- package/src/setup.ts +50 -10
package/src/setup.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { cliAttribution } from "./memory/cli-attribution.ts";
|
|
|
2
2
|
import { configuredPolicy } from "./memory/config.ts";
|
|
3
3
|
import { SETUP_CONFIG_BYTES, updateTomlArray } from "./setup-toml.ts";
|
|
4
4
|
import { managedAfter, mcpRecord, putMcpRecord, restoreOwned, sameImage, type ConfigImage } from "./setup-owned.ts";
|
|
5
|
+
import { opencodeServer } from "./setup-opencode.ts";
|
|
6
|
+
import { assertGeminiHome, assertGeminiRuntime, GEMINI_REMOVE, geminiAddCommand, geminiSettingsPath } from "./setup-gemini.ts";
|
|
5
7
|
import { spawnSync } from "node:child_process";
|
|
6
8
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
9
|
import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, renameSync, rmdirSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -14,13 +16,13 @@ import { memoryHome } from "./memory/home.ts";
|
|
|
14
16
|
import { label, PLAIN, singleLine, type Style } from "./memory/style.ts";
|
|
15
17
|
import { canonicalPath, memoryGitEnv, memoryGitSync, processLiveness, processSignature, realPath } from "./memory/platform.ts";
|
|
16
18
|
|
|
17
|
-
export const SETUP_TARGETS = ["claude", "codex", "kimi", "pi", "qwen", "jcode", "agy"] as const;
|
|
19
|
+
export const SETUP_TARGETS = ["claude", "codex", "kimi", "pi", "qwen", "jcode", "opencode", "gemini", "agy"] as const;
|
|
18
20
|
/** Workspace-scoped Antigravity setup is always selected explicitly. */
|
|
19
21
|
export const SETUP_ALL_TARGETS = SETUP_TARGETS.filter(target => target !== "agy");
|
|
20
22
|
export type SetupTarget = typeof SETUP_TARGETS[number];
|
|
21
23
|
// Only recorded layout verification versions belong here; null means no version was recorded.
|
|
22
24
|
const SETUP_VERIFIED_VERSIONS: Readonly<Record<SetupTarget, string | null>> = {
|
|
23
|
-
claude: null, codex: "0.165.0", kimi: null, pi: null, qwen: "0.26.1", jcode: "0.99.1", agy: null,
|
|
25
|
+
claude: null, codex: "0.165.0", kimi: null, pi: null, qwen: "0.26.1", jcode: "0.99.1", opencode: "1.18.35", gemini: "0.61.0", agy: null,
|
|
24
26
|
};
|
|
25
27
|
interface ClientVersion { output: string | null; verified: string | null; matches: boolean | null }
|
|
26
28
|
type Env = Readonly<Record<string, string | undefined>>;
|
|
@@ -145,6 +147,10 @@ function context(options: SetupContext, workspace?: WorkspaceBinding) {
|
|
|
145
147
|
qwen: [join(qwen, "settings.json")],
|
|
146
148
|
// Absolute JCODE_HOME spellings stay literal; unset uses the platform-home default.
|
|
147
149
|
jcode: [join(root("JCODE_HOME", ".jcode"), "mcp.json")],
|
|
150
|
+
// xdg-basedir 5.1.0: XDG_CONFIG_HOME verbatim when set and non-empty, else ~/.config (OpenCode v1.18.35 packages/core/src/global.ts).
|
|
151
|
+
opencode: [join(opencodeConfigRoot(env, home), "opencode", "opencode.json")],
|
|
152
|
+
// Gemini CLI's own user settings, never Antigravity's <home>/.gemini/config/mcp_config.json.
|
|
153
|
+
gemini: [geminiSettingsPath(env, home)],
|
|
148
154
|
agy: workspace ? [join(workspace.root, ".agents", "mcp_config.json")] : [],
|
|
149
155
|
};
|
|
150
156
|
const requestedBun = options.bun ?? "bun", bun = requestedBun === "bun" ? Bun.which("bun", { PATH: env.PATH ?? process.env.PATH }) : requestedBun;
|
|
@@ -158,10 +164,21 @@ function context(options: SetupContext, workspace?: WorkspaceBinding) {
|
|
|
158
164
|
return { env, home, paths, bun, entry, workspace, state: join(canonicalPath(memoryHome({ ...env, AGENTHUB_HOME: env.AGENTHUB_HOME?.trim() || join(home, ".agenthub") })), "setup") };
|
|
159
165
|
}
|
|
160
166
|
function command(target: SetupTarget, bun: string, entry: string): string[] | null {
|
|
161
|
-
if (target === "kimi" || target === "jcode" || target === "agy") return null;
|
|
167
|
+
if (target === "kimi" || target === "jcode" || target === "opencode" || target === "agy") return null;
|
|
162
168
|
if (target === "qwen") return [target, "mcp", "add", "--scope", "user", "--transport", "stdio", "amem", bun, "--", "--smol", entry, "mcp"];
|
|
169
|
+
if (target === "gemini") return geminiAddCommand(bun, entry);
|
|
163
170
|
return [target, "mcp", "add", ...(target === "claude" ? ["--scope", "user", "--transport", "stdio"] : []), "amem", "--", bun, "--smol", entry, "mcp"];
|
|
164
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* OpenCode 1.18.35 resolves its config root with xdg-basedir 5.1.0, which uses XDG_CONFIG_HOME verbatim when set
|
|
174
|
+
* (no trim) and falls back to ~/.config only when it is unset or empty. A relative value would resolve against
|
|
175
|
+
* OpenCode's own cwd, so setup refuses it rather than guess. An absolute value keeps its literal spelling.
|
|
176
|
+
*/
|
|
177
|
+
function opencodeConfigRoot(env: Env, home: string): string {
|
|
178
|
+
const value = env.XDG_CONFIG_HOME;
|
|
179
|
+
// A relative value is refused in assertOpencodeLayout; the path here only has to be well-formed for inventory.
|
|
180
|
+
return value === undefined || value === "" || !isAbsolute(value) ? join(home, ".config") : value;
|
|
181
|
+
}
|
|
165
182
|
function assertJcodeHome(env: Env): void {
|
|
166
183
|
if (env.JCODE_HOME !== undefined && !isAbsolute(env.JCODE_HOME)) throw Error("setup requires an absolute JCODE_HOME; unset it to use the default");
|
|
167
184
|
}
|
|
@@ -169,6 +186,15 @@ function assertJcodeRuntime(c: ReturnType<typeof context>): void {
|
|
|
169
186
|
assertJcodeHome(c.env);
|
|
170
187
|
if ([c.bun, c.entry].some(value => value.includes("${"))) throw Error("Jcode interpolates environment placeholders in MCP command paths; setup cannot register this runtime exactly");
|
|
171
188
|
}
|
|
189
|
+
/** OpenCode merges config.json, opencode.json, then opencode.jsonc from its global directory, and env routes can replace it. */
|
|
190
|
+
function assertOpencodeLayout(c: ReturnType<typeof context>, mode: SetupPlan["mode"]): void {
|
|
191
|
+
const xdg = c.env.XDG_CONFIG_HOME;
|
|
192
|
+
if (xdg !== undefined && xdg !== "" && !isAbsolute(xdg)) throw Error("OpenCode setup requires an absolute XDG_CONFIG_HOME; unset it to use ~/.config");
|
|
193
|
+
if (mode !== "undo") for (const key of ["OPENCODE_CONFIG", "OPENCODE_CONFIG_DIR", "OPENCODE_CONFIG_CONTENT"]) if (c.env[key]?.trim()) throw Error(`OpenCode setup refuses ${key}: it routes configuration away from the global file this setup owns`);
|
|
194
|
+
const dir = dirname(c.paths.opencode[0]!);
|
|
195
|
+
for (const name of ["opencode.jsonc", "config.json"]) if (read(join(dir, name)).bytes !== null) throw Error(`OpenCode setup supports one strict-JSON global opencode.json; ${name} beside it is merged too and is not supported`);
|
|
196
|
+
if ([c.bun, c.entry].some(value => /\{(?:env|file):/.test(value))) throw Error("OpenCode substitutes {env:} and {file:} in configuration; setup cannot register this runtime exactly");
|
|
197
|
+
}
|
|
172
198
|
function intent(c: ReturnType<typeof context>, target: SetupTarget): string { return digest([1, target, c.paths[target], c.bun, c.entry, ...(["codex", "kimi", "qwen"].includes(target) ? ["capture-json-v1"] : []), ...(target === "agy" ? [c.workspace!.root] : [])]); }
|
|
173
199
|
function journal(c: ReturnType<typeof context>, target: SetupTarget): string { return join(c.state, target === "agy" ? `agy-${digest(c.workspace!.root)}.json` : `${target}.json`); }
|
|
174
200
|
function receipt(c: ReturnType<typeof context>, target: SetupTarget, requireRuntime = false): Receipt | undefined {
|
|
@@ -189,18 +215,21 @@ function receipt(c: ReturnType<typeof context>, target: SetupTarget, requireRunt
|
|
|
189
215
|
return value as unknown as Receipt;
|
|
190
216
|
}
|
|
191
217
|
function plannedHooks(c: ReturnType<typeof context>, target: SetupTarget): PlannedHook[] {
|
|
192
|
-
if (target === "jcode" || target === "agy") return [];
|
|
218
|
+
if (target === "jcode" || target === "opencode" || target === "gemini" || target === "agy") return [];
|
|
193
219
|
const launch = `${quote(c.bun)} --smol ${quote(c.entry)}`;
|
|
194
220
|
return target === "claude" ? HOOK_EVENTS.map(event => ({ event, command: `${launch} hook ${event}`, timeout: 1 }))
|
|
195
221
|
: [{ event: target === "pi" ? "session_shutdown" : "SessionEnd", command: `${launch} ingest ${target}${target === "pi" ? "" : " --json"}`, timeout: target === "pi" ? 2.5 : 3 }];
|
|
196
222
|
}
|
|
197
|
-
const lifecycle = (target: SetupTarget) => target === "jcode" || target === "agy" ? "MCP only; native capture unavailable" : target === "claude" ? "seven bounded native hooks" : target === "pi" ? "session_shutdown incremental ingest" : "SessionEnd incremental ingest";
|
|
223
|
+
const lifecycle = (target: SetupTarget) => target === "jcode" || target === "opencode" || target === "gemini" || target === "agy" ? "MCP only; native capture unavailable" : target === "claude" ? "seven bounded native hooks" : target === "pi" ? "session_shutdown incremental ingest" : "SessionEnd incremental ingest";
|
|
198
224
|
export function planSetup(target: string, mode: SetupPlan["mode"] = "preview", options: SetupContext = {}): SetupPlan {
|
|
199
|
-
if (target !== "all" && !SETUP_TARGETS.includes(target as SetupTarget)) throw Error("usage: amem setup claude|codex|kimi|pi|qwen|jcode|agy|all [--apply|--undo]");
|
|
225
|
+
if (target !== "all" && !SETUP_TARGETS.includes(target as SetupTarget)) throw Error("usage: amem setup claude|codex|kimi|pi|qwen|jcode|opencode|gemini|agy|all [--apply|--undo]");
|
|
200
226
|
const targets: SetupTarget[] = target === "all" ? [...SETUP_ALL_TARGETS] : [target as SetupTarget];
|
|
201
227
|
if (targets.includes("jcode")) assertJcodeHome(options.env ?? process.env);
|
|
202
228
|
const c = context(options, targets.includes("agy") ? workspaceBinding(options) : undefined);
|
|
203
229
|
if (mode !== "undo" && targets.includes("jcode")) assertJcodeRuntime(c);
|
|
230
|
+
if (targets.includes("opencode") && (target === "opencode" || available(c, "opencode", options))) assertOpencodeLayout(c, mode);
|
|
231
|
+
if (targets.includes("gemini") && (target === "gemini" || available(c, "gemini", options))) assertGeminiHome(c.env);
|
|
232
|
+
if (mode !== "undo" && targets.includes("gemini")) assertGeminiRuntime(c.bun, c.entry);
|
|
204
233
|
const pluginNotes: string[] = [];
|
|
205
234
|
if (targets.includes("claude")) {
|
|
206
235
|
const settings = json(read(c.paths.claude[1]!));
|
|
@@ -212,6 +241,8 @@ export function planSetup(target: string, mode: SetupPlan["mode"] = "preview", o
|
|
|
212
241
|
}
|
|
213
242
|
if (targets.includes("qwen")) pluginNotes.push("Qwen capture supports the verified 0.26.1 native format. For custom advanced.runtimeOutputDir, export the matching QWEN_RUNTIME_DIR for manual and SessionEnd ingest.");
|
|
214
243
|
if (targets.includes("jcode")) pluginNotes.push("Jcode setup registers MCP only. Native saved sessions do not prove per-message project scope, so capture and ingest hooks are unavailable; no hook or config.toml is installed.");
|
|
244
|
+
if (targets.includes("gemini")) pluginNotes.push("Gemini CLI setup registers MCP only, at user scope in ~/.gemini/settings.json (GEMINI_CLI_HOME moves it), through the native gemini mcp add in a private staging HOME. Antigravity's ~/.gemini/config/mcp_config.json is never touched. A workspace .gemini/settings.json defining mcpServers.amem overrides this entry. No hook or ingest is installed: see docs/gemini-cli.md for why transcripts are not ingested.");
|
|
245
|
+
if (targets.includes("opencode")) pluginNotes.push("OpenCode setup registers MCP only in the global strict-JSON opencode.json (mcp.amem, a local server started in the session's project directory). No plugin or ingest hook is installed: see docs/opencode.md for why transcripts are not ingested. A project opencode.json defining mcp.amem overrides this entry.");
|
|
215
246
|
if (c.workspace) {
|
|
216
247
|
pluginNotes.push(`Antigravity MCP is pinned to this repository only: ${c.workspace.root}. Restart the CLI/IDE and use one workspace; no global or plugin definitions are replaced, and same-name definitions elsewhere are unsupported. Native capture is unavailable; no hooks are installed. Only strict JSON is supported; JSONC is refused. Native TUI/IDE presence and live behavior are unverified; availability means this direct configuration operation is supported.`);
|
|
217
248
|
if (!c.workspace.ignored) pluginNotes.push("WARNING: .agents/mcp_config.json is not Git-ignored; it contains absolute local paths. Setup will refuse it if it becomes tracked.");
|
|
@@ -222,9 +253,9 @@ export function planSetup(target: string, mode: SetupPlan["mode"] = "preview", o
|
|
|
222
253
|
const prior = receipt(c, target, false); for (const file of c.paths[target]) read(file);
|
|
223
254
|
const original = read(c.paths[target][0]!);
|
|
224
255
|
const document: Record<string, unknown> = target === "agy" ? {} : target === "codex" ? toml(original.bytes === null ? "" : Buffer.from(original.bytes, "base64").toString("utf8")) : json(original);
|
|
225
|
-
const hasAmem = target === "jcode" || target === "agy" ? mcpRecord(target, original) !== undefined : Object.hasOwn(record(document[target === "codex" ? "mcp_servers" : "mcpServers"] ?? {}), "amem");
|
|
256
|
+
const hasAmem = target === "jcode" || target === "opencode" || target === "agy" ? mcpRecord(target, original) !== undefined : Object.hasOwn(record(document[target === "codex" ? "mcp_servers" : "mcpServers"] ?? {}), "amem");
|
|
226
257
|
const unchanged = !!prior && prior.phase === "applied" && prior.intent === intent(c, target) && prior.changes.every((change, index) => { try { const live = read(change.path); restoreChange(prior, change, index, live); return managedAfter(target, index, change.before, change.after, live); } catch { return false; } });
|
|
227
|
-
const add = command(target, c.bun, c.entry), remove = [target, "mcp", "remove", ...(target === "claude" || target === "qwen" ? ["--scope", "user"] : []), "amem"];
|
|
258
|
+
const add = command(target, c.bun, c.entry), remove = target === "gemini" ? [...GEMINI_REMOVE] : [target, "mcp", "remove", ...(target === "claude" || target === "qwen" ? ["--scope", "user"] : []), "amem"];
|
|
228
259
|
const installed = available(c, target, options), version = installed && target !== "agy" && mode !== "undo" ? probeClientVersion(c, target, options) : undefined;
|
|
229
260
|
if (version?.matches === false) pluginNotes.push(`WARNING: ${target} version ${version.output}; layout verified on ${version.verified}. Apply remains guarded by native configuration validation.`);
|
|
230
261
|
return { target, available: installed, ...(version ? { version } : {}), files: c.paths[target], receipt: journal(c, target), ...(target === "agy" ? { cwd: c.workspace!.root } : {}), registration: mode === "undo" ? null : add, lifecycle: mode === "undo" ? "restore original MCP/hook files" : lifecycle(target), hooks: mode === "undo" || unchanged ? [] : plannedHooks(c, target), mcpChange: mode === "undo" ? prior ? "restore" : "unchanged" : unchanged ? "unchanged" : hasAmem ? "replace" : "add", commands: mode === "undo" || prior?.intent === intent(c, target) || !add || !installed ? [] : [...(hasAmem ? [remove] : []), add], state: prior && prior.intent !== intent(c, target) ? "runtime changed; managed entries will update" : prior?.phase ?? "not installed" };
|
|
@@ -237,6 +268,10 @@ function stagingEnvironment(c: ReturnType<typeof context>, target: SetupTarget,
|
|
|
237
268
|
env.QWEN_RUNTIME_DIR = join(staging, "qwen-runtime");
|
|
238
269
|
env.QWEN_CODE_SYSTEM_SETTINGS_PATH = join(staging, "qwen-system-settings.json");
|
|
239
270
|
env.QWEN_CODE_SYSTEM_DEFAULTS_PATH = join(staging, "qwen-system-defaults.json");
|
|
271
|
+
// Gemini CLI: GEMINI_CLI_HOME overrides HOME for all its state; system settings paths are isolated too.
|
|
272
|
+
env.GEMINI_CLI_HOME = staging;
|
|
273
|
+
env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = join(staging, "gemini-system-settings.json");
|
|
274
|
+
env.GEMINI_CLI_SYSTEM_DEFAULTS_PATH = join(staging, "gemini-system-defaults.json");
|
|
240
275
|
mkdirSync(env.TMPDIR!, { mode: 0o700 });
|
|
241
276
|
if (c.env.CLAUDE_CONFIG_DIR?.trim()) env.CLAUDE_CONFIG_DIR = join(staging, ".claude"); else delete env.CLAUDE_CONFIG_DIR;
|
|
242
277
|
// The staging workspace is a trusted private binding, not a Git repository or the real caller cwd.
|
|
@@ -266,7 +301,7 @@ function probeClientVersion(c: ReturnType<typeof context>, target: SetupTarget,
|
|
|
266
301
|
function verifyNativeMcp(target: SetupTarget, env: Env, cwd: string): void {
|
|
267
302
|
// In older free-form Pi CLIs, an unknown subcommand can be treated as a model prompt.
|
|
268
303
|
const help = spawnSync(target, ["--help"], { cwd, env: { ...env }, encoding: "utf8", timeout: 5000, maxBuffer: MAX, stdio: ["ignore", "pipe", "pipe"] });
|
|
269
|
-
if (help.status !== 0 || !/^\s+(?:(?:pi|qwen)\s+)?mcp(?:\s|$)/m.test(help.stdout)) throw Error(`installed ${target} CLI does not expose MCP registration; no registration command was run`);
|
|
304
|
+
if (help.status !== 0 || !/^\s+(?:(?:pi|qwen|gemini)\s+)?mcp(?:\s|$)/m.test(help.stdout)) throw Error(`installed ${target} CLI does not expose MCP registration; no registration command was run`);
|
|
270
305
|
}
|
|
271
306
|
function native(argv: readonly string[], env: Env, cwd: string): void {
|
|
272
307
|
const result = spawnSync(argv[0]!, argv.slice(1), { cwd, env: { ...env }, encoding: "utf8", timeout: 10_000, maxBuffer: MAX, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -303,6 +338,8 @@ function restoreChange(value: Receipt, change: Change, index: number, live: Imag
|
|
|
303
338
|
}
|
|
304
339
|
function prepare(c: ReturnType<typeof context>, target: SetupTarget, options: SetupContext): Prepared {
|
|
305
340
|
if (target === "jcode") assertJcodeRuntime(c);
|
|
341
|
+
if (target === "opencode") assertOpencodeLayout(c, "apply");
|
|
342
|
+
if (target === "gemini") assertGeminiHome(c.env);
|
|
306
343
|
const prior = receipt(c, target, false), expected = c.paths[target].map(path => read(path));
|
|
307
344
|
const restored = prior ? prior.changes.map((change, index) => restoreChange(prior, change, index, expected[index]!)) : expected;
|
|
308
345
|
if (prior?.phase === "applied" && prior.intent === intent(c, target) && prior.changes.every((change, index) => managedAfter(target, index, change.before, change.after, expected[index]!))) return { value: prior, expected, unchanged: true };
|
|
@@ -319,7 +356,7 @@ function prepare(c: ReturnType<typeof context>, target: SetupTarget, options: Se
|
|
|
319
356
|
const text = expected[0]!.bytes === null ? "" : Buffer.from(expected[0]!.bytes!, "base64").toString("utf8");
|
|
320
357
|
const document = target === "codex" ? toml(text) : json(expected[0]!);
|
|
321
358
|
const servers = document[target === "codex" ? "mcp_servers" : "mcpServers"];
|
|
322
|
-
if (servers && Object.hasOwn(record(servers), "amem")) (options.native ?? native)([target, "mcp", "remove", ...(target === "claude" || target === "qwen" ? ["--scope", "user"] : []), "amem"], env, staging);
|
|
359
|
+
if (servers && Object.hasOwn(record(servers), "amem")) (options.native ?? native)(target === "gemini" ? [...GEMINI_REMOVE] : [target, "mcp", "remove", ...(target === "claude" || target === "qwen" ? ["--scope", "user"] : []), "amem"], env, staging);
|
|
323
360
|
(options.native ?? native)(argv, env, staging);
|
|
324
361
|
changes[0]!.after = { ...read(staged.paths[target][0]!), mode: changes[0]!.before.mode };
|
|
325
362
|
if (changes[0]!.after.bytes === null) throw Error(`${target} native registration produced no configuration`);
|
|
@@ -361,6 +398,9 @@ function prepare(c: ReturnType<typeof context>, target: SetupTarget, options: Se
|
|
|
361
398
|
} else if (target === "jcode") {
|
|
362
399
|
// Shared native pools use the daemon cwd; amem must bind the calling session's project.
|
|
363
400
|
changes[0]!.after = putMcpRecord(target, changes[0]!.before, { command: c.bun, args: ["--smol", c.entry, "mcp"], shared: false });
|
|
401
|
+
} else if (target === "opencode") {
|
|
402
|
+
// `opencode mcp add` is interactive for local servers and has no remove; edit the owned entry directly.
|
|
403
|
+
changes[0]!.after = putMcpRecord(target, changes[0]!.before, opencodeServer(c.bun, c.entry));
|
|
364
404
|
} else {
|
|
365
405
|
const document = json(changes[0]!.before), servers = document.mcpServers === undefined ? {} : record(document.mcpServers);
|
|
366
406
|
servers.amem = { command: c.bun, args: ["--smol", c.entry, "mcp"] }; document.mcpServers = servers;
|