@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.
@@ -30,21 +30,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- Session: () => import_agent_sdk4.Session,
34
- SessionStore: () => import_agent_sdk4.SessionStore,
35
- TRUST_TO_MODE: () => import_agent_sdk4.TRUST_TO_MODE,
36
- query: () => import_agent_sdk4.query,
33
+ Session: () => import_agent_sdk6.Session,
34
+ SessionStore: () => import_agent_sdk6.SessionStore,
35
+ TRUST_TO_MODE: () => import_agent_sdk6.TRUST_TO_MODE,
36
+ query: () => import_agent_sdk6.query,
37
37
  startCli: () => startCli
38
38
  });
39
39
  module.exports = __toCommonJS(index_exports);
40
- var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
40
+ var import_agent_sdk6 = require("@robota-sdk/agent-sdk");
41
41
 
42
42
  // src/cli.ts
43
43
  var import_node_fs3 = require("fs");
44
- var import_node_path3 = require("path");
44
+ var import_node_path5 = require("path");
45
45
  var import_node_url = require("url");
46
- var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
47
- var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
46
+ var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
47
+ var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
48
48
 
49
49
  // src/utils/cli-args.ts
50
50
  var import_node_util = require("util");
@@ -182,7 +182,7 @@ var PrintTerminal = class {
182
182
  var import_ink11 = require("ink");
183
183
 
184
184
  // src/ui/App.tsx
185
- var import_react11 = require("react");
185
+ var import_react12 = require("react");
186
186
  var import_ink10 = require("ink");
187
187
  var import_agent_core3 = require("@robota-sdk/agent-core");
188
188
 
@@ -190,7 +190,7 @@ var import_agent_core3 = require("@robota-sdk/agent-core");
190
190
  var import_react = require("react");
191
191
  var import_agent_sdk = require("@robota-sdk/agent-sdk");
192
192
  var TOOL_ARG_DISPLAY_MAX = 80;
193
- var TOOL_ARG_TRUNCATE_AT = 77;
193
+ var TAIL_KEEP = 30;
194
194
  var NOOP_TERMINAL = {
195
195
  write: () => {
196
196
  },
@@ -249,13 +249,17 @@ function useSession(props) {
249
249
  if (event.toolArgs) {
250
250
  const firstVal = Object.values(event.toolArgs)[0];
251
251
  const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
252
- firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
252
+ firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
253
253
  }
254
- setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
254
+ setActiveTools((prev) => [
255
+ ...prev,
256
+ { toolName: event.toolName, firstArg, isRunning: true }
257
+ ]);
255
258
  } else {
259
+ const result = event.denied ? "denied" : event.success === false ? "error" : "success";
256
260
  setActiveTools(
257
261
  (prev) => prev.map(
258
- (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
262
+ (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result } : t
259
263
  )
260
264
  );
261
265
  }
@@ -279,7 +283,13 @@ function useSession(props) {
279
283
  setStreamingText("");
280
284
  setActiveTools([]);
281
285
  }, []);
282
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
286
+ return {
287
+ session: sessionRef.current,
288
+ permissionRequest,
289
+ streamingText,
290
+ clearStreamingText,
291
+ activeTools
292
+ };
283
293
  }
284
294
 
285
295
  // src/ui/hooks/useMessages.ts
@@ -402,7 +412,122 @@ function handleReset(addMessage) {
402
412
  }
403
413
  return { handled: true, exitRequested: true };
404
414
  }
