@robota-sdk/agent-cli 3.0.0-beta.23 → 3.0.0-beta.25
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 +626 -104
- package/dist/node/bin.js +1 -1
- package/dist/node/{chunk-EFSOR6D7.js → chunk-GJ3LG37H.js} +601 -74
- package/dist/node/index.cjs +631 -109
- package/dist/node/index.js +1 -1
- package/package.json +3 -3
package/dist/node/bin.cjs
CHANGED
|
@@ -25,10 +25,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
27
|
var import_node_fs3 = require("fs");
|
|
28
|
-
var
|
|
28
|
+
var import_node_path5 = require("path");
|
|
29
29
|
var import_node_url = require("url");
|
|
30
|
-
var
|
|
31
|
-
var
|
|
30
|
+
var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
|
|
31
|
+
var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
|
|
32
32
|
|
|
33
33
|
// src/utils/cli-args.ts
|
|
34
34
|
var import_node_util = require("util");
|
|
@@ -166,7 +166,7 @@ var PrintTerminal = class {
|
|
|
166
166
|
var import_ink11 = require("ink");
|
|
167
167
|
|
|
168
168
|
// src/ui/App.tsx
|
|
169
|
-
var
|
|
169
|
+
var import_react12 = require("react");
|
|
170
170
|
var import_ink10 = require("ink");
|
|
171
171
|
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
172
172
|
|
|
@@ -386,7 +386,122 @@ function handleReset(addMessage) {
|
|
|
386
386
|
}
|
|
387
387
|
return { handled: true, exitRequested: true };
|
|
388
388
|
}
|
|
389
|
-
async function
|
|
389
|
+
async function handlePluginCommand(args, addMessage, callbacks) {
|
|
390
|
+
const parts = args.trim().split(/\s+/);
|
|
391
|
+
const subcommand = parts[0] ?? "";
|
|
392
|
+
const subArgs = parts.slice(1).join(" ").trim();
|
|
393
|
+
try {
|
|
394
|
+
switch (subcommand) {
|
|
395
|
+
case "":
|
|
396
|
+
case void 0: {
|
|
397
|
+
const plugins = await callbacks.listInstalled();
|
|
398
|
+
if (plugins.length === 0) {
|
|
399
|
+
addMessage({ role: "system", content: "No plugins installed." });
|
|
400
|
+
} else {
|
|
401
|
+
const lines = plugins.map(
|
|
402
|
+
(p) => ` ${p.name} \u2014 ${p.description} [${p.enabled ? "enabled" : "disabled"}]`
|
|
403
|
+
);
|
|
404
|
+
addMessage({ role: "system", content: `Installed plugins:
|
|
405
|
+
${lines.join("\n")}` });
|
|
406
|
+
}
|
|
407
|
+
return { handled: true };
|
|
408
|
+
}
|
|
409
|
+
case "install": {
|
|
410
|
+
if (!subArgs) {
|
|
411
|
+
addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
|
|
412
|
+
return { handled: true };
|
|
413
|
+
}
|
|
414
|
+
await callbacks.install(subArgs);
|
|
415
|
+
addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
|
|
416
|
+
return { handled: true };
|
|
417
|
+
}
|
|
418
|
+
case "uninstall": {
|
|
419
|
+
if (!subArgs) {
|
|
420
|
+
addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
|
|
421
|
+
return { handled: true };
|
|
422
|
+
}
|
|
423
|
+
await callbacks.uninstall(subArgs);
|
|
424
|
+
addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
|
|
425
|
+
return { handled: true };
|
|
426
|
+
}
|
|
427
|
+
case "enable": {
|
|
428
|
+
if (!subArgs) {
|
|
429
|
+
addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
|
|
430
|
+
return { handled: true };
|
|
431
|
+
}
|
|
432
|
+
await callbacks.enable(subArgs);
|
|
433
|
+
addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
|
|
434
|
+
return { handled: true };
|
|
435
|
+
}
|
|
436
|
+
case "disable": {
|
|
437
|
+
if (!subArgs) {
|
|
438
|
+
addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
|
|
439
|
+
return { handled: true };
|
|
440
|
+
}
|
|
441
|
+
await callbacks.disable(subArgs);
|
|
442
|
+
addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
|
|
443
|
+
return { handled: true };
|
|
444
|
+
}
|
|
445
|
+
case "marketplace": {
|
|
446
|
+
const mpParts = subArgs.split(/\s+/);
|
|
447
|
+
const mpSubcommand = mpParts[0] ?? "";
|
|
448
|
+
const mpArgs = mpParts.slice(1).join(" ").trim();
|
|
449
|
+
if (mpSubcommand === "add" && mpArgs) {
|
|
450
|
+
const registeredName = await callbacks.marketplaceAdd(mpArgs);
|
|
451
|
+
addMessage({
|
|
452
|
+
role: "system",
|
|
453
|
+
content: `Added marketplace: "${registeredName}" (from ${mpArgs})
|
|
454
|
+
Install plugins with: /plugin install <name>@${registeredName}`
|
|
455
|
+
});
|
|
456
|
+
return { handled: true };
|
|
457
|
+
} else if (mpSubcommand === "remove" && mpArgs) {
|
|
458
|
+
await callbacks.marketplaceRemove(mpArgs);
|
|
459
|
+
addMessage({
|
|
460
|
+
role: "system",
|
|
461
|
+
content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
|
|
462
|
+
});
|
|
463
|
+
return { handled: true };
|
|
464
|
+
} else if (mpSubcommand === "update" && mpArgs) {
|
|
465
|
+
await callbacks.marketplaceUpdate(mpArgs);
|
|
466
|
+
addMessage({
|
|
467
|
+
role: "system",
|
|
468
|
+
content: `Updated marketplace "${mpArgs}".`
|
|
469
|
+
});
|
|
470
|
+
return { handled: true };
|
|
471
|
+
} else if (mpSubcommand === "list") {
|
|
472
|
+
const sources = await callbacks.marketplaceList();
|
|
473
|
+
if (sources.length === 0) {
|
|
474
|
+
addMessage({ role: "system", content: "No marketplace sources configured." });
|
|
475
|
+
} else {
|
|
476
|
+
const lines = sources.map((s) => ` ${s.name} (${s.type})`);
|
|
477
|
+
addMessage({ role: "system", content: `Marketplace sources:
|
|
478
|
+
${lines.join("\n")}` });
|
|
479
|
+
}
|
|
480
|
+
return { handled: true };
|
|
481
|
+
} else {
|
|
482
|
+
addMessage({
|
|
483
|
+
role: "system",
|
|
484
|
+
content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
|
|
485
|
+
});
|
|
486
|
+
return { handled: true };
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
default:
|
|
490
|
+
addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
|
|
491
|
+
return { handled: true };
|
|
492
|
+
}
|
|
493
|
+
} catch (error) {
|
|
494
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
495
|
+
addMessage({ role: "system", content: `Plugin error: ${message}` });
|
|
496
|
+
return { handled: true };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function handleReloadPlugins(addMessage, callbacks) {
|
|
500
|
+
await callbacks.reloadPlugins();
|
|
501
|
+
addMessage({ role: "system", content: "Plugins reload complete." });
|
|
502
|
+
return { handled: true };
|
|
503
|
+
}
|
|
504
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
|
|
390
505
|
switch (cmd) {
|
|
391
506
|
case "help":
|
|
392
507
|
return handleHelp(addMessage);
|
|
@@ -410,10 +525,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
410
525
|
return handleReset(addMessage);
|
|
411
526
|
case "exit":
|
|
412
527
|
return { handled: true, exitRequested: true };
|
|
528
|
+
case "plugin":
|
|
529
|
+
if (pluginCallbacks) {
|
|
530
|
+
return handlePluginCommand(args, addMessage, pluginCallbacks);
|
|
531
|
+
}
|
|
532
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
533
|
+
return { handled: true };
|
|
534
|
+
case "reload-plugins":
|
|
535
|
+
if (pluginCallbacks) {
|
|
536
|
+
return handleReloadPlugins(addMessage, pluginCallbacks);
|
|
537
|
+
}
|
|
538
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
539
|
+
return { handled: true };
|
|
413
540
|
default: {
|
|
414
|
-
const
|
|
415
|
-
if (
|
|
416
|
-
addMessage({ role: "system", content: `Invoking
|
|
541
|
+
const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
542
|
+
if (dynamicCmd) {
|
|
543
|
+
addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
|
|
417
544
|
return { handled: false };
|
|
418
545
|
}
|
|
419
546
|
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
@@ -424,14 +551,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
424
551
|
|
|
425
552
|
// src/ui/hooks/useSlashCommands.ts
|
|
426
553
|
var EXIT_DELAY_MS = 500;
|
|
427
|
-
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
554
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks) {
|
|
428
555
|
return (0, import_react3.useCallback)(
|
|
429
556
|
async (input) => {
|
|
430
557
|
const parts = input.slice(1).split(/\s+/);
|
|
431
558
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
432
559
|
const args = parts.slice(1).join(" ");
|
|
433
560
|
const clearMessages = () => setMessages([]);
|
|
434
|
-
const result = await executeSlashCommand(
|
|
561
|
+
const result = await executeSlashCommand(
|
|
562
|
+
cmd,
|
|
563
|
+
args,
|
|
564
|
+
session,
|
|
565
|
+
addMessage,
|
|
566
|
+
clearMessages,
|
|
567
|
+
registry,
|
|
568
|
+
pluginCallbacks
|
|
569
|
+
);
|
|
435
570
|
if (result.pendingModelId) {
|
|
436
571
|
pendingModelChangeRef.current = result.pendingModelId;
|
|
437
572
|
setPendingModelId(result.pendingModelId);
|
|
@@ -441,7 +576,16 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
|
|
|
441
576
|
}
|
|
442
577
|
return result.handled;
|
|
443
578
|
},
|
|
444
|
-
[
|
|
579
|
+
[
|
|
580
|
+
session,
|
|
581
|
+
addMessage,
|
|
582
|
+
setMessages,
|
|
583
|
+
exit,
|
|
584
|
+
registry,
|
|
585
|
+
pendingModelChangeRef,
|
|
586
|
+
setPendingModelId,
|
|
587
|
+
pluginCallbacks
|
|
588
|
+
]
|
|
445
589
|
);
|
|
446
590
|
}
|
|
447
591
|
|
|
@@ -476,16 +620,60 @@ function parseFirstArgValue(argsJson) {
|
|
|
476
620
|
}
|
|
477
621
|
|
|
478
622
|
// src/utils/skill-prompt.ts
|
|
479
|
-
|
|
623
|
+
var import_node_child_process = require("child_process");
|
|
624
|
+
function substituteVariables(content, args, context) {
|
|
625
|
+
const argParts = args ? args.split(/\s+/) : [];
|
|
626
|
+
let result = content;
|
|
627
|
+
result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
|
|
628
|
+
return argParts[Number(index)] ?? "";
|
|
629
|
+
});
|
|
630
|
+
result = result.replace(/\$ARGUMENTS/g, args);
|
|
631
|
+
result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
|
|
632
|
+
return argParts[Number(digit)] ?? "";
|
|
633
|
+
});
|
|
634
|
+
result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
|
|
635
|
+
result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
async function preprocessShellCommands(content) {
|
|
639
|
+
const shellPattern = /!`([^`]+)`/g;
|
|
640
|
+
if (!shellPattern.test(content)) {
|
|
641
|
+
return content;
|
|
642
|
+
}
|
|
643
|
+
shellPattern.lastIndex = 0;
|
|
644
|
+
let result = content;
|
|
645
|
+
let match;
|
|
646
|
+
const matches = [];
|
|
647
|
+
while ((match = shellPattern.exec(content)) !== null) {
|
|
648
|
+
matches.push({ full: match[0], command: match[1] });
|
|
649
|
+
}
|
|
650
|
+
for (const { full, command } of matches) {
|
|
651
|
+
let output = "";
|
|
652
|
+
try {
|
|
653
|
+
output = (0, import_node_child_process.execSync)(command, {
|
|
654
|
+
timeout: 5e3,
|
|
655
|
+
encoding: "utf-8",
|
|
656
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
657
|
+
}).trimEnd();
|
|
658
|
+
} catch {
|
|
659
|
+
output = "";
|
|
660
|
+
}
|
|
661
|
+
result = result.replace(full, output);
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
async function buildSkillPrompt(input, registry, context) {
|
|
480
666
|
const parts = input.slice(1).split(/\s+/);
|
|
481
667
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
482
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
668
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
483
669
|
if (!skillCmd) return null;
|
|
484
670
|
const args = parts.slice(1).join(" ").trim();
|
|
485
671
|
const userInstruction = args || skillCmd.description;
|
|
486
672
|
if (skillCmd.skillContent) {
|
|
673
|
+
let processed = await preprocessShellCommands(skillCmd.skillContent);
|
|
674
|
+
processed = substituteVariables(processed, args, context);
|
|
487
675
|
return `<skill name="${cmd}">
|
|
488
|
-
${
|
|
676
|
+
${processed}
|
|
489
677
|
</skill>
|
|
490
678
|
|
|
491
679
|
Execute the "${cmd}" skill: ${userInstruction}`;
|
|
@@ -498,12 +686,12 @@ function syncContextState(session, setter) {
|
|
|
498
686
|
const ctx = session.getContextState();
|
|
499
687
|
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
500
688
|
}
|
|
501
|
-
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
|
|
689
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
|
|
502
690
|
setIsThinking(true);
|
|
503
691
|
clearStreamingText();
|
|
504
692
|
const historyBefore = session.getHistory().length;
|
|
505
693
|
try {
|
|
506
|
-
const response = await session.run(prompt);
|
|
694
|
+
const response = await session.run(prompt, rawInput);
|
|
507
695
|
clearStreamingText();
|
|
508
696
|
const history = session.getHistory();
|
|
509
697
|
const toolLines = extractToolCalls(
|
|
@@ -511,7 +699,11 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
|
|
|
511
699
|
historyBefore
|
|
512
700
|
);
|
|
513
701
|
if (toolLines.length > 0) {
|
|
514
|
-
addMessage({
|
|
702
|
+
addMessage({
|
|
703
|
+
role: "tool",
|
|
704
|
+
content: toolLines.join("\n"),
|
|
705
|
+
toolName: `${toolLines.length} tools`
|
|
706
|
+
});
|
|
515
707
|
}
|
|
516
708
|
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
517
709
|
syncContextState(session, setContextState);
|
|
@@ -536,19 +728,45 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
536
728
|
syncContextState(session, setContextState);
|
|
537
729
|
return;
|
|
538
730
|
}
|
|
539
|
-
const prompt = buildSkillPrompt(input, registry);
|
|
731
|
+
const prompt = await buildSkillPrompt(input, registry);
|
|
540
732
|
if (!prompt) return;
|
|
541
|
-
return runSessionPrompt(
|
|
733
|
+
return runSessionPrompt(
|
|
734
|
+
prompt,
|
|
735
|
+
session,
|
|
736
|
+
addMessage,
|
|
737
|
+
clearStreamingText,
|
|
738
|
+
setIsThinking,
|
|
739
|
+
setContextState,
|
|
740
|
+
input
|
|
741
|
+
);
|
|
542
742
|
}
|
|
543
743
|
addMessage({ role: "user", content: input });
|
|
544
|
-
return runSessionPrompt(
|
|
744
|
+
return runSessionPrompt(
|
|
745
|
+
input,
|
|
746
|
+
session,
|
|
747
|
+
addMessage,
|
|
748
|
+
clearStreamingText,
|
|
749
|
+
setIsThinking,
|
|
750
|
+
setContextState
|
|
751
|
+
);
|
|
545
752
|
},
|
|
546
|
-
[
|
|
753
|
+
[
|
|
754
|
+
session,
|
|
755
|
+
addMessage,
|
|
756
|
+
handleSlashCommand,
|
|
757
|
+
clearStreamingText,
|
|
758
|
+
setIsThinking,
|
|
759
|
+
setContextState,
|
|
760
|
+
registry
|
|
761
|
+
]
|
|
547
762
|
);
|
|
548
763
|
}
|
|
549
764
|
|
|
550
765
|
// src/ui/hooks/useCommandRegistry.ts
|
|
551
766
|
var import_react5 = require("react");
|
|
767
|
+
var import_node_os2 = require("os");
|
|
768
|
+
var import_node_path3 = require("path");
|
|
769
|
+
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
552
770
|
|
|
553
771
|
// src/commands/command-registry.ts
|
|
554
772
|
var CommandRegistry = class {
|
|
@@ -632,6 +850,23 @@ function createBuiltinCommands() {
|
|
|
632
850
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
633
851
|
{ name: "context", description: "Context window info", source: "builtin" },
|
|
634
852
|
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
853
|
+
{
|
|
854
|
+
name: "plugin",
|
|
855
|
+
description: "Manage plugins",
|
|
856
|
+
source: "builtin",
|
|
857
|
+
subcommands: [
|
|
858
|
+
{ name: "install", description: "Install a plugin (name@marketplace)", source: "builtin" },
|
|
859
|
+
{
|
|
860
|
+
name: "uninstall",
|
|
861
|
+
description: "Uninstall a plugin (name@marketplace)",
|
|
862
|
+
source: "builtin"
|
|
863
|
+
},
|
|
864
|
+
{ name: "enable", description: "Enable a plugin (name@marketplace)", source: "builtin" },
|
|
865
|
+
{ name: "disable", description: "Disable a plugin (name@marketplace)", source: "builtin" },
|
|
866
|
+
{ name: "marketplace", description: "Manage marketplace sources", source: "builtin" }
|
|
867
|
+
]
|
|
868
|
+
},
|
|
869
|
+
{ name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
|
|
635
870
|
{ name: "reset", description: "Delete settings and exit", source: "builtin" },
|
|
636
871
|
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
637
872
|
];
|
|
@@ -651,25 +886,50 @@ var BuiltinCommandSource = class {
|
|
|
651
886
|
var import_node_fs2 = require("fs");
|
|
652
887
|
var import_node_path2 = require("path");
|
|
653
888
|
var import_node_os = require("os");
|
|
889
|
+
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
|
|
890
|
+
var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
|
|
891
|
+
function kebabToCamel(key) {
|
|
892
|
+
return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
893
|
+
}
|
|
654
894
|
function parseFrontmatter(content) {
|
|
655
895
|
const lines = content.split("\n");
|
|
656
896
|
if (lines[0]?.trim() !== "---") return null;
|
|
657
|
-
|
|
658
|
-
let description = "";
|
|
897
|
+
const result = {};
|
|
659
898
|
for (let i = 1; i < lines.length; i++) {
|
|
660
899
|
const line = lines[i];
|
|
661
900
|
if (line.trim() === "---") break;
|
|
662
|
-
const
|
|
663
|
-
if (
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
901
|
+
const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
|
|
902
|
+
if (!match) continue;
|
|
903
|
+
const key = match[1];
|
|
904
|
+
const rawValue = match[2].trim();
|
|
905
|
+
const camelKey = kebabToCamel(key);
|
|
906
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
907
|
+
result[camelKey] = rawValue === "true";
|
|
908
|
+
} else if (LIST_KEYS.has(key)) {
|
|
909
|
+
result[camelKey] = rawValue.split(",").map((s) => s.trim());
|
|
910
|
+
} else {
|
|
911
|
+
result[camelKey] = rawValue;
|
|
670
912
|
}
|
|
671
913
|
}
|
|
672
|
-
return
|
|
914
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
915
|
+
}
|
|
916
|
+
function buildCommand(frontmatter, content, fallbackName) {
|
|
917
|
+
const cmd = {
|
|
918
|
+
name: frontmatter?.name ?? fallbackName,
|
|
919
|
+
description: frontmatter?.description ?? `Skill: ${fallbackName}`,
|
|
920
|
+
source: "skill",
|
|
921
|
+
skillContent: content
|
|
922
|
+
};
|
|
923
|
+
if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
|
|
924
|
+
if (frontmatter?.disableModelInvocation !== void 0)
|
|
925
|
+
cmd.disableModelInvocation = frontmatter.disableModelInvocation;
|
|
926
|
+
if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
|
|
927
|
+
if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
|
|
928
|
+
if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
|
|
929
|
+
if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
|
|
930
|
+
if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
|
|
931
|
+
if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
|
|
932
|
+
return cmd;
|
|
673
933
|
}
|
|
674
934
|
function scanSkillsDir(skillsDir) {
|
|
675
935
|
if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
|
|
@@ -681,48 +941,246 @@ function scanSkillsDir(skillsDir) {
|
|
|
681
941
|
if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
|
|
682
942
|
const content = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
|
|
683
943
|
const frontmatter = parseFrontmatter(content);
|
|
684
|
-
commands.push(
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
944
|
+
commands.push(buildCommand(frontmatter, content, entry.name));
|
|
945
|
+
}
|
|
946
|
+
return commands;
|
|
947
|
+
}
|
|
948
|
+
function scanCommandsDir(commandsDir) {
|
|
949
|
+
if (!(0, import_node_fs2.existsSync)(commandsDir)) return [];
|
|
950
|
+
const commands = [];
|
|
951
|
+
const entries = (0, import_node_fs2.readdirSync)(commandsDir, { withFileTypes: true });
|
|
952
|
+
for (const entry of entries) {
|
|
953
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
954
|
+
const filePath = (0, import_node_path2.join)(commandsDir, entry.name);
|
|
955
|
+
const content = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
|
|
956
|
+
const frontmatter = parseFrontmatter(content);
|
|
957
|
+
const fallbackName = (0, import_node_path2.basename)(entry.name, ".md");
|
|
958
|
+
commands.push(buildCommand(frontmatter, content, fallbackName));
|
|
690
959
|
}
|
|
691
960
|
return commands;
|
|
692
961
|
}
|
|
693
962
|
var SkillCommandSource = class {
|
|
694
963
|
name = "skill";
|
|
695
964
|
cwd;
|
|
965
|
+
home;
|
|
696
966
|
cachedCommands = null;
|
|
697
|
-
constructor(cwd) {
|
|
967
|
+
constructor(cwd, home) {
|
|
698
968
|
this.cwd = cwd;
|
|
969
|
+
this.home = home ?? (0, import_node_os.homedir)();
|
|
699
970
|
}
|
|
700
971
|
getCommands() {
|
|
701
972
|
if (this.cachedCommands) return this.cachedCommands;
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
973
|
+
const sources = [
|
|
974
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".claude", "skills")),
|
|
975
|
+
// 1. project .claude/skills
|
|
976
|
+
scanCommandsDir((0, import_node_path2.join)(this.cwd, ".claude", "commands")),
|
|
977
|
+
// 2. project .claude/commands (legacy)
|
|
978
|
+
scanSkillsDir((0, import_node_path2.join)(this.home, ".robota", "skills")),
|
|
979
|
+
// 3. user ~/.robota/skills
|
|
980
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"))
|
|
981
|
+
// 4. project .agents/skills
|
|
982
|
+
];
|
|
983
|
+
const seen = /* @__PURE__ */ new Set();
|
|
984
|
+
const merged = [];
|
|
985
|
+
for (const commands of sources) {
|
|
986
|
+
for (const cmd of commands) {
|
|
987
|
+
if (!seen.has(cmd.name)) {
|
|
988
|
+
seen.add(cmd.name);
|
|
989
|
+
merged.push(cmd);
|
|
990
|
+
}
|
|
709
991
|
}
|
|
710
992
|
}
|
|
711
993
|
this.cachedCommands = merged;
|
|
712
994
|
return this.cachedCommands;
|
|
713
995
|
}
|
|
996
|
+
/** Get skills that models can invoke (excludes disableModelInvocation: true) */
|
|
997
|
+
getModelInvocableSkills() {
|
|
998
|
+
return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
|
|
999
|
+
}
|
|
1000
|
+
/** Get skills that users can invoke (excludes userInvocable: false) */
|
|
1001
|
+
getUserInvocableSkills() {
|
|
1002
|
+
return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
// src/commands/plugin-source.ts
|
|
1007
|
+
var PluginCommandSource = class {
|
|
1008
|
+
name = "plugin";
|
|
1009
|
+
plugins;
|
|
1010
|
+
constructor(plugins) {
|
|
1011
|
+
this.plugins = plugins;
|
|
1012
|
+
}
|
|
1013
|
+
getCommands() {
|
|
1014
|
+
const commands = [];
|
|
1015
|
+
for (const plugin of this.plugins) {
|
|
1016
|
+
for (const skill of plugin.skills) {
|
|
1017
|
+
const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
|
|
1018
|
+
commands.push({
|
|
1019
|
+
name: baseName,
|
|
1020
|
+
description: `${skill.description} (${plugin.manifest.name})`,
|
|
1021
|
+
source: "plugin",
|
|
1022
|
+
skillContent: skill.skillContent,
|
|
1023
|
+
pluginDir: plugin.pluginDir
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
for (const cmd of plugin.commands) {
|
|
1027
|
+
commands.push({
|
|
1028
|
+
name: cmd.name,
|
|
1029
|
+
description: cmd.description,
|
|
1030
|
+
source: "plugin",
|
|
1031
|
+
skillContent: cmd.skillContent,
|
|
1032
|
+
pluginDir: plugin.pluginDir
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return commands;
|
|
1037
|
+
}
|
|
714
1038
|
};
|
|
715
1039
|
|
|
716
1040
|
// src/ui/hooks/useCommandRegistry.ts
|
|
1041
|
+
function buildPluginEnv(plugin) {
|
|
1042
|
+
const dataDir = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
|
|
1043
|
+
return {
|
|
1044
|
+
CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
|
|
1045
|
+
CLAUDE_PLUGIN_PATH: plugin.pluginDir,
|
|
1046
|
+
CLAUDE_PLUGIN_DATA: dataDir
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function mergePluginHooks(plugins) {
|
|
1050
|
+
const merged = {};
|
|
1051
|
+
for (const plugin of plugins) {
|
|
1052
|
+
const hooksObj = plugin.hooks;
|
|
1053
|
+
if (!hooksObj) continue;
|
|
1054
|
+
const pluginEnv = buildPluginEnv(plugin);
|
|
1055
|
+
const innerHooks = hooksObj.hooks ?? hooksObj;
|
|
1056
|
+
for (const [event, groups] of Object.entries(innerHooks)) {
|
|
1057
|
+
if (!Array.isArray(groups)) continue;
|
|
1058
|
+
if (!merged[event]) merged[event] = [];
|
|
1059
|
+
const resolved = groups.map((group) => {
|
|
1060
|
+
const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
|
|
1061
|
+
if (typeof resolved2 === "object" && resolved2 !== null) {
|
|
1062
|
+
resolved2.env = pluginEnv;
|
|
1063
|
+
}
|
|
1064
|
+
return resolved2;
|
|
1065
|
+
});
|
|
1066
|
+
merged[event].push(...resolved);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return merged;
|
|
1070
|
+
}
|
|
1071
|
+
function resolvePluginRoot(group, pluginDir) {
|
|
1072
|
+
if (typeof group !== "object" || group === null) return group;
|
|
1073
|
+
const obj = group;
|
|
1074
|
+
if (Array.isArray(obj.hooks)) {
|
|
1075
|
+
return {
|
|
1076
|
+
...obj,
|
|
1077
|
+
hooks: obj.hooks.map((h) => {
|
|
1078
|
+
if (typeof h !== "object" || h === null) return h;
|
|
1079
|
+
const hook = h;
|
|
1080
|
+
if (typeof hook.command === "string") {
|
|
1081
|
+
return {
|
|
1082
|
+
...hook,
|
|
1083
|
+
command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
return hook;
|
|
1087
|
+
})
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return group;
|
|
1091
|
+
}
|
|
717
1092
|
function useCommandRegistry(cwd) {
|
|
718
|
-
const
|
|
719
|
-
if (
|
|
1093
|
+
const resultRef = (0, import_react5.useRef)(null);
|
|
1094
|
+
if (resultRef.current === null) {
|
|
720
1095
|
const registry = new CommandRegistry();
|
|
721
1096
|
registry.addSource(new BuiltinCommandSource());
|
|
722
1097
|
registry.addSource(new SkillCommandSource(cwd));
|
|
723
|
-
|
|
1098
|
+
let pluginHooks = {};
|
|
1099
|
+
const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
|
|
1100
|
+
const loader = new import_agent_sdk2.BundlePluginLoader(pluginsDir);
|
|
1101
|
+
try {
|
|
1102
|
+
const plugins = loader.loadPluginsSync();
|
|
1103
|
+
if (plugins.length > 0) {
|
|
1104
|
+
registry.addSource(new PluginCommandSource(plugins));
|
|
1105
|
+
pluginHooks = mergePluginHooks(plugins);
|
|
1106
|
+
}
|
|
1107
|
+
} catch {
|
|
1108
|
+
}
|
|
1109
|
+
resultRef.current = { registry, pluginHooks };
|
|
724
1110
|
}
|
|
725
|
-
return
|
|
1111
|
+
return resultRef.current;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/ui/hooks/usePluginCallbacks.ts
|
|
1115
|
+
var import_react6 = require("react");
|
|
1116
|
+
var import_node_os3 = require("os");
|
|
1117
|
+
var import_node_path4 = require("path");
|
|
1118
|
+
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
1119
|
+
function usePluginCallbacks(cwd) {
|
|
1120
|
+
return (0, import_react6.useMemo)(() => {
|
|
1121
|
+
const home = (0, import_node_os3.homedir)();
|
|
1122
|
+
const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
|
|
1123
|
+
const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
|
|
1124
|
+
const settingsStore = new import_agent_sdk3.PluginSettingsStore(userSettingsPath);
|
|
1125
|
+
const marketplace = new import_agent_sdk3.MarketplaceClient({ pluginsDir });
|
|
1126
|
+
const installer = new import_agent_sdk3.BundlePluginInstaller({
|
|
1127
|
+
pluginsDir,
|
|
1128
|
+
settingsStore,
|
|
1129
|
+
marketplaceClient: marketplace
|
|
1130
|
+
});
|
|
1131
|
+
const loader = new import_agent_sdk3.BundlePluginLoader(pluginsDir);
|
|
1132
|
+
return {
|
|
1133
|
+
listInstalled: async () => {
|
|
1134
|
+
const plugins = await loader.loadAll();
|
|
1135
|
+
return plugins.map((p) => ({
|
|
1136
|
+
name: p.manifest.name,
|
|
1137
|
+
description: p.manifest.description,
|
|
1138
|
+
enabled: true
|
|
1139
|
+
}));
|
|
1140
|
+
},
|
|
1141
|
+
install: async (pluginId) => {
|
|
1142
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
1143
|
+
if (!name || !marketplaceName) {
|
|
1144
|
+
throw new Error("Plugin ID must be in format: name@marketplace");
|
|
1145
|
+
}
|
|
1146
|
+
await installer.install(name, marketplaceName);
|
|
1147
|
+
},
|
|
1148
|
+
uninstall: async (pluginId) => {
|
|
1149
|
+
await installer.uninstall(pluginId);
|
|
1150
|
+
},
|
|
1151
|
+
enable: async (pluginId) => {
|
|
1152
|
+
await installer.enable(pluginId);
|
|
1153
|
+
},
|
|
1154
|
+
disable: async (pluginId) => {
|
|
1155
|
+
await installer.disable(pluginId);
|
|
1156
|
+
},
|
|
1157
|
+
marketplaceAdd: async (source) => {
|
|
1158
|
+
if (source.includes("/") && !source.includes(":")) {
|
|
1159
|
+
return marketplace.addMarketplace({ type: "github", repo: source });
|
|
1160
|
+
} else {
|
|
1161
|
+
return marketplace.addMarketplace({ type: "git", url: source });
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
marketplaceRemove: async (name) => {
|
|
1165
|
+
const installedFromMarketplace = installer.getPluginsByMarketplace(name);
|
|
1166
|
+
for (const record of installedFromMarketplace) {
|
|
1167
|
+
await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
|
|
1168
|
+
}
|
|
1169
|
+
marketplace.removeMarketplace(name);
|
|
1170
|
+
},
|
|
1171
|
+
marketplaceUpdate: async (name) => {
|
|
1172
|
+
marketplace.updateMarketplace(name);
|
|
1173
|
+
},
|
|
1174
|
+
marketplaceList: async () => {
|
|
1175
|
+
return marketplace.listMarketplaces().map((m) => ({
|
|
1176
|
+
name: m.name,
|
|
1177
|
+
type: m.source.type
|
|
1178
|
+
}));
|
|
1179
|
+
},
|
|
1180
|
+
reloadPlugins: async () => {
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
}, [cwd]);
|
|
726
1184
|
}
|
|
727
1185
|
|
|
728
1186
|
// src/ui/MessageList.tsx
|
|
@@ -845,11 +1303,11 @@ function StatusBar({
|
|
|
845
1303
|
}
|
|
846
1304
|
|
|
847
1305
|
// src/ui/InputArea.tsx
|
|
848
|
-
var
|
|
1306
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
849
1307
|
var import_ink6 = require("ink");
|
|
850
1308
|
|
|
851
1309
|
// src/ui/CjkTextInput.tsx
|
|
852
|
-
var
|
|
1310
|
+
var import_react7 = require("react");
|
|
853
1311
|
var import_ink3 = require("ink");
|
|
854
1312
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
855
1313
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -869,9 +1327,9 @@ function CjkTextInput({
|
|
|
869
1327
|
focus = true,
|
|
870
1328
|
showCursor = true
|
|
871
1329
|
}) {
|
|
872
|
-
const valueRef = (0,
|
|
873
|
-
const cursorRef = (0,
|
|
874
|
-
const [, forceRender] = (0,
|
|
1330
|
+
const valueRef = (0, import_react7.useRef)(value);
|
|
1331
|
+
const cursorRef = (0, import_react7.useRef)(value.length);
|
|
1332
|
+
const [, forceRender] = (0, import_react7.useState)(0);
|
|
875
1333
|
if (value !== valueRef.current) {
|
|
876
1334
|
valueRef.current = value;
|
|
877
1335
|
if (cursorRef.current > value.length) {
|
|
@@ -948,15 +1406,15 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
|
948
1406
|
}
|
|
949
1407
|
|
|
950
1408
|
// src/ui/WaveText.tsx
|
|
951
|
-
var
|
|
1409
|
+
var import_react8 = require("react");
|
|
952
1410
|
var import_ink4 = require("ink");
|
|
953
1411
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
954
1412
|
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
955
1413
|
var INTERVAL_MS = 400;
|
|
956
1414
|
var CHARS_PER_GROUP = 4;
|
|
957
1415
|
function WaveText({ text }) {
|
|
958
|
-
const [tick, setTick] = (0,
|
|
959
|
-
(0,
|
|
1416
|
+
const [tick, setTick] = (0, import_react8.useState)(0);
|
|
1417
|
+
(0, import_react8.useEffect)(() => {
|
|
960
1418
|
const timer = setInterval(() => {
|
|
961
1419
|
setTick((prev) => prev + 1);
|
|
962
1420
|
}, INTERVAL_MS);
|
|
@@ -1022,16 +1480,16 @@ function parseSlashInput(value) {
|
|
|
1022
1480
|
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
1023
1481
|
}
|
|
1024
1482
|
function useAutocomplete(value, registry) {
|
|
1025
|
-
const [selectedIndex, setSelectedIndex] = (0,
|
|
1026
|
-
const [dismissed, setDismissed] = (0,
|
|
1027
|
-
const prevValueRef =
|
|
1483
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(0);
|
|
1484
|
+
const [dismissed, setDismissed] = (0, import_react9.useState)(false);
|
|
1485
|
+
const prevValueRef = import_react9.default.useRef(value);
|
|
1028
1486
|
if (prevValueRef.current !== value) {
|
|
1029
1487
|
prevValueRef.current = value;
|
|
1030
1488
|
if (dismissed) setDismissed(false);
|
|
1031
1489
|
}
|
|
1032
1490
|
const parsed = parseSlashInput(value);
|
|
1033
1491
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
1034
|
-
const filteredCommands = (0,
|
|
1492
|
+
const filteredCommands = (0, import_react9.useMemo)(() => {
|
|
1035
1493
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
1036
1494
|
if (isSubcommandMode) {
|
|
1037
1495
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -1065,7 +1523,7 @@ function useAutocomplete(value, registry) {
|
|
|
1065
1523
|
};
|
|
1066
1524
|
}
|
|
1067
1525
|
function InputArea({ onSubmit, isDisabled, registry }) {
|
|
1068
|
-
const [value, setValue] = (0,
|
|
1526
|
+
const [value, setValue] = (0, import_react9.useState)("");
|
|
1069
1527
|
const {
|
|
1070
1528
|
showPopup,
|
|
1071
1529
|
filteredCommands,
|
|
@@ -1074,7 +1532,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
1074
1532
|
isSubcommandMode,
|
|
1075
1533
|
setShowPopup
|
|
1076
1534
|
} = useAutocomplete(value, registry);
|
|
1077
|
-
const handleSubmit = (0,
|
|
1535
|
+
const handleSubmit = (0, import_react9.useCallback)(
|
|
1078
1536
|
(text) => {
|
|
1079
1537
|
const trimmed = text.trim();
|
|
1080
1538
|
if (trimmed.length === 0) return;
|
|
@@ -1087,7 +1545,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
1087
1545
|
},
|
|
1088
1546
|
[showPopup, filteredCommands, selectedIndex, onSubmit]
|
|
1089
1547
|
);
|
|
1090
|
-
const selectCommand = (0,
|
|
1548
|
+
const selectCommand = (0, import_react9.useCallback)(
|
|
1091
1549
|
(cmd) => {
|
|
1092
1550
|
const parsed = parseSlashInput(value);
|
|
1093
1551
|
if (parsed.parentCommand) {
|
|
@@ -1148,7 +1606,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
1148
1606
|
}
|
|
1149
1607
|
|
|
1150
1608
|
// src/ui/ConfirmPrompt.tsx
|
|
1151
|
-
var
|
|
1609
|
+
var import_react10 = require("react");
|
|
1152
1610
|
var import_ink7 = require("ink");
|
|
1153
1611
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1154
1612
|
function ConfirmPrompt({
|
|
@@ -1156,9 +1614,9 @@ function ConfirmPrompt({
|
|
|
1156
1614
|
options = ["Yes", "No"],
|
|
1157
1615
|
onSelect
|
|
1158
1616
|
}) {
|
|
1159
|
-
const [selected, setSelected] = (0,
|
|
1160
|
-
const resolvedRef = (0,
|
|
1161
|
-
const doSelect = (0,
|
|
1617
|
+
const [selected, setSelected] = (0, import_react10.useState)(0);
|
|
1618
|
+
const resolvedRef = (0, import_react10.useRef)(false);
|
|
1619
|
+
const doSelect = (0, import_react10.useCallback)(
|
|
1162
1620
|
(index) => {
|
|
1163
1621
|
if (resolvedRef.current) return;
|
|
1164
1622
|
resolvedRef.current = true;
|
|
@@ -1191,7 +1649,7 @@ function ConfirmPrompt({
|
|
|
1191
1649
|
}
|
|
1192
1650
|
|
|
1193
1651
|
// src/ui/PermissionPrompt.tsx
|
|
1194
|
-
var
|
|
1652
|
+
var import_react11 = __toESM(require("react"), 1);
|
|
1195
1653
|
var import_ink8 = require("ink");
|
|
1196
1654
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1197
1655
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
@@ -1201,15 +1659,15 @@ function formatArgs(args) {
|
|
|
1201
1659
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
1202
1660
|
}
|
|
1203
1661
|
function PermissionPrompt({ request }) {
|
|
1204
|
-
const [selected, setSelected] =
|
|
1205
|
-
const resolvedRef =
|
|
1206
|
-
const prevRequestRef =
|
|
1662
|
+
const [selected, setSelected] = import_react11.default.useState(0);
|
|
1663
|
+
const resolvedRef = import_react11.default.useRef(false);
|
|
1664
|
+
const prevRequestRef = import_react11.default.useRef(request);
|
|
1207
1665
|
if (prevRequestRef.current !== request) {
|
|
1208
1666
|
prevRequestRef.current = request;
|
|
1209
1667
|
resolvedRef.current = false;
|
|
1210
1668
|
setSelected(0);
|
|
1211
1669
|
}
|
|
1212
|
-
const doResolve =
|
|
1670
|
+
const doResolve = import_react11.default.useCallback(
|
|
1213
1671
|
(index) => {
|
|
1214
1672
|
if (resolvedRef.current) return;
|
|
1215
1673
|
resolvedRef.current = true;
|
|
@@ -1288,21 +1746,56 @@ function StreamingIndicator({ text, activeTools }) {
|
|
|
1288
1746
|
// src/ui/App.tsx
|
|
1289
1747
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1290
1748
|
var EXIT_DELAY_MS2 = 500;
|
|
1749
|
+
function mergeHooksIntoConfig(configHooks, pluginHooks) {
|
|
1750
|
+
const pluginKeys = Object.keys(pluginHooks);
|
|
1751
|
+
if (pluginKeys.length === 0) return configHooks;
|
|
1752
|
+
const merged = {};
|
|
1753
|
+
for (const [event, groups] of Object.entries(pluginHooks)) {
|
|
1754
|
+
merged[event] = [...groups];
|
|
1755
|
+
}
|
|
1756
|
+
if (configHooks) {
|
|
1757
|
+
for (const [event, groups] of Object.entries(configHooks)) {
|
|
1758
|
+
if (!Array.isArray(groups)) continue;
|
|
1759
|
+
if (!merged[event]) merged[event] = [];
|
|
1760
|
+
merged[event].push(...groups);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
return merged;
|
|
1764
|
+
}
|
|
1291
1765
|
function App(props) {
|
|
1292
1766
|
const { exit } = (0, import_ink10.useApp)();
|
|
1293
|
-
const {
|
|
1767
|
+
const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1768
|
+
const configWithPluginHooks = {
|
|
1769
|
+
...props.config,
|
|
1770
|
+
hooks: mergeHooksIntoConfig(
|
|
1771
|
+
props.config.hooks,
|
|
1772
|
+
pluginHooks
|
|
1773
|
+
)
|
|
1774
|
+
};
|
|
1775
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
|
|
1776
|
+
{ ...props, config: configWithPluginHooks }
|
|
1777
|
+
);
|
|
1294
1778
|
const { messages, setMessages, addMessage } = useMessages();
|
|
1295
|
-
const [isThinking, setIsThinking] = (0,
|
|
1779
|
+
const [isThinking, setIsThinking] = (0, import_react12.useState)(false);
|
|
1296
1780
|
const initialCtx = session.getContextState();
|
|
1297
|
-
const [contextState, setContextState] = (0,
|
|
1781
|
+
const [contextState, setContextState] = (0, import_react12.useState)({
|
|
1298
1782
|
percentage: initialCtx.usedPercentage,
|
|
1299
1783
|
usedTokens: initialCtx.usedTokens,
|
|
1300
1784
|
maxTokens: initialCtx.maxTokens
|
|
1301
1785
|
});
|
|
1302
|
-
const
|
|
1303
|
-
const
|
|
1304
|
-
const
|
|
1305
|
-
const handleSlashCommand = useSlashCommands(
|
|
1786
|
+
const pendingModelChangeRef = (0, import_react12.useRef)(null);
|
|
1787
|
+
const [pendingModelId, setPendingModelId] = (0, import_react12.useState)(null);
|
|
1788
|
+
const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
|
|
1789
|
+
const handleSlashCommand = useSlashCommands(
|
|
1790
|
+
session,
|
|
1791
|
+
addMessage,
|
|
1792
|
+
setMessages,
|
|
1793
|
+
exit,
|
|
1794
|
+
registry,
|
|
1795
|
+
pendingModelChangeRef,
|
|
1796
|
+
setPendingModelId,
|
|
1797
|
+
pluginCallbacks
|
|
1798
|
+
);
|
|
1306
1799
|
const handleSubmit = useSubmitHandler(
|
|
1307
1800
|
session,
|
|
1308
1801
|
addMessage,
|
|
@@ -1349,10 +1842,16 @@ function App(props) {
|
|
|
1349
1842
|
try {
|
|
1350
1843
|
const settingsPath = getUserSettingsPath();
|
|
1351
1844
|
updateModelInSettings(settingsPath, pendingModelId);
|
|
1352
|
-
addMessage({
|
|
1845
|
+
addMessage({
|
|
1846
|
+
role: "system",
|
|
1847
|
+
content: `Model changed to ${(0, import_agent_core3.getModelName)(pendingModelId)}. Restarting...`
|
|
1848
|
+
});
|
|
1353
1849
|
setTimeout(() => exit(), EXIT_DELAY_MS2);
|
|
1354
1850
|
} catch (err) {
|
|
1355
|
-
addMessage({
|
|
1851
|
+
addMessage({
|
|
1852
|
+
role: "system",
|
|
1853
|
+
content: `Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1854
|
+
});
|
|
1356
1855
|
}
|
|
1357
1856
|
} else {
|
|
1358
1857
|
addMessage({ role: "system", content: "Model change cancelled." });
|
|
@@ -1411,23 +1910,24 @@ function renderApp(options) {
|
|
|
1411
1910
|
|
|
1412
1911
|
// src/cli.ts
|
|
1413
1912
|
var import_meta = {};
|
|
1414
|
-
function
|
|
1415
|
-
if (!(0, import_node_fs3.existsSync)(filePath)) return
|
|
1913
|
+
function checkSettingsFile(filePath) {
|
|
1914
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return "missing";
|
|
1416
1915
|
try {
|
|
1417
1916
|
const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
|
|
1418
|
-
if (raw.length === 0) return
|
|
1917
|
+
if (raw.length === 0) return "incomplete";
|
|
1419
1918
|
const parsed = JSON.parse(raw);
|
|
1420
1919
|
const provider = parsed.provider;
|
|
1421
|
-
|
|
1920
|
+
if (!provider?.apiKey) return "incomplete";
|
|
1921
|
+
return "valid";
|
|
1422
1922
|
} catch {
|
|
1423
|
-
return
|
|
1923
|
+
return "corrupt";
|
|
1424
1924
|
}
|
|
1425
1925
|
}
|
|
1426
1926
|
function readVersion() {
|
|
1427
1927
|
try {
|
|
1428
1928
|
const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
1429
|
-
const dir = (0,
|
|
1430
|
-
const candidates = [(0,
|
|
1929
|
+
const dir = (0, import_node_path5.dirname)(thisFile);
|
|
1930
|
+
const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
|
|
1431
1931
|
for (const pkgPath of candidates) {
|
|
1432
1932
|
try {
|
|
1433
1933
|
const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
|
|
@@ -1480,14 +1980,36 @@ function promptInput(label, masked = false) {
|
|
|
1480
1980
|
}
|
|
1481
1981
|
async function ensureConfig(cwd) {
|
|
1482
1982
|
const userPath = getUserSettingsPath();
|
|
1483
|
-
const projectPath = (0,
|
|
1484
|
-
const localPath = (0,
|
|
1485
|
-
|
|
1983
|
+
const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
|
|
1984
|
+
const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
|
|
1985
|
+
const paths = [userPath, projectPath, localPath];
|
|
1986
|
+
const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
|
|
1987
|
+
if (checks.some((c) => c.status === "valid")) {
|
|
1486
1988
|
return;
|
|
1487
1989
|
}
|
|
1990
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
1991
|
+
const incomplete = checks.filter((c) => c.status === "incomplete");
|
|
1488
1992
|
process.stdout.write("\n");
|
|
1489
|
-
|
|
1490
|
-
|
|
1993
|
+
if (corrupt.length > 0) {
|
|
1994
|
+
for (const c of corrupt) {
|
|
1995
|
+
process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
|
|
1996
|
+
`);
|
|
1997
|
+
}
|
|
1998
|
+
process.stdout.write("\n");
|
|
1999
|
+
}
|
|
2000
|
+
if (incomplete.length > 0) {
|
|
2001
|
+
for (const c of incomplete) {
|
|
2002
|
+
process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
|
|
2003
|
+
`);
|
|
2004
|
+
}
|
|
2005
|
+
process.stdout.write("\n");
|
|
2006
|
+
}
|
|
2007
|
+
if (corrupt.length === 0 && incomplete.length === 0) {
|
|
2008
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
2009
|
+
process.stdout.write(" No configuration found. Let's set up.\n");
|
|
2010
|
+
} else {
|
|
2011
|
+
process.stdout.write(" Reconfiguring...\n");
|
|
2012
|
+
}
|
|
1491
2013
|
process.stdout.write("\n");
|
|
1492
2014
|
const apiKey = await promptInput(" Anthropic API key: ", true);
|
|
1493
2015
|
if (!apiKey) {
|
|
@@ -1495,7 +2017,7 @@ async function ensureConfig(cwd) {
|
|
|
1495
2017
|
process.exit(1);
|
|
1496
2018
|
}
|
|
1497
2019
|
const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
|
|
1498
|
-
const settingsDir = (0,
|
|
2020
|
+
const settingsDir = (0, import_node_path5.dirname)(userPath);
|
|
1499
2021
|
(0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
|
|
1500
2022
|
const settings = {
|
|
1501
2023
|
provider: {
|
|
@@ -1536,9 +2058,9 @@ async function startCli() {
|
|
|
1536
2058
|
const cwd = process.cwd();
|
|
1537
2059
|
await ensureConfig(cwd);
|
|
1538
2060
|
const [config, context, projectInfo] = await Promise.all([
|
|
1539
|
-
(0,
|
|
1540
|
-
(0,
|
|
1541
|
-
(0,
|
|
2061
|
+
(0, import_agent_sdk4.loadConfig)(cwd),
|
|
2062
|
+
(0, import_agent_sdk4.loadContext)(cwd),
|
|
2063
|
+
(0, import_agent_sdk4.detectProject)(cwd)
|
|
1542
2064
|
]);
|
|
1543
2065
|
if (args.model !== void 0) {
|
|
1544
2066
|
config.provider.model = args.model;
|
|
@@ -1546,7 +2068,7 @@ async function startCli() {
|
|
|
1546
2068
|
if (args.language !== void 0) {
|
|
1547
2069
|
config.language = args.language;
|
|
1548
2070
|
}
|
|
1549
|
-
const sessionStore = new
|
|
2071
|
+
const sessionStore = new import_agent_sdk4.SessionStore();
|
|
1550
2072
|
if (args.printMode) {
|
|
1551
2073
|
const prompt = args.positional.join(" ").trim();
|
|
1552
2074
|
if (prompt.length === 0) {
|
|
@@ -1554,15 +2076,15 @@ async function startCli() {
|
|
|
1554
2076
|
process.exit(1);
|
|
1555
2077
|
}
|
|
1556
2078
|
const terminal = new PrintTerminal();
|
|
1557
|
-
const paths = (0,
|
|
1558
|
-
const session = (0,
|
|
2079
|
+
const paths = (0, import_agent_sdk4.projectPaths)(cwd);
|
|
2080
|
+
const session = (0, import_agent_sdk4.createSession)({
|
|
1559
2081
|
config,
|
|
1560
2082
|
context,
|
|
1561
2083
|
terminal,
|
|
1562
|
-
sessionLogger: new
|
|
2084
|
+
sessionLogger: new import_agent_sdk4.FileSessionLogger(paths.logs),
|
|
1563
2085
|
projectInfo,
|
|
1564
2086
|
permissionMode: args.permissionMode,
|
|
1565
|
-
promptForApproval:
|
|
2087
|
+
promptForApproval: import_agent_sdk5.promptForApproval
|
|
1566
2088
|
});
|
|
1567
2089
|
const response = await session.run(prompt);
|
|
1568
2090
|
process.stdout.write(response + "\n");
|