// l1-mode.mts —— L1 提取模式(code / chat)的读写入口(查看器写盘桥 + 人工运维)
//
// 包装 `memory/l1/config.ts` 的 `loadL1Config` / `saveL1Mode`。为什么不让桥自己改配置:
// 配置是 **JSONC**(浏览器 parse 不了)且 `saveAppSection` 是**整节替换**(`appProps.l1 = value`,
// 不做深合并)—— 外部编辑器很容易把同节的 `valueCheck` 一起冲掉。`saveL1Mode` 内部已经做了
// "先读旧节再合并 mode"的处理(见其注释),所以这条路必须走它。
//
// 本脚本是这批 CLI 里**唯一不要求 `--apply`** 的写操作,理由与 `task-state` 不默认备份同款:
// 动作可逆(改回去即可)、幂等、不改正文,而且是界面上的开关 —— 要求再多一道 flag 只是让
// 按钮的实现变复杂,不增加任何安全。
//
// 用法:
// node --experimental-strip-types scripts/l1-admin/l1-mode.mts [--profile
] # 读当前模式
// node --experimental-strip-types scripts/l1-admin/l1-mode.mts --set chat|code [--profile ] # 写
import { loadL1Config, saveL1Mode, type L1Mode } from "../../memory/l1/config.ts";
import { fail, finish, flagValue, profileArg, run, usageError } from "./viewer-cli.mts";
const USAGE = "node --experimental-strip-types scripts/l1-admin/l1-mode.mts [--set chat|code] [--profile ]";
run(() => {
const profilePath = profileArg();
if (!profilePath) usageError(USAGE);
const setRaw = flagValue("--set");
if (setRaw === undefined) {
finish({ success: true, mode: loadL1Config(profilePath).mode });
}
if (setRaw !== "chat" && setRaw !== "code") {
fail(`mode 只能是 chat / code,收到 ${JSON.stringify(setRaw)}`);
}
const before = loadL1Config(profilePath).mode;
saveL1Mode(profilePath, setRaw as L1Mode);
// 写完回读:`saveAppSection` 的整节替换语义下,"以为写了其实被别的字段覆盖"是真实风险,
// 所以判成功以回读为准,而不是以"函数没抛异常"为准。
const after = loadL1Config(profilePath).mode;
if (after !== setRaw) {
fail(`已调用 saveL1Mode 但回读仍是 ${after}(期望 ${setRaw})—— 配置节可能被并发写入覆盖`);
}
finish({ success: true, mode: after, before, changed: before !== after, note: "已写入 application-propertis.json 的 l1.mode" });
});