codexmate 0.0.13 → 0.0.15
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.en.md +213 -0
- package/README.md +147 -346
- package/cli.js +3163 -224
- package/doc/CHANGELOG.md +13 -1
- package/doc/CHANGELOG.zh-CN.md +14 -0
- package/lib/cli-utils.js +16 -0
- package/lib/workflow-engine.js +340 -0
- package/package.json +10 -3
- package/web-ui/app.js +275 -65
- package/web-ui/index.html +279 -33
- package/web-ui/logic.mjs +147 -1
- package/web-ui/modules/config-mode.computed.mjs +123 -0
- package/web-ui/modules/skills.computed.mjs +82 -0
- package/web-ui/modules/skills.methods.mjs +344 -0
- package/web-ui/styles.css +648 -10
- package/README.zh-CN.md +0 -419
package/doc/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
# Changelog
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.15
|
|
4
|
+
|
|
5
|
+
- Release: bump package version to 0.0.15
|
|
6
|
+
- Docs: sync README / README.en with current release marker
|
|
7
|
+
|
|
8
|
+
## 0.0.14
|
|
9
|
+
|
|
10
|
+
- Skills Manager: polish modal layout with overview counters and clearer section structure
|
|
11
|
+
- Skills Manager: unify status select style and refine list scrollbar density
|
|
12
|
+
- Docs: sync README / README.en release notes for 0.0.14
|
|
2
13
|
|
|
3
14
|
## 0.0.13
|
|
4
15
|
|
|
@@ -18,3 +29,4 @@
|
|
|
18
29
|
- Added OpenClaw config mode with JSON5 profiles and one-click apply
|
|
19
30
|
- Added OpenClaw workspace AGENTS.md management
|
|
20
31
|
- Added JSON5 parsing dependency
|
|
32
|
+
|
package/doc/CHANGELOG.zh-CN.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 0.0.15
|
|
4
|
+
|
|
5
|
+
- 发版:版本提升至 0.0.15
|
|
6
|
+
- 文档:同步 README / README.en 当前版本标记
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## 0.0.14
|
|
11
|
+
|
|
12
|
+
- Skills 管理:打磨弹窗信息层级,新增统计概览与分区结构
|
|
13
|
+
- Skills 管理:统一状态下拉样式,并优化列表滚动条密度
|
|
14
|
+
- 文档:同步 README / README.en 的 0.0.14 发版说明
|
|
15
|
+
|
|
3
16
|
## 0.0.13
|
|
4
17
|
|
|
5
18
|
- Web UI:调整为 IDE 风格三栏布局,并新增固定可见的状态检查器
|
|
@@ -18,3 +31,4 @@
|
|
|
18
31
|
- 新增 OpenClaw 配置模式(JSON5 多配置管理 + 一键应用)
|
|
19
32
|
- 新增 OpenClaw Workspace 的 AGENTS.md 管理
|
|
20
33
|
- 增加 JSON5 解析依赖
|
|
34
|
+
|
package/lib/cli-utils.js
CHANGED
|
@@ -71,6 +71,20 @@ function isValidProviderName(name) {
|
|
|
71
71
|
return typeof name === 'string' && /^[a-zA-Z0-9._-]+$/.test(name.trim());
|
|
72
72
|
}
|
|
73
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
|
+
|
|
74
88
|
function buildModelsCandidates(baseUrl) {
|
|
75
89
|
const trimmed = typeof baseUrl === 'string' ? baseUrl.trim() : '';
|
|
76
90
|
if (!trimmed) return [];
|
|
@@ -132,6 +146,8 @@ module.exports = {
|
|
|
132
146
|
detectLineEnding,
|
|
133
147
|
normalizeLineEnding,
|
|
134
148
|
isValidProviderName,
|
|
149
|
+
escapeTomlBasicString,
|
|
150
|
+
buildModelProviderTableHeader,
|
|
135
151
|
buildModelsCandidates,
|
|
136
152
|
isValidHttpUrl,
|
|
137
153
|
normalizeBaseUrl,
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function getPathValue(source, path) {
|
|
6
|
+
if (!path || typeof path !== 'string') {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const parts = path
|
|
10
|
+
.split('.')
|
|
11
|
+
.map((part) => part.trim())
|
|
12
|
+
.filter(Boolean);
|
|
13
|
+
let current = source;
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
if (current === null || current === undefined) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(current)) {
|
|
19
|
+
const index = Number.parseInt(part, 10);
|
|
20
|
+
if (!Number.isFinite(index)) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
current = current[index];
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!isPlainObject(current)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
current = current[part];
|
|
30
|
+
}
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveTemplateString(template, context) {
|
|
35
|
+
if (typeof template !== 'string') {
|
|
36
|
+
return template;
|
|
37
|
+
}
|
|
38
|
+
const direct = template.match(/^\{\{\s*([^{}]+?)\s*\}\}$/);
|
|
39
|
+
if (direct) {
|
|
40
|
+
return getPathValue(context, direct[1].trim());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pattern = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
44
|
+
return template.replace(pattern, (_, rawPath) => {
|
|
45
|
+
const value = getPathValue(context, String(rawPath || '').trim());
|
|
46
|
+
if (value === undefined || value === null) {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === 'object') {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify(value);
|
|
52
|
+
} catch (_) {
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return String(value);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveTemplateValue(value, context) {
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
return value.map((item) => resolveTemplateValue(item, context));
|
|
63
|
+
}
|
|
64
|
+
if (isPlainObject(value)) {
|
|
65
|
+
const result = {};
|
|
66
|
+
for (const [key, item] of Object.entries(value)) {
|
|
67
|
+
result[key] = resolveTemplateValue(item, context);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
if (typeof value === 'string') {
|
|
72
|
+
return resolveTemplateString(value, context);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function evaluateStepCondition(condition, context) {
|
|
78
|
+
if (!condition) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (!isPlainObject(condition)) {
|
|
82
|
+
return !!condition;
|
|
83
|
+
}
|
|
84
|
+
const path = typeof condition.path === 'string' ? condition.path.trim() : '';
|
|
85
|
+
if (!path) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
const value = getPathValue(context, path);
|
|
89
|
+
if (Object.prototype.hasOwnProperty.call(condition, 'equals')) {
|
|
90
|
+
return value === condition.equals;
|
|
91
|
+
}
|
|
92
|
+
if (Object.prototype.hasOwnProperty.call(condition, 'notEquals')) {
|
|
93
|
+
return value !== condition.notEquals;
|
|
94
|
+
}
|
|
95
|
+
if (Object.prototype.hasOwnProperty.call(condition, 'truthy')) {
|
|
96
|
+
return condition.truthy ? !!value : !value;
|
|
97
|
+
}
|
|
98
|
+
if (Object.prototype.hasOwnProperty.call(condition, 'exists')) {
|
|
99
|
+
return condition.exists ? value !== undefined : value === undefined;
|
|
100
|
+
}
|
|
101
|
+
return !!value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isFailurePayload(payload) {
|
|
105
|
+
if (!payload || typeof payload !== 'object') {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (typeof payload.error === 'string' && payload.error.trim()) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (payload.success === false) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toIso(value) {
|
|
118
|
+
const date = value ? new Date(value) : new Date();
|
|
119
|
+
if (Number.isNaN(date.getTime())) {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
return date.toISOString();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validateWorkflowDefinition(definition, options = {}) {
|
|
126
|
+
const issues = [];
|
|
127
|
+
const knownTools = options.knownTools instanceof Set ? options.knownTools : new Set();
|
|
128
|
+
|
|
129
|
+
if (!definition || !isPlainObject(definition)) {
|
|
130
|
+
return { ok: false, issues: [{ code: 'workflow-invalid', message: 'workflow must be an object' }] };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const id = typeof definition.id === 'string' ? definition.id.trim() : '';
|
|
134
|
+
if (!id) {
|
|
135
|
+
issues.push({ code: 'workflow-id-required', message: 'workflow.id is required' });
|
|
136
|
+
}
|
|
137
|
+
const steps = Array.isArray(definition.steps) ? definition.steps : [];
|
|
138
|
+
if (steps.length === 0) {
|
|
139
|
+
issues.push({ code: 'workflow-steps-required', message: 'workflow.steps must be a non-empty array' });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const stepIds = new Set();
|
|
143
|
+
for (const step of steps) {
|
|
144
|
+
if (!step || !isPlainObject(step)) {
|
|
145
|
+
issues.push({ code: 'workflow-step-invalid', message: 'workflow step must be an object' });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const stepId = typeof step.id === 'string' ? step.id.trim() : '';
|
|
149
|
+
if (!stepId) {
|
|
150
|
+
issues.push({ code: 'workflow-step-id-required', message: 'workflow step id is required' });
|
|
151
|
+
} else if (stepIds.has(stepId)) {
|
|
152
|
+
issues.push({ code: 'workflow-step-id-duplicate', message: `duplicate step id: ${stepId}` });
|
|
153
|
+
} else {
|
|
154
|
+
stepIds.add(stepId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const tool = typeof step.tool === 'string' ? step.tool.trim() : '';
|
|
158
|
+
if (!tool) {
|
|
159
|
+
issues.push({ code: 'workflow-step-tool-required', message: `step ${stepId || '(unknown)'} missing tool` });
|
|
160
|
+
} else if (knownTools.size > 0 && !knownTools.has(tool)) {
|
|
161
|
+
issues.push({ code: 'workflow-step-tool-unknown', message: `step ${stepId || '(unknown)'} unknown tool: ${tool}` });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (step.when !== undefined && step.when !== null && !isPlainObject(step.when)) {
|
|
165
|
+
issues.push({ code: 'workflow-step-when-invalid', message: `step ${stepId || '(unknown)'} when must be an object` });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (issues.length > 0) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
issues,
|
|
173
|
+
error: issues[0].message
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { ok: true, issues: [] };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function executeWorkflowDefinition(definition, input = {}, options = {}) {
|
|
181
|
+
const invokeTool = typeof options.invokeTool === 'function'
|
|
182
|
+
? options.invokeTool
|
|
183
|
+
: async () => ({ error: 'invokeTool is not configured' });
|
|
184
|
+
const allowWrite = options.allowWrite === true;
|
|
185
|
+
const dryRun = options.dryRun === true;
|
|
186
|
+
|
|
187
|
+
const startedAtTs = Date.now();
|
|
188
|
+
const context = {
|
|
189
|
+
input: isPlainObject(input) ? input : {},
|
|
190
|
+
steps: {}
|
|
191
|
+
};
|
|
192
|
+
const steps = Array.isArray(definition && definition.steps) ? definition.steps : [];
|
|
193
|
+
const logs = [];
|
|
194
|
+
let overallError = '';
|
|
195
|
+
let lastOutput = null;
|
|
196
|
+
|
|
197
|
+
for (const step of steps) {
|
|
198
|
+
const stepId = typeof step.id === 'string' ? step.id.trim() : '';
|
|
199
|
+
const startedStepTs = Date.now();
|
|
200
|
+
const baseLog = {
|
|
201
|
+
id: stepId || '',
|
|
202
|
+
tool: typeof step.tool === 'string' ? step.tool.trim() : '',
|
|
203
|
+
startedAt: toIso(startedStepTs)
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const conditionPass = evaluateStepCondition(step.when, context);
|
|
207
|
+
if (!conditionPass) {
|
|
208
|
+
const skippedLog = {
|
|
209
|
+
...baseLog,
|
|
210
|
+
status: 'skipped',
|
|
211
|
+
reason: 'condition-not-met',
|
|
212
|
+
endedAt: toIso(Date.now()),
|
|
213
|
+
durationMs: Date.now() - startedStepTs
|
|
214
|
+
};
|
|
215
|
+
logs.push(skippedLog);
|
|
216
|
+
context.steps[stepId] = {
|
|
217
|
+
status: 'skipped',
|
|
218
|
+
output: null,
|
|
219
|
+
error: '',
|
|
220
|
+
args: null
|
|
221
|
+
};
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const resolvedArgs = resolveTemplateValue(isPlainObject(step.arguments) ? step.arguments : {}, context);
|
|
226
|
+
const isWriteStep = step.write === true;
|
|
227
|
+
if (isWriteStep && !allowWrite) {
|
|
228
|
+
const error = `write step requires allowWrite: ${stepId || baseLog.tool}`;
|
|
229
|
+
const failedLog = {
|
|
230
|
+
...baseLog,
|
|
231
|
+
args: resolvedArgs,
|
|
232
|
+
status: 'failed',
|
|
233
|
+
error,
|
|
234
|
+
endedAt: toIso(Date.now()),
|
|
235
|
+
durationMs: Date.now() - startedStepTs
|
|
236
|
+
};
|
|
237
|
+
logs.push(failedLog);
|
|
238
|
+
context.steps[stepId] = {
|
|
239
|
+
status: 'failed',
|
|
240
|
+
output: null,
|
|
241
|
+
error,
|
|
242
|
+
args: resolvedArgs
|
|
243
|
+
};
|
|
244
|
+
overallError = error;
|
|
245
|
+
if (!step.continueOnError) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (isWriteStep && dryRun) {
|
|
252
|
+
const dryRunLog = {
|
|
253
|
+
...baseLog,
|
|
254
|
+
args: resolvedArgs,
|
|
255
|
+
status: 'skipped',
|
|
256
|
+
reason: 'dry-run-write-step',
|
|
257
|
+
endedAt: toIso(Date.now()),
|
|
258
|
+
durationMs: Date.now() - startedStepTs
|
|
259
|
+
};
|
|
260
|
+
logs.push(dryRunLog);
|
|
261
|
+
context.steps[stepId] = {
|
|
262
|
+
status: 'skipped',
|
|
263
|
+
output: null,
|
|
264
|
+
error: '',
|
|
265
|
+
args: resolvedArgs
|
|
266
|
+
};
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let payload;
|
|
271
|
+
let stepError = '';
|
|
272
|
+
try {
|
|
273
|
+
payload = await invokeTool(baseLog.tool, resolvedArgs, {
|
|
274
|
+
step,
|
|
275
|
+
context,
|
|
276
|
+
allowWrite,
|
|
277
|
+
dryRun
|
|
278
|
+
});
|
|
279
|
+
if (isFailurePayload(payload)) {
|
|
280
|
+
stepError = typeof payload.error === 'string' && payload.error.trim()
|
|
281
|
+
? payload.error.trim()
|
|
282
|
+
: `step failed: ${stepId || baseLog.tool}`;
|
|
283
|
+
}
|
|
284
|
+
} catch (error) {
|
|
285
|
+
stepError = error && error.message ? error.message : String(error || 'step execution failed');
|
|
286
|
+
payload = { error: stepError };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const status = stepError ? 'failed' : 'success';
|
|
290
|
+
const endedAtTs = Date.now();
|
|
291
|
+
const stepLog = {
|
|
292
|
+
...baseLog,
|
|
293
|
+
args: resolvedArgs,
|
|
294
|
+
status,
|
|
295
|
+
output: payload,
|
|
296
|
+
error: stepError,
|
|
297
|
+
endedAt: toIso(endedAtTs),
|
|
298
|
+
durationMs: endedAtTs - startedStepTs
|
|
299
|
+
};
|
|
300
|
+
logs.push(stepLog);
|
|
301
|
+
context.steps[stepId] = {
|
|
302
|
+
status,
|
|
303
|
+
output: payload,
|
|
304
|
+
error: stepError,
|
|
305
|
+
args: resolvedArgs
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
if (!stepError) {
|
|
309
|
+
lastOutput = payload;
|
|
310
|
+
} else {
|
|
311
|
+
overallError = stepError;
|
|
312
|
+
if (!step.continueOnError) {
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const endedAtTs = Date.now();
|
|
319
|
+
const hasFailed = logs.some((item) => item.status === 'failed');
|
|
320
|
+
const result = {
|
|
321
|
+
success: !hasFailed,
|
|
322
|
+
startedAt: toIso(startedAtTs),
|
|
323
|
+
endedAt: toIso(endedAtTs),
|
|
324
|
+
durationMs: endedAtTs - startedAtTs,
|
|
325
|
+
steps: logs,
|
|
326
|
+
output: lastOutput || null
|
|
327
|
+
};
|
|
328
|
+
if (overallError) {
|
|
329
|
+
result.error = overallError;
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = {
|
|
335
|
+
getPathValue,
|
|
336
|
+
resolveTemplateValue,
|
|
337
|
+
evaluateStepCondition,
|
|
338
|
+
validateWorkflowDefinition,
|
|
339
|
+
executeWorkflowDefinition
|
|
340
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexmate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Codex/Claude Code 配置与会话管理 CLI + Web 工具",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"res/",
|
|
15
15
|
"doc/",
|
|
16
16
|
"README.md",
|
|
17
|
-
"README.
|
|
17
|
+
"README.en.md",
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
20
|
"exports": {
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"dev": "node cli.js run",
|
|
28
28
|
"start": "node cli.js",
|
|
29
|
+
"docs:dev": "node ./node_modules/vitepress/dist/node/cli.js dev site",
|
|
30
|
+
"docs:build": "node ./node_modules/vitepress/dist/node/cli.js build site",
|
|
31
|
+
"docs:preview": "node ./node_modules/vitepress/dist/node/cli.js preview site",
|
|
29
32
|
"test": "npm run test:unit && npm run test:e2e",
|
|
30
33
|
"test:unit": "node tests/unit/run.mjs",
|
|
31
34
|
"test:e2e": "node tests/e2e/run.js",
|
|
@@ -34,6 +37,7 @@
|
|
|
34
37
|
"dependencies": {
|
|
35
38
|
"@iarna/toml": "^2.2.5",
|
|
36
39
|
"json5": "^2.2.3",
|
|
40
|
+
"yauzl": "^3.2.1",
|
|
37
41
|
"zip-lib": "^1.2.1"
|
|
38
42
|
},
|
|
39
43
|
"engines": {
|
|
@@ -52,5 +56,8 @@
|
|
|
52
56
|
"cli"
|
|
53
57
|
],
|
|
54
58
|
"author": "ymkiux",
|
|
55
|
-
"license": "Apache-2.0"
|
|
59
|
+
"license": "Apache-2.0",
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"vitepress": "^1.6.4"
|
|
62
|
+
}
|
|
56
63
|
}
|