plazbot-cli 0.3.1 → 0.3.3
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.
|
@@ -1,17 +1,71 @@
|
|
|
1
1
|
// @ts-ignore - marked-terminal no provee tipos
|
|
2
2
|
import TerminalRenderer from 'marked-terminal';
|
|
3
3
|
import { marked } from 'marked';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
// @ts-ignore - cli-highlight no provee tipos completos
|
|
6
|
+
import { highlight, supportsLanguage } from 'cli-highlight';
|
|
4
7
|
let configured = false;
|
|
8
|
+
const LANG_ALIASES = {
|
|
9
|
+
sh: 'bash',
|
|
10
|
+
shell: 'bash',
|
|
11
|
+
zsh: 'bash',
|
|
12
|
+
curl: 'bash',
|
|
13
|
+
jsx: 'javascript',
|
|
14
|
+
ts: 'typescript',
|
|
15
|
+
tsx: 'typescript',
|
|
16
|
+
yml: 'yaml',
|
|
17
|
+
html: 'xml',
|
|
18
|
+
text: 'plaintext',
|
|
19
|
+
txt: 'plaintext',
|
|
20
|
+
};
|
|
21
|
+
function resolveLang(raw) {
|
|
22
|
+
const k = (raw ?? '').toLowerCase().trim();
|
|
23
|
+
const aliased = LANG_ALIASES[k] ?? k;
|
|
24
|
+
if (!aliased)
|
|
25
|
+
return 'plaintext';
|
|
26
|
+
try {
|
|
27
|
+
return supportsLanguage(aliased) ? aliased : 'plaintext';
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 'plaintext';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function renderCodeBlock(code, rawLang) {
|
|
34
|
+
const lang = resolveLang(rawLang);
|
|
35
|
+
let body;
|
|
36
|
+
try {
|
|
37
|
+
body = highlight(code, { language: lang, ignoreIllegals: true });
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
body = code;
|
|
41
|
+
}
|
|
42
|
+
const label = (rawLang ?? '').trim() || lang;
|
|
43
|
+
const width = Math.max(20, Math.min(80, (process.stdout.columns || 80) - 8));
|
|
44
|
+
const headerLabel = ` ${label} `;
|
|
45
|
+
const headerRule = '─'.repeat(Math.max(0, width - headerLabel.length - 4));
|
|
46
|
+
const header = chalk.gray.dim(`──${headerLabel}${headerRule}`);
|
|
47
|
+
const footer = chalk.gray.dim('─'.repeat(width - 2));
|
|
48
|
+
// Indent del cuerpo para diferenciar del prosa
|
|
49
|
+
const indented = body
|
|
50
|
+
.split('\n')
|
|
51
|
+
.map((l) => ' ' + l)
|
|
52
|
+
.join('\n');
|
|
53
|
+
return `\n${header}\n${indented}\n${footer}\n`;
|
|
54
|
+
}
|
|
5
55
|
function configure() {
|
|
6
56
|
if (configured)
|
|
7
57
|
return;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
58
|
+
const renderer = new TerminalRenderer({
|
|
59
|
+
width: Math.max(40, (process.stdout.columns || 80) - 8),
|
|
60
|
+
reflowText: true,
|
|
61
|
+
tab: 2,
|
|
62
|
+
// Inline code mejor diferenciado del texto normal
|
|
63
|
+
codespan: chalk.cyan,
|
|
14
64
|
});
|
|
65
|
+
// Override del code block para syntax highlight + box delimitado.
|
|
66
|
+
// marked-terminal pasa (code, lang, escaped) al método `code`.
|
|
67
|
+
renderer.code = (code, lang) => renderCodeBlock(code, lang);
|
|
68
|
+
marked.setOptions({ renderer });
|
|
15
69
|
configured = true;
|
|
16
70
|
}
|
|
17
71
|
/**
|
package/dist/studio/runRepl.js
CHANGED
|
@@ -6,6 +6,12 @@ import { dirname, join } from 'node:path';
|
|
|
6
6
|
import { App } from './components/App.js';
|
|
7
7
|
import { getStoredCredentials } from '../utils/credentials.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
+
// Alternate screen buffer (ANSI). Same trick used by vim, htop, claude code, lazygit.
|
|
10
|
+
// The TUI runs in a clean canvas and the original scrollback is restored on exit,
|
|
11
|
+
// which eliminates the "ghost frames" Ink leaves behind when the terminal is resized.
|
|
12
|
+
const ALT_SCREEN_ENTER = '\x1b[?1049h\x1b[H';
|
|
13
|
+
const ALT_SCREEN_EXIT = '\x1b[?1049l';
|
|
14
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
9
15
|
export async function runRepl(opts) {
|
|
10
16
|
let creds;
|
|
11
17
|
try {
|
|
@@ -30,6 +36,31 @@ export async function runRepl(opts) {
|
|
|
30
36
|
dev: opts.dev,
|
|
31
37
|
};
|
|
32
38
|
const version = readVersion();
|
|
39
|
+
const useAltScreen = !!process.stdout.isTTY;
|
|
40
|
+
// Cleanup idempotente; lo registramos en varias señales para que la terminal
|
|
41
|
+
// nunca quede en alt screen ni con el cursor oculto si el proceso muere mal.
|
|
42
|
+
let cleanedUp = false;
|
|
43
|
+
const restoreTerminal = () => {
|
|
44
|
+
if (cleanedUp)
|
|
45
|
+
return;
|
|
46
|
+
cleanedUp = true;
|
|
47
|
+
if (useAltScreen) {
|
|
48
|
+
process.stdout.write(ALT_SCREEN_EXIT);
|
|
49
|
+
process.stdout.write(SHOW_CURSOR);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
if (useAltScreen) {
|
|
53
|
+
process.stdout.write(ALT_SCREEN_ENTER);
|
|
54
|
+
}
|
|
55
|
+
process.on('exit', restoreTerminal);
|
|
56
|
+
process.on('SIGHUP', restoreTerminal);
|
|
57
|
+
process.on('SIGTERM', restoreTerminal);
|
|
58
|
+
process.on('uncaughtException', (err) => {
|
|
59
|
+
restoreTerminal();
|
|
60
|
+
// Re-emit so node prints the trace after restoring the terminal.
|
|
61
|
+
console.error(err);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
33
64
|
const { waitUntilExit } = render(_jsx(App, { version: version, stream: stream, initialAgentId: opts.agentId ?? null, dev: opts.dev, supportMode: supportMode }), {
|
|
34
65
|
exitOnCtrlC: false,
|
|
35
66
|
});
|
|
@@ -37,7 +68,12 @@ export async function runRepl(opts) {
|
|
|
37
68
|
process.on('SIGINT', () => {
|
|
38
69
|
// no-op aquí; App.tsx llama exit() al detectar Ctrl+C.
|
|
39
70
|
});
|
|
40
|
-
|
|
71
|
+
try {
|
|
72
|
+
await waitUntilExit();
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
restoreTerminal();
|
|
76
|
+
}
|
|
41
77
|
}
|
|
42
78
|
function readVersion() {
|
|
43
79
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plazbot-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "CLI para Plazbot SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"axios": "^1.6.0",
|
|
40
40
|
"boxen": "^7.1.1",
|
|
41
41
|
"chalk": "^5.3.0",
|
|
42
|
+
"cli-highlight": "^2.1.11",
|
|
42
43
|
"cli-table3": "^0.6.5",
|
|
43
44
|
"commander": "^12.0.0",
|
|
44
45
|
"figures": "^6.1.0",
|
|
@@ -1,18 +1,79 @@
|
|
|
1
1
|
// @ts-ignore - marked-terminal no provee tipos
|
|
2
2
|
import TerminalRenderer from 'marked-terminal';
|
|
3
3
|
import { marked } from 'marked';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
// @ts-ignore - cli-highlight no provee tipos completos
|
|
6
|
+
import { highlight, supportsLanguage } from 'cli-highlight';
|
|
4
7
|
|
|
5
8
|
let configured = false;
|
|
6
9
|
|
|
10
|
+
const LANG_ALIASES: Record<string, string> = {
|
|
11
|
+
sh: 'bash',
|
|
12
|
+
shell: 'bash',
|
|
13
|
+
zsh: 'bash',
|
|
14
|
+
curl: 'bash',
|
|
15
|
+
jsx: 'javascript',
|
|
16
|
+
ts: 'typescript',
|
|
17
|
+
tsx: 'typescript',
|
|
18
|
+
yml: 'yaml',
|
|
19
|
+
html: 'xml',
|
|
20
|
+
text: 'plaintext',
|
|
21
|
+
txt: 'plaintext',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function resolveLang(raw: string | undefined): string {
|
|
25
|
+
const k = (raw ?? '').toLowerCase().trim();
|
|
26
|
+
const aliased = LANG_ALIASES[k] ?? k;
|
|
27
|
+
if (!aliased) return 'plaintext';
|
|
28
|
+
try {
|
|
29
|
+
return supportsLanguage(aliased) ? aliased : 'plaintext';
|
|
30
|
+
} catch {
|
|
31
|
+
return 'plaintext';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderCodeBlock(code: string, rawLang: string | undefined): string {
|
|
36
|
+
const lang = resolveLang(rawLang);
|
|
37
|
+
let body: string;
|
|
38
|
+
try {
|
|
39
|
+
body = highlight(code, { language: lang, ignoreIllegals: true });
|
|
40
|
+
} catch {
|
|
41
|
+
body = code;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const label = (rawLang ?? '').trim() || lang;
|
|
45
|
+
const width = Math.max(20, Math.min(80, (process.stdout.columns || 80) - 8));
|
|
46
|
+
const headerLabel = ` ${label} `;
|
|
47
|
+
const headerRule = '─'.repeat(Math.max(0, width - headerLabel.length - 4));
|
|
48
|
+
const header = chalk.gray.dim(`──${headerLabel}${headerRule}`);
|
|
49
|
+
const footer = chalk.gray.dim('─'.repeat(width - 2));
|
|
50
|
+
|
|
51
|
+
// Indent del cuerpo para diferenciar del prosa
|
|
52
|
+
const indented = body
|
|
53
|
+
.split('\n')
|
|
54
|
+
.map((l) => ' ' + l)
|
|
55
|
+
.join('\n');
|
|
56
|
+
|
|
57
|
+
return `\n${header}\n${indented}\n${footer}\n`;
|
|
58
|
+
}
|
|
59
|
+
|
|
7
60
|
function configure(): void {
|
|
8
61
|
if (configured) return;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
62
|
+
|
|
63
|
+
const renderer = new TerminalRenderer({
|
|
64
|
+
width: Math.max(40, (process.stdout.columns || 80) - 8),
|
|
65
|
+
reflowText: true,
|
|
66
|
+
tab: 2,
|
|
67
|
+
// Inline code mejor diferenciado del texto normal
|
|
68
|
+
codespan: chalk.cyan,
|
|
69
|
+
}) as any;
|
|
70
|
+
|
|
71
|
+
// Override del code block para syntax highlight + box delimitado.
|
|
72
|
+
// marked-terminal pasa (code, lang, escaped) al método `code`.
|
|
73
|
+
renderer.code = (code: string, lang: string | undefined): string =>
|
|
74
|
+
renderCodeBlock(code, lang);
|
|
75
|
+
|
|
76
|
+
marked.setOptions({ renderer });
|
|
16
77
|
configured = true;
|
|
17
78
|
}
|
|
18
79
|
|
package/src/studio/runRepl.tsx
CHANGED
|
@@ -15,6 +15,13 @@ interface RunReplOptions {
|
|
|
15
15
|
workspaceOverride?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Alternate screen buffer (ANSI). Same trick used by vim, htop, claude code, lazygit.
|
|
19
|
+
// The TUI runs in a clean canvas and the original scrollback is restored on exit,
|
|
20
|
+
// which eliminates the "ghost frames" Ink leaves behind when the terminal is resized.
|
|
21
|
+
const ALT_SCREEN_ENTER = '\x1b[?1049h\x1b[H';
|
|
22
|
+
const ALT_SCREEN_EXIT = '\x1b[?1049l';
|
|
23
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
24
|
+
|
|
18
25
|
export async function runRepl(opts: RunReplOptions): Promise<void> {
|
|
19
26
|
let creds;
|
|
20
27
|
try {
|
|
@@ -42,6 +49,32 @@ export async function runRepl(opts: RunReplOptions): Promise<void> {
|
|
|
42
49
|
};
|
|
43
50
|
|
|
44
51
|
const version = readVersion();
|
|
52
|
+
const useAltScreen = !!process.stdout.isTTY;
|
|
53
|
+
|
|
54
|
+
// Cleanup idempotente; lo registramos en varias señales para que la terminal
|
|
55
|
+
// nunca quede en alt screen ni con el cursor oculto si el proceso muere mal.
|
|
56
|
+
let cleanedUp = false;
|
|
57
|
+
const restoreTerminal = () => {
|
|
58
|
+
if (cleanedUp) return;
|
|
59
|
+
cleanedUp = true;
|
|
60
|
+
if (useAltScreen) {
|
|
61
|
+
process.stdout.write(ALT_SCREEN_EXIT);
|
|
62
|
+
process.stdout.write(SHOW_CURSOR);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (useAltScreen) {
|
|
67
|
+
process.stdout.write(ALT_SCREEN_ENTER);
|
|
68
|
+
}
|
|
69
|
+
process.on('exit', restoreTerminal);
|
|
70
|
+
process.on('SIGHUP', restoreTerminal);
|
|
71
|
+
process.on('SIGTERM', restoreTerminal);
|
|
72
|
+
process.on('uncaughtException', (err) => {
|
|
73
|
+
restoreTerminal();
|
|
74
|
+
// Re-emit so node prints the trace after restoring the terminal.
|
|
75
|
+
console.error(err);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
});
|
|
45
78
|
|
|
46
79
|
const { waitUntilExit } = render(
|
|
47
80
|
<App
|
|
@@ -61,7 +94,11 @@ export async function runRepl(opts: RunReplOptions): Promise<void> {
|
|
|
61
94
|
// no-op aquí; App.tsx llama exit() al detectar Ctrl+C.
|
|
62
95
|
});
|
|
63
96
|
|
|
64
|
-
|
|
97
|
+
try {
|
|
98
|
+
await waitUntilExit();
|
|
99
|
+
} finally {
|
|
100
|
+
restoreTerminal();
|
|
101
|
+
}
|
|
65
102
|
}
|
|
66
103
|
|
|
67
104
|
function readVersion(): string {
|