405
- async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry) {
415
+ async function handlePluginCommand(args, addMessage, callbacks) {
416
+ const parts = args.trim().split(/\s+/);
417
+ const subcommand = parts[0] ?? "";
418
+ const subArgs = parts.slice(1).join(" ").trim();
419
+ try {
420
+ switch (subcommand) {
421
+ case "":
422
+ case void 0: {
423
+ const plugins = await callbacks.listInstalled();
424
+ if (plugins.length === 0) {
425
+ addMessage({ role: "system", content: "No plugins installed." });
426
+ } else {
427
+ const lines = plugins.map(
428
+ (p) => ` ${p.name} \u2014 ${p.description} [${p.enabled ? "enabled" : "disabled"}]`
429
+ );
430
+ addMessage({ role: "system", content: `Installed plugins:
431
+ ${lines.join("\n")}` });
432
+ }
433
+ return { handled: true };
434
+ }
435
+ case "install": {
436
+ if (!subArgs) {
437
+ addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
438
+ return { handled: true };
439
+ }
440
+ await callbacks.install(subArgs);
441
+ addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
442
+ return { handled: true };
443
+ }
444
+ case "uninstall": {
445
+ if (!subArgs) {
446
+ addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
447
+ return { handled: true };
448
+ }
449
+ await callbacks.uninstall(subArgs);
450
+ addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
451
+ return { handled: true };
452
+ }
453
+ case "enable": {
454
+ if (!subArgs) {
455
+ addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
456
+ return { handled: true };
457
+ }
458
+ await callbacks.enable(subArgs);
459
+ addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
460
+ return { handled: true };
461
+ }
462
+ case "disable": {
463
+ if (!subArgs) {
464
+ addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
465
+ return { handled: true };
466
+ }
467
+ await callbacks.disable(subArgs);
468
+ addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
469
+ return { handled: true };
470
+ }
471
+ case "marketplace": {
472
+ const mpParts = subArgs.split(/\s+/);
473
+ const mpSubcommand = mpParts[0] ?? "";
474
+ const mpArgs = mpParts.slice(1).join(" ").trim();
475
+ if (mpSubcommand === "add" && mpArgs) {
476
+ const registeredName = await callbacks.marketplaceAdd(mpArgs);
477
+ addMessage({
478
+ role: "system",
479
+ content: `Added marketplace: "${registeredName}" (from ${mpArgs})
480
+ Install plugins with: /plugin install <name>@${registeredName}`
481
+ });
482
+ return { handled: true };
483
+ } else if (mpSubcommand === "remove" && mpArgs) {
484
+ await callbacks.marketplaceRemove(mpArgs);
485
+ addMessage({
486
+ role: "system",
487
+ content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
488
+ });
489
+ return { handled: true };
490
+ } else if (mpSubcommand === "update" && mpArgs) {
491
+ await callbacks.marketplaceUpdate(mpArgs);
492
+ addMessage({
493
+ role: "system",
494
+ content: `Updated marketplace "${mpArgs}".`
495
+ });
496
+ return { handled: true };
497
+ } else if (mpSubcommand === "list") {
498
+ const sources = await callbacks.marketplaceList();
499
+ if (sources.length === 0) {
500
+ addMessage({ role: "system", content: "No marketplace sources configured." });
501
+ } else {
502
+ const lines = sources.map((s) => ` ${s.name} (${s.type})`);
503
+ addMessage({ role: "system", content: `Marketplace sources:
504
+ ${lines.join("\n")}` });
505
+ }
506
+ return { handled: true };
507
+ } else {
508
+ addMessage({
509
+ role: "system",
510
+ content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
511
+ });
512
+ return { handled: true };
513
+ }
514
+ }
515
+ default:
516
+ addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
517
+ return { handled: true };
518
+ }
519
+ } catch (error) {
520
+ const message = error instanceof Error ? error.message : String(error);
521
+ addMessage({ role: "system", content: `Plugin error: ${message}` });
522
+ return { handled: true };
523
+ }
524
+ }
525
+ async function handleReloadPlugins(addMessage, callbacks) {
526
+ await callbacks.reloadPlugins();
527
+ addMessage({ role: "system", content: "Plugins reload complete." });
528
+ return { handled: true };
529
+ }
530
+ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
406
531
  switch (cmd) {
407
532
  case "help":
408
533
  return handleHelp(addMessage);
@@ -426,10 +551,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
426
551
  return handleReset(addMessage);
427
552
  case "exit":
428
553
  return { handled: true, exitRequested: true };
554
+ case "plugin":
555
+ if (pluginCallbacks) {
556
+ return handlePluginCommand(args, addMessage, pluginCallbacks);
557
+ }
558
+ addMessage({ role: "system", content: "Plugin management is not available." });
559
+ return { handled: true };
560
+ case "reload-plugins":
561
+ if (pluginCallbacks) {
562
+ return handleReloadPlugins(addMessage, pluginCallbacks);
563
+ }
564
+ addMessage({ role: "system", content: "Plugin management is not available." });
565
+ return { handled: true };
429
566
  default: {
430
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
431
- if (skillCmd) {
432
- addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
567
+ const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
568
+ if (dynamicCmd) {
569
+ addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
433
570
  return { handled: false };
434
571
  }
435
572
  addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
@@ -440,14 +577,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
440
577
 
441
578
  // src/ui/hooks/useSlashCommands.ts
442
579
  var EXIT_DELAY_MS = 500;
443
- function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
580
+ function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks) {
444
581
  return (0, import_react3.useCallback)(
445
582
  async (input) => {
446
583
  const parts = input.slice(1).split(/\s+/);
447
584
  const cmd = parts[0]?.toLowerCase() ?? "";
448
585
  const args = parts.slice(1).join(" ");
449
586
  const clearMessages = () => setMessages([]);
450
- const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
587
+ const result = await executeSlashCommand(
588
+ cmd,
589
+ args,
590
+ session,
591
+ addMessage,
592
+ clearMessages,
593
+ registry,
594
+ pluginCallbacks
595
+ );
451
596
  if (result.pendingModelId) {
452
597
  pendingModelChangeRef.current = result.pendingModelId;
453
598
  setPendingModelId(result.pendingModelId);
@@ -457,7 +602,16 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
457
602
  }
458
603
  return result.handled;
459
604
  },
460
- [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
605
+ [
606
+ session,
607
+ addMessage,
608
+ setMessages,
609
+ exit,
610
+ registry,
611
+ pendingModelChangeRef,
612
+ setPendingModelId,
613
+ pluginCallbacks
614
+ ]
461
615
  );
462
616
  }
463
617
 
@@ -466,7 +620,7 @@ var import_react4 = require("react");
466
620
 
467
621
  // src/utils/tool-call-extractor.ts
468
622
  var TOOL_ARG_MAX_LENGTH = 80;
469
- var TOOL_ARG_TRUNCATE_LENGTH = 77;
623
+ var TAIL_KEEP2 = 30;
470
624
  function extractToolCalls(history, startIndex) {
471
625
  const lines = [];
472
626
  for (let i = startIndex; i < history.length; i++) {
@@ -474,7 +628,7 @@ function extractToolCalls(history, startIndex) {
474
628
  if (msg.role === "assistant" && msg.toolCalls) {
475
629
  for (const tc of msg.toolCalls) {
476
630
  const value = parseFirstArgValue(tc.function.arguments);
477
- const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
631
+ const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_MAX_LENGTH - TAIL_KEEP2 - 3) + "..." + value.slice(-TAIL_KEEP2) : value;
478
632
  lines.push(`${tc.function.name}(${truncated})`);
479
633
  }
480
634
  }
@@ -492,16 +646,60 @@ function parseFirstArgValue(argsJson) {
492
646
  }
493
647
 
494
648
  // src/utils/skill-prompt.ts
495
- function buildSkillPrompt(input, registry) {
649
+ var import_node_child_process = require("child_process");
650
+ function substituteVariables(content, args, context) {
651
+ const argParts = args ? args.split(/\s+/) : [];
652
+ let result = content;
653
+ result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
654
+ return argParts[Number(index)] ?? "";
655
+ });
656
+ result = result.replace(/\$ARGUMENTS/g, args);
657
+ result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
658
+ return argParts[Number(digit)] ?? "";
659
+ });
660
+ result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
661
+ result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
662
+ return result;
663
+ }
664
+ async function preprocessShellCommands(content) {
665
+ const shellPattern = /!`([^`]+)`/g;
666
+ if (!shellPattern.test(content)) {
667
+ return content;
668
+ }
669
+ shellPattern.lastIndex = 0;
670
+ let result = content;
671
+ let match;
672
+ const matches = [];
673
+ while ((match = shellPattern.exec(content)) !== null) {
674
+ matches.push({ full: match[0], command: match[1] });
675
+ }
676
+ for (const { full, command } of matches) {
677
+ let output = "";
678
+ try {
679
+ output = (0, import_node_child_process.execSync)(command, {
680
+ timeout: 5e3,
681
+ encoding: "utf-8",
682
+ stdio: ["pipe", "pipe", "pipe"]
683
+ }).trimEnd();
684
+ } catch {
685
+ output = "";
686
+ }
687
+ result = result.replace(full, output);
688
+ }
689
+ return result;
690
+ }
691
+ async function buildSkillPrompt(input, registry, context) {
496
692
  const parts = input.slice(1).split(/\s+/);
497
693
  const cmd = parts[0]?.toLowerCase() ?? "";
498
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
694
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
499
695
  if (!skillCmd) return null;
500
696
  const args = parts.slice(1).join(" ").trim();
501
697
  const userInstruction = args || skillCmd.description;
502
698
  if (skillCmd.skillContent) {
699
+ let processed = await preprocessShellCommands(skillCmd.skillContent);
700
+ processed = substituteVariables(processed, args, context);
503
701
  return `<skill name="${cmd}">
504
- ${skillCmd.skillContent}
702
+ ${processed}
505
703
  </skill>
506
704
 
507
705
  Execute the "${cmd}" skill: ${userInstruction}`;
@@ -514,12 +712,12 @@ function syncContextState(session, setter) {
514
712
  const ctx = session.getContextState();
515
713
  setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
516
714
  }
517
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
715
+ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
518
716
  setIsThinking(true);
519
717
  clearStreamingText();
520
718
  const historyBefore = session.getHistory().length;
521
719
  try {
522
- const response = await session.run(prompt);
720
+ const response = await session.run(prompt, rawInput);
523
721
  clearStreamingText();
524
722
  const history = session.getHistory();
525
723
  const toolLines = extractToolCalls(
@@ -527,7 +725,11 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
527
725
  historyBefore
528
726
  );
529
727
  if (toolLines.length > 0) {
530
- addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
728
+ addMessage({
729
+ role: "tool",
730
+ content: toolLines.join("\n"),
731
+ toolName: `${toolLines.length} tools`
732
+ });
531
733
  }
532
734
  addMessage({ role: "assistant", content: response || "(empty response)" });
533
735
  syncContextState(session, setContextState);
@@ -552,19 +754,48 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
552
754
  syncContextState(session, setContextState);
553
755
  return;
554
756
  }
555
- const prompt = buildSkillPrompt(input, registry);
757
+ const prompt = await buildSkillPrompt(input, registry);
556
758
  if (!prompt) return;
557
- return runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState);
759
+ const cmdName = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
760
+ const qualifiedName = registry.resolveQualifiedName(cmdName);
761
+ const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmdName.length)}` : input;
762
+ return runSessionPrompt(
763
+ prompt,
764
+ session,
765
+ addMessage,
766
+ clearStreamingText,
767
+ setIsThinking,
768
+ setContextState,
769
+ hookInput
770
+ );
558
771
  }
559
772
  addMessage({ role: "user", content: input });
560
- return runSessionPrompt(input, session, addMessage, clearStreamingText, setIsThinking, setContextState);
773
+ return runSessionPrompt(
774
+ input,
775
+ session,
776
+ addMessage,
777
+ clearStreamingText,
778
+ setIsThinking,
779
+ setContextState
780
+ );
561
781
  },
