@orderful/droid 0.7.0 → 0.9.0
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/CHANGELOG.md +57 -0
- package/README.md +80 -89
- package/assets/droid+claude.png +0 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +77 -9
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +111 -70
- package/dist/commands/tui.js.map +1 -1
- package/dist/lib/agents.d.ts +19 -4
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +121 -42
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +56 -1
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/skills/brain/SKILL.md +16 -16
- package/dist/skills/brain/SKILL.yaml +4 -6
- package/dist/skills/brain/commands/brain.md +12 -5
- package/dist/skills/brain/commands/scratchpad.md +52 -0
- package/dist/skills/brain/references/workflows.md +14 -4
- package/dist/skills/brain-obsidian/SKILL.md +1 -4
- package/dist/skills/brain-obsidian/SKILL.yaml +1 -4
- package/dist/skills/code-review/SKILL.md +54 -0
- package/dist/skills/code-review/SKILL.yaml +19 -0
- package/dist/skills/code-review/agents/edi-standards-reviewer/AGENT.md +39 -0
- package/dist/skills/code-review/agents/edi-standards-reviewer/AGENT.yaml +14 -0
- package/dist/skills/code-review/agents/error-handling-reviewer/AGENT.md +51 -0
- package/dist/skills/code-review/agents/error-handling-reviewer/AGENT.yaml +14 -0
- package/dist/skills/code-review/agents/test-coverage-analyzer/AGENT.md +53 -0
- package/dist/skills/code-review/agents/test-coverage-analyzer/AGENT.yaml +14 -0
- package/dist/skills/code-review/agents/type-reviewer/AGENT.md +50 -0
- package/dist/skills/code-review/agents/type-reviewer/AGENT.yaml +13 -0
- package/dist/skills/code-review/commands/code-review.md +91 -0
- package/dist/skills/comments/SKILL.md +21 -9
- package/dist/skills/comments/SKILL.yaml +2 -5
- package/dist/skills/comments/commands/comments.md +1 -1
- package/dist/skills/project/SKILL.md +10 -13
- package/dist/skills/project/SKILL.yaml +2 -7
- package/dist/skills/project/commands/project.md +9 -4
- package/dist/skills/project/references/creating.md +9 -4
- package/dist/skills/project/references/loading.md +11 -5
- package/package.json +1 -1
- package/src/commands/setup.test.ts +276 -0
- package/src/commands/setup.ts +80 -10
- package/src/commands/tui.tsx +149 -82
- package/src/lib/agents.ts +134 -44
- package/src/lib/skills.ts +61 -1
- package/src/lib/types.ts +4 -0
- package/src/skills/brain/SKILL.md +16 -16
- package/src/skills/brain/SKILL.yaml +4 -6
- package/src/skills/brain/commands/brain.md +12 -5
- package/src/skills/brain/commands/scratchpad.md +52 -0
- package/src/skills/brain/references/workflows.md +14 -4
- package/src/skills/brain-obsidian/SKILL.md +1 -4
- package/src/skills/brain-obsidian/SKILL.yaml +1 -4
- package/src/skills/code-review/SKILL.md +54 -0
- package/src/skills/code-review/SKILL.yaml +19 -0
- package/src/skills/code-review/agents/edi-standards-reviewer/AGENT.md +39 -0
- package/src/skills/code-review/agents/edi-standards-reviewer/AGENT.yaml +14 -0
- package/src/skills/code-review/agents/error-handling-reviewer/AGENT.md +51 -0
- package/src/skills/code-review/agents/error-handling-reviewer/AGENT.yaml +14 -0
- package/src/skills/code-review/agents/test-coverage-analyzer/AGENT.md +53 -0
- package/src/skills/code-review/agents/test-coverage-analyzer/AGENT.yaml +14 -0
- package/src/skills/code-review/agents/type-reviewer/AGENT.md +50 -0
- package/src/skills/code-review/agents/type-reviewer/AGENT.yaml +13 -0
- package/src/skills/code-review/commands/code-review.md +91 -0
- package/src/skills/comments/SKILL.md +21 -9
- package/src/skills/comments/SKILL.yaml +2 -5
- package/src/skills/comments/commands/comments.md +1 -1
- package/src/skills/project/SKILL.md +10 -13
- package/src/skills/project/SKILL.yaml +2 -7
- package/src/skills/project/commands/project.md +9 -4
- package/src/skills/project/references/creating.md +9 -4
- package/src/skills/project/references/loading.md +11 -5
package/src/commands/tui.tsx
CHANGED
|
@@ -13,9 +13,6 @@ import {
|
|
|
13
13
|
uninstallSkill,
|
|
14
14
|
updateSkill,
|
|
15
15
|
getSkillUpdateStatus,
|
|
16
|
-
isCommandInstalled,
|
|
17
|
-
installCommand,
|
|
18
|
-
uninstallCommand,
|
|
19
16
|
} from '../lib/skills.js';
|
|
20
17
|
import { getBundledAgents, getBundledAgentsDir, isAgentInstalled, installAgent, uninstallAgent, type AgentManifest } from '../lib/agents.js';
|
|
21
18
|
import { configExists, loadConfig, saveConfig, loadSkillOverrides, saveSkillOverrides } from '../lib/config.js';
|
|
@@ -133,6 +130,56 @@ function getOutputOptions(): Array<{ label: string; value: OutputPreference }> {
|
|
|
133
130
|
return options;
|
|
134
131
|
}
|
|
135
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Check if an agent belongs to a skill bundle (whether installed or not)
|
|
135
|
+
* Returns the skill name if so, null otherwise
|
|
136
|
+
*/
|
|
137
|
+
function getAgentBundledSkill(agentName: string): string | null {
|
|
138
|
+
const skillsDir = getBundledSkillsDir();
|
|
139
|
+
if (!existsSync(skillsDir)) return null;
|
|
140
|
+
|
|
141
|
+
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
142
|
+
.filter((d) => d.isDirectory())
|
|
143
|
+
.map((d) => d.name);
|
|
144
|
+
|
|
145
|
+
for (const skillName of skillDirs) {
|
|
146
|
+
const agentDir = join(skillsDir, skillName, 'agents', agentName);
|
|
147
|
+
if (existsSync(agentDir)) {
|
|
148
|
+
return skillName;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Find the source path for an agent's AGENT.md
|
|
156
|
+
* Checks both standalone agents dir and skill-bundled agents
|
|
157
|
+
*/
|
|
158
|
+
function findAgentSourcePath(agentName: string): string | null {
|
|
159
|
+
// Check standalone agents first
|
|
160
|
+
const standalonePath = join(getBundledAgentsDir(), agentName, 'AGENT.md');
|
|
161
|
+
if (existsSync(standalonePath)) {
|
|
162
|
+
return standalonePath;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check skill-bundled agents
|
|
166
|
+
const skillsDir = getBundledSkillsDir();
|
|
167
|
+
if (existsSync(skillsDir)) {
|
|
168
|
+
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
169
|
+
.filter((d) => d.isDirectory())
|
|
170
|
+
.map((d) => d.name);
|
|
171
|
+
|
|
172
|
+
for (const skillName of skillDirs) {
|
|
173
|
+
const skillAgentPath = join(skillsDir, skillName, 'agents', agentName, 'AGENT.md');
|
|
174
|
+
if (existsSync(skillAgentPath)) {
|
|
175
|
+
return skillAgentPath;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
136
183
|
interface WelcomeScreenProps {
|
|
137
184
|
onContinue: () => void;
|
|
138
185
|
onUpdate: () => void;
|
|
@@ -461,13 +508,14 @@ function SetupScreen({ onComplete, onSkip, initialConfig }: SetupScreenProps) {
|
|
|
461
508
|
|
|
462
509
|
function TabBar({ tabs, activeTab }: { tabs: { id: Tab; label: string }[]; activeTab: Tab }) {
|
|
463
510
|
return (
|
|
464
|
-
<Box flexDirection="row">
|
|
511
|
+
<Box flexDirection="row" flexWrap="wrap">
|
|
465
512
|
{tabs.map((tab) => (
|
|
466
513
|
<Text
|
|
467
514
|
key={tab.id}
|
|
468
515
|
backgroundColor={tab.id === activeTab ? colors.primary : undefined}
|
|
469
516
|
color={tab.id === activeTab ? '#ffffff' : colors.textMuted}
|
|
470
517
|
bold={tab.id === activeTab}
|
|
518
|
+
wrap="truncate"
|
|
471
519
|
>
|
|
472
520
|
{' '}{tab.label}{' '}
|
|
473
521
|
</Text>
|
|
@@ -648,20 +696,9 @@ function CommandDetails({
|
|
|
648
696
|
}
|
|
649
697
|
|
|
650
698
|
const skillInstalled = isSkillInstalled(command.skillName);
|
|
651
|
-
const installed = isCommandInstalled(command.name, command.skillName);
|
|
652
699
|
|
|
653
|
-
//
|
|
654
|
-
const actions =
|
|
655
|
-
? [{ id: 'view', label: 'View', variant: 'default' }]
|
|
656
|
-
: installed
|
|
657
|
-
? [
|
|
658
|
-
{ id: 'view', label: 'View', variant: 'default' },
|
|
659
|
-
{ id: 'uninstall', label: 'Uninstall', variant: 'danger' },
|
|
660
|
-
]
|
|
661
|
-
: [
|
|
662
|
-
{ id: 'view', label: 'View', variant: 'default' },
|
|
663
|
-
{ id: 'install', label: 'Install', variant: 'primary' },
|
|
664
|
-
];
|
|
700
|
+
// Commands belong to skills - only show View, install via skill
|
|
701
|
+
const actions = [{ id: 'view', label: 'View', variant: 'default' }];
|
|
665
702
|
|
|
666
703
|
return (
|
|
667
704
|
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
@@ -670,8 +707,7 @@ function CommandDetails({
|
|
|
670
707
|
<Box marginTop={1}>
|
|
671
708
|
<Text color={colors.textDim}>
|
|
672
709
|
from {command.skillName}
|
|
673
|
-
{skillInstalled && <Text color={colors.success}> ·
|
|
674
|
-
{!skillInstalled && installed && <Text color={colors.success}> · installed</Text>}
|
|
710
|
+
{skillInstalled && <Text color={colors.success}> · installed</Text>}
|
|
675
711
|
</Text>
|
|
676
712
|
</Box>
|
|
677
713
|
|
|
@@ -747,18 +783,24 @@ function AgentDetails({
|
|
|
747
783
|
}
|
|
748
784
|
|
|
749
785
|
const installed = isAgentInstalled(agent.name);
|
|
786
|
+
const bundledSkill = getAgentBundledSkill(agent.name);
|
|
787
|
+
const skillInstalled = bundledSkill ? isSkillInstalled(bundledSkill) : false;
|
|
750
788
|
const statusDisplay = agent.status === 'alpha' ? '[alpha]' : agent.status === 'beta' ? '[beta]' : '';
|
|
751
789
|
const modeDisplay = agent.mode === 'primary' ? 'primary' : agent.mode === 'all' ? 'primary/subagent' : 'subagent';
|
|
752
790
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
791
|
+
// If agent belongs to a skill, only show View (install via skill)
|
|
792
|
+
// Otherwise show Install/Uninstall for standalone agents
|
|
793
|
+
const actions = bundledSkill
|
|
794
|
+
? [{ id: 'view', label: 'View', variant: 'default' }]
|
|
795
|
+
: installed
|
|
796
|
+
? [
|
|
797
|
+
{ id: 'view', label: 'View', variant: 'default' },
|
|
798
|
+
{ id: 'uninstall', label: 'Uninstall', variant: 'danger' },
|
|
799
|
+
]
|
|
800
|
+
: [
|
|
801
|
+
{ id: 'view', label: 'View', variant: 'default' },
|
|
802
|
+
{ id: 'install', label: 'Install', variant: 'primary' },
|
|
803
|
+
];
|
|
762
804
|
|
|
763
805
|
return (
|
|
764
806
|
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
@@ -769,7 +811,9 @@ function AgentDetails({
|
|
|
769
811
|
v{agent.version}
|
|
770
812
|
{statusDisplay && <Text> · {statusDisplay}</Text>}
|
|
771
813
|
{' · '}{modeDisplay}
|
|
772
|
-
{
|
|
814
|
+
{bundledSkill && <Text color={colors.textMuted}> · from {bundledSkill}</Text>}
|
|
815
|
+
{skillInstalled && <Text color={colors.success}> · installed</Text>}
|
|
816
|
+
{!bundledSkill && installed && <Text color={colors.success}> · installed</Text>}
|
|
773
817
|
</Text>
|
|
774
818
|
</Box>
|
|
775
819
|
|
|
@@ -1017,6 +1061,8 @@ interface SkillConfigScreenProps {
|
|
|
1017
1061
|
onCancel: () => void;
|
|
1018
1062
|
}
|
|
1019
1063
|
|
|
1064
|
+
const MAX_VISIBLE_CONFIG_ITEMS = 4; // Each config item takes ~3 lines
|
|
1065
|
+
|
|
1020
1066
|
function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenProps) {
|
|
1021
1067
|
const configSchema = skill.config_schema || {};
|
|
1022
1068
|
const configKeys = Object.keys(configSchema);
|
|
@@ -1034,11 +1080,15 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
|
|
|
1034
1080
|
});
|
|
1035
1081
|
|
|
1036
1082
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
1083
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
1037
1084
|
const [editingField, setEditingField] = useState<string | null>(null);
|
|
1038
1085
|
const [editValue, setEditValue] = useState('');
|
|
1039
1086
|
const [editingSelect, setEditingSelect] = useState<string | null>(null);
|
|
1040
1087
|
const [selectOptionIndex, setSelectOptionIndex] = useState(0);
|
|
1041
1088
|
|
|
1089
|
+
// Total items = config keys + Save button
|
|
1090
|
+
const totalItems = configKeys.length + 1;
|
|
1091
|
+
|
|
1042
1092
|
const handleSave = () => {
|
|
1043
1093
|
saveSkillOverrides(skill.name, values);
|
|
1044
1094
|
onComplete();
|
|
@@ -1090,11 +1140,25 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
|
|
|
1090
1140
|
}
|
|
1091
1141
|
|
|
1092
1142
|
if (key.upArrow) {
|
|
1093
|
-
setSelectedIndex((prev) =>
|
|
1143
|
+
setSelectedIndex((prev) => {
|
|
1144
|
+
const newIndex = Math.max(0, prev - 1);
|
|
1145
|
+
// Scroll up if needed
|
|
1146
|
+
if (newIndex < scrollOffset) {
|
|
1147
|
+
setScrollOffset(newIndex);
|
|
1148
|
+
}
|
|
1149
|
+
return newIndex;
|
|
1150
|
+
});
|
|
1094
1151
|
}
|
|
1095
1152
|
if (key.downArrow) {
|
|
1096
1153
|
// +1 for the Save button at the end
|
|
1097
|
-
setSelectedIndex((prev) =>
|
|
1154
|
+
setSelectedIndex((prev) => {
|
|
1155
|
+
const newIndex = Math.min(totalItems - 1, prev + 1);
|
|
1156
|
+
// Scroll down if needed
|
|
1157
|
+
if (newIndex >= scrollOffset + MAX_VISIBLE_CONFIG_ITEMS) {
|
|
1158
|
+
setScrollOffset(newIndex - MAX_VISIBLE_CONFIG_ITEMS + 1);
|
|
1159
|
+
}
|
|
1160
|
+
return newIndex;
|
|
1161
|
+
});
|
|
1098
1162
|
}
|
|
1099
1163
|
|
|
1100
1164
|
if (key.return) {
|
|
@@ -1145,6 +1209,13 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
|
|
|
1145
1209
|
);
|
|
1146
1210
|
}
|
|
1147
1211
|
|
|
1212
|
+
// Calculate visible range
|
|
1213
|
+
const visibleEndIndex = Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, totalItems);
|
|
1214
|
+
const visibleConfigKeys = configKeys.slice(scrollOffset, Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, configKeys.length));
|
|
1215
|
+
const showSaveButton = visibleEndIndex > configKeys.length || scrollOffset + MAX_VISIBLE_CONFIG_ITEMS > configKeys.length;
|
|
1216
|
+
const showTopIndicator = scrollOffset > 0;
|
|
1217
|
+
const showBottomIndicator = scrollOffset + MAX_VISIBLE_CONFIG_ITEMS < totalItems;
|
|
1218
|
+
|
|
1148
1219
|
return (
|
|
1149
1220
|
<Box flexDirection="column" padding={1}>
|
|
1150
1221
|
<Box marginBottom={1}>
|
|
@@ -1159,9 +1230,17 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
|
|
|
1159
1230
|
</Box>
|
|
1160
1231
|
|
|
1161
1232
|
<Box flexDirection="column">
|
|
1162
|
-
{
|
|
1233
|
+
{/* Scroll up indicator */}
|
|
1234
|
+
{showTopIndicator && (
|
|
1235
|
+
<Box marginBottom={1}>
|
|
1236
|
+
<Text color={colors.textDim}> ↑ {scrollOffset} more</Text>
|
|
1237
|
+
</Box>
|
|
1238
|
+
)}
|
|
1239
|
+
|
|
1240
|
+
{visibleConfigKeys.map((key) => {
|
|
1241
|
+
const actualIndex = configKeys.indexOf(key);
|
|
1163
1242
|
const option = configSchema[key];
|
|
1164
|
-
const isSelected = selectedIndex ===
|
|
1243
|
+
const isSelected = selectedIndex === actualIndex;
|
|
1165
1244
|
const isEditing = editingField === key;
|
|
1166
1245
|
|
|
1167
1246
|
return (
|
|
@@ -1213,19 +1292,28 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
|
|
|
1213
1292
|
);
|
|
1214
1293
|
})}
|
|
1215
1294
|
|
|
1216
|
-
{/* Save button */}
|
|
1217
|
-
|
|
1218
|
-
<
|
|
1219
|
-
<Text
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1295
|
+
{/* Save button - show if in visible range */}
|
|
1296
|
+
{showSaveButton && (
|
|
1297
|
+
<Box marginTop={1}>
|
|
1298
|
+
<Text>
|
|
1299
|
+
<Text color={colors.textDim}>{selectedIndex === configKeys.length ? '>' : ' '} </Text>
|
|
1300
|
+
<Text
|
|
1301
|
+
backgroundColor={selectedIndex === configKeys.length ? colors.primary : undefined}
|
|
1302
|
+
color={selectedIndex === configKeys.length ? '#ffffff' : colors.textMuted}
|
|
1303
|
+
bold={selectedIndex === configKeys.length}
|
|
1304
|
+
>
|
|
1305
|
+
{' '}Save{' '}
|
|
1306
|
+
</Text>
|
|
1226
1307
|
</Text>
|
|
1227
|
-
</
|
|
1228
|
-
|
|
1308
|
+
</Box>
|
|
1309
|
+
)}
|
|
1310
|
+
|
|
1311
|
+
{/* Scroll down indicator */}
|
|
1312
|
+
{showBottomIndicator && (
|
|
1313
|
+
<Box marginTop={1}>
|
|
1314
|
+
<Text color={colors.textDim}> ↓ {totalItems - scrollOffset - MAX_VISIBLE_CONFIG_ITEMS} more</Text>
|
|
1315
|
+
</Box>
|
|
1316
|
+
)}
|
|
1229
1317
|
</Box>
|
|
1230
1318
|
|
|
1231
1319
|
<Box marginTop={1}>
|
|
@@ -1329,7 +1417,10 @@ function App() {
|
|
|
1329
1417
|
}
|
|
1330
1418
|
if (key.downArrow) {
|
|
1331
1419
|
const maxIndex =
|
|
1332
|
-
activeTab === 'skills' ? skills.length - 1
|
|
1420
|
+
activeTab === 'skills' ? skills.length - 1
|
|
1421
|
+
: activeTab === 'commands' ? commands.length - 1
|
|
1422
|
+
: activeTab === 'agents' ? agents.length - 1
|
|
1423
|
+
: 0;
|
|
1333
1424
|
setSelectedIndex((prev) => {
|
|
1334
1425
|
const newIndex = Math.min(maxIndex, prev + 1);
|
|
1335
1426
|
// Scroll down if needed
|
|
@@ -1440,16 +1531,17 @@ function App() {
|
|
|
1440
1531
|
const agent = agents[selectedIndex];
|
|
1441
1532
|
if (agent) {
|
|
1442
1533
|
const installed = isAgentInstalled(agent.name);
|
|
1534
|
+
const bundledSkill = getAgentBundledSkill(agent.name);
|
|
1443
1535
|
if (selectedAction === 0) {
|
|
1444
1536
|
// View
|
|
1445
|
-
const agentMdPath =
|
|
1446
|
-
if (
|
|
1537
|
+
const agentMdPath = findAgentSourcePath(agent.name);
|
|
1538
|
+
if (agentMdPath) {
|
|
1447
1539
|
const content = readFileSync(agentMdPath, 'utf-8');
|
|
1448
1540
|
setReadmeContent({ title: `${agent.name}/AGENT.md`, content });
|
|
1449
1541
|
setView('readme');
|
|
1450
1542
|
}
|
|
1451
|
-
} else if (installed && selectedAction === 1) {
|
|
1452
|
-
// Uninstall
|
|
1543
|
+
} else if (!bundledSkill && installed && selectedAction === 1) {
|
|
1544
|
+
// Uninstall (only for standalone agents)
|
|
1453
1545
|
const result = uninstallAgent(agent.name);
|
|
1454
1546
|
setMessage({
|
|
1455
1547
|
text: result.success ? `✓ Uninstalled ${agent.name}` : `✗ ${result.message}`,
|
|
@@ -1459,8 +1551,8 @@ function App() {
|
|
|
1459
1551
|
setView('menu');
|
|
1460
1552
|
setSelectedAction(0);
|
|
1461
1553
|
}
|
|
1462
|
-
} else if (!installed && selectedAction === 1) {
|
|
1463
|
-
// Install
|
|
1554
|
+
} else if (!bundledSkill && !installed && selectedAction === 1) {
|
|
1555
|
+
// Install (only for standalone agents)
|
|
1464
1556
|
const result = installAgent(agent.name);
|
|
1465
1557
|
setMessage({
|
|
1466
1558
|
text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
|
|
@@ -1476,42 +1568,17 @@ function App() {
|
|
|
1476
1568
|
if (key.return && activeTab === 'commands') {
|
|
1477
1569
|
const command = commands[selectedIndex];
|
|
1478
1570
|
if (command) {
|
|
1479
|
-
const installed = isCommandInstalled(command.name, command.skillName);
|
|
1480
1571
|
// Command file: extract part after skill name (e.g., "comments check" → "check.md")
|
|
1481
1572
|
const cmdPart = command.name.startsWith(command.skillName + ' ')
|
|
1482
1573
|
? command.name.slice(command.skillName.length + 1)
|
|
1483
1574
|
: command.name;
|
|
1484
1575
|
const commandMdPath = join(getBundledSkillsDir(), command.skillName, 'commands', `${cmdPart}.md`);
|
|
1485
1576
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
setView('readme');
|
|
1492
|
-
}
|
|
1493
|
-
} else if (installed && selectedAction === 1) {
|
|
1494
|
-
// Uninstall
|
|
1495
|
-
const result = uninstallCommand(command.name, command.skillName);
|
|
1496
|
-
setMessage({
|
|
1497
|
-
text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
|
|
1498
|
-
type: result.success ? 'success' : 'error',
|
|
1499
|
-
});
|
|
1500
|
-
if (result.success) {
|
|
1501
|
-
setView('menu');
|
|
1502
|
-
setSelectedAction(0);
|
|
1503
|
-
}
|
|
1504
|
-
} else if (!installed && selectedAction === 1) {
|
|
1505
|
-
// Install
|
|
1506
|
-
const result = installCommand(command.name, command.skillName);
|
|
1507
|
-
setMessage({
|
|
1508
|
-
text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
|
|
1509
|
-
type: result.success ? 'success' : 'error',
|
|
1510
|
-
});
|
|
1511
|
-
if (result.success) {
|
|
1512
|
-
setView('menu');
|
|
1513
|
-
setSelectedAction(0);
|
|
1514
|
-
}
|
|
1577
|
+
// Only View action available - commands install via skills
|
|
1578
|
+
if (selectedAction === 0 && existsSync(commandMdPath)) {
|
|
1579
|
+
const content = readFileSync(commandMdPath, 'utf-8');
|
|
1580
|
+
setReadmeContent({ title: `/${command.name}`, content });
|
|
1581
|
+
setView('readme');
|
|
1515
1582
|
}
|
|
1516
1583
|
}
|
|
1517
1584
|
}
|
package/src/lib/agents.ts
CHANGED
|
@@ -3,10 +3,23 @@ import { join, dirname } from 'path';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import YAML from 'yaml';
|
|
6
|
+
import { loadConfig } from './config.js';
|
|
7
|
+
import { AITool } from './types.js';
|
|
6
8
|
|
|
7
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
10
|
const BUNDLED_AGENTS_DIR = join(__dirname, '../agents');
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the installation path for agents based on AI tool
|
|
14
|
+
*/
|
|
15
|
+
export function getAgentsInstallPath(aiTool: AITool): string {
|
|
16
|
+
switch (aiTool) {
|
|
17
|
+
case AITool.ClaudeCode:
|
|
18
|
+
return join(homedir(), '.claude', 'agents');
|
|
19
|
+
case AITool.OpenCode:
|
|
20
|
+
return join(homedir(), '.config', 'opencode', 'agent');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
10
23
|
|
|
11
24
|
/**
|
|
12
25
|
* Agent manifest structure
|
|
@@ -17,6 +30,8 @@ export interface AgentManifest {
|
|
|
17
30
|
version: string;
|
|
18
31
|
status?: 'alpha' | 'beta' | 'stable';
|
|
19
32
|
mode?: 'primary' | 'subagent' | 'all'; // How the agent is used
|
|
33
|
+
model?: string; // Model to use (e.g., 'sonnet', 'opus', 'haiku')
|
|
34
|
+
color?: string; // Display color (e.g., 'purple', 'blue', 'green')
|
|
20
35
|
tools?: string[]; // Allowed tools for this agent
|
|
21
36
|
triggers?: string[];
|
|
22
37
|
persona?: string;
|
|
@@ -48,23 +63,49 @@ export function loadAgentManifest(agentDir: string): AgentManifest | null {
|
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
/**
|
|
51
|
-
* Get all bundled agents
|
|
66
|
+
* Get all bundled agents (standalone + skill-bundled)
|
|
52
67
|
*/
|
|
53
68
|
export function getBundledAgents(): AgentManifest[] {
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
const agents: AgentManifest[] = [];
|
|
70
|
+
const seenNames = new Set<string>();
|
|
71
|
+
|
|
72
|
+
// Get standalone agents from src/agents/
|
|
73
|
+
if (existsSync(BUNDLED_AGENTS_DIR)) {
|
|
74
|
+
const agentDirs = readdirSync(BUNDLED_AGENTS_DIR, { withFileTypes: true })
|
|
75
|
+
.filter((dirent) => dirent.isDirectory())
|
|
76
|
+
.map((dirent) => dirent.name);
|
|
77
|
+
|
|
78
|
+
for (const agentDir of agentDirs) {
|
|
79
|
+
const manifest = loadAgentManifest(join(BUNDLED_AGENTS_DIR, agentDir));
|
|
80
|
+
if (manifest && !seenNames.has(manifest.name)) {
|
|
81
|
+
agents.push(manifest);
|
|
82
|
+
seenNames.add(manifest.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
56
85
|
}
|
|
57
86
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
87
|
+
// Get skill-bundled agents from src/skills/*/agents/
|
|
88
|
+
const skillsDir = join(__dirname, '../skills');
|
|
89
|
+
if (existsSync(skillsDir)) {
|
|
90
|
+
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
91
|
+
.filter((dirent) => dirent.isDirectory())
|
|
92
|
+
.map((dirent) => dirent.name);
|
|
61
93
|
|
|
62
|
-
|
|
94
|
+
for (const skillName of skillDirs) {
|
|
95
|
+
const skillAgentsDir = join(skillsDir, skillName, 'agents');
|
|
96
|
+
if (!existsSync(skillAgentsDir)) continue;
|
|
97
|
+
|
|
98
|
+
const agentDirs = readdirSync(skillAgentsDir, { withFileTypes: true })
|
|
99
|
+
.filter((dirent) => dirent.isDirectory())
|
|
100
|
+
.map((dirent) => dirent.name);
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
102
|
+
for (const agentDir of agentDirs) {
|
|
103
|
+
const manifest = loadAgentManifest(join(skillAgentsDir, agentDir));
|
|
104
|
+
if (manifest && !seenNames.has(manifest.name)) {
|
|
105
|
+
agents.push(manifest);
|
|
106
|
+
seenNames.add(manifest.name);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
68
109
|
}
|
|
69
110
|
}
|
|
70
111
|
|
|
@@ -87,26 +128,74 @@ export function getAgentStatusDisplay(status?: string): string {
|
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
/**
|
|
90
|
-
* Get installed agents directory
|
|
131
|
+
* Get installed agents directory for the configured AI tool
|
|
91
132
|
*/
|
|
92
133
|
export function getInstalledAgentsDir(): string {
|
|
93
|
-
|
|
134
|
+
const config = loadConfig();
|
|
135
|
+
return getAgentsInstallPath(config.ai_tool);
|
|
94
136
|
}
|
|
95
137
|
|
|
96
138
|
/**
|
|
97
139
|
* Check if an agent is installed
|
|
98
140
|
*/
|
|
99
141
|
export function isAgentInstalled(agentName: string): boolean {
|
|
100
|
-
const
|
|
142
|
+
const config = loadConfig();
|
|
143
|
+
const agentsDir = getAgentsInstallPath(config.ai_tool);
|
|
144
|
+
const agentPath = join(agentsDir, `${agentName}.md`);
|
|
101
145
|
return existsSync(agentPath);
|
|
102
146
|
}
|
|
103
147
|
|
|
104
148
|
/**
|
|
105
|
-
*
|
|
106
|
-
* Combines AGENT.yaml metadata with AGENT.md content into a single .md file
|
|
149
|
+
* Generate Claude Code agent frontmatter
|
|
107
150
|
*/
|
|
108
|
-
|
|
109
|
-
const
|
|
151
|
+
function generateClaudeCodeAgent(manifest: AgentManifest, agentContent: string): string {
|
|
152
|
+
const lines = [
|
|
153
|
+
'---',
|
|
154
|
+
`name: ${manifest.name}`,
|
|
155
|
+
`description: ${manifest.description}`,
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
if (manifest.tools && manifest.tools.length > 0) {
|
|
159
|
+
lines.push(`tools: ${manifest.tools.join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
if (manifest.color) {
|
|
162
|
+
lines.push(`color: ${manifest.color}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lines.push('---', '', agentContent.trim(), '');
|
|
166
|
+
return lines.join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Generate OpenCode agent frontmatter
|
|
171
|
+
* OpenCode uses a different format with explicit tool permissions
|
|
172
|
+
*/
|
|
173
|
+
function generateOpenCodeAgent(manifest: AgentManifest, agentContent: string): string {
|
|
174
|
+
const lines = [
|
|
175
|
+
'---',
|
|
176
|
+
`description: ${manifest.description}`,
|
|
177
|
+
`mode: ${manifest.mode || 'subagent'}`,
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
// OpenCode uses explicit tool permissions
|
|
181
|
+
if (manifest.tools && manifest.tools.length > 0) {
|
|
182
|
+
lines.push('tools:');
|
|
183
|
+
for (const tool of manifest.tools) {
|
|
184
|
+
const toolName = tool.toLowerCase();
|
|
185
|
+
lines.push(` ${toolName}: true`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
lines.push('---', '', agentContent.trim(), '');
|
|
190
|
+
return lines.join('\n');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Install an agent from a specific path
|
|
195
|
+
* Generates the appropriate format based on the configured AI tool
|
|
196
|
+
*/
|
|
197
|
+
export function installAgentFromPath(agentDir: string, agentName: string): { success: boolean; message: string } {
|
|
198
|
+
const config = loadConfig();
|
|
110
199
|
const manifestPath = join(agentDir, 'AGENT.yaml');
|
|
111
200
|
const contentPath = join(agentDir, 'AGENT.md');
|
|
112
201
|
|
|
@@ -132,46 +221,47 @@ export function installAgent(agentName: string): { success: boolean; message: st
|
|
|
132
221
|
agentContent = manifest.persona;
|
|
133
222
|
}
|
|
134
223
|
|
|
135
|
-
//
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Add tools if specified
|
|
142
|
-
if (manifest.tools && manifest.tools.length > 0) {
|
|
143
|
-
frontmatter.tools = manifest.tools.join(', ');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Generate the installed agent file
|
|
147
|
-
const installedContent = `---
|
|
148
|
-
name: ${frontmatter.name}
|
|
149
|
-
description: ${frontmatter.description}${frontmatter.tools ? `\ntools: ${frontmatter.tools}` : ''}
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
${agentContent.trim()}
|
|
153
|
-
`;
|
|
224
|
+
// Generate format based on AI tool
|
|
225
|
+
const installedContent = config.ai_tool === AITool.ClaudeCode
|
|
226
|
+
? generateClaudeCodeAgent(manifest, agentContent)
|
|
227
|
+
: generateOpenCodeAgent(manifest, agentContent);
|
|
154
228
|
|
|
155
229
|
// Ensure agents directory exists
|
|
156
|
-
|
|
157
|
-
|
|
230
|
+
const agentsDir = getAgentsInstallPath(config.ai_tool);
|
|
231
|
+
if (!existsSync(agentsDir)) {
|
|
232
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
158
233
|
}
|
|
159
234
|
|
|
160
235
|
// Write the agent file
|
|
161
|
-
const outputPath = join(
|
|
236
|
+
const outputPath = join(agentsDir, `${agentName}.md`);
|
|
162
237
|
writeFileSync(outputPath, installedContent);
|
|
163
238
|
|
|
164
|
-
|
|
239
|
+
const targetDir = config.ai_tool === AITool.ClaudeCode
|
|
240
|
+
? '~/.claude/agents/'
|
|
241
|
+
: '~/.config/opencode/agent/';
|
|
242
|
+
|
|
243
|
+
return { success: true, message: `Installed ${agentName} to ${targetDir}` };
|
|
165
244
|
} catch (error) {
|
|
166
245
|
return { success: false, message: `Failed to install agent: ${error}` };
|
|
167
246
|
}
|
|
168
247
|
}
|
|
169
248
|
|
|
170
249
|
/**
|
|
171
|
-
*
|
|
250
|
+
* Install an agent from the bundled agents directory
|
|
251
|
+
* Combines AGENT.yaml metadata with AGENT.md content into a single .md file
|
|
252
|
+
*/
|
|
253
|
+
export function installAgent(agentName: string): { success: boolean; message: string } {
|
|
254
|
+
const agentDir = join(BUNDLED_AGENTS_DIR, agentName);
|
|
255
|
+
return installAgentFromPath(agentDir, agentName);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Uninstall an agent from the agents directory
|
|
172
260
|
*/
|
|
173
261
|
export function uninstallAgent(agentName: string): { success: boolean; message: string } {
|
|
174
|
-
const
|
|
262
|
+
const config = loadConfig();
|
|
263
|
+
const agentsDir = getAgentsInstallPath(config.ai_tool);
|
|
264
|
+
const agentPath = join(agentsDir, `${agentName}.md`);
|
|
175
265
|
|
|
176
266
|
if (!existsSync(agentPath)) {
|
|
177
267
|
return { success: false, message: `Agent not installed: ${agentName}` };
|