@robota-sdk/agent-cli 3.0.0-beta.24 → 3.0.0-beta.26
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/dist/node/bin.cjs +684 -120
- package/dist/node/bin.js +1 -1
- package/dist/node/{chunk-AKNLV5UA.js → chunk-QTZS5QXJ.js} +659 -90
- package/dist/node/index.cjs +689 -125
- package/dist/node/index.js +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
2
|
import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3
|
-
import { join as
|
|
3
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import {
|
|
6
6
|
loadConfig,
|
|
@@ -157,7 +157,7 @@ import { getModelName } from "@robota-sdk/agent-core";
|
|
|
157
157
|
import { useState, useCallback, useRef } from "react";
|
|
158
158
|
import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
|
|
159
159
|
var TOOL_ARG_DISPLAY_MAX = 80;
|
|
160
|
-
var
|
|
160
|
+
var TAIL_KEEP = 30;
|
|
161
161
|
var NOOP_TERMINAL = {
|
|
162
162
|
write: () => {
|
|
163
163
|
},
|
|
@@ -216,13 +216,17 @@ function useSession(props) {
|
|
|
216
216
|
if (event.toolArgs) {
|
|
217
217
|
const firstVal = Object.values(event.toolArgs)[0];
|
|
218
218
|
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
219
|
-
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0,
|
|
219
|
+
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
|
|
220
220
|
}
|
|
221
|
-
setActiveTools((prev) => [
|
|
221
|
+
setActiveTools((prev) => [
|
|
222
|
+
...prev,
|
|
223
|
+
{ toolName: event.toolName, firstArg, isRunning: true }
|
|
224
|
+
]);
|
|
222
225
|
} else {
|
|
226
|
+
const result = event.denied ? "denied" : event.success === false ? "error" : "success";
|
|
223
227
|
setActiveTools(
|
|
224
228
|
(prev) => prev.map(
|
|
225
|
-
(t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
|
|
229
|
+
(t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result } : t
|
|
226
230
|
)
|
|
227
231
|
);
|
|
228
232
|
}
|
|
@@ -246,7 +250,13 @@ function useSession(props) {
|
|
|
246
250
|
setStreamingText("");
|
|
247
251
|
setActiveTools([]);
|
|
248
252
|
}, []);
|
|
249
|
-
return {
|
|
253
|
+
return {
|
|
254
|
+
session: sessionRef.current,
|
|
255
|
+
permissionRequest,
|
|
256
|
+
streamingText,
|
|
257
|
+
clearStreamingText,
|
|
258
|
+
activeTools
|
|
259
|
+
};
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
// src/ui/hooks/useMessages.ts
|
|
@@ -369,7 +379,122 @@ function handleReset(addMessage) {
|
|
|
369
379
|
}
|
|
370
380
|
return { handled: true, exitRequested: true };
|
|
371
381
|
}
|
|
372
|
-
async function
|
|
382
|
+
async function handlePluginCommand(args, addMessage, callbacks) {
|
|
383
|
+
const parts = args.trim().split(/\s+/);
|
|
384
|
+
const subcommand = parts[0] ?? "";
|
|
385
|
+
const subArgs = parts.slice(1).join(" ").trim();
|
|
386
|
+
try {
|
|
387
|
+
switch (subcommand) {
|
|
388
|
+
case "":
|
|
389
|
+
case void 0: {
|
|
390
|
+
const plugins = await callbacks.listInstalled();
|
|
391
|
+
if (plugins.length === 0) {
|
|
392
|
+
addMessage({ role: "system", content: "No plugins installed." });
|
|
393
|
+
} else {
|
|
394
|
+
const lines = plugins.map(
|
|
395
|
+
(p) => ` ${p.name} \u2014 ${p.description} [${p.enabled ? "enabled" : "disabled"}]`
|
|
396
|
+
);
|
|
397
|
+
addMessage({ role: "system", content: `Installed plugins:
|
|
398
|
+
${lines.join("\n")}` });
|
|
399
|
+
}
|
|
400
|
+
return { handled: true };
|
|
401
|
+
}
|
|
402
|
+
case "install": {
|
|
403
|
+
if (!subArgs) {
|
|
404
|
+
addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
|
|
405
|
+
return { handled: true };
|
|
406
|
+
}
|
|
407
|
+
await callbacks.install(subArgs);
|
|
408
|
+
addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
|
|
409
|
+
return { handled: true };
|
|
410
|
+
}
|
|
411
|
+
case "uninstall": {
|
|
412
|
+
if (!subArgs) {
|
|
413
|
+
addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
|
|
414
|
+
return { handled: true };
|
|
415
|
+
}
|
|
416
|
+
await callbacks.uninstall(subArgs);
|
|
417
|
+
addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
|
|
418
|
+
return { handled: true };
|
|
419
|
+
}
|
|
420
|
+
case "enable": {
|
|
421
|
+
if (!subArgs) {
|
|
422
|
+
addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
|
|
423
|
+
return { handled: true };
|
|
424
|
+
}
|
|
425
|
+
await callbacks.enable(subArgs);
|
|
426
|
+
addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
|
|
427
|
+
return { handled: true };
|
|
428
|
+
}
|
|
429
|
+
case "disable": {
|
|
430
|
+
if (!subArgs) {
|
|
431
|
+
addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
|
|
432
|
+
return { handled: true };
|
|
433
|
+
}
|
|
434
|
+
await callbacks.disable(subArgs);
|
|
435
|
+
addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
|
|
436
|
+
return { handled: true };
|
|
437
|
+
}
|
|
438
|
+
case "marketplace": {
|
|
439
|
+
const mpParts = subArgs.split(/\s+/);
|
|
440
|
+
const mpSubcommand = mpParts[0] ?? "";
|
|
441
|
+
const mpArgs = mpParts.slice(1).join(" ").trim();
|
|
442
|
+
if (mpSubcommand === "add" && mpArgs) {
|
|
443
|
+
const registeredName = await callbacks.marketplaceAdd(mpArgs);
|
|
444
|
+
addMessage({
|
|
445
|
+
role: "system",
|
|
446
|
+
content: `Added marketplace: "${registeredName}" (from ${mpArgs})
|
|
447
|
+
Install plugins with: /plugin install <name>@${registeredName}`
|
|
448
|
+
});
|
|
449
|
+
return { handled: true };
|
|
450
|
+
} else if (mpSubcommand === "remove" && mpArgs) {
|
|
451
|
+
await callbacks.marketplaceRemove(mpArgs);
|
|
452
|
+
addMessage({
|
|
453
|
+
role: "system",
|
|
454
|
+
content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
|
|
455
|
+
});
|
|
456
|
+
return { handled: true };
|
|
457
|
+
} else if (mpSubcommand === "update" && mpArgs) {
|
|
458
|
+
await callbacks.marketplaceUpdate(mpArgs);
|
|
459
|
+
addMessage({
|
|
460
|
+
role: "system",
|
|
461
|
+
content: `Updated marketplace "${mpArgs}".`
|
|
462
|
+
});
|
|
463
|
+
return { handled: true };
|
|
464
|
+
} else if (mpSubcommand === "list") {
|
|
465
|
+
const sources = await callbacks.marketplaceList();
|
|
466
|
+
if (sources.length === 0) {
|
|
467
|
+
addMessage({ role: "system", content: "No marketplace sources configured." });
|
|
468
|
+
} else {
|
|
469
|
+
const lines = sources.map((s) => ` ${s.name} (${s.type})`);
|
|
470
|
+
addMessage({ role: "system", content: `Marketplace sources:
|
|
471
|
+
${lines.join("\n")}` });
|
|
472
|
+
}
|
|
473
|
+
return { handled: true };
|
|
474
|
+
} else {
|
|
475
|
+
addMessage({
|
|
476
|
+
role: "system",
|
|
477
|
+
content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
|
|
478
|
+
});
|
|
479
|
+
return { handled: true };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
default:
|
|
483
|
+
addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
|
|
484
|
+
return { handled: true };
|
|
485
|
+
}
|
|
486
|
+
} catch (error) {
|
|
487
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
488
|
+
addMessage({ role: "system", content: `Plugin error: ${message}` });
|
|
489
|
+
return { handled: true };
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
async function handleReloadPlugins(addMessage, callbacks) {
|
|
493
|
+
await callbacks.reloadPlugins();
|
|
494
|
+
addMessage({ role: "system", content: "Plugins reload complete." });
|
|
495
|
+
return { handled: true };
|
|
496
|
+
}
|
|
497
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
|
|
373
498
|
switch (cmd) {
|
|
374
499
|
case "help":
|
|
375
500
|
return handleHelp(addMessage);
|
|
@@ -393,10 +518,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
393
518
|
return handleReset(addMessage);
|
|
394
519
|
case "exit":
|
|
395
520
|
return { handled: true, exitRequested: true };
|
|
521
|
+
case "plugin":
|
|
522
|
+
if (pluginCallbacks) {
|
|
523
|
+
return handlePluginCommand(args, addMessage, pluginCallbacks);
|
|
524
|
+
}
|
|
525
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
526
|
+
return { handled: true };
|
|
527
|
+
case "reload-plugins":
|
|
528
|
+
if (pluginCallbacks) {
|
|
529
|
+
return handleReloadPlugins(addMessage, pluginCallbacks);
|
|
530
|
+
}
|
|
531
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
532
|
+
return { handled: true };
|
|
396
533
|
default: {
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
399
|
-
addMessage({ role: "system", content: `Invoking
|
|
534
|
+
const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
535
|
+
if (dynamicCmd) {
|
|
536
|
+
addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
|
|
400
537
|
return { handled: false };
|
|
401
538
|
}
|
|
402
539
|
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
@@ -407,14 +544,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
407
544
|
|
|
408
545
|
// src/ui/hooks/useSlashCommands.ts
|
|
409
546
|
var EXIT_DELAY_MS = 500;
|
|
410
|
-
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
547
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks) {
|
|
411
548
|
return useCallback3(
|
|
412
549
|
async (input) => {
|
|
413
550
|
const parts = input.slice(1).split(/\s+/);
|
|
414
551
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
415
552
|
const args = parts.slice(1).join(" ");
|
|
416
553
|
const clearMessages = () => setMessages([]);
|
|
417
|
-
const result = await executeSlashCommand(
|
|
554
|
+
const result = await executeSlashCommand(
|
|
555
|
+
cmd,
|
|
556
|
+
args,
|
|
557
|
+
session,
|
|
558
|
+
addMessage,
|
|
559
|
+
clearMessages,
|
|
560
|
+
registry,
|
|
561
|
+
pluginCallbacks
|
|
562
|
+
);
|
|
418
563
|
if (result.pendingModelId) {
|
|
419
564
|
pendingModelChangeRef.current = result.pendingModelId;
|
|
420
565
|
setPendingModelId(result.pendingModelId);
|
|
@@ -424,7 +569,16 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
|
|
|
424
569
|
}
|
|
425
570
|
return result.handled;
|
|
426
571
|
},
|
|
427
|
-
[
|
|
572
|
+
[
|
|
573
|
+
session,
|
|
574
|
+
addMessage,
|
|
575
|
+
setMessages,
|
|
576
|
+
exit,
|
|
577
|
+
registry,
|
|
578
|
+
pendingModelChangeRef,
|
|
579
|
+
setPendingModelId,
|
|
580
|
+
pluginCallbacks
|
|
581
|
+
]
|
|
428
582
|
);
|
|
429
583
|
}
|
|
430
584
|
|
|
@@ -433,7 +587,7 @@ import { useCallback as useCallback4 } from "react";
|
|
|
433
587
|
|
|
434
588
|
// src/utils/tool-call-extractor.ts
|
|
435
589
|
var TOOL_ARG_MAX_LENGTH = 80;
|
|
436
|
-
var
|
|
590
|
+
var TAIL_KEEP2 = 30;
|
|
437
591
|
function extractToolCalls(history, startIndex) {
|
|
438
592
|
const lines = [];
|
|
439
593
|
for (let i = startIndex; i < history.length; i++) {
|
|
@@ -441,7 +595,7 @@ function extractToolCalls(history, startIndex) {
|
|
|
441
595
|
if (msg.role === "assistant" && msg.toolCalls) {
|
|
442
596
|
for (const tc of msg.toolCalls) {
|
|
443
597
|
const value = parseFirstArgValue(tc.function.arguments);
|
|
444
|
-
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0,
|
|
598
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_MAX_LENGTH - TAIL_KEEP2 - 3) + "..." + value.slice(-TAIL_KEEP2) : value;
|
|
445
599
|
lines.push(`${tc.function.name}(${truncated})`);
|
|
446
600
|
}
|
|
447
601
|
}
|
|
@@ -459,16 +613,60 @@ function parseFirstArgValue(argsJson) {
|
|
|
459
613
|
}
|
|
460
614
|
|
|
461
615
|
// src/utils/skill-prompt.ts
|
|
462
|
-
|
|
616
|
+
import { execSync } from "child_process";
|
|
617
|
+
function substituteVariables(content, args, context) {
|
|
618
|
+
const argParts = args ? args.split(/\s+/) : [];
|
|
619
|
+
let result = content;
|
|
620
|
+
result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
|
|
621
|
+
return argParts[Number(index)] ?? "";
|
|
622
|
+
});
|
|
623
|
+
result = result.replace(/\$ARGUMENTS/g, args);
|
|
624
|
+
result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
|
|
625
|
+
return argParts[Number(digit)] ?? "";
|
|
626
|
+
});
|
|
627
|
+
result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
|
|
628
|
+
result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
|
|
629
|
+
return result;
|
|
630
|
+
}
|
|
631
|
+
async function preprocessShellCommands(content) {
|
|
632
|
+
const shellPattern = /!`([^`]+)`/g;
|
|
633
|
+
if (!shellPattern.test(content)) {
|
|
634
|
+
return content;
|
|
635
|
+
}
|
|
636
|
+
shellPattern.lastIndex = 0;
|
|
637
|
+
let result = content;
|
|
638
|
+
let match;
|
|
639
|
+
const matches = [];
|
|
640
|
+
while ((match = shellPattern.exec(content)) !== null) {
|
|
641
|
+
matches.push({ full: match[0], command: match[1] });
|
|
642
|
+
}
|
|
643
|
+
for (const { full, command } of matches) {
|
|
644
|
+
let output = "";
|
|
645
|
+
try {
|
|
646
|
+
output = execSync(command, {
|
|
647
|
+
timeout: 5e3,
|
|
648
|
+
encoding: "utf-8",
|
|
649
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
650
|
+
}).trimEnd();
|
|
651
|
+
} catch {
|
|
652
|
+
output = "";
|
|
653
|
+
}
|
|
654
|
+
result = result.replace(full, output);
|
|
655
|
+
}
|
|
656
|
+
return result;
|
|
657
|
+
}
|
|
658
|
+
async function buildSkillPrompt(input, registry, context) {
|
|
463
659
|
const parts = input.slice(1).split(/\s+/);
|
|
464
660
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
465
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
661
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
466
662
|
if (!skillCmd) return null;
|
|
467
663
|
const args = parts.slice(1).join(" ").trim();
|
|
468
664
|
const userInstruction = args || skillCmd.description;
|
|
469
665
|
if (skillCmd.skillContent) {
|
|
666
|
+
let processed = await preprocessShellCommands(skillCmd.skillContent);
|
|
667
|
+
processed = substituteVariables(processed, args, context);
|
|
470
668
|
return `<skill name="${cmd}">
|
|
471
|
-
${
|
|
669
|
+
${processed}
|
|
472
670
|
</skill>
|
|
473
671
|
|
|
474
672
|
Execute the "${cmd}" skill: ${userInstruction}`;
|
|
@@ -481,12 +679,12 @@ function syncContextState(session, setter) {
|
|
|
481
679
|
const ctx = session.getContextState();
|
|
482
680
|
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
483
681
|
}
|
|
484
|
-
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
|
|
682
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
|
|
485
683
|
setIsThinking(true);
|
|
486
684
|
clearStreamingText();
|
|
487
685
|
const historyBefore = session.getHistory().length;
|
|
488
686
|
try {
|
|
489
|
-
const response = await session.run(prompt);
|
|
687
|
+
const response = await session.run(prompt, rawInput);
|
|
490
688
|
clearStreamingText();
|
|
491
689
|
const history = session.getHistory();
|
|
492
690
|
const toolLines = extractToolCalls(
|
|
@@ -494,7 +692,11 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
|
|
|
494
692
|
historyBefore
|
|
495
693
|
);
|
|
496
694
|
if (toolLines.length > 0) {
|
|
497
|
-
addMessage({
|
|
695
|
+
addMessage({
|
|
696
|
+
role: "tool",
|
|
697
|
+
content: toolLines.join("\n"),
|
|
698
|
+
toolName: `${toolLines.length} tools`
|
|
699
|
+
});
|
|
498
700
|
}
|
|
499
701
|
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
500
702
|
syncContextState(session, setContextState);
|
|
@@ -519,19 +721,48 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
519
721
|
syncContextState(session, setContextState);
|
|
520
722
|
return;
|
|
521
723
|
}
|
|
522
|
-
const prompt = buildSkillPrompt(input, registry);
|
|
724
|
+
const prompt = await buildSkillPrompt(input, registry);
|
|
523
725
|
if (!prompt) return;
|
|
524
|
-
|
|
726
|
+
const cmdName = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
727
|
+
const qualifiedName = registry.resolveQualifiedName(cmdName);
|
|
728
|
+
const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmdName.length)}` : input;
|
|
729
|
+
return runSessionPrompt(
|
|
730
|
+
prompt,
|
|
731
|
+
session,
|
|
732
|
+
addMessage,
|
|
733
|
+
clearStreamingText,
|
|
734
|
+
setIsThinking,
|
|
735
|
+
setContextState,
|
|
736
|
+
hookInput
|
|
737
|
+
);
|
|
525
738
|
}
|
|
526
739
|
addMessage({ role: "user", content: input });
|
|
527
|
-
return runSessionPrompt(
|
|
740
|
+
return runSessionPrompt(
|
|
741
|
+
input,
|
|
742
|
+
session,
|
|
743
|
+
addMessage,
|
|
744
|
+
clearStreamingText,
|
|
745
|
+
setIsThinking,
|
|
746
|
+
setContextState
|
|
747
|
+
);
|
|
528
748
|
},
|
|
529
|
-
[
|
|
749
|
+
[
|
|
750
|
+
session,
|
|
751
|
+
addMessage,
|
|
752
|
+
handleSlashCommand,
|
|
753
|
+
clearStreamingText,
|
|
754
|
+
setIsThinking,
|
|
755
|
+
setContextState,
|
|
756
|
+
registry
|
|
757
|
+
]
|
|
530
758
|
);
|
|
531
759
|
}
|
|
532
760
|
|
|
533
761
|
// src/ui/hooks/useCommandRegistry.ts
|
|
534
762
|
import { useRef as useRef2 } from "react";
|
|
763
|
+
import { homedir as homedir2 } from "os";
|
|
764
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
765
|
+
import { BundlePluginLoader } from "@robota-sdk/agent-sdk";
|
|
535
766
|
|
|
536
767
|
// src/commands/command-registry.ts
|
|
537
768
|
var CommandRegistry = class {
|
|
@@ -549,6 +780,14 @@ var CommandRegistry = class {
|
|
|
549
780
|
const lower = filter.toLowerCase();
|
|
550
781
|
return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
|
|
551
782
|
}
|
|
783
|
+
/** Resolve a short name to its fully qualified plugin:name form */
|
|
784
|
+
resolveQualifiedName(shortName) {
|
|
785
|
+
const matches = this.getCommands().filter(
|
|
786
|
+
(c) => c.source === "plugin" && c.name.includes(":") && c.name.endsWith(`:${shortName}`)
|
|
787
|
+
);
|
|
788
|
+
if (matches.length !== 1) return null;
|
|
789
|
+
return matches[0].name;
|
|
790
|
+
}
|
|
552
791
|
/** Get subcommands for a specific command */
|
|
553
792
|
getSubcommands(commandName) {
|
|
554
793
|
const lower = commandName.toLowerCase();
|
|
@@ -615,6 +854,23 @@ function createBuiltinCommands() {
|
|
|
615
854
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
616
855
|
{ name: "context", description: "Context window info", source: "builtin" },
|
|
617
856
|
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
857
|
+
{
|
|
858
|
+
name: "plugin",
|
|
859
|
+
description: "Manage plugins",
|
|
860
|
+
source: "builtin",
|
|
861
|
+
subcommands: [
|
|
862
|
+
{ name: "install", description: "Install a plugin (name@marketplace)", source: "builtin" },
|
|
863
|
+
{
|
|
864
|
+
name: "uninstall",
|
|
865
|
+
description: "Uninstall a plugin (name@marketplace)",
|
|
866
|
+
source: "builtin"
|
|
867
|
+
},
|
|
868
|
+
{ name: "enable", description: "Enable a plugin (name@marketplace)", source: "builtin" },
|
|
869
|
+
{ name: "disable", description: "Disable a plugin (name@marketplace)", source: "builtin" },
|
|
870
|
+
{ name: "marketplace", description: "Manage marketplace sources", source: "builtin" }
|
|
871
|
+
]
|
|
872
|
+
},
|
|
873
|
+
{ name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
|
|
618
874
|
{ name: "reset", description: "Delete settings and exit", source: "builtin" },
|
|
619
875
|
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
620
876
|
];
|
|
@@ -632,27 +888,52 @@ var BuiltinCommandSource = class {
|
|
|
632
888
|
|
|
633
889
|
// src/commands/skill-source.ts
|
|
634
890
|
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
635
|
-
import { join as join2 } from "path";
|
|
891
|
+
import { join as join2, basename } from "path";
|
|
636
892
|
import { homedir } from "os";
|
|
893
|
+
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
|
|
894
|
+
var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
|
|
895
|
+
function kebabToCamel(key) {
|
|
896
|
+
return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
897
|
+
}
|
|
637
898
|
function parseFrontmatter(content) {
|
|
638
899
|
const lines = content.split("\n");
|
|
639
900
|
if (lines[0]?.trim() !== "---") return null;
|
|
640
|
-
|
|
641
|
-
let description = "";
|
|
901
|
+
const result = {};
|
|
642
902
|
for (let i = 1; i < lines.length; i++) {
|
|
643
903
|
const line = lines[i];
|
|
644
904
|
if (line.trim() === "---") break;
|
|
645
|
-
const
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
905
|
+
const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
|
|
906
|
+
if (!match) continue;
|
|
907
|
+
const key = match[1];
|
|
908
|
+
const rawValue = match[2].trim();
|
|
909
|
+
const camelKey = kebabToCamel(key);
|
|
910
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
911
|
+
result[camelKey] = rawValue === "true";
|
|
912
|
+
} else if (LIST_KEYS.has(key)) {
|
|
913
|
+
result[camelKey] = rawValue.split(",").map((s) => s.trim());
|
|
914
|
+
} else {
|
|
915
|
+
result[camelKey] = rawValue;
|
|
653
916
|
}
|
|
654
917
|
}
|
|
655
|
-
return
|
|
918
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
919
|
+
}
|
|
920
|
+
function buildCommand(frontmatter, content, fallbackName) {
|
|
921
|
+
const cmd = {
|
|
922
|
+
name: frontmatter?.name ?? fallbackName,
|
|
923
|
+
description: frontmatter?.description ?? `Skill: ${fallbackName}`,
|
|
924
|
+
source: "skill",
|
|
925
|
+
skillContent: content
|
|
926
|
+
};
|
|
927
|
+
if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
|
|
928
|
+
if (frontmatter?.disableModelInvocation !== void 0)
|
|
929
|
+
cmd.disableModelInvocation = frontmatter.disableModelInvocation;
|
|
930
|
+
if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
|
|
931
|
+
if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
|
|
932
|
+
if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
|
|
933
|
+
if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
|
|
934
|
+
if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
|
|
935
|
+
if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
|
|
936
|
+
return cmd;
|
|
656
937
|
}
|
|
657
938
|
function scanSkillsDir(skillsDir) {
|
|
658
939
|
if (!existsSync2(skillsDir)) return [];
|
|
@@ -664,48 +945,251 @@ function scanSkillsDir(skillsDir) {
|
|
|
664
945
|
if (!existsSync2(skillFile)) continue;
|
|
665
946
|
const content = readFileSync2(skillFile, "utf-8");
|
|
666
947
|
const frontmatter = parseFrontmatter(content);
|
|
667
|
-
commands.push(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
948
|
+
commands.push(buildCommand(frontmatter, content, entry.name));
|
|
949
|
+
}
|
|
950
|
+
return commands;
|
|
951
|
+
}
|
|
952
|
+
function scanCommandsDir(commandsDir) {
|
|
953
|
+
if (!existsSync2(commandsDir)) return [];
|
|
954
|
+
const commands = [];
|
|
955
|
+
const entries = readdirSync(commandsDir, { withFileTypes: true });
|
|
956
|
+
for (const entry of entries) {
|
|
957
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
958
|
+
const filePath = join2(commandsDir, entry.name);
|
|
959
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
960
|
+
const frontmatter = parseFrontmatter(content);
|
|
961
|
+
const fallbackName = basename(entry.name, ".md");
|
|
962
|
+
commands.push(buildCommand(frontmatter, content, fallbackName));
|
|
673
963
|
}
|
|
674
964
|
return commands;
|
|
675
965
|
}
|
|
676
966
|
var SkillCommandSource = class {
|
|
677
967
|
name = "skill";
|
|
678
968
|
cwd;
|
|
969
|
+
home;
|
|
679
970
|
cachedCommands = null;
|
|
680
|
-
constructor(cwd) {
|
|
971
|
+
constructor(cwd, home) {
|
|
681
972
|
this.cwd = cwd;
|
|
973
|
+
this.home = home ?? homedir();
|
|
682
974
|
}
|
|
683
975
|
getCommands() {
|
|
684
976
|
if (this.cachedCommands) return this.cachedCommands;
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
977
|
+
const sources = [
|
|
978
|
+
scanSkillsDir(join2(this.cwd, ".claude", "skills")),
|
|
979
|
+
// 1. project .claude/skills
|
|
980
|
+
scanCommandsDir(join2(this.cwd, ".claude", "commands")),
|
|
981
|
+
// 2. project .claude/commands (legacy)
|
|
982
|
+
scanSkillsDir(join2(this.home, ".robota", "skills")),
|
|
983
|
+
// 3. user ~/.robota/skills
|
|
984
|
+
scanSkillsDir(join2(this.cwd, ".agents", "skills"))
|
|
985
|
+
// 4. project .agents/skills
|
|
986
|
+
];
|
|
987
|
+
const seen = /* @__PURE__ */ new Set();
|
|
988
|
+
const merged = [];
|
|
989
|
+
for (const commands of sources) {
|
|
990
|
+
for (const cmd of commands) {
|
|
991
|
+
if (!seen.has(cmd.name)) {
|
|
992
|
+
seen.add(cmd.name);
|
|
993
|
+
merged.push(cmd);
|
|
994
|
+
}
|
|
692
995
|
}
|
|
693
996
|
}
|
|
694
997
|
this.cachedCommands = merged;
|
|
695
998
|
return this.cachedCommands;
|
|
696
999
|
}
|
|
1000
|
+
/** Get skills that models can invoke (excludes disableModelInvocation: true) */
|
|
1001
|
+
getModelInvocableSkills() {
|
|
1002
|
+
return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
|
|
1003
|
+
}
|
|
1004
|
+
/** Get skills that users can invoke (excludes userInvocable: false) */
|
|
1005
|
+
getUserInvocableSkills() {
|
|
1006
|
+
return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
// src/commands/plugin-source.ts
|
|
1011
|
+
var PluginCommandSource = class {
|
|
1012
|
+
name = "plugin";
|
|
1013
|
+
plugins;
|
|
1014
|
+
constructor(plugins) {
|
|
1015
|
+
this.plugins = plugins;
|
|
1016
|
+
}
|
|
1017
|
+
getCommands() {
|
|
1018
|
+
const commands = [];
|
|
1019
|
+
for (const plugin of this.plugins) {
|
|
1020
|
+
for (const skill of plugin.skills) {
|
|
1021
|
+
const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
|
|
1022
|
+
commands.push({
|
|
1023
|
+
name: baseName,
|
|
1024
|
+
description: `(${plugin.manifest.name}) ${skill.description}`,
|
|
1025
|
+
source: "plugin",
|
|
1026
|
+
skillContent: skill.skillContent,
|
|
1027
|
+
pluginDir: plugin.pluginDir
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
for (const cmd of plugin.commands) {
|
|
1031
|
+
commands.push({
|
|
1032
|
+
name: cmd.name,
|
|
1033
|
+
description: cmd.description,
|
|
1034
|
+
source: "plugin",
|
|
1035
|
+
skillContent: cmd.skillContent,
|
|
1036
|
+
pluginDir: plugin.pluginDir
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return commands;
|
|
1041
|
+
}
|
|
697
1042
|
};
|
|
698
1043
|
|
|
699
1044
|
// src/ui/hooks/useCommandRegistry.ts
|
|
1045
|
+
function buildPluginEnv(plugin) {
|
|
1046
|
+
const dataDir = join3(dirname2(dirname2(plugin.pluginDir)), "data", plugin.manifest.name);
|
|
1047
|
+
return {
|
|
1048
|
+
CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
|
|
1049
|
+
CLAUDE_PLUGIN_PATH: plugin.pluginDir,
|
|
1050
|
+
CLAUDE_PLUGIN_DATA: dataDir
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
function mergePluginHooks(plugins) {
|
|
1054
|
+
const merged = {};
|
|
1055
|
+
for (const plugin of plugins) {
|
|
1056
|
+
const hooksObj = plugin.hooks;
|
|
1057
|
+
if (!hooksObj) continue;
|
|
1058
|
+
const pluginEnv = buildPluginEnv(plugin);
|
|
1059
|
+
const innerHooks = hooksObj.hooks ?? hooksObj;
|
|
1060
|
+
for (const [event, groups] of Object.entries(innerHooks)) {
|
|
1061
|
+
if (!Array.isArray(groups)) continue;
|
|
1062
|
+
if (!merged[event]) merged[event] = [];
|
|
1063
|
+
const resolved = groups.map((group) => {
|
|
1064
|
+
const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
|
|
1065
|
+
if (typeof resolved2 === "object" && resolved2 !== null) {
|
|
1066
|
+
resolved2.env = pluginEnv;
|
|
1067
|
+
}
|
|
1068
|
+
return resolved2;
|
|
1069
|
+
});
|
|
1070
|
+
merged[event].push(...resolved);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return merged;
|
|
1074
|
+
}
|
|
1075
|
+
function resolvePluginRoot(group, pluginDir) {
|
|
1076
|
+
if (typeof group !== "object" || group === null) return group;
|
|
1077
|
+
const obj = group;
|
|
1078
|
+
if (Array.isArray(obj.hooks)) {
|
|
1079
|
+
return {
|
|
1080
|
+
...obj,
|
|
1081
|
+
hooks: obj.hooks.map((h) => {
|
|
1082
|
+
if (typeof h !== "object" || h === null) return h;
|
|
1083
|
+
const hook = h;
|
|
1084
|
+
if (typeof hook.command === "string") {
|
|
1085
|
+
return {
|
|
1086
|
+
...hook,
|
|
1087
|
+
command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return hook;
|
|
1091
|
+
})
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
return group;
|
|
1095
|
+
}
|
|
700
1096
|
function useCommandRegistry(cwd) {
|
|
701
|
-
const
|
|
702
|
-
if (
|
|
1097
|
+
const resultRef = useRef2(null);
|
|
1098
|
+
if (resultRef.current === null) {
|
|
703
1099
|
const registry = new CommandRegistry();
|
|
704
1100
|
registry.addSource(new BuiltinCommandSource());
|
|
705
1101
|
registry.addSource(new SkillCommandSource(cwd));
|
|
706
|
-
|
|
1102
|
+
let pluginHooks = {};
|
|
1103
|
+
const pluginsDir = join3(homedir2(), ".robota", "plugins");
|
|
1104
|
+
const loader = new BundlePluginLoader(pluginsDir);
|
|
1105
|
+
try {
|
|
1106
|
+
const plugins = loader.loadPluginsSync();
|
|
1107
|
+
if (plugins.length > 0) {
|
|
1108
|
+
registry.addSource(new PluginCommandSource(plugins));
|
|
1109
|
+
pluginHooks = mergePluginHooks(plugins);
|
|
1110
|
+
}
|
|
1111
|
+
} catch {
|
|
1112
|
+
}
|
|
1113
|
+
resultRef.current = { registry, pluginHooks };
|
|
707
1114
|
}
|
|
708
|
-
return
|
|
1115
|
+
return resultRef.current;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// src/ui/hooks/usePluginCallbacks.ts
|
|
1119
|
+
import { useMemo } from "react";
|
|
1120
|
+
import { homedir as homedir3 } from "os";
|
|
1121
|
+
import { join as join4 } from "path";
|
|
1122
|
+
import {
|
|
1123
|
+
PluginSettingsStore,
|
|
1124
|
+
BundlePluginLoader as BundlePluginLoader2,
|
|
1125
|
+
BundlePluginInstaller,
|
|
1126
|
+
MarketplaceClient
|
|
1127
|
+
} from "@robota-sdk/agent-sdk";
|
|
1128
|
+
function usePluginCallbacks(cwd) {
|
|
1129
|
+
return useMemo(() => {
|
|
1130
|
+
const home = homedir3();
|
|
1131
|
+
const pluginsDir = join4(home, ".robota", "plugins");
|
|
1132
|
+
const userSettingsPath = join4(home, ".robota", "settings.json");
|
|
1133
|
+
const settingsStore = new PluginSettingsStore(userSettingsPath);
|
|
1134
|
+
const marketplace = new MarketplaceClient({ pluginsDir });
|
|
1135
|
+
const installer = new BundlePluginInstaller({
|
|
1136
|
+
pluginsDir,
|
|
1137
|
+
settingsStore,
|
|
1138
|
+
marketplaceClient: marketplace
|
|
1139
|
+
});
|
|
1140
|
+
const loader = new BundlePluginLoader2(pluginsDir);
|
|
1141
|
+
return {
|
|
1142
|
+
listInstalled: async () => {
|
|
1143
|
+
const plugins = await loader.loadAll();
|
|
1144
|
+
return plugins.map((p) => ({
|
|
1145
|
+
name: p.manifest.name,
|
|
1146
|
+
description: p.manifest.description,
|
|
1147
|
+
enabled: true
|
|
1148
|
+
}));
|
|
1149
|
+
},
|
|
1150
|
+
install: async (pluginId) => {
|
|
1151
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
1152
|
+
if (!name || !marketplaceName) {
|
|
1153
|
+
throw new Error("Plugin ID must be in format: name@marketplace");
|
|
1154
|
+
}
|
|
1155
|
+
await installer.install(name, marketplaceName);
|
|
1156
|
+
},
|
|
1157
|
+
uninstall: async (pluginId) => {
|
|
1158
|
+
await installer.uninstall(pluginId);
|
|
1159
|
+
},
|
|
1160
|
+
enable: async (pluginId) => {
|
|
1161
|
+
await installer.enable(pluginId);
|
|
1162
|
+
},
|
|
1163
|
+
disable: async (pluginId) => {
|
|
1164
|
+
await installer.disable(pluginId);
|
|
1165
|
+
},
|
|
1166
|
+
marketplaceAdd: async (source) => {
|
|
1167
|
+
if (source.includes("/") && !source.includes(":")) {
|
|
1168
|
+
return marketplace.addMarketplace({ type: "github", repo: source });
|
|
1169
|
+
} else {
|
|
1170
|
+
return marketplace.addMarketplace({ type: "git", url: source });
|
|
1171
|
+
}
|
|
1172
|
+
},
|
|
1173
|
+
marketplaceRemove: async (name) => {
|
|
1174
|
+
const installedFromMarketplace = installer.getPluginsByMarketplace(name);
|
|
1175
|
+
for (const record of installedFromMarketplace) {
|
|
1176
|
+
await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
|
|
1177
|
+
}
|
|
1178
|
+
marketplace.removeMarketplace(name);
|
|
1179
|
+
},
|
|
1180
|
+
marketplaceUpdate: async (name) => {
|
|
1181
|
+
marketplace.updateMarketplace(name);
|
|
1182
|
+
},
|
|
1183
|
+
marketplaceList: async () => {
|
|
1184
|
+
return marketplace.listMarketplaces().map((m) => ({
|
|
1185
|
+
name: m.name,
|
|
1186
|
+
type: m.source.type
|
|
1187
|
+
}));
|
|
1188
|
+
},
|
|
1189
|
+
reloadPlugins: async () => {
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
}, [cwd]);
|
|
709
1193
|
}
|
|
710
1194
|
|
|
711
1195
|
// src/ui/MessageList.tsx
|
|
@@ -742,13 +1226,39 @@ function RoleLabel({ role }) {
|
|
|
742
1226
|
" "
|
|
743
1227
|
] });
|
|
744
1228
|
case "tool":
|
|
745
|
-
return /* @__PURE__ */ jsxs(Text, { color: "
|
|
1229
|
+
return /* @__PURE__ */ jsxs(Text, { color: "white", bold: true, children: [
|
|
746
1230
|
"Tool:",
|
|
747
1231
|
" "
|
|
748
1232
|
] });
|
|
749
1233
|
}
|
|
750
1234
|
}
|
|
1235
|
+
function ToolMessage({ message }) {
|
|
1236
|
+
const lines = message.content.split("\n").filter((l) => l.trim());
|
|
1237
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1238
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
1239
|
+
/* @__PURE__ */ jsxs(Text, { color: "white", bold: true, children: [
|
|
1240
|
+
"Tool:",
|
|
1241
|
+
" "
|
|
1242
|
+
] }),
|
|
1243
|
+
message.toolName && /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
|
|
1244
|
+
"[",
|
|
1245
|
+
message.toolName,
|
|
1246
|
+
"]"
|
|
1247
|
+
] })
|
|
1248
|
+
] }),
|
|
1249
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1250
|
+
lines.map((line, i) => /* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
1251
|
+
" ",
|
|
1252
|
+
"\u2713",
|
|
1253
|
+
" ",
|
|
1254
|
+
line
|
|
1255
|
+
] }, i))
|
|
1256
|
+
] });
|
|
1257
|
+
}
|
|
751
1258
|
function MessageItem({ message }) {
|
|
1259
|
+
if (message.role === "tool") {
|
|
1260
|
+
return /* @__PURE__ */ jsx(ToolMessage, { message });
|
|
1261
|
+
}
|
|
752
1262
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
753
1263
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
754
1264
|
/* @__PURE__ */ jsx(RoleLabel, { role: message.role }),
|
|
@@ -828,7 +1338,7 @@ function StatusBar({
|
|
|
828
1338
|
}
|
|
829
1339
|
|
|
830
1340
|
// src/ui/InputArea.tsx
|
|
831
|
-
import
|
|
1341
|
+
import React4, { useState as useState5, useCallback as useCallback5, useMemo as useMemo2 } from "react";
|
|
832
1342
|
import { Box as Box4, Text as Text6, useInput as useInput2 } from "ink";
|
|
833
1343
|
|
|
834
1344
|
// src/ui/CjkTextInput.tsx
|
|
@@ -1007,14 +1517,14 @@ function parseSlashInput(value) {
|
|
|
1007
1517
|
function useAutocomplete(value, registry) {
|
|
1008
1518
|
const [selectedIndex, setSelectedIndex] = useState5(0);
|
|
1009
1519
|
const [dismissed, setDismissed] = useState5(false);
|
|
1010
|
-
const prevValueRef =
|
|
1520
|
+
const prevValueRef = React4.useRef(value);
|
|
1011
1521
|
if (prevValueRef.current !== value) {
|
|
1012
1522
|
prevValueRef.current = value;
|
|
1013
1523
|
if (dismissed) setDismissed(false);
|
|
1014
1524
|
}
|
|
1015
1525
|
const parsed = parseSlashInput(value);
|
|
1016
1526
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
1017
|
-
const filteredCommands =
|
|
1527
|
+
const filteredCommands = useMemo2(() => {
|
|
1018
1528
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
1019
1529
|
if (isSubcommandMode) {
|
|
1020
1530
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -1174,7 +1684,7 @@ function ConfirmPrompt({
|
|
|
1174
1684
|
}
|
|
1175
1685
|
|
|
1176
1686
|
// src/ui/PermissionPrompt.tsx
|
|
1177
|
-
import
|
|
1687
|
+
import React6 from "react";
|
|
1178
1688
|
import { Box as Box6, Text as Text8, useInput as useInput4 } from "ink";
|
|
1179
1689
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1180
1690
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
@@ -1184,15 +1694,15 @@ function formatArgs(args) {
|
|
|
1184
1694
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
1185
1695
|
}
|
|
1186
1696
|
function PermissionPrompt({ request }) {
|
|
1187
|
-
const [selected, setSelected] =
|
|
1188
|
-
const resolvedRef =
|
|
1189
|
-
const prevRequestRef =
|
|
1697
|
+
const [selected, setSelected] = React6.useState(0);
|
|
1698
|
+
const resolvedRef = React6.useRef(false);
|
|
1699
|
+
const prevRequestRef = React6.useRef(request);
|
|
1190
1700
|
if (prevRequestRef.current !== request) {
|
|
1191
1701
|
prevRequestRef.current = request;
|
|
1192
1702
|
resolvedRef.current = false;
|
|
1193
1703
|
setSelected(0);
|
|
1194
1704
|
}
|
|
1195
|
-
const doResolve =
|
|
1705
|
+
const doResolve = React6.useCallback(
|
|
1196
1706
|
(index) => {
|
|
1197
1707
|
if (resolvedRef.current) return;
|
|
1198
1708
|
resolvedRef.current = true;
|
|
@@ -1240,6 +1750,12 @@ function PermissionPrompt({ request }) {
|
|
|
1240
1750
|
// src/ui/StreamingIndicator.tsx
|
|
1241
1751
|
import { Box as Box7, Text as Text9 } from "ink";
|
|
1242
1752
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1753
|
+
function getToolStyle(t) {
|
|
1754
|
+
if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
|
|
1755
|
+
if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
|
|
1756
|
+
if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
|
|
1757
|
+
return { color: "green", icon: "\u2713", strikethrough: false };
|
|
1758
|
+
}
|
|
1243
1759
|
function StreamingIndicator({ text, activeTools }) {
|
|
1244
1760
|
const hasTools = activeTools.length > 0;
|
|
1245
1761
|
const hasText = text.length > 0;
|
|
@@ -1248,17 +1764,20 @@ function StreamingIndicator({ text, activeTools }) {
|
|
|
1248
1764
|
}
|
|
1249
1765
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1250
1766
|
hasTools && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
1251
|
-
/* @__PURE__ */ jsx9(Text9, { color: "
|
|
1767
|
+
/* @__PURE__ */ jsx9(Text9, { color: "white", bold: true, children: "Tools:" }),
|
|
1252
1768
|
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
1253
|
-
activeTools.map((t, i) =>
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1769
|
+
activeTools.map((t, i) => {
|
|
1770
|
+
const { color, icon, strikethrough } = getToolStyle(t);
|
|
1771
|
+
return /* @__PURE__ */ jsxs7(Text9, { color, strikethrough, children: [
|
|
1772
|
+
" ",
|
|
1773
|
+
icon,
|
|
1774
|
+
" ",
|
|
1775
|
+
t.toolName,
|
|
1776
|
+
"(",
|
|
1777
|
+
t.firstArg,
|
|
1778
|
+
")"
|
|
1779
|
+
] }, `${t.toolName}-${i}`);
|
|
1780
|
+
})
|
|
1262
1781
|
] }),
|
|
1263
1782
|
hasText && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
1264
1783
|
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "Robota:" }),
|
|
@@ -1271,9 +1790,35 @@ function StreamingIndicator({ text, activeTools }) {
|
|
|
1271
1790
|
// src/ui/App.tsx
|
|
1272
1791
|
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1273
1792
|
var EXIT_DELAY_MS2 = 500;
|
|
1793
|
+
function mergeHooksIntoConfig(configHooks, pluginHooks) {
|
|
1794
|
+
const pluginKeys = Object.keys(pluginHooks);
|
|
1795
|
+
if (pluginKeys.length === 0) return configHooks;
|
|
1796
|
+
const merged = {};
|
|
1797
|
+
for (const [event, groups] of Object.entries(pluginHooks)) {
|
|
1798
|
+
merged[event] = [...groups];
|
|
1799
|
+
}
|
|
1800
|
+
if (configHooks) {
|
|
1801
|
+
for (const [event, groups] of Object.entries(configHooks)) {
|
|
1802
|
+
if (!Array.isArray(groups)) continue;
|
|
1803
|
+
if (!merged[event]) merged[event] = [];
|
|
1804
|
+
merged[event].push(...groups);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return merged;
|
|
1808
|
+
}
|
|
1274
1809
|
function App(props) {
|
|
1275
1810
|
const { exit } = useApp();
|
|
1276
|
-
const {
|
|
1811
|
+
const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1812
|
+
const configWithPluginHooks = {
|
|
1813
|
+
...props.config,
|
|
1814
|
+
hooks: mergeHooksIntoConfig(
|
|
1815
|
+
props.config.hooks,
|
|
1816
|
+
pluginHooks
|
|
1817
|
+
)
|
|
1818
|
+
};
|
|
1819
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
|
|
1820
|
+
{ ...props, config: configWithPluginHooks }
|
|
1821
|
+
);
|
|
1277
1822
|
const { messages, setMessages, addMessage } = useMessages();
|
|
1278
1823
|
const [isThinking, setIsThinking] = useState7(false);
|
|
1279
1824
|
const initialCtx = session.getContextState();
|
|
@@ -1282,9 +1827,9 @@ function App(props) {
|
|
|
1282
1827
|
usedTokens: initialCtx.usedTokens,
|
|
1283
1828
|
maxTokens: initialCtx.maxTokens
|
|
1284
1829
|
});
|
|
1285
|
-
const registry = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1286
1830
|
const pendingModelChangeRef = useRef5(null);
|
|
1287
1831
|
const [pendingModelId, setPendingModelId] = useState7(null);
|
|
1832
|
+
const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
|
|
1288
1833
|
const handleSlashCommand = useSlashCommands(
|
|
1289
1834
|
session,
|
|
1290
1835
|
addMessage,
|
|
@@ -1292,7 +1837,8 @@ function App(props) {
|
|
|
1292
1837
|
exit,
|
|
1293
1838
|
registry,
|
|
1294
1839
|
pendingModelChangeRef,
|
|
1295
|
-
setPendingModelId
|
|
1840
|
+
setPendingModelId,
|
|
1841
|
+
pluginCallbacks
|
|
1296
1842
|
);
|
|
1297
1843
|
const handleSubmit = useSubmitHandler(
|
|
1298
1844
|
session,
|
|
@@ -1407,23 +1953,24 @@ function renderApp(options) {
|
|
|
1407
1953
|
}
|
|
1408
1954
|
|
|
1409
1955
|
// src/cli.ts
|
|
1410
|
-
function
|
|
1411
|
-
if (!existsSync3(filePath)) return
|
|
1956
|
+
function checkSettingsFile(filePath) {
|
|
1957
|
+
if (!existsSync3(filePath)) return "missing";
|
|
1412
1958
|
try {
|
|
1413
1959
|
const raw = readFileSync3(filePath, "utf8").trim();
|
|
1414
|
-
if (raw.length === 0) return
|
|
1960
|
+
if (raw.length === 0) return "incomplete";
|
|
1415
1961
|
const parsed = JSON.parse(raw);
|
|
1416
1962
|
const provider = parsed.provider;
|
|
1417
|
-
|
|
1963
|
+
if (!provider?.apiKey) return "incomplete";
|
|
1964
|
+
return "valid";
|
|
1418
1965
|
} catch {
|
|
1419
|
-
return
|
|
1966
|
+
return "corrupt";
|
|
1420
1967
|
}
|
|
1421
1968
|
}
|
|
1422
1969
|
function readVersion() {
|
|
1423
1970
|
try {
|
|
1424
1971
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1425
|
-
const dir =
|
|
1426
|
-
const candidates = [
|
|
1972
|
+
const dir = dirname3(thisFile);
|
|
1973
|
+
const candidates = [join5(dir, "..", "..", "package.json"), join5(dir, "..", "package.json")];
|
|
1427
1974
|
for (const pkgPath of candidates) {
|
|
1428
1975
|
try {
|
|
1429
1976
|
const raw = readFileSync3(pkgPath, "utf-8");
|
|
@@ -1476,14 +2023,36 @@ function promptInput(label, masked = false) {
|
|
|
1476
2023
|
}
|
|
1477
2024
|
async function ensureConfig(cwd) {
|
|
1478
2025
|
const userPath = getUserSettingsPath();
|
|
1479
|
-
const projectPath =
|
|
1480
|
-
const localPath =
|
|
1481
|
-
|
|
2026
|
+
const projectPath = join5(cwd, ".robota", "settings.json");
|
|
2027
|
+
const localPath = join5(cwd, ".robota", "settings.local.json");
|
|
2028
|
+
const paths = [userPath, projectPath, localPath];
|
|
2029
|
+
const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
|
|
2030
|
+
if (checks.some((c) => c.status === "valid")) {
|
|
1482
2031
|
return;
|
|
1483
2032
|
}
|
|
2033
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
2034
|
+
const incomplete = checks.filter((c) => c.status === "incomplete");
|
|
1484
2035
|
process.stdout.write("\n");
|
|
1485
|
-
|
|
1486
|
-
|
|
2036
|
+
if (corrupt.length > 0) {
|
|
2037
|
+
for (const c of corrupt) {
|
|
2038
|
+
process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
|
|
2039
|
+
`);
|
|
2040
|
+
}
|
|
2041
|
+
process.stdout.write("\n");
|
|
2042
|
+
}
|
|
2043
|
+
if (incomplete.length > 0) {
|
|
2044
|
+
for (const c of incomplete) {
|
|
2045
|
+
process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
|
|
2046
|
+
`);
|
|
2047
|
+
}
|
|
2048
|
+
process.stdout.write("\n");
|
|
2049
|
+
}
|
|
2050
|
+
if (corrupt.length === 0 && incomplete.length === 0) {
|
|
2051
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
2052
|
+
process.stdout.write(" No configuration found. Let's set up.\n");
|
|
2053
|
+
} else {
|
|
2054
|
+
process.stdout.write(" Reconfiguring...\n");
|
|
2055
|
+
}
|
|
1487
2056
|
process.stdout.write("\n");
|
|
1488
2057
|
const apiKey = await promptInput(" Anthropic API key: ", true);
|
|
1489
2058
|
if (!apiKey) {
|
|
@@ -1491,7 +2060,7 @@ async function ensureConfig(cwd) {
|
|
|
1491
2060
|
process.exit(1);
|
|
1492
2061
|
}
|
|
1493
2062
|
const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
|
|
1494
|
-
const settingsDir =
|
|
2063
|
+
const settingsDir = dirname3(userPath);
|
|
1495
2064
|
mkdirSync2(settingsDir, { recursive: true });
|
|
1496
2065
|
const settings = {
|
|
1497
2066
|
provider: {
|