clawflowbang 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +38 -0
- package/README.md +1 -0
- package/__tests__/config-manager.test.js +52 -0
- package/__tests__/cron-format.test.js +26 -0
- package/__tests__/cron-manager.local.test.js +65 -0
- package/__tests__/openclaw-cli.test.js +51 -0
- package/bin/clawflowhub.js +125 -0
- package/docs/clawhub-package-format.md +179 -0
- package/examples/npm-package-example/package.json +53 -0
- package/package.json +45 -0
- package/src/commands/cron.js +123 -0
- package/src/commands/init.js +112 -0
- package/src/commands/install.js +38 -0
- package/src/commands/list.js +77 -0
- package/src/commands/remove.js +27 -0
- package/src/commands/search.js +61 -0
- package/src/commands/status.js +68 -0
- package/src/core/ConfigManager.js +295 -0
- package/src/core/CronFormat.js +68 -0
- package/src/core/CronManager.js +512 -0
- package/src/core/Installer.js +352 -0
- package/src/core/NPMRegistry.js +285 -0
- package/src/core/OpenClawCLI.js +265 -0
- package/src/core/PackageResolver.js +94 -0
- package/src/core/Registry.js +400 -0
- package/src/index.js +91 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfigManager - จัดการ configuration และ state ของ ClawFlow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
class ConfigManager {
|
|
10
|
+
constructor(configPath = null, options = {}) {
|
|
11
|
+
this.configPath = configPath || this.getDefaultConfigPath();
|
|
12
|
+
this.overrides = options;
|
|
13
|
+
this.config = null;
|
|
14
|
+
this.ensureDirectories();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ได้รับ default config path ตาม OS
|
|
19
|
+
*/
|
|
20
|
+
getDefaultConfigPath() {
|
|
21
|
+
const homeDir = os.homedir();
|
|
22
|
+
switch (process.platform) {
|
|
23
|
+
case 'win32':
|
|
24
|
+
return path.join(homeDir, 'AppData', 'Roaming', 'ClawFlow');
|
|
25
|
+
case 'darwin':
|
|
26
|
+
return path.join(homeDir, 'Library', 'Application Support', 'ClawFlow');
|
|
27
|
+
default: // linux
|
|
28
|
+
return path.join(homeDir, '.config', 'clawflowhub');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* สร้างโฟลเดอร์ที่จำเป็น
|
|
34
|
+
*/
|
|
35
|
+
ensureDirectories() {
|
|
36
|
+
const configFile = path.join(this.configPath, 'config.json');
|
|
37
|
+
const defaultConfig = this.getDefaultConfig();
|
|
38
|
+
const existingConfig = fs.existsSync(configFile) ? fs.readJsonSync(configFile) : {};
|
|
39
|
+
const mergedConfig = this.applyOpenClawOverrides({
|
|
40
|
+
...defaultConfig,
|
|
41
|
+
...existingConfig,
|
|
42
|
+
openclaw: {
|
|
43
|
+
...defaultConfig.openclaw,
|
|
44
|
+
...(existingConfig.openclaw || {}),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const dirs = [
|
|
49
|
+
this.configPath,
|
|
50
|
+
path.dirname(mergedConfig.openclaw.skillsPath),
|
|
51
|
+
mergedConfig.openclaw.skillsPath,
|
|
52
|
+
path.dirname(mergedConfig.openclaw.cronJobsFile),
|
|
53
|
+
path.join(this.configPath, 'logs'),
|
|
54
|
+
path.join(this.configPath, 'packages'),
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
dirs.forEach((dir) => {
|
|
58
|
+
fs.ensureDirSync(dir);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!fs.existsSync(configFile) || JSON.stringify(existingConfig) !== JSON.stringify(mergedConfig)) {
|
|
62
|
+
fs.writeJsonSync(configFile, mergedConfig, { spaces: 2 });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Config เริ่มต้น
|
|
68
|
+
*/
|
|
69
|
+
getDefaultConfig() {
|
|
70
|
+
const openclawHome = path.join(os.homedir(), '.openclaw');
|
|
71
|
+
const workspacePath = path.join(openclawHome, 'workspace');
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
version: '1.0.0',
|
|
75
|
+
openclaw: {
|
|
76
|
+
baseUrl: 'http://localhost:3000',
|
|
77
|
+
apiKey: null,
|
|
78
|
+
cliBin: 'openclaw',
|
|
79
|
+
clawhubBin: 'clawhub',
|
|
80
|
+
workspacePath,
|
|
81
|
+
skillsPath: path.join(workspacePath, 'skills'),
|
|
82
|
+
cronJobsFile: path.join(openclawHome, 'cron', 'jobs.json'),
|
|
83
|
+
},
|
|
84
|
+
registry: {
|
|
85
|
+
url: 'https://registry.clawflowhub.dev',
|
|
86
|
+
cacheExpiry: 3600, // วินาที
|
|
87
|
+
},
|
|
88
|
+
cron: {
|
|
89
|
+
enabled: true,
|
|
90
|
+
logLevel: 'info',
|
|
91
|
+
maxConcurrentJobs: 5,
|
|
92
|
+
},
|
|
93
|
+
installed: {},
|
|
94
|
+
crons: [],
|
|
95
|
+
lastUpdate: null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
applyOpenClawOverrides(config) {
|
|
100
|
+
return {
|
|
101
|
+
...config,
|
|
102
|
+
openclaw: {
|
|
103
|
+
...(config.openclaw || {}),
|
|
104
|
+
...(this.overrides.skillsPath ? { skillsPath: this.overrides.skillsPath } : {}),
|
|
105
|
+
...(this.overrides.cronJobsFile ? { cronJobsFile: this.overrides.cronJobsFile } : {}),
|
|
106
|
+
...(this.overrides.openclawBin ? { cliBin: this.overrides.openclawBin } : {}),
|
|
107
|
+
...(this.overrides.clawhubBin ? { clawhubBin: this.overrides.clawhubBin } : {}),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* โหลด config
|
|
114
|
+
*/
|
|
115
|
+
loadConfig() {
|
|
116
|
+
const configFile = path.join(this.configPath, 'config.json');
|
|
117
|
+
if (fs.existsSync(configFile)) {
|
|
118
|
+
this.config = fs.readJsonSync(configFile);
|
|
119
|
+
} else {
|
|
120
|
+
this.config = this.applyOpenClawOverrides(this.getDefaultConfig());
|
|
121
|
+
}
|
|
122
|
+
return this.config;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* บันทึก config
|
|
127
|
+
*/
|
|
128
|
+
saveConfig(config = null) {
|
|
129
|
+
if (config) {
|
|
130
|
+
this.config = config;
|
|
131
|
+
}
|
|
132
|
+
const configFile = path.join(this.configPath, 'config.json');
|
|
133
|
+
fs.writeJsonSync(configFile, this.config, { spaces: 2 });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* ดึงค่า config ปัจจุบัน
|
|
138
|
+
*/
|
|
139
|
+
getConfig() {
|
|
140
|
+
if (!this.config) {
|
|
141
|
+
this.loadConfig();
|
|
142
|
+
}
|
|
143
|
+
return this.config;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* อัปเดตค่า config
|
|
148
|
+
*/
|
|
149
|
+
setConfig(key, value) {
|
|
150
|
+
const config = this.getConfig();
|
|
151
|
+
const keys = key.split('.');
|
|
152
|
+
let target = config;
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
155
|
+
if (!target[keys[i]]) {
|
|
156
|
+
target[keys[i]] = {};
|
|
157
|
+
}
|
|
158
|
+
target = target[keys[i]];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
target[keys[keys.length - 1]] = value;
|
|
162
|
+
this.saveConfig();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* ดึง path ของ config
|
|
167
|
+
*/
|
|
168
|
+
getConfigPath() {
|
|
169
|
+
return this.configPath;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* ดึง path ของ skills
|
|
174
|
+
*/
|
|
175
|
+
getSkillsPath() {
|
|
176
|
+
const config = this.getConfig();
|
|
177
|
+
return config.openclaw?.skillsPath || path.join(this.configPath, 'skills');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* ดึง path ของ crons
|
|
182
|
+
*/
|
|
183
|
+
getCronsPath() {
|
|
184
|
+
return path.dirname(this.getCronJobsFilePath());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* ดึง path ของ cron jobs file
|
|
189
|
+
*/
|
|
190
|
+
getCronJobsFilePath() {
|
|
191
|
+
const config = this.getConfig();
|
|
192
|
+
return config.openclaw?.cronJobsFile || path.join(this.configPath, 'crons', 'jobs.json');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* ดึง path ของ logs
|
|
197
|
+
*/
|
|
198
|
+
getLogsPath() {
|
|
199
|
+
return path.join(this.configPath, 'logs');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* บันทึก installed package
|
|
204
|
+
*/
|
|
205
|
+
addInstalledPackage(name, info) {
|
|
206
|
+
const config = this.getConfig();
|
|
207
|
+
if (!config.installed) {
|
|
208
|
+
config.installed = {};
|
|
209
|
+
}
|
|
210
|
+
config.installed[name] = {
|
|
211
|
+
...info,
|
|
212
|
+
installedAt: new Date().toISOString(),
|
|
213
|
+
};
|
|
214
|
+
this.saveConfig();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* ลบ installed package
|
|
219
|
+
*/
|
|
220
|
+
removeInstalledPackage(name) {
|
|
221
|
+
const config = this.getConfig();
|
|
222
|
+
if (config.installed && config.installed[name]) {
|
|
223
|
+
delete config.installed[name];
|
|
224
|
+
this.saveConfig();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* ดึงรายการ installed packages
|
|
230
|
+
*/
|
|
231
|
+
getInstalledPackages() {
|
|
232
|
+
return this.getConfig().installed || {};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* เพิ่ม cronjob
|
|
237
|
+
*/
|
|
238
|
+
addCron(cronInfo) {
|
|
239
|
+
const config = this.getConfig();
|
|
240
|
+
if (!config.crons) {
|
|
241
|
+
config.crons = [];
|
|
242
|
+
}
|
|
243
|
+
config.crons.push({
|
|
244
|
+
id: cronInfo.id || Date.now().toString(),
|
|
245
|
+
...cronInfo,
|
|
246
|
+
createdAt: new Date().toISOString(),
|
|
247
|
+
});
|
|
248
|
+
this.saveConfig();
|
|
249
|
+
return config.crons[config.crons.length - 1];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* ลบ cronjob
|
|
254
|
+
*/
|
|
255
|
+
removeCron(cronId) {
|
|
256
|
+
const config = this.getConfig();
|
|
257
|
+
if (config.crons) {
|
|
258
|
+
config.crons = config.crons.filter(cron => cron.id !== cronId);
|
|
259
|
+
this.saveConfig();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* แก้ไข cronjob
|
|
265
|
+
*/
|
|
266
|
+
updateCron(cronId, patch = {}) {
|
|
267
|
+
const config = this.getConfig();
|
|
268
|
+
if (!config.crons) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const idx = config.crons.findIndex((c) => c.id === cronId);
|
|
273
|
+
if (idx === -1) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
config.crons[idx] = {
|
|
278
|
+
...config.crons[idx],
|
|
279
|
+
...patch,
|
|
280
|
+
updatedAt: new Date().toISOString(),
|
|
281
|
+
};
|
|
282
|
+
this.saveConfig();
|
|
283
|
+
|
|
284
|
+
return config.crons[idx];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* ดึงรายการ cronjobs
|
|
289
|
+
*/
|
|
290
|
+
getCrons() {
|
|
291
|
+
return this.getConfig().crons || [];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = ConfigManager;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const cron = require('node-cron');
|
|
2
|
+
|
|
3
|
+
const CRON_PRESETS = {
|
|
4
|
+
'@yearly': '0 0 1 1 *',
|
|
5
|
+
'@annually': '0 0 1 1 *',
|
|
6
|
+
'@monthly': '0 0 1 * *',
|
|
7
|
+
'@weekly': '0 0 * * 0',
|
|
8
|
+
'@daily': '0 0 * * *',
|
|
9
|
+
'@midnight': '0 0 * * *',
|
|
10
|
+
'@hourly': '0 * * * *',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function durationToCron(value, unit) {
|
|
14
|
+
if (unit === 'm') {
|
|
15
|
+
if (value > 0 && value <= 59) {
|
|
16
|
+
return `*/${value} * * * *`;
|
|
17
|
+
}
|
|
18
|
+
if (value % 60 === 0) {
|
|
19
|
+
return `0 */${Math.floor(value / 60)} * * *`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (unit === 'h' && value > 0) {
|
|
24
|
+
return `0 */${value} * * *`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (unit === 'd' && value > 0) {
|
|
28
|
+
return `0 0 */${value} * *`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeCronExpression(input) {
|
|
35
|
+
if (typeof input !== 'string' || !input.trim()) {
|
|
36
|
+
throw new Error('ต้องระบุ schedule');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let schedule = input.trim().replace(/\s+/g, ' ');
|
|
40
|
+
const lowered = schedule.toLowerCase();
|
|
41
|
+
|
|
42
|
+
if (CRON_PRESETS[lowered]) {
|
|
43
|
+
schedule = CRON_PRESETS[lowered];
|
|
44
|
+
} else {
|
|
45
|
+
const durationMatch = lowered.match(/^(?:every\s+)?(\d+)([mhd])$/i);
|
|
46
|
+
if (durationMatch) {
|
|
47
|
+
const value = Number(durationMatch[1]);
|
|
48
|
+
const unit = durationMatch[2].toLowerCase();
|
|
49
|
+
const converted = durationToCron(value, unit);
|
|
50
|
+
if (!converted) {
|
|
51
|
+
throw new Error(`ไม่รองรับช่วงเวลา "${input}"`);
|
|
52
|
+
}
|
|
53
|
+
schedule = converted;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!cron.validate(schedule)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Cron expression "${input}" ไม่ถูกต้อง (ตัวอย่างที่ใช้ได้: "*/5 * * * *", "@daily", "every 15m")`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return schedule;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
normalizeCronExpression,
|
|
68
|
+
};
|