@ryuu-reinzz/ryuu-cli 0.4.2 → 0.5.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/CHANGELOG.md +102 -0
- package/README.md +53 -6
- package/docs/drive.md +484 -0
- package/package.json +4 -1
- package/plugins/google-drive/auth.js +57 -0
- package/plugins/google-drive/backup.js +282 -0
- package/plugins/google-drive/command.js +266 -0
- package/plugins/google-drive/config.js +252 -0
- package/plugins/google-drive/credentials.js +171 -0
- package/plugins/google-drive/drive-api.js +304 -0
- package/plugins/google-drive/index.js +26 -0
- package/plugins/google-drive/manager.js +507 -0
- package/plugins/google-drive/oauth-backend.js +161 -0
- package/plugins/google-drive/secret.js +50 -0
- package/plugins/google-drive/token-client.js +80 -0
- package/plugins/google-drive/tool-helpers.js +46 -0
- package/plugins/google-drive/tools-backup.js +204 -0
- package/plugins/google-drive/tools-files.js +315 -0
- package/plugins/google-drive/tools.js +13 -0
- package/plugins/google-drive/zip-reader.js +217 -0
- package/plugins/google-drive/zip-writer.js +204 -0
- package/plugins/index.js +56 -0
- package/src/agent/agent.js +128 -48
- package/src/agent/planner.js +1 -0
- package/src/cli/app.js +16 -0
- package/src/cli/commands.js +19 -0
- package/src/cli/markdown.js +10 -5
- package/src/cli/renderer.js +38 -5
- package/src/cli/tui.js +80 -9
- package/src/tools/registry.js +13 -0
package/src/cli/renderer.js
CHANGED
|
@@ -311,6 +311,15 @@ export class Renderer {
|
|
|
311
311
|
return this.c.hex(PALETTE[name] || PALETTE.white)(String(text));
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Tool name colour: built-ins stay flat blue, plugin tools are sky blue and
|
|
316
|
+
* bold so an installed integration is visible at a glance.
|
|
317
|
+
*/
|
|
318
|
+
#toolName(name, plugin) {
|
|
319
|
+
const text = String(name ?? '');
|
|
320
|
+
return plugin ? this.c.bold(this.c.hex(PALETTE.sky)(text)) : this.c.hex(PALETTE.blue)(text);
|
|
321
|
+
}
|
|
322
|
+
|
|
314
323
|
#visibleLength(text) {
|
|
315
324
|
return displayWidth(text, { ansi: true });
|
|
316
325
|
}
|
|
@@ -510,6 +519,29 @@ export class Renderer {
|
|
|
510
519
|
for (const line of String(text).split('\n')) this.#write(line);
|
|
511
520
|
}
|
|
512
521
|
|
|
522
|
+
/**
|
|
523
|
+
* A framed monospace block with a `⧉ copy` chip — used by commands that must
|
|
524
|
+
* show something copyable, such as a login URL. It reuses the Markdown code
|
|
525
|
+
* block path, so the whole box is the clipboard hit target.
|
|
526
|
+
*/
|
|
527
|
+
showCode({ label = '', text = '' } = {}) {
|
|
528
|
+
const body = String(text ?? '');
|
|
529
|
+
if (!body) return;
|
|
530
|
+
// A piped or plain run has no frame and no click target: print the value.
|
|
531
|
+
if (this.plain) {
|
|
532
|
+
this.lines(body);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (/(^|\n)\s*```/.test(body)) {
|
|
536
|
+
this.lines(body);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const lang = String(label ?? '')
|
|
540
|
+
.replace(/[^A-Za-z0-9_+#.-]+/g, '-')
|
|
541
|
+
.replace(/^-+|-+$/g, '');
|
|
542
|
+
this.#markdown('```' + `${lang}\n${body}\n` + '```');
|
|
543
|
+
}
|
|
544
|
+
|
|
513
545
|
banner({ version, model, provider, workspace, node, approvalPolicy, logFile }) {
|
|
514
546
|
const c = this.c;
|
|
515
547
|
if (this.plain) {
|
|
@@ -1344,7 +1376,7 @@ export class Renderer {
|
|
|
1344
1376
|
}
|
|
1345
1377
|
}
|
|
1346
1378
|
|
|
1347
|
-
toolStart({ name, args, index = 0, total = 0, detail = '' }) {
|
|
1379
|
+
toolStart({ name, args, index = 0, total = 0, detail = '', plugin = null }) {
|
|
1348
1380
|
if (this.reasoning.active) this.reasoningEnd({});
|
|
1349
1381
|
if (this.answer.active) this.answerEnd();
|
|
1350
1382
|
// Running a tool is exactly the `working` phase the user asked for.
|
|
@@ -1365,11 +1397,11 @@ export class Renderer {
|
|
|
1365
1397
|
return;
|
|
1366
1398
|
}
|
|
1367
1399
|
this.#write(
|
|
1368
|
-
` ${this.c.dim('│')} ${this.#paint('violet', '◆')} ${this
|
|
1400
|
+
` ${this.c.dim('│')} ${this.#paint('violet', '◆')} ${this.#toolName(name, plugin)} ${this.c.dim(this.#fit(argsPreview, this.#innerWidth() - 20))}`,
|
|
1369
1401
|
);
|
|
1370
1402
|
}
|
|
1371
1403
|
|
|
1372
|
-
toolEnd({ name, ok, durationMs, summary, cached = false }) {
|
|
1404
|
+
toolEnd({ name, ok, durationMs, summary, cached = false, plugin = null }) {
|
|
1373
1405
|
this.status.tool = '';
|
|
1374
1406
|
this.status.toolIndex = 0;
|
|
1375
1407
|
this.status.toolTotal = 0;
|
|
@@ -1384,7 +1416,7 @@ export class Renderer {
|
|
|
1384
1416
|
this.#write(this.c.dim(` [cached] ${name} · ${detail}`));
|
|
1385
1417
|
return;
|
|
1386
1418
|
}
|
|
1387
|
-
this.#write(` ${this.c.dim('│')} ${this.#paint('cyan', '↺')} ${this
|
|
1419
|
+
this.#write(` ${this.c.dim('│')} ${this.#paint('cyan', '↺')} ${this.#toolName(name, plugin)} ${this.c.dim(`· ${detail}`)}`);
|
|
1388
1420
|
return;
|
|
1389
1421
|
}
|
|
1390
1422
|
if (this.plain) {
|
|
@@ -1396,8 +1428,9 @@ export class Renderer {
|
|
|
1396
1428
|
}
|
|
1397
1429
|
const symbol = ok ? this.#paint('green', '✓') : this.#paint('red', '✕');
|
|
1398
1430
|
const stats = [duration, detail].filter(Boolean).join(' · ');
|
|
1431
|
+
const label = ok ? this.#toolName(name, plugin) : this.c.hex(PALETTE.red)(String(name ?? ''));
|
|
1399
1432
|
this.#write(
|
|
1400
|
-
` ${this.c.dim('│')} ${symbol} ${
|
|
1433
|
+
` ${this.c.dim('│')} ${symbol} ${label}${stats ? ` ${this.c.dim(`· ${stats}`)}` : ''}`,
|
|
1401
1434
|
);
|
|
1402
1435
|
}
|
|
1403
1436
|
|
package/src/cli/tui.js
CHANGED
|
@@ -31,7 +31,8 @@ const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇',
|
|
|
31
31
|
/**
|
|
32
32
|
* One colour per kind of line, so a glance tells blocks apart:
|
|
33
33
|
* prose white, Markdown accents and tables sky blue, tool names yellow,
|
|
34
|
-
*
|
|
34
|
+
* plugin tool names sky blue in bold, reasoning green, failures red.
|
|
35
|
+
* This palette is the only place colours live.
|
|
35
36
|
*/
|
|
36
37
|
const PALETTE = {
|
|
37
38
|
white: '#e2e8f0',
|
|
@@ -556,6 +557,35 @@ function buildRows(item, width, { c, color }) {
|
|
|
556
557
|
return rows;
|
|
557
558
|
}
|
|
558
559
|
|
|
560
|
+
if (item.kind === 'code') {
|
|
561
|
+
// A framed monospace block with a `⧉ copy` chip. The whole box is the click
|
|
562
|
+
// target, so a long URL is copied in one click instead of a text selection.
|
|
563
|
+
const body = String(item.text ?? '');
|
|
564
|
+
const label = String(item.label ?? '');
|
|
565
|
+
if (/(^|\n)\s*```/.test(body)) {
|
|
566
|
+
// A body that itself carries a fence cannot be framed safely: print it plain.
|
|
567
|
+
for (const line of wrapAnsi(body, inner)) rows.push(line ? ` ${line}` : '');
|
|
568
|
+
return rows;
|
|
569
|
+
}
|
|
570
|
+
const lang = label.replace(/[^A-Za-z0-9_+#.-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
571
|
+
const copies = new Map();
|
|
572
|
+
const offset = rows.length;
|
|
573
|
+
const lines = renderMarkdown('```' + `${lang}\n${body}\n` + '```', {
|
|
574
|
+
width: Math.max(20, width - 2),
|
|
575
|
+
c,
|
|
576
|
+
color,
|
|
577
|
+
base: (text) => paint('white', text),
|
|
578
|
+
accent: (text) => paint('sky', text),
|
|
579
|
+
onCodeBlock: ({ start, end, code }) => {
|
|
580
|
+
const target = { lang: label, code: String(code ?? '').slice(0, MAX_CODE_COPY_CHARS) };
|
|
581
|
+
for (let row = start; row <= end; row += 1) copies.set(offset + row, target);
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
for (const line of lines) rows.push(line ? ` ${line}` : '');
|
|
585
|
+
if (copies.size > 0) rows.copies = copies;
|
|
586
|
+
return rows;
|
|
587
|
+
}
|
|
588
|
+
|
|
559
589
|
if (item.kind === 'answer') {
|
|
560
590
|
// Every block carries a small label, like reasoning does, so white response
|
|
561
591
|
// text is never mistaken for the tail of a tool or a notice.
|
|
@@ -564,6 +594,9 @@ function buildRows(item, width, { c, color }) {
|
|
|
564
594
|
// Every rendered row of a fenced code block is remembered so a click on its
|
|
565
595
|
// `⧉ copy` chip (or anywhere in the block) copies exactly that code.
|
|
566
596
|
const copies = new Map();
|
|
597
|
+
// Code-block row numbers are relative to this item's own rows, not to the
|
|
598
|
+
// transcript, so the click map keeps working after scrolling.
|
|
599
|
+
const base = rows.length;
|
|
567
600
|
const lines = renderMarkdown(item.text || '', {
|
|
568
601
|
width: Math.max(20, width - 2),
|
|
569
602
|
c,
|
|
@@ -572,7 +605,7 @@ function buildRows(item, width, { c, color }) {
|
|
|
572
605
|
accent: (text) => paint('sky', text),
|
|
573
606
|
onCodeBlock: ({ lang, code, start, end }) => {
|
|
574
607
|
const target = { lang, code: String(code ?? '').slice(0, MAX_CODE_COPY_CHARS) };
|
|
575
|
-
for (let row = start; row <= end; row += 1) copies.set(row, target);
|
|
608
|
+
for (let row = start; row <= end; row += 1) copies.set(base + row, target);
|
|
576
609
|
},
|
|
577
610
|
});
|
|
578
611
|
for (const line of lines) rows.push(line ? ` ${line}` : '');
|
|
@@ -583,7 +616,13 @@ function buildRows(item, width, { c, color }) {
|
|
|
583
616
|
if (item.kind === 'tool') {
|
|
584
617
|
const failed = item.ok === false;
|
|
585
618
|
const mark = failed ? paint('red', '✕') : paint('green', '✓');
|
|
586
|
-
|
|
619
|
+
// Plugin tools wear their own colour — sky blue and bold — so an installed
|
|
620
|
+
// integration never looks like built-in tooling. A failure is still red.
|
|
621
|
+
const name = failed
|
|
622
|
+
? c.bold(paint('red', item.name))
|
|
623
|
+
: item.plugin
|
|
624
|
+
? c.bold(paint('sky', item.name))
|
|
625
|
+
: paint('yellow', item.name);
|
|
587
626
|
if (failed) {
|
|
588
627
|
pushHeader(`${mark} ${name}${item.durationMs ? ` ${dim(`· ${formatSeconds(item.durationMs)}`)}` : ''}`);
|
|
589
628
|
pushWrapped(item.summary || 'tool failed', ' ', 'redSoft', { dimmed: true });
|
|
@@ -730,7 +769,7 @@ export class Tui {
|
|
|
730
769
|
step: null,
|
|
731
770
|
maxSteps: null,
|
|
732
771
|
tool: '',
|
|
733
|
-
|
|
772
|
+
toolPlugin: null,
|
|
734
773
|
completionTokens: 0,
|
|
735
774
|
frame: 0,
|
|
736
775
|
output: '',
|
|
@@ -891,7 +930,29 @@ export class Tui {
|
|
|
891
930
|
}
|
|
892
931
|
|
|
893
932
|
lines(text = '') {
|
|
894
|
-
|
|
933
|
+
// One command usually prints many lines. They belong to a single block: a
|
|
934
|
+
// rule between every line is what made command output look ragged.
|
|
935
|
+
const value = String(text ?? '');
|
|
936
|
+
const items = this.transcript.items;
|
|
937
|
+
const last = items[items.length - 1];
|
|
938
|
+
if (last && last.kind === 'info' && last.fromLines) {
|
|
939
|
+
last.text = `${last.text}\n${value}`;
|
|
940
|
+
last.version += 1;
|
|
941
|
+
} else {
|
|
942
|
+
const item = this.transcript.message(value);
|
|
943
|
+
item.fromLines = true;
|
|
944
|
+
}
|
|
945
|
+
this.requestRender();
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* A monospace block with a `⧉ copy` chip (renderMarkdown draws the frame).
|
|
950
|
+
* The whole block is a click target, like a code block in an answer.
|
|
951
|
+
*/
|
|
952
|
+
showCode({ label = '', text = '' } = {}) {
|
|
953
|
+
const body = String(text ?? '');
|
|
954
|
+
if (!body) return;
|
|
955
|
+
this.transcript.add({ kind: 'code', label: String(label ?? ''), text: body, version: 0 });
|
|
895
956
|
this.requestRender();
|
|
896
957
|
}
|
|
897
958
|
|
|
@@ -1067,14 +1128,17 @@ export class Tui {
|
|
|
1067
1128
|
this.requestRender();
|
|
1068
1129
|
}
|
|
1069
1130
|
|
|
1070
|
-
toolStart({ name = '', detail = '' } = {}) {
|
|
1131
|
+
toolStart({ name = '', detail = '', plugin = null } = {}) {
|
|
1071
1132
|
this.status.tool = name;
|
|
1133
|
+
// Plugin tools keep their own colour in the status bar too.
|
|
1134
|
+
this.status.toolPlugin = plugin || null;
|
|
1072
1135
|
this.requestRender();
|
|
1073
1136
|
}
|
|
1074
1137
|
|
|
1075
|
-
toolEnd({ name = '', ok = true, durationMs = 0, summary = '' } = {}) {
|
|
1138
|
+
toolEnd({ name = '', ok = true, durationMs = 0, summary = '', plugin = null } = {}) {
|
|
1076
1139
|
this.status.tool = '';
|
|
1077
|
-
this.
|
|
1140
|
+
this.status.toolPlugin = null;
|
|
1141
|
+
this.transcript.add({ kind: 'tool', name, ok, durationMs, summary, plugin: plugin || null, version: 0 });
|
|
1078
1142
|
this.requestRender();
|
|
1079
1143
|
}
|
|
1080
1144
|
|
|
@@ -1433,7 +1497,14 @@ export class Tui {
|
|
|
1433
1497
|
this.paint('blue', `step ${this.status.step}${this.status.maxSteps ? `/${this.status.maxSteps}` : ''}`),
|
|
1434
1498
|
);
|
|
1435
1499
|
}
|
|
1436
|
-
if (this.status.tool)
|
|
1500
|
+
if (this.status.tool) {
|
|
1501
|
+
// Plugin tools are sky blue and bold here too, matching the transcript.
|
|
1502
|
+
parts.push(
|
|
1503
|
+
this.status.toolPlugin
|
|
1504
|
+
? this.c.bold(this.paint('sky', this.status.tool))
|
|
1505
|
+
: this.paint('yellow', this.status.tool),
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1437
1508
|
if (this.toast && Date.now() <= this.toast.until) parts.push(this.paint('green', this.toast.text));
|
|
1438
1509
|
if (this.status.promptTokens || this.status.completionTokens) {
|
|
1439
1510
|
parts.push(
|
package/src/tools/registry.js
CHANGED
|
@@ -76,6 +76,19 @@ export class ToolRegistry {
|
|
|
76
76
|
return tool;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* The plugin a tool belongs to, or null for a built-in tool.
|
|
81
|
+
*
|
|
82
|
+
* The CLI uses this to give plugin tools their own colour, so an integration
|
|
83
|
+
* the user installed never looks like core tooling.
|
|
84
|
+
*/
|
|
85
|
+
pluginFor(name) {
|
|
86
|
+
for (const plugin of this.plugins.values()) {
|
|
87
|
+
if (plugin.toolNames.includes(name)) return plugin;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
79
92
|
has(name) {
|
|
80
93
|
return this.tools.has(name);
|
|
81
94
|
}
|