@barryfan2045/dsh-matrix 0.1.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/LICENSE +21 -0
- package/README.md +286 -0
- package/THIRD_PARTY_NOTICES.md +53 -0
- package/assets/avatars/instance-01.png +0 -0
- package/assets/avatars/instance-02.png +0 -0
- package/assets/avatars/instance-03.png +0 -0
- package/assets/avatars/instance-04.png +0 -0
- package/assets/avatars/instance-05.png +0 -0
- package/assets/avatars/instance-06.png +0 -0
- package/assets/avatars/instance-07.png +0 -0
- package/assets/avatars/instance-08.png +0 -0
- package/assets/avatars/instance-09.png +0 -0
- package/assets/avatars/instance-10.png +0 -0
- package/assets/avatars/instance-11.png +0 -0
- package/assets/avatars/instance-12.png +0 -0
- package/assets/avatars/management.png +0 -0
- package/cordis.patch.yml +13 -0
- package/dist/auth/admin-commands.d.ts +47 -0
- package/dist/auth/admin-commands.d.ts.map +1 -0
- package/dist/auth/admin-commands.js +112 -0
- package/dist/auth/admin-commands.js.map +1 -0
- package/dist/auth/challenge.d.ts +93 -0
- package/dist/auth/challenge.d.ts.map +1 -0
- package/dist/auth/challenge.js +104 -0
- package/dist/auth/challenge.js.map +1 -0
- package/dist/auth/store.d.ts +55 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +124 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/bridge/agent-commands.d.ts +39 -0
- package/dist/bridge/agent-commands.d.ts.map +1 -0
- package/dist/bridge/agent-commands.js +243 -0
- package/dist/bridge/agent-commands.js.map +1 -0
- package/dist/bridge/attachments-ledger.d.ts +54 -0
- package/dist/bridge/attachments-ledger.d.ts.map +1 -0
- package/dist/bridge/attachments-ledger.js +68 -0
- package/dist/bridge/attachments-ledger.js.map +1 -0
- package/dist/bridge/commands.d.ts +28 -0
- package/dist/bridge/commands.d.ts.map +1 -0
- package/dist/bridge/commands.js +18 -0
- package/dist/bridge/commands.js.map +1 -0
- package/dist/bridge/crypto-health.d.ts +37 -0
- package/dist/bridge/crypto-health.d.ts.map +1 -0
- package/dist/bridge/crypto-health.js +38 -0
- package/dist/bridge/crypto-health.js.map +1 -0
- package/dist/bridge/group-gate.d.ts +38 -0
- package/dist/bridge/group-gate.d.ts.map +1 -0
- package/dist/bridge/group-gate.js +49 -0
- package/dist/bridge/group-gate.js.map +1 -0
- package/dist/bridge/instance-lock.d.ts +10 -0
- package/dist/bridge/instance-lock.d.ts.map +1 -0
- package/dist/bridge/instance-lock.js +82 -0
- package/dist/bridge/instance-lock.js.map +1 -0
- package/dist/bridge/interactions.d.ts +62 -0
- package/dist/bridge/interactions.d.ts.map +1 -0
- package/dist/bridge/interactions.js +237 -0
- package/dist/bridge/interactions.js.map +1 -0
- package/dist/bridge/management-room.d.ts +13 -0
- package/dist/bridge/management-room.d.ts.map +1 -0
- package/dist/bridge/management-room.js +27 -0
- package/dist/bridge/management-room.js.map +1 -0
- package/dist/bridge/multi-project.d.ts +34 -0
- package/dist/bridge/multi-project.d.ts.map +1 -0
- package/dist/bridge/multi-project.js +44 -0
- package/dist/bridge/multi-project.js.map +1 -0
- package/dist/bridge/pipeline.d.ts +151 -0
- package/dist/bridge/pipeline.d.ts.map +1 -0
- package/dist/bridge/pipeline.js +502 -0
- package/dist/bridge/pipeline.js.map +1 -0
- package/dist/bridge/pmctl.d.ts +50 -0
- package/dist/bridge/pmctl.d.ts.map +1 -0
- package/dist/bridge/pmctl.js +272 -0
- package/dist/bridge/pmctl.js.map +1 -0
- package/dist/bridge/power-levels.d.ts +57 -0
- package/dist/bridge/power-levels.d.ts.map +1 -0
- package/dist/bridge/power-levels.js +92 -0
- package/dist/bridge/power-levels.js.map +1 -0
- package/dist/bridge/power-sync.d.ts +64 -0
- package/dist/bridge/power-sync.d.ts.map +1 -0
- package/dist/bridge/power-sync.js +121 -0
- package/dist/bridge/power-sync.js.map +1 -0
- package/dist/bridge/projects.d.ts +97 -0
- package/dist/bridge/projects.d.ts.map +1 -0
- package/dist/bridge/projects.js +288 -0
- package/dist/bridge/projects.js.map +1 -0
- package/dist/bridge/quote-cache.d.ts +38 -0
- package/dist/bridge/quote-cache.d.ts.map +1 -0
- package/dist/bridge/quote-cache.js +56 -0
- package/dist/bridge/quote-cache.js.map +1 -0
- package/dist/bridge/reply.d.ts +31 -0
- package/dist/bridge/reply.d.ts.map +1 -0
- package/dist/bridge/reply.js +122 -0
- package/dist/bridge/reply.js.map +1 -0
- package/dist/bridge/space-identity.d.ts +43 -0
- package/dist/bridge/space-identity.d.ts.map +1 -0
- package/dist/bridge/space-identity.js +92 -0
- package/dist/bridge/space-identity.js.map +1 -0
- package/dist/bridge/space-sync.d.ts +94 -0
- package/dist/bridge/space-sync.d.ts.map +1 -0
- package/dist/bridge/space-sync.js +137 -0
- package/dist/bridge/space-sync.js.map +1 -0
- package/dist/bridge/turn-commands.d.ts +28 -0
- package/dist/bridge/turn-commands.d.ts.map +1 -0
- package/dist/bridge/turn-commands.js +97 -0
- package/dist/bridge/turn-commands.js.map +1 -0
- package/dist/cli/config-store.d.ts +32 -0
- package/dist/cli/config-store.d.ts.map +1 -0
- package/dist/cli/config-store.js +118 -0
- package/dist/cli/config-store.js.map +1 -0
- package/dist/cli/crypto-store.d.ts +42 -0
- package/dist/cli/crypto-store.d.ts.map +1 -0
- package/dist/cli/crypto-store.js +70 -0
- package/dist/cli/crypto-store.js.map +1 -0
- package/dist/cli/device-prune.d.ts +105 -0
- package/dist/cli/device-prune.d.ts.map +1 -0
- package/dist/cli/device-prune.js +163 -0
- package/dist/cli/device-prune.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +584 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/log-filter.d.ts +32 -0
- package/dist/cli/log-filter.d.ts.map +1 -0
- package/dist/cli/log-filter.js +40 -0
- package/dist/cli/log-filter.js.map +1 -0
- package/dist/cli/pnpm-builds.d.ts +32 -0
- package/dist/cli/pnpm-builds.d.ts.map +1 -0
- package/dist/cli/pnpm-builds.js +73 -0
- package/dist/cli/pnpm-builds.js.map +1 -0
- package/dist/cli/profile-patch.d.ts +26 -0
- package/dist/cli/profile-patch.d.ts.map +1 -0
- package/dist/cli/profile-patch.js +56 -0
- package/dist/cli/profile-patch.js.map +1 -0
- package/dist/cli/setup.d.ts +39 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +234 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/systemd-hint.d.ts +11 -0
- package/dist/cli/systemd-hint.d.ts.map +1 -0
- package/dist/cli/systemd-hint.js +46 -0
- package/dist/cli/systemd-hint.js.map +1 -0
- package/dist/cli/systemd.d.ts +32 -0
- package/dist/cli/systemd.d.ts.map +1 -0
- package/dist/cli/systemd.js +58 -0
- package/dist/cli/systemd.js.map +1 -0
- package/dist/config.d.ts +54 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/dsh/backend.d.ts +110 -0
- package/dist/dsh/backend.d.ts.map +1 -0
- package/dist/dsh/backend.js +481 -0
- package/dist/dsh/backend.js.map +1 -0
- package/dist/dsh/interactions.d.ts +50 -0
- package/dist/dsh/interactions.d.ts.map +1 -0
- package/dist/dsh/interactions.js +141 -0
- package/dist/dsh/interactions.js.map +1 -0
- package/dist/dsh/stream.d.ts +32 -0
- package/dist/dsh/stream.d.ts.map +1 -0
- package/dist/dsh/stream.js +109 -0
- package/dist/dsh/stream.js.map +1 -0
- package/dist/formatting.d.ts +14 -0
- package/dist/formatting.d.ts.map +1 -0
- package/dist/formatting.js +63 -0
- package/dist/formatting.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +28 -0
- package/dist/logger.js.map +1 -0
- package/dist/matrix/matrix-events.d.ts +49 -0
- package/dist/matrix/matrix-events.d.ts.map +1 -0
- package/dist/matrix/matrix-events.js +89 -0
- package/dist/matrix/matrix-events.js.map +1 -0
- package/dist/matrix/transport.d.ts +192 -0
- package/dist/matrix/transport.d.ts.map +1 -0
- package/dist/matrix/transport.js +510 -0
- package/dist/matrix/transport.js.map +1 -0
- package/dist/ports/attachments.d.ts +51 -0
- package/dist/ports/attachments.d.ts.map +1 -0
- package/dist/ports/attachments.js +27 -0
- package/dist/ports/attachments.js.map +1 -0
- package/dist/ports/backend.d.ts +159 -0
- package/dist/ports/backend.d.ts.map +1 -0
- package/dist/ports/backend.js +25 -0
- package/dist/ports/backend.js.map +1 -0
- package/dist/ports/interactions.d.ts +65 -0
- package/dist/ports/interactions.d.ts.map +1 -0
- package/dist/ports/interactions.js +15 -0
- package/dist/ports/interactions.js.map +1 -0
- package/dist/ports/streaming.d.ts +45 -0
- package/dist/ports/streaming.d.ts.map +1 -0
- package/dist/ports/streaming.js +23 -0
- package/dist/ports/streaming.js.map +1 -0
- package/dist/ports/transport.d.ts +68 -0
- package/dist/ports/transport.d.ts.map +1 -0
- package/dist/ports/transport.js +2 -0
- package/dist/ports/transport.js.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** "/name args…" 的极简解析;非斜杠开头的文本返回 undefined。 */
|
|
2
|
+
export function parseTurnCommand(text) {
|
|
3
|
+
const trimmed = text.trim();
|
|
4
|
+
if (!trimmed.startsWith('/'))
|
|
5
|
+
return undefined;
|
|
6
|
+
const space = trimmed.indexOf(' ');
|
|
7
|
+
const name = space === -1 ? trimmed : trimmed.slice(0, space);
|
|
8
|
+
const args = space === -1 ? '' : trimmed.slice(space + 1).trim();
|
|
9
|
+
return { name, args };
|
|
10
|
+
}
|
|
11
|
+
/** Collapse a queue entry to one bounded line for chat display. */
|
|
12
|
+
function truncateLine(text, max = 120) {
|
|
13
|
+
const oneLine = text.replace(/\s+/g, ' ').trim();
|
|
14
|
+
return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* /stop 与 /interrupt 的队列播报:keepInbox 取消保留 pending 项,它们将在
|
|
18
|
+
* 下一轮生效 —— 必须显式列出,避免"背刺式"延迟执行(pi-courier 同款 UX)。
|
|
19
|
+
*/
|
|
20
|
+
function queueWarning(items) {
|
|
21
|
+
if (items.length === 0)
|
|
22
|
+
return null;
|
|
23
|
+
const lines = items.map((m) => `- ${truncateLine(m.text)}`);
|
|
24
|
+
return `⚠️ 队列中仍有 ${items.length} 条消息将在下一轮生效:\n${lines.join('\n')}`;
|
|
25
|
+
}
|
|
26
|
+
function formatQueueView(items) {
|
|
27
|
+
if (items.length === 0)
|
|
28
|
+
return '📋 队列为空:没有排队中的消息。';
|
|
29
|
+
const steering = items.filter((i) => i.kind === 'steer');
|
|
30
|
+
const followUp = items.filter((i) => i.kind === 'queue');
|
|
31
|
+
const lines = ['📋 当前消息队列:'];
|
|
32
|
+
if (steering.length > 0) {
|
|
33
|
+
lines.push(`steering(${steering.length} 条,注入当前运行):`);
|
|
34
|
+
for (const m of steering)
|
|
35
|
+
lines.push(`- ${truncateLine(m.text)}`);
|
|
36
|
+
}
|
|
37
|
+
if (followUp.length > 0) {
|
|
38
|
+
lines.push(`followUp(${followUp.length} 条,后续轮次执行):`);
|
|
39
|
+
for (const m of followUp)
|
|
40
|
+
lines.push(`- ${truncateLine(m.text)}`);
|
|
41
|
+
}
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
/** 起一个回合并把最终回答作为回帖文本返回(命令内部的"当普通消息"路径)。 */
|
|
45
|
+
async function runTurn(session, text) {
|
|
46
|
+
session.prompt(text);
|
|
47
|
+
await session.whenIdle();
|
|
48
|
+
return session.lastReply() ?? '(本轮没有文本输出)';
|
|
49
|
+
}
|
|
50
|
+
/** Names owned by this table (pipeline uses it to keep the T4 router from
|
|
51
|
+
* claiming /stop etc.). Values keep the leading slash, matching parseTurnCommand. */
|
|
52
|
+
export const TURN_COMMANDS = new Set(['/stop', '/abort', '/queue', '/interrupt', '/last']);
|
|
53
|
+
export async function handleTurnCommand(name, args, ctx) {
|
|
54
|
+
const { session } = ctx;
|
|
55
|
+
switch (name) {
|
|
56
|
+
case '/stop':
|
|
57
|
+
case '/abort': {
|
|
58
|
+
if (!session.isRunning())
|
|
59
|
+
return 'ℹ️ 当前没有运行中的任务';
|
|
60
|
+
// keepInbox = pi abort 语义:只掐当前回合,不动队列
|
|
61
|
+
session.cancel({ keepInbox: true });
|
|
62
|
+
const warning = queueWarning(session.queuedItems());
|
|
63
|
+
return warning ? `⏹️ 已停止\n${warning}` : '⏹️ 已停止';
|
|
64
|
+
}
|
|
65
|
+
case '/queue': {
|
|
66
|
+
if (!args)
|
|
67
|
+
return formatQueueView(session.queuedItems());
|
|
68
|
+
if (session.isRunning()) {
|
|
69
|
+
// DSH followup 在 running 时天然排到下一轮(Alt+Enter 语义)
|
|
70
|
+
session.prompt(args);
|
|
71
|
+
return '📥 已排队:不打断当前任务,将在空闲后自动执行。';
|
|
72
|
+
}
|
|
73
|
+
return runTurn(session, args);
|
|
74
|
+
}
|
|
75
|
+
case '/interrupt': {
|
|
76
|
+
if (!args)
|
|
77
|
+
return '用法: /interrupt <新指令> — 中断当前回合并立即下发新指令。';
|
|
78
|
+
if (!session.isRunning())
|
|
79
|
+
return runTurn(session, args);
|
|
80
|
+
session.cancel({ keepInbox: true });
|
|
81
|
+
await session.whenIdle();
|
|
82
|
+
const answer = await runTurn(session, args);
|
|
83
|
+
const warning = queueWarning(session.queuedItems());
|
|
84
|
+
const head = `⏸️ 已中断当前任务并发送新指令\n\n${answer}`;
|
|
85
|
+
return warning ? `${head}\n${warning}` : head;
|
|
86
|
+
}
|
|
87
|
+
case '/last': {
|
|
88
|
+
const last = session.lastReply()?.trim();
|
|
89
|
+
if (!last)
|
|
90
|
+
return '💤 没有可复述的回复(本会话还没有 assistant 输出)。';
|
|
91
|
+
return last.length > 3000 ? `${last.slice(0, 3000)}\n…(已截断)` : last;
|
|
92
|
+
}
|
|
93
|
+
default:
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=turn-commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-commands.js","sourceRoot":"","sources":["../../src/bridge/turn-commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type OpsLogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export interface OpsConfig {
|
|
3
|
+
/** 实例名 = dsh profile 名(~/.dsh/profiles/<profile>),默认 "matrix"。 */
|
|
4
|
+
profile: string;
|
|
5
|
+
homeserver: string;
|
|
6
|
+
/** 登录方式一:用户名 + 密码(桥启动时换取 token)。 */
|
|
7
|
+
username?: string;
|
|
8
|
+
password?: string;
|
|
9
|
+
/** 登录方式二:直接粘贴 access token。 */
|
|
10
|
+
accessToken?: string;
|
|
11
|
+
trustedUsers: string[];
|
|
12
|
+
workdir: string;
|
|
13
|
+
logLevel?: OpsLogLevel;
|
|
14
|
+
}
|
|
15
|
+
/** dsh 的家目录(与 dsh 一致:DSH_HOME 覆盖,默认 ~/.dsh)。 */
|
|
16
|
+
export declare function dshHome(homedir?: string): string;
|
|
17
|
+
export declare function opsConfigPath(homedir?: string): string;
|
|
18
|
+
/** pi-courier 的旧配置文件路径(US41 迁移源):~/.pi/pi-courier.json。 */
|
|
19
|
+
export declare function piCourierConfigPath(homedir?: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* 把 pi-courier 的配置对象映射成本包的 OpsConfig(US41)。
|
|
22
|
+
*
|
|
23
|
+
* 同构字段直接搬(homeserver/accessToken/trustedUsers/workdir/logLevel);
|
|
24
|
+
* pi 专属字段(cliPath/sessionDir/hideToolCalls/multiProject 等)一律丢弃。
|
|
25
|
+
* 缺 homeserver 视为不可迁移,返回 null。
|
|
26
|
+
*/
|
|
27
|
+
export declare function migratePiCourierConfig(raw: unknown, profile?: string, homedir?: string): OpsConfig | null;
|
|
28
|
+
/** 读配置;文件不存在时先尝试从 pi-courier 旧配置迁移,仍无则返回 null。权限过开(组/其他可读)时告警。 */
|
|
29
|
+
export declare function loadOpsConfig(homedir?: string): OpsConfig | null;
|
|
30
|
+
/** 写配置(0600)。返回写入路径。目录不存在时以 0700 创建。 */
|
|
31
|
+
export declare function saveOpsConfig(cfg: OpsConfig, homedir?: string): string;
|
|
32
|
+
//# sourceMappingURL=config-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../../src/cli/config-store.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE7D,MAAM,WAAW,SAAS;IACxB,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,WAAW,CAAA;CACvB;AAED,gDAAgD;AAChD,wBAAgB,OAAO,CAAC,OAAO,GAAE,MAAqB,GAAG,MAAM,CAI9D;AAED,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,2DAA2D;AAC3D,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,MAAqB,GAAG,MAAM,CAE1E;AAOD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,OAAO,EACZ,OAAO,SAAW,EAClB,OAAO,GAAE,MAAqB,GAC7B,SAAS,GAAG,IAAI,CAgClB;AAqBD,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAchE;AAED,wCAAwC;AACxC,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAQtE"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运维配置存储:~/.dsh/dsh-matrix.json(权限 0600)。
|
|
3
|
+
*
|
|
4
|
+
* 这是 CLI 侧的"运维真相源":setup 写它,enable/status/install-profile 读它。
|
|
5
|
+
* 桥运行时的配置仍走 cordis patch 行(src/config.ts loadConfig,不改);
|
|
6
|
+
* install-profile 负责把这里的值生成 profile 的 cordis.patch.yml 覆盖块。
|
|
7
|
+
*
|
|
8
|
+
* 0600 读写与不安全权限告警移植自 pi-courier src/config.ts 的 loadConfig/
|
|
9
|
+
* saveConfig(第 9-100 行),语义改为 DSH 目录。
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as os from 'node:os';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
/** dsh 的家目录(与 dsh 一致:DSH_HOME 覆盖,默认 ~/.dsh)。 */
|
|
15
|
+
export function dshHome(homedir = os.homedir()) {
|
|
16
|
+
const envHome = process.env.DSH_HOME;
|
|
17
|
+
if (envHome && envHome.trim())
|
|
18
|
+
return path.resolve(envHome.trim());
|
|
19
|
+
return path.join(homedir, '.dsh');
|
|
20
|
+
}
|
|
21
|
+
export function opsConfigPath(homedir) {
|
|
22
|
+
return path.join(dshHome(homedir), 'dsh-matrix.json');
|
|
23
|
+
}
|
|
24
|
+
/** pi-courier 的旧配置文件路径(US41 迁移源):~/.pi/pi-courier.json。 */
|
|
25
|
+
export function piCourierConfigPath(homedir = os.homedir()) {
|
|
26
|
+
return path.join(homedir, '.pi', 'pi-courier.json');
|
|
27
|
+
}
|
|
28
|
+
/** pi-courier 的 trustedUsers 带 matrix: 前缀,迁移时剥掉成 MXID。 */
|
|
29
|
+
function stripMatrixPrefix(user) {
|
|
30
|
+
return user.startsWith('matrix:') ? user.slice('matrix:'.length) : user;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 把 pi-courier 的配置对象映射成本包的 OpsConfig(US41)。
|
|
34
|
+
*
|
|
35
|
+
* 同构字段直接搬(homeserver/accessToken/trustedUsers/workdir/logLevel);
|
|
36
|
+
* pi 专属字段(cliPath/sessionDir/hideToolCalls/multiProject 等)一律丢弃。
|
|
37
|
+
* 缺 homeserver 视为不可迁移,返回 null。
|
|
38
|
+
*/
|
|
39
|
+
export function migratePiCourierConfig(raw, profile = 'matrix', homedir = os.homedir()) {
|
|
40
|
+
if (typeof raw !== 'object' || raw === null)
|
|
41
|
+
return null;
|
|
42
|
+
const r = raw;
|
|
43
|
+
const matrix = typeof r.matrix === 'object' && r.matrix !== null ? r.matrix : {};
|
|
44
|
+
const auth = typeof r.auth === 'object' && r.auth !== null ? r.auth : {};
|
|
45
|
+
const homeserver = typeof matrix.homeserverUrl === 'string' && matrix.homeserverUrl
|
|
46
|
+
? matrix.homeserverUrl
|
|
47
|
+
: typeof r.homeserver === 'string' && r.homeserver
|
|
48
|
+
? r.homeserver
|
|
49
|
+
: undefined;
|
|
50
|
+
if (!homeserver)
|
|
51
|
+
return null;
|
|
52
|
+
const trustedRaw = auth.trustedUsers ?? r.trustedUsers;
|
|
53
|
+
const trustedUsers = Array.isArray(trustedRaw)
|
|
54
|
+
? trustedRaw.map(String).map(stripMatrixPrefix)
|
|
55
|
+
: typeof trustedRaw === 'string'
|
|
56
|
+
? trustedRaw.split(',').map((s) => s.trim()).filter(Boolean).map(stripMatrixPrefix)
|
|
57
|
+
: [];
|
|
58
|
+
const lvl = r.logLevel;
|
|
59
|
+
const logLevel = lvl === 'debug' || lvl === 'info' || lvl === 'warn' || lvl === 'error' ? lvl : undefined;
|
|
60
|
+
return {
|
|
61
|
+
profile,
|
|
62
|
+
homeserver,
|
|
63
|
+
...(typeof matrix.accessToken === 'string' && matrix.accessToken ? { accessToken: matrix.accessToken } : {}),
|
|
64
|
+
trustedUsers,
|
|
65
|
+
workdir: typeof r.workdir === 'string' && r.workdir ? r.workdir : path.join(homedir, 'Projects'),
|
|
66
|
+
...(logLevel ? { logLevel } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 当 ~/.dsh/dsh-matrix.json 不存在时,尝试从 pi-courier 旧配置迁移(US41):
|
|
71
|
+
* 迁移成功会写出新配置(0600)并返回;旧文件原样保留。
|
|
72
|
+
*/
|
|
73
|
+
function migratePiCourierIfPresent(homedir) {
|
|
74
|
+
const legacy = piCourierConfigPath(homedir);
|
|
75
|
+
if (!fs.existsSync(legacy))
|
|
76
|
+
return null;
|
|
77
|
+
try {
|
|
78
|
+
const migrated = migratePiCourierConfig(JSON.parse(fs.readFileSync(legacy, 'utf-8')), 'matrix', homedir);
|
|
79
|
+
if (!migrated)
|
|
80
|
+
return null;
|
|
81
|
+
const target = saveOpsConfig(migrated, homedir);
|
|
82
|
+
console.warn('ℹ️ 检测到 pi-courier 旧配置 ' + legacy + ',已迁移到 ' + target + '(旧文件保留未动)。');
|
|
83
|
+
return migrated;
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error('❌ 迁移 pi-courier 配置 ' + legacy + ' 失败: ' + err.message);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** 读配置;文件不存在时先尝试从 pi-courier 旧配置迁移,仍无则返回 null。权限过开(组/其他可读)时告警。 */
|
|
91
|
+
export function loadOpsConfig(homedir) {
|
|
92
|
+
const file = opsConfigPath(homedir);
|
|
93
|
+
if (!fs.existsSync(file))
|
|
94
|
+
return migratePiCourierIfPresent(homedir);
|
|
95
|
+
try {
|
|
96
|
+
const mode = fs.statSync(file).mode & 0o777;
|
|
97
|
+
if ((mode & 0o077) !== 0) {
|
|
98
|
+
console.warn(`⚠️ ${file} 权限为 ${mode.toString(8).padStart(4, '0')},含密码/token,应为 0600。已自动收紧。`);
|
|
99
|
+
fs.chmodSync(file, 0o600);
|
|
100
|
+
}
|
|
101
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error(`❌ 读取 ${file} 失败: ${err.message}`);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** 写配置(0600)。返回写入路径。目录不存在时以 0700 创建。 */
|
|
109
|
+
export function saveOpsConfig(cfg, homedir) {
|
|
110
|
+
const dir = dshHome(homedir);
|
|
111
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
112
|
+
const file = opsConfigPath(homedir);
|
|
113
|
+
// 已存在的文件 writeFileSync 不改权限,写完统一显式 chmod,保证 0600。
|
|
114
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
115
|
+
fs.chmodSync(file, 0o600);
|
|
116
|
+
return file;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=config-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-store.js","sourceRoot":"","sources":["../../src/cli/config-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAkBjC,gDAAgD;AAChD,MAAM,UAAU,OAAO,CAAC,UAAkB,EAAE,CAAC,OAAO,EAAE;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAA;IACpC,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IAClE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAA;AACvD,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,mBAAmB,CAAC,UAAkB,EAAE,CAAC,OAAO,EAAE;IAChE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAA;AACrD,CAAC;AAED,0DAA0D;AAC1D,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACzE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,GAAY,EACZ,OAAO,GAAG,QAAQ,EAClB,UAAkB,EAAE,CAAC,OAAO,EAAE;IAE9B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IACxD,MAAM,CAAC,GAAG,GAA8B,CAAA;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,MAAkC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7G,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,IAAgC,CAAC,CAAC,CAAC,EAAE,CAAA;IAErG,MAAM,UAAU,GACd,OAAO,MAAM,CAAC,aAAa,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa;QAC9D,CAAC,CAAC,MAAM,CAAC,aAAa;QACtB,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU;YAChD,CAAC,CAAC,CAAC,CAAC,UAAU;YACd,CAAC,CAAC,SAAS,CAAA;IACjB,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAA;IAE5B,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,YAAY,CAAA;IACtD,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QAC5C,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC;QAC/C,CAAC,CAAC,OAAO,UAAU,KAAK,QAAQ;YAC9B,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC;YACnF,CAAC,CAAC,EAAE,CAAA;IAER,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAA;IACtB,MAAM,QAAQ,GAAG,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;IAEzG,OAAO;QACL,OAAO;QACP,UAAU;QACV,GAAG,CAAC,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5G,YAAY;QACZ,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC;QAChG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClC,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,yBAAyB,CAAC,OAAgB;IACjD,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IACvC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;QACxG,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAA;QAC1B,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC/C,OAAO,CAAC,IAAI,CAAC,yBAAyB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAC,CAAA;QACnF,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,qBAAqB,GAAG,MAAM,GAAG,OAAO,GAAI,GAAa,CAAC,OAAO,CAAC,CAAA;QAChF,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAA;IACnE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,KAAK,CAAA;QAC3C,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,QAAQ,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,2BAA2B,CAAC,CAAA;YAC7F,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAC3B,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAc,CAAA;IAChE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,QAAS,GAAa,CAAC,OAAO,EAAE,CAAC,CAAA;QAC3D,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,aAAa,CAAC,GAAc,EAAE,OAAgB;IAC5D,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACnD,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;IACnC,kDAAkD;IAClD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IAC3D,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzB,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** crypto 存储目录:与运行时一致(`<dataDir>/matrix-crypto`,dataDir 缺省 `$DSH_HOME/dsh-matrix`)。 */
|
|
2
|
+
export declare function cryptoStoreDir(opts: {
|
|
3
|
+
dataDir?: string;
|
|
4
|
+
dshHome: string;
|
|
5
|
+
}): string;
|
|
6
|
+
/**
|
|
7
|
+
* T14:读出桥的 crypto 引擎自居的**设备身份**。
|
|
8
|
+
*
|
|
9
|
+
* matrix-bot-sdk 的 RustSdkCryptoStorageProvider 把它记在存储目录的 `bot-sdk.json`
|
|
10
|
+
* 里(`{deviceId, rooms}`)。`prune-devices` 要靠它带 `device_id` 登录 ——
|
|
11
|
+
* 否则"清理设备"这个动作自己又会新建一个设备,那就荒唐了。
|
|
12
|
+
*
|
|
13
|
+
* 读不到(文件缺失/坏 JSON/没记 deviceId)一律返回 undefined:调用方据此退回
|
|
14
|
+
* "不带 device_id 登录"的旧行为,不会因此崩。
|
|
15
|
+
*/
|
|
16
|
+
export declare function readCryptoStoreDeviceId(dir: string, read?: (p: string) => string): string | undefined;
|
|
17
|
+
export interface CryptoResetPlan {
|
|
18
|
+
action: 'backup' | 'purge' | 'absent';
|
|
19
|
+
/** 要动的目录。 */
|
|
20
|
+
dir: string;
|
|
21
|
+
/** action='backup' 时的改名目标。 */
|
|
22
|
+
target?: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 复位计划(纯函数,便于测):
|
|
27
|
+
* - `purge` → 直接删(用户明确要求,不保留旧身份);
|
|
28
|
+
* - 缺省 → **改名备份**(`<dir>.bak-<stamp>`)而不是删 —— 万一只是误判,旧身份还能拿回来;
|
|
29
|
+
* - 目录不存在 → absent(不改任何东西)。
|
|
30
|
+
*/
|
|
31
|
+
export declare function planCryptoReset(input: {
|
|
32
|
+
dir: string;
|
|
33
|
+
purge?: boolean;
|
|
34
|
+
stamp: string;
|
|
35
|
+
exists?: (p: string) => boolean;
|
|
36
|
+
}): CryptoResetPlan;
|
|
37
|
+
/** 执行计划;返回实际做了什么(供 CLI 打印)。测试可注入 exists 之外的 fs 操作。 */
|
|
38
|
+
export declare function applyCryptoReset(plan: CryptoResetPlan, ops?: {
|
|
39
|
+
rename?: (from: string, to: string) => void;
|
|
40
|
+
rm?: (p: string) => void;
|
|
41
|
+
}): CryptoResetPlan;
|
|
42
|
+
//# sourceMappingURL=crypto-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-store.d.ts","sourceRoot":"","sources":["../../src/cli/crypto-store.ts"],"names":[],"mappings":"AAeA,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAGlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAA2C,GAC/D,MAAM,GAAG,SAAS,CAOpB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;IACrC,aAAa;IACb,GAAG,EAAE,MAAM,CAAA;IACX,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;CAChC,GAAG,eAAe,CAclB;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,eAAe,EACrB,GAAG,GAAE;IAAE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAClF,eAAe,CAMjB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T13:crypto 存储的复位路径。
|
|
3
|
+
*
|
|
4
|
+
* 背景(见 docs/tickets/13-crypto-store-health.md):桥的 crypto 存储一旦进入坏状态,
|
|
5
|
+
* E2EE 会**静默失效** —— 桥照常启动、照常打 "E2EE 已启用",只是所有密文都解不开
|
|
6
|
+
* (`Can't find the room key`)。用户手上唯一可行的自救就是换一份存储,而这件事此前
|
|
7
|
+
* 没有任何入口。这里给出**显式**的复位动作(而不是"解密失败就自动删库重建" ——
|
|
8
|
+
* 那会静默更换设备身份,把用户原有的验证关系打乱)。
|
|
9
|
+
*
|
|
10
|
+
* 复位代价必须说清楚:换存储 = **换设备身份**,对方(Element 等)会看到一个新设备,
|
|
11
|
+
* 需要重新验证;历史加密消息也解不开(密钥在旧存储里)。
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
/** crypto 存储目录:与运行时一致(`<dataDir>/matrix-crypto`,dataDir 缺省 `$DSH_HOME/dsh-matrix`)。 */
|
|
16
|
+
export function cryptoStoreDir(opts) {
|
|
17
|
+
const dataDir = opts.dataDir ?? path.join(opts.dshHome, 'dsh-matrix');
|
|
18
|
+
return path.join(dataDir, 'matrix-crypto');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* T14:读出桥的 crypto 引擎自居的**设备身份**。
|
|
22
|
+
*
|
|
23
|
+
* matrix-bot-sdk 的 RustSdkCryptoStorageProvider 把它记在存储目录的 `bot-sdk.json`
|
|
24
|
+
* 里(`{deviceId, rooms}`)。`prune-devices` 要靠它带 `device_id` 登录 ——
|
|
25
|
+
* 否则"清理设备"这个动作自己又会新建一个设备,那就荒唐了。
|
|
26
|
+
*
|
|
27
|
+
* 读不到(文件缺失/坏 JSON/没记 deviceId)一律返回 undefined:调用方据此退回
|
|
28
|
+
* "不带 device_id 登录"的旧行为,不会因此崩。
|
|
29
|
+
*/
|
|
30
|
+
export function readCryptoStoreDeviceId(dir, read = (p) => fs.readFileSync(p, 'utf-8')) {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(read(path.join(dir, 'bot-sdk.json')));
|
|
33
|
+
return typeof parsed.deviceId === 'string' && parsed.deviceId ? parsed.deviceId : undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 复位计划(纯函数,便于测):
|
|
41
|
+
* - `purge` → 直接删(用户明确要求,不保留旧身份);
|
|
42
|
+
* - 缺省 → **改名备份**(`<dir>.bak-<stamp>`)而不是删 —— 万一只是误判,旧身份还能拿回来;
|
|
43
|
+
* - 目录不存在 → absent(不改任何东西)。
|
|
44
|
+
*/
|
|
45
|
+
export function planCryptoReset(input) {
|
|
46
|
+
const exists = input.exists ?? fs.existsSync;
|
|
47
|
+
if (!exists(input.dir)) {
|
|
48
|
+
return { action: 'absent', dir: input.dir, reason: '目录不存在(可能从未启用过 E2EE,或已经复位过)' };
|
|
49
|
+
}
|
|
50
|
+
if (input.purge) {
|
|
51
|
+
return { action: 'purge', dir: input.dir, reason: '已按 --purge 删除(不保留旧设备身份)' };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
action: 'backup',
|
|
55
|
+
dir: input.dir,
|
|
56
|
+
target: `${input.dir}.bak-${input.stamp}`,
|
|
57
|
+
reason: '已改名备份(想彻底删掉可手动删该目录)',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** 执行计划;返回实际做了什么(供 CLI 打印)。测试可注入 exists 之外的 fs 操作。 */
|
|
61
|
+
export function applyCryptoReset(plan, ops = {}) {
|
|
62
|
+
const rename = ops.rename ?? fs.renameSync;
|
|
63
|
+
const rm = ops.rm ?? ((p) => fs.rmSync(p, { recursive: true, force: true }));
|
|
64
|
+
if (plan.action === 'backup' && plan.target)
|
|
65
|
+
rename(plan.dir, plan.target);
|
|
66
|
+
else if (plan.action === 'purge')
|
|
67
|
+
rm(plan.dir);
|
|
68
|
+
return plan;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=crypto-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-store.js","sourceRoot":"","sources":["../../src/cli/crypto-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,uFAAuF;AACvF,MAAM,UAAU,cAAc,CAAC,IAA2C;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IACrE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CACrC,GAAW,EACX,OAA8B,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC;IAEhE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAA2B,CAAA;QACzF,OAAO,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAC7F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAWD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAK/B;IACC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,UAAU,CAAA;IAC5C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,4BAA4B,EAAE,CAAA;IACnF,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAA;IAC/E,CAAC;IACD,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,QAAQ,KAAK,CAAC,KAAK,EAAE;QACzC,MAAM,EAAE,qBAAqB;KAC9B,CAAA;AACH,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,gBAAgB,CAC9B,IAAqB,EACrB,MAAiF,EAAE;IAEnF,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,UAAU,CAAA;IAC1C,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACpF,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM;QAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;SACrE,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO;QAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC9C,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T14 后续:清理账号上的**孤儿设备**。
|
|
3
|
+
*
|
|
4
|
+
* 背景(见 docs/tickets/14-e2ee-bridge-regression.md):桥以前每次密码登录都**不带
|
|
5
|
+
* `device_id`**,Synapse 于是每次新建一个设备;累积起来不只是难看 ——
|
|
6
|
+
* 发送方每次加密都要对这一大堆设备做 keys/query、keys/claim 并逐个建 olm 会话,
|
|
7
|
+
* 实测把首次 sendMessage 拖到 6 分 13 秒。T14 已让桥不再造新设备,但**历史遗留**
|
|
8
|
+
* 的那些孤儿设备还在,本模块就是清理它们的显式入口。
|
|
9
|
+
*
|
|
10
|
+
* 设计原则(与 T13 §4 同一条):**显式、可见、不擅自**。
|
|
11
|
+
* - 默认只是**列出**(演练),必须 `--yes`(或交互确认)才真删;
|
|
12
|
+
* - 当前桥设备**永远保留**;
|
|
13
|
+
* - `--keep-days <n>` 可把"最近 n 天活动过"的设备一并保留(安全网,缺省 0 = 只留桥设备);
|
|
14
|
+
* - 删设备是**不可逆**的(私钥没了就是没了),所以这里绝不静默动手。
|
|
15
|
+
*/
|
|
16
|
+
export interface DeviceInfo {
|
|
17
|
+
device_id: string;
|
|
18
|
+
display_name?: string | null;
|
|
19
|
+
last_seen_ts?: number | null;
|
|
20
|
+
last_seen_ip?: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface PrunePlan {
|
|
23
|
+
/** 保留的设备 id(当前桥设备)。 */
|
|
24
|
+
keep: string;
|
|
25
|
+
/** keep 是否真的在设备列表里(不在 = 配置里的设备身份可能已经没了)。 */
|
|
26
|
+
keepFound: boolean;
|
|
27
|
+
/** 计划删除的设备。 */
|
|
28
|
+
remove: DeviceInfo[];
|
|
29
|
+
/** 明确保留的设备及原因(让"为什么没删"也可见)。 */
|
|
30
|
+
retained: {
|
|
31
|
+
device: DeviceInfo;
|
|
32
|
+
why: string;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 纯函数:算出"该删谁、留谁"。
|
|
37
|
+
*
|
|
38
|
+
* 判定顺序:当前桥设备 → 最近 keepDays 天活动过 → 其余都删。
|
|
39
|
+
* 没有 `last_seen_ts`(从未登录过)的设备一律算孤儿 —— 它们正是复位/改名留下的残骸。
|
|
40
|
+
*/
|
|
41
|
+
export declare function planDevicePrune(input: {
|
|
42
|
+
devices: DeviceInfo[];
|
|
43
|
+
keep: string;
|
|
44
|
+
now: number;
|
|
45
|
+
/** 最近多少天内活动过的设备也保留(缺省 0:只留桥设备)。 */
|
|
46
|
+
keepDays?: number;
|
|
47
|
+
}): PrunePlan;
|
|
48
|
+
export interface MatrixAdminOps {
|
|
49
|
+
whoAmI(): Promise<{
|
|
50
|
+
user_id: string;
|
|
51
|
+
device_id?: string;
|
|
52
|
+
}>;
|
|
53
|
+
listDevices(): Promise<DeviceInfo[]>;
|
|
54
|
+
/** 删一个设备(Synapse 要 UI auth,内部自动走两步)。 */
|
|
55
|
+
deleteDevice(deviceId: string): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export declare function createMatrixAdminOps(opts: {
|
|
58
|
+
homeserver: string;
|
|
59
|
+
accessToken: string;
|
|
60
|
+
userId: string;
|
|
61
|
+
/** 删除设备需要重新认证;没有密码就只能演练(会给出明确报错)。 */
|
|
62
|
+
password?: string;
|
|
63
|
+
fetchImpl?: typeof fetch;
|
|
64
|
+
}): MatrixAdminOps;
|
|
65
|
+
/** 密码登录换一个 token;带 deviceId 时复用该设备(不新建)。 */
|
|
66
|
+
export declare function loginForToken(opts: {
|
|
67
|
+
homeserver: string;
|
|
68
|
+
username: string;
|
|
69
|
+
password: string;
|
|
70
|
+
deviceId?: string;
|
|
71
|
+
fetchImpl?: typeof fetch;
|
|
72
|
+
}): Promise<{
|
|
73
|
+
accessToken: string;
|
|
74
|
+
userId: string;
|
|
75
|
+
deviceId?: string;
|
|
76
|
+
}>;
|
|
77
|
+
/** 给人看的"最后活动"列。 */
|
|
78
|
+
export declare function formatLastSeen(ts: number | null | undefined, now: number): string;
|
|
79
|
+
export type IdentityChoice = {
|
|
80
|
+
mode: 'token';
|
|
81
|
+
} | {
|
|
82
|
+
mode: 'password';
|
|
83
|
+
deviceId: string;
|
|
84
|
+
} | {
|
|
85
|
+
mode: 'unavailable';
|
|
86
|
+
message: string;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 选登录身份。
|
|
90
|
+
*
|
|
91
|
+
* **硬不变量:本命令绝不新建设备。** —— 一个"清理孤儿设备"的工具自己再造一个设备,
|
|
92
|
+
* 就正好是它要治的那毛病(实测踩到过:对着没有本地 crypto 存储的账号跑演练,直接多出
|
|
93
|
+
* 一个设备)。所以拿不到**既有**身份时明确拒绝,并告诉用户两条出路,而不是默默登录
|
|
94
|
+
* (密码登录不带 device_id 必然新建)。
|
|
95
|
+
*/
|
|
96
|
+
export declare function chooseLoginIdentity(input: {
|
|
97
|
+
accessToken?: string;
|
|
98
|
+
username?: string;
|
|
99
|
+
password?: string;
|
|
100
|
+
/** 桥的 crypto 存储里记着的设备身份(没有 = 拿不到既有身份)。 */
|
|
101
|
+
storedDeviceId?: string;
|
|
102
|
+
/** 仅供报错时提示位置。 */
|
|
103
|
+
cryptoDir: string;
|
|
104
|
+
}): IdentityChoice;
|
|
105
|
+
//# sourceMappingURL=device-prune.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-prune.d.ts","sourceRoot":"","sources":["../../src/cli/device-prune.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,SAAS;IACxB,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,SAAS,EAAE,OAAO,CAAA;IAClB,eAAe;IACf,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,+BAA+B;IAC/B,QAAQ,EAAE;QAAE,MAAM,EAAE,UAAU,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAChD;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GAAG,SAAS,CAqBZ;AAMD,MAAM,WAAW,cAAc;IAC7B,MAAM,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC1D,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IACpC,wCAAwC;IACxC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C;AAuBD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB,GAAG,cAAc,CA+CjB;AAED,4CAA4C;AAC5C,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBtE;AAED,mBAAmB;AACnB,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKjF;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5C;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0CAA0C;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,GAAG,cAAc,CAgBjB"}
|