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.
@@ -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
+ };