clawflowbang 1.0.0 → 1.0.2
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/.env.example +14 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- package/.github/pull_request_template.md +19 -0
- package/.github/workflows/publish.yml +46 -0
- package/.github/workflows/test.yml +31 -0
- package/CODE_OF_CONDUCT.md +29 -0
- package/LICENSE +21 -0
- package/README.md +151 -1
- package/SECURITY.md +20 -0
- package/SUPPORT.md +21 -0
- package/bin/clawflowhub.js +60 -64
- package/package.json +18 -16
- package/src/commands/cron.js +71 -36
- package/src/commands/init.js +23 -23
- package/src/commands/install.js +17 -17
- package/src/commands/list.js +27 -25
- package/src/commands/remove.js +4 -4
- package/src/commands/search.js +23 -20
- package/src/commands/status.js +19 -25
- package/src/core/ConfigManager.js +132 -132
- package/src/core/CronManager.js +245 -245
- package/src/core/Installer.js +53 -53
- package/src/core/OpenClawCLI.js +62 -17
- package/src/core/TerminalUI.js +332 -0
- package/.eslintrc.json +0 -38
- package/__tests__/config-manager.test.js +0 -52
- package/__tests__/cron-format.test.js +0 -26
- package/__tests__/cron-manager.local.test.js +0 -65
- package/__tests__/openclaw-cli.test.js +0 -51
- package/docs/clawhub-package-format.md +0 -179
- package/examples/npm-package-example/package.json +0 -53
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawFlow Terminal UI
|
|
3
|
+
* Retro + Modern terminal styling
|
|
4
|
+
*
|
|
5
|
+
* Design: Retro Terminal + Modern Polish
|
|
6
|
+
* - Box-drawing characters (retro)
|
|
7
|
+
* - Neon colors (modern)
|
|
8
|
+
* - Gradient effects (modern)
|
|
9
|
+
* - ASCII art headers (retro)
|
|
10
|
+
* - Glow effects (modern)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const gradientString = require('gradient-string');
|
|
15
|
+
const gradient = gradientString.default || gradientString;
|
|
16
|
+
const boxen = require('boxen');
|
|
17
|
+
|
|
18
|
+
// Color palette - Neon Retro with Orange & Lobster theme
|
|
19
|
+
const colors = {
|
|
20
|
+
primary: '#ff9500', // Orange
|
|
21
|
+
secondary: '#ff6b35', // Deep orange
|
|
22
|
+
accent: '#ff9f1c', // Bright orange
|
|
23
|
+
warning: '#ffd93d', // Yellow
|
|
24
|
+
error: '#ff6b6b', // Red
|
|
25
|
+
success: '#00ff9f', // Neon green
|
|
26
|
+
info: '#6c5ce7', // Purple
|
|
27
|
+
|
|
28
|
+
// Orange/Lobster themed colors
|
|
29
|
+
lobster: '#ff6b35', // Lobster orange
|
|
30
|
+
orange: '#ff9500', // Apple orange
|
|
31
|
+
sunset: '#ff7f50', // Coral
|
|
32
|
+
tangerine: '#ffb347', // Pastel orange
|
|
33
|
+
pumpkin: '#ff7518', // Pumpkin
|
|
34
|
+
coral: '#ff6b6b', // Coral red
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Box styles
|
|
38
|
+
const boxStyles = {
|
|
39
|
+
round: {
|
|
40
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
41
|
+
borderStyle: 'round',
|
|
42
|
+
borderColor: colors.primary,
|
|
43
|
+
},
|
|
44
|
+
bold: {
|
|
45
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
46
|
+
borderStyle: 'bold',
|
|
47
|
+
borderColor: colors.secondary,
|
|
48
|
+
},
|
|
49
|
+
double: {
|
|
50
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
51
|
+
borderStyle: 'double',
|
|
52
|
+
borderColor: colors.accent,
|
|
53
|
+
},
|
|
54
|
+
classic: {
|
|
55
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
56
|
+
borderStyle: 'classic',
|
|
57
|
+
borderColor: colors.info,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/* eslint-disable no-useless-escape */
|
|
62
|
+
// ASCII Art for logo - Cool Retro Style
|
|
63
|
+
const asciiLogo = `
|
|
64
|
+
________ ___ ________ ___ __ ________ ___ ________ ___ __
|
|
65
|
+
|\ ____\|\ \ |\ __ \|\ \ |\ \|\ _____\\ \ |\ __ \|\ \ |\ \
|
|
66
|
+
\ \ \___|\ \ \ \ \ \|\ \ \ \ \ \ \ \ \__/\ \ \ \ \ \|\ \ \ \ \ \ \
|
|
67
|
+
\ \ \ \ \ \ \ \ __ \ \ \ __\ \ \ \ __\\ \ \ \ \ \\\ \ \ \ __\ \ \
|
|
68
|
+
\ \ \____\ \ \____\ \ \ \ \ \ \|\__\_\ \ \ \_| \ \ \____\ \ \\\ \ \ \|\__\_\ \
|
|
69
|
+
\ \_______\ \_______\ \__\ \__\ \____________\ \__\ \ \_______\ \_______\ \____________\
|
|
70
|
+
\|_______|\|_______|\|__|\|__|\|____________|\|__| \|_______|\|_______|\|____________|
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
ClawFlow - Skill + Cron Installer for OpenClaw
|
|
75
|
+
`;
|
|
76
|
+
/* eslint-enable no-useless-escape */
|
|
77
|
+
|
|
78
|
+
// Mini logo for sub-headers
|
|
79
|
+
const miniLogo = `
|
|
80
|
+
╭──────────────────────────────────────╮
|
|
81
|
+
│ 🦞 ClawFlow v{pkgVersion} │
|
|
82
|
+
│ ═══════════════════════════════════ │
|
|
83
|
+
│ Skill + Cron Installer for OpenClaw │
|
|
84
|
+
╰──────────────────────────────────────╯
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create gradient text (modern effect)
|
|
89
|
+
*/
|
|
90
|
+
function gradientText(text, color1 = colors.primary, color2 = colors.secondary) {
|
|
91
|
+
return gradient(color1, color2)(text);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create neon glow effect
|
|
96
|
+
*/
|
|
97
|
+
function neonGlow(text, color = colors.primary) {
|
|
98
|
+
return chalk.hex(color).bold(text);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create retro scanline effect
|
|
103
|
+
*/
|
|
104
|
+
function scanline(text) {
|
|
105
|
+
return chalk.dim(text);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create box with custom style
|
|
110
|
+
*/
|
|
111
|
+
function createBox(content, style = 'round') {
|
|
112
|
+
const boxOptions = boxStyles[style] || boxStyles.round;
|
|
113
|
+
return boxen(content, boxOptions);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create a retro-styled header
|
|
118
|
+
*/
|
|
119
|
+
function createHeader(title, style = 'gradient') {
|
|
120
|
+
const line = '═'.repeat(50);
|
|
121
|
+
|
|
122
|
+
switch (style) {
|
|
123
|
+
case 'gradient':
|
|
124
|
+
return `\n${gradientText(line, colors.primary, colors.secondary)}
|
|
125
|
+
${chalk.bold.cyan(' ' + title)}
|
|
126
|
+
${gradientText(line, colors.primary, colors.secondary)}\n`;
|
|
127
|
+
|
|
128
|
+
case 'neon':
|
|
129
|
+
return `\n${neonGlow(line, colors.primary)}
|
|
130
|
+
${neonGlow(' ' + title, colors.secondary)}
|
|
131
|
+
${neonGlow(line, colors.primary)}\n`;
|
|
132
|
+
|
|
133
|
+
case 'retro':
|
|
134
|
+
return `\n${chalk.green(line)}
|
|
135
|
+
${chalk.bold.green(' ' + title)}
|
|
136
|
+
${chalk.green(line)}\n`;
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
return `\n${line}\n ${title}\n${line}\n`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a loading spinner with retro style
|
|
145
|
+
*/
|
|
146
|
+
function createSpinner(message) {
|
|
147
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
148
|
+
let frame = 0;
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
start: () => {
|
|
152
|
+
process.stdout.write(chalk.cyan(frames[0]) + ' ' + message);
|
|
153
|
+
const interval = setInterval(() => {
|
|
154
|
+
process.stdout.write('\r' + chalk.cyan(frames[frame % frames.length]) + ' ' + message);
|
|
155
|
+
frame++;
|
|
156
|
+
}, 100);
|
|
157
|
+
return {
|
|
158
|
+
stop: (success = true, finalMessage = '') => {
|
|
159
|
+
clearInterval(interval);
|
|
160
|
+
process.stdout.write('\r' + (success ? chalk.green('✓') : chalk.red('✗')) + ' ' + finalMessage + '\n');
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create progress bar (retro style)
|
|
169
|
+
*/
|
|
170
|
+
function createProgressBar(total, current, width = 30) {
|
|
171
|
+
const filled = Math.round((current / total) * width);
|
|
172
|
+
const empty = width - filled;
|
|
173
|
+
|
|
174
|
+
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
175
|
+
const percentage = Math.round((current / total) * 100);
|
|
176
|
+
|
|
177
|
+
return `[${bar}] ${percentage}%`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Print main banner
|
|
182
|
+
*/
|
|
183
|
+
function printBanner(_pkgVersion = '1.0.0') {
|
|
184
|
+
const banner = gradientText(asciiLogo, colors.primary, colors.secondary);
|
|
185
|
+
|
|
186
|
+
console.log(chalk.bgBlack(banner));
|
|
187
|
+
console.log(gradientText('─'.repeat(60), colors.primary, colors.secondary));
|
|
188
|
+
console.log(' 🦞 ' + gradientText('Install skills and configure them for immediate use', colors.primary, colors.secondary));
|
|
189
|
+
console.log(gradientText('─'.repeat(60), colors.primary, colors.secondary));
|
|
190
|
+
console.log();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Print section header
|
|
195
|
+
*/
|
|
196
|
+
function printSection(title) {
|
|
197
|
+
console.log();
|
|
198
|
+
console.log(neonGlow('┌' + '─'.repeat(58) + '┐', colors.secondary));
|
|
199
|
+
console.log(neonGlow('│ ', colors.secondary) + chalk.bold.cyan(title.padEnd(56)) + neonGlow(' │', colors.secondary));
|
|
200
|
+
console.log(neonGlow('└' + '─'.repeat(58) + '┘', colors.secondary));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Print success message
|
|
205
|
+
*/
|
|
206
|
+
function printSuccess(message) {
|
|
207
|
+
console.log(chalk.green(' ✓ ') + message);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Print error message
|
|
212
|
+
*/
|
|
213
|
+
function printError(message) {
|
|
214
|
+
console.log(chalk.red(' ✗ ') + message);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Print warning message
|
|
219
|
+
*/
|
|
220
|
+
function printWarning(message) {
|
|
221
|
+
console.log(chalk.yellow(' ⚠ ') + message);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Print info message
|
|
226
|
+
*/
|
|
227
|
+
function printInfo(message) {
|
|
228
|
+
console.log(chalk.cyan(' ℹ ') + message);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Print table row
|
|
233
|
+
*/
|
|
234
|
+
function printTableRow(columns, widths) {
|
|
235
|
+
const row = columns.map((col, i) => {
|
|
236
|
+
return String(col).padEnd(widths[i]);
|
|
237
|
+
}).join(chalk.gray(' │ '));
|
|
238
|
+
|
|
239
|
+
console.log(chalk.gray('│ ') + row + chalk.gray(' │'));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Print table header separator
|
|
244
|
+
*/
|
|
245
|
+
function printTableSeparator(widths) {
|
|
246
|
+
console.log(chalk.gray('├─' + widths.map(w => '─'.repeat(w)).join('─┼─') + '─┤'));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Print key-value pairs in a nice format
|
|
251
|
+
*/
|
|
252
|
+
function printKeyValue(key, value, indent = 2) {
|
|
253
|
+
const spaces = ' '.repeat(indent);
|
|
254
|
+
console.log(`${spaces}${chalk.cyan('●')} ${chalk.gray(key + ':')} ${chalk.white(value)}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Print a list item with bullet
|
|
259
|
+
*/
|
|
260
|
+
function printListItem(item, indent = 2) {
|
|
261
|
+
const spaces = ' '.repeat(indent);
|
|
262
|
+
console.log(`${spaces}${chalk.green('›')} ${item}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Print a list item with number
|
|
267
|
+
*/
|
|
268
|
+
function printNumberedItem(num, item, indent = 2) {
|
|
269
|
+
const spaces = ' '.repeat(indent);
|
|
270
|
+
console.log(`${spaces}${chalk.cyan(num + '.')} ${item}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Print help command list
|
|
275
|
+
*/
|
|
276
|
+
function printCommandHelp(commands) {
|
|
277
|
+
console.log();
|
|
278
|
+
commands.forEach(cmd => {
|
|
279
|
+
const alias = cmd.alias ? ` | ${chalk.yellow(cmd.alias)}` : '';
|
|
280
|
+
console.log(` ${chalk.green(cmd.name)}${alias} ${chalk.gray('- ' + cmd.description)}`);
|
|
281
|
+
});
|
|
282
|
+
console.log();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Print loading animation
|
|
287
|
+
*/
|
|
288
|
+
function loading(text) {
|
|
289
|
+
const frames = ['◐', '◑', '◒', '◓'];
|
|
290
|
+
let i = 0;
|
|
291
|
+
const interval = setInterval(() => {
|
|
292
|
+
process.stdout.write(`\r${chalk.cyan(frames[i++])} ${text}`);
|
|
293
|
+
i = i % frames.length;
|
|
294
|
+
}, 100);
|
|
295
|
+
|
|
296
|
+
return () => clearInterval(interval);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Clear screen and print banner
|
|
301
|
+
*/
|
|
302
|
+
function clearAndBanner(pkgVersion = '1.0.0') {
|
|
303
|
+
console.clear();
|
|
304
|
+
printBanner(pkgVersion);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
colors,
|
|
309
|
+
gradientText,
|
|
310
|
+
neonGlow,
|
|
311
|
+
scanline,
|
|
312
|
+
createBox,
|
|
313
|
+
createHeader,
|
|
314
|
+
createSpinner,
|
|
315
|
+
createProgressBar,
|
|
316
|
+
printBanner,
|
|
317
|
+
printSection,
|
|
318
|
+
printSuccess,
|
|
319
|
+
printError,
|
|
320
|
+
printWarning,
|
|
321
|
+
printInfo,
|
|
322
|
+
printTableRow,
|
|
323
|
+
printTableSeparator,
|
|
324
|
+
printKeyValue,
|
|
325
|
+
printListItem,
|
|
326
|
+
printNumberedItem,
|
|
327
|
+
printCommandHelp,
|
|
328
|
+
loading,
|
|
329
|
+
clearAndBanner,
|
|
330
|
+
asciiLogo,
|
|
331
|
+
miniLogo,
|
|
332
|
+
};
|
package/.eslintrc.json
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"root": true,
|
|
3
|
-
"env": {
|
|
4
|
-
"node": true,
|
|
5
|
-
"es2021": true
|
|
6
|
-
},
|
|
7
|
-
"extends": [
|
|
8
|
-
"eslint:recommended"
|
|
9
|
-
],
|
|
10
|
-
"parserOptions": {
|
|
11
|
-
"ecmaVersion": 12
|
|
12
|
-
},
|
|
13
|
-
"ignorePatterns": [
|
|
14
|
-
"node_modules/",
|
|
15
|
-
"coverage/"
|
|
16
|
-
],
|
|
17
|
-
"rules": {
|
|
18
|
-
"no-console": "off",
|
|
19
|
-
"no-unused-vars": [
|
|
20
|
-
"warn",
|
|
21
|
-
{
|
|
22
|
-
"argsIgnorePattern": "^_",
|
|
23
|
-
"varsIgnorePattern": "^_"
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
},
|
|
27
|
-
"overrides": [
|
|
28
|
-
{
|
|
29
|
-
"files": [
|
|
30
|
-
"**/*.test.js",
|
|
31
|
-
"__tests__/**/*.js"
|
|
32
|
-
],
|
|
33
|
-
"env": {
|
|
34
|
-
"jest": true
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
]
|
|
38
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const ConfigManager = require('../src/core/ConfigManager');
|
|
5
|
-
|
|
6
|
-
function makeTempDir(prefix) {
|
|
7
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
describe('ConfigManager', () => {
|
|
11
|
-
test('applies path and bin overrides into openclaw config', () => {
|
|
12
|
-
const configDir = makeTempDir('cfh-config-');
|
|
13
|
-
const skillsPath = path.join(configDir, 'custom-skills');
|
|
14
|
-
const cronJobsFile = path.join(configDir, 'custom-cron', 'jobs.json');
|
|
15
|
-
|
|
16
|
-
const manager = new ConfigManager(configDir, {
|
|
17
|
-
skillsPath,
|
|
18
|
-
cronJobsFile,
|
|
19
|
-
openclawBin: '/usr/local/bin/openclaw',
|
|
20
|
-
clawhubBin: '/usr/local/bin/clawhub',
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const config = manager.getConfig();
|
|
24
|
-
expect(config.openclaw.skillsPath).toBe(skillsPath);
|
|
25
|
-
expect(config.openclaw.cronJobsFile).toBe(cronJobsFile);
|
|
26
|
-
expect(config.openclaw.cliBin).toBe('/usr/local/bin/openclaw');
|
|
27
|
-
expect(config.openclaw.clawhubBin).toBe('/usr/local/bin/clawhub');
|
|
28
|
-
expect(manager.getCronJobsFilePath()).toBe(cronJobsFile);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('addCron preserves provided id and updateCron mutates fields', () => {
|
|
32
|
-
const configDir = makeTempDir('cfh-config-');
|
|
33
|
-
const manager = new ConfigManager(configDir);
|
|
34
|
-
|
|
35
|
-
manager.addCron({
|
|
36
|
-
id: 'job-123',
|
|
37
|
-
skill: 'crypto-price',
|
|
38
|
-
schedule: '*/5 * * * *',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const updated = manager.updateCron('job-123', {
|
|
42
|
-
schedule: '0 * * * *',
|
|
43
|
-
description: 'hourly',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
expect(updated).toBeTruthy();
|
|
47
|
-
expect(updated.id).toBe('job-123');
|
|
48
|
-
expect(updated.schedule).toBe('0 * * * *');
|
|
49
|
-
expect(updated.description).toBe('hourly');
|
|
50
|
-
expect(updated.updatedAt).toBeTruthy();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const { normalizeCronExpression } = require('../src/core/CronFormat');
|
|
2
|
-
|
|
3
|
-
describe('CronFormat.normalizeCronExpression', () => {
|
|
4
|
-
test('accepts valid cron expressions as-is', () => {
|
|
5
|
-
expect(normalizeCronExpression('*/5 * * * *')).toBe('*/5 * * * *');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
test('normalizes preset aliases', () => {
|
|
9
|
-
expect(normalizeCronExpression('@daily')).toBe('0 0 * * *');
|
|
10
|
-
expect(normalizeCronExpression('@hourly')).toBe('0 * * * *');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test('normalizes shorthand durations', () => {
|
|
14
|
-
expect(normalizeCronExpression('15m')).toBe('*/15 * * * *');
|
|
15
|
-
expect(normalizeCronExpression('every 2h')).toBe('0 */2 * * *');
|
|
16
|
-
expect(normalizeCronExpression('1d')).toBe('0 0 */1 * *');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('throws on invalid cron expressions', () => {
|
|
20
|
-
expect(() => normalizeCronExpression('abc def')).toThrow(/ไม่ถูกต้อง/);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('throws on unsupported duration shorthands', () => {
|
|
24
|
-
expect(() => normalizeCronExpression('70m')).toThrow(/ไม่รองรับ/);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const ConfigManager = require('../src/core/ConfigManager');
|
|
5
|
-
const CronManager = require('../src/core/CronManager');
|
|
6
|
-
|
|
7
|
-
function makeTempDir(prefix) {
|
|
8
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('CronManager local mode', () => {
|
|
12
|
-
test('add/edit/list/remove lifecycle works with normalized schedules', async () => {
|
|
13
|
-
const root = makeTempDir('cfh-cron-');
|
|
14
|
-
const configPath = path.join(root, 'config');
|
|
15
|
-
const cronJobsFile = path.join(root, 'cron', 'jobs.json');
|
|
16
|
-
|
|
17
|
-
const configManager = new ConfigManager(configPath, {
|
|
18
|
-
cronJobsFile,
|
|
19
|
-
openclawBin: 'notfound-openclaw',
|
|
20
|
-
});
|
|
21
|
-
const cronManager = new CronManager(configManager);
|
|
22
|
-
|
|
23
|
-
expect(cronManager.useOpenClawCron).toBe(false);
|
|
24
|
-
|
|
25
|
-
const added = await cronManager.add('smoke-skill', 'every 15m', { foo: 'bar' }, 'demo');
|
|
26
|
-
expect(added.id).toBeTruthy();
|
|
27
|
-
expect(added.schedule).toBe('*/15 * * * *');
|
|
28
|
-
|
|
29
|
-
const listed = cronManager.list();
|
|
30
|
-
expect(listed).toHaveLength(1);
|
|
31
|
-
expect(listed[0].schedule).toBe('*/15 * * * *');
|
|
32
|
-
|
|
33
|
-
const edited = await cronManager.edit(added.id, {
|
|
34
|
-
schedule: '@daily',
|
|
35
|
-
description: 'daily run',
|
|
36
|
-
params: { foo: 'baz' },
|
|
37
|
-
});
|
|
38
|
-
expect(edited.schedule).toBe('0 0 * * *');
|
|
39
|
-
expect(edited.description).toBe('daily run');
|
|
40
|
-
expect(edited.params).toEqual({ foo: 'baz' });
|
|
41
|
-
|
|
42
|
-
const listedAfterEdit = cronManager.list();
|
|
43
|
-
expect(listedAfterEdit).toHaveLength(1);
|
|
44
|
-
expect(listedAfterEdit[0].schedule).toBe('0 0 * * *');
|
|
45
|
-
|
|
46
|
-
await cronManager.remove(added.id);
|
|
47
|
-
expect(cronManager.list()).toEqual([]);
|
|
48
|
-
|
|
49
|
-
cronManager.stopAll();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('rejects invalid schedules', async () => {
|
|
53
|
-
const root = makeTempDir('cfh-cron-');
|
|
54
|
-
const configPath = path.join(root, 'config');
|
|
55
|
-
const cronJobsFile = path.join(root, 'cron', 'jobs.json');
|
|
56
|
-
|
|
57
|
-
const configManager = new ConfigManager(configPath, {
|
|
58
|
-
cronJobsFile,
|
|
59
|
-
openclawBin: 'notfound-openclaw',
|
|
60
|
-
});
|
|
61
|
-
const cronManager = new CronManager(configManager);
|
|
62
|
-
|
|
63
|
-
await expect(cronManager.add('smoke-skill', 'wrong schedule', {})).rejects.toThrow(/ไม่ถูกต้อง/);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
const OpenClawCLI = require('../src/core/OpenClawCLI');
|
|
2
|
-
|
|
3
|
-
function createCli() {
|
|
4
|
-
return new OpenClawCLI({
|
|
5
|
-
getConfig() {
|
|
6
|
-
return {
|
|
7
|
-
openclaw: {
|
|
8
|
-
cliBin: 'openclaw',
|
|
9
|
-
clawhubBin: 'clawhub',
|
|
10
|
-
},
|
|
11
|
-
};
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('OpenClawCLI git fallback helpers', () => {
|
|
17
|
-
test('resolveGitRepository from explicit fields', () => {
|
|
18
|
-
const cli = createCli();
|
|
19
|
-
expect(
|
|
20
|
-
cli.resolveGitRepository({
|
|
21
|
-
name: 'crypto-price',
|
|
22
|
-
repository: 'https://github.com/acme/crypto-price.git',
|
|
23
|
-
}),
|
|
24
|
-
).toBe('https://github.com/acme/crypto-price.git');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('resolveGitRepository from github source + owner/repo name', () => {
|
|
28
|
-
const cli = createCli();
|
|
29
|
-
expect(
|
|
30
|
-
cli.resolveGitRepository({
|
|
31
|
-
source: 'github',
|
|
32
|
-
name: 'acme/my-skill',
|
|
33
|
-
}),
|
|
34
|
-
).toBe('https://github.com/acme/my-skill.git');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('resolveSkillDirName from url and scoped names', () => {
|
|
38
|
-
const cli = createCli();
|
|
39
|
-
expect(
|
|
40
|
-
cli.resolveSkillDirName({
|
|
41
|
-
name: 'https://github.com/acme/skill-x.git',
|
|
42
|
-
}),
|
|
43
|
-
).toBe('skill-x');
|
|
44
|
-
|
|
45
|
-
expect(
|
|
46
|
-
cli.resolveSkillDirName({
|
|
47
|
-
name: 'acme/skill-y',
|
|
48
|
-
}),
|
|
49
|
-
).toBe('skill-y');
|
|
50
|
-
});
|
|
51
|
-
});
|