@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 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 import_node_path3 = require("path");
28
+ var import_node_path5 = require("path");
29
29
  var import_node_url = require("url");
30
- var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
31
- var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
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 import_react11 = require("react");
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 TOOL_ARG_TRUNCATE_AT = 77;
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, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
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) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
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 { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
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 executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry) {
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 skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
415
- if (skillCmd) {
416
- addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
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(cmd, args, session, addMessage, clearMessages, registry);
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
- [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
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 TOOL_ARG_TRUNCATE_LENGTH = 77;
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, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
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
- function buildSkillPrompt(input, registry) {
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
- ${skillCmd.skillContent}
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({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
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
- return runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState);
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(input, session, addMessage, clearStreamingText, setIsThinking, setContextState);
757
+ return runSessionPrompt(
758
+ input,
759
+ session,
760
+ addMessage,
761
+ clearStreamingText,
762
+ setIsThinking,
763
+ setContextState
764
+ );
545
765
  },
546
- [session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry]
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
- let name = "";
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 nameMatch = line.match(/^name:\s*(.+)/);
663
- if (nameMatch) {
664
- name = nameMatch[1].trim();
665
- continue;
666
- }
667
- const descMatch = line.match(/^description:\s*(.+)/);
668
- if (descMatch) {
669
- description = descMatch[1].trim();
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 name ? { name, description } : null;
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
- name: frontmatter?.name ?? entry.name,
686
- description: frontmatter?.description ?? `Skill: ${entry.name}`,
687
- source: "skill",
688
- skillContent: content
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 projectSkills = scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"));
703
- const userSkills = scanSkillsDir((0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "skills"));
704
- const seen = new Set(projectSkills.map((cmd) => cmd.name));
705
- const merged = [...projectSkills];
706
- for (const cmd of userSkills) {
707
- if (!seen.has(cmd.name)) {
708
- merged.push(cmd);
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 registryRef = (0, import_react5.useRef)(null);
719
- if (registryRef.current === null) {
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
- registryRef.current = registry;
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 registryRef.current;
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: "magenta", bold: true, children: [
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 import_react8 = __toESM(require("react"), 1);
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 import_react6 = require("react");
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, import_react6.useRef)(value);
873
- const cursorRef = (0, import_react6.useRef)(value.length);
874
- const [, forceRender] = (0, import_react6.useState)(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 import_react7 = require("react");
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, import_react7.useState)(0);
959
- (0, import_react7.useEffect)(() => {
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, import_react8.useState)(0);
1026
- const [dismissed, setDismissed] = (0, import_react8.useState)(false);
1027
- const prevValueRef = import_react8.default.useRef(value);
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, import_react8.useMemo)(() => {
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, import_react8.useState)("");
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, import_react8.useCallback)(
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, import_react8.useCallback)(
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 import_react9 = require("react");
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, import_react9.useState)(0);
1160
- const resolvedRef = (0, import_react9.useRef)(false);
1161
- const doSelect = (0, import_react9.useCallback)(
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 import_react10 = __toESM(require("react"), 1);
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] = import_react10.default.useState(0);
1205
- const resolvedRef = import_react10.default.useRef(false);
1206
- const prevRequestRef = import_react10.default.useRef(request);
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 = import_react10.default.useCallback(
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: "gray", bold: true, children: "Tools:" }),
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) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: t.isRunning ? "yellow" : "green", children: [
1271
- " ",
1272
- t.isRunning ? "\u27F3" : "\u2713",
1273
- " ",
1274
- t.toolName,
1275
- "(",
1276
- t.firstArg,
1277
- ")"
1278
- ] }, `${t.toolName}-${i}`))
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 { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
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, import_react11.useState)(false);
1835
+ const [isThinking, setIsThinking] = (0, import_react12.useState)(false);
1296
1836
  const initialCtx = session.getContextState();
1297
- const [contextState, setContextState] = (0, import_react11.useState)({
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 registry = useCommandRegistry(props.cwd ?? process.cwd());
1303
- const pendingModelChangeRef = (0, import_react11.useRef)(null);
1304
- const [pendingModelId, setPendingModelId] = (0, import_react11.useState)(null);
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 hasValidSettingsFile(filePath) {
1429
- if (!(0, import_node_fs3.existsSync)(filePath)) return false;
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 false;
1973
+ if (raw.length === 0) return "incomplete";
1433
1974
  const parsed = JSON.parse(raw);
1434
1975
  const provider = parsed.provider;
1435
- return !!provider?.apiKey;
1976
+ if (!provider?.apiKey) return "incomplete";
1977
+ return "valid";
1436
1978
  } catch {
1437
- return false;
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, import_node_path3.dirname)(thisFile);
1444
- const candidates = [(0, import_node_path3.join)(dir, "..", "..", "package.json"), (0, import_node_path3.join)(dir, "..", "package.json")];
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, import_node_path3.join)(cwd, ".robota", "settings.json");
1498
- const localPath = (0, import_node_path3.join)(cwd, ".robota", "settings.local.json");
1499
- if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
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
- process.stdout.write(" Welcome to Robota CLI!\n");
1504
- process.stdout.write(" No configuration found. Let's set up.\n");
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, import_node_path3.dirname)(userPath);
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, import_agent_sdk2.loadConfig)(cwd),
1554
- (0, import_agent_sdk2.loadContext)(cwd),
1555
- (0, import_agent_sdk2.detectProject)(cwd)
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 import_agent_sdk2.SessionStore();
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, import_agent_sdk2.projectPaths)(cwd);
1572
- const session = (0, import_agent_sdk2.createSession)({
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 import_agent_sdk2.FileSessionLogger(paths.logs),
2140
+ sessionLogger: new import_agent_sdk4.FileSessionLogger(paths.logs),
1577
2141
  projectInfo,
1578
2142
  permissionMode: args.permissionMode,
1579
- promptForApproval: import_agent_sdk3.promptForApproval
2143
+ promptForApproval: import_agent_sdk5.promptForApproval
1580
2144
  });
1581
2145
  const response = await session.run(prompt);
1582
2146
  process.stdout.write(response + "\n");