@mu-cabin/muc-cli 0.6.0 → 0.6.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 +6 -0
- package/REFERENCE.md +1 -1
- package/dist/muc.mjs +217 -73
- package/package.json +1 -1
- package/skills/muc/references/profiles-and-lifecycle.md +3 -1
package/README.md
CHANGED
|
@@ -32,12 +32,18 @@ JSON
|
|
|
32
32
|
|
|
33
33
|
消费程序保存最后一条成功处理的事件游标,重连时通过 `cursor` 继续。`events.list` 用于持久化事件补取。后台离线期间的历史消息通过 `messages.list/search` 查询,不能把本地事件为空当作离线期间没有消息。
|
|
34
34
|
|
|
35
|
+
启动配置错误会直接返回原因,不再仅等待超时。`daemon logs` 返回安全的错误代码、说明和处理提示,不输出原始异常或凭据。
|
|
36
|
+
|
|
35
37
|
普通命令按需启动 daemon;`daemon status/logs/stop` 不会启动后台。升级可执行文件前先停止相应配置根的 daemon,更新后再启动。被踢下线或主动停止的 profile 保留停止状态,须明确登录或 `runtime resume` 才能恢复。
|
|
36
38
|
|
|
37
39
|
完整参数见 [REFERENCE.md](REFERENCE.md),agent 操作说明见 [skills/muc/SKILL.md](skills/muc/SKILL.md)。文件消息默认不附加尾注;文字和图片仍遵循原有尾注设置。发送结果不明时查询原 delivery/idempotency key,不自动重发。
|
|
38
40
|
|
|
39
41
|
## 从旧 CLI 迁移
|
|
40
42
|
|
|
43
|
+
0.6.1 支持单账号平铺配置和 `activeProfile + profiles` 多账号配置;后者保留全部 profile,并将默认账号映射为 `selectedProfileId`。不同租户、缺失默认账号或无法解密的记住密码会拒绝迁移并保留原文件。
|
|
44
|
+
|
|
45
|
+
迁移后会在启动时登记消息数据库中的账号,确保群信息和消息同步可正常落库。
|
|
46
|
+
|
|
41
47
|
首次启动会识别当前配置根中的旧版 `config.json` / `credentials.json`,保留设备、插槽、账号、token、记住密码和消息尾注,并生成私有的 `*.v1.backup.json` 备份。迁移只处理所选配置根;旧后台仍运行时会拒绝迁移。旧消息日志、assistant 配置和旧版命令不迁入。
|
|
42
48
|
|
|
43
49
|
旧 `send/history/listen/assistant/daemon-*` 命令改用新版 `exec` 和 `daemon` 命令,Skill 合并为 `skills/muc`。此版本不提供 TUI、机器人执行后端和独立升级控制面。不要直接将旧二进制指向迁移后的目录;回退须停止新后台后在独立目录恢复旧备份及原 `remember.salt`。
|
package/REFERENCE.md
CHANGED
package/dist/muc.mjs
CHANGED
|
@@ -11956,26 +11956,29 @@ function deriveKey(root) {
|
|
|
11956
11956
|
maxmem: 64 * 1024 * 1024
|
|
11957
11957
|
});
|
|
11958
11958
|
}
|
|
11959
|
-
function encryptRemembered(plaintext, root) {
|
|
11959
|
+
function encryptRemembered(plaintext, root, profile) {
|
|
11960
11960
|
if (!plaintext) throw new Error("encryptRemembered: empty plaintext");
|
|
11961
11961
|
const key = deriveKey(root);
|
|
11962
11962
|
const iv = randomBytes2(IV_BYTES);
|
|
11963
11963
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
11964
|
+
if (profile !== void 0) cipher.setAAD(Buffer.from(profile, "utf8"));
|
|
11964
11965
|
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
11965
11966
|
const tag = cipher.getAuthTag();
|
|
11966
|
-
return
|
|
11967
|
+
return `${profile === void 0 ? "v1" : "v2"}.${iv.toString("base64")}.${tag.toString("base64")}.${ct.toString("base64")}`;
|
|
11967
11968
|
}
|
|
11968
|
-
function decryptRemembered(blob, root) {
|
|
11969
|
+
function decryptRemembered(blob, root, profile) {
|
|
11969
11970
|
try {
|
|
11970
11971
|
if (typeof blob !== "string") return null;
|
|
11971
11972
|
const parts = blob.split(".");
|
|
11972
|
-
if (parts.length !== 4 || parts[0]
|
|
11973
|
+
if (parts.length !== 4 || !["v1", "v2"].includes(parts[0]) || parts[0] === "v2" && profile === void 0) return null;
|
|
11974
|
+
if (!existsSync(saltFile(root))) return null;
|
|
11973
11975
|
const iv = Buffer.from(parts[1], "base64");
|
|
11974
11976
|
const tag = Buffer.from(parts[2], "base64");
|
|
11975
11977
|
const ct = Buffer.from(parts[3], "base64");
|
|
11976
11978
|
if (iv.length !== IV_BYTES || tag.length !== TAG_BYTES) return null;
|
|
11977
11979
|
const key = deriveKey(root);
|
|
11978
11980
|
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
11981
|
+
if (parts[0] === "v2") decipher.setAAD(Buffer.from(profile, "utf8"));
|
|
11979
11982
|
decipher.setAuthTag(tag);
|
|
11980
11983
|
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
11981
11984
|
return pt.toString("utf8");
|
|
@@ -12117,6 +12120,103 @@ var LegacyCredentials = external_exports.object({
|
|
|
12117
12120
|
rememberedPwd: external_exports.string().optional()
|
|
12118
12121
|
});
|
|
12119
12122
|
var Journal = external_exports.object({ config: NativeProfileDocumentSchema, credentials: CredentialDocumentSchema }).strict();
|
|
12123
|
+
var MultiProfile = LegacyConfig.omit({
|
|
12124
|
+
schemaVersion: true,
|
|
12125
|
+
appKey: true,
|
|
12126
|
+
appId: true,
|
|
12127
|
+
signSecretRest: true,
|
|
12128
|
+
signSecretSecure: true,
|
|
12129
|
+
pwdDesKey: true
|
|
12130
|
+
}).extend({
|
|
12131
|
+
account: external_exports.string().min(1),
|
|
12132
|
+
generation: external_exports.string().uuid(),
|
|
12133
|
+
deviceId: LegacyCredentials.shape.deviceId,
|
|
12134
|
+
deviceName: LegacyCredentials.shape.deviceName,
|
|
12135
|
+
identity: external_exports.object({ uid: external_exports.string().optional(), employeeId: external_exports.string().optional(), name: external_exports.string().optional() }).optional()
|
|
12136
|
+
});
|
|
12137
|
+
var MultiConfig = external_exports.object({
|
|
12138
|
+
schemaVersion: external_exports.literal(1),
|
|
12139
|
+
activeProfile: NativeProfileIdSchema,
|
|
12140
|
+
profiles: external_exports.record(NativeProfileIdSchema, MultiProfile)
|
|
12141
|
+
});
|
|
12142
|
+
var MultiCredentials = external_exports.object({ schemaVersion: external_exports.literal(1), profiles: external_exports.record(NativeProfileIdSchema, external_exports.object({
|
|
12143
|
+
tenant: LegacyConfig.pick({ appKey: true, appId: true, signSecretRest: true, signSecretSecure: true, pwdDesKey: true, savedAt: true }).optional(),
|
|
12144
|
+
session: LegacyCredentials.omit({ uid: true, empId: true, name: true, deviceId: true, deviceName: true }).extend({
|
|
12145
|
+
appVersion: external_exports.string().min(1),
|
|
12146
|
+
rememberedPassword: external_exports.string().min(1).optional()
|
|
12147
|
+
}).optional()
|
|
12148
|
+
})) });
|
|
12149
|
+
function convertMulti(root, raw, secrets) {
|
|
12150
|
+
const old = MultiConfig.parse(raw);
|
|
12151
|
+
const creds = MultiCredentials.parse(secrets ?? { schemaVersion: 1, profiles: {} });
|
|
12152
|
+
if (!Object.hasOwn(old.profiles, old.activeProfile)) throw new CliConfigError("CONFIG_INVALID", "Legacy default profile does not exist. Original files are unchanged.");
|
|
12153
|
+
if (Object.keys(creds.profiles).some((id) => !Object.hasOwn(old.profiles, id))) throw new CliConfigError("CONFIG_INVALID", "Legacy credentials contain an unknown profile. Original files are unchanged.");
|
|
12154
|
+
const profiles = {};
|
|
12155
|
+
const credentials = { schemaVersion: 2, profiles: {} };
|
|
12156
|
+
for (const [id, oldProfile] of Object.entries(old.profiles)) {
|
|
12157
|
+
const profile = createNativeProfile({
|
|
12158
|
+
id,
|
|
12159
|
+
endpoints: {
|
|
12160
|
+
restOrigin: new URL(oldProfile.restHost).origin,
|
|
12161
|
+
imHost: oldProfile.imHost,
|
|
12162
|
+
imPort: oldProfile.imPort,
|
|
12163
|
+
fileHost: oldProfile.fileHost,
|
|
12164
|
+
filePort: oldProfile.filePort
|
|
12165
|
+
},
|
|
12166
|
+
slot: slotFromChatOs(oldProfile.defaultChatOs ?? "iPad"),
|
|
12167
|
+
cliSignature: oldProfile.messageTail === void 0 ? { mode: "builtin" } : oldProfile.messageTail === "" ? { mode: "disabled" } : { mode: "custom", text: oldProfile.messageTail }
|
|
12168
|
+
}, { generation: oldProfile.generation, now: () => oldProfile.savedAt });
|
|
12169
|
+
profile.device = { id: oldProfile.deviceId, name: oldProfile.deviceName };
|
|
12170
|
+
profile.identity = {
|
|
12171
|
+
account: oldProfile.account.toUpperCase(),
|
|
12172
|
+
...oldProfile.identity?.employeeId ? { employeeId: oldProfile.identity.employeeId } : {},
|
|
12173
|
+
...oldProfile.identity?.name ? { displayName: oldProfile.identity.name } : {}
|
|
12174
|
+
};
|
|
12175
|
+
profiles[id] = NativeProfileSchema.parse(profile);
|
|
12176
|
+
const secret = creds.profiles[id];
|
|
12177
|
+
if (secret?.tenant) {
|
|
12178
|
+
const t = secret.tenant;
|
|
12179
|
+
const tenant = NativeTenantSecretSchema.parse({
|
|
12180
|
+
schemaVersion: 2,
|
|
12181
|
+
appKey: t.appKey,
|
|
12182
|
+
appId: t.appId,
|
|
12183
|
+
signSecretRest: t.signSecretRest,
|
|
12184
|
+
signSecretSecure: t.signSecretSecure,
|
|
12185
|
+
passwordKey: t.pwdDesKey,
|
|
12186
|
+
savedAtMs: t.savedAt
|
|
12187
|
+
});
|
|
12188
|
+
const { savedAtMs: _, ...identity } = tenant;
|
|
12189
|
+
const { savedAtMs: __, ...existing } = credentials.tenant ?? tenant;
|
|
12190
|
+
if (JSON.stringify(identity) !== JSON.stringify(existing)) throw new CliConfigError("CONFIG_INVALID", "Legacy profiles use different tenants; migrate them to separate config roots. Original files are unchanged.");
|
|
12191
|
+
credentials.tenant = tenant;
|
|
12192
|
+
}
|
|
12193
|
+
if (secret?.session) {
|
|
12194
|
+
const s = secret.session;
|
|
12195
|
+
credentials.profiles[profile.storageKey] = {
|
|
12196
|
+
session: NativeSessionSecretSchema.parse({
|
|
12197
|
+
schemaVersion: 2,
|
|
12198
|
+
accessToken: s.accessToken,
|
|
12199
|
+
tokenPassword: s.tokenPwd,
|
|
12200
|
+
restPlatform: s.restPlatform ?? 5,
|
|
12201
|
+
appVersion: s.appVersion,
|
|
12202
|
+
savedAtMs: s.savedAt,
|
|
12203
|
+
accessTokenExpireAtMs: s.accessTokenExpireAt,
|
|
12204
|
+
tokenPasswordExpireAtMs: s.tokenPwdExpireAt
|
|
12205
|
+
}),
|
|
12206
|
+
...s.rememberedPwd ? { rememberedPwd: s.rememberedPwd } : {}
|
|
12207
|
+
};
|
|
12208
|
+
}
|
|
12209
|
+
}
|
|
12210
|
+
const next = Journal.parse({ config: { schemaVersion: 2, selectedProfileId: old.activeProfile, profiles }, credentials });
|
|
12211
|
+
for (const [id, secret] of Object.entries(creds.profiles)) {
|
|
12212
|
+
if (secret.session?.rememberedPassword) {
|
|
12213
|
+
const password = decryptRemembered(secret.session.rememberedPassword, root, id);
|
|
12214
|
+
if (password === null) throw new CliConfigError("CONFIG_INVALID", "Legacy remembered password could not be decrypted. Preserve the original remember.salt and account profile; original configuration files are unchanged.");
|
|
12215
|
+
next.credentials.profiles[profiles[id].storageKey].rememberedPwd = encryptRemembered(password, root);
|
|
12216
|
+
}
|
|
12217
|
+
}
|
|
12218
|
+
return next;
|
|
12219
|
+
}
|
|
12120
12220
|
async function importLegacyStorage(root) {
|
|
12121
12221
|
try {
|
|
12122
12222
|
const journalPath = join6(root, ".migration-v2.json");
|
|
@@ -12128,47 +12228,8 @@ async function importLegacyStorage(root) {
|
|
|
12128
12228
|
await commit(root, Journal.parse(pending));
|
|
12129
12229
|
return true;
|
|
12130
12230
|
}
|
|
12131
|
-
const legacy = LegacyConfig.parse(config);
|
|
12132
12231
|
const rawCredentials = await readPrivateJson(join6(root, "credentials.json"));
|
|
12133
|
-
const
|
|
12134
|
-
const profile = createNativeProfile({
|
|
12135
|
-
id: "default",
|
|
12136
|
-
endpoints: { restOrigin: new URL(legacy.restHost).origin, imHost: legacy.imHost, imPort: legacy.imPort, fileHost: legacy.fileHost, filePort: legacy.filePort },
|
|
12137
|
-
slot: slotFromChatOs(legacy.defaultChatOs ?? "iPad"),
|
|
12138
|
-
cliSignature: legacy.messageTail === void 0 ? { mode: "builtin" } : legacy.messageTail === "" ? { mode: "disabled" } : { mode: "custom", text: legacy.messageTail }
|
|
12139
|
-
});
|
|
12140
|
-
if (credentials) {
|
|
12141
|
-
profile.device = { id: credentials.deviceId, name: credentials.deviceName };
|
|
12142
|
-
profile.identity = { account: credentials.uid.toUpperCase(), ...credentials.empId ? { employeeId: credentials.empId } : {}, ...credentials.name ? { displayName: credentials.name } : {} };
|
|
12143
|
-
}
|
|
12144
|
-
const tenant = NativeTenantSecretSchema.parse({
|
|
12145
|
-
schemaVersion: 2,
|
|
12146
|
-
appKey: legacy.appKey,
|
|
12147
|
-
appId: legacy.appId,
|
|
12148
|
-
signSecretRest: legacy.signSecretRest,
|
|
12149
|
-
signSecretSecure: legacy.signSecretSecure,
|
|
12150
|
-
...legacy.pwdDesKey ? { passwordKey: legacy.pwdDesKey } : {},
|
|
12151
|
-
savedAtMs: legacy.savedAt
|
|
12152
|
-
});
|
|
12153
|
-
const restPlatform = credentials?.restPlatform ?? 5;
|
|
12154
|
-
const next = Journal.parse({
|
|
12155
|
-
config: { schemaVersion: 2, selectedProfileId: profile.id, profiles: { [profile.id]: NativeProfileSchema.parse(profile) } },
|
|
12156
|
-
credentials: { schemaVersion: 2, tenant, profiles: credentials ? {
|
|
12157
|
-
[profile.storageKey]: {
|
|
12158
|
-
session: NativeSessionSecretSchema.parse({
|
|
12159
|
-
schemaVersion: 2,
|
|
12160
|
-
accessToken: credentials.accessToken,
|
|
12161
|
-
tokenPassword: credentials.tokenPwd,
|
|
12162
|
-
restPlatform,
|
|
12163
|
-
appVersion: restPlatform === 1 ? "3.3.0301" : "3.3.1-250925001",
|
|
12164
|
-
savedAtMs: credentials.savedAt,
|
|
12165
|
-
accessTokenExpireAtMs: credentials.accessTokenExpireAt,
|
|
12166
|
-
tokenPasswordExpireAtMs: credentials.tokenPwdExpireAt
|
|
12167
|
-
}),
|
|
12168
|
-
...credentials.rememberedPwd ? { rememberedPwd: credentials.rememberedPwd } : {}
|
|
12169
|
-
}
|
|
12170
|
-
} : {} }
|
|
12171
|
-
});
|
|
12232
|
+
const next = config && typeof config === "object" && "profiles" in config ? convertMulti(root, config, rawCredentials) : convertSingle(config, rawCredentials);
|
|
12172
12233
|
await backupOnce(root, "config.v1.backup.json", config);
|
|
12173
12234
|
if (rawCredentials !== void 0) await backupOnce(root, "credentials.v1.backup.json", rawCredentials);
|
|
12174
12235
|
await writePrivateJson(root, ".migration-v2.json", next);
|
|
@@ -12176,9 +12237,52 @@ async function importLegacyStorage(root) {
|
|
|
12176
12237
|
return true;
|
|
12177
12238
|
} catch (error) {
|
|
12178
12239
|
if (error instanceof CliConfigError) throw error;
|
|
12179
|
-
throw new CliConfigError("CONFIG_INVALID", "Legacy configuration could not be imported. Original backups are retained; inspect the local files privately.");
|
|
12240
|
+
throw new CliConfigError("CONFIG_INVALID", "Legacy configuration could not be imported. Original files or migration backups are retained; inspect the local files privately.");
|
|
12180
12241
|
}
|
|
12181
12242
|
}
|
|
12243
|
+
function convertSingle(config, rawCredentials) {
|
|
12244
|
+
const legacy = LegacyConfig.parse(config);
|
|
12245
|
+
const credentials = rawCredentials === void 0 ? void 0 : LegacyCredentials.parse(rawCredentials);
|
|
12246
|
+
const profile = createNativeProfile({
|
|
12247
|
+
id: "default",
|
|
12248
|
+
endpoints: { restOrigin: new URL(legacy.restHost).origin, imHost: legacy.imHost, imPort: legacy.imPort, fileHost: legacy.fileHost, filePort: legacy.filePort },
|
|
12249
|
+
slot: slotFromChatOs(legacy.defaultChatOs ?? "iPad"),
|
|
12250
|
+
cliSignature: legacy.messageTail === void 0 ? { mode: "builtin" } : legacy.messageTail === "" ? { mode: "disabled" } : { mode: "custom", text: legacy.messageTail }
|
|
12251
|
+
});
|
|
12252
|
+
if (credentials) {
|
|
12253
|
+
profile.device = { id: credentials.deviceId, name: credentials.deviceName };
|
|
12254
|
+
profile.identity = { account: credentials.uid.toUpperCase(), ...credentials.empId ? { employeeId: credentials.empId } : {}, ...credentials.name ? { displayName: credentials.name } : {} };
|
|
12255
|
+
}
|
|
12256
|
+
const tenant = NativeTenantSecretSchema.parse({
|
|
12257
|
+
schemaVersion: 2,
|
|
12258
|
+
appKey: legacy.appKey,
|
|
12259
|
+
appId: legacy.appId,
|
|
12260
|
+
signSecretRest: legacy.signSecretRest,
|
|
12261
|
+
signSecretSecure: legacy.signSecretSecure,
|
|
12262
|
+
...legacy.pwdDesKey ? { passwordKey: legacy.pwdDesKey } : {},
|
|
12263
|
+
savedAtMs: legacy.savedAt
|
|
12264
|
+
});
|
|
12265
|
+
const restPlatform = credentials?.restPlatform ?? 5;
|
|
12266
|
+
const next = Journal.parse({
|
|
12267
|
+
config: { schemaVersion: 2, selectedProfileId: profile.id, profiles: { [profile.id]: NativeProfileSchema.parse(profile) } },
|
|
12268
|
+
credentials: { schemaVersion: 2, tenant, profiles: credentials ? {
|
|
12269
|
+
[profile.storageKey]: {
|
|
12270
|
+
session: NativeSessionSecretSchema.parse({
|
|
12271
|
+
schemaVersion: 2,
|
|
12272
|
+
accessToken: credentials.accessToken,
|
|
12273
|
+
tokenPassword: credentials.tokenPwd,
|
|
12274
|
+
restPlatform,
|
|
12275
|
+
appVersion: restPlatform === 1 ? "3.3.0301" : "3.3.1-250925001",
|
|
12276
|
+
savedAtMs: credentials.savedAt,
|
|
12277
|
+
accessTokenExpireAtMs: credentials.accessTokenExpireAt,
|
|
12278
|
+
tokenPasswordExpireAtMs: credentials.tokenPwdExpireAt
|
|
12279
|
+
}),
|
|
12280
|
+
...credentials.rememberedPwd ? { rememberedPwd: credentials.rememberedPwd } : {}
|
|
12281
|
+
}
|
|
12282
|
+
} : {} }
|
|
12283
|
+
});
|
|
12284
|
+
return next;
|
|
12285
|
+
}
|
|
12182
12286
|
async function backupOnce(root, name, value) {
|
|
12183
12287
|
if (await readPrivateJson(join6(root, name)) === void 0) await writePrivateJson(root, name, value);
|
|
12184
12288
|
}
|
|
@@ -12212,7 +12316,7 @@ async function assertLegacyStopped(root) {
|
|
|
12212
12316
|
// package.json
|
|
12213
12317
|
var package_default = {
|
|
12214
12318
|
name: "@mu-cabin/muc-cli",
|
|
12215
|
-
version: "0.6.
|
|
12319
|
+
version: "0.6.1",
|
|
12216
12320
|
description: "Standalone agent-first MUC CLI with an embedded background daemon",
|
|
12217
12321
|
license: "MIT",
|
|
12218
12322
|
type: "module",
|
|
@@ -22832,6 +22936,14 @@ var NativeDaemonCore = class _NativeDaemonCore {
|
|
|
22832
22936
|
state,
|
|
22833
22937
|
...delivery === void 0 ? {} : { delivery }
|
|
22834
22938
|
});
|
|
22939
|
+
for (const profile of await core.profiles.list()) {
|
|
22940
|
+
await state.ensureProfile({
|
|
22941
|
+
profileId: profile.id,
|
|
22942
|
+
...profile.identity?.account === void 0 ? {} : { accountId: profile.identity.account },
|
|
22943
|
+
...profile.identity?.displayName === void 0 ? {} : { displayName: profile.identity.displayName },
|
|
22944
|
+
updatedAtMs: profile.updatedAtMs
|
|
22945
|
+
});
|
|
22946
|
+
}
|
|
22835
22947
|
core.#ownsState = ownsState;
|
|
22836
22948
|
core.#ownsDelivery = ownsDelivery;
|
|
22837
22949
|
return core;
|
|
@@ -30076,6 +30188,8 @@ async function ensureDaemon(options) {
|
|
|
30076
30188
|
} catch (error) {
|
|
30077
30189
|
if (error.code !== "ENOENT") throw error;
|
|
30078
30190
|
}
|
|
30191
|
+
let spawned;
|
|
30192
|
+
let spawnFailed = false;
|
|
30079
30193
|
if (!existingOwner) {
|
|
30080
30194
|
await ensurePrivateDirectory(join15(root, "run"));
|
|
30081
30195
|
const logPath = join15(root, "run", "daemon.log");
|
|
@@ -30092,7 +30206,10 @@ async function ensureDaemon(options) {
|
|
|
30092
30206
|
"daemon",
|
|
30093
30207
|
"run"
|
|
30094
30208
|
], { detached: true, stdio: ["ignore", log.fd, log.fd], windowsHide: true });
|
|
30095
|
-
|
|
30209
|
+
spawned = child;
|
|
30210
|
+
child.on("error", () => {
|
|
30211
|
+
spawnFailed = true;
|
|
30212
|
+
});
|
|
30096
30213
|
child.unref();
|
|
30097
30214
|
} finally {
|
|
30098
30215
|
await log.close();
|
|
@@ -30106,6 +30223,12 @@ async function ensureDaemon(options) {
|
|
|
30106
30223
|
} catch (error) {
|
|
30107
30224
|
if (!(error instanceof DaemonUnavailableError)) throw error;
|
|
30108
30225
|
}
|
|
30226
|
+
if (spawnFailed || spawned && (spawned.exitCode !== null || spawned.signalCode !== null)) {
|
|
30227
|
+
const entries = await readDiagnosticLog(join15(root, "run", "daemon.log"));
|
|
30228
|
+
const failure = entries.findLast((e) => e.pid === spawned?.pid && e.event === "start-failed");
|
|
30229
|
+
if (failure?.error && failure.error.code !== "CLI_UNEXPECTED_FAILURE") throw new CliConfigError(failure.error.code, failure.error.message, failure.error.code === "CONFIG_INVALID" ? 7 : 1);
|
|
30230
|
+
if (spawnFailed) throw new CliConfigError("DAEMON_START_FAILED", "The daemon process could not be spawned. Check the installed Node executable.", 1);
|
|
30231
|
+
}
|
|
30109
30232
|
await delay(80);
|
|
30110
30233
|
}
|
|
30111
30234
|
throw new CliConfigError("DAEMON_START_FAILED", "The daemon did not become ready. Inspect muc daemon logs.", 1);
|
|
@@ -30132,29 +30255,7 @@ async function runDaemonCommand(command2, io) {
|
|
|
30132
30255
|
}
|
|
30133
30256
|
if (command2.action === "run") return runForegroundDaemon(root, io);
|
|
30134
30257
|
if (command2.action === "logs") {
|
|
30135
|
-
|
|
30136
|
-
await assertPrivateRegularOrMissing(path);
|
|
30137
|
-
const handle = await open9(path, "r").catch((e) => {
|
|
30138
|
-
if (e.code === "ENOENT") return void 0;
|
|
30139
|
-
throw e;
|
|
30140
|
-
});
|
|
30141
|
-
if (!handle) return { entries: [] };
|
|
30142
|
-
try {
|
|
30143
|
-
const size = (await handle.stat()).size;
|
|
30144
|
-
const buffer = Buffer.alloc(Math.min(size, 64 * 1024));
|
|
30145
|
-
await handle.read(buffer, 0, buffer.length, Math.max(0, size - buffer.length));
|
|
30146
|
-
const entries = buffer.toString("utf8").split("\n").flatMap((line) => {
|
|
30147
|
-
try {
|
|
30148
|
-
const v = LogSchema.safeParse(JSON.parse(line));
|
|
30149
|
-
return v.success ? [v.data] : [];
|
|
30150
|
-
} catch {
|
|
30151
|
-
return [];
|
|
30152
|
-
}
|
|
30153
|
-
}).slice(-100);
|
|
30154
|
-
return { entries };
|
|
30155
|
-
} finally {
|
|
30156
|
-
await handle.close();
|
|
30157
|
-
}
|
|
30258
|
+
return { entries: await readDiagnosticLog(join15(root, "run", "daemon.log")) };
|
|
30158
30259
|
}
|
|
30159
30260
|
let client;
|
|
30160
30261
|
try {
|
|
@@ -30178,12 +30279,12 @@ async function runDaemonCommand(command2, io) {
|
|
|
30178
30279
|
}
|
|
30179
30280
|
}
|
|
30180
30281
|
async function runForegroundDaemon(root, io) {
|
|
30181
|
-
const log = (event) => io.writeStderr(JSON.stringify({ time: (/* @__PURE__ */ new Date()).toISOString(), event }) + "\n");
|
|
30282
|
+
const log = (event, error) => io.writeStderr(JSON.stringify({ time: (/* @__PURE__ */ new Date()).toISOString(), event, pid: process.pid, ...error === void 0 ? {} : { error: diagnosticError(error) } }) + "\n");
|
|
30182
30283
|
let runtime;
|
|
30183
30284
|
try {
|
|
30184
30285
|
runtime = await NativeV2DaemonRuntime.open({ configRoot: root, onBackgroundError: () => log("background-error") });
|
|
30185
30286
|
} catch (error) {
|
|
30186
|
-
log("start-failed");
|
|
30287
|
+
log("start-failed", error);
|
|
30187
30288
|
throw error;
|
|
30188
30289
|
}
|
|
30189
30290
|
let stop;
|
|
@@ -30204,6 +30305,9 @@ async function runForegroundDaemon(root, io) {
|
|
|
30204
30305
|
await stopped;
|
|
30205
30306
|
log("stopped");
|
|
30206
30307
|
return { stopped: true };
|
|
30308
|
+
} catch (error) {
|
|
30309
|
+
log("start-failed", error);
|
|
30310
|
+
throw error;
|
|
30207
30311
|
} finally {
|
|
30208
30312
|
process.off("SIGTERM", onSignal);
|
|
30209
30313
|
process.off("SIGINT", onSignal);
|
|
@@ -30233,8 +30337,48 @@ var InitSchema = external_exports.object({
|
|
|
30233
30337
|
}).strict();
|
|
30234
30338
|
var LogSchema = external_exports.object({
|
|
30235
30339
|
time: external_exports.string().datetime(),
|
|
30236
|
-
event: external_exports.enum(["started", "stopped", "start-failed", "stop-failed", "background-error"])
|
|
30340
|
+
event: external_exports.enum(["started", "stopped", "start-failed", "stop-failed", "background-error"]),
|
|
30341
|
+
pid: external_exports.number().int().positive().optional(),
|
|
30342
|
+
error: external_exports.object({ code: external_exports.string(), message: external_exports.string(), hint: external_exports.string() }).strict().optional()
|
|
30237
30343
|
}).strict();
|
|
30344
|
+
var diagnostics = {
|
|
30345
|
+
CONFIG_INVALID: { message: "Configuration could not be loaded or migrated.", hint: "Check config.json and credentials.json privately. Preserve the originals and migration backups; do not paste credentials into diagnostics." },
|
|
30346
|
+
LEGACY_DAEMON_RUNNING: { message: "An old daemon is still using this config root.", hint: "Stop the old CLI daemon for this root before retrying." },
|
|
30347
|
+
CLI_UNEXPECTED_FAILURE: { message: "The daemon could not start.", hint: "Check the local configuration, file permissions and installed Node version." }
|
|
30348
|
+
};
|
|
30349
|
+
function diagnosticError(error) {
|
|
30350
|
+
const code = error instanceof CliConfigError && Object.hasOwn(diagnostics, error.code) ? error.code : "CLI_UNEXPECTED_FAILURE";
|
|
30351
|
+
return { code, ...diagnostics[code] };
|
|
30352
|
+
}
|
|
30353
|
+
async function readDiagnosticLog(path) {
|
|
30354
|
+
await assertPrivateRegularOrMissing(path);
|
|
30355
|
+
const handle = await open9(path, "r").catch((e) => {
|
|
30356
|
+
if (e.code === "ENOENT") return void 0;
|
|
30357
|
+
throw e;
|
|
30358
|
+
});
|
|
30359
|
+
if (!handle) return [];
|
|
30360
|
+
try {
|
|
30361
|
+
const size = (await handle.stat()).size;
|
|
30362
|
+
const buffer = Buffer.alloc(Math.min(size, 64 * 1024));
|
|
30363
|
+
await handle.read(buffer, 0, buffer.length, Math.max(0, size - buffer.length));
|
|
30364
|
+
return buffer.toString("utf8").split("\n").flatMap((line) => {
|
|
30365
|
+
try {
|
|
30366
|
+
const parsed = LogSchema.safeParse(JSON.parse(line));
|
|
30367
|
+
if (!parsed.success) return [];
|
|
30368
|
+
const value = parsed.data;
|
|
30369
|
+
if (value.error) {
|
|
30370
|
+
const code = Object.hasOwn(diagnostics, value.error.code) ? value.error.code : "CLI_UNEXPECTED_FAILURE";
|
|
30371
|
+
value.error = { code, ...diagnostics[code] };
|
|
30372
|
+
}
|
|
30373
|
+
return [value];
|
|
30374
|
+
} catch {
|
|
30375
|
+
return [];
|
|
30376
|
+
}
|
|
30377
|
+
}).slice(-100);
|
|
30378
|
+
} finally {
|
|
30379
|
+
await handle.close();
|
|
30380
|
+
}
|
|
30381
|
+
}
|
|
30238
30382
|
|
|
30239
30383
|
// src/ipc/qr-code.ts
|
|
30240
30384
|
var import_qrcode = __toESM(require_lib(), 1);
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ The default root is `~/.muc`; `MUC_CONFIG_DIR` and `--config-root` select an iso
|
|
|
6
6
|
|
|
7
7
|
- `muc daemon start`: start the background process for this root.
|
|
8
8
|
- `muc daemon status`: inspect without starting it.
|
|
9
|
-
- `muc daemon logs`: read safe diagnostic events, never raw credentials.
|
|
9
|
+
- `muc daemon logs`: read safe diagnostic events with error codes and recovery hints, never raw credentials.
|
|
10
10
|
- `muc daemon stop`: stop this background process; existing credentials and stored messages remain.
|
|
11
11
|
- `muc runtime status --profile-id <id>`: inspect the profile connection.
|
|
12
12
|
- `muc runtime ensure|start|resume|stop --profile-id <id>`: change only the requested profile runtime. Do not use resume to bypass an authentication failure or displacement.
|
|
@@ -18,3 +18,5 @@ User-terminal login: `muc login --account <uid> [--profile-id <id>] [--remember]
|
|
|
18
18
|
The daemon performs accessToken → tokenPwd → remembered-password recovery, with `/emp/get` attempted first. Network errors do not downgrade credentials. Exhausted recovery requires explicit login. Existing profiles preserve their device slot on reauthentication.
|
|
19
19
|
|
|
20
20
|
Use `muc profile logout --profile-id <id>` only when the user requests logout; it clears that profile's session and remembered password.
|
|
21
|
+
|
|
22
|
+
Legacy migration supports flat single-account and activeProfile/profiles multi-account roots. It preserves profile IDs, the selected default, device slots and recovery credentials, with private backups and a resumable journal. Different tenants, a missing default or an undecryptable remembered password fail without overwriting the originals. Preserve remember.salt; do not reinitialize or delete credentials to bypass migration errors.
|