@sundial-ai/cli 0.0.1
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/DEV.md +58 -0
- package/README.md +30 -0
- package/dist/commands/add.d.ts +13 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +111 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +40 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +53 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/remove.d.ts +13 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +129 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/show-dev.d.ts +20 -0
- package/dist/commands/show-dev.d.ts.map +1 -0
- package/dist/commands/show-dev.js +195 -0
- package/dist/commands/show-dev.js.map +1 -0
- package/dist/commands/show.d.ts +11 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +175 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/core/agent-detect.d.ts +22 -0
- package/dist/core/agent-detect.d.ts.map +1 -0
- package/dist/core/agent-detect.js +107 -0
- package/dist/core/agent-detect.js.map +1 -0
- package/dist/core/agents.d.ts +8 -0
- package/dist/core/agents.d.ts.map +1 -0
- package/dist/core/agents.js +34 -0
- package/dist/core/agents.js.map +1 -0
- package/dist/core/config-manager.d.ts +9 -0
- package/dist/core/config-manager.d.ts.map +1 -0
- package/dist/core/config-manager.js +47 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/skill-hash.d.ts +12 -0
- package/dist/core/skill-hash.d.ts.map +1 -0
- package/dist/core/skill-hash.js +53 -0
- package/dist/core/skill-hash.js.map +1 -0
- package/dist/core/skill-info.d.ts +34 -0
- package/dist/core/skill-info.d.ts.map +1 -0
- package/dist/core/skill-info.js +213 -0
- package/dist/core/skill-info.js.map +1 -0
- package/dist/core/skill-install.d.ts +24 -0
- package/dist/core/skill-install.d.ts.map +1 -0
- package/dist/core/skill-install.js +123 -0
- package/dist/core/skill-install.js.map +1 -0
- package/dist/core/skill-source.d.ts +29 -0
- package/dist/core/skill-source.d.ts.map +1 -0
- package/dist/core/skill-source.js +111 -0
- package/dist/core/skill-source.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +57 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/fuzzy-match.d.ts +16 -0
- package/dist/utils/fuzzy-match.d.ts.map +1 -0
- package/dist/utils/fuzzy-match.js +37 -0
- package/dist/utils/fuzzy-match.js.map +1 -0
- package/dist/utils/prompts.d.ts +16 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +46 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/registry.d.ts +6 -0
- package/dist/utils/registry.d.ts.map +1 -0
- package/dist/utils/registry.js +14 -0
- package/dist/utils/registry.js.map +1 -0
- package/package.json +42 -0
- package/src/commands/add.ts +136 -0
- package/src/commands/config.ts +47 -0
- package/src/commands/list.ts +68 -0
- package/src/commands/remove.ts +154 -0
- package/src/commands/show-dev.ts +223 -0
- package/src/commands/show.ts +203 -0
- package/src/core/agent-detect.ts +125 -0
- package/src/core/agents.ts +40 -0
- package/src/core/config-manager.ts +55 -0
- package/src/core/skill-hash.ts +61 -0
- package/src/core/skill-info.ts +248 -0
- package/src/core/skill-install.ts +165 -0
- package/src/core/skill-source.ts +125 -0
- package/src/index.ts +116 -0
- package/src/types/index.ts +64 -0
- package/src/utils/fuzzy-match.ts +48 -0
- package/src/utils/prompts.ts +54 -0
- package/src/utils/registry.ts +16 -0
- package/test/README.md +123 -0
- package/test/fixtures/multi-skills/skill-one/SKILL.md +8 -0
- package/test/fixtures/multi-skills/skill-two/SKILL.md +8 -0
- package/test/fixtures/sample-skill/SKILL.md +8 -0
- package/test/logs/add-remove.log +108 -0
- package/test/logs/config.log +72 -0
- package/test/logs/fuzzy-match.log +64 -0
- package/test/logs/show.log +110 -0
- package/test/run-all.sh +83 -0
- package/test/test-add-remove.sh +245 -0
- package/test/test-config.sh +208 -0
- package/test/test-fuzzy-match.sh +166 -0
- package/test/test-show.sh +179 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getSupportedAgentsMessage, SUPPORTED_AGENTS } from '../core/agents.js';
|
|
3
|
+
import { loadConfig, setDefaultAgents, getConfigPath } from '../core/config-manager.js';
|
|
4
|
+
import { promptAgentSelection } from '../utils/prompts.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Re-open agent selection dialog and update config.
|
|
8
|
+
*/
|
|
9
|
+
export async function configCommand(): Promise<void> {
|
|
10
|
+
const config = await loadConfig();
|
|
11
|
+
|
|
12
|
+
console.log(chalk.cyan('Sundial CLI Configuration'));
|
|
13
|
+
console.log(chalk.gray(`Config file: ${getConfigPath()}`));
|
|
14
|
+
console.log();
|
|
15
|
+
|
|
16
|
+
// Show current defaults
|
|
17
|
+
if (config.defaultAgents.length > 0) {
|
|
18
|
+
console.log('Current default agents:');
|
|
19
|
+
for (const agentFlag of config.defaultAgents) {
|
|
20
|
+
const agent = SUPPORTED_AGENTS.find(a => a.flag === agentFlag);
|
|
21
|
+
if (agent) {
|
|
22
|
+
console.log(` - ${agent.name} ${chalk.gray(`(~/${agent.folderName}/ and ./${agent.folderName}/)`)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
console.log();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(chalk.gray(getSupportedAgentsMessage()));
|
|
29
|
+
console.log();
|
|
30
|
+
|
|
31
|
+
// Show agent type selection dialog
|
|
32
|
+
const selectedAgents = await promptAgentSelection(config.defaultAgents);
|
|
33
|
+
|
|
34
|
+
await setDefaultAgents(selectedAgents);
|
|
35
|
+
|
|
36
|
+
console.log();
|
|
37
|
+
console.log(chalk.green('Configuration saved!'));
|
|
38
|
+
|
|
39
|
+
// Show new defaults
|
|
40
|
+
console.log('New default agents:');
|
|
41
|
+
for (const agentFlag of selectedAgents) {
|
|
42
|
+
const agent = SUPPORTED_AGENTS.find(a => a.flag === agentFlag);
|
|
43
|
+
if (agent) {
|
|
44
|
+
console.log(` - ${agent.name} ${chalk.gray(`(~/${agent.folderName}/ and ./${agent.folderName}/)`)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { SUPPORTED_AGENTS, getSupportedAgentsMessage } from '../core/agents.js';
|
|
3
|
+
import { listSkillsForAgent } from '../core/skill-info.js';
|
|
4
|
+
|
|
5
|
+
interface AgentSkills {
|
|
6
|
+
agentName: string;
|
|
7
|
+
folderName: string;
|
|
8
|
+
isGlobal: boolean;
|
|
9
|
+
skills: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* List all installed skills for each agent.
|
|
14
|
+
*/
|
|
15
|
+
export async function listCommand(): Promise<void> {
|
|
16
|
+
const allAgentSkills: AgentSkills[] = [];
|
|
17
|
+
|
|
18
|
+
// Check both local and global for each agent
|
|
19
|
+
for (const agent of SUPPORTED_AGENTS) {
|
|
20
|
+
// Local
|
|
21
|
+
const localSkills = await listSkillsForAgent(agent.folderName, false);
|
|
22
|
+
if (localSkills.length > 0) {
|
|
23
|
+
allAgentSkills.push({
|
|
24
|
+
agentName: agent.name,
|
|
25
|
+
folderName: agent.folderName,
|
|
26
|
+
isGlobal: false,
|
|
27
|
+
skills: localSkills
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Global
|
|
32
|
+
const globalSkills = await listSkillsForAgent(agent.folderName, true);
|
|
33
|
+
if (globalSkills.length > 0) {
|
|
34
|
+
allAgentSkills.push({
|
|
35
|
+
agentName: agent.name,
|
|
36
|
+
folderName: agent.folderName,
|
|
37
|
+
isGlobal: true,
|
|
38
|
+
skills: globalSkills
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (allAgentSkills.length === 0) {
|
|
44
|
+
console.log(chalk.yellow('No skills installed.'));
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(chalk.gray(getSupportedAgentsMessage()));
|
|
47
|
+
console.log(chalk.gray('You can add skills with "sun add <skill>"'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Print skills grouped by agent
|
|
52
|
+
for (const agentSkills of allAgentSkills) {
|
|
53
|
+
const location = agentSkills.isGlobal
|
|
54
|
+
? `global ~/${agentSkills.folderName}/`
|
|
55
|
+
: `${agentSkills.folderName}/`;
|
|
56
|
+
|
|
57
|
+
console.log(chalk.cyan(`${agentSkills.agentName} (${location}):`));
|
|
58
|
+
|
|
59
|
+
for (const skill of agentSkills.skills) {
|
|
60
|
+
console.log(` - ${skill}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(chalk.gray(getSupportedAgentsMessage()));
|
|
67
|
+
console.log(chalk.gray('You can add skills with "sun add <skill>" or remove with "sun remove <skill>"'));
|
|
68
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getAgentByFlag, SUPPORTED_AGENTS } from '../core/agents.js';
|
|
4
|
+
import { getDefaultAgents } from '../core/config-manager.js';
|
|
5
|
+
import { detectLocalAgents } from '../core/agent-detect.js';
|
|
6
|
+
import { removeSkill } from '../core/skill-install.js';
|
|
7
|
+
import type { AgentType, CommandFlags } from '../types/index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Determine which agents to remove from and whether to target global installs.
|
|
11
|
+
* Uses same logic as add command (except no first-run prompt).
|
|
12
|
+
*
|
|
13
|
+
* Logic:
|
|
14
|
+
* 1. If --global flag is set, always target global
|
|
15
|
+
* 2. If agent flags (--claude, --codex, etc.) are set, use those agents
|
|
16
|
+
* 3. Otherwise use saved default agents (error if none configured)
|
|
17
|
+
*
|
|
18
|
+
* For local vs global:
|
|
19
|
+
* - If --global: always global
|
|
20
|
+
* - Otherwise: check if any configured agents have local folders
|
|
21
|
+
* - If yes: target local
|
|
22
|
+
* - If no local folders exist for configured agents: target global
|
|
23
|
+
*/
|
|
24
|
+
async function resolveTargetAgents(flags: CommandFlags): Promise<{ agents: AgentType[]; isGlobal: boolean }> {
|
|
25
|
+
const forceGlobal = flags.global ?? false;
|
|
26
|
+
|
|
27
|
+
// Check if any agent flags were explicitly set
|
|
28
|
+
const explicitAgents: AgentType[] = [];
|
|
29
|
+
for (const agent of SUPPORTED_AGENTS) {
|
|
30
|
+
if (flags[agent.flag as keyof CommandFlags]) {
|
|
31
|
+
explicitAgents.push(agent.flag as AgentType);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let targetAgents: AgentType[];
|
|
36
|
+
|
|
37
|
+
// Determine which agents to target
|
|
38
|
+
if (explicitAgents.length > 0) {
|
|
39
|
+
targetAgents = explicitAgents;
|
|
40
|
+
} else {
|
|
41
|
+
// Use saved defaults (no first-run prompt for remove)
|
|
42
|
+
const defaultAgents = await getDefaultAgents();
|
|
43
|
+
if (defaultAgents.length === 0) {
|
|
44
|
+
throw new Error('No default agents configured. Run "sun add" first or use --claude/--codex/--gemini flags.');
|
|
45
|
+
}
|
|
46
|
+
targetAgents = defaultAgents;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Determine if we should target global or local
|
|
50
|
+
if (forceGlobal) {
|
|
51
|
+
return { agents: targetAgents, isGlobal: true };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if any of the target agents have local folders in current directory
|
|
55
|
+
const localAgents = await detectLocalAgents();
|
|
56
|
+
const localAgentFlags = new Set(localAgents.map(a => a.agent.flag));
|
|
57
|
+
|
|
58
|
+
const hasLocalFolders = targetAgents.some(agentFlag => localAgentFlags.has(agentFlag));
|
|
59
|
+
|
|
60
|
+
// If no local folders exist for any configured agent, target global
|
|
61
|
+
const isGlobal = !hasLocalFolders;
|
|
62
|
+
|
|
63
|
+
return { agents: targetAgents, isGlobal };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RemoveResult {
|
|
67
|
+
skill: string;
|
|
68
|
+
removedFrom: string[];
|
|
69
|
+
notFoundIn: string[];
|
|
70
|
+
isGlobal: boolean;
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Remove skill(s) from agent configuration(s).
|
|
76
|
+
*/
|
|
77
|
+
export async function removeCommand(skills: string[], flags: CommandFlags): Promise<void> {
|
|
78
|
+
if (skills.length === 0) {
|
|
79
|
+
console.error(chalk.red('Error: No skills specified. Usage: sun remove <skill> [skill2] ...'));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Resolve target agents
|
|
84
|
+
let agents: AgentType[];
|
|
85
|
+
let isGlobal: boolean;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const resolved = await resolveTargetAgents(flags);
|
|
89
|
+
agents = resolved.agents;
|
|
90
|
+
isGlobal = resolved.isGlobal;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const results: RemoveResult[] = [];
|
|
97
|
+
|
|
98
|
+
for (const skill of skills) {
|
|
99
|
+
const spinner = ora(`Removing ${skill}...`).start();
|
|
100
|
+
|
|
101
|
+
const result: RemoveResult = {
|
|
102
|
+
skill,
|
|
103
|
+
removedFrom: [],
|
|
104
|
+
notFoundIn: [],
|
|
105
|
+
isGlobal
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Remove from each target agent
|
|
110
|
+
for (const agentFlag of agents) {
|
|
111
|
+
const removed = await removeSkill(skill, agentFlag, isGlobal);
|
|
112
|
+
const agentName = getAgentByFlag(agentFlag)!.name;
|
|
113
|
+
|
|
114
|
+
if (removed) {
|
|
115
|
+
result.removedFrom.push(agentName);
|
|
116
|
+
} else {
|
|
117
|
+
result.notFoundIn.push(agentName);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (result.removedFrom.length > 0) {
|
|
122
|
+
spinner.succeed(`Removed ${skill} from ${result.removedFrom.join(' and ')}`);
|
|
123
|
+
} else {
|
|
124
|
+
const pathPrefix = isGlobal ? '~/' : './';
|
|
125
|
+
const checkedPaths = agents.map(a => `${pathPrefix}${getAgentByFlag(a)!.folderName}/`);
|
|
126
|
+
spinner.warn(`${skill} not found in configured agents (${checkedPaths.join(', ')})`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (result.notFoundIn.length > 0 && result.removedFrom.length > 0) {
|
|
130
|
+
console.log(chalk.gray(` (not found in: ${result.notFoundIn.join(', ')})`));
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
134
|
+
result.error = message;
|
|
135
|
+
spinner.fail(`Failed to remove ${skill}: ${message}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
results.push(result);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Print summary if multiple skills
|
|
142
|
+
if (skills.length > 1) {
|
|
143
|
+
const totalRemoved = results.filter(r => r.removedFrom.length > 0).length;
|
|
144
|
+
const totalFailed = results.filter(r => r.error || r.removedFrom.length === 0).length;
|
|
145
|
+
|
|
146
|
+
console.log();
|
|
147
|
+
if (totalRemoved > 0) {
|
|
148
|
+
console.log(chalk.green(`Removed ${totalRemoved} skill(s)`));
|
|
149
|
+
}
|
|
150
|
+
if (totalFailed > 0) {
|
|
151
|
+
console.log(chalk.yellow(`${totalFailed} skill(s) not found or failed`));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only show commands for debugging and exploration.
|
|
3
|
+
* These search the entire file system starting from home directory.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
import { exec } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
import { SUPPORTED_AGENTS } from '../core/agents.js';
|
|
13
|
+
import { readSkillMetadata } from '../core/skill-info.js';
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
// Timeout for find commands (60 seconds)
|
|
18
|
+
const FIND_TIMEOUT = 60000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* List skills in a directory, returning name and description.
|
|
22
|
+
*/
|
|
23
|
+
async function listSkillsInDir(skillsDir: string): Promise<Array<{ name: string; description: string }>> {
|
|
24
|
+
const skills: Array<{ name: string; description: string }> = [];
|
|
25
|
+
|
|
26
|
+
if (!await fs.pathExists(skillsDir)) {
|
|
27
|
+
return skills;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
31
|
+
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
const skillPath = path.join(skillsDir, entry.name);
|
|
35
|
+
const metadata = await readSkillMetadata(skillPath);
|
|
36
|
+
if (metadata) {
|
|
37
|
+
skills.push({
|
|
38
|
+
name: metadata.name,
|
|
39
|
+
description: metadata.description
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return skills;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* sun show all-agent-folders
|
|
50
|
+
* Find ALL agent folders (.claude, .codex, .gemini) starting from ~ and show skills in each.
|
|
51
|
+
*/
|
|
52
|
+
export async function showAllAgentFolders(): Promise<void> {
|
|
53
|
+
const homeDir = os.homedir();
|
|
54
|
+
|
|
55
|
+
// Build find command: find ~ -type d \( -name ".claude" -o -name ".codex" -o -name ".gemini" \)
|
|
56
|
+
const namePatterns = SUPPORTED_AGENTS.map(a => `-name "${a.folderName}"`).join(' -o ');
|
|
57
|
+
const findCmd = `find "${homeDir}" -type d \\( ${namePatterns} \\)`;
|
|
58
|
+
|
|
59
|
+
console.log(chalk.cyan('All Agent Folders'));
|
|
60
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
61
|
+
console.log();
|
|
62
|
+
|
|
63
|
+
let stdout = '';
|
|
64
|
+
try {
|
|
65
|
+
const result = await execAsync(findCmd, { maxBuffer: 10 * 1024 * 1024, timeout: FIND_TIMEOUT });
|
|
66
|
+
stdout = result.stdout;
|
|
67
|
+
} catch (err: any) {
|
|
68
|
+
// find exits non-zero on permission errors but still produces output
|
|
69
|
+
if (err.stdout) {
|
|
70
|
+
stdout = err.stdout;
|
|
71
|
+
} else {
|
|
72
|
+
console.log(chalk.red('Failed to search for agent folders.'));
|
|
73
|
+
console.log(chalk.gray(String(err)));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const agentPaths = stdout.trim().split('\n').filter(Boolean);
|
|
79
|
+
|
|
80
|
+
if (agentPaths.length === 0) {
|
|
81
|
+
console.log(chalk.yellow('No agent folders found.'));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const agentPath of agentPaths) {
|
|
86
|
+
const folderName = path.basename(agentPath);
|
|
87
|
+
const agent = SUPPORTED_AGENTS.find(a => a.folderName === folderName);
|
|
88
|
+
if (!agent) continue;
|
|
89
|
+
|
|
90
|
+
console.log(chalk.white.bold(agent.name));
|
|
91
|
+
console.log(chalk.gray(` ${agentPath}`));
|
|
92
|
+
|
|
93
|
+
// List skills
|
|
94
|
+
const skillsDir = path.join(agentPath, 'skills');
|
|
95
|
+
const skills = await listSkillsInDir(skillsDir);
|
|
96
|
+
|
|
97
|
+
if (skills.length === 0) {
|
|
98
|
+
console.log(chalk.gray(' No skills installed'));
|
|
99
|
+
} else {
|
|
100
|
+
console.log(` Skills (${skills.length}):`);
|
|
101
|
+
for (const skill of skills) {
|
|
102
|
+
console.log(` - ${skill.name} ${chalk.gray(`- ${skill.description}`)}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
console.log();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* sun show all-agent-skills-folders
|
|
111
|
+
* Find ALL <agent>/skills folders starting from ~ and show skills in each.
|
|
112
|
+
*/
|
|
113
|
+
export async function showAllAgentSkillsFolders(): Promise<void> {
|
|
114
|
+
const homeDir = os.homedir();
|
|
115
|
+
|
|
116
|
+
// Build find command for skills directories inside agent folders
|
|
117
|
+
// e.g., find ~ -type d \( -path "*/.claude/skills" -o -path "*/.codex/skills" -o -path "*/.gemini/skills" \)
|
|
118
|
+
const pathPatterns = SUPPORTED_AGENTS.map(a => `-path "*/${a.folderName}/skills"`).join(' -o ');
|
|
119
|
+
const findCmd = `find "${homeDir}" -type d \\( ${pathPatterns} \\)`;
|
|
120
|
+
|
|
121
|
+
console.log(chalk.cyan('All Agent Skills Folders'));
|
|
122
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
123
|
+
console.log();
|
|
124
|
+
|
|
125
|
+
let stdout = '';
|
|
126
|
+
try {
|
|
127
|
+
const result = await execAsync(findCmd, { maxBuffer: 10 * 1024 * 1024, timeout: FIND_TIMEOUT });
|
|
128
|
+
stdout = result.stdout;
|
|
129
|
+
} catch (err: any) {
|
|
130
|
+
if (err.stdout) {
|
|
131
|
+
stdout = err.stdout;
|
|
132
|
+
} else {
|
|
133
|
+
console.log(chalk.red('Failed to search for skills folders.'));
|
|
134
|
+
console.log(chalk.gray(String(err)));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const skillsDirs = stdout.trim().split('\n').filter(Boolean);
|
|
140
|
+
|
|
141
|
+
if (skillsDirs.length === 0) {
|
|
142
|
+
console.log(chalk.yellow('No agent skills folders found.'));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const skillsDir of skillsDirs) {
|
|
147
|
+
// Get agent name from parent folder
|
|
148
|
+
const agentFolder = path.basename(path.dirname(skillsDir));
|
|
149
|
+
const agent = SUPPORTED_AGENTS.find(a => a.folderName === agentFolder);
|
|
150
|
+
|
|
151
|
+
console.log(chalk.white.bold(agent?.name ?? agentFolder));
|
|
152
|
+
console.log(chalk.gray(` ${skillsDir}`));
|
|
153
|
+
|
|
154
|
+
// List skills
|
|
155
|
+
const skills = await listSkillsInDir(skillsDir);
|
|
156
|
+
|
|
157
|
+
if (skills.length === 0) {
|
|
158
|
+
console.log(chalk.gray(' No skills installed'));
|
|
159
|
+
} else {
|
|
160
|
+
console.log(` Skills (${skills.length}):`);
|
|
161
|
+
for (const skill of skills) {
|
|
162
|
+
console.log(` - ${skill.name} ${chalk.gray(`- ${skill.description}`)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
console.log();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* sun show all-skill-folders
|
|
171
|
+
* Find ALL folders containing a valid SKILL.md (with name/description frontmatter).
|
|
172
|
+
*/
|
|
173
|
+
export async function showAllSkillFolders(): Promise<void> {
|
|
174
|
+
const homeDir = os.homedir();
|
|
175
|
+
|
|
176
|
+
// Find all SKILL.md files
|
|
177
|
+
const findCmd = `find "${homeDir}" -name "SKILL.md" -type f`;
|
|
178
|
+
|
|
179
|
+
console.log(chalk.cyan('All Skill Folders'));
|
|
180
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
181
|
+
console.log();
|
|
182
|
+
|
|
183
|
+
let stdout = '';
|
|
184
|
+
try {
|
|
185
|
+
const result = await execAsync(findCmd, { maxBuffer: 10 * 1024 * 1024, timeout: FIND_TIMEOUT });
|
|
186
|
+
stdout = result.stdout;
|
|
187
|
+
} catch (err: any) {
|
|
188
|
+
if (err.stdout) {
|
|
189
|
+
stdout = err.stdout;
|
|
190
|
+
} else {
|
|
191
|
+
console.log(chalk.red('Failed to search for SKILL.md files.'));
|
|
192
|
+
console.log(chalk.gray(String(err)));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const skillMdPaths = stdout.trim().split('\n').filter(Boolean);
|
|
198
|
+
|
|
199
|
+
if (skillMdPaths.length === 0) {
|
|
200
|
+
console.log(chalk.yellow('No SKILL.md files found.'));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let validCount = 0;
|
|
205
|
+
|
|
206
|
+
for (const skillMdPath of skillMdPaths) {
|
|
207
|
+
const skillDir = path.dirname(skillMdPath);
|
|
208
|
+
const metadata = await readSkillMetadata(skillDir);
|
|
209
|
+
|
|
210
|
+
// Only show if it has valid name and description
|
|
211
|
+
if (metadata && metadata.name && metadata.description) {
|
|
212
|
+
validCount++;
|
|
213
|
+
console.log(chalk.white.bold(metadata.name));
|
|
214
|
+
console.log(chalk.gray(` ${skillDir}`));
|
|
215
|
+
console.log(` ${metadata.description}`);
|
|
216
|
+
console.log();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (validCount === 0) {
|
|
221
|
+
console.log(chalk.yellow('No valid SKILL.md files found (missing name/description frontmatter).'));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { findSkillInstallations, readSkillMetadata } from '../core/skill-info.js';
|
|
4
|
+
import { getAgentByFlag } from '../core/agents.js';
|
|
5
|
+
import { detectAllAgents, detectLocalAgents } from '../core/agent-detect.js';
|
|
6
|
+
import { getDefaultAgents } from '../core/config-manager.js';
|
|
7
|
+
import { showAllAgentFolders, showAllAgentSkillsFolders, showAllSkillFolders } from './show-dev.js';
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List all skills in a given skills directory path.
|
|
12
|
+
*/
|
|
13
|
+
async function listSkillsInPath(skillsDir: string): Promise<Array<{ name: string; description: string }>> {
|
|
14
|
+
const skills: Array<{ name: string; description: string }> = [];
|
|
15
|
+
|
|
16
|
+
if (!await fs.pathExists(skillsDir)) {
|
|
17
|
+
return skills;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
21
|
+
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
const skillPath = path.join(skillsDir, entry.name);
|
|
25
|
+
const metadata = await readSkillMetadata(skillPath);
|
|
26
|
+
if (metadata) {
|
|
27
|
+
skills.push({
|
|
28
|
+
name: metadata.name,
|
|
29
|
+
description: metadata.description
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return skills;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Show all agent folders and their installed skills.
|
|
40
|
+
*/
|
|
41
|
+
async function showAllAgents(): Promise<void> {
|
|
42
|
+
const allAgents = await detectAllAgents();
|
|
43
|
+
const defaultAgents = await getDefaultAgents();
|
|
44
|
+
const defaultSet = new Set(defaultAgents);
|
|
45
|
+
const localAgents = await detectLocalAgents();
|
|
46
|
+
const localAgentFlags = new Set(localAgents.map(a => a.agent.flag));
|
|
47
|
+
|
|
48
|
+
if (allAgents.length === 0) {
|
|
49
|
+
console.log(chalk.yellow('No agent folders found.'));
|
|
50
|
+
console.log(chalk.gray('Agent folders are directories like .claude/, .codex/, .gemini/'));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(chalk.cyan('Agent Folders & Installed Skills'));
|
|
55
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
56
|
+
console.log();
|
|
57
|
+
|
|
58
|
+
for (const detected of allAgents) {
|
|
59
|
+
const { agent, path: agentPath, isGlobal } = detected;
|
|
60
|
+
const locationLabel = isGlobal ? chalk.gray('(global)') : chalk.gray('(local)');
|
|
61
|
+
const isDefault = defaultSet.has(agent.flag as any);
|
|
62
|
+
const hasLocalFolder = localAgentFlags.has(agent.flag);
|
|
63
|
+
|
|
64
|
+
// Show checkmark if this folder would be affected by sun add/remove:
|
|
65
|
+
// - Local folder: affected if agent is selected
|
|
66
|
+
// - Global folder: affected if agent is selected AND no local folder exists
|
|
67
|
+
const wouldBeAffected = isDefault && (isGlobal ? !hasLocalFolder : true);
|
|
68
|
+
const marker = wouldBeAffected ? chalk.green('✓') : chalk.gray('○');
|
|
69
|
+
|
|
70
|
+
console.log(`${marker} ${chalk.white.bold(agent.name)} ${locationLabel}`);
|
|
71
|
+
console.log(chalk.gray(` ${agentPath}`));
|
|
72
|
+
|
|
73
|
+
// List skills in this agent folder
|
|
74
|
+
const skillsDir = path.join(agentPath, 'skills');
|
|
75
|
+
const skills = await listSkillsInPath(skillsDir);
|
|
76
|
+
|
|
77
|
+
if (skills.length === 0) {
|
|
78
|
+
console.log(chalk.gray(' No skills installed'));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(` Skills (${skills.length}):`);
|
|
81
|
+
for (const skill of skills) {
|
|
82
|
+
console.log(` - ${skill.name} ${chalk.gray(`- ${skill.description}`)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.log();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`${chalk.green('✓')} = affected by sun add/remove in this directory`);
|
|
89
|
+
console.log('Run "sun config" to change selected agents.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Show skill details and installation locations.
|
|
94
|
+
* If no skill specified, shows all agent folders and their packages.
|
|
95
|
+
*
|
|
96
|
+
* Secret commands (not in README, documented in DEV.md):
|
|
97
|
+
* - all-agent-folders: Find all agent folders on the system
|
|
98
|
+
* - all-agent-skills-folders: Find all <agent>/skills folders
|
|
99
|
+
* - all-skill-folders: Find all folders with valid SKILL.md
|
|
100
|
+
*/
|
|
101
|
+
export async function showCommand(skillName?: string): Promise<void> {
|
|
102
|
+
// Handle secret dev commands
|
|
103
|
+
if (skillName === 'all-agent-folders') {
|
|
104
|
+
await showAllAgentFolders();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (skillName === 'all-agent-skills-folders') {
|
|
109
|
+
await showAllAgentSkillsFolders();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (skillName === 'all-skill-folders') {
|
|
114
|
+
await showAllSkillFolders();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If no skill specified, show all agents and their packages
|
|
119
|
+
if (!skillName) {
|
|
120
|
+
await showAllAgents();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const installations = await findSkillInstallations(skillName);
|
|
125
|
+
|
|
126
|
+
if (installations.length === 0) {
|
|
127
|
+
console.error(chalk.yellow(`Skill "${skillName}" is not installed.`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if there are multiple versions (different content hashes)
|
|
132
|
+
const uniqueHashes = new Set(installations.map(i => i.contentHash));
|
|
133
|
+
const hasMultipleVersions = uniqueHashes.size > 1;
|
|
134
|
+
|
|
135
|
+
if (hasMultipleVersions) {
|
|
136
|
+
// Multiple versions detected
|
|
137
|
+
console.log(chalk.cyan(`Skill: ${skillName}`));
|
|
138
|
+
console.log(chalk.yellow('Warning: Multiple versions detected'));
|
|
139
|
+
console.log();
|
|
140
|
+
|
|
141
|
+
// Group by content hash
|
|
142
|
+
const byHash = new Map<string, typeof installations>();
|
|
143
|
+
for (const inst of installations) {
|
|
144
|
+
const existing = byHash.get(inst.contentHash) || [];
|
|
145
|
+
existing.push(inst);
|
|
146
|
+
byHash.set(inst.contentHash, existing);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let versionNum = 1;
|
|
150
|
+
for (const [hash, insts] of byHash) {
|
|
151
|
+
const firstInst = insts[0];
|
|
152
|
+
const locations = insts.map(i => {
|
|
153
|
+
const agent = getAgentByFlag(i.agent)!;
|
|
154
|
+
return i.isGlobal ? `~/${agent.folderName}/` : `${agent.folderName}/`;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log(chalk.white(`Version ${versionNum} (${locations.join(', ')}):`));
|
|
158
|
+
console.log(` Description: ${firstInst.metadata.description}`);
|
|
159
|
+
|
|
160
|
+
if (firstInst.metadata.metadata?.author) {
|
|
161
|
+
console.log(` Author: ${firstInst.metadata.metadata.author}`);
|
|
162
|
+
}
|
|
163
|
+
if (firstInst.metadata.metadata?.version) {
|
|
164
|
+
console.log(` Version: ${firstInst.metadata.metadata.version}`);
|
|
165
|
+
}
|
|
166
|
+
if (firstInst.metadata.license) {
|
|
167
|
+
console.log(` License: ${firstInst.metadata.license}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(` Content hash: ${hash}`);
|
|
171
|
+
console.log();
|
|
172
|
+
|
|
173
|
+
versionNum++;
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
// Single version across all installations
|
|
177
|
+
const firstInst = installations[0];
|
|
178
|
+
|
|
179
|
+
console.log(chalk.cyan(`Skill: ${skillName}`));
|
|
180
|
+
console.log(`Description: ${firstInst.metadata.description}`);
|
|
181
|
+
|
|
182
|
+
if (firstInst.metadata.metadata?.author) {
|
|
183
|
+
console.log(`Author: ${firstInst.metadata.metadata.author}`);
|
|
184
|
+
}
|
|
185
|
+
if (firstInst.metadata.metadata?.version) {
|
|
186
|
+
console.log(`Version: ${firstInst.metadata.metadata.version}`);
|
|
187
|
+
}
|
|
188
|
+
if (firstInst.metadata.license) {
|
|
189
|
+
console.log(`License: ${firstInst.metadata.license}`);
|
|
190
|
+
}
|
|
191
|
+
if (firstInst.metadata.compatibility) {
|
|
192
|
+
console.log(`Compatibility: ${firstInst.metadata.compatibility}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log();
|
|
196
|
+
console.log('Installed in:');
|
|
197
|
+
for (const inst of installations) {
|
|
198
|
+
const agent = getAgentByFlag(inst.agent)!;
|
|
199
|
+
const location = inst.isGlobal ? '(global)' : '(local)';
|
|
200
|
+
console.log(` - ${agent.folderName}/ ${chalk.gray(location)}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|