clawflowbang 1.0.0 → 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.
@@ -0,0 +1,332 @@
1
+ /**
2
+ * ClawFlow Terminal UI
3
+ * Retro + Modern terminal styling
4
+ *
5
+ * Design: Retro Terminal + Modern Polish
6
+ * - Box-drawing characters (retro)
7
+ * - Neon colors (modern)
8
+ * - Gradient effects (modern)
9
+ * - ASCII art headers (retro)
10
+ * - Glow effects (modern)
11
+ */
12
+
13
+ const chalk = require('chalk');
14
+ const gradientString = require('gradient-string');
15
+ const gradient = gradientString.default || gradientString;
16
+ const boxen = require('boxen');
17
+
18
+ // Color palette - Neon Retro with Orange & Lobster theme
19
+ const colors = {
20
+ primary: '#ff9500', // Orange
21
+ secondary: '#ff6b35', // Deep orange
22
+ accent: '#ff9f1c', // Bright orange
23
+ warning: '#ffd93d', // Yellow
24
+ error: '#ff6b6b', // Red
25
+ success: '#00ff9f', // Neon green
26
+ info: '#6c5ce7', // Purple
27
+
28
+ // Orange/Lobster themed colors
29
+ lobster: '#ff6b35', // Lobster orange
30
+ orange: '#ff9500', // Apple orange
31
+ sunset: '#ff7f50', // Coral
32
+ tangerine: '#ffb347', // Pastel orange
33
+ pumpkin: '#ff7518', // Pumpkin
34
+ coral: '#ff6b6b', // Coral red
35
+ };
36
+
37
+ // Box styles
38
+ const boxStyles = {
39
+ round: {
40
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
41
+ borderStyle: 'round',
42
+ borderColor: colors.primary,
43
+ },
44
+ bold: {
45
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
46
+ borderStyle: 'bold',
47
+ borderColor: colors.secondary,
48
+ },
49
+ double: {
50
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
51
+ borderStyle: 'double',
52
+ borderColor: colors.accent,
53
+ },
54
+ classic: {
55
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
56
+ borderStyle: 'classic',
57
+ borderColor: colors.info,
58
+ },
59
+ };
60
+
61
+ /* eslint-disable no-useless-escape */
62
+ // ASCII Art for logo - Cool Retro Style
63
+ const asciiLogo = `
64
+ ________ ___ ________ ___ __ ________ ___ ________ ___ __
65
+ |\ ____\|\ \ |\ __ \|\ \ |\ \|\ _____\\ \ |\ __ \|\ \ |\ \
66
+ \ \ \___|\ \ \ \ \ \|\ \ \ \ \ \ \ \ \__/\ \ \ \ \ \|\ \ \ \ \ \ \
67
+ \ \ \ \ \ \ \ \ __ \ \ \ __\ \ \ \ __\\ \ \ \ \ \\\ \ \ \ __\ \ \
68
+ \ \ \____\ \ \____\ \ \ \ \ \ \|\__\_\ \ \ \_| \ \ \____\ \ \\\ \ \ \|\__\_\ \
69
+ \ \_______\ \_______\ \__\ \__\ \____________\ \__\ \ \_______\ \_______\ \____________\
70
+ \|_______|\|_______|\|__|\|__|\|____________|\|__| \|_______|\|_______|\|____________|
71
+
72
+
73
+
74
+ ClawFlow - Skill + Cron Installer for OpenClaw
75
+ `;
76
+ /* eslint-enable no-useless-escape */
77
+
78
+ // Mini logo for sub-headers
79
+ const miniLogo = `
80
+ ╭──────────────────────────────────────╮
81
+ │ 🦞 ClawFlow v{pkgVersion} │
82
+ │ ═══════════════════════════════════ │
83
+ │ Skill + Cron Installer for OpenClaw │
84
+ ╰──────────────────────────────────────╯
85
+ `;
86
+
87
+ /**
88
+ * Create gradient text (modern effect)
89
+ */
90
+ function gradientText(text, color1 = colors.primary, color2 = colors.secondary) {
91
+ return gradient(color1, color2)(text);
92
+ }
93
+
94
+ /**
95
+ * Create neon glow effect
96
+ */
97
+ function neonGlow(text, color = colors.primary) {
98
+ return chalk.hex(color).bold(text);
99
+ }
100
+
101
+ /**
102
+ * Create retro scanline effect
103
+ */
104
+ function scanline(text) {
105
+ return chalk.dim(text);
106
+ }
107
+
108
+ /**
109
+ * Create box with custom style
110
+ */
111
+ function createBox(content, style = 'round') {
112
+ const boxOptions = boxStyles[style] || boxStyles.round;
113
+ return boxen(content, boxOptions);
114
+ }
115
+
116
+ /**
117
+ * Create a retro-styled header
118
+ */
119
+ function createHeader(title, style = 'gradient') {
120
+ const line = '═'.repeat(50);
121
+
122
+ switch (style) {
123
+ case 'gradient':
124
+ return `\n${gradientText(line, colors.primary, colors.secondary)}
125
+ ${chalk.bold.cyan(' ' + title)}
126
+ ${gradientText(line, colors.primary, colors.secondary)}\n`;
127
+
128
+ case 'neon':
129
+ return `\n${neonGlow(line, colors.primary)}
130
+ ${neonGlow(' ' + title, colors.secondary)}
131
+ ${neonGlow(line, colors.primary)}\n`;
132
+
133
+ case 'retro':
134
+ return `\n${chalk.green(line)}
135
+ ${chalk.bold.green(' ' + title)}
136
+ ${chalk.green(line)}\n`;
137
+
138
+ default:
139
+ return `\n${line}\n ${title}\n${line}\n`;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Create a loading spinner with retro style
145
+ */
146
+ function createSpinner(message) {
147
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
148
+ let frame = 0;
149
+
150
+ return {
151
+ start: () => {
152
+ process.stdout.write(chalk.cyan(frames[0]) + ' ' + message);
153
+ const interval = setInterval(() => {
154
+ process.stdout.write('\r' + chalk.cyan(frames[frame % frames.length]) + ' ' + message);
155
+ frame++;
156
+ }, 100);
157
+ return {
158
+ stop: (success = true, finalMessage = '') => {
159
+ clearInterval(interval);
160
+ process.stdout.write('\r' + (success ? chalk.green('✓') : chalk.red('✗')) + ' ' + finalMessage + '\n');
161
+ }
162
+ };
163
+ }
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Create progress bar (retro style)
169
+ */
170
+ function createProgressBar(total, current, width = 30) {
171
+ const filled = Math.round((current / total) * width);
172
+ const empty = width - filled;
173
+
174
+ const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
175
+ const percentage = Math.round((current / total) * 100);
176
+
177
+ return `[${bar}] ${percentage}%`;
178
+ }
179
+
180
+ /**
181
+ * Print main banner
182
+ */
183
+ function printBanner(_pkgVersion = '1.0.0') {
184
+ const banner = gradientText(asciiLogo, colors.primary, colors.secondary);
185
+
186
+ console.log(chalk.bgBlack(banner));
187
+ console.log(gradientText('─'.repeat(60), colors.primary, colors.secondary));
188
+ console.log(' 🦞 ' + gradientText('Install skills and configure them for immediate use', colors.primary, colors.secondary));
189
+ console.log(gradientText('─'.repeat(60), colors.primary, colors.secondary));
190
+ console.log();
191
+ }
192
+
193
+ /**
194
+ * Print section header
195
+ */
196
+ function printSection(title) {
197
+ console.log();
198
+ console.log(neonGlow('┌' + '─'.repeat(58) + '┐', colors.secondary));
199
+ console.log(neonGlow('│ ', colors.secondary) + chalk.bold.cyan(title.padEnd(56)) + neonGlow(' │', colors.secondary));
200
+ console.log(neonGlow('└' + '─'.repeat(58) + '┘', colors.secondary));
201
+ }
202
+
203
+ /**
204
+ * Print success message
205
+ */
206
+ function printSuccess(message) {
207
+ console.log(chalk.green(' ✓ ') + message);
208
+ }
209
+
210
+ /**
211
+ * Print error message
212
+ */
213
+ function printError(message) {
214
+ console.log(chalk.red(' ✗ ') + message);
215
+ }
216
+
217
+ /**
218
+ * Print warning message
219
+ */
220
+ function printWarning(message) {
221
+ console.log(chalk.yellow(' ⚠ ') + message);
222
+ }
223
+
224
+ /**
225
+ * Print info message
226
+ */
227
+ function printInfo(message) {
228
+ console.log(chalk.cyan(' ℹ ') + message);
229
+ }
230
+
231
+ /**
232
+ * Print table row
233
+ */
234
+ function printTableRow(columns, widths) {
235
+ const row = columns.map((col, i) => {
236
+ return String(col).padEnd(widths[i]);
237
+ }).join(chalk.gray(' │ '));
238
+
239
+ console.log(chalk.gray('│ ') + row + chalk.gray(' │'));
240
+ }
241
+
242
+ /**
243
+ * Print table header separator
244
+ */
245
+ function printTableSeparator(widths) {
246
+ console.log(chalk.gray('├─' + widths.map(w => '─'.repeat(w)).join('─┼─') + '─┤'));
247
+ }
248
+
249
+ /**
250
+ * Print key-value pairs in a nice format
251
+ */
252
+ function printKeyValue(key, value, indent = 2) {
253
+ const spaces = ' '.repeat(indent);
254
+ console.log(`${spaces}${chalk.cyan('●')} ${chalk.gray(key + ':')} ${chalk.white(value)}`);
255
+ }
256
+
257
+ /**
258
+ * Print a list item with bullet
259
+ */
260
+ function printListItem(item, indent = 2) {
261
+ const spaces = ' '.repeat(indent);
262
+ console.log(`${spaces}${chalk.green('›')} ${item}`);
263
+ }
264
+
265
+ /**
266
+ * Print a list item with number
267
+ */
268
+ function printNumberedItem(num, item, indent = 2) {
269
+ const spaces = ' '.repeat(indent);
270
+ console.log(`${spaces}${chalk.cyan(num + '.')} ${item}`);
271
+ }
272
+
273
+ /**
274
+ * Print help command list
275
+ */
276
+ function printCommandHelp(commands) {
277
+ console.log();
278
+ commands.forEach(cmd => {
279
+ const alias = cmd.alias ? ` | ${chalk.yellow(cmd.alias)}` : '';
280
+ console.log(` ${chalk.green(cmd.name)}${alias} ${chalk.gray('- ' + cmd.description)}`);
281
+ });
282
+ console.log();
283
+ }
284
+
285
+ /**
286
+ * Print loading animation
287
+ */
288
+ function loading(text) {
289
+ const frames = ['◐', '◑', '◒', '◓'];
290
+ let i = 0;
291
+ const interval = setInterval(() => {
292
+ process.stdout.write(`\r${chalk.cyan(frames[i++])} ${text}`);
293
+ i = i % frames.length;
294
+ }, 100);
295
+
296
+ return () => clearInterval(interval);
297
+ }
298
+
299
+ /**
300
+ * Clear screen and print banner
301
+ */
302
+ function clearAndBanner(pkgVersion = '1.0.0') {
303
+ console.clear();
304
+ printBanner(pkgVersion);
305
+ }
306
+
307
+ module.exports = {
308
+ colors,
309
+ gradientText,
310
+ neonGlow,
311
+ scanline,
312
+ createBox,
313
+ createHeader,
314
+ createSpinner,
315
+ createProgressBar,
316
+ printBanner,
317
+ printSection,
318
+ printSuccess,
319
+ printError,
320
+ printWarning,
321
+ printInfo,
322
+ printTableRow,
323
+ printTableSeparator,
324
+ printKeyValue,
325
+ printListItem,
326
+ printNumberedItem,
327
+ printCommandHelp,
328
+ loading,
329
+ clearAndBanner,
330
+ asciiLogo,
331
+ miniLogo,
332
+ };
package/.eslintrc.json DELETED
@@ -1,38 +0,0 @@
1
- {
2
- "root": true,
3
- "env": {
4
- "node": true,
5
- "es2021": true
6
- },
7
- "extends": [
8
- "eslint:recommended"
9
- ],
10
- "parserOptions": {
11
- "ecmaVersion": 12
12
- },
13
- "ignorePatterns": [
14
- "node_modules/",
15
- "coverage/"
16
- ],
17
- "rules": {
18
- "no-console": "off",
19
- "no-unused-vars": [
20
- "warn",
21
- {
22
- "argsIgnorePattern": "^_",
23
- "varsIgnorePattern": "^_"
24
- }
25
- ]
26
- },
27
- "overrides": [
28
- {
29
- "files": [
30
- "**/*.test.js",
31
- "__tests__/**/*.js"
32
- ],
33
- "env": {
34
- "jest": true
35
- }
36
- }
37
- ]
38
- }
@@ -1,52 +0,0 @@
1
- const fs = require('fs-extra');
2
- const os = require('os');
3
- const path = require('path');
4
- const ConfigManager = require('../src/core/ConfigManager');
5
-
6
- function makeTempDir(prefix) {
7
- return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
8
- }
9
-
10
- describe('ConfigManager', () => {
11
- test('applies path and bin overrides into openclaw config', () => {
12
- const configDir = makeTempDir('cfh-config-');
13
- const skillsPath = path.join(configDir, 'custom-skills');
14
- const cronJobsFile = path.join(configDir, 'custom-cron', 'jobs.json');
15
-
16
- const manager = new ConfigManager(configDir, {
17
- skillsPath,
18
- cronJobsFile,
19
- openclawBin: '/usr/local/bin/openclaw',
20
- clawhubBin: '/usr/local/bin/clawhub',
21
- });
22
-
23
- const config = manager.getConfig();
24
- expect(config.openclaw.skillsPath).toBe(skillsPath);
25
- expect(config.openclaw.cronJobsFile).toBe(cronJobsFile);
26
- expect(config.openclaw.cliBin).toBe('/usr/local/bin/openclaw');
27
- expect(config.openclaw.clawhubBin).toBe('/usr/local/bin/clawhub');
28
- expect(manager.getCronJobsFilePath()).toBe(cronJobsFile);
29
- });
30
-
31
- test('addCron preserves provided id and updateCron mutates fields', () => {
32
- const configDir = makeTempDir('cfh-config-');
33
- const manager = new ConfigManager(configDir);
34
-
35
- manager.addCron({
36
- id: 'job-123',
37
- skill: 'crypto-price',
38
- schedule: '*/5 * * * *',
39
- });
40
-
41
- const updated = manager.updateCron('job-123', {
42
- schedule: '0 * * * *',
43
- description: 'hourly',
44
- });
45
-
46
- expect(updated).toBeTruthy();
47
- expect(updated.id).toBe('job-123');
48
- expect(updated.schedule).toBe('0 * * * *');
49
- expect(updated.description).toBe('hourly');
50
- expect(updated.updatedAt).toBeTruthy();
51
- });
52
- });
@@ -1,26 +0,0 @@
1
- const { normalizeCronExpression } = require('../src/core/CronFormat');
2
-
3
- describe('CronFormat.normalizeCronExpression', () => {
4
- test('accepts valid cron expressions as-is', () => {
5
- expect(normalizeCronExpression('*/5 * * * *')).toBe('*/5 * * * *');
6
- });
7
-
8
- test('normalizes preset aliases', () => {
9
- expect(normalizeCronExpression('@daily')).toBe('0 0 * * *');
10
- expect(normalizeCronExpression('@hourly')).toBe('0 * * * *');
11
- });
12
-
13
- test('normalizes shorthand durations', () => {
14
- expect(normalizeCronExpression('15m')).toBe('*/15 * * * *');
15
- expect(normalizeCronExpression('every 2h')).toBe('0 */2 * * *');
16
- expect(normalizeCronExpression('1d')).toBe('0 0 */1 * *');
17
- });
18
-
19
- test('throws on invalid cron expressions', () => {
20
- expect(() => normalizeCronExpression('abc def')).toThrow(/ไม่ถูกต้อง/);
21
- });
22
-
23
- test('throws on unsupported duration shorthands', () => {
24
- expect(() => normalizeCronExpression('70m')).toThrow(/ไม่รองรับ/);
25
- });
26
- });
@@ -1,65 +0,0 @@
1
- const fs = require('fs-extra');
2
- const os = require('os');
3
- const path = require('path');
4
- const ConfigManager = require('../src/core/ConfigManager');
5
- const CronManager = require('../src/core/CronManager');
6
-
7
- function makeTempDir(prefix) {
8
- return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
9
- }
10
-
11
- describe('CronManager local mode', () => {
12
- test('add/edit/list/remove lifecycle works with normalized schedules', async () => {
13
- const root = makeTempDir('cfh-cron-');
14
- const configPath = path.join(root, 'config');
15
- const cronJobsFile = path.join(root, 'cron', 'jobs.json');
16
-
17
- const configManager = new ConfigManager(configPath, {
18
- cronJobsFile,
19
- openclawBin: 'notfound-openclaw',
20
- });
21
- const cronManager = new CronManager(configManager);
22
-
23
- expect(cronManager.useOpenClawCron).toBe(false);
24
-
25
- const added = await cronManager.add('smoke-skill', 'every 15m', { foo: 'bar' }, 'demo');
26
- expect(added.id).toBeTruthy();
27
- expect(added.schedule).toBe('*/15 * * * *');
28
-
29
- const listed = cronManager.list();
30
- expect(listed).toHaveLength(1);
31
- expect(listed[0].schedule).toBe('*/15 * * * *');
32
-
33
- const edited = await cronManager.edit(added.id, {
34
- schedule: '@daily',
35
- description: 'daily run',
36
- params: { foo: 'baz' },
37
- });
38
- expect(edited.schedule).toBe('0 0 * * *');
39
- expect(edited.description).toBe('daily run');
40
- expect(edited.params).toEqual({ foo: 'baz' });
41
-
42
- const listedAfterEdit = cronManager.list();
43
- expect(listedAfterEdit).toHaveLength(1);
44
- expect(listedAfterEdit[0].schedule).toBe('0 0 * * *');
45
-
46
- await cronManager.remove(added.id);
47
- expect(cronManager.list()).toEqual([]);
48
-
49
- cronManager.stopAll();
50
- });
51
-
52
- test('rejects invalid schedules', async () => {
53
- const root = makeTempDir('cfh-cron-');
54
- const configPath = path.join(root, 'config');
55
- const cronJobsFile = path.join(root, 'cron', 'jobs.json');
56
-
57
- const configManager = new ConfigManager(configPath, {
58
- cronJobsFile,
59
- openclawBin: 'notfound-openclaw',
60
- });
61
- const cronManager = new CronManager(configManager);
62
-
63
- await expect(cronManager.add('smoke-skill', 'wrong schedule', {})).rejects.toThrow(/ไม่ถูกต้อง/);
64
- });
65
- });
@@ -1,51 +0,0 @@
1
- const OpenClawCLI = require('../src/core/OpenClawCLI');
2
-
3
- function createCli() {
4
- return new OpenClawCLI({
5
- getConfig() {
6
- return {
7
- openclaw: {
8
- cliBin: 'openclaw',
9
- clawhubBin: 'clawhub',
10
- },
11
- };
12
- },
13
- });
14
- }
15
-
16
- describe('OpenClawCLI git fallback helpers', () => {
17
- test('resolveGitRepository from explicit fields', () => {
18
- const cli = createCli();
19
- expect(
20
- cli.resolveGitRepository({
21
- name: 'crypto-price',
22
- repository: 'https://github.com/acme/crypto-price.git',
23
- }),
24
- ).toBe('https://github.com/acme/crypto-price.git');
25
- });
26
-
27
- test('resolveGitRepository from github source + owner/repo name', () => {
28
- const cli = createCli();
29
- expect(
30
- cli.resolveGitRepository({
31
- source: 'github',
32
- name: 'acme/my-skill',
33
- }),
34
- ).toBe('https://github.com/acme/my-skill.git');
35
- });
36
-
37
- test('resolveSkillDirName from url and scoped names', () => {
38
- const cli = createCli();
39
- expect(
40
- cli.resolveSkillDirName({
41
- name: 'https://github.com/acme/skill-x.git',
42
- }),
43
- ).toBe('skill-x');
44
-
45
- expect(
46
- cli.resolveSkillDirName({
47
- name: 'acme/skill-y',
48
- }),
49
- ).toBe('skill-y');
50
- });
51
- });