@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
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
|
|
|
@@ -174,7 +174,7 @@ var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
|
174
174
|
var import_react = require("react");
|
|
175
175
|
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
176
176
|
var TOOL_ARG_DISPLAY_MAX = 80;
|
|
177
|
-
var
|
|
177
|
+
var TAIL_KEEP = 30;
|
|
178
178
|
var NOOP_TERMINAL = {
|
|
179
179
|
write: () => {
|
|
180
180
|
},
|
|
@@ -233,13 +233,17 @@ function useSession(props) {
|
|
|
233
233
|
if (event.toolArgs) {
|
|
234
234
|
const firstVal = Object.values(event.toolArgs)[0];
|
|
235
235
|
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
236
|
-
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0,
|
|
236
|
+
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
|
|
237
237
|
}
|
|
238
|
-
setActiveTools((prev) => [
|
|
238
|
+
setActiveTools((prev) => [
|
|
239
|
+
...prev,
|
|
240
|
+
{ toolName: event.toolName, firstArg, isRunning: true }
|
|
241
|
+
]);
|
|
239
242
|
} else {
|
|
243
|
+
const result = event.denied ? "denied" : event.success === false ? "error" : "success";
|
|
240
244
|
setActiveTools(
|
|
241
245
|
(prev) => prev.map(
|
|
242
|
-
(t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
|
|
246
|
+
(t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result } : t
|
|
243
247
|
)
|
|
244
248
|
);
|
|
245
249
|
}
|
|
@@ -263,7 +267,13 @@ function useSession(props) {
|
|
|
263
267
|
setStreamingText("");
|
|
264
268
|
setActiveTools([]);
|
|
265
269
|
}, []);
|
|
266
|
-
return {
|
|
270
|
+
return {
|
|
271
|
+
session: sessionRef.current,
|
|
272
|
+
permissionRequest,
|
|
273
|
+
streamingText,
|
|
274
|
+
clearStreamingText,
|
|
275
|
+
activeTools
|
|
276
|
+
};
|
|
267
277
|
}
|
|
268
278
|
|
|
269
279
|
// src/ui/hooks/useMessages.ts
|
|
@@ -386,7 +396,122 @@ function handleReset(addMessage) {
|
|
|
386
396
|
}
|
|
387
397
|
return { handled: true, exitRequested: true };
|
|
388
398
|
}
|
|
389
|
-
async function
|
|
399
|
+
async function handlePluginCommand(args, addMessage, callbacks) {
|
|
400
|
+
const parts = args.trim().split(/\s+/);
|
|
401
|
+
const subcommand = parts[0] ?? "";
|
|
402
|
+
const subArgs = parts.slice(1).join(" ").trim();
|
|
403
|
+
try {
|
|
404
|
+
switch (subcommand) {
|
|
405
|
+
case "":
|
|
406
|
+
case void 0: {
|
|
407
|
+
const plugins = await callbacks.listInstalled();
|
|
408
|
+
if (plugins.length === 0) {
|
|
409
|
+
addMessage({ role: "system", content: "No plugins installed." });
|
|
410
|
+
} else {
|
|
411
|
+
const lines = plugins.map(
|
|
412
|
+
(p) => ` ${p.name} \u2014 ${p.description} [${p.enabled ? "enabled" : "disabled"}]`
|
|
413
|
+
);
|
|
414
|
+
addMessage({ role: "system", content: `Installed plugins:
|
|
415
|
+
${lines.join("\n")}` });
|
|
416
|
+
}
|
|
417
|
+
return { handled: true };
|
|
418
|
+
}
|
|
419
|
+
case "install": {
|
|
420
|
+
if (!subArgs) {
|
|
421
|
+
addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
|
|
422
|
+
return { handled: true };
|
|
423
|
+
}
|
|
424
|
+
await callbacks.install(subArgs);
|
|
425
|
+
addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
|
|
426
|
+
return { handled: true };
|
|
427
|
+
}
|
|
428
|
+
case "uninstall": {
|
|
429
|
+
if (!subArgs) {
|
|
430
|
+
addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
|
|
431
|
+
return { handled: true };
|
|
432
|
+
}
|
|
433
|
+
await callbacks.uninstall(subArgs);
|
|
434
|
+
addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
|
|
435
|
+
return { handled: true };
|
|
436
|
+
}
|
|
437
|
+
case "enable": {
|
|
438
|
+
if (!subArgs) {
|
|
439
|
+
addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
|
|
440
|
+
return { handled: true };
|
|
441
|
+
}
|
|
442
|
+
await callbacks.enable(subArgs);
|
|
443
|
+
addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
|
|
444
|
+
return { handled: true };
|
|
445
|
+
}
|
|
446
|
+
case "disable": {
|
|
447
|
+
if (!subArgs) {
|
|
448
|
+
addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
|
|
449
|
+
return { handled: true };
|
|
450
|
+
}
|
|
451
|
+
await callbacks.disable(subArgs);
|
|
452
|
+
addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
|
|
453
|
+
return { handled: true };
|
|
454
|
+
}
|
|
455
|
+
case "marketplace": {
|
|
456
|
+
const mpParts = subArgs.split(/\s+/);
|
|
457
|
+
const mpSubcommand = mpParts[0] ?? "";
|
|
458
|
+
const mpArgs = mpParts.slice(1).join(" ").trim();
|
|
459
|
+
if (mpSubcommand === "add" && mpArgs) {
|
|
460
|
+
const registeredName = await callbacks.marketplaceAdd(mpArgs);
|
|
461
|
+
addMessage({
|
|
462
|
+
role: "system",
|
|
463
|
+
content: `Added marketplace: "${registeredName}" (from ${mpArgs})
|
|
464
|
+
Install plugins with: /plugin install <name>@${registeredName}`
|
|
465
|
+
});
|
|
466
|
+
return { handled: true };
|
|
467
|
+
} else if (mpSubcommand === "remove" && mpArgs) {
|
|
468
|
+
await callbacks.marketplaceRemove(mpArgs);
|
|
469
|
+
addMessage({
|
|
470
|
+
role: "system",
|
|
471
|
+
content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
|
|
472
|
+
});
|
|
473
|
+
return { handled: true };
|
|
474
|
+
} else if (mpSubcommand === "update" && mpArgs) {
|
|
475
|
+
await callbacks.marketplaceUpdate(mpArgs);
|
|
476
|
+
addMessage({
|
|
477
|
+
role: "system",
|
|
478
|
+
content: `Updated marketplace "${mpArgs}".`
|
|
479
|
+
});
|
|
480
|
+
return { handled: true };
|
|
481
|
+
} else if (mpSubcommand === "list") {
|
|
482
|
+
const sources = await callbacks.marketplaceList();
|
|
483
|
+
if (sources.length === 0) {
|
|
484
|
+
addMessage({ role: "system", content: "No marketplace sources configured." });
|
|
485
|
+
} else {
|
|
486
|
+
const lines = sources.map((s) => ` ${s.name} (${s.type})`);
|
|
487
|
+
addMessage({ role: "system", content: `Marketplace sources:
|
|
488
|
+
${lines.join("\n")}` });
|
|
489
|
+
}
|
|
490
|
+
return { handled: true };
|
|
491
|
+
} else {
|
|
492
|
+
addMessage({
|
|
493
|
+
role: "system",
|
|
494
|
+
content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
|
|
495
|
+
});
|
|
496
|
+
return { handled: true };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
default:
|
|
500
|
+
addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
|
|
501
|
+
return { handled: true };
|
|
502
|
+
}
|
|
503
|
+
} catch (error) {
|
|
504
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
505
|
+
addMessage({ role: "system", content: `Plugin error: ${message}` });
|
|
506
|
+
return { handled: true };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function handleReloadPlugins(addMessage, callbacks) {
|
|
510
|
+
await callbacks.reloadPlugins();
|
|
511
|
+
addMessage({ role: "system", content: "Plugins reload complete." });
|
|
512
|
+
return { handled: true };
|
|
513
|
+
}
|
|
514
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
|
|
390
515
|
switch (cmd) {
|
|
391
516
|
case "help":
|
|
392
517
|
return handleHelp(addMessage);
|
|
@@ -410,10 +535,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
410
535
|
return handleReset(addMessage);
|
|
411
536
|
case "exit":
|
|
412
537
|
return { handled: true, exitRequested: true };
|
|
538
|
+
case "plugin":
|
|
539
|
+
if (pluginCallbacks) {
|
|
540
|
+
return handlePluginCommand(args, addMessage, pluginCallbacks);
|
|
541
|
+
}
|
|
542
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
543
|
+
return { handled: true };
|
|
544
|
+
case "reload-plugins":
|
|
545
|
+
if (pluginCallbacks) {
|
|
546
|
+
return handleReloadPlugins(addMessage, pluginCallbacks);
|
|
547
|
+
}
|
|
548
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
549
|
+
return { handled: true };
|
|
413
550
|
default: {
|
|
414
|
-
const
|
|
415
|
-
if (
|
|
416
|
-
addMessage({ role: "system", content: `Invoking
|
|
551
|
+
const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
552
|
+
if (dynamicCmd) {
|
|
553
|
+
addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
|
|
417
554
|
return { handled: false };
|
|
418
555
|
}
|
|
419
556
|
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
@@ -424,14 +561,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
424
561
|
|
|
425
562
|
// src/ui/hooks/useSlashCommands.ts
|
|
426
563
|
var EXIT_DELAY_MS = 500;
|
|
427
|
-
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
564
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks) {
|
|
428
565
|
return (0, import_react3.useCallback)(
|
|
429
566
|
async (input) => {
|
|
430
567
|
const parts = input.slice(1).split(/\s+/);
|
|
431
568
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
432
569
|
const args = parts.slice(1).join(" ");
|
|
433
570
|
const clearMessages = () => setMessages([]);
|
|
434
|
-
const result = await executeSlashCommand(
|
|
571
|
+
const result = await executeSlashCommand(
|
|
572
|
+
cmd,
|
|
573
|
+
args,
|
|
574
|
+
session,
|
|
575
|
+
addMessage,
|
|
576
|
+
clearMessages,
|
|
577
|
+
registry,
|
|
578
|
+
pluginCallbacks
|
|
579
|
+
);
|
|
435
580
|
if (result.pendingModelId) {
|
|
436
581
|
pendingModelChangeRef.current = result.pendingModelId;
|
|
437
582
|
setPendingModelId(result.pendingModelId);
|
|
@@ -441,7 +586,16 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
|
|
|
441
586
|
}
|
|
442
587
|
return result.handled;
|
|
443
588
|
},
|
|
444
|
-
[
|
|
589
|
+
[
|
|
590
|
+
session,
|
|
591
|
+
addMessage,
|
|
592
|
+
setMessages,
|
|
593
|
+
exit,
|
|
594
|
+
registry,
|
|
595
|
+
pendingModelChangeRef,
|
|
596
|
+
setPendingModelId,
|
|
597
|
+
pluginCallbacks
|
|
598
|
+
]
|
|
445
599
|
);
|
|
446
600
|
}
|
|
447
601
|
|
|
@@ -450,7 +604,7 @@ var import_react4 = require("react");
|
|
|
450
604
|
|
|
451
605
|
// src/utils/tool-call-extractor.ts
|
|
452
606
|
var TOOL_ARG_MAX_LENGTH = 80;
|
|
453
|
-
var
|
|
607
|
+
var TAIL_KEEP2 = 30;
|
|
454
608
|
function extractToolCalls(history, startIndex) {
|
|
455
609
|
const lines = [];
|
|
456
610
|
for (let i = startIndex; i < history.length; i++) {
|
|
@@ -458,7 +612,7 @@ function extractToolCalls(history, startIndex) {
|
|
|
458
612
|
if (msg.role === "assistant" && msg.toolCalls) {
|
|
459
613
|
for (const tc of msg.toolCalls) {
|
|
460
614
|
const value = parseFirstArgValue(tc.function.arguments);
|
|
461
|
-
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0,
|
|
615
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_MAX_LENGTH - TAIL_KEEP2 - 3) + "..." + value.slice(-TAIL_KEEP2) : value;
|
|
462
616
|
lines.push(`${tc.function.name}(${truncated})`);
|
|
463
617
|
}
|
|
464
618
|
}
|
|
@@ -476,16 +630,60 @@ function parseFirstArgValue(argsJson) {
|
|
|
476
630
|
}
|
|
477
631
|
|
|
478
632
|
// src/utils/skill-prompt.ts
|
|
479
|
-
|
|
633
|
+
var import_node_child_process = require("child_process");
|
|
634
|
+
function substituteVariables(content, args, context) {
|
|
635
|
+
const argParts = args ? args.split(/\s+/) : [];
|
|
636
|
+
let result = content;
|
|
637
|
+
result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
|
|
638
|
+
return argParts[Number(index)] ?? "";
|
|
639
|
+
});
|
|
640
|
+
result = result.replace(/\$ARGUMENTS/g, args);
|
|
641
|
+
result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
|
|
642
|
+
return argParts[Number(digit)] ?? "";
|
|
643
|
+
});
|
|
644
|
+
result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
|
|
645
|
+
result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
|
|
646
|
+
return result;
|
|
647
|
+
}
|
|
648
|
+
async function preprocessShellCommands(content) {
|
|
649
|
+
const shellPattern = /!`([^`]+)`/g;
|
|
650
|
+
if (!shellPattern.test(content)) {
|
|
651
|
+
return content;
|
|
652
|
+
}
|
|
653
|
+
shellPattern.lastIndex = 0;
|
|
654
|
+
let result = content;
|
|
655
|
+
let match;
|
|
656
|
+
const matches = [];
|
|
657
|
+
while ((match = shellPattern.exec(content)) !== null) {
|
|
658
|
+
matches.push({ full: match[0], command: match[1] });
|
|
659
|
+
}
|
|
660
|
+
for (const { full, command } of matches) {
|
|
661
|
+
let output = "";
|
|
662
|
+
try {
|
|
663
|
+
output = (0, import_node_child_process.execSync)(command, {
|
|
664
|
+
timeout: 5e3,
|
|
665
|
+
encoding: "utf-8",
|
|
666
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
667
|
+
}).trimEnd();
|
|
668
|
+
} catch {
|
|
669
|
+
output = "";
|
|
670
|
+
}
|
|
671
|
+
result = result.replace(full, output);
|
|
672
|
+
}
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
async function buildSkillPrompt(input, registry, context) {
|
|
480
676
|
const parts = input.slice(1).split(/\s+/);
|
|
481
677
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
482
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
678
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
483
679
|
if (!skillCmd) return null;
|
|
484
680
|
const args = parts.slice(1).join(" ").trim();
|
|
485
681
|
const userInstruction = args || skillCmd.description;
|
|
486
682
|
if (skillCmd.skillContent) {
|
|
683
|
+
let processed = await preprocessShellCommands(skillCmd.skillContent);
|
|
684
|
+
processed = substituteVariables(processed, args, context);
|
|
487
685
|
return `<skill name="${cmd}">
|
|
488
|
-
${
|
|
686
|
+
${processed}
|
|
489
687
|
</skill>
|
|
490
688
|
|
|
491
689
|
Execute the "${cmd}" skill: ${userInstruction}`;
|
|
@@ -498,12 +696,12 @@ function syncContextState(session, setter) {
|
|
|
498
696
|
const ctx = session.getContextState();
|
|
499
697
|
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
500
698
|
}
|
|
501
|
-
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
|
|
699
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
|
|
502
700
|
setIsThinking(true);
|
|
503
701
|
clearStreamingText();
|
|
504
702
|
const historyBefore = session.getHistory().length;
|
|
505
703
|
try {
|
|
506
|
-
const response = await session.run(prompt);
|
|
704
|
+
const response = await session.run(prompt, rawInput);
|
|
507
705
|
clearStreamingText();
|
|
508
706
|
const history = session.getHistory();
|
|
509
707
|
const toolLines = extractToolCalls(
|
|
@@ -511,7 +709,11 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
|
|
|
511
709
|
historyBefore
|
|
512
710
|
);
|
|
513
711
|
if (toolLines.length > 0) {
|
|
514
|
-
addMessage({
|
|
712
|
+
addMessage({
|
|
713
|
+
role: "tool",
|
|
714
|
+
content: toolLines.join("\n"),
|
|
715
|
+
toolName: `${toolLines.length} tools`
|
|
716
|
+
});
|
|
515
717
|
}
|
|
516
718
|
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
517
719
|
syncContextState(session, setContextState);
|
|
@@ -536,19 +738,48 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
536
738
|
syncContextState(session, setContextState);
|
|
537
739
|
return;
|
|
538
740
|
}
|
|
539
|
-
const prompt = buildSkillPrompt(input, registry);
|
|
741
|
+
const prompt = await buildSkillPrompt(input, registry);
|
|
540
742
|
if (!prompt) return;
|
|
541
|
-
|
|
743
|
+
const cmdName = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
744
|
+
const qualifiedName = registry.resolveQualifiedName(cmdName);
|
|
745
|
+
const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmdName.length)}` : input;
|
|
746
|
+
return runSessionPrompt(
|
|
747
|
+
prompt,
|
|
748
|
+
session,
|
|
749
|
+
addMessage,
|
|
750
|
+
clearStreamingText,
|
|
751
|
+
setIsThinking,
|
|
752
|
+
setContextState,
|
|
753
|
+
hookInput
|
|
754
|
+
);
|
|
542
755
|
}
|
|
543
756
|
addMessage({ role: "user", content: input });
|
|
544
|
-
return runSessionPrompt(
|
|
757
|
+
return runSessionPrompt(
|
|
758
|
+
input,
|
|
759
|
+
session,
|
|
760
|
+
addMessage,
|
|
761
|
+
clearStreamingText,
|
|
762
|
+
setIsThinking,
|
|
763
|
+
setContextState
|
|
764
|
+
);
|
|
545
765
|
},
|
|
546
|
-
[
|
|
766
|
+
[
|
|
767
|
+
session,
|
|
768
|
+
addMessage,
|
|
769
|
+
handleSlashCommand,
|
|
770
|
+
clearStreamingText,
|
|
771
|
+
setIsThinking,
|
|
772
|
+
setContextState,
|
|
773
|
+
registry
|
|
774
|
+
]
|
|
547
775
|
);
|
|
548
776
|
}
|
|
549
777
|
|
|
550
778
|
// src/ui/hooks/useCommandRegistry.ts
|
|
551
779
|
var import_react5 = require("react");
|
|
780
|
+
var import_node_os2 = require("os");
|
|
781
|
+
var import_node_path3 = require("path");
|
|
782
|
+
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
552
783
|
|
|
553
784
|
// src/commands/command-registry.ts
|
|
554
785
|
var CommandRegistry = class {
|
|
@@ -566,6 +797,14 @@ var CommandRegistry = class {
|
|
|
566
797
|
const lower = filter.toLowerCase();
|
|
567
798
|
return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
|
|
568
799
|
}
|
|
800
|
+
/** Resolve a short name to its fully qualified plugin:name form */
|
|
801
|
+
resolveQualifiedName(shortName) {
|
|
802
|
+
const matches = this.getCommands().filter(
|
|
803
|
+
(c) => c.source === "plugin" && c.name.includes(":") && c.name.endsWith(`:${shortName}`)
|
|
804
|
+
);
|
|
805
|
+
if (matches.length !== 1) return null;
|
|
806
|
+
return matches[0].name;
|
|
807
|
+
}
|
|
569
808
|
/** Get subcommands for a specific command */
|
|
570
809
|
getSubcommands(commandName) {
|
|
571
810
|
const lower = commandName.toLowerCase();
|
|
@@ -632,6 +871,23 @@ function createBuiltinCommands() {
|
|
|
632
871
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
633
872
|
{ name: "context", description: "Context window info", source: "builtin" },
|
|
634
873
|
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
874
|
+
{
|
|
875
|
+
name: "plugin",
|
|
876
|
+
description: "Manage plugins",
|
|
877
|
+
source: "builtin",
|
|
878
|
+
subcommands: [
|
|
879
|
+
{ name: "install", description: "Install a plugin (name@marketplace)", source: "builtin" },
|
|
880
|
+
{
|
|
881
|
+
name: "uninstall",
|
|
882
|
+
description: "Uninstall a plugin (name@marketplace)",
|
|
883
|
+
source: "builtin"
|
|
884
|
+
},
|
|
885
|
+
{ name: "enable", description: "Enable a plugin (name@marketplace)", source: "builtin" },
|
|
886
|
+
{ name: "disable", description: "Disable a plugin (name@marketplace)", source: "builtin" },
|
|
887
|
+
{ name: "marketplace", description: "Manage marketplace sources", source: "builtin" }
|
|
888
|
+
]
|
|
889
|
+
},
|
|
890
|
+
{ name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
|
|
635
891
|
{ name: "reset", description: "Delete settings and exit", source: "builtin" },
|
|
636
892
|
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
637
893
|
];
|
|
@@ -651,25 +907,50 @@ var BuiltinCommandSource = class {
|
|
|
651
907
|
var import_node_fs2 = require("fs");
|
|
652
908
|
var import_node_path2 = require("path");
|
|
653
909
|
var import_node_os = require("os");
|
|
910
|
+
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
|
|
911
|
+
var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
|
|
912
|
+
function kebabToCamel(key) {
|
|
913
|
+
return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
914
|
+
}
|
|
654
915
|
function parseFrontmatter(content) {
|
|
655
916
|
const lines = content.split("\n");
|
|
656
917
|
if (lines[0]?.trim() !== "---") return null;
|
|
657
|
-
|
|
658
|
-
let description = "";
|
|
918
|
+
const result = {};
|
|
659
919
|
for (let i = 1; i < lines.length; i++) {
|
|
660
920
|
const line = lines[i];
|
|
661
921
|
if (line.trim() === "---") break;
|
|
662
|
-
const
|
|
663
|
-
if (
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
922
|
+
const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
|
|
923
|
+
if (!match) continue;
|
|
924
|
+
const key = match[1];
|
|
925
|
+
const rawValue = match[2].trim();
|
|
926
|
+
const camelKey = kebabToCamel(key);
|
|
927
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
928
|
+
result[camelKey] = rawValue === "true";
|
|
929
|
+
} else if (LIST_KEYS.has(key)) {
|
|
930
|
+
result[camelKey] = rawValue.split(",").map((s) => s.trim());
|
|
931
|
+
} else {
|
|
932
|
+
result[camelKey] = rawValue;
|
|
670
933
|
}
|
|
671
934
|
}
|
|
672
|
-
return
|
|
935
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
936
|
+
}
|
|
937
|
+
function buildCommand(frontmatter, content, fallbackName) {
|
|
938
|
+
const cmd = {
|
|
939
|
+
name: frontmatter?.name ?? fallbackName,
|
|
940
|
+
description: frontmatter?.description ?? `Skill: ${fallbackName}`,
|
|
941
|
+
source: "skill",
|
|
942
|
+
skillContent: content
|
|
943
|
+
};
|
|
944
|
+
if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
|
|
945
|
+
if (frontmatter?.disableModelInvocation !== void 0)
|
|
946
|
+
cmd.disableModelInvocation = frontmatter.disableModelInvocation;
|
|
947
|
+
if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
|
|
948
|
+
if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
|
|
949
|
+
if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
|
|
950
|
+
if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
|
|
951
|
+
if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
|
|
952
|
+
if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
|
|
953
|
+
return cmd;
|
|
673
954
|
}
|
|
674
955
|
function scanSkillsDir(skillsDir) {
|
|
675
956
|
if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
|
|
@@ -681,48 +962,246 @@ function scanSkillsDir(skillsDir) {
|
|
|
681
962
|
if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
|
|
682
963
|
const content = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
|
|
683
964
|
const frontmatter = parseFrontmatter(content);
|
|
684
|
-
commands.push(
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
965
|
+
commands.push(buildCommand(frontmatter, content, entry.name));
|
|
966
|
+
}
|
|
967
|
+
return commands;
|
|
968
|
+
}
|
|
969
|
+
function scanCommandsDir(commandsDir) {
|
|
970
|
+
if (!(0, import_node_fs2.existsSync)(commandsDir)) return [];
|
|
971
|
+
const commands = [];
|
|
972
|
+
const entries = (0, import_node_fs2.readdirSync)(commandsDir, { withFileTypes: true });
|
|
973
|
+
for (const entry of entries) {
|
|
974
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
975
|
+
const filePath = (0, import_node_path2.join)(commandsDir, entry.name);
|
|
976
|
+
const content = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
|
|
977
|
+
const frontmatter = parseFrontmatter(content);
|
|
978
|
+
const fallbackName = (0, import_node_path2.basename)(entry.name, ".md");
|
|
979
|
+
commands.push(buildCommand(frontmatter, content, fallbackName));
|
|
690
980
|
}
|
|
691
981
|
return commands;
|
|
692
982
|
}
|
|
693
983
|
var SkillCommandSource = class {
|
|
694
984
|
name = "skill";
|
|
695
985
|
cwd;
|
|
986
|
+
home;
|
|
696
987
|
cachedCommands = null;
|
|
697
|
-
constructor(cwd) {
|
|
988
|
+
constructor(cwd, home) {
|
|
698
989
|
this.cwd = cwd;
|
|
990
|
+
this.home = home ?? (0, import_node_os.homedir)();
|
|
699
991
|
}
|
|
700
992
|
getCommands() {
|
|
701
993
|
if (this.cachedCommands) return this.cachedCommands;
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
994
|
+
const sources = [
|
|
995
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".claude", "skills")),
|
|
996
|
+
// 1. project .claude/skills
|
|
997
|
+
scanCommandsDir((0, import_node_path2.join)(this.cwd, ".claude", "commands")),
|
|
998
|
+
// 2. project .claude/commands (legacy)
|
|
999
|
+
scanSkillsDir((0, import_node_path2.join)(this.home, ".robota", "skills")),
|
|
1000
|
+
// 3. user ~/.robota/skills
|
|
1001
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"))
|
|
1002
|
+
// 4. project .agents/skills
|
|
1003
|
+
];
|
|
1004
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1005
|
+
const merged = [];
|
|
1006
|
+
for (const commands of sources) {
|
|
1007
|
+
for (const cmd of commands) {
|
|
1008
|
+
if (!seen.has(cmd.name)) {
|
|
1009
|
+
seen.add(cmd.name);
|
|
1010
|
+
merged.push(cmd);
|
|
1011
|
+
}
|
|
709
1012
|
}
|
|
710
1013
|
}
|
|
711
1014
|
this.cachedCommands = merged;
|
|
712
1015
|
return this.cachedCommands;
|
|
713
1016
|
}
|
|
1017
|
+
/** Get skills that models can invoke (excludes disableModelInvocation: true) */
|
|
1018
|
+
getModelInvocableSkills() {
|
|
1019
|
+
return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
|
|
1020
|
+
}
|
|
1021
|
+
/** Get skills that users can invoke (excludes userInvocable: false) */
|
|
1022
|
+
getUserInvocableSkills() {
|
|
1023
|
+
return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/commands/plugin-source.ts
|
|
1028
|
+
var PluginCommandSource = class {
|
|
1029
|
+
name = "plugin";
|
|
1030
|
+
plugins;
|
|
1031
|
+
constructor(plugins) {
|
|
1032
|
+
this.plugins = plugins;
|
|
1033
|
+
}
|
|
1034
|
+
getCommands() {
|
|
1035
|
+
const commands = [];
|
|
1036
|
+
for (const plugin of this.plugins) {
|
|
1037
|
+
for (const skill of plugin.skills) {
|
|
1038
|
+
const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
|
|
1039
|
+
commands.push({
|
|
1040
|
+
name: baseName,
|
|
1041
|
+
description: `(${plugin.manifest.name}) ${skill.description}`,
|
|
1042
|
+
source: "plugin",
|
|
1043
|
+
skillContent: skill.skillContent,
|
|
1044
|
+
pluginDir: plugin.pluginDir
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
for (const cmd of plugin.commands) {
|
|
1048
|
+
commands.push({
|
|
1049
|
+
name: cmd.name,
|
|
1050
|
+
description: cmd.description,
|
|
1051
|
+
source: "plugin",
|
|
1052
|
+
skillContent: cmd.skillContent,
|
|
1053
|
+
pluginDir: plugin.pluginDir
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return commands;
|
|
1058
|
+
}
|
|
714
1059
|
};
|
|
715
1060
|
|
|
716
1061
|
// src/ui/hooks/useCommandRegistry.ts
|
|
1062
|
+
function buildPluginEnv(plugin) {
|
|
1063
|
+
const dataDir = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
|
|
1064
|
+
return {
|
|
1065
|
+
CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
|
|
1066
|
+
CLAUDE_PLUGIN_PATH: plugin.pluginDir,
|
|
1067
|
+
CLAUDE_PLUGIN_DATA: dataDir
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
function mergePluginHooks(plugins) {
|
|
1071
|
+
const merged = {};
|
|
1072
|
+
for (const plugin of plugins) {
|
|
1073
|
+
const hooksObj = plugin.hooks;
|
|
1074
|
+
if (!hooksObj) continue;
|
|
1075
|
+
const pluginEnv = buildPluginEnv(plugin);
|
|
1076
|
+
const innerHooks = hooksObj.hooks ?? hooksObj;
|
|
1077
|
+
for (const [event, groups] of Object.entries(innerHooks)) {
|
|
1078
|
+
if (!Array.isArray(groups)) continue;
|
|
1079
|
+
if (!merged[event]) merged[event] = [];
|
|
1080
|
+
const resolved = groups.map((group) => {
|
|
1081
|
+
const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
|
|
1082
|
+
if (typeof resolved2 === "object" && resolved2 !== null) {
|
|
1083
|
+
resolved2.env = pluginEnv;
|
|
1084
|
+
}
|
|
1085
|
+
return resolved2;
|
|
1086
|
+
});
|
|
1087
|
+
merged[event].push(...resolved);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return merged;
|
|
1091
|
+
}
|
|
1092
|
+
function resolvePluginRoot(group, pluginDir) {
|
|
1093
|
+
if (typeof group !== "object" || group === null) return group;
|
|
1094
|
+
const obj = group;
|
|
1095
|
+
if (Array.isArray(obj.hooks)) {
|
|
1096
|
+
return {
|
|
1097
|
+
...obj,
|
|
1098
|
+
hooks: obj.hooks.map((h) => {
|
|
1099
|
+
if (typeof h !== "object" || h === null) return h;
|
|
1100
|
+
const hook = h;
|
|
1101
|
+
if (typeof hook.command === "string") {
|
|
1102
|
+
return {
|
|
1103
|
+
...hook,
|
|
1104
|
+
command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
return hook;
|
|
1108
|
+
})
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
return group;
|
|
1112
|
+
}
|
|
717
1113
|
function useCommandRegistry(cwd) {
|
|
718
|
-
const
|
|
719
|
-
if (
|
|
1114
|
+
const resultRef = (0, import_react5.useRef)(null);
|
|
1115
|
+
if (resultRef.current === null) {
|
|
720
1116
|
const registry = new CommandRegistry();
|
|
721
1117
|
registry.addSource(new BuiltinCommandSource());
|
|
722
1118
|
registry.addSource(new SkillCommandSource(cwd));
|
|
723
|
-
|
|
1119
|
+
let pluginHooks = {};
|
|
1120
|
+
const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
|
|
1121
|
+
const loader = new import_agent_sdk2.BundlePluginLoader(pluginsDir);
|
|
1122
|
+
try {
|
|
1123
|
+
const plugins = loader.loadPluginsSync();
|
|
1124
|
+
if (plugins.length > 0) {
|
|
1125
|
+
registry.addSource(new PluginCommandSource(plugins));
|
|
1126
|
+
pluginHooks = mergePluginHooks(plugins);
|
|
1127
|
+
}
|
|
1128
|
+
} catch {
|
|
1129
|
+
}
|
|
1130
|
+
resultRef.current = { registry, pluginHooks };
|
|
724
1131
|
}
|
|
725
|
-
return
|
|
1132
|
+
return resultRef.current;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// src/ui/hooks/usePluginCallbacks.ts
|
|
1136
|
+
var import_react6 = require("react");
|
|
1137
|
+
var import_node_os3 = require("os");
|
|
1138
|
+
var import_node_path4 = require("path");
|
|
1139
|
+
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
1140
|
+
function usePluginCallbacks(cwd) {
|
|
1141
|
+
return (0, import_react6.useMemo)(() => {
|
|
1142
|
+
const home = (0, import_node_os3.homedir)();
|
|
1143
|
+
const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
|
|
1144
|
+
const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
|
|
1145
|
+
const settingsStore = new import_agent_sdk3.PluginSettingsStore(userSettingsPath);
|
|
1146
|
+
const marketplace = new import_agent_sdk3.MarketplaceClient({ pluginsDir });
|
|
1147
|
+
const installer = new import_agent_sdk3.BundlePluginInstaller({
|
|
1148
|
+
pluginsDir,
|
|
1149
|
+
settingsStore,
|
|
1150
|
+
marketplaceClient: marketplace
|
|
1151
|
+
});
|
|
1152
|
+
const loader = new import_agent_sdk3.BundlePluginLoader(pluginsDir);
|
|
1153
|
+
return {
|
|
1154
|
+
listInstalled: async () => {
|
|
1155
|
+
const plugins = await loader.loadAll();
|
|
1156
|
+
return plugins.map((p) => ({
|
|
1157
|
+
name: p.manifest.name,
|
|
1158
|
+
description: p.manifest.description,
|
|
1159
|
+
enabled: true
|
|
1160
|
+
}));
|
|
1161
|
+
},
|
|
1162
|
+
install: async (pluginId) => {
|
|
1163
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
1164
|
+
if (!name || !marketplaceName) {
|
|
1165
|
+
throw new Error("Plugin ID must be in format: name@marketplace");
|
|
1166
|
+
}
|
|
1167
|
+
await installer.install(name, marketplaceName);
|
|
1168
|
+
},
|
|
1169
|
+
uninstall: async (pluginId) => {
|
|
1170
|
+
await installer.uninstall(pluginId);
|
|
1171
|
+
},
|
|
1172
|
+
enable: async (pluginId) => {
|
|
1173
|
+
await installer.enable(pluginId);
|
|
1174
|
+
},
|
|
1175
|
+
disable: async (pluginId) => {
|
|
1176
|
+
await installer.disable(pluginId);
|
|
1177
|
+
},
|
|
1178
|
+
marketplaceAdd: async (source) => {
|
|
1179
|
+
if (source.includes("/") && !source.includes(":")) {
|
|
1180
|
+
return marketplace.addMarketplace({ type: "github", repo: source });
|
|
1181
|
+
} else {
|
|
1182
|
+
return marketplace.addMarketplace({ type: "git", url: source });
|
|
1183
|
+
}
|
|
1184
|
+
},
|
|
1185
|
+
marketplaceRemove: async (name) => {
|
|
1186
|
+
const installedFromMarketplace = installer.getPluginsByMarketplace(name);
|
|
1187
|
+
for (const record of installedFromMarketplace) {
|
|
1188
|
+
await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
|
|
1189
|
+
}
|
|
1190
|
+
marketplace.removeMarketplace(name);
|
|
1191
|
+
},
|
|
1192
|
+
marketplaceUpdate: async (name) => {
|
|
1193
|
+
marketplace.updateMarketplace(name);
|
|
1194
|
+
},
|
|
1195
|
+
marketplaceList: async () => {
|
|
1196
|
+
return marketplace.listMarketplaces().map((m) => ({
|
|
1197
|
+
name: m.name,
|
|
1198
|
+
type: m.source.type
|
|
1199
|
+
}));
|
|
1200
|
+
},
|
|
1201
|
+
reloadPlugins: async () => {
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
}, [cwd]);
|
|
726
1205
|
}
|
|
727
1206
|
|
|
728
1207
|
// src/ui/MessageList.tsx
|
|
@@ -759,13 +1238,39 @@ function RoleLabel({ role }) {
|
|
|
759
1238
|
" "
|
|
760
1239
|
] });
|
|
761
1240
|
case "tool":
|
|
762
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "
|
|
1241
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", bold: true, children: [
|
|
763
1242
|
"Tool:",
|
|
764
1243
|
" "
|
|
765
1244
|
] });
|
|
766
1245
|
}
|
|
767
1246
|
}
|
|
1247
|
+
function ToolMessage({ message }) {
|
|
1248
|
+
const lines = message.content.split("\n").filter((l) => l.trim());
|
|
1249
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1250
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { children: [
|
|
1251
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", bold: true, children: [
|
|
1252
|
+
"Tool:",
|
|
1253
|
+
" "
|
|
1254
|
+
] }),
|
|
1255
|
+
message.toolName && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1256
|
+
"[",
|
|
1257
|
+
message.toolName,
|
|
1258
|
+
"]"
|
|
1259
|
+
] })
|
|
1260
|
+
] }),
|
|
1261
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: " " }),
|
|
1262
|
+
lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "green", children: [
|
|
1263
|
+
" ",
|
|
1264
|
+
"\u2713",
|
|
1265
|
+
" ",
|
|
1266
|
+
line
|
|
1267
|
+
] }, i))
|
|
1268
|
+
] });
|
|
1269
|
+
}
|
|
768
1270
|
function MessageItem({ message }) {
|
|
1271
|
+
if (message.role === "tool") {
|
|
1272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolMessage, { message });
|
|
1273
|
+
}
|
|
769
1274
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
770
1275
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { children: [
|
|
771
1276
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoleLabel, { role: message.role }),
|
|
@@ -845,11 +1350,11 @@ function StatusBar({
|
|
|
845
1350
|
}
|
|
846
1351
|
|
|
847
1352
|
// src/ui/InputArea.tsx
|
|
848
|
-
var
|
|
1353
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
849
1354
|
var import_ink6 = require("ink");
|
|
850
1355
|
|
|
851
1356
|
// src/ui/CjkTextInput.tsx
|
|
852
|
-
var
|
|
1357
|
+
var import_react7 = require("react");
|
|
853
1358
|
var import_ink3 = require("ink");
|
|
854
1359
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
855
1360
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -869,9 +1374,9 @@ function CjkTextInput({
|
|
|
869
1374
|
focus = true,
|
|
870
1375
|
showCursor = true
|
|
871
1376
|
}) {
|
|
872
|
-
const valueRef = (0,
|
|
873
|
-
const cursorRef = (0,
|
|
874
|
-
const [, forceRender] = (0,
|
|
1377
|
+
const valueRef = (0, import_react7.useRef)(value);
|
|
1378
|
+
const cursorRef = (0, import_react7.useRef)(value.length);
|
|
1379
|
+
const [, forceRender] = (0, import_react7.useState)(0);
|
|
875
1380
|
if (value !== valueRef.current) {
|
|
876
1381
|
valueRef.current = value;
|
|
877
1382
|
if (cursorRef.current > value.length) {
|
|
@@ -948,15 +1453,15 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
|
948
1453
|
}
|
|
949
1454
|
|
|
950
1455
|
// src/ui/WaveText.tsx
|
|
951
|
-
var
|
|
1456
|
+
var import_react8 = require("react");
|
|
952
1457
|
var import_ink4 = require("ink");
|
|
953
1458
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
954
1459
|
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
955
1460
|
var INTERVAL_MS = 400;
|
|
956
1461
|
var CHARS_PER_GROUP = 4;
|
|
957
1462
|
function WaveText({ text }) {
|
|
958
|
-
const [tick, setTick] = (0,
|
|
959
|
-
(0,
|
|
1463
|
+
const [tick, setTick] = (0, import_react8.useState)(0);
|
|
1464
|
+
(0, import_react8.useEffect)(() => {
|
|
960
1465
|
const timer = setInterval(() => {
|
|
961
1466
|
setTick((prev) => prev + 1);
|
|
962
1467
|
}, INTERVAL_MS);
|
|
@@ -1022,16 +1527,16 @@ function parseSlashInput(value) {
|
|
|
1022
1527
|
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
1023
1528
|
}
|
|
1024
1529
|
function useAutocomplete(value, registry) {
|
|
1025
|
-
const [selectedIndex, setSelectedIndex] = (0,
|
|
1026
|
-
const [dismissed, setDismissed] = (0,
|
|
1027
|
-
const prevValueRef =
|
|
1530
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(0);
|
|
1531
|
+
const [dismissed, setDismissed] = (0, import_react9.useState)(false);
|
|
1532
|
+
const prevValueRef = import_react9.default.useRef(value);
|
|
1028
1533
|
if (prevValueRef.current !== value) {
|
|
1029
1534
|
prevValueRef.current = value;
|
|
1030
1535
|
if (dismissed) setDismissed(false);
|
|
1031
1536
|
}
|
|
1032
1537
|
const parsed = parseSlashInput(value);
|
|
1033
1538
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
1034
|
-
const filteredCommands = (0,
|
|
1539
|
+
const filteredCommands = (0, import_react9.useMemo)(() => {
|
|
1035
1540
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
1036
1541
|
if (isSubcommandMode) {
|
|
1037
1542
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -1065,7 +1570,7 @@ function useAutocomplete(value, registry) {
|
|
|
1065
1570
|
};
|
|
1066
1571
|
}
|
|
1067
1572
|
function InputArea({ onSubmit, isDisabled, registry }) {
|
|
1068
|
-
const [value, setValue] = (0,
|
|
1573
|
+
const [value, setValue] = (0, import_react9.useState)("");
|
|
1069
1574
|
const {
|
|
1070
1575
|
showPopup,
|
|
1071
1576
|
filteredCommands,
|
|
@@ -1074,7 +1579,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
1074
1579
|
isSubcommandMode,
|
|
1075
1580
|
setShowPopup
|
|
1076
1581
|
} = useAutocomplete(value, registry);
|
|
1077
|
-
const handleSubmit = (0,
|
|
1582
|
+
const handleSubmit = (0, import_react9.useCallback)(
|
|
1078
1583
|
(text) => {
|
|
1079
1584
|
const trimmed = text.trim();
|
|
1080
1585
|
if (trimmed.length === 0) return;
|
|
@@ -1087,7 +1592,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
1087
1592
|
},
|
|
1088
1593
|
[showPopup, filteredCommands, selectedIndex, onSubmit]
|
|
1089
1594
|
);
|
|
1090
|
-
const selectCommand = (0,
|
|
1595
|
+
const selectCommand = (0, import_react9.useCallback)(
|
|
1091
1596
|
(cmd) => {
|
|
1092
1597
|
const parsed = parseSlashInput(value);
|
|
1093
1598
|
if (parsed.parentCommand) {
|
|
@@ -1148,7 +1653,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
1148
1653
|
}
|
|
1149
1654
|
|
|
1150
1655
|
// src/ui/ConfirmPrompt.tsx
|
|
1151
|
-
var
|
|
1656
|
+
var import_react10 = require("react");
|
|
1152
1657
|
var import_ink7 = require("ink");
|
|
1153
1658
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1154
1659
|
function ConfirmPrompt({
|
|
@@ -1156,9 +1661,9 @@ function ConfirmPrompt({
|
|
|
1156
1661
|
options = ["Yes", "No"],
|
|
1157
1662
|
onSelect
|
|
1158
1663
|
}) {
|
|
1159
|
-
const [selected, setSelected] = (0,
|
|
1160
|
-
const resolvedRef = (0,
|
|
1161
|
-
const doSelect = (0,
|
|
1664
|
+
const [selected, setSelected] = (0, import_react10.useState)(0);
|
|
1665
|
+
const resolvedRef = (0, import_react10.useRef)(false);
|
|
1666
|
+
const doSelect = (0, import_react10.useCallback)(
|
|
1162
1667
|
(index) => {
|
|
1163
1668
|
if (resolvedRef.current) return;
|
|
1164
1669
|
resolvedRef.current = true;
|
|
@@ -1191,7 +1696,7 @@ function ConfirmPrompt({
|
|
|
1191
1696
|
}
|
|
1192
1697
|
|
|
1193
1698
|
// src/ui/PermissionPrompt.tsx
|
|
1194
|
-
var
|
|
1699
|
+
var import_react11 = __toESM(require("react"), 1);
|
|
1195
1700
|
var import_ink8 = require("ink");
|
|
1196
1701
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1197
1702
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
@@ -1201,15 +1706,15 @@ function formatArgs(args) {
|
|
|
1201
1706
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
1202
1707
|
}
|
|
1203
1708
|
function PermissionPrompt({ request }) {
|
|
1204
|
-
const [selected, setSelected] =
|
|
1205
|
-
const resolvedRef =
|
|
1206
|
-
const prevRequestRef =
|
|
1709
|
+
const [selected, setSelected] = import_react11.default.useState(0);
|
|
1710
|
+
const resolvedRef = import_react11.default.useRef(false);
|
|
1711
|
+
const prevRequestRef = import_react11.default.useRef(request);
|
|
1207
1712
|
if (prevRequestRef.current !== request) {
|
|
1208
1713
|
prevRequestRef.current = request;
|
|
1209
1714
|
resolvedRef.current = false;
|
|
1210
1715
|
setSelected(0);
|
|
1211
1716
|
}
|
|
1212
|
-
const doResolve =
|
|
1717
|
+
const doResolve = import_react11.default.useCallback(
|
|
1213
1718
|
(index) => {
|
|
1214
1719
|
if (resolvedRef.current) return;
|
|
1215
1720
|
resolvedRef.current = true;
|
|
@@ -1257,6 +1762,12 @@ function PermissionPrompt({ request }) {
|
|
|
1257
1762
|
// src/ui/StreamingIndicator.tsx
|
|
1258
1763
|
var import_ink9 = require("ink");
|
|
1259
1764
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1765
|
+
function getToolStyle(t) {
|
|
1766
|
+
if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
|
|
1767
|
+
if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
|
|
1768
|
+
if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
|
|
1769
|
+
return { color: "green", icon: "\u2713", strikethrough: false };
|
|
1770
|
+
}
|
|
1260
1771
|
function StreamingIndicator({ text, activeTools }) {
|
|
1261
1772
|
const hasTools = activeTools.length > 0;
|
|
1262
1773
|
const hasText = text.length > 0;
|
|
@@ -1265,17 +1776,20 @@ function StreamingIndicator({ text, activeTools }) {
|
|
|
1265
1776
|
}
|
|
1266
1777
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
|
|
1267
1778
|
hasTools && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1268
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "
|
|
1779
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "white", bold: true, children: "Tools:" }),
|
|
1269
1780
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
|
|
1270
|
-
activeTools.map((t, i) =>
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1781
|
+
activeTools.map((t, i) => {
|
|
1782
|
+
const { color, icon, strikethrough } = getToolStyle(t);
|
|
1783
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color, strikethrough, children: [
|
|
1784
|
+
" ",
|
|
1785
|
+
icon,
|
|
1786
|
+
" ",
|
|
1787
|
+
t.toolName,
|
|
1788
|
+
"(",
|
|
1789
|
+
t.firstArg,
|
|
1790
|
+
")"
|
|
1791
|
+
] }, `${t.toolName}-${i}`);
|
|
1792
|
+
})
|
|
1279
1793
|
] }),
|
|
1280
1794
|
hasText && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1281
1795
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: "Robota:" }),
|
|
@@ -1288,20 +1802,46 @@ function StreamingIndicator({ text, activeTools }) {
|
|
|
1288
1802
|
// src/ui/App.tsx
|
|
1289
1803
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1290
1804
|
var EXIT_DELAY_MS2 = 500;
|
|
1805
|
+
function mergeHooksIntoConfig(configHooks, pluginHooks) {
|
|
1806
|
+
const pluginKeys = Object.keys(pluginHooks);
|
|
1807
|
+
if (pluginKeys.length === 0) return configHooks;
|
|
1808
|
+
const merged = {};
|
|
1809
|
+
for (const [event, groups] of Object.entries(pluginHooks)) {
|
|
1810
|
+
merged[event] = [...groups];
|
|
1811
|
+
}
|
|
1812
|
+
if (configHooks) {
|
|
1813
|
+
for (const [event, groups] of Object.entries(configHooks)) {
|
|
1814
|
+
if (!Array.isArray(groups)) continue;
|
|
1815
|
+
if (!merged[event]) merged[event] = [];
|
|
1816
|
+
merged[event].push(...groups);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return merged;
|
|
1820
|
+
}
|
|
1291
1821
|
function App(props) {
|
|
1292
1822
|
const { exit } = (0, import_ink10.useApp)();
|
|
1293
|
-
const {
|
|
1823
|
+
const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1824
|
+
const configWithPluginHooks = {
|
|
1825
|
+
...props.config,
|
|
1826
|
+
hooks: mergeHooksIntoConfig(
|
|
1827
|
+
props.config.hooks,
|
|
1828
|
+
pluginHooks
|
|
1829
|
+
)
|
|
1830
|
+
};
|
|
1831
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
|
|
1832
|
+
{ ...props, config: configWithPluginHooks }
|
|
1833
|
+
);
|
|
1294
1834
|
const { messages, setMessages, addMessage } = useMessages();
|
|
1295
|
-
const [isThinking, setIsThinking] = (0,
|
|
1835
|
+
const [isThinking, setIsThinking] = (0, import_react12.useState)(false);
|
|
1296
1836
|
const initialCtx = session.getContextState();
|
|
1297
|
-
const [contextState, setContextState] = (0,
|
|
1837
|
+
const [contextState, setContextState] = (0, import_react12.useState)({
|
|
1298
1838
|
percentage: initialCtx.usedPercentage,
|
|
1299
1839
|
usedTokens: initialCtx.usedTokens,
|
|
1300
1840
|
maxTokens: initialCtx.maxTokens
|
|
1301
1841
|
});
|
|
1302
|
-
const
|
|
1303
|
-
const
|
|
1304
|
-
const
|
|
1842
|
+
const pendingModelChangeRef = (0, import_react12.useRef)(null);
|
|
1843
|
+
const [pendingModelId, setPendingModelId] = (0, import_react12.useState)(null);
|
|
1844
|
+
const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
|
|
1305
1845
|
const handleSlashCommand = useSlashCommands(
|
|
1306
1846
|
session,
|
|
1307
1847
|
addMessage,
|
|
@@ -1309,7 +1849,8 @@ function App(props) {
|
|
|
1309
1849
|
exit,
|
|
1310
1850
|
registry,
|
|
1311
1851
|
pendingModelChangeRef,
|
|
1312
|
-
setPendingModelId
|
|
1852
|
+
setPendingModelId,
|
|
1853
|
+
pluginCallbacks
|
|
1313
1854
|
);
|
|
1314
1855
|
const handleSubmit = useSubmitHandler(
|
|
1315
1856
|
session,
|
|
@@ -1425,23 +1966,24 @@ function renderApp(options) {
|
|
|
1425
1966
|
|
|
1426
1967
|
// src/cli.ts
|
|
1427
1968
|
var import_meta = {};
|
|
1428
|
-
function
|
|
1429
|
-
if (!(0, import_node_fs3.existsSync)(filePath)) return
|
|
1969
|
+
function checkSettingsFile(filePath) {
|
|
1970
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return "missing";
|
|
1430
1971
|
try {
|
|
1431
1972
|
const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
|
|
1432
|
-
if (raw.length === 0) return
|
|
1973
|
+
if (raw.length === 0) return "incomplete";
|
|
1433
1974
|
const parsed = JSON.parse(raw);
|
|
1434
1975
|
const provider = parsed.provider;
|
|
1435
|
-
|
|
1976
|
+
if (!provider?.apiKey) return "incomplete";
|
|
1977
|
+
return "valid";
|
|
1436
1978
|
} catch {
|
|
1437
|
-
return
|
|
1979
|
+
return "corrupt";
|
|
1438
1980
|
}
|
|
1439
1981
|
}
|
|
1440
1982
|
function readVersion() {
|
|
1441
1983
|
try {
|
|
1442
1984
|
const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
1443
|
-
const dir = (0,
|
|
1444
|
-
const candidates = [(0,
|
|
1985
|
+
const dir = (0, import_node_path5.dirname)(thisFile);
|
|
1986
|
+
const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
|
|
1445
1987
|
for (const pkgPath of candidates) {
|
|
1446
1988
|
try {
|
|
1447
1989
|
const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
|
|
@@ -1494,14 +2036,36 @@ function promptInput(label, masked = false) {
|
|
|
1494
2036
|
}
|
|
1495
2037
|
async function ensureConfig(cwd) {
|
|
1496
2038
|
const userPath = getUserSettingsPath();
|
|
1497
|
-
const projectPath = (0,
|
|
1498
|
-
const localPath = (0,
|
|
1499
|
-
|
|
2039
|
+
const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
|
|
2040
|
+
const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
|
|
2041
|
+
const paths = [userPath, projectPath, localPath];
|
|
2042
|
+
const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
|
|
2043
|
+
if (checks.some((c) => c.status === "valid")) {
|
|
1500
2044
|
return;
|
|
1501
2045
|
}
|
|
2046
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
2047
|
+
const incomplete = checks.filter((c) => c.status === "incomplete");
|
|
1502
2048
|
process.stdout.write("\n");
|
|
1503
|
-
|
|
1504
|
-
|
|
2049
|
+
if (corrupt.length > 0) {
|
|
2050
|
+
for (const c of corrupt) {
|
|
2051
|
+
process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
|
|
2052
|
+
`);
|
|
2053
|
+
}
|
|
2054
|
+
process.stdout.write("\n");
|
|
2055
|
+
}
|
|
2056
|
+
if (incomplete.length > 0) {
|
|
2057
|
+
for (const c of incomplete) {
|
|
2058
|
+
process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
|
|
2059
|
+
`);
|
|
2060
|
+
}
|
|
2061
|
+
process.stdout.write("\n");
|
|
2062
|
+
}
|
|
2063
|
+
if (corrupt.length === 0 && incomplete.length === 0) {
|
|
2064
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
2065
|
+
process.stdout.write(" No configuration found. Let's set up.\n");
|
|
2066
|
+
} else {
|
|
2067
|
+
process.stdout.write(" Reconfiguring...\n");
|
|
2068
|
+
}
|
|
1505
2069
|
process.stdout.write("\n");
|
|
1506
2070
|
const apiKey = await promptInput(" Anthropic API key: ", true);
|
|
1507
2071
|
if (!apiKey) {
|
|
@@ -1509,7 +2073,7 @@ async function ensureConfig(cwd) {
|
|
|
1509
2073
|
process.exit(1);
|
|
1510
2074
|
}
|
|
1511
2075
|
const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
|
|
1512
|
-
const settingsDir = (0,
|
|
2076
|
+
const settingsDir = (0, import_node_path5.dirname)(userPath);
|
|
1513
2077
|
(0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
|
|
1514
2078
|
const settings = {
|
|
1515
2079
|
provider: {
|
|
@@ -1550,9 +2114,9 @@ async function startCli() {
|
|
|
1550
2114
|
const cwd = process.cwd();
|
|
1551
2115
|
await ensureConfig(cwd);
|
|
1552
2116
|
const [config, context, projectInfo] = await Promise.all([
|
|
1553
|
-
(0,
|
|
1554
|
-
(0,
|
|
1555
|
-
(0,
|
|
2117
|
+
(0, import_agent_sdk4.loadConfig)(cwd),
|
|
2118
|
+
(0, import_agent_sdk4.loadContext)(cwd),
|
|
2119
|
+
(0, import_agent_sdk4.detectProject)(cwd)
|
|
1556
2120
|
]);
|
|
1557
2121
|
if (args.model !== void 0) {
|
|
1558
2122
|
config.provider.model = args.model;
|
|
@@ -1560,7 +2124,7 @@ async function startCli() {
|
|
|
1560
2124
|
if (args.language !== void 0) {
|
|
1561
2125
|
config.language = args.language;
|
|
1562
2126
|
}
|
|
1563
|
-
const sessionStore = new
|
|
2127
|
+
const sessionStore = new import_agent_sdk4.SessionStore();
|
|
1564
2128
|
if (args.printMode) {
|
|
1565
2129
|
const prompt = args.positional.join(" ").trim();
|
|
1566
2130
|
if (prompt.length === 0) {
|
|
@@ -1568,15 +2132,15 @@ async function startCli() {
|
|
|
1568
2132
|
process.exit(1);
|
|
1569
2133
|
}
|
|
1570
2134
|
const terminal = new PrintTerminal();
|
|
1571
|
-
const paths = (0,
|
|
1572
|
-
const session = (0,
|
|
2135
|
+
const paths = (0, import_agent_sdk4.projectPaths)(cwd);
|
|
2136
|
+
const session = (0, import_agent_sdk4.createSession)({
|
|
1573
2137
|
config,
|
|
1574
2138
|
context,
|
|
1575
2139
|
terminal,
|
|
1576
|
-
sessionLogger: new
|
|
2140
|
+
sessionLogger: new import_agent_sdk4.FileSessionLogger(paths.logs),
|
|
1577
2141
|
projectInfo,
|
|
1578
2142
|
permissionMode: args.permissionMode,
|
|
1579
|
-
promptForApproval:
|
|
2143
|
+
promptForApproval: import_agent_sdk5.promptForApproval
|
|
1580
2144
|
});
|
|
1581
2145
|
const response = await session.run(prompt);
|
|
1582
2146
|
process.stdout.write(response + "\n");
|