clawflowbang 1.0.2 → 1.1.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/README.md +162 -49
- package/bin/clawflowhub.js +152 -66
- package/package.json +20 -8
- package/src/commands/create.js +286 -0
- package/src/commands/cron.js +46 -21
- package/src/commands/doctor.js +146 -0
- package/src/commands/explore.js +49 -0
- package/src/commands/register.js +83 -0
- package/src/commands/update.js +35 -0
- package/src/core/Installer.js +226 -109
- package/src/core/TerminalUI.js +12 -17
- package/.env.example +0 -14
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -35
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
- package/.github/pull_request_template.md +0 -19
- package/.github/workflows/publish.yml +0 -46
- package/.github/workflows/test.yml +0 -31
- package/CODE_OF_CONDUCT.md +0 -29
- package/SECURITY.md +0 -20
- package/SUPPORT.md +0 -21
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register command - Register a local skill or agent via symlink
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require("chalk");
|
|
6
|
+
const fs = require("fs-extra");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const ClawFlow = require("../index");
|
|
9
|
+
|
|
10
|
+
module.exports = async function registerCommand(targetPath) {
|
|
11
|
+
const hub = new ClawFlow();
|
|
12
|
+
const sourcePath = path.resolve(process.cwd(), targetPath || ".");
|
|
13
|
+
|
|
14
|
+
console.log(chalk.cyan.bold("\n🏷️ ClawFlow Register\n"));
|
|
15
|
+
|
|
16
|
+
// 1. Verify source
|
|
17
|
+
const skillYaml = path.join(sourcePath, "skill.yaml");
|
|
18
|
+
const agentYaml = path.join(sourcePath, "agent.yaml");
|
|
19
|
+
const skillMd = path.join(sourcePath, "SKILL.md");
|
|
20
|
+
|
|
21
|
+
if (
|
|
22
|
+
!fs.existsSync(skillYaml) &&
|
|
23
|
+
!fs.existsSync(agentYaml) &&
|
|
24
|
+
!fs.existsSync(skillMd)
|
|
25
|
+
) {
|
|
26
|
+
console.error(
|
|
27
|
+
chalk.red(
|
|
28
|
+
`❌ Error: No skill.yaml, agent.yaml, or SKILL.md found at ${sourcePath}`,
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
console.log(
|
|
32
|
+
chalk.gray(
|
|
33
|
+
" Please make sure you are in the correct directory or provide a path.",
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const name = path.basename(sourcePath);
|
|
40
|
+
const skillsPath = hub.config.getSkillsPath();
|
|
41
|
+
const targetPathInWorkspace = path.join(skillsPath, name);
|
|
42
|
+
|
|
43
|
+
// 2. Register via symlink
|
|
44
|
+
try {
|
|
45
|
+
process.stdout.write(chalk.white(` Registering ${chalk.bold(name)}... `));
|
|
46
|
+
|
|
47
|
+
fs.ensureDirSync(path.dirname(targetPathInWorkspace));
|
|
48
|
+
if (fs.existsSync(targetPathInWorkspace)) {
|
|
49
|
+
// Check if it's already a symlink
|
|
50
|
+
const stats = fs.lstatSync(targetPathInWorkspace);
|
|
51
|
+
if (stats.isSymbolicLink()) {
|
|
52
|
+
fs.removeSync(targetPathInWorkspace);
|
|
53
|
+
} else {
|
|
54
|
+
const { overwrite } = await require("inquirer").prompt([
|
|
55
|
+
{
|
|
56
|
+
type: "confirm",
|
|
57
|
+
name: "overwrite",
|
|
58
|
+
message: `\n Directory ${name} already exists in skills workspace. Overwrite with symlink?`,
|
|
59
|
+
default: false,
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
if (!overwrite) {
|
|
63
|
+
console.log(chalk.yellow("\n Aborted."));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
fs.removeSync(targetPathInWorkspace);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.ensureSymlinkSync(sourcePath, targetPathInWorkspace, "junction");
|
|
71
|
+
process.stdout.write(chalk.green("✓\n"));
|
|
72
|
+
|
|
73
|
+
// 3. Verification
|
|
74
|
+
console.log(chalk.gray(` Path: ${targetPathInWorkspace}`));
|
|
75
|
+
console.log(chalk.green("\n✅ Successfully registered to OpenClaw!"));
|
|
76
|
+
console.log(chalk.white("\nNext steps:"));
|
|
77
|
+
console.log(chalk.gray(' - Run "clawflow status" to see your skill'));
|
|
78
|
+
console.log(chalk.gray(' - Use "clawflow cron-add" to schedule it\n'));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(chalk.red("\n❌ Registration failed:"), err.message);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update command - Check for updates
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require("chalk");
|
|
6
|
+
const pkg = require("../../package.json");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
module.exports = async function updateCommand() {
|
|
10
|
+
console.log(chalk.cyan.bold("\n🔄 Checking for ClawFlow updates...\n"));
|
|
11
|
+
console.log(chalk.gray(` Current version: ${pkg.version}`));
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const latest = execSync(`npm show ${pkg.name} version`, { stdio: "pipe" })
|
|
15
|
+
.toString()
|
|
16
|
+
.trim();
|
|
17
|
+
|
|
18
|
+
if (latest === pkg.version) {
|
|
19
|
+
console.log(chalk.green("✨ You are already using the latest version!"));
|
|
20
|
+
} else {
|
|
21
|
+
console.log(
|
|
22
|
+
chalk.yellow(`🚀 A new version is available: ${chalk.bold(latest)}`),
|
|
23
|
+
);
|
|
24
|
+
console.log(chalk.white("\nTo update, run:"));
|
|
25
|
+
console.log(chalk.cyan(` npm install -g ${pkg.name}`));
|
|
26
|
+
}
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.log(
|
|
29
|
+
chalk.red(
|
|
30
|
+
"❌ Failed to check for updates. Please check your internet connection.",
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
console.log();
|
|
35
|
+
};
|
package/src/core/Installer.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Installer - Manage installation and uninstallation of packages
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const fs = require(
|
|
6
|
-
const path = require(
|
|
7
|
-
const chalk = require(
|
|
8
|
-
const ora = require(
|
|
9
|
-
const inquirer = require(
|
|
10
|
-
const OpenClawCLI = require(
|
|
5
|
+
const fs = require("fs-extra");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const chalk = require("chalk");
|
|
8
|
+
const ora = require("ora");
|
|
9
|
+
const inquirer = require("inquirer");
|
|
10
|
+
const OpenClawCLI = require("./OpenClawCLI");
|
|
11
11
|
|
|
12
12
|
class Installer {
|
|
13
13
|
constructor(configManager, registry) {
|
|
@@ -22,85 +22,124 @@ class Installer {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
|
|
25
|
+
* Install package
|
|
26
26
|
*/
|
|
27
27
|
async install(packageName, options = {}) {
|
|
28
|
-
const {
|
|
28
|
+
const {
|
|
29
|
+
global = false,
|
|
30
|
+
cron: withCron = true,
|
|
31
|
+
dryRun = false,
|
|
32
|
+
dev = false,
|
|
33
|
+
bundle = false,
|
|
34
|
+
} = options;
|
|
29
35
|
|
|
30
36
|
// Check whether the package exists (supports built-in and npm)
|
|
31
37
|
const pkg = await this.registry.getPackage(packageName);
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
|
|
39
|
+
// Create package object if it's a URL or local path but not in registry
|
|
40
|
+
let targetPkg = pkg;
|
|
41
|
+
if (
|
|
42
|
+
!targetPkg &&
|
|
43
|
+
(packageName.includes("://") ||
|
|
44
|
+
packageName.startsWith(".") ||
|
|
45
|
+
packageName.startsWith("/"))
|
|
46
|
+
) {
|
|
47
|
+
targetPkg = {
|
|
48
|
+
name: path.basename(packageName).replace(/\.git$/, ""),
|
|
49
|
+
version: "local",
|
|
50
|
+
skills: [
|
|
51
|
+
{
|
|
52
|
+
name: path.basename(packageName),
|
|
53
|
+
source: "git",
|
|
54
|
+
repository: packageName,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
config: {},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!targetPkg) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Package "${packageName}" not found in registry and is not a valid URL/Path`,
|
|
64
|
+
);
|
|
34
65
|
}
|
|
35
66
|
|
|
36
67
|
// ตรวจสอบว่าติดตั้งแล้วหรือยัง
|
|
37
68
|
const installed = this.configManager.getInstalledPackages();
|
|
38
|
-
if (installed[
|
|
39
|
-
console.log(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
69
|
+
if (installed[targetPkg.name] && !dev) {
|
|
70
|
+
console.log(
|
|
71
|
+
chalk.yellow(`⚠️ Package "${targetPkg.name}" is already installed`),
|
|
72
|
+
);
|
|
73
|
+
const { reinstall } = await inquirer.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: "confirm",
|
|
76
|
+
name: "reinstall",
|
|
77
|
+
message: "Do you want to reinstall?",
|
|
78
|
+
default: false,
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
46
81
|
if (!reinstall) {
|
|
47
|
-
return { success: false, reason:
|
|
82
|
+
return { success: false, reason: "already_installed" };
|
|
48
83
|
}
|
|
49
84
|
}
|
|
50
85
|
|
|
51
|
-
console.log(
|
|
86
|
+
console.log(
|
|
87
|
+
chalk.cyan(
|
|
88
|
+
`📦 Installing ${chalk.bold(targetPkg.name)}${dev ? chalk.yellow(" [DEV MODE]") : ""}...\n`,
|
|
89
|
+
),
|
|
90
|
+
);
|
|
52
91
|
|
|
53
92
|
// Dry run - แสดงเฉพาะข้อมูล
|
|
54
93
|
if (dryRun) {
|
|
55
|
-
this.showDryRunInfo(
|
|
94
|
+
this.showDryRunInfo(targetPkg, withCron);
|
|
56
95
|
return { success: true, dryRun: true };
|
|
57
96
|
}
|
|
58
97
|
|
|
59
|
-
const spinner = ora(
|
|
98
|
+
const spinner = ora("Processing...").start();
|
|
60
99
|
|
|
61
100
|
try {
|
|
62
101
|
// 1. ติดตั้ง skills
|
|
63
|
-
spinner.text =
|
|
64
|
-
await this.installSkills(
|
|
102
|
+
spinner.text = "Installing skills...";
|
|
103
|
+
await this.installSkills(targetPkg.skills, { global, dev, bundle });
|
|
65
104
|
|
|
66
105
|
// 2. ตั้งค่า config
|
|
67
|
-
spinner.text =
|
|
68
|
-
await this.setupConfig(
|
|
106
|
+
spinner.text = "Setting up configuration...";
|
|
107
|
+
await this.setupConfig(targetPkg.name, targetPkg.config);
|
|
69
108
|
|
|
70
109
|
// 3. ตั้ง cronjobs (ถ้าเปิดใช้งาน)
|
|
71
110
|
let crons = [];
|
|
72
|
-
if (withCron &&
|
|
73
|
-
spinner.text =
|
|
74
|
-
crons = await this.setupCrons(
|
|
111
|
+
if (withCron && targetPkg.crons && targetPkg.crons.length > 0) {
|
|
112
|
+
spinner.text = "Setting up cronjobs...";
|
|
113
|
+
crons = await this.setupCrons(targetPkg.crons);
|
|
75
114
|
}
|
|
76
115
|
|
|
77
116
|
// 4. บันทึกว่าติดตั้งแล้ว
|
|
78
|
-
spinner.text =
|
|
79
|
-
this.configManager.addInstalledPackage(
|
|
80
|
-
version:
|
|
81
|
-
skills:
|
|
82
|
-
crons: crons.map(c => c.id),
|
|
83
|
-
configPath: this.getPackageConfigPath(
|
|
117
|
+
spinner.text = "Saving state...";
|
|
118
|
+
this.configManager.addInstalledPackage(targetPkg.name, {
|
|
119
|
+
version: targetPkg.version,
|
|
120
|
+
skills: targetPkg.skills.map((s) => s.name),
|
|
121
|
+
crons: crons.map((c) => c.id),
|
|
122
|
+
configPath: this.getPackageConfigPath(targetPkg.name),
|
|
123
|
+
dev: dev,
|
|
84
124
|
});
|
|
85
125
|
|
|
86
|
-
spinner.succeed(chalk.green(`Installed ${
|
|
126
|
+
spinner.succeed(chalk.green(`Installed ${targetPkg.name} successfully!`));
|
|
87
127
|
|
|
88
128
|
// แสดงข้อมูลสรุป
|
|
89
|
-
this.showInstallSummary(
|
|
129
|
+
this.showInstallSummary(targetPkg, crons);
|
|
90
130
|
|
|
91
131
|
// แสดง post-install message
|
|
92
|
-
if (
|
|
93
|
-
console.log(chalk.yellow(
|
|
94
|
-
console.log(chalk.gray(` ${
|
|
132
|
+
if (targetPkg.postInstall) {
|
|
133
|
+
console.log(chalk.yellow("\n📋 Next steps:"));
|
|
134
|
+
console.log(chalk.gray(` ${targetPkg.postInstall}`));
|
|
95
135
|
}
|
|
96
136
|
|
|
97
137
|
return {
|
|
98
138
|
success: true,
|
|
99
|
-
package:
|
|
100
|
-
skills:
|
|
139
|
+
package: targetPkg.name,
|
|
140
|
+
skills: targetPkg.skills.map((s) => s.name),
|
|
101
141
|
crons: crons,
|
|
102
142
|
};
|
|
103
|
-
|
|
104
143
|
} catch (error) {
|
|
105
144
|
spinner.fail(chalk.red(`Installation failed: ${error.message}`));
|
|
106
145
|
throw error;
|
|
@@ -108,62 +147,123 @@ class Installer {
|
|
|
108
147
|
}
|
|
109
148
|
|
|
110
149
|
/**
|
|
111
|
-
|
|
150
|
+
* Install skills
|
|
112
151
|
*/
|
|
113
|
-
async installSkills(skills,
|
|
152
|
+
async installSkills(skills, options = {}) {
|
|
153
|
+
const { global = false, dev = false, bundle = false } = options;
|
|
114
154
|
for (const skill of skills) {
|
|
115
|
-
console.log(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
155
|
+
console.log(
|
|
156
|
+
chalk.gray(
|
|
157
|
+
` → Installing skill: ${skill.name}${dev ? " (link)" : ""}`,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
await this.installSkillToOpenClaw(skill, { global, dev, bundle });
|
|
120
162
|
}
|
|
121
163
|
}
|
|
122
164
|
|
|
123
165
|
/**
|
|
124
166
|
* ติดตั้ง skill ไปยัง OpenClaw
|
|
125
167
|
*/
|
|
126
|
-
async installSkillToOpenClaw(skill,
|
|
127
|
-
|
|
168
|
+
async installSkillToOpenClaw(skill, options = {}) {
|
|
169
|
+
const { _global = false, dev = false, bundle = false } = options;
|
|
170
|
+
|
|
128
171
|
const skillsPath = this.configManager.getSkillsPath();
|
|
129
172
|
|
|
130
|
-
|
|
131
|
-
|
|
173
|
+
// Dev mode - Symlink
|
|
174
|
+
if (
|
|
175
|
+
dev &&
|
|
176
|
+
(skill.repository?.startsWith(".") ||
|
|
177
|
+
skill.repository?.startsWith("/") ||
|
|
178
|
+
fs.existsSync(skill.name))
|
|
179
|
+
) {
|
|
180
|
+
const sourcePath = path.resolve(
|
|
181
|
+
process.cwd(),
|
|
182
|
+
skill.repository || skill.name,
|
|
183
|
+
);
|
|
184
|
+
const targetPath = path.join(skillsPath, path.basename(sourcePath));
|
|
132
185
|
|
|
133
|
-
|
|
134
|
-
|
|
186
|
+
console.log(chalk.gray(` ↳ Symlinking: ${sourcePath} → ${targetPath}`));
|
|
187
|
+
fs.ensureDirSync(path.dirname(targetPath));
|
|
188
|
+
if (fs.existsSync(targetPath)) fs.removeSync(targetPath);
|
|
189
|
+
fs.ensureSymlinkSync(sourcePath, targetPath, "junction");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
135
192
|
|
|
193
|
+
try {
|
|
194
|
+
await this.openclawCLI.installSkill(
|
|
195
|
+
skill.name,
|
|
196
|
+
skill.version,
|
|
197
|
+
skillsPath,
|
|
198
|
+
);
|
|
199
|
+
await this.openclawCLI.verifySkill(skill.name).catch(() => null);
|
|
136
200
|
} catch (error) {
|
|
137
201
|
try {
|
|
138
|
-
const cloned = await this.openclawCLI.installSkillFromGit(
|
|
139
|
-
|
|
202
|
+
const cloned = await this.openclawCLI.installSkillFromGit(
|
|
203
|
+
skill,
|
|
204
|
+
skillsPath,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Bundle detection
|
|
208
|
+
if (bundle) {
|
|
209
|
+
await this.detectAndInstallSubSkills(cloned.path, skillsPath);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(
|
|
213
|
+
chalk.yellow(` ↳ git clone succeeded: ${cloned.repository}`),
|
|
214
|
+
);
|
|
140
215
|
return;
|
|
141
216
|
} catch (gitError) {
|
|
142
217
|
throw new Error(
|
|
143
218
|
`Failed to install skill "${skill.name}" from both clawhub and git clone\n` +
|
|
144
|
-
|
|
145
|
-
|
|
219
|
+
`clawhub: ${error.message}\n` +
|
|
220
|
+
`git: ${gitError.message}`,
|
|
146
221
|
);
|
|
147
222
|
}
|
|
148
223
|
}
|
|
149
224
|
}
|
|
150
225
|
|
|
151
226
|
/**
|
|
152
|
-
|
|
227
|
+
* ตรวจสอบและลง Sub-skills (Bundle mode)
|
|
228
|
+
*/
|
|
229
|
+
async detectAndInstallSubSkills(mainSkillPath, targetSkillsPath) {
|
|
230
|
+
const items = fs.readdirSync(mainSkillPath);
|
|
231
|
+
for (const item of items) {
|
|
232
|
+
const subPath = path.join(mainSkillPath, item);
|
|
233
|
+
if (fs.statSync(subPath).isDirectory()) {
|
|
234
|
+
const isSkill =
|
|
235
|
+
fs.existsSync(path.join(subPath, "skill.yaml")) ||
|
|
236
|
+
fs.existsSync(path.join(subPath, "SKILL.md"));
|
|
237
|
+
|
|
238
|
+
if (isSkill) {
|
|
239
|
+
const targetPath = path.join(targetSkillsPath, item);
|
|
240
|
+
if (!fs.existsSync(targetPath)) {
|
|
241
|
+
console.log(
|
|
242
|
+
chalk.gray(` ↳ Bundle detected: linking sub-skill ${item}`),
|
|
243
|
+
);
|
|
244
|
+
fs.ensureSymlinkSync(subPath, targetPath, "junction");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Setup config for package
|
|
153
253
|
*/
|
|
154
254
|
async setupConfig(packageName, configSchema) {
|
|
155
255
|
if (!configSchema) return;
|
|
156
256
|
|
|
157
257
|
const configPath = this.getPackageConfigPath(packageName);
|
|
158
|
-
const existingConfig = fs.existsSync(configPath)
|
|
159
|
-
? fs.readJsonSync(configPath)
|
|
258
|
+
const existingConfig = fs.existsSync(configPath)
|
|
259
|
+
? fs.readJsonSync(configPath)
|
|
160
260
|
: {};
|
|
161
261
|
|
|
162
262
|
const newConfig = {};
|
|
163
263
|
|
|
164
264
|
for (const [skillName, schema] of Object.entries(configSchema)) {
|
|
165
265
|
newConfig[skillName] = {};
|
|
166
|
-
|
|
266
|
+
|
|
167
267
|
for (const [key, value] of Object.entries(schema)) {
|
|
168
268
|
if (value.env) {
|
|
169
269
|
// Pull value from environment variable
|
|
@@ -172,23 +272,29 @@ class Installer {
|
|
|
172
272
|
newConfig[skillName][key] = envValue;
|
|
173
273
|
} else if (value.required && !existingConfig[skillName]?.[key]) {
|
|
174
274
|
// If not in env and required
|
|
175
|
-
const answer = await inquirer.prompt([
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
275
|
+
const answer = await inquirer.prompt([
|
|
276
|
+
{
|
|
277
|
+
type: "input",
|
|
278
|
+
name: key,
|
|
279
|
+
message: `Please enter ${key} for ${skillName} (or set ${value.env}):`,
|
|
280
|
+
validate: (input) =>
|
|
281
|
+
input.length > 0 || "This field is required",
|
|
282
|
+
},
|
|
283
|
+
]);
|
|
181
284
|
newConfig[skillName][key] = answer[key];
|
|
182
285
|
}
|
|
183
286
|
} else if (value.default !== undefined) {
|
|
184
|
-
newConfig[skillName][key] =
|
|
287
|
+
newConfig[skillName][key] =
|
|
288
|
+
existingConfig[skillName]?.[key] ?? value.default;
|
|
185
289
|
} else if (value.required && !existingConfig[skillName]?.[key]) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
290
|
+
const answer = await inquirer.prompt([
|
|
291
|
+
{
|
|
292
|
+
type: "input",
|
|
293
|
+
name: key,
|
|
189
294
|
message: `Please enter ${key} for ${skillName}:`,
|
|
190
|
-
validate: (input) => input.length > 0 ||
|
|
191
|
-
|
|
295
|
+
validate: (input) => input.length > 0 || "This field is required",
|
|
296
|
+
},
|
|
297
|
+
]);
|
|
192
298
|
newConfig[skillName][key] = answer[key];
|
|
193
299
|
}
|
|
194
300
|
}
|
|
@@ -204,13 +310,13 @@ class Installer {
|
|
|
204
310
|
*/
|
|
205
311
|
async setupCrons(cronsConfig) {
|
|
206
312
|
const crons = [];
|
|
207
|
-
|
|
313
|
+
|
|
208
314
|
for (const cronConfig of cronsConfig) {
|
|
209
315
|
const cronInfo = await this.cronManager.add(
|
|
210
316
|
cronConfig.skill,
|
|
211
317
|
cronConfig.schedule,
|
|
212
318
|
cronConfig.params,
|
|
213
|
-
cronConfig.description
|
|
319
|
+
cronConfig.description,
|
|
214
320
|
);
|
|
215
321
|
crons.push(cronInfo);
|
|
216
322
|
}
|
|
@@ -226,20 +332,24 @@ class Installer {
|
|
|
226
332
|
|
|
227
333
|
const installed = this.configManager.getInstalledPackages();
|
|
228
334
|
if (!installed[packageName]) {
|
|
229
|
-
console.log(
|
|
230
|
-
|
|
335
|
+
console.log(
|
|
336
|
+
chalk.yellow(`⚠️ Package "${packageName}" ไม่ได้ถูกติดตั้ง`),
|
|
337
|
+
);
|
|
338
|
+
return { success: false, reason: "not_installed" };
|
|
231
339
|
}
|
|
232
340
|
|
|
233
|
-
console.log(
|
|
341
|
+
console.log(
|
|
342
|
+
chalk.cyan(`🗑️ กำลังถอนการติดตั้ง ${chalk.bold(packageName)}...`),
|
|
343
|
+
);
|
|
234
344
|
|
|
235
|
-
const spinner = ora(
|
|
345
|
+
const spinner = ora("กำลังดำเนินการ...").start();
|
|
236
346
|
|
|
237
347
|
try {
|
|
238
348
|
const pkgInfo = installed[packageName];
|
|
239
349
|
|
|
240
350
|
// 1. ลบ cronjobs
|
|
241
351
|
if (pkgInfo.crons && pkgInfo.crons.length > 0) {
|
|
242
|
-
spinner.text =
|
|
352
|
+
spinner.text = "กำลังลบ cronjobs...";
|
|
243
353
|
for (const cronId of pkgInfo.crons) {
|
|
244
354
|
await this.cronManager.remove(cronId);
|
|
245
355
|
}
|
|
@@ -247,7 +357,7 @@ class Installer {
|
|
|
247
357
|
|
|
248
358
|
// 2. ลบ skills
|
|
249
359
|
if (pkgInfo.skills && pkgInfo.skills.length > 0) {
|
|
250
|
-
spinner.text =
|
|
360
|
+
spinner.text = "กำลังลบ skills...";
|
|
251
361
|
for (const skillName of pkgInfo.skills) {
|
|
252
362
|
await this.removeSkillFromOpenClaw(skillName);
|
|
253
363
|
}
|
|
@@ -255,7 +365,7 @@ class Installer {
|
|
|
255
365
|
|
|
256
366
|
// 3. ลบ config (ถ้าไม่ได้ระบุให้เก็บไว้)
|
|
257
367
|
if (!keepConfig) {
|
|
258
|
-
spinner.text =
|
|
368
|
+
spinner.text = "กำลังลบ config...";
|
|
259
369
|
const configPath = this.getPackageConfigPath(packageName);
|
|
260
370
|
if (fs.existsSync(configPath)) {
|
|
261
371
|
fs.removeSync(configPath);
|
|
@@ -265,10 +375,11 @@ class Installer {
|
|
|
265
375
|
// 4. ลบจาก installed list
|
|
266
376
|
this.configManager.removeInstalledPackage(packageName);
|
|
267
377
|
|
|
268
|
-
spinner.succeed(
|
|
378
|
+
spinner.succeed(
|
|
379
|
+
chalk.green(`ถอนการติดตั้ง ${packageName} เสร็จสมบูรณ์!`),
|
|
380
|
+
);
|
|
269
381
|
|
|
270
382
|
return { success: true, package: packageName };
|
|
271
|
-
|
|
272
383
|
} catch (error) {
|
|
273
384
|
spinner.fail(chalk.red(`ถอนการติดตั้งไม่สำเร็จ: ${error.message}`));
|
|
274
385
|
throw error;
|
|
@@ -289,63 +400,69 @@ class Installer {
|
|
|
289
400
|
* แสดงข้อมูล dry run
|
|
290
401
|
*/
|
|
291
402
|
showDryRunInfo(pkg, withCron) {
|
|
292
|
-
console.log(chalk.cyan(
|
|
293
|
-
|
|
294
|
-
console.log(chalk.white(
|
|
295
|
-
console.log(chalk.white(
|
|
296
|
-
console.log(chalk.white(
|
|
297
|
-
|
|
298
|
-
console.log(chalk.yellow(
|
|
299
|
-
pkg.skills.forEach(skill => {
|
|
403
|
+
console.log(chalk.cyan("\n📋 ข้อมูลการติดตั้ง (Dry Run):\n"));
|
|
404
|
+
|
|
405
|
+
console.log(chalk.white("Package:"), chalk.bold(pkg.name));
|
|
406
|
+
console.log(chalk.white("Version:"), pkg.version);
|
|
407
|
+
console.log(chalk.white("Description:"), pkg.description);
|
|
408
|
+
|
|
409
|
+
console.log(chalk.yellow("\n📦 Skills ที่จะติดตั้ง:"));
|
|
410
|
+
pkg.skills.forEach((skill) => {
|
|
300
411
|
console.log(` • ${skill.name}@${skill.version}`);
|
|
301
412
|
});
|
|
302
413
|
|
|
303
414
|
if (withCron && pkg.crons && pkg.crons.length > 0) {
|
|
304
|
-
console.log(chalk.yellow(
|
|
305
|
-
pkg.crons.forEach(cron => {
|
|
415
|
+
console.log(chalk.yellow("\n⏰ Cronjobs ที่จะตั้ง:"));
|
|
416
|
+
pkg.crons.forEach((cron) => {
|
|
306
417
|
console.log(` • ${cron.skill}`);
|
|
307
418
|
console.log(` Schedule: ${cron.schedule}`);
|
|
308
419
|
console.log(` Description: ${cron.description}`);
|
|
309
420
|
});
|
|
310
421
|
}
|
|
311
422
|
|
|
312
|
-
console.log(chalk.yellow(
|
|
423
|
+
console.log(chalk.yellow("\n⚙️ Config ที่จะตั้ง:"));
|
|
313
424
|
if (pkg.config) {
|
|
314
425
|
console.log(JSON.stringify(pkg.config, null, 2));
|
|
315
426
|
} else {
|
|
316
|
-
console.log(
|
|
427
|
+
console.log(" (ไม่มี config พิเศษ)");
|
|
317
428
|
}
|
|
318
429
|
|
|
319
|
-
console.log(chalk.gray(
|
|
430
|
+
console.log(chalk.gray("\n(ไม่ได้ทำการติดตั้งจริง - dry run mode)"));
|
|
320
431
|
}
|
|
321
432
|
|
|
322
433
|
/**
|
|
323
434
|
* แสดงสรุปการติดตั้ง
|
|
324
435
|
*/
|
|
325
436
|
showInstallSummary(pkg, crons) {
|
|
326
|
-
console.log(chalk.green(
|
|
327
|
-
|
|
328
|
-
console.log(chalk.white(
|
|
329
|
-
console.log(chalk.white(
|
|
330
|
-
pkg.skills.forEach(skill => {
|
|
437
|
+
console.log(chalk.green("\n✅ สรุปการติดตั้ง:\n"));
|
|
438
|
+
|
|
439
|
+
console.log(chalk.white("📦 Package:"), pkg.name);
|
|
440
|
+
console.log(chalk.white("🛠️ Skills ที่ติดตั้ง:"));
|
|
441
|
+
pkg.skills.forEach((skill) => {
|
|
331
442
|
console.log(` ✓ ${skill.name}`);
|
|
332
443
|
});
|
|
333
444
|
|
|
334
445
|
if (crons.length > 0) {
|
|
335
|
-
console.log(chalk.white(
|
|
336
|
-
crons.forEach(cron => {
|
|
446
|
+
console.log(chalk.white("\n⏰ Cronjobs ที่ตั้ง:"));
|
|
447
|
+
crons.forEach((cron) => {
|
|
337
448
|
console.log(` ✓ ${cron.skill} (${cron.schedule})`);
|
|
338
449
|
});
|
|
339
450
|
}
|
|
340
451
|
|
|
341
|
-
console.log(
|
|
452
|
+
console.log(
|
|
453
|
+
chalk.white("\n📁 Config path:"),
|
|
454
|
+
this.getPackageConfigPath(pkg.name),
|
|
455
|
+
);
|
|
342
456
|
}
|
|
343
457
|
|
|
344
458
|
/**
|
|
345
459
|
* ดึง path ของ config สำหรับ package
|
|
346
460
|
*/
|
|
347
461
|
getPackageConfigPath(packageName) {
|
|
348
|
-
return path.join(
|
|
462
|
+
return path.join(
|
|
463
|
+
this.configManager.getSkillsPath(),
|
|
464
|
+
`${packageName}.config.json`,
|
|
465
|
+
);
|
|
349
466
|
}
|
|
350
467
|
}
|
|
351
468
|
|