clawflowbang 1.0.1 → 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/workflows/publish.yml +46 -0
- package/.github/workflows/test.yml +31 -0
- package/README.md +4 -2
- package/bin/clawflowhub.js +7 -5
- package/package.json +6 -6
- package/src/commands/cron.js +36 -1
- package/src/commands/init.js +3 -3
- package/src/core/ConfigManager.js +132 -132
- package/src/core/CronManager.js +245 -245
- package/src/core/OpenClawCLI.js +52 -7
- package/src/core/TerminalUI.js +7 -5
package/.env.example
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ClawFlow local environment
|
|
2
|
+
DEBUG=
|
|
3
|
+
TZ=Asia/Bangkok
|
|
4
|
+
OPENCLAW_URL=http://localhost:18789
|
|
5
|
+
|
|
6
|
+
# Optional skill credentials
|
|
7
|
+
BINANCE_API_KEY=
|
|
8
|
+
BINANCE_SECRET_KEY=
|
|
9
|
+
FB_ACCESS_TOKEN=
|
|
10
|
+
TWITTER_API_KEY=
|
|
11
|
+
TWITTER_API_SECRET=
|
|
12
|
+
DISCORD_WEBHOOK=
|
|
13
|
+
GOOGLE_CREDENTIALS=
|
|
14
|
+
NOTION_TOKEN=
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
packages: write
|
|
13
|
+
id-token: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
publish:
|
|
17
|
+
name: Publish package
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Use Node.js
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: '18'
|
|
28
|
+
registry-url: 'https://registry.npmjs.org'
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: npm ci
|
|
32
|
+
|
|
33
|
+
- name: Publish to npm (using NPM_TOKEN)
|
|
34
|
+
if: secrets.NPM_TOKEN != ''
|
|
35
|
+
env:
|
|
36
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
37
|
+
run: |
|
|
38
|
+
echo "Publishing with NPM_TOKEN..."
|
|
39
|
+
npm publish --access public
|
|
40
|
+
|
|
41
|
+
- name: Publish to npm (OIDC / Trusted Publisher)
|
|
42
|
+
if: secrets.NPM_TOKEN == ''
|
|
43
|
+
run: |
|
|
44
|
+
echo "No NPM_TOKEN found. To publish without a stored token, set up a Trusted Publisher in npm and enable OIDC."
|
|
45
|
+
echo "See README or npm docs for steps to configure Trusted Publisher."
|
|
46
|
+
exit 1
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Run tests
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Use Node.js
|
|
19
|
+
uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: '18'
|
|
22
|
+
cache: 'npm'
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: npm test
|
|
29
|
+
|
|
30
|
+
- name: Run linter
|
|
31
|
+
run: npm run lint
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`ClawFlow` is a CLI wrapper for OpenClaw that installs skill bundles and wires cron jobs in one flow.
|
|
4
4
|
|
|
5
5
|
Published package: `clawflowbang`
|
|
6
|
-
Primary command: `clawflow` (
|
|
6
|
+
Primary command: `clawflow` (alias: `cfh`)
|
|
7
7
|
|
|
8
8
|
## What It Does
|
|
9
9
|
|
|
@@ -61,7 +61,7 @@ clawflow remove <package>
|
|
|
61
61
|
|
|
62
62
|
clawflow cron-list
|
|
63
63
|
clawflow cron-add <skill> --schedule "*/5 * * * *"
|
|
64
|
-
clawflow cron-edit <id> --every 15m
|
|
64
|
+
clawflow cron-edit <id> --every 15m --description "updated job"
|
|
65
65
|
clawflow cron-remove <id>
|
|
66
66
|
```
|
|
67
67
|
|
|
@@ -78,6 +78,8 @@ Examples:
|
|
|
78
78
|
```bash
|
|
79
79
|
clawflow cron-add crypto-price --every 15m
|
|
80
80
|
clawflow cron-edit <job-id> --schedule "@daily"
|
|
81
|
+
clawflow cron-edit <job-id> --params '{"symbols":["BTC","ETH"]}'
|
|
82
|
+
clawflow cron-remove <job-id>
|
|
81
83
|
```
|
|
82
84
|
|
|
83
85
|
## Skill Install Fallback (ClawHub -> Git)
|
package/bin/clawflowhub.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
require('dotenv').config();
|
|
4
|
+
|
|
5
|
+
const { program } = require('commander');
|
|
6
|
+
const pkg = require('../package.json');
|
|
5
7
|
|
|
6
8
|
// Import TUI
|
|
7
9
|
const TUI = require('../src/core/TerminalUI');
|
|
@@ -35,7 +37,7 @@ program
|
|
|
35
37
|
.option('--openclaw-bin <path>', 'Path to openclaw CLI binary')
|
|
36
38
|
.option('--clawhub-bin <path>', 'Path to clawhub CLI binary')
|
|
37
39
|
.option('--no-cron', 'Install skills without creating cronjobs')
|
|
38
|
-
.option('--dry-run', 'Show what would be done without installing')
|
|
40
|
+
.option('--dry-run', 'Show what would be done without installing')
|
|
39
41
|
.action(installCommand);
|
|
40
42
|
|
|
41
43
|
// List command
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawflowbang",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Skill + Cron Installer for OpenClaw - Install skills and configure them for immediate use",
|
|
5
5
|
"main": "src/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"clawflow": "bin/clawflowhub.js",
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
},
|
|
6
|
+
"bin": {
|
|
7
|
+
"clawflow": "bin/clawflowhub.js",
|
|
8
|
+
"cfh": "bin/clawflowhub.js"
|
|
9
|
+
},
|
|
11
10
|
"scripts": {
|
|
12
11
|
"start": "node bin/clawflowhub.js",
|
|
13
12
|
"test": "jest",
|
|
@@ -29,6 +28,7 @@
|
|
|
29
28
|
"boxen": "^8.0.1",
|
|
30
29
|
"chalk": "^4.1.2",
|
|
31
30
|
"commander": "^11.0.0",
|
|
31
|
+
"dotenv": "^16.6.1",
|
|
32
32
|
"fs-extra": "^11.1.1",
|
|
33
33
|
"gradient-string": "^3.0.0",
|
|
34
34
|
"inquirer": "^8.2.6",
|
package/src/commands/cron.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
const ClawFlow = require('../index');
|
|
7
7
|
const { normalizeCronExpression } = require('../core/CronFormat');
|
|
8
|
+
const YAML = require('yaml');
|
|
8
9
|
|
|
9
10
|
function parseJsonParams(params) {
|
|
10
11
|
if (params === undefined || params === null || params === '') {
|
|
@@ -15,7 +16,41 @@ function parseJsonParams(params) {
|
|
|
15
16
|
return params;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
const raw = String(params).trim();
|
|
20
|
+
const unquoted =
|
|
21
|
+
(raw.startsWith("'") && raw.endsWith("'")) || (raw.startsWith('"') && raw.endsWith('"'))
|
|
22
|
+
? raw.slice(1, -1).trim()
|
|
23
|
+
: raw;
|
|
24
|
+
|
|
25
|
+
const candidates = [raw, unquoted];
|
|
26
|
+
|
|
27
|
+
for (const candidate of candidates) {
|
|
28
|
+
if (!candidate) continue;
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(candidate);
|
|
31
|
+
if (parsed && typeof parsed === 'object') {
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
} catch (_error) {
|
|
35
|
+
// Continue to YAML fallback
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const candidate of candidates) {
|
|
40
|
+
if (!candidate) continue;
|
|
41
|
+
try {
|
|
42
|
+
const parsed = YAML.parse(candidate);
|
|
43
|
+
if (parsed && typeof parsed === 'object') {
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
} catch (_error) {
|
|
47
|
+
// Continue
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
throw new Error(
|
|
52
|
+
'รูปแบบ --params ไม่ถูกต้อง (ใช้ JSON object เช่น {"message":"hello"} หรือ YAML object เช่น message: hello)',
|
|
53
|
+
);
|
|
19
54
|
}
|
|
20
55
|
|
|
21
56
|
function resolveScheduleInput(options) {
|
package/src/commands/init.js
CHANGED
|
@@ -65,9 +65,9 @@ module.exports = async function initCommand(options) {
|
|
|
65
65
|
// สร้าง config
|
|
66
66
|
const config = {
|
|
67
67
|
version: '1.0.0',
|
|
68
|
-
openclaw: {
|
|
69
|
-
baseUrl: 'http://localhost:
|
|
70
|
-
apiKey: null,
|
|
68
|
+
openclaw: {
|
|
69
|
+
baseUrl: 'http://localhost:18789',
|
|
70
|
+
apiKey: null,
|
|
71
71
|
cliBin: answers.openclawBin,
|
|
72
72
|
clawhubBin: answers.clawhubBin,
|
|
73
73
|
workspacePath: path.join(cwd, '.openclaw'),
|
|
@@ -6,13 +6,13 @@ const fs = require('fs-extra');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const os = require('os');
|
|
8
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
|
-
}
|
|
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
16
|
|
|
17
17
|
/**
|
|
18
18
|
* ได้รับ default config path ตาม OS
|
|
@@ -32,55 +32,55 @@ class ConfigManager {
|
|
|
32
32
|
/**
|
|
33
33
|
* สร้างโฟลเดอร์ที่จำเป็น
|
|
34
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
|
-
}
|
|
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
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Config เริ่มต้น
|
|
68
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:
|
|
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
|
-
},
|
|
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:18789',
|
|
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
84
|
registry: {
|
|
85
85
|
url: 'https://registry.clawflowhub.dev',
|
|
86
86
|
cacheExpiry: 3600, // วินาที
|
|
@@ -93,34 +93,34 @@ class ConfigManager {
|
|
|
93
93
|
installed: {},
|
|
94
94
|
crons: [],
|
|
95
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
|
-
}
|
|
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
111
|
|
|
112
112
|
/**
|
|
113
113
|
* โหลด config
|
|
114
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
|
-
}
|
|
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
124
|
|
|
125
125
|
/**
|
|
126
126
|
* บันทึก config
|
|
@@ -172,25 +172,25 @@ class ConfigManager {
|
|
|
172
172
|
/**
|
|
173
173
|
* ดึง path ของ skills
|
|
174
174
|
*/
|
|
175
|
-
getSkillsPath() {
|
|
176
|
-
const config = this.getConfig();
|
|
177
|
-
return config.openclaw?.skillsPath || path.join(this.configPath, 'skills');
|
|
178
|
-
}
|
|
175
|
+
getSkillsPath() {
|
|
176
|
+
const config = this.getConfig();
|
|
177
|
+
return config.openclaw?.skillsPath || path.join(this.configPath, 'skills');
|
|
178
|
+
}
|
|
179
179
|
|
|
180
180
|
/**
|
|
181
181
|
* ดึง path ของ crons
|
|
182
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
|
-
}
|
|
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
194
|
|
|
195
195
|
/**
|
|
196
196
|
* ดึง path ของ logs
|
|
@@ -235,16 +235,16 @@ class ConfigManager {
|
|
|
235
235
|
/**
|
|
236
236
|
* เพิ่ม cronjob
|
|
237
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
|
-
});
|
|
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
248
|
this.saveConfig();
|
|
249
249
|
return config.crons[config.crons.length - 1];
|
|
250
250
|
}
|
|
@@ -252,37 +252,37 @@ class ConfigManager {
|
|
|
252
252
|
/**
|
|
253
253
|
* ลบ cronjob
|
|
254
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
|
-
}
|
|
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
286
|
|
|
287
287
|
/**
|
|
288
288
|
* ดึงรายการ cronjobs
|
package/src/core/CronManager.js
CHANGED
|
@@ -3,46 +3,46 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const cron = require('node-cron');
|
|
8
|
-
const chalk = require('chalk');
|
|
9
|
-
const OpenClawCLI = require('./OpenClawCLI');
|
|
10
|
-
const { normalizeCronExpression } = require('./CronFormat');
|
|
11
|
-
|
|
12
|
-
class CronManager {
|
|
13
|
-
constructor(configManager) {
|
|
14
|
-
this.configManager = configManager;
|
|
15
|
-
this.tasks = new Map();
|
|
16
|
-
this.jobsFile = configManager.getCronJobsFilePath();
|
|
17
|
-
this.openclawCLI = new OpenClawCLI(configManager);
|
|
18
|
-
this.useOpenClawCron = this.openclawCLI.hasOpenClaw();
|
|
19
|
-
|
|
20
|
-
if (!this.useOpenClawCron) {
|
|
21
|
-
this.ensureJobsFile();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const cron = require('node-cron');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const OpenClawCLI = require('./OpenClawCLI');
|
|
10
|
+
const { normalizeCronExpression } = require('./CronFormat');
|
|
11
|
+
|
|
12
|
+
class CronManager {
|
|
13
|
+
constructor(configManager) {
|
|
14
|
+
this.configManager = configManager;
|
|
15
|
+
this.tasks = new Map();
|
|
16
|
+
this.jobsFile = configManager.getCronJobsFilePath();
|
|
17
|
+
this.openclawCLI = new OpenClawCLI(configManager);
|
|
18
|
+
this.useOpenClawCron = this.openclawCLI.hasOpenClaw();
|
|
19
|
+
|
|
20
|
+
if (!this.useOpenClawCron) {
|
|
21
|
+
this.ensureJobsFile();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* สร้างไฟล์ jobs ถ้ายังไม่มี
|
|
27
27
|
*/
|
|
28
|
-
ensureJobsFile() {
|
|
29
|
-
fs.ensureDirSync(path.dirname(this.jobsFile));
|
|
30
|
-
if (!fs.existsSync(this.jobsFile)) {
|
|
31
|
-
fs.writeJsonSync(this.jobsFile, { jobs: [] }, { spaces: 2 });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
28
|
+
ensureJobsFile() {
|
|
29
|
+
fs.ensureDirSync(path.dirname(this.jobsFile));
|
|
30
|
+
if (!fs.existsSync(this.jobsFile)) {
|
|
31
|
+
fs.writeJsonSync(this.jobsFile, { jobs: [] }, { spaces: 2 });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* อ่านรายการ jobs
|
|
37
37
|
*/
|
|
38
|
-
getJobs() {
|
|
39
|
-
if (!fs.existsSync(this.jobsFile)) {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const data = fs.readJsonSync(this.jobsFile);
|
|
44
|
-
return data.jobs || [];
|
|
45
|
-
}
|
|
38
|
+
getJobs() {
|
|
39
|
+
if (!fs.existsSync(this.jobsFile)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = fs.readJsonSync(this.jobsFile);
|
|
44
|
+
return data.jobs || [];
|
|
45
|
+
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* บันทึกรายการ jobs
|
|
@@ -54,76 +54,76 @@ class CronManager {
|
|
|
54
54
|
/**
|
|
55
55
|
* เพิ่ม cronjob
|
|
56
56
|
*/
|
|
57
|
-
async add(skillName, schedule, params = {}, description = '') {
|
|
58
|
-
const normalizedSchedule = normalizeCronExpression(schedule);
|
|
59
|
-
|
|
60
|
-
if (this.useOpenClawCron) {
|
|
61
|
-
const uniqueName = `cfh:${skillName}:${Date.now()}`;
|
|
62
|
-
const message = `Run skill
|
|
63
|
-
const created = await this.openclawCLI.addCronJob({
|
|
64
|
-
name: uniqueName,
|
|
65
|
-
description: description || `Run ${skillName}`,
|
|
66
|
-
schedule: normalizedSchedule,
|
|
67
|
-
message,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
let jobId = created.jobId;
|
|
71
|
-
if (!jobId) {
|
|
72
|
-
const jobs = await this.openclawCLI.listCronJobs();
|
|
73
|
-
const matched = jobs.find((job) => job.name === uniqueName);
|
|
74
|
-
jobId = matched?.id || null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!jobId) {
|
|
78
|
-
throw new Error('สร้าง cronjob ผ่าน openclaw สำเร็จ แต่ไม่สามารถระบุ job id ได้');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
this.configManager.addCron({
|
|
82
|
-
id: jobId,
|
|
83
|
-
skill: skillName,
|
|
84
|
-
schedule: normalizedSchedule,
|
|
85
|
-
description: description || `Run ${skillName}`,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
console.log(chalk.green(`✓ เพิ่ม cronjob (openclaw): ${skillName} (${normalizedSchedule})`));
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
id: jobId,
|
|
92
|
-
skill: skillName,
|
|
93
|
-
schedule: normalizedSchedule,
|
|
94
|
-
params,
|
|
95
|
-
description: description || `Run ${skillName}`,
|
|
96
|
-
enabled: true,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const jobs = this.getJobs();
|
|
101
|
-
const jobId = `cron_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
57
|
+
async add(skillName, schedule, params = {}, description = '') {
|
|
58
|
+
const normalizedSchedule = normalizeCronExpression(schedule);
|
|
59
|
+
|
|
60
|
+
if (this.useOpenClawCron) {
|
|
61
|
+
const uniqueName = `cfh:${skillName}:${Date.now()}`;
|
|
62
|
+
const message = `Run skill ${skillName} with params: ${JSON.stringify(params)}`;
|
|
63
|
+
const created = await this.openclawCLI.addCronJob({
|
|
64
|
+
name: uniqueName,
|
|
65
|
+
description: description || `Run ${skillName}`,
|
|
66
|
+
schedule: normalizedSchedule,
|
|
67
|
+
message,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
let jobId = created.jobId;
|
|
71
|
+
if (!jobId) {
|
|
72
|
+
const jobs = await this.openclawCLI.listCronJobs();
|
|
73
|
+
const matched = jobs.find((job) => job.name === uniqueName);
|
|
74
|
+
jobId = matched?.id || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!jobId) {
|
|
78
|
+
throw new Error('สร้าง cronjob ผ่าน openclaw สำเร็จ แต่ไม่สามารถระบุ job id ได้');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.configManager.addCron({
|
|
82
|
+
id: jobId,
|
|
83
|
+
skill: skillName,
|
|
84
|
+
schedule: normalizedSchedule,
|
|
85
|
+
description: description || `Run ${skillName}`,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
console.log(chalk.green(`✓ เพิ่ม cronjob (openclaw): ${skillName} (${normalizedSchedule})`));
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
id: jobId,
|
|
92
|
+
skill: skillName,
|
|
93
|
+
schedule: normalizedSchedule,
|
|
94
|
+
params,
|
|
95
|
+
description: description || `Run ${skillName}`,
|
|
96
|
+
enabled: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const jobs = this.getJobs();
|
|
101
|
+
const jobId = `cron_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
102
102
|
|
|
103
103
|
const job = {
|
|
104
|
-
id: jobId,
|
|
105
|
-
skill: skillName,
|
|
106
|
-
schedule: normalizedSchedule,
|
|
107
|
-
params,
|
|
108
|
-
description: description || `Run ${skillName}`,
|
|
109
|
-
enabled: true,
|
|
110
|
-
createdAt: new Date().toISOString(),
|
|
111
|
-
lastRun: null,
|
|
112
|
-
nextRun: this.getNextRun(normalizedSchedule),
|
|
113
|
-
runCount: 0,
|
|
114
|
-
errorCount: 0,
|
|
115
|
-
};
|
|
104
|
+
id: jobId,
|
|
105
|
+
skill: skillName,
|
|
106
|
+
schedule: normalizedSchedule,
|
|
107
|
+
params,
|
|
108
|
+
description: description || `Run ${skillName}`,
|
|
109
|
+
enabled: true,
|
|
110
|
+
createdAt: new Date().toISOString(),
|
|
111
|
+
lastRun: null,
|
|
112
|
+
nextRun: this.getNextRun(normalizedSchedule),
|
|
113
|
+
runCount: 0,
|
|
114
|
+
errorCount: 0,
|
|
115
|
+
};
|
|
116
116
|
|
|
117
117
|
jobs.push(job);
|
|
118
118
|
this.saveJobs(jobs);
|
|
119
119
|
|
|
120
120
|
// บันทึกลง config ด้วย
|
|
121
121
|
this.configManager.addCron({
|
|
122
|
-
id: jobId,
|
|
123
|
-
skill: skillName,
|
|
124
|
-
schedule: normalizedSchedule,
|
|
125
|
-
description,
|
|
126
|
-
});
|
|
122
|
+
id: jobId,
|
|
123
|
+
skill: skillName,
|
|
124
|
+
schedule: normalizedSchedule,
|
|
125
|
+
description,
|
|
126
|
+
});
|
|
127
127
|
|
|
128
128
|
// สร้าง cron script
|
|
129
129
|
this.createCronScript(job);
|
|
@@ -133,89 +133,89 @@ class CronManager {
|
|
|
133
133
|
this.startJob(job);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
console.log(chalk.green(`✓ เพิ่ม cronjob: ${skillName} (${normalizedSchedule})`));
|
|
137
|
-
|
|
138
|
-
return job;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* แก้ไข cronjob
|
|
143
|
-
*/
|
|
144
|
-
async edit(jobId, updates = {}) {
|
|
145
|
-
const normalized = { ...updates };
|
|
146
|
-
|
|
147
|
-
if (updates.schedule) {
|
|
148
|
-
normalized.schedule = normalizeCronExpression(updates.schedule);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (this.useOpenClawCron) {
|
|
152
|
-
const tracked = this.configManager.getCrons().find((c) => c.id === jobId);
|
|
153
|
-
const message =
|
|
154
|
-
Object.prototype.hasOwnProperty.call(updates, 'params') && tracked?.skill
|
|
155
|
-
? `Run skill
|
|
156
|
-
: undefined;
|
|
157
|
-
|
|
158
|
-
if (Object.prototype.hasOwnProperty.call(updates, 'params') && !tracked?.skill) {
|
|
159
|
-
throw new Error('แก้ไข params ไม่ได้ เพราะไม่พบ mapping skill ของ cron นี้ใน config');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
await this.openclawCLI.editCronJob(jobId, {
|
|
163
|
-
schedule: normalized.schedule,
|
|
164
|
-
description: normalized.description,
|
|
165
|
-
message,
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
this.configManager.updateCron(jobId, {
|
|
169
|
-
...(normalized.schedule ? { schedule: normalized.schedule } : {}),
|
|
170
|
-
...(typeof normalized.description === 'string' ? { description: normalized.description } : {}),
|
|
171
|
-
...(Object.prototype.hasOwnProperty.call(updates, 'params') ? { params: updates.params } : {}),
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
return { id: jobId, ...normalized };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const jobs = this.getJobs();
|
|
178
|
-
const job = jobs.find((j) => j.id === jobId);
|
|
179
|
-
|
|
180
|
-
if (!job) {
|
|
181
|
-
throw new Error(`ไม่พบ cronjob ID: ${jobId}`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (normalized.schedule) {
|
|
185
|
-
job.schedule = normalized.schedule;
|
|
186
|
-
job.nextRun = this.getNextRun(normalized.schedule);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (typeof normalized.description === 'string') {
|
|
190
|
-
job.description = normalized.description;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (Object.prototype.hasOwnProperty.call(updates, 'params')) {
|
|
194
|
-
job.params = updates.params || {};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
this.saveJobs(jobs);
|
|
198
|
-
this.configManager.updateCron(jobId, {
|
|
199
|
-
...(normalized.schedule ? { schedule: normalized.schedule } : {}),
|
|
200
|
-
...(typeof normalized.description === 'string' ? { description: normalized.description } : {}),
|
|
201
|
-
...(Object.prototype.hasOwnProperty.call(updates, 'params') ? { params: updates.params } : {}),
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return job;
|
|
205
|
-
}
|
|
136
|
+
console.log(chalk.green(`✓ เพิ่ม cronjob: ${skillName} (${normalizedSchedule})`));
|
|
137
|
+
|
|
138
|
+
return job;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* แก้ไข cronjob
|
|
143
|
+
*/
|
|
144
|
+
async edit(jobId, updates = {}) {
|
|
145
|
+
const normalized = { ...updates };
|
|
146
|
+
|
|
147
|
+
if (updates.schedule) {
|
|
148
|
+
normalized.schedule = normalizeCronExpression(updates.schedule);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (this.useOpenClawCron) {
|
|
152
|
+
const tracked = this.configManager.getCrons().find((c) => c.id === jobId);
|
|
153
|
+
const message =
|
|
154
|
+
Object.prototype.hasOwnProperty.call(updates, 'params') && tracked?.skill
|
|
155
|
+
? `Run skill ${tracked.skill} with params: ${JSON.stringify(updates.params || {})}`
|
|
156
|
+
: undefined;
|
|
157
|
+
|
|
158
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'params') && !tracked?.skill) {
|
|
159
|
+
throw new Error('แก้ไข params ไม่ได้ เพราะไม่พบ mapping skill ของ cron นี้ใน config');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await this.openclawCLI.editCronJob(jobId, {
|
|
163
|
+
schedule: normalized.schedule,
|
|
164
|
+
description: normalized.description,
|
|
165
|
+
message,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
this.configManager.updateCron(jobId, {
|
|
169
|
+
...(normalized.schedule ? { schedule: normalized.schedule } : {}),
|
|
170
|
+
...(typeof normalized.description === 'string' ? { description: normalized.description } : {}),
|
|
171
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'params') ? { params: updates.params } : {}),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return { id: jobId, ...normalized };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const jobs = this.getJobs();
|
|
178
|
+
const job = jobs.find((j) => j.id === jobId);
|
|
179
|
+
|
|
180
|
+
if (!job) {
|
|
181
|
+
throw new Error(`ไม่พบ cronjob ID: ${jobId}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (normalized.schedule) {
|
|
185
|
+
job.schedule = normalized.schedule;
|
|
186
|
+
job.nextRun = this.getNextRun(normalized.schedule);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (typeof normalized.description === 'string') {
|
|
190
|
+
job.description = normalized.description;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'params')) {
|
|
194
|
+
job.params = updates.params || {};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.saveJobs(jobs);
|
|
198
|
+
this.configManager.updateCron(jobId, {
|
|
199
|
+
...(normalized.schedule ? { schedule: normalized.schedule } : {}),
|
|
200
|
+
...(typeof normalized.description === 'string' ? { description: normalized.description } : {}),
|
|
201
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'params') ? { params: updates.params } : {}),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return job;
|
|
205
|
+
}
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
208
|
* ลบ cronjob
|
|
209
209
|
*/
|
|
210
|
-
async remove(jobId) {
|
|
211
|
-
if (this.useOpenClawCron) {
|
|
212
|
-
await this.openclawCLI.removeCronJob(jobId);
|
|
213
|
-
this.configManager.removeCron(jobId);
|
|
214
|
-
console.log(chalk.green(`✓ ลบ cronjob (openclaw): ${jobId}`));
|
|
215
|
-
return { success: true, removed: { id: jobId } };
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
let jobs = this.getJobs();
|
|
210
|
+
async remove(jobId) {
|
|
211
|
+
if (this.useOpenClawCron) {
|
|
212
|
+
await this.openclawCLI.removeCronJob(jobId);
|
|
213
|
+
this.configManager.removeCron(jobId);
|
|
214
|
+
console.log(chalk.green(`✓ ลบ cronjob (openclaw): ${jobId}`));
|
|
215
|
+
return { success: true, removed: { id: jobId } };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let jobs = this.getJobs();
|
|
219
219
|
const job = jobs.find(j => j.id === jobId);
|
|
220
220
|
|
|
221
221
|
if (!job) {
|
|
@@ -243,12 +243,12 @@ class CronManager {
|
|
|
243
243
|
/**
|
|
244
244
|
* แสดงรายการ cronjobs
|
|
245
245
|
*/
|
|
246
|
-
list() {
|
|
247
|
-
if (this.useOpenClawCron) {
|
|
248
|
-
return this.listViaOpenClaw();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const jobs = this.getJobs();
|
|
246
|
+
list() {
|
|
247
|
+
if (this.useOpenClawCron) {
|
|
248
|
+
return this.listViaOpenClaw();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const jobs = this.getJobs();
|
|
252
252
|
|
|
253
253
|
if (jobs.length === 0) {
|
|
254
254
|
console.log(chalk.gray('ไม่มี cronjob ที่ตั้งไว้'));
|
|
@@ -265,30 +265,30 @@ class CronManager {
|
|
|
265
265
|
nextRun: job.nextRun,
|
|
266
266
|
runCount: job.runCount,
|
|
267
267
|
}));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* แสดงรายการ cronjobs ผ่าน OpenClaw CLI
|
|
272
|
-
*/
|
|
273
|
-
async listViaOpenClaw() {
|
|
274
|
-
const jobs = await this.openclawCLI.listCronJobs();
|
|
275
|
-
|
|
276
|
-
if (!jobs || jobs.length === 0) {
|
|
277
|
-
console.log(chalk.gray('ไม่มี cronjob ที่ตั้งไว้'));
|
|
278
|
-
return [];
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return jobs.map((job) => ({
|
|
282
|
-
id: job.id,
|
|
283
|
-
skill: job.name || 'unknown',
|
|
284
|
-
schedule: job.schedule?.expr || job.schedule?.kind || '-',
|
|
285
|
-
description: job.description || '',
|
|
286
|
-
enabled: job.enabled !== false,
|
|
287
|
-
lastRun: job.lastRunAt || null,
|
|
288
|
-
nextRun: job.nextRunAt || null,
|
|
289
|
-
runCount: job.runCount || 0,
|
|
290
|
-
}));
|
|
291
|
-
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* แสดงรายการ cronjobs ผ่าน OpenClaw CLI
|
|
272
|
+
*/
|
|
273
|
+
async listViaOpenClaw() {
|
|
274
|
+
const jobs = await this.openclawCLI.listCronJobs();
|
|
275
|
+
|
|
276
|
+
if (!jobs || jobs.length === 0) {
|
|
277
|
+
console.log(chalk.gray('ไม่มี cronjob ที่ตั้งไว้'));
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return jobs.map((job) => ({
|
|
282
|
+
id: job.id,
|
|
283
|
+
skill: job.name || 'unknown',
|
|
284
|
+
schedule: job.schedule?.expr || job.schedule?.kind || '-',
|
|
285
|
+
description: job.description || '',
|
|
286
|
+
enabled: job.enabled !== false,
|
|
287
|
+
lastRun: job.lastRunAt || null,
|
|
288
|
+
nextRun: job.nextRunAt || null,
|
|
289
|
+
runCount: job.runCount || 0,
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
292
|
|
|
293
293
|
/**
|
|
294
294
|
* เริ่มต้น job
|
|
@@ -311,16 +311,16 @@ class CronManager {
|
|
|
311
311
|
/**
|
|
312
312
|
* หยุด job
|
|
313
313
|
*/
|
|
314
|
-
stopJob(jobId) {
|
|
315
|
-
const task = this.tasks.get(jobId);
|
|
316
|
-
if (task) {
|
|
317
|
-
task.stop();
|
|
318
|
-
if (typeof task.destroy === 'function') {
|
|
319
|
-
task.destroy();
|
|
320
|
-
}
|
|
321
|
-
this.tasks.delete(jobId);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
314
|
+
stopJob(jobId) {
|
|
315
|
+
const task = this.tasks.get(jobId);
|
|
316
|
+
if (task) {
|
|
317
|
+
task.stop();
|
|
318
|
+
if (typeof task.destroy === 'function') {
|
|
319
|
+
task.destroy();
|
|
320
|
+
}
|
|
321
|
+
this.tasks.delete(jobId);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
324
|
|
|
325
325
|
/**
|
|
326
326
|
* รัน job
|
|
@@ -362,10 +362,10 @@ class CronManager {
|
|
|
362
362
|
/**
|
|
363
363
|
* รัน skill
|
|
364
364
|
*/
|
|
365
|
-
async runSkill(skillName, params) {
|
|
366
|
-
// ในโลกจริงจะเรียก OpenClaw API
|
|
367
|
-
// const response = await axios.post(`${baseUrl}/api/skills/execute`, {
|
|
368
|
-
// name: skillName,
|
|
365
|
+
async runSkill(skillName, params) {
|
|
366
|
+
// ในโลกจริงจะเรียก OpenClaw API
|
|
367
|
+
// const response = await axios.post(`${baseUrl}/api/skills/execute`, {
|
|
368
|
+
// name: skillName,
|
|
369
369
|
// params,
|
|
370
370
|
// });
|
|
371
371
|
|
|
@@ -392,7 +392,7 @@ async function run() {
|
|
|
392
392
|
console.log(\`[\${new Date().toISOString()}] Running \${skill}...\`);
|
|
393
393
|
|
|
394
394
|
// Call OpenClaw API
|
|
395
|
-
const baseUrl = process.env.OPENCLAW_URL || 'http://localhost:
|
|
395
|
+
const baseUrl = process.env.OPENCLAW_URL || 'http://localhost:18789';
|
|
396
396
|
const response = await axios.post(\`\${baseUrl}/api/skills/execute\`, {
|
|
397
397
|
name: skill,
|
|
398
398
|
params: { ...config, ...params },
|
|
@@ -435,7 +435,7 @@ run();
|
|
|
435
435
|
/**
|
|
436
436
|
* คำนวณเวลารันครั้งถัดไป
|
|
437
437
|
*/
|
|
438
|
-
getNextRun(_schedule) {
|
|
438
|
+
getNextRun(_schedule) {
|
|
439
439
|
// ง่ายๆ แค่ return null ตอนนี้
|
|
440
440
|
// ในอนาคตอาจใช้ library คำนวณจริง
|
|
441
441
|
return null;
|
|
@@ -444,13 +444,13 @@ run();
|
|
|
444
444
|
/**
|
|
445
445
|
* เปิด/ปิด job
|
|
446
446
|
*/
|
|
447
|
-
toggleJob(jobId, enabled) {
|
|
448
|
-
if (this.useOpenClawCron) {
|
|
449
|
-
throw new Error('โหมด openclaw cron: ให้ใช้คำสั่ง "openclaw cron enable|disable <id>"');
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const jobs = this.getJobs();
|
|
453
|
-
const job = jobs.find(j => j.id === jobId);
|
|
447
|
+
toggleJob(jobId, enabled) {
|
|
448
|
+
if (this.useOpenClawCron) {
|
|
449
|
+
throw new Error('โหมด openclaw cron: ให้ใช้คำสั่ง "openclaw cron enable|disable <id>"');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const jobs = this.getJobs();
|
|
453
|
+
const job = jobs.find(j => j.id === jobId);
|
|
454
454
|
|
|
455
455
|
if (!job) {
|
|
456
456
|
throw new Error(`ไม่พบ cronjob ID: ${jobId}`);
|
|
@@ -473,13 +473,13 @@ run();
|
|
|
473
473
|
/**
|
|
474
474
|
* เริ่มต้นระบบ cron (เรียกตอน start)
|
|
475
475
|
*/
|
|
476
|
-
startAll() {
|
|
477
|
-
if (this.useOpenClawCron) {
|
|
478
|
-
console.log(chalk.green('✓ ใช้ openclaw cron scheduler (ไม่ต้อง start local tasks)'));
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const jobs = this.getJobs().filter(j => j.enabled);
|
|
476
|
+
startAll() {
|
|
477
|
+
if (this.useOpenClawCron) {
|
|
478
|
+
console.log(chalk.green('✓ ใช้ openclaw cron scheduler (ไม่ต้อง start local tasks)'));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const jobs = this.getJobs().filter(j => j.enabled);
|
|
483
483
|
|
|
484
484
|
for (const job of jobs) {
|
|
485
485
|
this.startJob(job);
|
|
@@ -491,18 +491,18 @@ run();
|
|
|
491
491
|
/**
|
|
492
492
|
* หยุดระบบ cron ทั้งหมด
|
|
493
493
|
*/
|
|
494
|
-
stopAll() {
|
|
495
|
-
if (this.useOpenClawCron) {
|
|
496
|
-
console.log(chalk.yellow('⏹️ openclaw cron scheduler ยังทำงานภายนอก process นี้'));
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
for (const [, task] of this.tasks) {
|
|
501
|
-
task.stop();
|
|
502
|
-
if (typeof task.destroy === 'function') {
|
|
503
|
-
task.destroy();
|
|
504
|
-
}
|
|
505
|
-
}
|
|
494
|
+
stopAll() {
|
|
495
|
+
if (this.useOpenClawCron) {
|
|
496
|
+
console.log(chalk.yellow('⏹️ openclaw cron scheduler ยังทำงานภายนอก process นี้'));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
for (const [, task] of this.tasks) {
|
|
501
|
+
task.stop();
|
|
502
|
+
if (typeof task.destroy === 'function') {
|
|
503
|
+
task.destroy();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
506
|
this.tasks.clear();
|
|
507
507
|
|
|
508
508
|
console.log(chalk.yellow('⏹️ หยุดระบบ cron'));
|
package/src/core/OpenClawCLI.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const { promisify } = require('util');
|
|
4
|
-
const { execFile, spawnSync } = require('child_process');
|
|
4
|
+
const { execFile, spawn, spawnSync } = require('child_process');
|
|
5
5
|
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
|
|
@@ -33,22 +33,67 @@ class OpenClawCLI {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
commandExists(command, args = ['--help']) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
try {
|
|
37
|
+
const result = spawnSync(command, args, {
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
stdio: 'pipe',
|
|
40
|
+
windowsHide: true,
|
|
41
|
+
shell: true,
|
|
42
|
+
});
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
return result.status === 0;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
async run(command, args, options = {}) {
|
|
51
|
+
if (process.platform === 'win32') {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const comspec = process.env.ComSpec || 'cmd.exe';
|
|
54
|
+
|
|
55
|
+
const child = spawn(comspec, ['/d', '/s', '/c', command, ...args], {
|
|
56
|
+
cwd: options.cwd,
|
|
57
|
+
env: process.env,
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
shell: false,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let stdout = '';
|
|
63
|
+
let stderr = '';
|
|
64
|
+
|
|
65
|
+
child.stdout.on('data', (chunk) => {
|
|
66
|
+
stdout += String(chunk);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
child.stderr.on('data', (chunk) => {
|
|
70
|
+
stderr += String(chunk);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
child.on('error', (error) => {
|
|
74
|
+
reject(new Error(`${command} ${args.join(' ')} ล้มเหลว: ${error.message}`));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
child.on('close', (code) => {
|
|
78
|
+
const out = stdout.trim();
|
|
79
|
+
const err = stderr.trim();
|
|
80
|
+
if (code === 0) {
|
|
81
|
+
resolve({ stdout: out, stderr: err });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const detail = err || out || `exit code ${code}`;
|
|
85
|
+
reject(new Error(`${command} ${args.join(' ')} ล้มเหลว: ${detail}`));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
46
90
|
try {
|
|
47
91
|
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
48
92
|
cwd: options.cwd,
|
|
49
93
|
env: process.env,
|
|
50
94
|
maxBuffer: 1024 * 1024 * 4,
|
|
51
95
|
windowsHide: true,
|
|
96
|
+
shell: true,
|
|
52
97
|
});
|
|
53
98
|
return {
|
|
54
99
|
stdout: (stdout || '').trim(),
|
package/src/core/TerminalUI.js
CHANGED
|
@@ -58,8 +58,9 @@ const boxStyles = {
|
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
/* eslint-disable no-useless-escape */
|
|
62
|
+
// ASCII Art for logo - Cool Retro Style
|
|
63
|
+
const asciiLogo = `
|
|
63
64
|
________ ___ ________ ___ __ ________ ___ ________ ___ __
|
|
64
65
|
|\ ____\|\ \ |\ __ \|\ \ |\ \|\ _____\\ \ |\ __ \|\ \ |\ \
|
|
65
66
|
\ \ \___|\ \ \ \ \ \|\ \ \ \ \ \ \ \ \__/\ \ \ \ \ \|\ \ \ \ \ \ \
|
|
@@ -70,8 +71,9 @@ const asciiLogo = `
|
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
ClawFlow - Skill + Cron Installer for OpenClaw
|
|
74
|
-
`;
|
|
74
|
+
ClawFlow - Skill + Cron Installer for OpenClaw
|
|
75
|
+
`;
|
|
76
|
+
/* eslint-enable no-useless-escape */
|
|
75
77
|
|
|
76
78
|
// Mini logo for sub-headers
|
|
77
79
|
const miniLogo = `
|
|
@@ -178,7 +180,7 @@ function createProgressBar(total, current, width = 30) {
|
|
|
178
180
|
/**
|
|
179
181
|
* Print main banner
|
|
180
182
|
*/
|
|
181
|
-
function printBanner(
|
|
183
|
+
function printBanner(_pkgVersion = '1.0.0') {
|
|
182
184
|
const banner = gradientText(asciiLogo, colors.primary, colors.secondary);
|
|
183
185
|
|
|
184
186
|
console.log(chalk.bgBlack(banner));
|