pelulu-cli 1.0.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 +273 -0
- package/config.example.json +24 -0
- package/package.json +44 -0
- package/src/core/auto-format.js +47 -0
- package/src/core/completion.js +65 -0
- package/src/core/config.js +68 -0
- package/src/core/confirm.js +51 -0
- package/src/core/context.js +69 -0
- package/src/core/conversation.js +57 -0
- package/src/core/diff.js +50 -0
- package/src/core/doctor.js +60 -0
- package/src/core/error-handler.js +39 -0
- package/src/core/event-bus.js +43 -0
- package/src/core/file-tracker.js +44 -0
- package/src/core/formatter.js +83 -0
- package/src/core/intent.js +81 -0
- package/src/core/keybindings.js +29 -0
- package/src/core/logger.js +67 -0
- package/src/core/model-info.js +26 -0
- package/src/core/retry.js +26 -0
- package/src/core/sandbox.js +54 -0
- package/src/core/session.js +46 -0
- package/src/core/spinner.js +60 -0
- package/src/core/stats.js +73 -0
- package/src/core/system-prompt.js +42 -0
- package/src/core/thinking.js +39 -0
- package/src/core/tool-help.js +117 -0
- package/src/core/tool-registry.js +82 -0
- package/src/core/wizard.js +55 -0
- package/src/core/workspace.js +96 -0
- package/src/index.js +150 -0
- package/src/mcp/activation.js +54 -0
- package/src/mcp/mcp-handler.js +92 -0
- package/src/mcp/message-sender.js +147 -0
- package/src/mcp/mqtt-client.js +160 -0
- package/src/mcp/wss-endpoint.js +133 -0
- package/src/plugins/manager.js +72 -0
- package/src/repl-commands.js +91 -0
- package/src/repl.js +115 -0
- package/src/tools/ai.js +128 -0
- package/src/tools/config.js +87 -0
- package/src/tools/diff.js +118 -0
- package/src/tools/env.js +58 -0
- package/src/tools/file.js +177 -0
- package/src/tools/git.js +166 -0
- package/src/tools/history.js +64 -0
- package/src/tools/network.js +87 -0
- package/src/tools/process.js +69 -0
- package/src/tools/project.js +152 -0
- package/src/tools/search.js +100 -0
- package/src/tools/shell.js +91 -0
- package/src/tools/snippet.js +95 -0
- package/src/tools/template.js +113 -0
- package/src/tools/watch.js +87 -0
- package/src/tui/renderer.js +119 -0
- package/src/tui/status-bar.js +34 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch Tool โ file change monitoring (1 MCP tool, 3 actions)
|
|
3
|
+
* Actions: start, stop, status
|
|
4
|
+
*/
|
|
5
|
+
import { watch as fsWatch } from 'fs';
|
|
6
|
+
import { resolve, join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { readdir, stat } from 'fs/promises';
|
|
9
|
+
import { log } from '../core/logger.js';
|
|
10
|
+
import { bus } from '../core/event-bus.js';
|
|
11
|
+
|
|
12
|
+
const HOME = homedir();
|
|
13
|
+
const _watchers = new Map();
|
|
14
|
+
|
|
15
|
+
function safe(p) {
|
|
16
|
+
return resolve((p || '.').replace(/^~(?=$|[/\\])/g, HOME));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ACTIONS = {
|
|
20
|
+
start: {
|
|
21
|
+
required: ['path'],
|
|
22
|
+
handler: async ({ path, recursive, filter }) => {
|
|
23
|
+
const abs = safe(path);
|
|
24
|
+
if (_watchers.has(abs)) throw new Error(`Already watching: ${abs}`);
|
|
25
|
+
|
|
26
|
+
const watcher = fsWatch(abs, { recursive: recursive !== false }, (eventType, filename) => {
|
|
27
|
+
if (filter && !filename.includes(filter)) return;
|
|
28
|
+
const event = { type: eventType, file: filename, path: abs, ts: Date.now() };
|
|
29
|
+
log('watch', `๐ ${eventType}: ${filename}`);
|
|
30
|
+
bus.emit('file:change', event);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
_watchers.set(abs, { watcher, started: Date.now() });
|
|
34
|
+
log('watch', `๐๏ธ Watching: ${abs}`);
|
|
35
|
+
return { watching: true, path: abs };
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
stop: {
|
|
40
|
+
required: ['path'],
|
|
41
|
+
handler: async ({ path }) => {
|
|
42
|
+
const abs = safe(path);
|
|
43
|
+
const entry = _watchers.get(abs);
|
|
44
|
+
if (!entry) throw new Error(`Not watching: ${abs}`);
|
|
45
|
+
entry.watcher.close();
|
|
46
|
+
_watchers.delete(abs);
|
|
47
|
+
return { stopped: true, path: abs };
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
status: {
|
|
52
|
+
required: [],
|
|
53
|
+
handler: async () => {
|
|
54
|
+
const watchers = [];
|
|
55
|
+
for (const [path, entry] of _watchers) {
|
|
56
|
+
watchers.push({ path, uptime: Math.round((Date.now() - entry.started) / 1000) });
|
|
57
|
+
}
|
|
58
|
+
return { count: watchers.length, watchers };
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const actionNames = Object.keys(ACTIONS);
|
|
64
|
+
|
|
65
|
+
export default {
|
|
66
|
+
name: 'watch',
|
|
67
|
+
description: 'File monitoring: start, stop, status',
|
|
68
|
+
actions: actionNames.map(name => ({ name, required: ACTIONS[name].required })),
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
action: { type: 'string', enum: actionNames },
|
|
73
|
+
path: { type: 'string', description: 'Directory to watch' },
|
|
74
|
+
recursive: { type: 'boolean', description: 'Watch recursively' },
|
|
75
|
+
filter: { type: 'string', description: 'Filename filter' },
|
|
76
|
+
},
|
|
77
|
+
required: ['action'],
|
|
78
|
+
},
|
|
79
|
+
async handler({ action, ...params }) {
|
|
80
|
+
const a = ACTIONS[action];
|
|
81
|
+
if (!a) throw new Error(`Unknown action: ${action}`);
|
|
82
|
+
for (const f of a.required) {
|
|
83
|
+
if (params[f] === undefined) throw new Error(`Missing required: ${f}`);
|
|
84
|
+
}
|
|
85
|
+
return a.handler(params);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Renderer โ rich terminal UI using chalk
|
|
3
|
+
* Like Claude Code / Gemini CLI / OpenCode
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { createInterface } from 'readline';
|
|
7
|
+
|
|
8
|
+
const box = {
|
|
9
|
+
tl: 'โญ', tr: 'โฎ', bl: 'โฐ', br: 'โฏ',
|
|
10
|
+
h: 'โ', v: 'โ', ml: 'โ', mr: 'โค',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function horizontal(width, char = box.h) {
|
|
14
|
+
return char.repeat(width);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function pad(text, width) {
|
|
18
|
+
return text + ' '.repeat(Math.max(0, width - text.length));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function center(text, width) {
|
|
22
|
+
const left = Math.floor((width - text.length) / 2);
|
|
23
|
+
return ' '.repeat(left) + text + ' '.repeat(width - left - text.length);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function renderBanner(config, tools, connected) {
|
|
27
|
+
const w = 48;
|
|
28
|
+
const actions = tools.reduce((s, t) => s + (t.actions?.length || 0), 0);
|
|
29
|
+
const cwd = process.cwd();
|
|
30
|
+
const dirName = cwd.split('/').pop() || cwd;
|
|
31
|
+
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(chalk.cyan(`${box.tl}${horizontal(w, box.h)}${box.tr}`));
|
|
34
|
+
console.log(chalk.cyan(`${box.v}`) + chalk.bold.white(center(`๐พ ${config.agent?.name || 'Pelulu CLI'}`, w)) + chalk.cyan(`${box.v}`));
|
|
35
|
+
console.log(chalk.cyan(`${box.ml}${horizontal(w, box.h)}${box.mr}`));
|
|
36
|
+
console.log(chalk.cyan(`${box.v}`) + chalk.gray(pad(` ๐ ${dirName}`, w)) + chalk.cyan(`${box.v}`));
|
|
37
|
+
console.log(chalk.cyan(`${box.v}`) + chalk.gray(pad(` ๐ง ${tools.length} tools ยท ${actions} actions ยท 15 MCP slots used`, w)) + chalk.cyan(`${box.v}`));
|
|
38
|
+
console.log(chalk.cyan(`${box.v}`) + chalk.gray(pad(` ${connected ? '๐ข MQTT Connected' : '๐ด Disconnected'}`, w)) + chalk.cyan(`${box.v}`));
|
|
39
|
+
console.log(chalk.cyan(`${box.bl}${horizontal(w, box.h)}${box.br}`));
|
|
40
|
+
console.log('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function renderStatus(status) {
|
|
44
|
+
const w = 48;
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log(chalk.cyan(`${box.tl}${horizontal(w, box.h)}${box.tr}`));
|
|
47
|
+
console.log(chalk.cyan(`${box.v}`) + chalk.bold.white(pad(' ๐ Status', w)) + chalk.cyan(`${box.v}`));
|
|
48
|
+
console.log(chalk.cyan(`${box.ml}${horizontal(w, box.h)}${box.mr}`));
|
|
49
|
+
for (const [key, value] of Object.entries(status)) {
|
|
50
|
+
console.log(chalk.cyan(`${box.v}`) + chalk.gray(pad(` ${key}: ${value}`, w)) + chalk.cyan(`${box.v}`));
|
|
51
|
+
}
|
|
52
|
+
console.log(chalk.cyan(`${box.bl}${horizontal(w, box.h)}${box.br}`));
|
|
53
|
+
console.log('');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function renderTools(tools) {
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log(chalk.bold.white('๐ง Available Tools:'));
|
|
59
|
+
console.log('');
|
|
60
|
+
for (const t of tools) {
|
|
61
|
+
const actions = t.actions?.map(a => a.name || a).join(', ') || '';
|
|
62
|
+
console.log(chalk.cyan(` ${t.name}`) + chalk.gray(` โ ${t.description}`));
|
|
63
|
+
console.log(chalk.dim(` ${actions}`));
|
|
64
|
+
}
|
|
65
|
+
console.log('');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function renderToolCall(name, action, args) {
|
|
69
|
+
const ts = new Date().toLocaleTimeString();
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log(chalk.dim(` ${ts} `) + chalk.cyan('โ๏ธ ') + chalk.white(`${name}.${action}`));
|
|
72
|
+
if (args?.path) console.log(chalk.dim(` ๐ ${args.path}`));
|
|
73
|
+
if (args?.command) console.log(chalk.dim(` ๐ป ${args.command}`));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function renderToolResult(success, data) {
|
|
77
|
+
if (success) {
|
|
78
|
+
console.log(chalk.green(` โ
OK`));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(chalk.red(` โ ${data || 'error'}`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function renderAiResponse(text) {
|
|
85
|
+
console.log('');
|
|
86
|
+
console.log(chalk.green(` ๐ค ${text}`));
|
|
87
|
+
console.log('');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function renderUserInput(text) {
|
|
91
|
+
const ts = new Date().toLocaleTimeString();
|
|
92
|
+
console.log(chalk.dim(` ${ts} `) + chalk.blue('๐ค ') + chalk.white(text));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function renderHelp() {
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log(chalk.bold.white(' Commands:'));
|
|
98
|
+
console.log(chalk.cyan(' /tools') + chalk.gray(' Show MCP tools'));
|
|
99
|
+
console.log(chalk.cyan(' /help <tool>') + chalk.gray(' Tool examples'));
|
|
100
|
+
console.log(chalk.cyan(' /status') + chalk.gray(' Connection & session'));
|
|
101
|
+
console.log(chalk.cyan(' /stats') + chalk.gray(' Usage statistics'));
|
|
102
|
+
console.log(chalk.cyan(' /workspace') + chalk.gray(' Project info'));
|
|
103
|
+
console.log(chalk.cyan(' /files') + chalk.gray(' File changes'));
|
|
104
|
+
console.log(chalk.cyan(' /call <tool>') + chalk.gray(' Call tool directly'));
|
|
105
|
+
console.log(chalk.cyan(' /doctor') + chalk.gray(' Health check'));
|
|
106
|
+
console.log(chalk.cyan(' /clear') + chalk.gray(' Clear screen'));
|
|
107
|
+
console.log(chalk.cyan(' /quit') + chalk.gray(' Exit'));
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(chalk.bold.white(' Shortcuts:'));
|
|
110
|
+
console.log(chalk.cyan(' read index.js') + chalk.gray(' โ file read'));
|
|
111
|
+
console.log(chalk.cyan(' run npm test') + chalk.gray(' โ shell exec'));
|
|
112
|
+
console.log(chalk.cyan(' git status') + chalk.gray(' โ git status'));
|
|
113
|
+
console.log(chalk.cyan(' build') + chalk.gray(' โ project build'));
|
|
114
|
+
console.log('');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function createPrompt(dirName) {
|
|
118
|
+
return chalk.cyan(`${dirName} `) + chalk.white('โฏ ');
|
|
119
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Bar โ persistent bottom status bar
|
|
3
|
+
* Shows: connection, session, tool count, time
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
export class StatusBar {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.items = {
|
|
10
|
+
mqtt: 'โณ',
|
|
11
|
+
session: '-',
|
|
12
|
+
tools: 0,
|
|
13
|
+
calls: 0,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
update(key, value) {
|
|
18
|
+
this.items[key] = value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
render() {
|
|
22
|
+
const { mqtt, session, tools, calls } = this.items;
|
|
23
|
+
const time = new Date().toLocaleTimeString();
|
|
24
|
+
const line = [
|
|
25
|
+
chalk.dim('โ'.repeat(process.stdout.columns || 60)),
|
|
26
|
+
chalk.gray(` ${mqtt === 'โ
' ? '๐ข' : '๐ด'} MQTT: ${mqtt}`),
|
|
27
|
+
chalk.gray(` ๐ก Session: ${session}`),
|
|
28
|
+
chalk.gray(` ๐ง Tools: ${tools}`),
|
|
29
|
+
chalk.gray(` ๐ Calls: ${calls}`),
|
|
30
|
+
chalk.gray(` ๐ ${time}`),
|
|
31
|
+
].join(' ');
|
|
32
|
+
console.log(line);
|
|
33
|
+
}
|
|
34
|
+
}
|