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.
@@ -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
+ };
@@ -2,12 +2,12 @@
2
2
  * Installer - Manage installation and uninstallation of packages
3
3
  */
4
4
 
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');
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
- * Install package
25
+ * Install package
26
26
  */
27
27
  async install(packageName, options = {}) {
28
- const { global = false, cron: withCron = true, dryRun = false } = options;
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
- if (!pkg) {
33
- throw new Error(`Package "${packageName}" not found in registry (check package name or internet connection)`);
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[packageName]) {
39
- console.log(chalk.yellow(`⚠️ Package "${packageName}" is already installed`));
40
- const { reinstall } = await inquirer.prompt([{
41
- type: 'confirm',
42
- name: 'reinstall',
43
- message: 'Do you want to reinstall?',
44
- default: false,
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: 'already_installed' };
82
+ return { success: false, reason: "already_installed" };
48
83
  }
49
84
  }
50
85
 
51
- console.log(chalk.cyan(`📦 Installing ${chalk.bold(packageName)}...\n`));
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(pkg, withCron);
94
+ this.showDryRunInfo(targetPkg, withCron);
56
95
  return { success: true, dryRun: true };
57
96
  }
58
97
 
59
- const spinner = ora('Processing...').start();
98
+ const spinner = ora("Processing...").start();
60
99
 
61
100
  try {
62
101
  // 1. ติดตั้ง skills
63
- spinner.text = 'Installing skills...';
64
- await this.installSkills(pkg.skills, global);
102
+ spinner.text = "Installing skills...";
103
+ await this.installSkills(targetPkg.skills, { global, dev, bundle });
65
104
 
66
105
  // 2. ตั้งค่า config
67
- spinner.text = 'Setting up configuration...';
68
- await this.setupConfig(packageName, pkg.config);
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 && pkg.crons && pkg.crons.length > 0) {
73
- spinner.text = 'Setting up cronjobs...';
74
- crons = await this.setupCrons(pkg.crons);
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 = 'Saving state...';
79
- this.configManager.addInstalledPackage(packageName, {
80
- version: pkg.version,
81
- skills: pkg.skills.map(s => s.name),
82
- crons: crons.map(c => c.id),
83
- configPath: this.getPackageConfigPath(packageName),
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 ${packageName} successfully!`));
126
+ spinner.succeed(chalk.green(`Installed ${targetPkg.name} successfully!`));
87
127
 
88
128
  // แสดงข้อมูลสรุป
89
- this.showInstallSummary(pkg, crons);
129
+ this.showInstallSummary(targetPkg, crons);
90
130
 
91
131
  // แสดง post-install message
92
- if (pkg.postInstall) {
93
- console.log(chalk.yellow('\n📋 Next steps:'));
94
- console.log(chalk.gray(` ${pkg.postInstall}`));
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: packageName,
100
- skills: pkg.skills.map(s => s.name),
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
- * Install skills
150
+ * Install skills
112
151
  */
113
- async installSkills(skills, global = false) {
152
+ async installSkills(skills, options = {}) {
153
+ const { global = false, dev = false, bundle = false } = options;
114
154
  for (const skill of skills) {
115
- console.log(chalk.gray(` → Installing skill: ${skill.name}@${skill.version}`));
116
-
117
- // Install skill via OpenClaw
118
- // In production this would call OpenClaw API or CLI
119
- await this.installSkillToOpenClaw(skill, global);
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, global = false) {
127
- void global;
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
- try {
131
- await this.openclawCLI.installSkill(skill.name, skill.version, skillsPath);
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
- // ตรวจสอบหลังติดตั้ง (best effort)
134
- await this.openclawCLI.verifySkill(skill.name).catch(() => null);
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(skill, skillsPath);
139
- console.log(chalk.yellow(` ↳ fallback git clone succeeded: ${cloned.repository}`));
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
- `clawhub: ${error.message}\n` +
145
- `git: ${gitError.message}`,
219
+ `clawhub: ${error.message}\n` +
220
+ `git: ${gitError.message}`,
146
221
  );
147
222
  }
148
223
  }
149
224
  }
150
225
 
151
226
  /**
152
- * Setup config for package
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
- type: 'input',
177
- name: key,
178
- message: `Please enter ${key} for ${skillName} (or set ${value.env}):`,
179
- validate: (input) => input.length > 0 || 'This field is required',
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] = existingConfig[skillName]?.[key] ?? value.default;
287
+ newConfig[skillName][key] =
288
+ existingConfig[skillName]?.[key] ?? value.default;
185
289
  } else if (value.required && !existingConfig[skillName]?.[key]) {
186
- const answer = await inquirer.prompt([{
187
- type: 'input',
188
- name: key,
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 || 'This field is required',
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(chalk.yellow(`⚠️ Package "${packageName}" ไม่ได้ถูกติดตั้ง`));
230
- return { success: false, reason: 'not_installed' };
335
+ console.log(
336
+ chalk.yellow(`⚠️ Package "${packageName}" ไม่ได้ถูกติดตั้ง`),
337
+ );
338
+ return { success: false, reason: "not_installed" };
231
339
  }
232
340
 
233
- console.log(chalk.cyan(`🗑️ กำลังถอนการติดตั้ง ${chalk.bold(packageName)}...`));
341
+ console.log(
342
+ chalk.cyan(`🗑️ กำลังถอนการติดตั้ง ${chalk.bold(packageName)}...`),
343
+ );
234
344
 
235
- const spinner = ora('กำลังดำเนินการ...').start();
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 = 'กำลังลบ cronjobs...';
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 = 'กำลังลบ skills...';
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 = 'กำลังลบ config...';
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(chalk.green(`ถอนการติดตั้ง ${packageName} เสร็จสมบูรณ์!`));
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('\n📋 ข้อมูลการติดตั้ง (Dry Run):\n'));
293
-
294
- console.log(chalk.white('Package:'), chalk.bold(pkg.name));
295
- console.log(chalk.white('Version:'), pkg.version);
296
- console.log(chalk.white('Description:'), pkg.description);
297
-
298
- console.log(chalk.yellow('\n📦 Skills ที่จะติดตั้ง:'));
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('\n⏰ Cronjobs ที่จะตั้ง:'));
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('\n⚙️ Config ที่จะตั้ง:'));
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(' (ไม่มี config พิเศษ)');
427
+ console.log(" (ไม่มี config พิเศษ)");
317
428
  }
318
429
 
319
- console.log(chalk.gray('\n(ไม่ได้ทำการติดตั้งจริง - dry run mode)'));
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('\n✅ สรุปการติดตั้ง:\n'));
327
-
328
- console.log(chalk.white('📦 Package:'), pkg.name);
329
- console.log(chalk.white('🛠️ Skills ที่ติดตั้ง:'));
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('\n⏰ Cronjobs ที่ตั้ง:'));
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(chalk.white('\n📁 Config path:'), this.getPackageConfigPath(pkg.name));
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(this.configManager.getSkillsPath(), `${packageName}.config.json`);
462
+ return path.join(
463
+ this.configManager.getSkillsPath(),
464
+ `${packageName}.config.json`,
465
+ );
349
466
  }
350
467
  }
351
468