termdock 1.4.34 → 1.4.35
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/.desktop-dist/main.js +800 -0
- package/README.md +13 -0
- package/dist/client/assets/{abnfDiagram-VRR7QNED-C0dQSsoH.js → abnfDiagram-VRR7QNED-DJ6Yq6GP.js} +1 -1
- package/dist/client/assets/{arc-DYA1ov03.js → arc-kBJ9sW7T.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-BLFQJMml.js → architectureDiagram-ZJ3FMSHR-Cb93VIke.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-yG6-Tbj-.js → blockDiagram-677ZJIJ3-Ti9R15O5.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-CvQTDVga.js → c4Diagram-LMCZKHZV-Bmig4PBm.js} +1 -1
- package/dist/client/assets/channel-BoXSn7FH.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-Bf4YGJ3F.js → chunk-2Q5K7J3B-07zEMyec.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-B-kMdGHl.js → chunk-32BRIVSS-BIk-_oIf.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Ce8Cqwk_.js → chunk-5VM5RSS4-BzhNbnWD.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BPBbbSD3.js → chunk-EX3LRPZG-t9bReK7N.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-BXIFurSn.js → chunk-JWPE2WC7-CLSDgcjE.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-CYWJVxLV.js → chunk-MOJQB5TN-B5T4ivkz.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-S9f9wct-.js → chunk-RYQCIY6F-BS3eFWQI.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-BV7bx9fJ.js → chunk-V7JOEXUC-CJF3jzYE.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-rMA-UjjY.js → chunk-VR4S4FIN-DU-SHKYW.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-Ckst7G5N.js → chunk-XXDRQBXY-CobNAbcK.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-C26JutgZ.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-C26JutgZ.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CrbATiXx.js → cose-bilkent-JH36ORCC-B79BNIyG.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-BZ-R3La7.js → cynefin-VYW2F7L2-CXICklHM.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-BtIXk4X7.js → cynefinDiagram-TSTJHNR4-DyTkCIbt.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-DURezupR.js → dagre-VKFMJZFB-DcVOWzSf.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-CsoGSzC5.js → diagram-FQU43EPY-DZYk_nYw.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-QuB2r0Vg.js → diagram-G47NLZAW-DVA6NeKc.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-BhZeAlmq.js → diagram-NH7WQ7WH-uKF70Mhc.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-BT1Mmddm.js → diagram-OA4YK3LP-CkN9kgIx.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-SHNc7tUN.js → diagram-WEI45ONY-DpxBX964.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-CJYipYZ-.js → ebnfDiagram-CCIWWBDH-CE2I50iL.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-CkZOpI-I.js → erDiagram-Q63AITRT-B9YVjL3f.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-BY81IcpE.js → flowDiagram-23GEKE2U-DdJSODkn.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-Db20bqVZ.js → ganttDiagram-NO4QXBWP-BAJm8N7S.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-kW_pwwxZ.js → gitGraphDiagram-IHSO6WYX-BidRfnwi.js} +1 -1
- package/dist/client/assets/index-BpANqDLk.css +1 -0
- package/dist/client/assets/index-DHJJPt2f.js +428 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-BgA-e5yO.js → infoDiagram-FWYZ7A6U-BeNR07Sb.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-BXm4cs64.js → ishikawaDiagram-FXEZZL3T-DNDoTEWX.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-BKLJTXOT.js → journeyDiagram-5HDEW3XC-ChVhQ3fS.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-BqG2vBDj.js → kanban-definition-HUTT4EX6-BbXFe0q5.js} +1 -1
- package/dist/client/assets/{linear-Cd3N-C-u.js → linear-E-rJq47Y.js} +1 -1
- package/dist/client/assets/{mermaid.core-CTF-egug.js → mermaid.core-DjsjqJ8C.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-DtcrNWwo.js → mindmap-definition-LN4V7U3C-VFDIOJEA.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-ClbJdO1u.js → pegDiagram-2B236MQR-CHG4KUqq.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-DM0_Ei5k.js → pieDiagram-ENE6RG2P-B5TjQCWy.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-j5oZ1Yp6.js → quadrantDiagram-ABIIQ3AL-DVUvdogx.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-DLWubqvc.js → railroadDiagram-RFXS5EU6-CoIP0_iy.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-Uj3DMCA0.js → requirementDiagram-TGXJPOKE-Bvjo3ha6.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-CKEuv1WM.js → sankeyDiagram-HTMAVEWB-pxTZHAYO.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-DrO9nw6k.js → sequenceDiagram-DBY2YBRQ-DAI2QZkb.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-B1YO3j08.js → sizeCapture-X5ZJPWSS-C6GqdPZu.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-BWlXpdah.js → stateDiagram-2N3HPSRC-BFWLhcdy.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BE3-8lFK.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-wQX0-mMC.js → swimlanes-5IMT3BWC-BHbh9wxz.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-BXs0aUO4.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-Del9sQNG.js → timeline-definition-FHXFAJF6-Cy-efeIk.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BPiPBIT9.js → vennDiagram-L72KCM5P-Dyt_IAiy.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-CMk3mt0q.js → wardleyDiagram-EHGQE667-D7vEeOqf.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-B-8ypIu6.js → xychartDiagram-FW5EYKEG-CyUEG84c.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw-notifications.js +78 -0
- package/dist/client/sw.js +1 -1
- package/dist/server/cli.js +3 -3
- package/dist/server/entry.js +17 -0
- package/dist/server/notifications/pushService.js +136 -0
- package/dist/server/routes/notifications.js +75 -0
- package/dist/server/routes/terminal.js +10 -3
- package/dist/server/utils/settings.js +9 -0
- package/dist/server/utils/version.js +33 -0
- package/package.json +12 -1
- package/scripts/prepare-desktop-runtime.mjs +200 -0
- package/scripts/prepare-desktop-tokens.mjs +35 -0
- package/dist/client/assets/channel-Ba_VIQg_.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-BuY9UlR6.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-BuY9UlR6.js +0 -1
- package/dist/client/assets/index-V98UNkyI.css +0 -1
- package/dist/client/assets/index-o3mSVtfJ.js +0 -422
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Cyw-2qEK.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-DGZQ-K3m.js +0 -8
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import webpush from 'web-push';
|
|
5
|
+
const storeDirectory = path.join(os.homedir(), '.termdock');
|
|
6
|
+
const storePath = path.join(storeDirectory, 'push-notifications.json');
|
|
7
|
+
let store = null;
|
|
8
|
+
function persistStore(next) {
|
|
9
|
+
fs.mkdirSync(storeDirectory, { recursive: true, mode: 0o700 });
|
|
10
|
+
const temporaryPath = `${storePath}.${process.pid}.tmp`;
|
|
11
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
12
|
+
fs.renameSync(temporaryPath, storePath);
|
|
13
|
+
}
|
|
14
|
+
function readStore() {
|
|
15
|
+
if (store)
|
|
16
|
+
return store;
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(fs.readFileSync(storePath, 'utf8'));
|
|
19
|
+
if (parsed.version === 1
|
|
20
|
+
&& typeof parsed.vapid?.publicKey === 'string'
|
|
21
|
+
&& typeof parsed.vapid?.privateKey === 'string'
|
|
22
|
+
&& Array.isArray(parsed.subscriptions)) {
|
|
23
|
+
store = parsed;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// First run or damaged state: create a stable local key pair.
|
|
28
|
+
}
|
|
29
|
+
if (!store) {
|
|
30
|
+
store = {
|
|
31
|
+
version: 1,
|
|
32
|
+
vapid: webpush.generateVAPIDKeys(),
|
|
33
|
+
subscriptions: [],
|
|
34
|
+
};
|
|
35
|
+
persistStore(store);
|
|
36
|
+
}
|
|
37
|
+
webpush.setVapidDetails('mailto:push@termdock.local', store.vapid.publicKey, store.vapid.privateKey);
|
|
38
|
+
return store;
|
|
39
|
+
}
|
|
40
|
+
export function getVapidPublicKey() {
|
|
41
|
+
return readStore().vapid.publicKey;
|
|
42
|
+
}
|
|
43
|
+
export function getPushSubscription(clientId) {
|
|
44
|
+
return readStore().subscriptions.find((entry) => entry.clientId === clientId) ?? null;
|
|
45
|
+
}
|
|
46
|
+
export function upsertPushSubscription(clientId, subscription) {
|
|
47
|
+
const current = readStore();
|
|
48
|
+
const next = { ...subscription, clientId, updatedAt: Date.now() };
|
|
49
|
+
current.subscriptions = current.subscriptions.filter((entry) => entry.clientId !== clientId && entry.endpoint !== subscription.endpoint);
|
|
50
|
+
current.subscriptions.push(next);
|
|
51
|
+
persistStore(current);
|
|
52
|
+
return next;
|
|
53
|
+
}
|
|
54
|
+
export function updatePushPreferences(clientId, preferences) {
|
|
55
|
+
const current = readStore();
|
|
56
|
+
const subscription = current.subscriptions.find((entry) => entry.clientId === clientId);
|
|
57
|
+
if (!subscription)
|
|
58
|
+
return null;
|
|
59
|
+
Object.assign(subscription, preferences, { updatedAt: Date.now() });
|
|
60
|
+
persistStore(current);
|
|
61
|
+
return subscription;
|
|
62
|
+
}
|
|
63
|
+
export function removePushSubscription(clientId, endpoint) {
|
|
64
|
+
const current = readStore();
|
|
65
|
+
const previousLength = current.subscriptions.length;
|
|
66
|
+
current.subscriptions = current.subscriptions.filter((entry) => entry.clientId !== clientId && (!endpoint || entry.endpoint !== endpoint));
|
|
67
|
+
if (current.subscriptions.length !== previousLength)
|
|
68
|
+
persistStore(current);
|
|
69
|
+
}
|
|
70
|
+
async function sendPayload(payload) {
|
|
71
|
+
const current = readStore();
|
|
72
|
+
const invalidEndpoints = new Set();
|
|
73
|
+
await Promise.all(current.subscriptions.filter((entry) => entry.aiEnabled).map(async (entry) => {
|
|
74
|
+
try {
|
|
75
|
+
const subscription = {
|
|
76
|
+
endpoint: entry.endpoint,
|
|
77
|
+
expirationTime: entry.expirationTime,
|
|
78
|
+
keys: entry.keys,
|
|
79
|
+
};
|
|
80
|
+
await webpush.sendNotification(subscription, JSON.stringify({
|
|
81
|
+
...payload,
|
|
82
|
+
alertStyle: entry.alertStyle,
|
|
83
|
+
locale: entry.locale,
|
|
84
|
+
}), {
|
|
85
|
+
TTL: 60 * 60,
|
|
86
|
+
urgency: payload.kind === 'waiting' ? 'high' : 'normal',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const statusCode = error.statusCode;
|
|
91
|
+
if (statusCode === 404 || statusCode === 410) {
|
|
92
|
+
invalidEndpoints.add(entry.endpoint);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.warn('[push] Delivery failed:', error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
if (invalidEndpoints.size > 0) {
|
|
100
|
+
current.subscriptions = current.subscriptions.filter((entry) => !invalidEndpoints.has(entry.endpoint));
|
|
101
|
+
persistStore(current);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function notifyAgentTransition(sessionId, previous, current) {
|
|
105
|
+
if (!shouldNotifyAgentTransition(previous, current))
|
|
106
|
+
return;
|
|
107
|
+
const agentExited = current.agentStatus === null && previous?.agentStatus === 'working';
|
|
108
|
+
const kind = agentExited ? 'exited' : current.agentStatus;
|
|
109
|
+
const agentName = current.agent?.displayName ?? previous?.agent?.displayName ?? 'Agent';
|
|
110
|
+
const title = kind === 'waiting'
|
|
111
|
+
? `${agentName} 需要你的处理`
|
|
112
|
+
: kind === 'done'
|
|
113
|
+
? `${agentName} 已完成`
|
|
114
|
+
: `${agentName} 已退出`;
|
|
115
|
+
void sendPayload({
|
|
116
|
+
kind,
|
|
117
|
+
title,
|
|
118
|
+
body: current.agentMessage ?? (kind === 'waiting' ? '点按返回对应会话' : '点按查看结果'),
|
|
119
|
+
tag: `agent:${sessionId}:${kind}:${current.agentActivity}`,
|
|
120
|
+
sessionId,
|
|
121
|
+
url: `/?session=${encodeURIComponent(sessionId)}`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
export function shouldNotifyAgentTransition(previous, current) {
|
|
125
|
+
if (!previous)
|
|
126
|
+
return false;
|
|
127
|
+
const needsAttention = current.reviewed !== true
|
|
128
|
+
&& (current.agentStatus === 'waiting' || current.agentStatus === 'done');
|
|
129
|
+
const attentionChanged = previous.agentStatus !== current.agentStatus
|
|
130
|
+
|| previous.agentActivity !== current.agentActivity
|
|
131
|
+
|| previous.reviewed === true;
|
|
132
|
+
const agentExited = current.agentStatus === null
|
|
133
|
+
&& previous.agentStatus === 'working'
|
|
134
|
+
&& current.reviewed !== true;
|
|
135
|
+
return (needsAttention && attentionChanged) || agentExited;
|
|
136
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { getPushSubscription, getVapidPublicKey, removePushSubscription, updatePushPreferences, upsertPushSubscription, } from '../notifications/pushService.js';
|
|
3
|
+
const router = Router();
|
|
4
|
+
function clientIdFromRequest(req) {
|
|
5
|
+
return typeof req.cookies?.['termdock-client'] === 'string' ? req.cookies['termdock-client'] : '';
|
|
6
|
+
}
|
|
7
|
+
function validAlertStyle(value) {
|
|
8
|
+
return value === 'normal' || value === 'quiet' || value === 'persistent';
|
|
9
|
+
}
|
|
10
|
+
router.get('/status', (req, res) => {
|
|
11
|
+
const subscription = getPushSubscription(clientIdFromRequest(req));
|
|
12
|
+
res.json({
|
|
13
|
+
publicKey: getVapidPublicKey(),
|
|
14
|
+
subscribed: Boolean(subscription),
|
|
15
|
+
subscription: subscription
|
|
16
|
+
? {
|
|
17
|
+
endpoint: subscription.endpoint,
|
|
18
|
+
expirationTime: subscription.expirationTime,
|
|
19
|
+
aiEnabled: subscription.aiEnabled,
|
|
20
|
+
alertStyle: subscription.alertStyle,
|
|
21
|
+
locale: subscription.locale,
|
|
22
|
+
updatedAt: subscription.updatedAt,
|
|
23
|
+
}
|
|
24
|
+
: null,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
router.post('/subscribe', (req, res) => {
|
|
28
|
+
const clientId = clientIdFromRequest(req);
|
|
29
|
+
const { subscription, aiEnabled, alertStyle, locale } = req.body ?? {};
|
|
30
|
+
if (!clientId
|
|
31
|
+
|| typeof subscription?.endpoint !== 'string'
|
|
32
|
+
|| subscription.endpoint.length > 4096
|
|
33
|
+
|| !subscription.endpoint.startsWith('https://')
|
|
34
|
+
|| typeof subscription?.keys?.p256dh !== 'string'
|
|
35
|
+
|| typeof subscription?.keys?.auth !== 'string'
|
|
36
|
+
|| subscription.keys.p256dh.length > 1024
|
|
37
|
+
|| subscription.keys.auth.length > 512
|
|
38
|
+
|| !validAlertStyle(alertStyle)) {
|
|
39
|
+
res.status(400).json({ error: 'Invalid push subscription' });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const saved = upsertPushSubscription(clientId, {
|
|
43
|
+
endpoint: subscription.endpoint,
|
|
44
|
+
expirationTime: typeof subscription.expirationTime === 'number'
|
|
45
|
+
? subscription.expirationTime
|
|
46
|
+
: null,
|
|
47
|
+
keys: {
|
|
48
|
+
p256dh: subscription.keys.p256dh,
|
|
49
|
+
auth: subscription.keys.auth,
|
|
50
|
+
},
|
|
51
|
+
aiEnabled: aiEnabled !== false,
|
|
52
|
+
alertStyle,
|
|
53
|
+
locale: typeof locale === 'string' ? locale.slice(0, 32) : 'zh-CN',
|
|
54
|
+
});
|
|
55
|
+
res.json({ ok: true, updatedAt: saved.updatedAt });
|
|
56
|
+
});
|
|
57
|
+
router.post('/preferences', (req, res) => {
|
|
58
|
+
const { aiEnabled, alertStyle, locale } = req.body ?? {};
|
|
59
|
+
if (typeof aiEnabled !== 'boolean' || !validAlertStyle(alertStyle)) {
|
|
60
|
+
res.status(400).json({ error: 'Invalid notification preferences' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const updated = updatePushPreferences(clientIdFromRequest(req), {
|
|
64
|
+
aiEnabled,
|
|
65
|
+
alertStyle,
|
|
66
|
+
locale: typeof locale === 'string' ? locale.slice(0, 32) : 'zh-CN',
|
|
67
|
+
});
|
|
68
|
+
res.status(updated ? 200 : 404).json({ ok: Boolean(updated) });
|
|
69
|
+
});
|
|
70
|
+
router.post('/unsubscribe', (req, res) => {
|
|
71
|
+
const endpoint = typeof req.body?.endpoint === 'string' ? req.body.endpoint : undefined;
|
|
72
|
+
removePushSubscription(clientIdFromRequest(req), endpoint);
|
|
73
|
+
res.json({ ok: true });
|
|
74
|
+
});
|
|
75
|
+
export default router;
|
|
@@ -14,7 +14,7 @@ import { getPtyHostManager } from '../ptyhost/manager.js';
|
|
|
14
14
|
import { pathValidator } from '../utils/pathValidator.js';
|
|
15
15
|
import { TERMINAL, TMUX } from '../config.js';
|
|
16
16
|
import { localAccessManager } from '../utils/localAccess.js';
|
|
17
|
-
import { normalizeLocalAccessName, getLocaleSetting, setLocaleSetting } from '../utils/settings.js';
|
|
17
|
+
import { normalizeLocalAccessName, getActivityReorderSetting, getLocaleSetting, setActivityReorderSetting, setLocaleSetting, } from '../utils/settings.js';
|
|
18
18
|
import { getOnboardingServerUrl } from '../onboardingServer.js';
|
|
19
19
|
import { getFocusSequence, removeClientFocusState, scanFocusTrackingMode, setClientFocusState, } from '../utils/tmuxFocus.js';
|
|
20
20
|
import { extractProgramLabelFromArgs, normalizeProgramName, normalizeTmuxMetadataProgram, selectTmuxForegroundProgram, tmuxMetadataChanged, } from '../utils/tmuxProgramDetection.js';
|
|
@@ -23,6 +23,7 @@ import { applyAgentEvent, defaultAgentSessionState, parseAgentEvent, } from '../
|
|
|
23
23
|
import { listAllHookAgents, refreshStaleHooksAtLaunch, installHooksForSlug, uninstallHooksForSlug, } from '../agent/installers.js';
|
|
24
24
|
import { loadPlugins, savePlugin, removePlugin, readPluginIcon, } from '../agent/plugins.js';
|
|
25
25
|
import { registerPluginAgents } from '../agent/registry.js';
|
|
26
|
+
import { notifyAgentTransition } from '../notifications/pushService.js';
|
|
26
27
|
const router = express.Router();
|
|
27
28
|
const execFileAsync = promisify(execFile);
|
|
28
29
|
const TERMDOCK_DIR = `${os.homedir()}/.termdock`;
|
|
@@ -112,7 +113,7 @@ const ACTIVITY_REORDER_COOLDOWN_MS = 5_000;
|
|
|
112
113
|
const ACTIVITY_REORDER_PERIODIC_MS = 30_000;
|
|
113
114
|
let activityReorderTimer = null;
|
|
114
115
|
let activityReorderPeriodicTimer = null;
|
|
115
|
-
let activityReorderEnabled =
|
|
116
|
+
let activityReorderEnabled = getActivityReorderSetting();
|
|
116
117
|
function getEffectiveLastActivity(session) {
|
|
117
118
|
// 只用 PTY 输出时间判断「最近活跃」——resize / input / connect
|
|
118
119
|
// 不参与排序,避免拖窗口尺寸等非输出操作导致 session 跳到前面。
|
|
@@ -2113,10 +2114,15 @@ let lastAgentStatusSnapshots = new Map();
|
|
|
2113
2114
|
function broadcastAgentStatus(sessionId, session, force = false) {
|
|
2114
2115
|
const payload = buildAgentStatusPayload(sessionId, session);
|
|
2115
2116
|
const snapshot = JSON.stringify(payload);
|
|
2116
|
-
|
|
2117
|
+
const previousSnapshot = lastAgentStatusSnapshots.get(sessionId);
|
|
2118
|
+
if (!force && previousSnapshot === snapshot)
|
|
2117
2119
|
return;
|
|
2118
2120
|
lastAgentStatusSnapshots.set(sessionId, snapshot);
|
|
2119
2121
|
broadcastEvent(sessionId, payload);
|
|
2122
|
+
const previous = previousSnapshot
|
|
2123
|
+
? JSON.parse(previousSnapshot)
|
|
2124
|
+
: null;
|
|
2125
|
+
notifyAgentTransition(sessionId, previous, payload);
|
|
2120
2126
|
}
|
|
2121
2127
|
/** Persist the info needed to resume this pane's agent conversation after the
|
|
2122
2128
|
* agent exits / the server restarts (last-known semantics, debounced). */
|
|
@@ -4132,6 +4138,7 @@ router.get('/settings/activity-reorder', (_req, res) => {
|
|
|
4132
4138
|
router.put('/settings/activity-reorder', (req, res) => {
|
|
4133
4139
|
const body = req.body ?? {};
|
|
4134
4140
|
if (typeof body.enabled === 'boolean') {
|
|
4141
|
+
setActivityReorderSetting(body.enabled);
|
|
4135
4142
|
activityReorderEnabled = body.enabled;
|
|
4136
4143
|
// 关闭后立刻停掉还在队列里的 reorder timer
|
|
4137
4144
|
if (!activityReorderEnabled && activityReorderTimer) {
|
|
@@ -51,6 +51,7 @@ function normalizeSettings(value) {
|
|
|
51
51
|
return {
|
|
52
52
|
version: 1,
|
|
53
53
|
preventSleep: raw.preventSleep === true,
|
|
54
|
+
activityReorderEnabled: raw.activityReorderEnabled !== false,
|
|
54
55
|
localAccess: normalizeLocalAccessSettings(raw.localAccess),
|
|
55
56
|
firstRunCompleted: raw.firstRunCompleted === true,
|
|
56
57
|
locale: typeof raw.locale === 'string' && raw.locale === 'zh' ? 'zh' : 'en',
|
|
@@ -127,6 +128,14 @@ export async function setPreventSleepSettingAsync(enabled) {
|
|
|
127
128
|
settings.preventSleep = enabled;
|
|
128
129
|
});
|
|
129
130
|
}
|
|
131
|
+
export function getActivityReorderSetting() {
|
|
132
|
+
return loadSettings().activityReorderEnabled;
|
|
133
|
+
}
|
|
134
|
+
export function setActivityReorderSetting(enabled) {
|
|
135
|
+
return updateSettings((settings) => {
|
|
136
|
+
settings.activityReorderEnabled = enabled;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
130
139
|
export function getLocalAccessSetting() {
|
|
131
140
|
return loadSettings().localAccess;
|
|
132
141
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
export const TERMDOCK_PROTOCOL_VERSION = 1;
|
|
6
|
+
export const TERMDOCK_CAPABILITIES = [
|
|
7
|
+
'auth-cookie',
|
|
8
|
+
'client-state-ws',
|
|
9
|
+
'desktop-service-discovery',
|
|
10
|
+
'filesystem',
|
|
11
|
+
'local-api',
|
|
12
|
+
'terminal-ws',
|
|
13
|
+
'tmux',
|
|
14
|
+
];
|
|
15
|
+
export function getTermdockVersion() {
|
|
16
|
+
const override = process.env.TERMDOCK_VERSION?.trim();
|
|
17
|
+
if (override)
|
|
18
|
+
return override;
|
|
19
|
+
try {
|
|
20
|
+
const require_ = createRequire(import.meta.url);
|
|
21
|
+
const packagePath = path.resolve(currentDir, '..', '..', '..', 'package.json');
|
|
22
|
+
const packageJson = require_(packagePath);
|
|
23
|
+
if (typeof packageJson.version === 'string' && packageJson.version.trim()) {
|
|
24
|
+
return packageJson.version.trim();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Packagers may omit the root package file. The desktop launcher sets
|
|
29
|
+
// TERMDOCK_VERSION explicitly, so this is only a backwards-compatible
|
|
30
|
+
// fallback for unusual distributions.
|
|
31
|
+
}
|
|
32
|
+
return '0.0.0';
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termdock",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.35",
|
|
4
4
|
"description": "A complete web-based terminal application with modern UI",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": ".desktop-dist/main.js",
|
|
6
7
|
"bin": {
|
|
7
8
|
"termdock": "dist/server/cli.js",
|
|
8
9
|
"td": "dist/server/cli.js"
|
|
@@ -22,6 +23,10 @@
|
|
|
22
23
|
"dev:client": "vite",
|
|
23
24
|
"dev:server": "cross-env PORT=9835 tsx watch src/server/entry.ts",
|
|
24
25
|
"build": "npm run clean && npm run build:client && npm run build:server",
|
|
26
|
+
"build:desktop": "node scripts/prepare-desktop-tokens.mjs && tsc -p tsconfig.desktop.json",
|
|
27
|
+
"desktop:dev": "npm run build && npm run build:desktop && electron-forge start",
|
|
28
|
+
"desktop:prepare": "npm run build && npm run build:desktop && node scripts/prepare-desktop-runtime.mjs",
|
|
29
|
+
"desktop:make": "npm run desktop:prepare && electron-forge make",
|
|
25
30
|
"build:client": "vite build",
|
|
26
31
|
"build:server": "tsc -p tsconfig.server.json && node -e \"require('fs').chmodSync('dist/server/cli.js', 0o755)\"",
|
|
27
32
|
"generate:pwa-assets": "pwa-assets-generator --config pwa-assets.config.ts",
|
|
@@ -76,10 +81,14 @@
|
|
|
76
81
|
"swiper": "^12.1.3",
|
|
77
82
|
"tailwind-merge": "^2.2.0",
|
|
78
83
|
"uuid": "^13.0.0",
|
|
84
|
+
"web-push": "^3.6.7",
|
|
79
85
|
"ws": "^8.21.0",
|
|
80
86
|
"zustand": "^4.5.0"
|
|
81
87
|
},
|
|
82
88
|
"devDependencies": {
|
|
89
|
+
"@electron-forge/cli": "^7.11.2",
|
|
90
|
+
"@electron-forge/maker-dmg": "^7.11.2",
|
|
91
|
+
"@electron-forge/maker-zip": "^7.11.2",
|
|
83
92
|
"@testing-library/react": "^14.1.0",
|
|
84
93
|
"@testing-library/user-event": "^14.5.0",
|
|
85
94
|
"@types/busboy": "^1.5.4",
|
|
@@ -88,12 +97,14 @@
|
|
|
88
97
|
"@types/react": "^18.2.0",
|
|
89
98
|
"@types/react-dom": "^18.2.0",
|
|
90
99
|
"@types/uuid": "^10.0.0",
|
|
100
|
+
"@types/web-push": "^3.6.4",
|
|
91
101
|
"@types/ws": "^8.18.1",
|
|
92
102
|
"@vite-pwa/assets-generator": "^1.0.2",
|
|
93
103
|
"@vitejs/plugin-react": "^4.2.0",
|
|
94
104
|
"autoprefixer": "^10.4.0",
|
|
95
105
|
"concurrently": "^8.2.0",
|
|
96
106
|
"cross-env": "^10.1.0",
|
|
107
|
+
"electron": "^43.2.0",
|
|
97
108
|
"eslint": "^9.39.2",
|
|
98
109
|
"jsdom": "^24.0.0",
|
|
99
110
|
"postcss": "^8.4.0",
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const root = path.resolve(scriptDir, '..');
|
|
10
|
+
const stage = path.join(root, '.desktop-runtime');
|
|
11
|
+
const runtimeDir = path.join(stage, 'runtime');
|
|
12
|
+
const serverDir = path.join(stage, 'server');
|
|
13
|
+
const toolchainDir = path.join(stage, 'toolchain');
|
|
14
|
+
const toolchainBinDir = path.join(toolchainDir, 'bin');
|
|
15
|
+
const toolchainLibDir = path.join(toolchainDir, 'lib');
|
|
16
|
+
const nodeDownloadDir = path.join(stage, 'node-download');
|
|
17
|
+
const desktopBuildNodeMajor = 22;
|
|
18
|
+
|
|
19
|
+
const buildNodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
20
|
+
if (buildNodeMajor !== desktopBuildNodeMajor) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`macOS desktop packaging requires Node.js ${desktopBuildNodeMajor}.x; `
|
|
23
|
+
+ `current build runtime is ${process.version}.`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function run(command, args, options = {}) {
|
|
28
|
+
const result = spawnSync(command, args, {
|
|
29
|
+
cwd: options.cwd ?? root,
|
|
30
|
+
env: options.env ?? process.env,
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
stdio: options.stdio ?? 'inherit',
|
|
33
|
+
});
|
|
34
|
+
if (result.status !== 0) {
|
|
35
|
+
throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}`);
|
|
36
|
+
}
|
|
37
|
+
return result.stdout ?? '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function executableOnPath(name) {
|
|
41
|
+
if (process.platform === 'darwin') {
|
|
42
|
+
for (const candidate of [
|
|
43
|
+
`/opt/homebrew/bin/${name}`,
|
|
44
|
+
`/usr/local/bin/${name}`,
|
|
45
|
+
]) {
|
|
46
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return execFileSync('/usr/bin/which', [name], { encoding: 'utf8' }).trim() || null;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isSystemLibrary(libraryPath) {
|
|
57
|
+
return libraryPath.startsWith('/System/Library/')
|
|
58
|
+
|| libraryPath.startsWith('/usr/lib/');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function linkedLibraries(binaryPath) {
|
|
62
|
+
if (process.platform !== 'darwin') return [];
|
|
63
|
+
const output = execFileSync('/usr/bin/otool', ['-L', binaryPath], { encoding: 'utf8' });
|
|
64
|
+
return output
|
|
65
|
+
.split('\n')
|
|
66
|
+
.slice(1)
|
|
67
|
+
.map((line) => line.trim().split(/\s+\(/)[0])
|
|
68
|
+
.filter((entry) => entry && !entry.startsWith('@') && !isSystemLibrary(entry));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function copyMacBinary(sourcePath, destinationPath) {
|
|
72
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
73
|
+
fs.chmodSync(destinationPath, 0o755);
|
|
74
|
+
if (process.platform !== 'darwin') return;
|
|
75
|
+
|
|
76
|
+
const queue = [{ source: sourcePath, destination: destinationPath, inLibDir: false }];
|
|
77
|
+
const copiedLibraries = new Map();
|
|
78
|
+
while (queue.length > 0) {
|
|
79
|
+
const current = queue.shift();
|
|
80
|
+
for (const dependency of linkedLibraries(current.source)) {
|
|
81
|
+
const basename = path.basename(dependency);
|
|
82
|
+
const bundledDependency = path.join(toolchainLibDir, basename);
|
|
83
|
+
if (!copiedLibraries.has(dependency)) {
|
|
84
|
+
if (!fs.existsSync(dependency)) {
|
|
85
|
+
throw new Error(`Missing linked library ${dependency} required by ${current.source}`);
|
|
86
|
+
}
|
|
87
|
+
fs.copyFileSync(dependency, bundledDependency);
|
|
88
|
+
fs.chmodSync(bundledDependency, 0o755);
|
|
89
|
+
copiedLibraries.set(dependency, bundledDependency);
|
|
90
|
+
queue.push({ source: dependency, destination: bundledDependency, inLibDir: true });
|
|
91
|
+
}
|
|
92
|
+
const relativeReference = current.inLibDir
|
|
93
|
+
? `@loader_path/${basename}`
|
|
94
|
+
: `@loader_path/../lib/${basename}`;
|
|
95
|
+
run('/usr/bin/install_name_tool', ['-change', dependency, relativeReference, current.destination]);
|
|
96
|
+
}
|
|
97
|
+
if (current.inLibDir) {
|
|
98
|
+
run('/usr/bin/install_name_tool', ['-id', `@loader_path/${path.basename(current.destination)}`, current.destination]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function makeSpawnHelpersExecutable(directory) {
|
|
104
|
+
if (!fs.existsSync(directory)) return;
|
|
105
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
106
|
+
const entryPath = path.join(directory, entry.name);
|
|
107
|
+
if (entry.isDirectory()) {
|
|
108
|
+
makeSpawnHelpersExecutable(entryPath);
|
|
109
|
+
} else if (entry.isFile() && entry.name === 'spawn-helper') {
|
|
110
|
+
fs.chmodSync(entryPath, 0o755);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
116
|
+
fs.mkdirSync(path.join(runtimeDir, 'bin'), { recursive: true });
|
|
117
|
+
fs.mkdirSync(serverDir, { recursive: true });
|
|
118
|
+
fs.mkdirSync(toolchainBinDir, { recursive: true });
|
|
119
|
+
fs.mkdirSync(toolchainLibDir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
const runtimeNode = path.join(runtimeDir, 'bin', 'node');
|
|
122
|
+
const nodePlatform = process.platform === 'win32' ? 'win' : process.platform;
|
|
123
|
+
const nodeArch = process.arch === 'x64' ? 'x64' : process.arch;
|
|
124
|
+
const nodeArchiveExt = process.platform === 'win32' ? 'zip' : 'tar.gz';
|
|
125
|
+
const nodeArchiveName = `node-${process.version}-${nodePlatform}-${nodeArch}.${nodeArchiveExt}`;
|
|
126
|
+
const nodeArchiveUrl = `https://nodejs.org/dist/${process.version}/${nodeArchiveName}`;
|
|
127
|
+
fs.mkdirSync(nodeDownloadDir, { recursive: true });
|
|
128
|
+
const nodeArchivePath = path.join(nodeDownloadDir, nodeArchiveName);
|
|
129
|
+
run('/usr/bin/curl', ['-fL', '--retry', '4', '--output', nodeArchivePath, nodeArchiveUrl]);
|
|
130
|
+
if (nodeArchiveExt !== 'tar.gz') {
|
|
131
|
+
throw new Error(`Desktop runtime preparation is not implemented for ${process.platform}`);
|
|
132
|
+
}
|
|
133
|
+
run('/usr/bin/tar', ['-xzf', nodeArchivePath, '-C', nodeDownloadDir]);
|
|
134
|
+
const extractedNode = path.join(
|
|
135
|
+
nodeDownloadDir,
|
|
136
|
+
`node-${process.version}-${nodePlatform}-${nodeArch}`,
|
|
137
|
+
'bin',
|
|
138
|
+
'node',
|
|
139
|
+
);
|
|
140
|
+
copyMacBinary(extractedNode, runtimeNode);
|
|
141
|
+
|
|
142
|
+
const rootPackage = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
143
|
+
const runtimePackage = {
|
|
144
|
+
name: 'termdock-desktop-runtime',
|
|
145
|
+
version: rootPackage.version,
|
|
146
|
+
private: true,
|
|
147
|
+
type: 'module',
|
|
148
|
+
dependencies: rootPackage.dependencies,
|
|
149
|
+
};
|
|
150
|
+
fs.writeFileSync(path.join(serverDir, 'package.json'), `${JSON.stringify(runtimePackage, null, 2)}\n`);
|
|
151
|
+
fs.cpSync(path.join(root, 'dist'), path.join(serverDir, 'dist'), { recursive: true });
|
|
152
|
+
|
|
153
|
+
run(process.platform === 'win32' ? 'npm.cmd' : 'npm', [
|
|
154
|
+
'install',
|
|
155
|
+
'--omit=dev',
|
|
156
|
+
'--no-audit',
|
|
157
|
+
'--no-fund',
|
|
158
|
+
], {
|
|
159
|
+
cwd: serverDir,
|
|
160
|
+
env: {
|
|
161
|
+
...process.env,
|
|
162
|
+
PATH: `${path.dirname(process.execPath)}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
makeSpawnHelpersExecutable(path.join(serverDir, 'node_modules', 'node-pty'));
|
|
167
|
+
run(runtimeNode, [
|
|
168
|
+
'-e',
|
|
169
|
+
[
|
|
170
|
+
"const {createRequire}=require('node:module')",
|
|
171
|
+
"const req=createRequire(process.cwd()+'/desktop-runtime-probe.cjs')",
|
|
172
|
+
"const pty=req('node-pty')",
|
|
173
|
+
"const child=pty.spawn(process.platform==='win32'?'cmd.exe':'/bin/sh',process.platform==='win32'?['/d','/s','/c','echo termdock-desktop-pty-ok']:['-lc','printf termdock-desktop-pty-ok'],{name:'xterm',cols:80,rows:24})",
|
|
174
|
+
"let output=''",
|
|
175
|
+
"child.onData((data)=>{output+=data})",
|
|
176
|
+
"child.onExit(()=>{if(!output.includes('termdock-desktop-pty-ok')){console.error(output);process.exit(1)}})",
|
|
177
|
+
].join(';'),
|
|
178
|
+
], { cwd: serverDir });
|
|
179
|
+
|
|
180
|
+
for (const name of ['tmux', 'rg', 'git', 'mkcert']) {
|
|
181
|
+
const source = executableOnPath(name);
|
|
182
|
+
if (!source) {
|
|
183
|
+
console.warn(`[desktop-runtime] optional bundled tool not found: ${name}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const destination = path.join(toolchainBinDir, name);
|
|
187
|
+
copyMacBinary(source, destination);
|
|
188
|
+
console.log(`[desktop-runtime] bundled ${name} from ${source}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const manifest = {
|
|
192
|
+
version: rootPackage.version,
|
|
193
|
+
platform: process.platform,
|
|
194
|
+
arch: process.arch,
|
|
195
|
+
nodeVersion: process.version,
|
|
196
|
+
createdAt: new Date().toISOString(),
|
|
197
|
+
tools: fs.readdirSync(toolchainBinDir).sort(),
|
|
198
|
+
};
|
|
199
|
+
fs.writeFileSync(path.join(stage, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
200
|
+
console.log(`[desktop-runtime] ready at ${stage}`);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const root = path.resolve(scriptDir, '..');
|
|
7
|
+
const sourcePath = path.join(root, 'src', 'index.css');
|
|
8
|
+
const outputPath = path.join(root, 'desktop', 'renderer', 'tokens.css');
|
|
9
|
+
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
10
|
+
|
|
11
|
+
function extractBlock(selector) {
|
|
12
|
+
const start = source.indexOf(`${selector} {`);
|
|
13
|
+
if (start < 0) throw new Error(`Missing ${selector} token block in ${sourcePath}`);
|
|
14
|
+
let depth = 0;
|
|
15
|
+
let bodyStart = -1;
|
|
16
|
+
for (let index = start; index < source.length; index += 1) {
|
|
17
|
+
if (source[index] === '{') {
|
|
18
|
+
depth += 1;
|
|
19
|
+
if (depth === 1) bodyStart = index + 1;
|
|
20
|
+
} else if (source[index] === '}') {
|
|
21
|
+
depth -= 1;
|
|
22
|
+
if (depth === 0) return `${selector} {${source.slice(bodyStart, index)}}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Unclosed ${selector} token block in ${sourcePath}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const generated = [
|
|
29
|
+
'/* Generated from src/index.css. Do not edit. */',
|
|
30
|
+
extractBlock(':root'),
|
|
31
|
+
extractBlock("html[data-theme='light']"),
|
|
32
|
+
'',
|
|
33
|
+
].join('\n\n');
|
|
34
|
+
|
|
35
|
+
fs.writeFileSync(outputPath, generated);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./mermaid.core-CTF-egug.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-BV7bx9fJ.js";import{b as i}from"./mermaid.core-CTF-egug.js";import"./chunk-5VM5RSS4-Ce8Cqwk_.js";import"./chunk-XXDRQBXY-Ckst7G5N.js";import"./chunk-VR4S4FIN-rMA-UjjY.js";import"./chunk-32BRIVSS-B-kMdGHl.js";import"./purify.es-CC4Brkr_.js";import"./index-o3mSVtfJ.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-BV7bx9fJ.js";import{b as i}from"./mermaid.core-CTF-egug.js";import"./chunk-5VM5RSS4-Ce8Cqwk_.js";import"./chunk-XXDRQBXY-Ckst7G5N.js";import"./chunk-VR4S4FIN-rMA-UjjY.js";import"./chunk-32BRIVSS-B-kMdGHl.js";import"./purify.es-CC4Brkr_.js";import"./index-o3mSVtfJ.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
|