codexmate 0.0.27 → 0.0.28
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 +421 -421
- package/README.zh.md +354 -354
- package/cli/agents-files.js +224 -224
- package/cli/archive-helpers.js +446 -446
- package/cli/auth-profiles.js +375 -375
- package/cli/builtin-proxy.js +1620 -1299
- package/cli/claude-proxy.js +1022 -1022
- package/cli/config-bootstrap.js +384 -384
- package/cli/config-health.js +338 -338
- package/cli/doctor-core.js +903 -903
- package/cli/import-skills-url.js +356 -356
- package/cli/openai-bridge.js +1489 -1091
- package/cli/openclaw-config.js +629 -629
- package/cli/session-convert-args.js +65 -65
- package/cli/session-convert-io.js +82 -82
- package/cli/session-convert.js +43 -43
- package/cli/session-usage.concurrent.js +28 -28
- package/cli/session-usage.js +118 -118
- package/cli/session-usage.models.js +176 -176
- package/cli/skills.js +1141 -1141
- package/cli/zip-commands.js +510 -510
- package/cli.js +15264 -15251
- package/lib/automation.js +404 -404
- package/lib/cli-file-utils.js +151 -151
- package/lib/cli-models-utils.js +440 -379
- package/lib/cli-network-utils.js +190 -190
- package/lib/cli-path-utils.js +85 -85
- package/lib/cli-session-utils.js +121 -121
- package/lib/cli-sessions.js +417 -417
- package/lib/cli-utils.js +155 -155
- package/lib/download-artifacts.js +92 -92
- package/lib/mcp-stdio.js +453 -453
- package/lib/task-orchestrator.js +869 -869
- package/lib/text-diff.js +303 -303
- package/lib/workflow-engine.js +340 -340
- package/package.json +3 -1
- package/plugins/README.md +20 -20
- package/plugins/README.zh-CN.md +20 -20
- package/plugins/prompt-templates/comment-polish/index.mjs +25 -25
- package/plugins/prompt-templates/computed.mjs +253 -253
- package/plugins/prompt-templates/index.mjs +8 -8
- package/plugins/prompt-templates/manifest.mjs +15 -15
- package/plugins/prompt-templates/methods.mjs +553 -619
- package/plugins/prompt-templates/overview.mjs +91 -90
- package/plugins/prompt-templates/ownership.mjs +19 -19
- package/plugins/prompt-templates/rule-ack/index.mjs +21 -21
- package/plugins/prompt-templates/storage.mjs +64 -64
- package/plugins/registry.mjs +16 -16
- package/web-ui/app.js +625 -634
- package/web-ui/index.html +35 -35
- package/web-ui/logic.agents-diff.mjs +386 -386
- package/web-ui/logic.claude.mjs +168 -168
- package/web-ui/logic.codex.mjs +56 -0
- package/web-ui/logic.mjs +5 -5
- package/web-ui/logic.runtime.mjs +128 -128
- package/web-ui/logic.session-convert.mjs +70 -70
- package/web-ui/logic.sessions.mjs +765 -765
- package/web-ui/modules/api.mjs +90 -90
- package/web-ui/modules/app.computed.dashboard.mjs +225 -171
- package/web-ui/modules/app.computed.index.mjs +17 -17
- package/web-ui/modules/app.computed.main-tabs.mjs +205 -205
- package/web-ui/modules/app.computed.session.mjs +994 -994
- package/web-ui/modules/app.constants.mjs +15 -15
- package/web-ui/modules/app.methods.agents.mjs +632 -632
- package/web-ui/modules/app.methods.claude-config.mjs +190 -184
- package/web-ui/modules/app.methods.codex-config.mjs +892 -860
- package/web-ui/modules/app.methods.index.mjs +92 -92
- package/web-ui/modules/app.methods.install.mjs +205 -205
- package/web-ui/modules/app.methods.navigation.mjs +743 -743
- package/web-ui/modules/app.methods.openclaw-core.mjs +814 -814
- package/web-ui/modules/app.methods.openclaw-editing.mjs +372 -372
- package/web-ui/modules/app.methods.openclaw-persist.mjs +369 -369
- package/web-ui/modules/app.methods.providers.mjs +412 -404
- package/web-ui/modules/app.methods.runtime.mjs +345 -345
- package/web-ui/modules/app.methods.session-actions.mjs +593 -596
- package/web-ui/modules/app.methods.session-browser.mjs +984 -989
- package/web-ui/modules/app.methods.session-timeline.mjs +479 -479
- package/web-ui/modules/app.methods.session-trash.mjs +439 -439
- package/web-ui/modules/app.methods.startup-claude.mjs +533 -526
- package/web-ui/modules/app.methods.task-orchestration.mjs +556 -556
- package/web-ui/modules/config-mode.computed.mjs +124 -124
- package/web-ui/modules/config-template-confirm-pref.mjs +33 -33
- package/web-ui/modules/i18n.dict.mjs +2109 -2131
- package/web-ui/modules/i18n.mjs +56 -56
- package/web-ui/modules/plugins.computed.mjs +3 -3
- package/web-ui/modules/plugins.methods.mjs +3 -3
- package/web-ui/modules/plugins.storage.mjs +11 -11
- package/web-ui/modules/provider-url-display.mjs +17 -0
- package/web-ui/modules/sessions-filters-url.mjs +85 -85
- package/web-ui/modules/skills.computed.mjs +107 -107
- package/web-ui/modules/skills.methods.mjs +481 -481
- package/web-ui/partials/index/layout-footer.html +13 -13
- package/web-ui/partials/index/layout-header.html +475 -475
- package/web-ui/partials/index/modal-config-template-agents.html +174 -174
- package/web-ui/partials/index/modal-confirm-toast.html +32 -32
- package/web-ui/partials/index/modal-health-check.html +45 -45
- package/web-ui/partials/index/modal-openclaw-config.html +280 -280
- package/web-ui/partials/index/modal-skills.html +200 -200
- package/web-ui/partials/index/modals-basic.html +165 -165
- package/web-ui/partials/index/panel-config-claude.html +188 -187
- package/web-ui/partials/index/panel-config-codex.html +312 -283
- package/web-ui/partials/index/panel-config-openclaw.html +83 -83
- package/web-ui/partials/index/panel-dashboard.html +186 -186
- package/web-ui/partials/index/panel-docs.html +147 -147
- package/web-ui/partials/index/panel-market.html +177 -177
- package/web-ui/partials/index/panel-orchestration.html +391 -391
- package/web-ui/partials/index/panel-plugins.html +253 -279
- package/web-ui/partials/index/panel-sessions.html +316 -326
- package/web-ui/partials/index/panel-settings.html +253 -274
- package/web-ui/partials/index/panel-usage.html +371 -371
- package/web-ui/res/json5.min.js +1 -1
- package/web-ui/res/vue.global.prod.js +13 -13
- package/web-ui/session-helpers.mjs +576 -576
- package/web-ui/source-bundle.cjs +233 -233
- package/web-ui/styles/base-theme.css +281 -268
- package/web-ui/styles/controls-forms.css +422 -423
- package/web-ui/styles/dashboard.css +274 -274
- package/web-ui/styles/docs-panel.css +247 -247
- package/web-ui/styles/feedback.css +108 -108
- package/web-ui/styles/health-check-dialog.css +144 -144
- package/web-ui/styles/layout-shell.css +606 -603
- package/web-ui/styles/modals-core.css +466 -464
- package/web-ui/styles/navigation-panels.css +391 -390
- package/web-ui/styles/openclaw-structured.css +266 -266
- package/web-ui/styles/plugins-panel.css +523 -523
- package/web-ui/styles/responsive.css +454 -454
- package/web-ui/styles/sessions-list.css +419 -415
- package/web-ui/styles/sessions-preview.css +411 -411
- package/web-ui/styles/sessions-toolbar-trash.css +330 -330
- package/web-ui/styles/sessions-usage.css +1040 -1040
- package/web-ui/styles/settings-panel.css +185 -185
- package/web-ui/styles/skills-list.css +303 -303
- package/web-ui/styles/skills-market.css +406 -406
- package/web-ui/styles/task-orchestration.css +822 -822
- package/web-ui/styles/titles-cards.css +472 -408
- package/web-ui/styles.css +21 -21
- package/web-ui.html +17 -17
package/lib/cli-utils.js
CHANGED
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
const UTF8_BOM = '\ufeff';
|
|
6
|
-
|
|
7
|
-
function expandHomePath(value) {
|
|
8
|
-
if (typeof value !== 'string') {
|
|
9
|
-
return '';
|
|
10
|
-
}
|
|
11
|
-
const trimmed = value.trim();
|
|
12
|
-
if (!trimmed) {
|
|
13
|
-
return '';
|
|
14
|
-
}
|
|
15
|
-
if (trimmed === '~') {
|
|
16
|
-
return os.homedir();
|
|
17
|
-
}
|
|
18
|
-
if (trimmed.startsWith(`~${path.sep}`) || trimmed.startsWith('~/') || trimmed.startsWith('~\\')) {
|
|
19
|
-
return path.resolve(os.homedir(), trimmed.slice(2));
|
|
20
|
-
}
|
|
21
|
-
return trimmed;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function resolveExistingDir(candidates = [], fallback = '') {
|
|
25
|
-
for (const raw of candidates) {
|
|
26
|
-
const candidate = expandHomePath(raw);
|
|
27
|
-
if (!candidate) continue;
|
|
28
|
-
try {
|
|
29
|
-
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
30
|
-
return candidate;
|
|
31
|
-
}
|
|
32
|
-
} catch (e) {}
|
|
33
|
-
}
|
|
34
|
-
return fallback;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function resolveHomePath(input) {
|
|
38
|
-
const raw = typeof input === 'string' ? input.trim() : '';
|
|
39
|
-
if (!raw) return '';
|
|
40
|
-
if (raw === '~') return os.homedir();
|
|
41
|
-
if (raw.startsWith('~/') || raw.startsWith('~\\')) {
|
|
42
|
-
return path.join(os.homedir(), raw.slice(2));
|
|
43
|
-
}
|
|
44
|
-
return raw;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function hasUtf8Bom(text) {
|
|
48
|
-
return typeof text === 'string' && text.charCodeAt(0) === 0xfeff;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function stripUtf8Bom(text) {
|
|
52
|
-
if (!text) return '';
|
|
53
|
-
return hasUtf8Bom(text) ? text.slice(1) : text;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function ensureUtf8Bom(text) {
|
|
57
|
-
const content = typeof text === 'string' ? text : '';
|
|
58
|
-
return hasUtf8Bom(content) ? content : UTF8_BOM + content;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function detectLineEnding(text) {
|
|
62
|
-
return typeof text === 'string' && text.includes('\r\n') ? '\r\n' : '\n';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function normalizeLineEnding(text, lineEnding) {
|
|
66
|
-
const normalized = String(text || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
67
|
-
return lineEnding === '\r\n' ? normalized.replace(/\n/g, '\r\n') : normalized;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function isValidProviderName(name) {
|
|
71
|
-
return typeof name === 'string' && /^[a-zA-Z0-9._-]+$/.test(name.trim());
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function escapeTomlBasicString(value) {
|
|
75
|
-
return String(value || '')
|
|
76
|
-
.replace(/\\/g, '\\\\')
|
|
77
|
-
.replace(/"/g, '\\"');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function buildModelProviderTableHeader(providerName) {
|
|
81
|
-
const raw = typeof providerName === 'string' ? providerName.trim() : '';
|
|
82
|
-
if (/^[a-zA-Z0-9_-]+$/.test(raw)) {
|
|
83
|
-
return `[model_providers.${raw}]`;
|
|
84
|
-
}
|
|
85
|
-
return `[model_providers."${escapeTomlBasicString(raw)}"]`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function buildModelsCandidates(baseUrl) {
|
|
89
|
-
const trimmed = typeof baseUrl === 'string' ? baseUrl.trim() : '';
|
|
90
|
-
if (!trimmed) return [];
|
|
91
|
-
if (/\/models\/?$/.test(trimmed)) {
|
|
92
|
-
return [trimmed];
|
|
93
|
-
}
|
|
94
|
-
const normalized = trimmed.replace(/\/+$/, '');
|
|
95
|
-
const candidates = [];
|
|
96
|
-
const pushUnique = (url) => {
|
|
97
|
-
if (url && !candidates.includes(url)) {
|
|
98
|
-
candidates.push(url);
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
if (/\/v1$/i.test(normalized)) {
|
|
103
|
-
pushUnique(normalized + '/models');
|
|
104
|
-
} else {
|
|
105
|
-
pushUnique(normalized + '/v1/models');
|
|
106
|
-
pushUnique(normalized + '/models');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
pushUnique(trimmed);
|
|
110
|
-
return candidates;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function isValidHttpUrl(value) {
|
|
114
|
-
if (typeof value !== 'string' || !value.trim()) return false;
|
|
115
|
-
try {
|
|
116
|
-
const parsed = new URL(value);
|
|
117
|
-
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
118
|
-
} catch (e) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function normalizeBaseUrl(value) {
|
|
124
|
-
if (typeof value !== 'string') return '';
|
|
125
|
-
return value.trim().replace(/\/+$/g, '');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function joinApiUrl(baseUrl, pathSuffix) {
|
|
129
|
-
const trimmed = normalizeBaseUrl(baseUrl);
|
|
130
|
-
if (!trimmed) return '';
|
|
131
|
-
const safeSuffix = String(pathSuffix || '').replace(/^\/+/g, '');
|
|
132
|
-
if (!safeSuffix) return trimmed;
|
|
133
|
-
if (/\/v1$/i.test(trimmed)) {
|
|
134
|
-
return `${trimmed}/${safeSuffix}`;
|
|
135
|
-
}
|
|
136
|
-
return `${trimmed}/v1/${safeSuffix}`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
module.exports = {
|
|
140
|
-
expandHomePath,
|
|
141
|
-
resolveExistingDir,
|
|
142
|
-
resolveHomePath,
|
|
143
|
-
hasUtf8Bom,
|
|
144
|
-
stripUtf8Bom,
|
|
145
|
-
ensureUtf8Bom,
|
|
146
|
-
detectLineEnding,
|
|
147
|
-
normalizeLineEnding,
|
|
148
|
-
isValidProviderName,
|
|
149
|
-
escapeTomlBasicString,
|
|
150
|
-
buildModelProviderTableHeader,
|
|
151
|
-
buildModelsCandidates,
|
|
152
|
-
isValidHttpUrl,
|
|
153
|
-
normalizeBaseUrl,
|
|
154
|
-
joinApiUrl
|
|
155
|
-
};
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const UTF8_BOM = '\ufeff';
|
|
6
|
+
|
|
7
|
+
function expandHomePath(value) {
|
|
8
|
+
if (typeof value !== 'string') {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
if (!trimmed) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
if (trimmed === '~') {
|
|
16
|
+
return os.homedir();
|
|
17
|
+
}
|
|
18
|
+
if (trimmed.startsWith(`~${path.sep}`) || trimmed.startsWith('~/') || trimmed.startsWith('~\\')) {
|
|
19
|
+
return path.resolve(os.homedir(), trimmed.slice(2));
|
|
20
|
+
}
|
|
21
|
+
return trimmed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveExistingDir(candidates = [], fallback = '') {
|
|
25
|
+
for (const raw of candidates) {
|
|
26
|
+
const candidate = expandHomePath(raw);
|
|
27
|
+
if (!candidate) continue;
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
30
|
+
return candidate;
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {}
|
|
33
|
+
}
|
|
34
|
+
return fallback;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveHomePath(input) {
|
|
38
|
+
const raw = typeof input === 'string' ? input.trim() : '';
|
|
39
|
+
if (!raw) return '';
|
|
40
|
+
if (raw === '~') return os.homedir();
|
|
41
|
+
if (raw.startsWith('~/') || raw.startsWith('~\\')) {
|
|
42
|
+
return path.join(os.homedir(), raw.slice(2));
|
|
43
|
+
}
|
|
44
|
+
return raw;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasUtf8Bom(text) {
|
|
48
|
+
return typeof text === 'string' && text.charCodeAt(0) === 0xfeff;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function stripUtf8Bom(text) {
|
|
52
|
+
if (!text) return '';
|
|
53
|
+
return hasUtf8Bom(text) ? text.slice(1) : text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ensureUtf8Bom(text) {
|
|
57
|
+
const content = typeof text === 'string' ? text : '';
|
|
58
|
+
return hasUtf8Bom(content) ? content : UTF8_BOM + content;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function detectLineEnding(text) {
|
|
62
|
+
return typeof text === 'string' && text.includes('\r\n') ? '\r\n' : '\n';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeLineEnding(text, lineEnding) {
|
|
66
|
+
const normalized = String(text || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
67
|
+
return lineEnding === '\r\n' ? normalized.replace(/\n/g, '\r\n') : normalized;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isValidProviderName(name) {
|
|
71
|
+
return typeof name === 'string' && /^[a-zA-Z0-9._-]+$/.test(name.trim());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function escapeTomlBasicString(value) {
|
|
75
|
+
return String(value || '')
|
|
76
|
+
.replace(/\\/g, '\\\\')
|
|
77
|
+
.replace(/"/g, '\\"');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildModelProviderTableHeader(providerName) {
|
|
81
|
+
const raw = typeof providerName === 'string' ? providerName.trim() : '';
|
|
82
|
+
if (/^[a-zA-Z0-9_-]+$/.test(raw)) {
|
|
83
|
+
return `[model_providers.${raw}]`;
|
|
84
|
+
}
|
|
85
|
+
return `[model_providers."${escapeTomlBasicString(raw)}"]`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildModelsCandidates(baseUrl) {
|
|
89
|
+
const trimmed = typeof baseUrl === 'string' ? baseUrl.trim() : '';
|
|
90
|
+
if (!trimmed) return [];
|
|
91
|
+
if (/\/models\/?$/.test(trimmed)) {
|
|
92
|
+
return [trimmed];
|
|
93
|
+
}
|
|
94
|
+
const normalized = trimmed.replace(/\/+$/, '');
|
|
95
|
+
const candidates = [];
|
|
96
|
+
const pushUnique = (url) => {
|
|
97
|
+
if (url && !candidates.includes(url)) {
|
|
98
|
+
candidates.push(url);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (/\/v1$/i.test(normalized)) {
|
|
103
|
+
pushUnique(normalized + '/models');
|
|
104
|
+
} else {
|
|
105
|
+
pushUnique(normalized + '/v1/models');
|
|
106
|
+
pushUnique(normalized + '/models');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pushUnique(trimmed);
|
|
110
|
+
return candidates;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isValidHttpUrl(value) {
|
|
114
|
+
if (typeof value !== 'string' || !value.trim()) return false;
|
|
115
|
+
try {
|
|
116
|
+
const parsed = new URL(value);
|
|
117
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeBaseUrl(value) {
|
|
124
|
+
if (typeof value !== 'string') return '';
|
|
125
|
+
return value.trim().replace(/\/+$/g, '');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function joinApiUrl(baseUrl, pathSuffix) {
|
|
129
|
+
const trimmed = normalizeBaseUrl(baseUrl);
|
|
130
|
+
if (!trimmed) return '';
|
|
131
|
+
const safeSuffix = String(pathSuffix || '').replace(/^\/+/g, '');
|
|
132
|
+
if (!safeSuffix) return trimmed;
|
|
133
|
+
if (/\/v1$/i.test(trimmed)) {
|
|
134
|
+
return `${trimmed}/${safeSuffix}`;
|
|
135
|
+
}
|
|
136
|
+
return `${trimmed}/v1/${safeSuffix}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
expandHomePath,
|
|
141
|
+
resolveExistingDir,
|
|
142
|
+
resolveHomePath,
|
|
143
|
+
hasUtf8Bom,
|
|
144
|
+
stripUtf8Bom,
|
|
145
|
+
ensureUtf8Bom,
|
|
146
|
+
detectLineEnding,
|
|
147
|
+
normalizeLineEnding,
|
|
148
|
+
isValidProviderName,
|
|
149
|
+
escapeTomlBasicString,
|
|
150
|
+
buildModelProviderTableHeader,
|
|
151
|
+
buildModelsCandidates,
|
|
152
|
+
isValidHttpUrl,
|
|
153
|
+
normalizeBaseUrl,
|
|
154
|
+
joinApiUrl
|
|
155
|
+
};
|
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
|
|
5
|
-
const DEFAULT_DOWNLOAD_ARTIFACT_TTL_MS = 10 * 60 * 1000;
|
|
6
|
-
const MAX_DOWNLOAD_ARTIFACTS = 200;
|
|
7
|
-
const g_downloadArtifacts = new Map();
|
|
8
|
-
|
|
9
|
-
function registerDownloadArtifact(filePath, options = {}) {
|
|
10
|
-
const token = crypto.randomBytes(16).toString('hex');
|
|
11
|
-
const fileName = typeof options.fileName === 'string' && options.fileName.trim()
|
|
12
|
-
? options.fileName.trim()
|
|
13
|
-
: path.basename(filePath || '');
|
|
14
|
-
const ttlMs = Number.isFinite(options.ttlMs) && options.ttlMs > 0
|
|
15
|
-
? Math.floor(options.ttlMs)
|
|
16
|
-
: DEFAULT_DOWNLOAD_ARTIFACT_TTL_MS;
|
|
17
|
-
const expiresAt = Date.now() + ttlMs;
|
|
18
|
-
const deleteAfterDownload = options.deleteAfterDownload !== false;
|
|
19
|
-
|
|
20
|
-
g_downloadArtifacts.set(token, {
|
|
21
|
-
filePath,
|
|
22
|
-
fileName,
|
|
23
|
-
deleteAfterDownload,
|
|
24
|
-
expiresAt
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
while (g_downloadArtifacts.size > MAX_DOWNLOAD_ARTIFACTS) {
|
|
28
|
-
const firstKey = g_downloadArtifacts.keys().next().value;
|
|
29
|
-
if (!firstKey) break;
|
|
30
|
-
const evicted = g_downloadArtifacts.get(firstKey);
|
|
31
|
-
g_downloadArtifacts.delete(firstKey);
|
|
32
|
-
if (evicted && evicted.deleteAfterDownload && evicted.filePath && fs.existsSync(evicted.filePath)) {
|
|
33
|
-
try {
|
|
34
|
-
fs.unlinkSync(evicted.filePath);
|
|
35
|
-
} catch (_) {}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const timer = setTimeout(() => {
|
|
40
|
-
const artifact = g_downloadArtifacts.get(token);
|
|
41
|
-
if (!artifact) return;
|
|
42
|
-
if (Date.now() < artifact.expiresAt) return;
|
|
43
|
-
g_downloadArtifacts.delete(token);
|
|
44
|
-
if (artifact.deleteAfterDownload && artifact.filePath && fs.existsSync(artifact.filePath)) {
|
|
45
|
-
try {
|
|
46
|
-
fs.unlinkSync(artifact.filePath);
|
|
47
|
-
} catch (_) {}
|
|
48
|
-
}
|
|
49
|
-
}, ttlMs + 2000);
|
|
50
|
-
if (timer && typeof timer.unref === 'function') {
|
|
51
|
-
timer.unref();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
token,
|
|
56
|
-
fileName,
|
|
57
|
-
downloadPath: `/download/${encodeURIComponent(token)}`
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function resolveDownloadArtifact(tokenOrFileName, options = {}) {
|
|
62
|
-
if (!tokenOrFileName) return null;
|
|
63
|
-
const token = typeof tokenOrFileName === 'string' ? tokenOrFileName.trim() : '';
|
|
64
|
-
if (!token) return null;
|
|
65
|
-
|
|
66
|
-
const artifact = g_downloadArtifacts.get(token);
|
|
67
|
-
if (!artifact) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
if (Date.now() > artifact.expiresAt) {
|
|
71
|
-
g_downloadArtifacts.delete(token);
|
|
72
|
-
if (artifact.deleteAfterDownload && artifact.filePath && fs.existsSync(artifact.filePath)) {
|
|
73
|
-
try {
|
|
74
|
-
fs.unlinkSync(artifact.filePath);
|
|
75
|
-
} catch (_) {}
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
if (options && options.consume === true) {
|
|
80
|
-
g_downloadArtifacts.delete(token);
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
token,
|
|
84
|
-
...artifact
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
module.exports = {
|
|
89
|
-
DEFAULT_DOWNLOAD_ARTIFACT_TTL_MS,
|
|
90
|
-
registerDownloadArtifact,
|
|
91
|
-
resolveDownloadArtifact
|
|
92
|
-
};
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_DOWNLOAD_ARTIFACT_TTL_MS = 10 * 60 * 1000;
|
|
6
|
+
const MAX_DOWNLOAD_ARTIFACTS = 200;
|
|
7
|
+
const g_downloadArtifacts = new Map();
|
|
8
|
+
|
|
9
|
+
function registerDownloadArtifact(filePath, options = {}) {
|
|
10
|
+
const token = crypto.randomBytes(16).toString('hex');
|
|
11
|
+
const fileName = typeof options.fileName === 'string' && options.fileName.trim()
|
|
12
|
+
? options.fileName.trim()
|
|
13
|
+
: path.basename(filePath || '');
|
|
14
|
+
const ttlMs = Number.isFinite(options.ttlMs) && options.ttlMs > 0
|
|
15
|
+
? Math.floor(options.ttlMs)
|
|
16
|
+
: DEFAULT_DOWNLOAD_ARTIFACT_TTL_MS;
|
|
17
|
+
const expiresAt = Date.now() + ttlMs;
|
|
18
|
+
const deleteAfterDownload = options.deleteAfterDownload !== false;
|
|
19
|
+
|
|
20
|
+
g_downloadArtifacts.set(token, {
|
|
21
|
+
filePath,
|
|
22
|
+
fileName,
|
|
23
|
+
deleteAfterDownload,
|
|
24
|
+
expiresAt
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
while (g_downloadArtifacts.size > MAX_DOWNLOAD_ARTIFACTS) {
|
|
28
|
+
const firstKey = g_downloadArtifacts.keys().next().value;
|
|
29
|
+
if (!firstKey) break;
|
|
30
|
+
const evicted = g_downloadArtifacts.get(firstKey);
|
|
31
|
+
g_downloadArtifacts.delete(firstKey);
|
|
32
|
+
if (evicted && evicted.deleteAfterDownload && evicted.filePath && fs.existsSync(evicted.filePath)) {
|
|
33
|
+
try {
|
|
34
|
+
fs.unlinkSync(evicted.filePath);
|
|
35
|
+
} catch (_) {}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
const artifact = g_downloadArtifacts.get(token);
|
|
41
|
+
if (!artifact) return;
|
|
42
|
+
if (Date.now() < artifact.expiresAt) return;
|
|
43
|
+
g_downloadArtifacts.delete(token);
|
|
44
|
+
if (artifact.deleteAfterDownload && artifact.filePath && fs.existsSync(artifact.filePath)) {
|
|
45
|
+
try {
|
|
46
|
+
fs.unlinkSync(artifact.filePath);
|
|
47
|
+
} catch (_) {}
|
|
48
|
+
}
|
|
49
|
+
}, ttlMs + 2000);
|
|
50
|
+
if (timer && typeof timer.unref === 'function') {
|
|
51
|
+
timer.unref();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
token,
|
|
56
|
+
fileName,
|
|
57
|
+
downloadPath: `/download/${encodeURIComponent(token)}`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveDownloadArtifact(tokenOrFileName, options = {}) {
|
|
62
|
+
if (!tokenOrFileName) return null;
|
|
63
|
+
const token = typeof tokenOrFileName === 'string' ? tokenOrFileName.trim() : '';
|
|
64
|
+
if (!token) return null;
|
|
65
|
+
|
|
66
|
+
const artifact = g_downloadArtifacts.get(token);
|
|
67
|
+
if (!artifact) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (Date.now() > artifact.expiresAt) {
|
|
71
|
+
g_downloadArtifacts.delete(token);
|
|
72
|
+
if (artifact.deleteAfterDownload && artifact.filePath && fs.existsSync(artifact.filePath)) {
|
|
73
|
+
try {
|
|
74
|
+
fs.unlinkSync(artifact.filePath);
|
|
75
|
+
} catch (_) {}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (options && options.consume === true) {
|
|
80
|
+
g_downloadArtifacts.delete(token);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
token,
|
|
84
|
+
...artifact
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
DEFAULT_DOWNLOAD_ARTIFACT_TTL_MS,
|
|
90
|
+
registerDownloadArtifact,
|
|
91
|
+
resolveDownloadArtifact
|
|
92
|
+
};
|