562
- [session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry]
782
+ [
783
+ session,
784
+ addMessage,
785
+ handleSlashCommand,
786
+ clearStreamingText,
787
+ setIsThinking,
788
+ setContextState,
789
+ registry
790
+ ]
563
791
  );
564
792
  }
565
793
 
566
794
  // src/ui/hooks/useCommandRegistry.ts
567
795
  var import_react5 = require("react");
796
+ var import_node_os2 = require("os");
797
+ var import_node_path3 = require("path");
798
+ var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
568
799
 
569
800
  // src/commands/command-registry.ts
570
801
  var CommandRegistry = class {
@@ -582,6 +813,14 @@ var CommandRegistry = class {
582
813
  const lower = filter.toLowerCase();
583
814
  return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
584
815
  }
816
+ /** Resolve a short name to its fully qualified plugin:name form */
817
+ resolveQualifiedName(shortName) {
818
+ const matches = this.getCommands().filter(
819
+ (c) => c.source === "plugin" && c.name.includes(":") && c.name.endsWith(`:${shortName}`)
820
+ );
821
+ if (matches.length !== 1) return null;
822
+ return matches[0].name;
823
+ }
585
824
  /** Get subcommands for a specific command */
586
825
  getSubcommands(commandName) {
587
826
  const lower = commandName.toLowerCase();
@@ -648,6 +887,23 @@ function createBuiltinCommands() {
648
887
  { name: "cost", description: "Show session info", source: "builtin" },
649
888
  { name: "context", description: "Context window info", source: "builtin" },
650
889
  { name: "permissions", description: "Permission rules", source: "builtin" },
890
+ {
891
+ name: "plugin",
892
+ description: "Manage plugins",
893
+ source: "builtin",
894
+ subcommands: [
895
+ { name: "install", description: "Install a plugin (name@marketplace)", source: "builtin" },
896
+ {
897
+ name: "uninstall",
898
+ description: "Uninstall a plugin (name@marketplace)",
899
+ source: "builtin"
900
+ },
901
+ { name: "enable", description: "Enable a plugin (name@marketplace)", source: "builtin" },
902
+ { name: "disable", description: "Disable a plugin (name@marketplace)", source: "builtin" },
903
+ { name: "marketplace", description: "Manage marketplace sources", source: "builtin" }
904
+ ]
905
+ },
906
+ { name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
651
907
  { name: "reset", description: "Delete settings and exit", source: "builtin" },
652
908
  { name: "exit", description: "Exit CLI", source: "builtin" }
653
909
  ];
@@ -667,25 +923,50 @@ var BuiltinCommandSource = class {
667
923
  var import_node_fs2 = require("fs");
668
924
  var import_node_path2 = require("path");
669
925
  var import_node_os = require("os");
926
+ var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
927
+ var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
928
+ function kebabToCamel(key) {
929
+ return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
930
+ }
670
931
  function parseFrontmatter(content) {
671
932
  const lines = content.split("\n");
672
933
  if (lines[0]?.trim() !== "---") return null;
673
- let name = "";
674
- let description = "";
934
+ const result = {};
675
935
  for (let i = 1; i < lines.length; i++) {
676
936
  const line = lines[i];
677
937
  if (line.trim() === "---") break;
678
- const nameMatch = line.match(/^name:\s*(.+)/);
679
- if (nameMatch) {
680
- name = nameMatch[1].trim();
681
- continue;
682
- }
683
- const descMatch = line.match(/^description:\s*(.+)/);
684
- if (descMatch) {
685
- description = descMatch[1].trim();
938
+ const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
939
+ if (!match) continue;
940
+ const key = match[1];
941
+ const rawValue = match[2].trim();
942
+ const camelKey = kebabToCamel(key);
943
+ if (BOOLEAN_KEYS.has(key)) {
944
+ result[camelKey] = rawValue === "true";
945
+ } else if (LIST_KEYS.has(key)) {
946
+ result[camelKey] = rawValue.split(",").map((s) => s.trim());
947
+ } else {
948
+ result[camelKey] = rawValue;
686
949
  }
687
950
  }
688
- return name ? { name, description } : null;
951
+ return Object.keys(result).length > 0 ? result : null;
952
+ }
953
+ function buildCommand(frontmatter, content, fallbackName) {
954
+ const cmd = {
955
+ name: frontmatter?.name ?? fallbackName,
956
+ description: frontmatter?.description ?? `Skill: ${fallbackName}`,
957
+ source: "skill",
958
+ skillContent: content
959
+ };
960
+ if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
961
+ if (frontmatter?.disableModelInvocation !== void 0)
962
+ cmd.disableModelInvocation = frontmatter.disableModelInvocation;
963
+ if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
964
+ if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
965
+ if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
966
+ if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
967
+ if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
968
+ if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
969
+ return cmd;
689
970
  }
690
971
  function scanSkillsDir(skillsDir) {
691
972
  if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
@@ -697,48 +978,246 @@ function scanSkillsDir(skillsDir) {
697
978
  if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
698
979
  const content = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
699
980
  const frontmatter = parseFrontmatter(content);
700
- commands.push({
701
- name: frontmatter?.name ?? entry.name,
702
- description: frontmatter?.description ?? `Skill: ${entry.name}`,
703
- source: "skill",
704
- skillContent: content
705
- });
981
+ commands.push(buildCommand(frontmatter, content, entry.name));
982
+ }
983
+ return commands;
984
+ }
985
+ function scanCommandsDir(commandsDir) {
986
+ if (!(0, import_node_fs2.existsSync)(commandsDir)) return [];
987
+ const commands = [];
988
+ const entries = (0, import_node_fs2.readdirSync)(commandsDir, { withFileTypes: true });
989
+ for (const entry of entries) {
990
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
991
+ const filePath = (0, import_node_path2.join)(commandsDir, entry.name);
992
+ const content = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
993
+ const frontmatter = parseFrontmatter(content);
994
+ const fallbackName = (0, import_node_path2.basename)(entry.name, ".md");
995
+ commands.push(buildCommand(frontmatter, content, fallbackName));
706
996
  }
707
997
  return commands;
708
998
  }
709
999
  var SkillCommandSource = class {
710
1000
  name = "skill";
711
1001
  cwd;
1002
+ home;
712
1003
  cachedCommands = null;
713
- constructor(cwd) {
1004
+ constructor(cwd, home) {
714
1005
  this.cwd = cwd;
1006
+ this.home = home ?? (0, import_node_os.homedir)();
715
1007
  }
716
1008
  getCommands() {
717
1009
  if (this.cachedCommands) return this.cachedCommands;
718
- const projectSkills = scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"));
719
- const userSkills = scanSkillsDir((0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "skills"));
720
- const seen = new Set(projectSkills.map((cmd) => cmd.name));
721
- const merged = [...projectSkills];
722
- for (const cmd of userSkills) {
723
- if (!seen.has(cmd.name)) {
724
- merged.push(cmd);
1010
+ const sources = [
1011
+ scanSkillsDir((0, import_node_path2.join)(this.cwd, ".claude", "skills")),
1012
+ // 1. project .claude/skills
1013
+ scanCommandsDir((0, import_node_path2.join)(this.cwd, ".claude", "commands")),
1014
+ // 2. project .claude/commands (legacy)
1015
+ scanSkillsDir((0, import_node_path2.join)(this.home, ".robota", "skills")),
1016
+ // 3. user ~/.robota/skills
1017
+ scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"))
1018
+ // 4. project .agents/skills
1019
+ ];
1020
+ const seen = /* @__PURE__ */ new Set();
1021
+ const merged = [];
1022
+ for (const commands of sources) {
1023
+ for (const cmd of commands) {
1024
+ if (!seen.has(cmd.name)) {
1025
+ seen.add(cmd.name);
1026
+ merged.push(cmd);
1027
+ }
725
1028
  }
726
1029
  }
727
1030
  this.cachedCommands = merged;
728
1031
  return this.cachedCommands;
729
1032
  }
1033
+ /** Get skills that models can invoke (excludes disableModelInvocation: true) */
1034
+ getModelInvocableSkills() {
1035
+ return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
1036
+ }
1037
+ /** Get skills that users can invoke (excludes userInvocable: false) */
1038
+ getUserInvocableSkills() {
1039
+ return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
1040
+ }
1041
+ };
1042
+
1043
+ // src/commands/plugin-source.ts
1044
+ var PluginCommandSource = class {
1045
+ name = "plugin";
1046
+ plugins;
1047
+ constructor(plugins) {
1048
+ this.plugins = plugins;
1049
+ }
1050
+ getCommands() {
1051
+ const commands = [];
1052
+ for (const plugin of this.plugins) {
1053
+ for (const skill of plugin.skills) {
1054
+ const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
1055
+ commands.push({
1056
+ name: baseName,
1057
+ description: `(${plugin.manifest.name}) ${skill.description}`,
1058
+ source: "plugin",
1059
+ skillContent: skill.skillContent,
1060
+ pluginDir: plugin.pluginDir
1061
+ });
1062
+ }
1063
+ for (const cmd of plugin.commands) {
1064
+ commands.push({
1065
+ name: cmd.name,
1066
+ description: cmd.description,
1067
+ source: "plugin",
1068
+ skillContent: cmd.skillContent,
1069
+ pluginDir: plugin.pluginDir
1070
+ });
1071
+ }
1072
+ }
1073
+ return commands;
1074
+ }
730
1075
  };
731
1076
 
732
1077
  // src/ui/hooks/useCommandRegistry.ts
1078
+ function buildPluginEnv(plugin) {
1079
+ const dataDir = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
1080
+ return {
1081
+ CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
1082
+ CLAUDE_PLUGIN_PATH: plugin.pluginDir,
1083
+ CLAUDE_PLUGIN_DATA: dataDir
1084
+ };
1085
+ }
1086
+ function mergePluginHooks(plugins) {
1087
+ const merged = {};
1088
+ for (const plugin of plugins) {
1089
+ const hooksObj = plugin.hooks;
1090
+ if (!hooksObj) continue;
1091
+ const pluginEnv = buildPluginEnv(plugin);
1092
+ const innerHooks = hooksObj.hooks ?? hooksObj;
1093
+ for (const [event, groups] of Object.entries(innerHooks)) {
1094
+ if (!Array.isArray(groups)) continue;
1095
+ if (!merged[event]) merged[event] = [];
1096
+ const resolved = groups.map((group) => {
1097
+ const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
1098
+ if (typeof resolved2 === "object" && resolved2 !== null) {
1099
+ resolved2.env = pluginEnv;
1100
+ }
1101
+ return resolved2;
1102
+ });
1103
+ merged[event].push(...resolved);
1104
+ }
1105
+ }
1106
+ return merged;
1107
+ }
1108
+ function resolvePluginRoot(group, pluginDir) {
1109
+ if (typeof group !== "object" || group === null) return group;
1110
+ const obj = group;
1111
+ if (Array.isArray(obj.hooks)) {
1112
+ return {
1113
+ ...obj,
1114
+ hooks: obj.hooks.map((h) => {
1115
+ if (typeof h !== "object" || h === null) return h;
1116
+ const hook = h;
1117
+ if (typeof hook.command === "string") {
1118
+ return {
1119
+ ...hook,
1120
+ command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
1121
+ };
1122
+ }
1123
+ return hook;
1124
+ })
1125
+ };
1126
+ }
1127
+ return group;
1128
+ }
733
1129
  function useCommandRegistry(cwd) {
734
- const registryRef = (0, import_react5.useRef)(null);
735
- if (registryRef.current === null) {
1130
+ const resultRef = (0, import_react5.useRef)(null);
1131
+ if (resultRef.current === null) {
736
1132
  const registry = new CommandRegistry();
737
1133
  registry.addSource(new BuiltinCommandSource());
738
1134
  registry.addSource(new SkillCommandSource(cwd));
739
- registryRef.current = registry;
1135
+ let pluginHooks = {};
1136
+ const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
1137
+ const loader = new import_agent_sdk2.BundlePluginLoader(pluginsDir);
1138
+ try {
1139
+ const plugins = loader.loadPluginsSync();
1140
+ if (plugins.length > 0) {
1141
+ registry.addSource(new PluginCommandSource(plugins));
1142
+ pluginHooks = mergePluginHooks(plugins);
1143
+ }
1144
+ } catch {
1145
+ }
1146
+ resultRef.current = { registry, pluginHooks };
740
1147
  }
741
- return registryRef.current;
1148
+ return resultRef.current;
1149
+ }
1150
+
1151
+ // src/ui/hooks/usePluginCallbacks.ts
1152
+ var import_react6 = require("react");
1153
+ var import_node_os3 = require("os");
1154
+ var import_node_path4 = require("path");
1155
+ var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
1156
+ function usePluginCallbacks(cwd) {
1157
+ return (0, import_react6.useMemo)(() => {
1158
+ const home = (0, import_node_os3.homedir)();
1159
+ const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
1160
+ const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
1161
+ const settingsStore = new import_agent_sdk3.PluginSettingsStore(userSettingsPath);
1162
+ const marketplace = new import_agent_sdk3.MarketplaceClient({ pluginsDir });
1163
+ const installer = new import_agent_sdk3.BundlePluginInstaller({
1164
+ pluginsDir,
1165
+ settingsStore,
1166
+ marketplaceClient: marketplace
1167
+ });
1168
+ const loader = new import_agent_sdk3.BundlePluginLoader(pluginsDir);
1169
+ return {
1170
+ listInstalled: async () => {
1171
+ const plugins = await loader.loadAll();
1172
+ return plugins.map((p) => ({
1173
+ name: p.manifest.name,
1174
+ description: p.manifest.description,
1175
+ enabled: true
1176
+ }));
1177
+ },
1178
+ install: async (pluginId) => {
1179
+ const [name, marketplaceName] = pluginId.split("@");
1180
+ if (!name || !marketplaceName) {
1181
+ throw new Error("Plugin ID must be in format: name@marketplace");
1182
+ }
1183
+ await installer.install(name, marketplaceName);
1184
+ },
1185
+ uninstall: async (pluginId) => {
1186
+ await installer.uninstall(pluginId);
1187
+ },
1188
+ enable: async (pluginId) => {
1189
+ await installer.enable(pluginId);
1190
+ },
1191
+ disable: async (pluginId) => {
1192
+ await installer.disable(pluginId);
1193
+ },
1194
+ marketplaceAdd: async (source) => {
1195
+ if (source.includes("/") && !source.includes(":")) {
1196
+ return marketplace.addMarketplace({ type: "github", repo: source });
1197
+ } else {
1198
+ return marketplace.addMarketplace({ type: "git", url: source });
1199
+ }
1200
+ },
1201
+ marketplaceRemove: async (name) => {
1202
+ const installedFromMarketplace = installer.getPluginsByMarketplace(name);
1203
+ for (const record of installedFromMarketplace) {
1204
+ await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
1205
+ }
1206
+ marketplace.removeMarketplace(name);
1207
+ },
1208
+ marketplaceUpdate: async (name) => {
1209
+ marketplace.updateMarketplace(name);
1210
+ },
1211
+ marketplaceList: async () => {
1212
+ return marketplace.listMarketplaces().map((m) => ({
1213
+ name: m.name,
1214
+ type: m.source.type
1215
+ }));
1216
+ },
1217
+ reloadPlugins: async () => {
1218
+ }
1219
+ };
1220
+ }, [cwd]);
742
1221
  }
743
1222
 
744
1223
  // src/ui/MessageList.tsx
@@ -775,13 +1254,39 @@ function RoleLabel({ role }) {
775
1254
  " "
776
1255
  ] });
777
1256
  case "tool":
778
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "magenta", bold: true, children: [
1257
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", bold: true, children: [
779
1258
  "Tool:",
780
1259
  " "
781
1260
  ] });
782
1261
  }
783
1262
  }
1263
+ function ToolMessage({ message }) {
1264
+ const lines = message.content.split("\n").filter((l) => l.trim());
1265
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
1266
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", bold: true, children: [
1268
+ "Tool:",
1269
+ " "
1270
+ ] }),
1271
+ message.toolName && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
1272
+ "[",
1273
+ message.toolName,
1274
+ "]"
1275
+ ] })
1276
+ ] }),
1277
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: " " }),
1278
+ lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "green", children: [
1279
+ " ",
1280
+ "\u2713",
1281
+ " ",
1282
+ line
1283
+ ] }, i))
1284
+ ] });
1285
+ }
784
1286
  function MessageItem({ message }) {
1287
+ if (message.role === "tool") {
1288
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolMessage, { message });
1289
+ }
785
1290
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
786
1291
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { children: [
787
1292
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoleLabel, { role: message.role }),
@@ -861,11 +1366,11 @@ function StatusBar({
861
1366
  }
862
1367
 
863
1368
  // src/ui/InputArea.tsx
864
- var import_react8 = __toESM(require("react"), 1);
1369
+ var import_react9 = __toESM(require("react"), 1);
865
1370
  var import_ink6 = require("ink");
866
1371
 
867
1372
  // src/ui/CjkTextInput.tsx
868
- var import_react6 = require("react");
1373
+ var import_react7 = require("react");
869
1374
  var import_ink3 = require("ink");
870
1375
  var import_chalk = __toESM(require("chalk"), 1);
871
1376
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -885,9 +1390,9 @@ function CjkTextInput({
885
1390
  focus = true,
886
1391
  showCursor = true
887
1392
  }) {
888
- const valueRef = (0, import_react6.useRef)(value);
889
- const cursorRef = (0, import_react6.useRef)(value.length);
890
- const [, forceRender] = (0, import_react6.useState)(0);
1393
+ const valueRef = (0, import_react7.useRef)(value);
1394
+ const cursorRef = (0, import_react7.useRef)(value.length);
1395
+ const [, forceRender] = (0, import_react7.useState)(0);
891
1396
  if (value !== valueRef.current) {
892
1397
  valueRef.current = value;
893
1398
  if (cursorRef.current > value.length) {
@@ -964,15 +1469,15 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
964
1469
  }
965
1470
 
966
1471
  // src/ui/WaveText.tsx
967
- var import_react7 = require("react");
1472
+ var import_react8 = require("react");
968
1473
  var import_ink4 = require("ink");
969
1474
  var import_jsx_runtime4 = require("react/jsx-runtime");
970
1475
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
971
1476
  var INTERVAL_MS = 400;
972
1477
  var CHARS_PER_GROUP = 4;
973
1478
  function WaveText({ text }) {
974
- const [tick, setTick] = (0, import_react7.useState)(0);
975
- (0, import_react7.useEffect)(() => {
1479
+ const [tick, setTick] = (0, import_react8.useState)(0);
1480
+ (0, import_react8.useEffect)(() => {
976
1481
  const timer = setInterval(() => {
977
1482
  setTick((prev) => prev + 1);
978
1483
  }, INTERVAL_MS);
@@ -1038,16 +1543,16 @@ function parseSlashInput(value) {
1038
1543
  return { isSlash: true, parentCommand: parent, filter: rest };
1039
1544
  }
1040
1545
  function useAutocomplete(value, registry) {
1041
- const [selectedIndex, setSelectedIndex] = (0, import_react8.useState)(0);
1042
- const [dismissed, setDismissed] = (0, import_react8.useState)(false);
1043
- const prevValueRef = import_react8.default.useRef(value);
1546
+ const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(0);
1547
+ const [dismissed, setDismissed] = (0, import_react9.useState)(false);
1548
+ const prevValueRef = import_react9.default.useRef(value);
1044
1549
  if (prevValueRef.current !== value) {
1045
1550
  prevValueRef.current = value;
1046
1551
  if (dismissed) setDismissed(false);
1047
1552
  }
1048
1553
  const parsed = parseSlashInput(value);
1049
1554
  const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
1050
- const filteredCommands = (0, import_react8.useMemo)(() => {
1555
+ const filteredCommands = (0, import_react9.useMemo)(() => {
1051
1556
  if (!registry || !parsed.isSlash || dismissed) return [];
1052
1557
  if (isSubcommandMode) {
1053
1558
  const subs = registry.getSubcommands(parsed.parentCommand);
@@ -1081,7 +1586,7 @@ function useAutocomplete(value, registry) {
1081
1586
  };
1082
1587
  }
1083
1588
  function InputArea({ onSubmit, isDisabled, registry }) {
1084
- const [value, setValue] = (0, import_react8.useState)("");
1589
+ const [value, setValue] = (0, import_react9.useState)("");
1085
1590
  const {
1086
1591
  showPopup,
1087
1592
  filteredCommands,
@@ -1090,7 +1595,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1090
1595
  isSubcommandMode,
1091
1596
  setShowPopup
1092
1597
  } = useAutocomplete(value, registry);
1093
- const handleSubmit = (0, import_react8.useCallback)(
1598
+ const handleSubmit = (0, import_react9.useCallback)(
1094
1599
  (text) => {
1095
1600
  const trimmed = text.trim();
1096
1601
  if (trimmed.length === 0) return;
@@ -1103,7 +1608,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1103
1608
  },
1104
1609
  [showPopup, filteredCommands, selectedIndex, onSubmit]
1105
1610
  );
1106
- const selectCommand = (0, import_react8.useCallback)(
1611
+ const selectCommand = (0, import_react9.useCallback)(
1107
1612
  (cmd) => {
1108
1613
  const parsed = parseSlashInput(value);
1109
1614
  if (parsed.parentCommand) {
@@ -1164,7 +1669,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1164
1669
  }
1165
1670
 
1166
1671
  // src/ui/ConfirmPrompt.tsx
1167
- var import_react9 = require("react");
1672
+ var import_react10 = require("react");
1168
1673
  var import_ink7 = require("ink");
1169
1674
  var import_jsx_runtime7 = require("react/jsx-runtime");
1170
1675
  function ConfirmPrompt({
@@ -1172,9 +1677,9 @@ function ConfirmPrompt({
1172
1677
  options = ["Yes", "No"],
1173
1678
  onSelect
1174
1679
  }) {
1175
- const [selected, setSelected] = (0, import_react9.useState)(0);
1176
- const resolvedRef = (0, import_react9.useRef)(false);
1177
- const doSelect = (0, import_react9.useCallback)(
1680
+ const [selected, setSelected] = (0, import_react10.useState)(0);
1681
+ const resolvedRef = (0, import_react10.useRef)(false);
1682
+ const doSelect = (0, import_react10.useCallback)(
1178
1683
  (index) => {
1179
1684
  if (resolvedRef.current) return;
1180
1685
  resolvedRef.current = true;
@@ -1207,7 +1712,7 @@ function ConfirmPrompt({
1207
1712
  }
1208
1713
 
1209
1714
  // src/ui/PermissionPrompt.tsx
1210
- var import_react10 = __toESM(require("react"), 1);
1715
+ var import_react11 = __toESM(require("react"), 1);
1211
1716
  var import_ink8 = require("ink");
1212
1717
  var import_jsx_runtime8 = require("react/jsx-runtime");
1213
1718
  var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
@@ -1217,15 +1722,15 @@ function formatArgs(args) {
1217
1722
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
1218
1723
  }
1219
1724
  function PermissionPrompt({ request }) {
1220
- const [selected, setSelected] = import_react10.default.useState(0);
1221
- const resolvedRef = import_react10.default.useRef(false);
1222
- const prevRequestRef = import_react10.default.useRef(request);
1725
+ const [selected, setSelected] = import_react11.default.useState(0);
1726
+ const resolvedRef = import_react11.default.useRef(false);
1727
+ const prevRequestRef = import_react11.default.useRef(request);
1223
1728
  if (prevRequestRef.current !== request) {
1224
1729
  prevRequestRef.current = request;
1225
1730
  resolvedRef.current = false;
1226
1731
  setSelected(0);
1227
1732
  }
1228
- const doResolve = import_react10.default.useCallback(
1733
+ const doResolve = import_react11.default.useCallback(
1229
1734
  (index) => {
1230
1735
  if (resolvedRef.current) return;
1231
1736
  resolvedRef.current = true;
@@ -1273,6 +1778,12 @@ function PermissionPrompt({ request }) {
1273
1778
  // src/ui/StreamingIndicator.tsx
1274
1779
  var import_ink9 = require("ink");
1275
1780
  var import_jsx_runtime9 = require("react/jsx-runtime");
1781
+ function getToolStyle(t) {
1782
+ if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
1783
+ if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
1784
+ if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
1785
+ return { color: "green", icon: "\u2713", strikethrough: false };
1786
+ }
1276
1787
  function StreamingIndicator({ text, activeTools }) {
1277
1788
  const hasTools = activeTools.length > 0;
1278
1789
  const hasText = text.length > 0;
@@ -1281,17 +1792,20 @@ function StreamingIndicator({ text, activeTools }) {
1281
1792
  }
1282
1793
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
1283
1794
  hasTools && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
1284
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "gray", bold: true, children: "Tools:" }),
1795
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "white", bold: true, children: "Tools:" }),
1285
1796
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
1286
- activeTools.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: t.isRunning ? "yellow" : "green", children: [
1287
- " ",
1288
- t.isRunning ? "\u27F3" : "\u2713",
1289
- " ",
1290
- t.toolName,
1291
- "(",
1292
- t.firstArg,
1293
- ")"
1294
- ] }, `${t.toolName}-${i}`))
1797
+ activeTools.map((t, i) => {
1798
+ const { color, icon, strikethrough } = getToolStyle(t);
1799
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color, strikethrough, children: [
1800
+ " ",
1801
+ icon,
1802
+ " ",
1803
+ t.toolName,
1804
+ "(",
1805
+ t.firstArg,
1806
+ ")"
1807
+ ] }, `${t.toolName}-${i}`);
1808
+ })
1295
1809
  ] }),
1296
1810
  hasText && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
1297
1811
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: "Robota:" }),
@@ -1304,20 +1818,46 @@ function StreamingIndicator({ text, activeTools }) {
1304
1818
  // src/ui/App.tsx
1305
1819
  var import_jsx_runtime10 = require("react/jsx-runtime");
1306
1820
  var EXIT_DELAY_MS2 = 500;
1821
+ function mergeHooksIntoConfig(configHooks, pluginHooks) {
1822
+ const pluginKeys = Object.keys(pluginHooks);
1823
+ if (pluginKeys.length === 0) return configHooks;
1824
+ const merged = {};
1825
+ for (const [event, groups] of Object.entries(pluginHooks)) {
1826
+ merged[event] = [...groups];
1827
+ }
1828
+ if (configHooks) {
1829
+ for (const [event, groups] of Object.entries(configHooks)) {
1830
+ if (!Array.isArray(groups)) continue;
1831
+ if (!merged[event]) merged[event] = [];
1832
+ merged[event].push(...groups);
1833
+ }
1834
+ }
1835
+ return merged;
1836
+ }
1307
1837
  function App(props) {
1308
1838
  const { exit } = (0, import_ink10.useApp)();
1309
- const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
1839
+ const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
1840
+ const configWithPluginHooks = {
1841
+ ...props.config,
1842
+ hooks: mergeHooksIntoConfig(
1843
+ props.config.hooks,
1844
+ pluginHooks
1845
+ )
1846
+ };
1847
+ const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
1848
+ { ...props, config: configWithPluginHooks }
1849
+ );
1310
1850
  const { messages, setMessages, addMessage } = useMessages();
1311
- const [isThinking, setIsThinking] = (0, import_react11.useState)(false);
1851
+ const [isThinking, setIsThinking] = (0, import_react12.useState)(false);
1312
1852
  const initialCtx = session.getContextState();
1313
- const [contextState, setContextState] = (0, import_react11.useState)({
1853
+ const [contextState, setContextState] = (0, import_react12.useState)({
1314
1854
  percentage: initialCtx.usedPercentage,
1315
1855
  usedTokens: initialCtx.usedTokens,
1316
1856
  maxTokens: initialCtx.maxTokens
1317
1857
  });
1318
- const registry = useCommandRegistry(props.cwd ?? process.cwd());
1319
- const pendingModelChangeRef = (0, import_react11.useRef)(null);
1320
- const [pendingModelId, setPendingModelId] = (0, import_react11.useState)(null);
1858
+ const pendingModelChangeRef = (0, import_react12.useRef)(null);
1859
+ const [pendingModelId, setPendingModelId] = (0, import_react12.useState)(null);
1860
+ const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
1321
1861
  const handleSlashCommand = useSlashCommands(
1322
1862
  session,
1323
1863
  addMessage,
@@ -1325,7 +1865,8 @@ function App(props) {
1325
1865
  exit,
1326
1866
  registry,
1327
1867
  pendingModelChangeRef,
1328
- setPendingModelId
1868
+ setPendingModelId,
1869
+ pluginCallbacks
1329
1870
  );
1330
1871
  const handleSubmit = useSubmitHandler(
1331
1872
  session,
@@ -1441,23 +1982,24 @@ function renderApp(options) {
1441
1982
 
1442
1983
  // src/cli.ts
1443
1984
  var import_meta = {};
1444
- function hasValidSettingsFile(filePath) {
1445
- if (!(0, import_node_fs3.existsSync)(filePath)) return false;
1985
+ function checkSettingsFile(filePath) {
1986
+ if (!(0, import_node_fs3.existsSync)(filePath)) return "missing";
1446
1987
  try {
1447
1988
  const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
1448
- if (raw.length === 0) return false;
1989
+ if (raw.length === 0) return "incomplete";
1449
1990
  const parsed = JSON.parse(raw);
1450
1991
  const provider = parsed.provider;
1451
- return !!provider?.apiKey;
1992
+ if (!provider?.apiKey) return "incomplete";
1993
+ return "valid";
1452
1994
  } catch {
1453
- return false;
1995
+ return "corrupt";
1454
1996
  }
1455
1997
  }
1456
1998
  function readVersion() {
1457
1999
  try {
1458
2000
  const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
1459
- const dir = (0, import_node_path3.dirname)(thisFile);
1460
- const candidates = [(0, import_node_path3.join)(dir, "..", "..", "package.json"), (0, import_node_path3.join)(dir, "..", "package.json")];
2001
+ const dir = (0, import_node_path5.dirname)(thisFile);
2002
+ const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
1461
2003
  for (const pkgPath of candidates) {
1462
2004
  try {
1463
2005
  const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
@@ -1510,14 +2052,36 @@ function promptInput(label, masked = false) {
1510
2052
  }
1511
2053
  async function ensureConfig(cwd) {
1512
2054
  const userPath = getUserSettingsPath();
1513
- const projectPath = (0, import_node_path3.join)(cwd, ".robota", "settings.json");
1514
- const localPath = (0, import_node_path3.join)(cwd, ".robota", "settings.local.json");
1515
- if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
2055
+ const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
2056
+ const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
2057
+ const paths = [userPath, projectPath, localPath];
2058
+ const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
2059
+ if (checks.some((c) => c.status === "valid")) {
1516
2060
  return;
1517
2061
  }
2062
+ const corrupt = checks.filter((c) => c.status === "corrupt");
2063
+ const incomplete = checks.filter((c) => c.status === "incomplete");
1518
2064
  process.stdout.write("\n");
1519
- process.stdout.write(" Welcome to Robota CLI!\n");
1520
- process.stdout.write(" No configuration found. Let's set up.\n");
2065
+ if (corrupt.length > 0) {
2066
+ for (const c of corrupt) {
2067
+ process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
2068
+ `);
2069
+ }
2070
+ process.stdout.write("\n");
2071
+ }
2072
+ if (incomplete.length > 0) {
2073
+ for (const c of incomplete) {
2074
+ process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
2075
+ `);
2076
+ }
2077
+ process.stdout.write("\n");
2078
+ }
2079
+ if (corrupt.length === 0 && incomplete.length === 0) {
2080
+ process.stdout.write(" Welcome to Robota CLI!\n");
2081
+ process.stdout.write(" No configuration found. Let's set up.\n");
2082
+ } else {
2083
+ process.stdout.write(" Reconfiguring...\n");
2084
+ }
1521
2085
  process.stdout.write("\n");
1522
2086
  const apiKey = await promptInput(" Anthropic API key: ", true);
1523
2087
  if (!apiKey) {
@@ -1525,7 +2089,7 @@ async function ensureConfig(cwd) {
1525
2089
  process.exit(1);
1526
2090
  }
1527
2091
  const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
1528
- const settingsDir = (0, import_node_path3.dirname)(userPath);
2092
+ const settingsDir = (0, import_node_path5.dirname)(userPath);
1529
2093
  (0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
1530
2094
  const settings = {
1531
2095
  provider: {
@@ -1566,9 +2130,9 @@ async function startCli() {
1566
2130
  const cwd = process.cwd();
1567
2131
  await ensureConfig(cwd);
1568
2132
  const [config, context, projectInfo] = await Promise.all([
1569
- (0, import_agent_sdk2.loadConfig)(cwd),
1570
- (0, import_agent_sdk2.loadContext)(cwd),
1571
- (0, import_agent_sdk2.detectProject)(cwd)
2133
+ (0, import_agent_sdk4.loadConfig)(cwd),
2134
+ (0, import_agent_sdk4.loadContext)(cwd),
2135
+ (0, import_agent_sdk4.detectProject)(cwd)
1572
2136
  ]);
1573
2137
  if (args.model !== void 0) {
1574
2138
  config.provider.model = args.model;
@@ -1576,7 +2140,7 @@ async function startCli() {
1576
2140
  if (args.language !== void 0) {
1577
2141
  config.language = args.language;
1578
2142
  }
1579
- const sessionStore = new import_agent_sdk2.SessionStore();
2143
+ const sessionStore = new import_agent_sdk4.SessionStore();
1580
2144
  if (args.printMode) {
1581
2145
  const prompt = args.positional.join(" ").trim();
1582
2146
  if (prompt.length === 0) {
@@ -1584,15 +2148,15 @@ async function startCli() {
1584
2148
  process.exit(1);
1585
2149
  }
1586
2150
  const terminal = new PrintTerminal();
1587
- const paths = (0, import_agent_sdk2.projectPaths)(cwd);
1588
- const session = (0, import_agent_sdk2.createSession)({
2151
+ const paths = (0, import_agent_sdk4.projectPaths)(cwd);
2152
+ const session = (0, import_agent_sdk4.createSession)({
1589
2153
  config,
1590
2154
  context,
1591
2155
  terminal,
1592
- sessionLogger: new import_agent_sdk2.FileSessionLogger(paths.logs),
2156
+ sessionLogger: new import_agent_sdk4.FileSessionLogger(paths.logs),
1593
2157
  projectInfo,
1594
2158
  permissionMode: args.permissionMode,
1595
- promptForApproval: import_agent_sdk3.promptForApproval
2159
+ promptForApproval: import_agent_sdk5.promptForApproval
1596
2160
  });
1597
2161
  const response = await session.run(prompt);
1598
2162
  process.stdout.write(response + "\n");