@robota-sdk/agent-cli 3.0.0-beta.23 → 3.0.0-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/bin.cjs +626 -104
- package/dist/node/bin.js +1 -1
- package/dist/node/{chunk-EFSOR6D7.js → chunk-GJ3LG37H.js} +601 -74
- package/dist/node/index.cjs +631 -109
- package/dist/node/index.js +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
2
|
import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3
|
-
import { join as
|
|
3
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import {
|
|
6
6
|
loadConfig,
|
|
@@ -369,7 +369,122 @@ function handleReset(addMessage) {
|
|
|
369
369
|
}
|
|
370
370
|
return { handled: true, exitRequested: true };
|
|
371
371
|
}
|
|
372
|
-
async function
|
|
372
|
+
async function handlePluginCommand(args, addMessage, callbacks) {
|
|
373
|
+
const parts = args.trim().split(/\s+/);
|
|
374
|
+
const subcommand = parts[0] ?? "";
|
|
375
|
+
const subArgs = parts.slice(1).join(" ").trim();
|
|
376
|
+
try {
|
|
377
|
+
switch (subcommand) {
|
|
378
|
+
case "":
|
|
379
|
+
case void 0: {
|
|
380
|
+
const plugins = await callbacks.listInstalled();
|
|
381
|
+
if (plugins.length === 0) {
|
|
382
|
+
addMessage({ role: "system", content: "No plugins installed." });
|
|
383
|
+
} else {
|
|
384
|
+
const lines = plugins.map(
|
|
385
|
+
(p) => ` ${p.name} \u2014 ${p.description} [${p.enabled ? "enabled" : "disabled"}]`
|
|
386
|
+
);
|
|
387
|
+
addMessage({ role: "system", content: `Installed plugins:
|
|
388
|
+
${lines.join("\n")}` });
|
|
389
|
+
}
|
|
390
|
+
return { handled: true };
|
|
391
|
+
}
|
|
392
|
+
case "install": {
|
|
393
|
+
if (!subArgs) {
|
|
394
|
+
addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
|
|
395
|
+
return { handled: true };
|
|
396
|
+
}
|
|
397
|
+
await callbacks.install(subArgs);
|
|
398
|
+
addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
|
|
399
|
+
return { handled: true };
|
|
400
|
+
}
|
|
401
|
+
case "uninstall": {
|
|
402
|
+
if (!subArgs) {
|
|
403
|
+
addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
|
|
404
|
+
return { handled: true };
|
|
405
|
+
}
|
|
406
|
+
await callbacks.uninstall(subArgs);
|
|
407
|
+
addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
|
|
408
|
+
return { handled: true };
|
|
409
|
+
}
|
|
410
|
+
case "enable": {
|
|
411
|
+
if (!subArgs) {
|
|
412
|
+
addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
|
|
413
|
+
return { handled: true };
|
|
414
|
+
}
|
|
415
|
+
await callbacks.enable(subArgs);
|
|
416
|
+
addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
|
|
417
|
+
return { handled: true };
|
|
418
|
+
}
|
|
419
|
+
case "disable": {
|
|
420
|
+
if (!subArgs) {
|
|
421
|
+
addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
|
|
422
|
+
return { handled: true };
|
|
423
|
+
}
|
|
424
|
+
await callbacks.disable(subArgs);
|
|
425
|
+
addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
|
|
426
|
+
return { handled: true };
|
|
427
|
+
}
|
|
428
|
+
case "marketplace": {
|
|
429
|
+
const mpParts = subArgs.split(/\s+/);
|
|
430
|
+
const mpSubcommand = mpParts[0] ?? "";
|
|
431
|
+
const mpArgs = mpParts.slice(1).join(" ").trim();
|
|
432
|
+
if (mpSubcommand === "add" && mpArgs) {
|
|
433
|
+
const registeredName = await callbacks.marketplaceAdd(mpArgs);
|
|
434
|
+
addMessage({
|
|
435
|
+
role: "system",
|
|
436
|
+
content: `Added marketplace: "${registeredName}" (from ${mpArgs})
|
|
437
|
+
Install plugins with: /plugin install <name>@${registeredName}`
|
|
438
|
+
});
|
|
439
|
+
return { handled: true };
|
|
440
|
+
} else if (mpSubcommand === "remove" && mpArgs) {
|
|
441
|
+
await callbacks.marketplaceRemove(mpArgs);
|
|
442
|
+
addMessage({
|
|
443
|
+
role: "system",
|
|
444
|
+
content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
|
|
445
|
+
});
|
|
446
|
+
return { handled: true };
|
|
447
|
+
} else if (mpSubcommand === "update" && mpArgs) {
|
|
448
|
+
await callbacks.marketplaceUpdate(mpArgs);
|
|
449
|
+
addMessage({
|
|
450
|
+
role: "system",
|
|
451
|
+
content: `Updated marketplace "${mpArgs}".`
|
|
452
|
+
});
|
|
453
|
+
return { handled: true };
|
|
454
|
+
} else if (mpSubcommand === "list") {
|
|
455
|
+
const sources = await callbacks.marketplaceList();
|
|
456
|
+
if (sources.length === 0) {
|
|
457
|
+
addMessage({ role: "system", content: "No marketplace sources configured." });
|
|
458
|
+
} else {
|
|
459
|
+
const lines = sources.map((s) => ` ${s.name} (${s.type})`);
|
|
460
|
+
addMessage({ role: "system", content: `Marketplace sources:
|
|
461
|
+
${lines.join("\n")}` });
|
|
462
|
+
}
|
|
463
|
+
return { handled: true };
|
|
464
|
+
} else {
|
|
465
|
+
addMessage({
|
|
466
|
+
role: "system",
|
|
467
|
+
content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
|
|
468
|
+
});
|
|
469
|
+
return { handled: true };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
default:
|
|
473
|
+
addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
|
|
474
|
+
return { handled: true };
|
|
475
|
+
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
478
|
+
addMessage({ role: "system", content: `Plugin error: ${message}` });
|
|
479
|
+
return { handled: true };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async function handleReloadPlugins(addMessage, callbacks) {
|
|
483
|
+
await callbacks.reloadPlugins();
|
|
484
|
+
addMessage({ role: "system", content: "Plugins reload complete." });
|
|
485
|
+
return { handled: true };
|
|
486
|
+
}
|
|
487
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
|
|
373
488
|
switch (cmd) {
|
|
374
489
|
case "help":
|
|
375
490
|
return handleHelp(addMessage);
|
|
@@ -393,10 +508,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
393
508
|
return handleReset(addMessage);
|
|
394
509
|
case "exit":
|
|
395
510
|
return { handled: true, exitRequested: true };
|
|
511
|
+
case "plugin":
|
|
512
|
+
if (pluginCallbacks) {
|
|
513
|
+
return handlePluginCommand(args, addMessage, pluginCallbacks);
|
|
514
|
+
}
|
|
515
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
516
|
+
return { handled: true };
|
|
517
|
+
case "reload-plugins":
|
|
518
|
+
if (pluginCallbacks) {
|
|
519
|
+
return handleReloadPlugins(addMessage, pluginCallbacks);
|
|
520
|
+
}
|
|
521
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
522
|
+
return { handled: true };
|
|
396
523
|
default: {
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
399
|
-
addMessage({ role: "system", content: `Invoking
|
|
524
|
+
const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
525
|
+
if (dynamicCmd) {
|
|
526
|
+
addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
|
|
400
527
|
return { handled: false };
|
|
401
528
|
}
|
|
402
529
|
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
@@ -407,14 +534,22 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
407
534
|
|
|
408
535
|
// src/ui/hooks/useSlashCommands.ts
|
|
409
536
|
var EXIT_DELAY_MS = 500;
|
|
410
|
-
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
537
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks) {
|
|
411
538
|
return useCallback3(
|
|
412
539
|
async (input) => {
|
|
413
540
|
const parts = input.slice(1).split(/\s+/);
|
|
414
541
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
415
542
|
const args = parts.slice(1).join(" ");
|
|
416
543
|
const clearMessages = () => setMessages([]);
|
|
417
|
-
const result = await executeSlashCommand(
|
|
544
|
+
const result = await executeSlashCommand(
|
|
545
|
+
cmd,
|
|
546
|
+
args,
|
|
547
|
+
session,
|
|
548
|
+
addMessage,
|
|
549
|
+
clearMessages,
|
|
550
|
+
registry,
|
|
551
|
+
pluginCallbacks
|
|
552
|
+
);
|
|
418
553
|
if (result.pendingModelId) {
|
|
419
554
|
pendingModelChangeRef.current = result.pendingModelId;
|
|
420
555
|
setPendingModelId(result.pendingModelId);
|
|
@@ -424,7 +559,16 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
|
|
|
424
559
|
}
|
|
425
560
|
return result.handled;
|
|
426
561
|
},
|
|
427
|
-
[
|
|
562
|
+
[
|
|
563
|
+
session,
|
|
564
|
+
addMessage,
|
|
565
|
+
setMessages,
|
|
566
|
+
exit,
|
|
567
|
+
registry,
|
|
568
|
+
pendingModelChangeRef,
|
|
569
|
+
setPendingModelId,
|
|
570
|
+
pluginCallbacks
|
|
571
|
+
]
|
|
428
572
|
);
|
|
429
573
|
}
|
|
430
574
|
|
|
@@ -459,16 +603,60 @@ function parseFirstArgValue(argsJson) {
|
|
|
459
603
|
}
|
|
460
604
|
|
|
461
605
|
// src/utils/skill-prompt.ts
|
|
462
|
-
|
|
606
|
+
import { execSync } from "child_process";
|
|
607
|
+
function substituteVariables(content, args, context) {
|
|
608
|
+
const argParts = args ? args.split(/\s+/) : [];
|
|
609
|
+
let result = content;
|
|
610
|
+
result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
|
|
611
|
+
return argParts[Number(index)] ?? "";
|
|
612
|
+
});
|
|
613
|
+
result = result.replace(/\$ARGUMENTS/g, args);
|
|
614
|
+
result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
|
|
615
|
+
return argParts[Number(digit)] ?? "";
|
|
616
|
+
});
|
|
617
|
+
result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
|
|
618
|
+
result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
621
|
+
async function preprocessShellCommands(content) {
|
|
622
|
+
const shellPattern = /!`([^`]+)`/g;
|
|
623
|
+
if (!shellPattern.test(content)) {
|
|
624
|
+
return content;
|
|
625
|
+
}
|
|
626
|
+
shellPattern.lastIndex = 0;
|
|
627
|
+
let result = content;
|
|
628
|
+
let match;
|
|
629
|
+
const matches = [];
|
|
630
|
+
while ((match = shellPattern.exec(content)) !== null) {
|
|
631
|
+
matches.push({ full: match[0], command: match[1] });
|
|
632
|
+
}
|
|
633
|
+
for (const { full, command } of matches) {
|
|
634
|
+
let output = "";
|
|
635
|
+
try {
|
|
636
|
+
output = execSync(command, {
|
|
637
|
+
timeout: 5e3,
|
|
638
|
+
encoding: "utf-8",
|
|
639
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
640
|
+
}).trimEnd();
|
|
641
|
+
} catch {
|
|
642
|
+
output = "";
|
|
643
|
+
}
|
|
644
|
+
result = result.replace(full, output);
|
|
645
|
+
}
|
|
646
|
+
return result;
|
|
647
|
+
}
|
|
648
|
+
async function buildSkillPrompt(input, registry, context) {
|
|
463
649
|
const parts = input.slice(1).split(/\s+/);
|
|
464
650
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
465
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
651
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
466
652
|
if (!skillCmd) return null;
|
|
467
653
|
const args = parts.slice(1).join(" ").trim();
|
|
468
654
|
const userInstruction = args || skillCmd.description;
|
|
469
655
|
if (skillCmd.skillContent) {
|
|
656
|
+
let processed = await preprocessShellCommands(skillCmd.skillContent);
|
|
657
|
+
processed = substituteVariables(processed, args, context);
|
|
470
658
|
return `<skill name="${cmd}">
|
|
471
|
-
${
|
|
659
|
+
${processed}
|
|
472
660
|
</skill>
|
|
473
661
|
|
|
474
662
|
Execute the "${cmd}" skill: ${userInstruction}`;
|
|
@@ -481,12 +669,12 @@ function syncContextState(session, setter) {
|
|
|
481
669
|
const ctx = session.getContextState();
|
|
482
670
|
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
483
671
|
}
|
|
484
|
-
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
|
|
672
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
|
|
485
673
|
setIsThinking(true);
|
|
486
674
|
clearStreamingText();
|
|
487
675
|
const historyBefore = session.getHistory().length;
|
|
488
676
|
try {
|
|
489
|
-
const response = await session.run(prompt);
|
|
677
|
+
const response = await session.run(prompt, rawInput);
|
|
490
678
|
clearStreamingText();
|
|
491
679
|
const history = session.getHistory();
|
|
492
680
|
const toolLines = extractToolCalls(
|
|
@@ -494,7 +682,11 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
|
|
|
494
682
|
historyBefore
|
|
495
683
|
);
|
|
496
684
|
if (toolLines.length > 0) {
|
|
497
|
-
addMessage({
|
|
685
|
+
addMessage({
|
|
686
|
+
role: "tool",
|
|
687
|
+
content: toolLines.join("\n"),
|
|
688
|
+
toolName: `${toolLines.length} tools`
|
|
689
|
+
});
|
|
498
690
|
}
|
|
499
691
|
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
500
692
|
syncContextState(session, setContextState);
|
|
@@ -519,19 +711,45 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
519
711
|
syncContextState(session, setContextState);
|
|
520
712
|
return;
|
|
521
713
|
}
|
|
522
|
-
const prompt = buildSkillPrompt(input, registry);
|
|
714
|
+
const prompt = await buildSkillPrompt(input, registry);
|
|
523
715
|
if (!prompt) return;
|
|
524
|
-
return runSessionPrompt(
|
|
716
|
+
return runSessionPrompt(
|
|
717
|
+
prompt,
|
|
718
|
+
session,
|
|
719
|
+
addMessage,
|
|
720
|
+
clearStreamingText,
|
|
721
|
+
setIsThinking,
|
|
722
|
+
setContextState,
|
|
723
|
+
input
|
|
724
|
+
);
|
|
525
725
|
}
|
|
526
726
|
addMessage({ role: "user", content: input });
|
|
527
|
-
return runSessionPrompt(
|
|
727
|
+
return runSessionPrompt(
|
|
728
|
+
input,
|
|
729
|
+
session,
|
|
730
|
+
addMessage,
|
|
731
|
+
clearStreamingText,
|
|
732
|
+
setIsThinking,
|
|
733
|
+
setContextState
|
|
734
|
+
);
|
|
528
735
|
},
|
|
529
|
-
[
|
|
736
|
+
[
|
|
737
|
+
session,
|
|
738
|
+
addMessage,
|
|
739
|
+
handleSlashCommand,
|
|
740
|
+
clearStreamingText,
|
|
741
|
+
setIsThinking,
|
|
742
|
+
setContextState,
|
|
743
|
+
registry
|
|
744
|
+
]
|
|
530
745
|
);
|
|
531
746
|
}
|
|
532
747
|
|
|
533
748
|
// src/ui/hooks/useCommandRegistry.ts
|
|
534
749
|
import { useRef as useRef2 } from "react";
|
|
750
|
+
import { homedir as homedir2 } from "os";
|
|
751
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
752
|
+
import { BundlePluginLoader } from "@robota-sdk/agent-sdk";
|
|
535
753
|
|
|
536
754
|
// src/commands/command-registry.ts
|
|
537
755
|
var CommandRegistry = class {
|
|
@@ -615,6 +833,23 @@ function createBuiltinCommands() {
|
|
|
615
833
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
616
834
|
{ name: "context", description: "Context window info", source: "builtin" },
|
|
617
835
|
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
836
|
+
{
|
|
837
|
+
name: "plugin",
|
|
838
|
+
description: "Manage plugins",
|
|
839
|
+
source: "builtin",
|
|
840
|
+
subcommands: [
|
|
841
|
+
{ name: "install", description: "Install a plugin (name@marketplace)", source: "builtin" },
|
|
842
|
+
{
|
|
843
|
+
name: "uninstall",
|
|
844
|
+
description: "Uninstall a plugin (name@marketplace)",
|
|
845
|
+
source: "builtin"
|
|
846
|
+
},
|
|
847
|
+
{ name: "enable", description: "Enable a plugin (name@marketplace)", source: "builtin" },
|
|
848
|
+
{ name: "disable", description: "Disable a plugin (name@marketplace)", source: "builtin" },
|
|
849
|
+
{ name: "marketplace", description: "Manage marketplace sources", source: "builtin" }
|
|
850
|
+
]
|
|
851
|
+
},
|
|
852
|
+
{ name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
|
|
618
853
|
{ name: "reset", description: "Delete settings and exit", source: "builtin" },
|
|
619
854
|
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
620
855
|
];
|
|
@@ -632,27 +867,52 @@ var BuiltinCommandSource = class {
|
|
|
632
867
|
|
|
633
868
|
// src/commands/skill-source.ts
|
|
634
869
|
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
635
|
-
import { join as join2 } from "path";
|
|
870
|
+
import { join as join2, basename } from "path";
|
|
636
871
|
import { homedir } from "os";
|
|
872
|
+
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
|
|
873
|
+
var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
|
|
874
|
+
function kebabToCamel(key) {
|
|
875
|
+
return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
876
|
+
}
|
|
637
877
|
function parseFrontmatter(content) {
|
|
638
878
|
const lines = content.split("\n");
|
|
639
879
|
if (lines[0]?.trim() !== "---") return null;
|
|
640
|
-
|
|
641
|
-
let description = "";
|
|
880
|
+
const result = {};
|
|
642
881
|
for (let i = 1; i < lines.length; i++) {
|
|
643
882
|
const line = lines[i];
|
|
644
883
|
if (line.trim() === "---") break;
|
|
645
|
-
const
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
884
|
+
const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
|
|
885
|
+
if (!match) continue;
|
|
886
|
+
const key = match[1];
|
|
887
|
+
const rawValue = match[2].trim();
|
|
888
|
+
const camelKey = kebabToCamel(key);
|
|
889
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
890
|
+
result[camelKey] = rawValue === "true";
|
|
891
|
+
} else if (LIST_KEYS.has(key)) {
|
|
892
|
+
result[camelKey] = rawValue.split(",").map((s) => s.trim());
|
|
893
|
+
} else {
|
|
894
|
+
result[camelKey] = rawValue;
|
|
653
895
|
}
|
|
654
896
|
}
|
|
655
|
-
return
|
|
897
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
898
|
+
}
|
|
899
|
+
function buildCommand(frontmatter, content, fallbackName) {
|
|
900
|
+
const cmd = {
|
|
901
|
+
name: frontmatter?.name ?? fallbackName,
|
|
902
|
+
description: frontmatter?.description ?? `Skill: ${fallbackName}`,
|
|
903
|
+
source: "skill",
|
|
904
|
+
skillContent: content
|
|
905
|
+
};
|
|
906
|
+
if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
|
|
907
|
+
if (frontmatter?.disableModelInvocation !== void 0)
|
|
908
|
+
cmd.disableModelInvocation = frontmatter.disableModelInvocation;
|
|
909
|
+
if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
|
|
910
|
+
if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
|
|
911
|
+
if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
|
|
912
|
+
if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
|
|
913
|
+
if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
|
|
914
|
+
if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
|
|
915
|
+
return cmd;
|
|
656
916
|
}
|
|
657
917
|
function scanSkillsDir(skillsDir) {
|
|
658
918
|
if (!existsSync2(skillsDir)) return [];
|
|
@@ -664,48 +924,251 @@ function scanSkillsDir(skillsDir) {
|
|
|
664
924
|
if (!existsSync2(skillFile)) continue;
|
|
665
925
|
const content = readFileSync2(skillFile, "utf-8");
|
|
666
926
|
const frontmatter = parseFrontmatter(content);
|
|
667
|
-
commands.push(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
927
|
+
commands.push(buildCommand(frontmatter, content, entry.name));
|
|
928
|
+
}
|
|
929
|
+
return commands;
|
|
930
|
+
}
|
|
931
|
+
function scanCommandsDir(commandsDir) {
|
|
932
|
+
if (!existsSync2(commandsDir)) return [];
|
|
933
|
+
const commands = [];
|
|
934
|
+
const entries = readdirSync(commandsDir, { withFileTypes: true });
|
|
935
|
+
for (const entry of entries) {
|
|
936
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
937
|
+
const filePath = join2(commandsDir, entry.name);
|
|
938
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
939
|
+
const frontmatter = parseFrontmatter(content);
|
|
940
|
+
const fallbackName = basename(entry.name, ".md");
|
|
941
|
+
commands.push(buildCommand(frontmatter, content, fallbackName));
|
|
673
942
|
}
|
|
674
943
|
return commands;
|
|
675
944
|
}
|
|
676
945
|
var SkillCommandSource = class {
|
|
677
946
|
name = "skill";
|
|
678
947
|
cwd;
|
|
948
|
+
home;
|
|
679
949
|
cachedCommands = null;
|
|
680
|
-
constructor(cwd) {
|
|
950
|
+
constructor(cwd, home) {
|
|
681
951
|
this.cwd = cwd;
|
|
952
|
+
this.home = home ?? homedir();
|
|
682
953
|
}
|
|
683
954
|
getCommands() {
|
|
684
955
|
if (this.cachedCommands) return this.cachedCommands;
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
956
|
+
const sources = [
|
|
957
|
+
scanSkillsDir(join2(this.cwd, ".claude", "skills")),
|
|
958
|
+
// 1. project .claude/skills
|
|
959
|
+
scanCommandsDir(join2(this.cwd, ".claude", "commands")),
|
|
960
|
+
// 2. project .claude/commands (legacy)
|
|
961
|
+
scanSkillsDir(join2(this.home, ".robota", "skills")),
|
|
962
|
+
// 3. user ~/.robota/skills
|
|
963
|
+
scanSkillsDir(join2(this.cwd, ".agents", "skills"))
|
|
964
|
+
// 4. project .agents/skills
|
|
965
|
+
];
|
|
966
|
+
const seen = /* @__PURE__ */ new Set();
|
|
967
|
+
const merged = [];
|
|
968
|
+
for (const commands of sources) {
|
|
969
|
+
for (const cmd of commands) {
|
|
970
|
+
if (!seen.has(cmd.name)) {
|
|
971
|
+
seen.add(cmd.name);
|
|
972
|
+
merged.push(cmd);
|
|
973
|
+
}
|
|
692
974
|
}
|
|
693
975
|
}
|
|
694
976
|
this.cachedCommands = merged;
|
|
695
977
|
return this.cachedCommands;
|
|
696
978
|
}
|
|
979
|
+
/** Get skills that models can invoke (excludes disableModelInvocation: true) */
|
|
980
|
+
getModelInvocableSkills() {
|
|
981
|
+
return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
|
|
982
|
+
}
|
|
983
|
+
/** Get skills that users can invoke (excludes userInvocable: false) */
|
|
984
|
+
getUserInvocableSkills() {
|
|
985
|
+
return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
// src/commands/plugin-source.ts
|
|
990
|
+
var PluginCommandSource = class {
|
|
991
|
+
name = "plugin";
|
|
992
|
+
plugins;
|
|
993
|
+
constructor(plugins) {
|
|
994
|
+
this.plugins = plugins;
|
|
995
|
+
}
|
|
996
|
+
getCommands() {
|
|
997
|
+
const commands = [];
|
|
998
|
+
for (const plugin of this.plugins) {
|
|
999
|
+
for (const skill of plugin.skills) {
|
|
1000
|
+
const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
|
|
1001
|
+
commands.push({
|
|
1002
|
+
name: baseName,
|
|
1003
|
+
description: `${skill.description} (${plugin.manifest.name})`,
|
|
1004
|
+
source: "plugin",
|
|
1005
|
+
skillContent: skill.skillContent,
|
|
1006
|
+
pluginDir: plugin.pluginDir
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
for (const cmd of plugin.commands) {
|
|
1010
|
+
commands.push({
|
|
1011
|
+
name: cmd.name,
|
|
1012
|
+
description: cmd.description,
|
|
1013
|
+
source: "plugin",
|
|
1014
|
+
skillContent: cmd.skillContent,
|
|
1015
|
+
pluginDir: plugin.pluginDir
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return commands;
|
|
1020
|
+
}
|
|
697
1021
|
};
|
|
698
1022
|
|
|
699
1023
|
// src/ui/hooks/useCommandRegistry.ts
|
|
1024
|
+
function buildPluginEnv(plugin) {
|
|
1025
|
+
const dataDir = join3(dirname2(dirname2(plugin.pluginDir)), "data", plugin.manifest.name);
|
|
1026
|
+
return {
|
|
1027
|
+
CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
|
|
1028
|
+
CLAUDE_PLUGIN_PATH: plugin.pluginDir,
|
|
1029
|
+
CLAUDE_PLUGIN_DATA: dataDir
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function mergePluginHooks(plugins) {
|
|
1033
|
+
const merged = {};
|
|
1034
|
+
for (const plugin of plugins) {
|
|
1035
|
+
const hooksObj = plugin.hooks;
|
|
1036
|
+
if (!hooksObj) continue;
|
|
1037
|
+
const pluginEnv = buildPluginEnv(plugin);
|
|
1038
|
+
const innerHooks = hooksObj.hooks ?? hooksObj;
|
|
1039
|
+
for (const [event, groups] of Object.entries(innerHooks)) {
|
|
1040
|
+
if (!Array.isArray(groups)) continue;
|
|
1041
|
+
if (!merged[event]) merged[event] = [];
|
|
1042
|
+
const resolved = groups.map((group) => {
|
|
1043
|
+
const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
|
|
1044
|
+
if (typeof resolved2 === "object" && resolved2 !== null) {
|
|
1045
|
+
resolved2.env = pluginEnv;
|
|
1046
|
+
}
|
|
1047
|
+
return resolved2;
|
|
1048
|
+
});
|
|
1049
|
+
merged[event].push(...resolved);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return merged;
|
|
1053
|
+
}
|
|
1054
|
+
function resolvePluginRoot(group, pluginDir) {
|
|
1055
|
+
if (typeof group !== "object" || group === null) return group;
|
|
1056
|
+
const obj = group;
|
|
1057
|
+
if (Array.isArray(obj.hooks)) {
|
|
1058
|
+
return {
|
|
1059
|
+
...obj,
|
|
1060
|
+
hooks: obj.hooks.map((h) => {
|
|
1061
|
+
if (typeof h !== "object" || h === null) return h;
|
|
1062
|
+
const hook = h;
|
|
1063
|
+
if (typeof hook.command === "string") {
|
|
1064
|
+
return {
|
|
1065
|
+
...hook,
|
|
1066
|
+
command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
return hook;
|
|
1070
|
+
})
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
return group;
|
|
1074
|
+
}
|
|
700
1075
|
function useCommandRegistry(cwd) {
|
|
701
|
-
const
|
|
702
|
-
if (
|
|
1076
|
+
const resultRef = useRef2(null);
|
|
1077
|
+
if (resultRef.current === null) {
|
|
703
1078
|
const registry = new CommandRegistry();
|
|
704
1079
|
registry.addSource(new BuiltinCommandSource());
|
|
705
1080
|
registry.addSource(new SkillCommandSource(cwd));
|
|
706
|
-
|
|
1081
|
+
let pluginHooks = {};
|
|
1082
|
+
const pluginsDir = join3(homedir2(), ".robota", "plugins");
|
|
1083
|
+
const loader = new BundlePluginLoader(pluginsDir);
|
|
1084
|
+
try {
|
|
1085
|
+
const plugins = loader.loadPluginsSync();
|
|
1086
|
+
if (plugins.length > 0) {
|
|
1087
|
+
registry.addSource(new PluginCommandSource(plugins));
|
|
1088
|
+
pluginHooks = mergePluginHooks(plugins);
|
|
1089
|
+
}
|
|
1090
|
+
} catch {
|
|
1091
|
+
}
|
|
1092
|
+
resultRef.current = { registry, pluginHooks };
|
|
707
1093
|
}
|
|
708
|
-
return
|
|
1094
|
+
return resultRef.current;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/ui/hooks/usePluginCallbacks.ts
|
|
1098
|
+
import { useMemo } from "react";
|
|
1099
|
+
import { homedir as homedir3 } from "os";
|
|
1100
|
+
import { join as join4 } from "path";
|
|
1101
|
+
import {
|
|
1102
|
+
PluginSettingsStore,
|
|
1103
|
+
BundlePluginLoader as BundlePluginLoader2,
|
|
1104
|
+
BundlePluginInstaller,
|
|
1105
|
+
MarketplaceClient
|
|
1106
|
+
} from "@robota-sdk/agent-sdk";
|
|
1107
|
+
function usePluginCallbacks(cwd) {
|
|
1108
|
+
return useMemo(() => {
|
|
1109
|
+
const home = homedir3();
|
|
1110
|
+
const pluginsDir = join4(home, ".robota", "plugins");
|
|
1111
|
+
const userSettingsPath = join4(home, ".robota", "settings.json");
|
|
1112
|
+
const settingsStore = new PluginSettingsStore(userSettingsPath);
|
|
1113
|
+
const marketplace = new MarketplaceClient({ pluginsDir });
|
|
1114
|
+
const installer = new BundlePluginInstaller({
|
|
1115
|
+
pluginsDir,
|
|
1116
|
+
settingsStore,
|
|
1117
|
+
marketplaceClient: marketplace
|
|
1118
|
+
});
|
|
1119
|
+
const loader = new BundlePluginLoader2(pluginsDir);
|
|
1120
|
+
return {
|
|
1121
|
+
listInstalled: async () => {
|
|
1122
|
+
const plugins = await loader.loadAll();
|
|
1123
|
+
return plugins.map((p) => ({
|
|
1124
|
+
name: p.manifest.name,
|
|
1125
|
+
description: p.manifest.description,
|
|
1126
|
+
enabled: true
|
|
1127
|
+
}));
|
|
1128
|
+
},
|
|
1129
|
+
install: async (pluginId) => {
|
|
1130
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
1131
|
+
if (!name || !marketplaceName) {
|
|
1132
|
+
throw new Error("Plugin ID must be in format: name@marketplace");
|
|
1133
|
+
}
|
|
1134
|
+
await installer.install(name, marketplaceName);
|
|
1135
|
+
},
|
|
1136
|
+
uninstall: async (pluginId) => {
|
|
1137
|
+
await installer.uninstall(pluginId);
|
|
1138
|
+
},
|
|
1139
|
+
enable: async (pluginId) => {
|
|
1140
|
+
await installer.enable(pluginId);
|
|
1141
|
+
},
|
|
1142
|
+
disable: async (pluginId) => {
|
|
1143
|
+
await installer.disable(pluginId);
|
|
1144
|
+
},
|
|
1145
|
+
marketplaceAdd: async (source) => {
|
|
1146
|
+
if (source.includes("/") && !source.includes(":")) {
|
|
1147
|
+
return marketplace.addMarketplace({ type: "github", repo: source });
|
|
1148
|
+
} else {
|
|
1149
|
+
return marketplace.addMarketplace({ type: "git", url: source });
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
1152
|
+
marketplaceRemove: async (name) => {
|
|
1153
|
+
const installedFromMarketplace = installer.getPluginsByMarketplace(name);
|
|
1154
|
+
for (const record of installedFromMarketplace) {
|
|
1155
|
+
await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
|
|
1156
|
+
}
|
|
1157
|
+
marketplace.removeMarketplace(name);
|
|
1158
|
+
},
|
|
1159
|
+
marketplaceUpdate: async (name) => {
|
|
1160
|
+
marketplace.updateMarketplace(name);
|
|
1161
|
+
},
|
|
1162
|
+
marketplaceList: async () => {
|
|
1163
|
+
return marketplace.listMarketplaces().map((m) => ({
|
|
1164
|
+
name: m.name,
|
|
1165
|
+
type: m.source.type
|
|
1166
|
+
}));
|
|
1167
|
+
},
|
|
1168
|
+
reloadPlugins: async () => {
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}, [cwd]);
|
|
709
1172
|
}
|
|
710
1173
|
|
|
711
1174
|
// src/ui/MessageList.tsx
|
|
@@ -828,7 +1291,7 @@ function StatusBar({
|
|
|
828
1291
|
}
|
|
829
1292
|
|
|
830
1293
|
// src/ui/InputArea.tsx
|
|
831
|
-
import
|
|
1294
|
+
import React4, { useState as useState5, useCallback as useCallback5, useMemo as useMemo2 } from "react";
|
|
832
1295
|
import { Box as Box4, Text as Text6, useInput as useInput2 } from "ink";
|
|
833
1296
|
|
|
834
1297
|
// src/ui/CjkTextInput.tsx
|
|
@@ -1007,14 +1470,14 @@ function parseSlashInput(value) {
|
|
|
1007
1470
|
function useAutocomplete(value, registry) {
|
|
1008
1471
|
const [selectedIndex, setSelectedIndex] = useState5(0);
|
|
1009
1472
|
const [dismissed, setDismissed] = useState5(false);
|
|
1010
|
-
const prevValueRef =
|
|
1473
|
+
const prevValueRef = React4.useRef(value);
|
|
1011
1474
|
if (prevValueRef.current !== value) {
|
|
1012
1475
|
prevValueRef.current = value;
|
|
1013
1476
|
if (dismissed) setDismissed(false);
|
|
1014
1477
|
}
|
|
1015
1478
|
const parsed = parseSlashInput(value);
|
|
1016
1479
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
1017
|
-
const filteredCommands =
|
|
1480
|
+
const filteredCommands = useMemo2(() => {
|
|
1018
1481
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
1019
1482
|
if (isSubcommandMode) {
|
|
1020
1483
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -1174,7 +1637,7 @@ function ConfirmPrompt({
|
|
|
1174
1637
|
}
|
|
1175
1638
|
|
|
1176
1639
|
// src/ui/PermissionPrompt.tsx
|
|
1177
|
-
import
|
|
1640
|
+
import React6 from "react";
|
|
1178
1641
|
import { Box as Box6, Text as Text8, useInput as useInput4 } from "ink";
|
|
1179
1642
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1180
1643
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
@@ -1184,15 +1647,15 @@ function formatArgs(args) {
|
|
|
1184
1647
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
1185
1648
|
}
|
|
1186
1649
|
function PermissionPrompt({ request }) {
|
|
1187
|
-
const [selected, setSelected] =
|
|
1188
|
-
const resolvedRef =
|
|
1189
|
-
const prevRequestRef =
|
|
1650
|
+
const [selected, setSelected] = React6.useState(0);
|
|
1651
|
+
const resolvedRef = React6.useRef(false);
|
|
1652
|
+
const prevRequestRef = React6.useRef(request);
|
|
1190
1653
|
if (prevRequestRef.current !== request) {
|
|
1191
1654
|
prevRequestRef.current = request;
|
|
1192
1655
|
resolvedRef.current = false;
|
|
1193
1656
|
setSelected(0);
|
|
1194
1657
|
}
|
|
1195
|
-
const doResolve =
|
|
1658
|
+
const doResolve = React6.useCallback(
|
|
1196
1659
|
(index) => {
|
|
1197
1660
|
if (resolvedRef.current) return;
|
|
1198
1661
|
resolvedRef.current = true;
|
|
@@ -1271,9 +1734,35 @@ function StreamingIndicator({ text, activeTools }) {
|
|
|
1271
1734
|
// src/ui/App.tsx
|
|
1272
1735
|
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1273
1736
|
var EXIT_DELAY_MS2 = 500;
|
|
1737
|
+
function mergeHooksIntoConfig(configHooks, pluginHooks) {
|
|
1738
|
+
const pluginKeys = Object.keys(pluginHooks);
|
|
1739
|
+
if (pluginKeys.length === 0) return configHooks;
|
|
1740
|
+
const merged = {};
|
|
1741
|
+
for (const [event, groups] of Object.entries(pluginHooks)) {
|
|
1742
|
+
merged[event] = [...groups];
|
|
1743
|
+
}
|
|
1744
|
+
if (configHooks) {
|
|
1745
|
+
for (const [event, groups] of Object.entries(configHooks)) {
|
|
1746
|
+
if (!Array.isArray(groups)) continue;
|
|
1747
|
+
if (!merged[event]) merged[event] = [];
|
|
1748
|
+
merged[event].push(...groups);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
return merged;
|
|
1752
|
+
}
|
|
1274
1753
|
function App(props) {
|
|
1275
1754
|
const { exit } = useApp();
|
|
1276
|
-
const {
|
|
1755
|
+
const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1756
|
+
const configWithPluginHooks = {
|
|
1757
|
+
...props.config,
|
|
1758
|
+
hooks: mergeHooksIntoConfig(
|
|
1759
|
+
props.config.hooks,
|
|
1760
|
+
pluginHooks
|
|
1761
|
+
)
|
|
1762
|
+
};
|
|
1763
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
|
|
1764
|
+
{ ...props, config: configWithPluginHooks }
|
|
1765
|
+
);
|
|
1277
1766
|
const { messages, setMessages, addMessage } = useMessages();
|
|
1278
1767
|
const [isThinking, setIsThinking] = useState7(false);
|
|
1279
1768
|
const initialCtx = session.getContextState();
|
|
@@ -1282,10 +1771,19 @@ function App(props) {
|
|
|
1282
1771
|
usedTokens: initialCtx.usedTokens,
|
|
1283
1772
|
maxTokens: initialCtx.maxTokens
|
|
1284
1773
|
});
|
|
1285
|
-
const registry = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1286
1774
|
const pendingModelChangeRef = useRef5(null);
|
|
1287
1775
|
const [pendingModelId, setPendingModelId] = useState7(null);
|
|
1288
|
-
const
|
|
1776
|
+
const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
|
|
1777
|
+
const handleSlashCommand = useSlashCommands(
|
|
1778
|
+
session,
|
|
1779
|
+
addMessage,
|
|
1780
|
+
setMessages,
|
|
1781
|
+
exit,
|
|
1782
|
+
registry,
|
|
1783
|
+
pendingModelChangeRef,
|
|
1784
|
+
setPendingModelId,
|
|
1785
|
+
pluginCallbacks
|
|
1786
|
+
);
|
|
1289
1787
|
const handleSubmit = useSubmitHandler(
|
|
1290
1788
|
session,
|
|
1291
1789
|
addMessage,
|
|
@@ -1332,10 +1830,16 @@ function App(props) {
|
|
|
1332
1830
|
try {
|
|
1333
1831
|
const settingsPath = getUserSettingsPath();
|
|
1334
1832
|
updateModelInSettings(settingsPath, pendingModelId);
|
|
1335
|
-
addMessage({
|
|
1833
|
+
addMessage({
|
|
1834
|
+
role: "system",
|
|
1835
|
+
content: `Model changed to ${getModelName(pendingModelId)}. Restarting...`
|
|
1836
|
+
});
|
|
1336
1837
|
setTimeout(() => exit(), EXIT_DELAY_MS2);
|
|
1337
1838
|
} catch (err) {
|
|
1338
|
-
addMessage({
|
|
1839
|
+
addMessage({
|
|
1840
|
+
role: "system",
|
|
1841
|
+
content: `Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1842
|
+
});
|
|
1339
1843
|
}
|
|
1340
1844
|
} else {
|
|
1341
1845
|
addMessage({ role: "system", content: "Model change cancelled." });
|
|
@@ -1393,23 +1897,24 @@ function renderApp(options) {
|
|
|
1393
1897
|
}
|
|
1394
1898
|
|
|
1395
1899
|
// src/cli.ts
|
|
1396
|
-
function
|
|
1397
|
-
if (!existsSync3(filePath)) return
|
|
1900
|
+
function checkSettingsFile(filePath) {
|
|
1901
|
+
if (!existsSync3(filePath)) return "missing";
|
|
1398
1902
|
try {
|
|
1399
1903
|
const raw = readFileSync3(filePath, "utf8").trim();
|
|
1400
|
-
if (raw.length === 0) return
|
|
1904
|
+
if (raw.length === 0) return "incomplete";
|
|
1401
1905
|
const parsed = JSON.parse(raw);
|
|
1402
1906
|
const provider = parsed.provider;
|
|
1403
|
-
|
|
1907
|
+
if (!provider?.apiKey) return "incomplete";
|
|
1908
|
+
return "valid";
|
|
1404
1909
|
} catch {
|
|
1405
|
-
return
|
|
1910
|
+
return "corrupt";
|
|
1406
1911
|
}
|
|
1407
1912
|
}
|
|
1408
1913
|
function readVersion() {
|
|
1409
1914
|
try {
|
|
1410
1915
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1411
|
-
const dir =
|
|
1412
|
-
const candidates = [
|
|
1916
|
+
const dir = dirname3(thisFile);
|
|
1917
|
+
const candidates = [join5(dir, "..", "..", "package.json"), join5(dir, "..", "package.json")];
|
|
1413
1918
|
for (const pkgPath of candidates) {
|
|
1414
1919
|
try {
|
|
1415
1920
|
const raw = readFileSync3(pkgPath, "utf-8");
|
|
@@ -1462,14 +1967,36 @@ function promptInput(label, masked = false) {
|
|
|
1462
1967
|
}
|
|
1463
1968
|
async function ensureConfig(cwd) {
|
|
1464
1969
|
const userPath = getUserSettingsPath();
|
|
1465
|
-
const projectPath =
|
|
1466
|
-
const localPath =
|
|
1467
|
-
|
|
1970
|
+
const projectPath = join5(cwd, ".robota", "settings.json");
|
|
1971
|
+
const localPath = join5(cwd, ".robota", "settings.local.json");
|
|
1972
|
+
const paths = [userPath, projectPath, localPath];
|
|
1973
|
+
const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
|
|
1974
|
+
if (checks.some((c) => c.status === "valid")) {
|
|
1468
1975
|
return;
|
|
1469
1976
|
}
|
|
1977
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
1978
|
+
const incomplete = checks.filter((c) => c.status === "incomplete");
|
|
1470
1979
|
process.stdout.write("\n");
|
|
1471
|
-
|
|
1472
|
-
|
|
1980
|
+
if (corrupt.length > 0) {
|
|
1981
|
+
for (const c of corrupt) {
|
|
1982
|
+
process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
|
|
1983
|
+
`);
|
|
1984
|
+
}
|
|
1985
|
+
process.stdout.write("\n");
|
|
1986
|
+
}
|
|
1987
|
+
if (incomplete.length > 0) {
|
|
1988
|
+
for (const c of incomplete) {
|
|
1989
|
+
process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
|
|
1990
|
+
`);
|
|
1991
|
+
}
|
|
1992
|
+
process.stdout.write("\n");
|
|
1993
|
+
}
|
|
1994
|
+
if (corrupt.length === 0 && incomplete.length === 0) {
|
|
1995
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
1996
|
+
process.stdout.write(" No configuration found. Let's set up.\n");
|
|
1997
|
+
} else {
|
|
1998
|
+
process.stdout.write(" Reconfiguring...\n");
|
|
1999
|
+
}
|
|
1473
2000
|
process.stdout.write("\n");
|
|
1474
2001
|
const apiKey = await promptInput(" Anthropic API key: ", true);
|
|
1475
2002
|
if (!apiKey) {
|
|
@@ -1477,7 +2004,7 @@ async function ensureConfig(cwd) {
|
|
|
1477
2004
|
process.exit(1);
|
|
1478
2005
|
}
|
|
1479
2006
|
const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
|
|
1480
|
-
const settingsDir =
|
|
2007
|
+
const settingsDir = dirname3(userPath);
|
|
1481
2008
|
mkdirSync2(settingsDir, { recursive: true });
|
|
1482
2009
|
const settings = {
|
|
1483
2010
|
provider: {
|