copilot-liku-cli 0.0.1
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/ARCHITECTURE.md +411 -0
- package/CONFIGURATION.md +302 -0
- package/CONTRIBUTING.md +225 -0
- package/ELECTRON_README.md +121 -0
- package/INSTALLATION.md +350 -0
- package/LICENSE.md +1 -0
- package/PROJECT_STATUS.md +229 -0
- package/QUICKSTART.md +255 -0
- package/README.md +167 -0
- package/TESTING.md +274 -0
- package/package.json +61 -0
- package/scripts/start.js +30 -0
- package/src/assets/tray-icon.png +0 -0
- package/src/cli/commands/agent.js +327 -0
- package/src/cli/commands/click.js +108 -0
- package/src/cli/commands/drag.js +85 -0
- package/src/cli/commands/find.js +109 -0
- package/src/cli/commands/keys.js +132 -0
- package/src/cli/commands/mouse.js +79 -0
- package/src/cli/commands/repl.js +290 -0
- package/src/cli/commands/screenshot.js +72 -0
- package/src/cli/commands/scroll.js +74 -0
- package/src/cli/commands/start.js +67 -0
- package/src/cli/commands/type.js +57 -0
- package/src/cli/commands/wait.js +84 -0
- package/src/cli/commands/window.js +104 -0
- package/src/cli/liku.js +249 -0
- package/src/cli/util/output.js +174 -0
- package/src/main/agents/base-agent.js +410 -0
- package/src/main/agents/builder.js +484 -0
- package/src/main/agents/index.js +62 -0
- package/src/main/agents/orchestrator.js +362 -0
- package/src/main/agents/researcher.js +511 -0
- package/src/main/agents/state-manager.js +344 -0
- package/src/main/agents/supervisor.js +365 -0
- package/src/main/agents/verifier.js +452 -0
- package/src/main/ai-service.js +1633 -0
- package/src/main/index.js +2208 -0
- package/src/main/inspect-service.js +467 -0
- package/src/main/system-automation.js +1186 -0
- package/src/main/ui-automation/config.js +76 -0
- package/src/main/ui-automation/core/helpers.js +41 -0
- package/src/main/ui-automation/core/index.js +15 -0
- package/src/main/ui-automation/core/powershell.js +82 -0
- package/src/main/ui-automation/elements/finder.js +274 -0
- package/src/main/ui-automation/elements/index.js +14 -0
- package/src/main/ui-automation/elements/wait.js +66 -0
- package/src/main/ui-automation/index.js +164 -0
- package/src/main/ui-automation/interactions/element-click.js +211 -0
- package/src/main/ui-automation/interactions/high-level.js +230 -0
- package/src/main/ui-automation/interactions/index.js +47 -0
- package/src/main/ui-automation/keyboard/index.js +15 -0
- package/src/main/ui-automation/keyboard/input.js +179 -0
- package/src/main/ui-automation/mouse/click.js +186 -0
- package/src/main/ui-automation/mouse/drag.js +88 -0
- package/src/main/ui-automation/mouse/index.js +30 -0
- package/src/main/ui-automation/mouse/movement.js +51 -0
- package/src/main/ui-automation/mouse/scroll.js +116 -0
- package/src/main/ui-automation/screenshot.js +183 -0
- package/src/main/ui-automation/window/index.js +23 -0
- package/src/main/ui-automation/window/manager.js +305 -0
- package/src/main/utils/time.js +62 -0
- package/src/main/visual-awareness.js +597 -0
- package/src/renderer/chat/chat.js +671 -0
- package/src/renderer/chat/index.html +725 -0
- package/src/renderer/chat/preload.js +112 -0
- package/src/renderer/overlay/index.html +648 -0
- package/src/renderer/overlay/overlay.js +782 -0
- package/src/renderer/overlay/preload.js +90 -0
- package/src/shared/grid-math.js +82 -0
- package/src/shared/inspect-types.js +230 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* window command - Window management
|
|
3
|
+
* @module cli/commands/window
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { success, error, info, table, dim, highlight } = require('../util/output');
|
|
8
|
+
|
|
9
|
+
const UI_MODULE = path.resolve(__dirname, '../../main/ui-automation');
|
|
10
|
+
let ui;
|
|
11
|
+
|
|
12
|
+
function loadUI() {
|
|
13
|
+
if (!ui) {
|
|
14
|
+
ui = require(UI_MODULE);
|
|
15
|
+
}
|
|
16
|
+
return ui;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run the window command
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* liku window # List all windows
|
|
24
|
+
* liku window "Visual Studio" # Focus window by title
|
|
25
|
+
* liku window --active # Show active window info
|
|
26
|
+
*/
|
|
27
|
+
async function run(args, options) {
|
|
28
|
+
loadUI();
|
|
29
|
+
|
|
30
|
+
// Show active window info
|
|
31
|
+
if (options.active) {
|
|
32
|
+
const win = await ui.getActiveWindow();
|
|
33
|
+
if (!win) {
|
|
34
|
+
error('Could not get active window');
|
|
35
|
+
return { success: false };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!options.quiet && !options.json) {
|
|
39
|
+
const bounds = win.bounds || { x: '?', y: '?', width: '?', height: '?' };
|
|
40
|
+
console.log(`
|
|
41
|
+
${highlight('Active Window:')}
|
|
42
|
+
Title: ${win.title || '(unknown)'}
|
|
43
|
+
Process: ${win.processName || '(unknown)'}
|
|
44
|
+
Class: ${win.className || '(unknown)'}
|
|
45
|
+
Handle: ${win.hwnd}
|
|
46
|
+
Position: ${bounds.x}, ${bounds.y}
|
|
47
|
+
Size: ${bounds.width} x ${bounds.height}
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
return { success: true, window: win };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Focus window by title
|
|
54
|
+
if (args.length > 0) {
|
|
55
|
+
const title = args.join(' ');
|
|
56
|
+
|
|
57
|
+
if (!options.quiet) {
|
|
58
|
+
info(`Focusing window: "${title}"`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = await ui.focusWindow({ title });
|
|
62
|
+
|
|
63
|
+
if (result.success) {
|
|
64
|
+
if (!options.quiet) {
|
|
65
|
+
success(`Focused: ${result.window?.title || title}`);
|
|
66
|
+
}
|
|
67
|
+
return { success: true, window: result.window };
|
|
68
|
+
} else {
|
|
69
|
+
error(`Window not found: "${title}"`);
|
|
70
|
+
return { success: false, error: 'Window not found' };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// List all windows
|
|
75
|
+
if (!options.quiet) {
|
|
76
|
+
info('Listing windows...');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const windows = await ui.findWindows({});
|
|
80
|
+
|
|
81
|
+
if (windows.length === 0) {
|
|
82
|
+
if (!options.quiet) {
|
|
83
|
+
info('No windows found');
|
|
84
|
+
}
|
|
85
|
+
return { success: true, windows: [], count: 0 };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!options.quiet && !options.json) {
|
|
89
|
+
console.log(`\n${highlight(`Found ${windows.length} windows:`)}\n`);
|
|
90
|
+
|
|
91
|
+
const rows = windows.map((w, i) => [
|
|
92
|
+
i + 1,
|
|
93
|
+
w.title?.substring(0, 50) || dim('(untitled)'),
|
|
94
|
+
w.processName || '-',
|
|
95
|
+
`${w.bounds.width}x${w.bounds.height}`,
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
table(rows, ['#', 'Title', 'Process', 'Size']);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { success: true, windows, count: windows.length };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { run };
|
package/src/cli/liku.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* liku - Copilot-Liku CLI
|
|
4
|
+
*
|
|
5
|
+
* A powerful command-line interface for UI automation and the Copilot-Liku agent.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* liku Start the Electron agent (visual mode)
|
|
9
|
+
* liku start Same as above
|
|
10
|
+
* liku click <text> Click element by text
|
|
11
|
+
* liku find <text> Find UI elements matching text
|
|
12
|
+
* liku type <text> Type text at cursor
|
|
13
|
+
* liku keys <combo> Send key combination (e.g., "ctrl+c")
|
|
14
|
+
* liku screenshot [path] Take screenshot
|
|
15
|
+
* liku window <title> Focus window by title
|
|
16
|
+
* liku mouse <x> <y> Move mouse to coordinates
|
|
17
|
+
* liku repl Interactive automation shell
|
|
18
|
+
* liku --help Show help
|
|
19
|
+
* liku --version Show version
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
|
|
25
|
+
// Resolve paths relative to CLI location
|
|
26
|
+
const CLI_DIR = __dirname;
|
|
27
|
+
const PROJECT_ROOT = path.resolve(CLI_DIR, '../..');
|
|
28
|
+
const COMMANDS_DIR = path.join(CLI_DIR, 'commands');
|
|
29
|
+
|
|
30
|
+
// Import output utilities
|
|
31
|
+
const { log, success, error, warn, info, dim, highlight } = require('./util/output');
|
|
32
|
+
|
|
33
|
+
// Package info
|
|
34
|
+
const pkg = require(path.join(PROJECT_ROOT, 'package.json'));
|
|
35
|
+
|
|
36
|
+
// Command registry
|
|
37
|
+
const COMMANDS = {
|
|
38
|
+
start: { desc: 'Start the Electron agent with overlay', file: 'start' },
|
|
39
|
+
click: { desc: 'Click element by text or coordinates', file: 'click', args: '<text|x,y>' },
|
|
40
|
+
find: { desc: 'Find UI elements matching criteria', file: 'find', args: '<text>' },
|
|
41
|
+
type: { desc: 'Type text at current cursor position', file: 'type', args: '<text>' },
|
|
42
|
+
keys: { desc: 'Send keyboard shortcut', file: 'keys', args: '<combo>' },
|
|
43
|
+
screenshot: { desc: 'Capture screenshot', file: 'screenshot', args: '[path]' },
|
|
44
|
+
window: { desc: 'Focus or list windows', file: 'window', args: '[title]' },
|
|
45
|
+
mouse: { desc: 'Move mouse to coordinates', file: 'mouse', args: '<x> <y>' },
|
|
46
|
+
drag: { desc: 'Drag from one point to another', file: 'drag', args: '<x1> <y1> <x2> <y2>' },
|
|
47
|
+
scroll: { desc: 'Scroll up or down', file: 'scroll', args: '<up|down> [amount]' },
|
|
48
|
+
wait: { desc: 'Wait for element to appear', file: 'wait', args: '<text> [timeout]' },
|
|
49
|
+
repl: { desc: 'Interactive automation shell', file: 'repl' },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Show help message
|
|
54
|
+
*/
|
|
55
|
+
function showHelp() {
|
|
56
|
+
console.log(`
|
|
57
|
+
${highlight('liku')} - Copilot-Liku CLI v${pkg.version}
|
|
58
|
+
${dim('A powerful command-line interface for UI automation')}
|
|
59
|
+
|
|
60
|
+
${highlight('USAGE:')}
|
|
61
|
+
liku [command] [options]
|
|
62
|
+
|
|
63
|
+
${highlight('COMMANDS:')}
|
|
64
|
+
`);
|
|
65
|
+
|
|
66
|
+
// Calculate padding for alignment
|
|
67
|
+
const maxLen = Math.max(...Object.keys(COMMANDS).map(k => k.length + (COMMANDS[k].args?.length || 0)));
|
|
68
|
+
|
|
69
|
+
for (const [name, cmd] of Object.entries(COMMANDS)) {
|
|
70
|
+
const cmdStr = cmd.args ? `${name} ${cmd.args}` : name;
|
|
71
|
+
const padding = ' '.repeat(maxLen - cmdStr.length + 4);
|
|
72
|
+
console.log(` ${highlight(cmdStr)}${padding}${dim(cmd.desc)}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`
|
|
76
|
+
${highlight('OPTIONS:')}
|
|
77
|
+
--help, -h Show this help message
|
|
78
|
+
--version, -v Show version
|
|
79
|
+
--json Output results as JSON (for scripting)
|
|
80
|
+
--quiet, -q Suppress non-essential output
|
|
81
|
+
|
|
82
|
+
${highlight('EXAMPLES:')}
|
|
83
|
+
${dim('# Start the visual agent')}
|
|
84
|
+
liku start
|
|
85
|
+
|
|
86
|
+
${dim('# Click a button by text')}
|
|
87
|
+
liku click "Submit"
|
|
88
|
+
|
|
89
|
+
${dim('# Find all buttons with "Save" in their text')}
|
|
90
|
+
liku find "Save" --type Button
|
|
91
|
+
|
|
92
|
+
${dim('# Type text')}
|
|
93
|
+
liku type "Hello, World!"
|
|
94
|
+
|
|
95
|
+
${dim('# Send keyboard shortcut')}
|
|
96
|
+
liku keys ctrl+shift+s
|
|
97
|
+
|
|
98
|
+
${dim('# Take a screenshot')}
|
|
99
|
+
liku screenshot ./capture.png
|
|
100
|
+
|
|
101
|
+
${dim('# Focus VS Code window')}
|
|
102
|
+
liku window "Visual Studio Code"
|
|
103
|
+
|
|
104
|
+
${dim('# Interactive mode')}
|
|
105
|
+
liku repl
|
|
106
|
+
|
|
107
|
+
${highlight('ENVIRONMENT:')}
|
|
108
|
+
LIKU_DEBUG=1 Enable debug output
|
|
109
|
+
LIKU_JSON=1 Default to JSON output
|
|
110
|
+
|
|
111
|
+
${dim('Documentation: https://github.com/TayDa64/copilot-Liku-cli')}
|
|
112
|
+
`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Show version
|
|
117
|
+
*/
|
|
118
|
+
function showVersion() {
|
|
119
|
+
console.log(`liku v${pkg.version}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse command-line arguments
|
|
124
|
+
*/
|
|
125
|
+
function parseArgs(argv) {
|
|
126
|
+
const args = argv.slice(2);
|
|
127
|
+
const result = {
|
|
128
|
+
command: null,
|
|
129
|
+
args: [],
|
|
130
|
+
flags: {
|
|
131
|
+
help: false,
|
|
132
|
+
version: false,
|
|
133
|
+
json: false,
|
|
134
|
+
quiet: false,
|
|
135
|
+
debug: process.env.LIKU_DEBUG === '1',
|
|
136
|
+
},
|
|
137
|
+
options: {},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
let i = 0;
|
|
141
|
+
while (i < args.length) {
|
|
142
|
+
const arg = args[i];
|
|
143
|
+
|
|
144
|
+
if (arg === '--help' || arg === '-h') {
|
|
145
|
+
result.flags.help = true;
|
|
146
|
+
} else if (arg === '--version' || arg === '-v') {
|
|
147
|
+
result.flags.version = true;
|
|
148
|
+
} else if (arg === '--json') {
|
|
149
|
+
result.flags.json = true;
|
|
150
|
+
} else if (arg === '--quiet' || arg === '-q') {
|
|
151
|
+
result.flags.quiet = true;
|
|
152
|
+
} else if (arg === '--debug') {
|
|
153
|
+
result.flags.debug = true;
|
|
154
|
+
} else if (arg.startsWith('--')) {
|
|
155
|
+
// Named option (--key=value or --key value)
|
|
156
|
+
const [key, val] = arg.slice(2).split('=');
|
|
157
|
+
if (val !== undefined) {
|
|
158
|
+
result.options[key] = val;
|
|
159
|
+
} else if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
160
|
+
result.options[key] = args[++i];
|
|
161
|
+
} else {
|
|
162
|
+
result.options[key] = true;
|
|
163
|
+
}
|
|
164
|
+
} else if (!result.command) {
|
|
165
|
+
result.command = arg;
|
|
166
|
+
} else {
|
|
167
|
+
result.args.push(arg);
|
|
168
|
+
}
|
|
169
|
+
i++;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Default JSON from env
|
|
173
|
+
if (process.env.LIKU_JSON === '1') {
|
|
174
|
+
result.flags.json = true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Load and execute a command module
|
|
182
|
+
*/
|
|
183
|
+
async function executeCommand(name, cmdArgs, flags, options) {
|
|
184
|
+
const cmdInfo = COMMANDS[name];
|
|
185
|
+
if (!cmdInfo) {
|
|
186
|
+
error(`Unknown command: ${name}`);
|
|
187
|
+
console.log(`\nRun ${highlight('liku --help')} for available commands.`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const cmdPath = path.join(COMMANDS_DIR, `${cmdInfo.file}.js`);
|
|
192
|
+
|
|
193
|
+
if (!fs.existsSync(cmdPath)) {
|
|
194
|
+
error(`Command module not found: ${cmdPath}`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const command = require(cmdPath);
|
|
200
|
+
const result = await command.run(cmdArgs, { ...flags, ...options });
|
|
201
|
+
|
|
202
|
+
// Output result
|
|
203
|
+
if (flags.json && result !== undefined) {
|
|
204
|
+
console.log(JSON.stringify(result, null, 2));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Exit with appropriate code
|
|
208
|
+
if (result && result.success === false) {
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (flags.debug) {
|
|
213
|
+
console.error(err);
|
|
214
|
+
} else {
|
|
215
|
+
error(err.message);
|
|
216
|
+
}
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Main entry point
|
|
223
|
+
*/
|
|
224
|
+
async function main() {
|
|
225
|
+
const { command, args, flags, options } = parseArgs(process.argv);
|
|
226
|
+
|
|
227
|
+
// Handle global flags
|
|
228
|
+
if (flags.version) {
|
|
229
|
+
showVersion();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (flags.help || (!command && args.length === 0)) {
|
|
234
|
+
showHelp();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Default command is 'start' (launch Electron)
|
|
239
|
+
const cmd = command || 'start';
|
|
240
|
+
|
|
241
|
+
// Execute the command
|
|
242
|
+
await executeCommand(cmd, args, flags, options);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Run
|
|
246
|
+
main().catch(err => {
|
|
247
|
+
error(err.message);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Output Utilities
|
|
3
|
+
*
|
|
4
|
+
* Colored console output helpers for the liku CLI.
|
|
5
|
+
* @module cli/util/output
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ANSI color codes
|
|
9
|
+
const COLORS = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
bright: '\x1b[1m',
|
|
12
|
+
dim: '\x1b[2m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
blue: '\x1b[34m',
|
|
17
|
+
magenta: '\x1b[35m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
white: '\x1b[37m',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Check if colors are supported
|
|
23
|
+
const supportsColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
24
|
+
|
|
25
|
+
function colorize(color, text) {
|
|
26
|
+
if (!supportsColor) return text;
|
|
27
|
+
return `${color}${text}${COLORS.reset}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Log a message (no prefix)
|
|
32
|
+
*/
|
|
33
|
+
function log(message) {
|
|
34
|
+
console.log(message);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Success message (green checkmark)
|
|
39
|
+
*/
|
|
40
|
+
function success(message) {
|
|
41
|
+
console.log(colorize(COLORS.green, '✓ ') + message);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Error message (red X)
|
|
46
|
+
*/
|
|
47
|
+
function error(message) {
|
|
48
|
+
console.error(colorize(COLORS.red, '✗ ') + message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Warning message (yellow)
|
|
53
|
+
*/
|
|
54
|
+
function warn(message) {
|
|
55
|
+
console.log(colorize(COLORS.yellow, '⚠ ') + message);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Info message (blue)
|
|
60
|
+
*/
|
|
61
|
+
function info(message) {
|
|
62
|
+
console.log(colorize(COLORS.blue, 'ℹ ') + message);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Dim text (muted)
|
|
67
|
+
*/
|
|
68
|
+
function dim(text) {
|
|
69
|
+
return colorize(COLORS.dim, text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Highlight text (cyan/bright)
|
|
74
|
+
*/
|
|
75
|
+
function highlight(text) {
|
|
76
|
+
return colorize(COLORS.cyan, text);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Bold text
|
|
81
|
+
*/
|
|
82
|
+
function bold(text) {
|
|
83
|
+
return colorize(COLORS.bright, text);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Format a table of data
|
|
88
|
+
*/
|
|
89
|
+
function table(rows, headers = null) {
|
|
90
|
+
if (rows.length === 0) return;
|
|
91
|
+
|
|
92
|
+
// Calculate column widths
|
|
93
|
+
const allRows = headers ? [headers, ...rows] : rows;
|
|
94
|
+
const colCount = Math.max(...allRows.map(r => r.length));
|
|
95
|
+
const colWidths = [];
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < colCount; i++) {
|
|
98
|
+
colWidths[i] = Math.max(...allRows.map(r => String(r[i] || '').length));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Print headers
|
|
102
|
+
if (headers) {
|
|
103
|
+
const headerLine = headers.map((h, i) => String(h).padEnd(colWidths[i])).join(' ');
|
|
104
|
+
console.log(bold(headerLine));
|
|
105
|
+
console.log(dim('-'.repeat(headerLine.length)));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Print rows
|
|
109
|
+
for (const row of rows) {
|
|
110
|
+
const line = row.map((cell, i) => String(cell || '').padEnd(colWidths[i])).join(' ');
|
|
111
|
+
console.log(line);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Spinner for long-running operations
|
|
117
|
+
*/
|
|
118
|
+
class Spinner {
|
|
119
|
+
constructor(message) {
|
|
120
|
+
this.message = message;
|
|
121
|
+
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
122
|
+
this.frameIndex = 0;
|
|
123
|
+
this.interval = null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
start() {
|
|
127
|
+
if (!supportsColor) {
|
|
128
|
+
console.log(this.message + '...');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
process.stdout.write(this.message + ' ');
|
|
133
|
+
this.interval = setInterval(() => {
|
|
134
|
+
process.stdout.write(`\r${this.message} ${this.frames[this.frameIndex]}`);
|
|
135
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
136
|
+
}, 80);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
stop(finalMessage = null) {
|
|
140
|
+
if (this.interval) {
|
|
141
|
+
clearInterval(this.interval);
|
|
142
|
+
this.interval = null;
|
|
143
|
+
}
|
|
144
|
+
if (supportsColor) {
|
|
145
|
+
process.stdout.write('\r' + ' '.repeat(this.message.length + 10) + '\r');
|
|
146
|
+
}
|
|
147
|
+
if (finalMessage) {
|
|
148
|
+
console.log(finalMessage);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
succeed(message) {
|
|
153
|
+
this.stop(colorize(COLORS.green, '✓ ') + (message || this.message));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fail(message) {
|
|
157
|
+
this.stop(colorize(COLORS.red, '✗ ') + (message || this.message));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
log,
|
|
163
|
+
success,
|
|
164
|
+
error,
|
|
165
|
+
warn,
|
|
166
|
+
info,
|
|
167
|
+
dim,
|
|
168
|
+
highlight,
|
|
169
|
+
bold,
|
|
170
|
+
table,
|
|
171
|
+
Spinner,
|
|
172
|
+
COLORS,
|
|
173
|
+
colorize,
|
|
174
|
+
};
|