+16
-3
@@ -22,5 +22,7 @@ import { execFile } from "node:child_process"; | ||
| ]; | ||
| /** Parse `setup` flags: `--dry-run`, `--remote <url>`, `--token <token>`, `--client <id>...`. */ | ||
| /** Parse `setup` flags: `--dry-run`, `--remote <url>`, `--token <token>`, `--client <id>...`, | ||
| * `--force`. */ | ||
| export function parseSetupArgs(argv, env) { | ||
| let dryRun = false; | ||
| let force = false; | ||
| let url; | ||
@@ -34,2 +36,5 @@ let token = env.JAMGATE_TOKEN; | ||
| } | ||
| else if (arg === "--force") { | ||
| force = true; | ||
| } | ||
| else if (arg === "--remote") { | ||
@@ -70,3 +75,3 @@ url = argv[++i]; | ||
| } | ||
| return { mode, url, token, dryRun, only: only.length ? only : undefined }; | ||
| return { mode, url, token, dryRun, force, only: only.length ? only : undefined }; | ||
| } | ||
@@ -113,2 +118,3 @@ /** The real `claude` CLI adapter. `isAvailable` probes `claude --version`; `add` shells out to | ||
| "already-configured": "•", | ||
| preserved: "•", | ||
| "not-found": "–", | ||
@@ -127,2 +133,4 @@ skipped: "–", | ||
| return "already configured"; | ||
| case "preserved": | ||
| return "left as-is"; | ||
| case "not-found": | ||
@@ -142,3 +150,7 @@ return "not found"; | ||
| let line = ` ${SYMBOL[r.outcome]} ${r.label.padEnd(15)} ${describe(r)}`; | ||
| if (r.detail && (r.outcome === "not-found" || r.outcome === "skipped" || r.outcome === "error")) { | ||
| if (r.detail && | ||
| (r.outcome === "not-found" || | ||
| r.outcome === "skipped" || | ||
| r.outcome === "preserved" || | ||
| r.outcome === "error")) { | ||
| line += ` — ${r.detail}`; | ||
@@ -198,2 +210,3 @@ } | ||
| only: parsed.only, | ||
| force: parsed.force, | ||
| platform: deps.platform, | ||
@@ -200,0 +213,0 @@ env, |
+28
-1
@@ -21,6 +21,16 @@ import { promises as fs } from "node:fs"; | ||
| * `url`, or a client-specific alias (`httpUrl` for Gemini, `serverUrl` for Windsurf) — whereas | ||
| * stdio entries carry `command`. Used only for status reporting. */ | ||
| * stdio entries carry `command`. Used for status reporting and the downgrade guard (D-047). */ | ||
| function isRemoteEntry(entry) { | ||
| return "url" in entry || "httpUrl" in entry || "serverUrl" in entry; | ||
| } | ||
| /** Extract the currently-wired jamgate entry from a parsed config, if any (else undefined). */ | ||
| function currentEntry(existing, containerKey, serverKey) { | ||
| if (!existing || typeof existing !== "object") | ||
| return undefined; | ||
| const container = existing[containerKey]; | ||
| if (!container || typeof container !== "object") | ||
| return undefined; | ||
| const entry = container[serverKey]; | ||
| return entry && typeof entry === "object" ? entry : undefined; | ||
| } | ||
| async function pathExists(p) { | ||
@@ -117,2 +127,18 @@ try { | ||
| } | ||
| // Transport-downgrade guard (D-047): if this run would overwrite an existing REMOTE (HTTP) | ||
| // wiring with a local stdio one, don't do it silently — a plain `jamgate setup` (stdio default) | ||
| // must not un-do a deliberate `--remote` install and re-fragment the user's memory. Preserve | ||
| // the existing entry and report it, unless `--force` is set. The reverse direction (stdio entry, | ||
| // `--remote` run) is a genuine upgrade the user is explicitly requesting via the flag, so it | ||
| // falls through to the normal update path below. | ||
| const wired = currentEntry(existing, client.containerKey, client.serverKey); | ||
| if (wired && !opts.force && isRemoteEntry(wired) && opts.mode !== "remote") { | ||
| return { | ||
| ...base, | ||
| transport: "remote", | ||
| outcome: "preserved", | ||
| detail: "currently remote — re-run with --remote <url> to update it, or --force to overwrite " + | ||
| "with a local stdio entry", | ||
| }; | ||
| } | ||
| const plan = planMerge(existing, client.serverKey, entry, client.containerKey); | ||
@@ -166,2 +192,3 @@ if (plan.status === "already-configured") { | ||
| claudeCli: opts.claudeCli, | ||
| force: opts.force, | ||
| }, platform, env)); | ||
@@ -168,0 +195,0 @@ } |
+1
-1
@@ -9,2 +9,2 @@ /** | ||
| */ | ||
| export const VERSION = "0.9.1"; | ||
| export const VERSION = "0.9.2"; |
+1
-1
| { | ||
| "name": "jamgate", | ||
| "version": "0.9.1", | ||
| "version": "0.9.2", | ||
| "mcpName": "io.github.amirj4m/jamgate", | ||
@@ -5,0 +5,0 @@ "description": "A neutral, cross-agent memory quality gate for AI agents, delivered as an MCP server — a gate, not a store.", |
+4
-1
@@ -104,3 +104,5 @@ # Jamgate | ||
| server entry but its own, and it backs up each config file to `<file>.jamgate-backup` before | ||
| writing. Useful flags: | ||
| writing. A plain `setup` (local stdio) will also **never silently overwrite a remote (`--remote`) | ||
| wiring** — it leaves that client as-is and tells you so; pass `--force` to downgrade it on | ||
| purpose. Useful flags: | ||
@@ -110,2 +112,3 @@ ```bash | ||
| npx jamgate setup --remote https://you/mcp --token … # wire HTTP transport (see Remote mode) | ||
| npx jamgate setup --force # overwrite even a remote wiring with local stdio | ||
| npx jamgate status # show which clients are wired + where the store lives | ||
@@ -112,0 +115,0 @@ ``` |
264722
0.81%4706
0.86%734
0.41%