dotai-cli 1.0.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/README.md +186 -0
- package/package.json +54 -0
- package/src/commands/config.js +93 -0
- package/src/commands/create.js +120 -0
- package/src/commands/install.js +120 -0
- package/src/commands/list.js +68 -0
- package/src/commands/open.js +82 -0
- package/src/commands/sync.js +71 -0
- package/src/commands/uninstall.js +92 -0
- package/src/index.js +141 -0
- package/src/lib/config.js +90 -0
- package/src/lib/paths.js +60 -0
- package/src/lib/skills.js +261 -0
- package/src/providers/index.js +125 -0
- package/src/providers/mcp.js +184 -0
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# dotai
|
|
2
|
+
|
|
3
|
+
**Dotfiles for AI** - Manage skills and MCP servers across all your AI coding assistants from one place.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g dotai-cli
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Why dotai?
|
|
10
|
+
|
|
11
|
+
You use multiple AI coding assistants - Claude Code, Cursor, Gemini CLI, Codex, etc. Each has its own config location for skills and MCP servers. **dotai** lets you configure once and sync everywhere.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Skills Management** - Create, install, and sync skills across providers
|
|
16
|
+
- **MCP Servers** - Configure once, deploy to all apps *(coming soon)*
|
|
17
|
+
- **AI-Assisted Creation** - Generate skills with LLM prompts
|
|
18
|
+
- **Cross-Platform** - Works on Mac, Windows, and Linux
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Create a skill (opens editor for full instructions)
|
|
24
|
+
dotai skill create
|
|
25
|
+
|
|
26
|
+
# Install to all your AI assistants
|
|
27
|
+
dotai skill install my-skill
|
|
28
|
+
|
|
29
|
+
# List your skills
|
|
30
|
+
dotai skill list
|
|
31
|
+
|
|
32
|
+
# Sync everything
|
|
33
|
+
dotai skill sync
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Supported Providers
|
|
37
|
+
|
|
38
|
+
### Skills
|
|
39
|
+
| Provider | Global Path | Project Path |
|
|
40
|
+
|----------|-------------|--------------|
|
|
41
|
+
| Claude Code | `~/.claude/skills/<name>/` | `.claude/skills/<name>/` |
|
|
42
|
+
| Cursor | `~/.cursor/skills/<name>/` | `.cursor/skills/<name>/` |
|
|
43
|
+
| Gemini CLI | `~/.gemini/skills/<name>/` | `.gemini/skills/<name>/` |
|
|
44
|
+
| OpenCode | `~/.config/opencode/skill/<name>/` | `.opencode/skill/<name>/` |
|
|
45
|
+
| Codex CLI | `~/.codex/skills/<name>/` | `skills/<name>/` |
|
|
46
|
+
| Antigravity | `~/.gemini/antigravity/skills/<name>/` | `.agent/skills/<name>/` |
|
|
47
|
+
|
|
48
|
+
### MCP Servers *(coming soon)*
|
|
49
|
+
| App | Global Config | Project Config |
|
|
50
|
+
|-----|---------------|----------------|
|
|
51
|
+
| Claude Code | `~/.claude.json` | `.mcp.json` |
|
|
52
|
+
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` | - |
|
|
53
|
+
| Cursor | `~/.cursor/mcp.json` | `.cursor/mcp.json` |
|
|
54
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | - |
|
|
55
|
+
| VS Code | User `settings.json` | `.vscode/mcp.json` |
|
|
56
|
+
| Cline | VS Code storage `cline_mcp_settings.json` | - |
|
|
57
|
+
| Zed | `~/.config/zed/settings.json` (key: `context_servers`) | - |
|
|
58
|
+
| Roo Code | VS Code storage `mcp_settings.json` | `.roo/mcp.json` |
|
|
59
|
+
| Antigravity | `mcp_config.json` (via MCP Store) | - |
|
|
60
|
+
|
|
61
|
+
## Commands
|
|
62
|
+
|
|
63
|
+
### Skills
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Create a new skill
|
|
67
|
+
dotai skill create [name]
|
|
68
|
+
dotai skill create my-skill -d "Short description"
|
|
69
|
+
|
|
70
|
+
# Install skill to providers
|
|
71
|
+
dotai skill install <name> # Install to enabled providers
|
|
72
|
+
dotai skill install <name> -a # Install to ALL providers
|
|
73
|
+
dotai skill install <name> -p claude-code,cursor # Specific providers
|
|
74
|
+
dotai skill install <name> --project # Project scope instead of global
|
|
75
|
+
dotai skill install /path/to/skill # Import from external path
|
|
76
|
+
|
|
77
|
+
# List skills
|
|
78
|
+
dotai skill list # List all skills
|
|
79
|
+
dotai skill list -v # With installation status
|
|
80
|
+
|
|
81
|
+
# Sync all skills
|
|
82
|
+
dotai skill sync # Sync to enabled providers
|
|
83
|
+
dotai skill sync -a # Sync to all providers
|
|
84
|
+
|
|
85
|
+
# Uninstall
|
|
86
|
+
dotai skill uninstall <name>
|
|
87
|
+
dotai skill uninstall <name> -y # Skip confirmation
|
|
88
|
+
|
|
89
|
+
# Open in editor/finder
|
|
90
|
+
dotai skill open <name> # Open folder
|
|
91
|
+
dotai skill open <name> -f # Open SKILL.md file
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### MCP Servers *(coming soon)*
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
dotai mcp add # Add an MCP server
|
|
98
|
+
dotai mcp list # List configured servers
|
|
99
|
+
dotai mcp sync # Sync to all apps
|
|
100
|
+
dotai mcp remove <name> # Remove a server
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Configuration
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
dotai providers # List supported providers
|
|
107
|
+
dotai config # Interactive configuration
|
|
108
|
+
dotai config -s # Show current config
|
|
109
|
+
dotai enable <provider> # Enable a provider
|
|
110
|
+
dotai disable <provider> # Disable a provider
|
|
111
|
+
dotai repo # Open dotai folder
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Creating Skills with AI
|
|
115
|
+
|
|
116
|
+
When you run `dotai skill create`, it:
|
|
117
|
+
|
|
118
|
+
1. Asks for skill name and description
|
|
119
|
+
2. Creates the skill folder with a template
|
|
120
|
+
3. Opens SKILL.md in your editor
|
|
121
|
+
|
|
122
|
+
**Pro tip:** Describe what you want the skill to do to any LLM (ChatGPT, Claude, etc.) and ask it to generate the SKILL.md content. Then paste it into the file.
|
|
123
|
+
|
|
124
|
+
Example prompt:
|
|
125
|
+
```
|
|
126
|
+
Create a SKILL.md file for an AI coding assistant skill that helps with
|
|
127
|
+
database migrations. It should include best practices for writing migrations,
|
|
128
|
+
rollback strategies, and common pitfalls to avoid. Use YAML frontmatter with
|
|
129
|
+
name and description fields.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Skill Structure
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
my-skill/
|
|
136
|
+
āāā SKILL.md # Required: Instructions for the AI
|
|
137
|
+
āāā scripts/ # Optional: Helper scripts
|
|
138
|
+
āāā references/ # Optional: Documentation
|
|
139
|
+
āāā templates/ # Optional: Code templates
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### SKILL.md Format
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
---
|
|
146
|
+
name: my-skill
|
|
147
|
+
description: Short description for auto-discovery (max 200 chars)
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
# My Skill
|
|
151
|
+
|
|
152
|
+
Detailed instructions for the AI agent.
|
|
153
|
+
|
|
154
|
+
## When to use this skill
|
|
155
|
+
|
|
156
|
+
- Scenario 1
|
|
157
|
+
- Scenario 2
|
|
158
|
+
|
|
159
|
+
## Instructions
|
|
160
|
+
|
|
161
|
+
Step-by-step guidance...
|
|
162
|
+
|
|
163
|
+
## Examples
|
|
164
|
+
|
|
165
|
+
Show expected inputs/outputs...
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Data Location
|
|
169
|
+
|
|
170
|
+
All your configurations are stored in `~/.dotai/`:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
~/.dotai/
|
|
174
|
+
āāā config.json # Your settings
|
|
175
|
+
āāā skills/ # Central skill repository
|
|
176
|
+
āāā my-skill/
|
|
177
|
+
āāā another-skill/
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
Built by [Jithin Garapati](https://github.com/jithin-garapati)
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dotai-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Unified CLI for AI coding assistants - manage skills and MCP servers across Claude Code, Gemini CLI, Cursor, VS Code, and more",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dotai": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js",
|
|
12
|
+
"link": "npm link"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ai",
|
|
16
|
+
"dotfiles",
|
|
17
|
+
"skills",
|
|
18
|
+
"mcp",
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"gemini",
|
|
22
|
+
"cursor",
|
|
23
|
+
"cline",
|
|
24
|
+
"opencode",
|
|
25
|
+
"codex",
|
|
26
|
+
"cli",
|
|
27
|
+
"agent",
|
|
28
|
+
"mcp-server",
|
|
29
|
+
"config"
|
|
30
|
+
],
|
|
31
|
+
"author": "Jithin Garapati",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/jithin-garapati/dotai"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/jithin-garapati/dotai#readme",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"src/**/*",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"chalk": "^5.3.0",
|
|
47
|
+
"commander": "^12.1.0",
|
|
48
|
+
"enquirer": "^2.4.1",
|
|
49
|
+
"fs-extra": "^11.2.0",
|
|
50
|
+
"gray-matter": "^4.0.3",
|
|
51
|
+
"ora": "^8.0.1",
|
|
52
|
+
"yaml": "^2.4.1"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
const { prompt } = Enquirer;
|
|
4
|
+
import { loadConfig, saveConfig, enableProvider, disableProvider } from '../lib/config.js';
|
|
5
|
+
import { providers, getProviderIds } from '../providers/index.js';
|
|
6
|
+
import { getConfigFilePath, getSkillsRepoDir } from '../lib/paths.js';
|
|
7
|
+
|
|
8
|
+
export async function configCommand(options) {
|
|
9
|
+
const config = await loadConfig();
|
|
10
|
+
|
|
11
|
+
if (options.show) {
|
|
12
|
+
console.log(chalk.bold('\nāļø Current Configuration\n'));
|
|
13
|
+
console.log(chalk.dim(`Config file: ${getConfigFilePath()}`));
|
|
14
|
+
console.log(chalk.dim(`Skills repo: ${getSkillsRepoDir()}\n`));
|
|
15
|
+
console.log(JSON.stringify(config, null, 2));
|
|
16
|
+
console.log('');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Interactive config
|
|
21
|
+
console.log(chalk.bold('\nāļø Configure skills-cli\n'));
|
|
22
|
+
|
|
23
|
+
const allProviders = getProviderIds();
|
|
24
|
+
|
|
25
|
+
const answers = await prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'multiselect',
|
|
28
|
+
name: 'enabledProviders',
|
|
29
|
+
message: 'Select providers to enable:',
|
|
30
|
+
choices: allProviders.map(id => ({
|
|
31
|
+
name: id,
|
|
32
|
+
message: `${providers[id].name} - ${chalk.dim(providers[id].description)}`,
|
|
33
|
+
value: id
|
|
34
|
+
})),
|
|
35
|
+
initial: config.enabledProviders
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'select',
|
|
39
|
+
name: 'defaultScope',
|
|
40
|
+
message: 'Default installation scope:',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'global', message: 'Global - Available in all projects' },
|
|
43
|
+
{ name: 'project', message: 'Project - Only in current project' }
|
|
44
|
+
],
|
|
45
|
+
initial: config.defaultScope === 'project' ? 1 : 0
|
|
46
|
+
}
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const newConfig = {
|
|
50
|
+
...config,
|
|
51
|
+
enabledProviders: answers.enabledProviders,
|
|
52
|
+
defaultScope: answers.defaultScope,
|
|
53
|
+
updatedAt: new Date().toISOString()
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
await saveConfig(newConfig);
|
|
57
|
+
|
|
58
|
+
console.log(chalk.green('\nā Configuration saved\n'));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function enableCommand(providerId) {
|
|
62
|
+
if (!providerId) {
|
|
63
|
+
console.error(chalk.red('Error: Provider ID required'));
|
|
64
|
+
console.log(`Available providers: ${getProviderIds().join(', ')}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!providers[providerId]) {
|
|
69
|
+
console.error(chalk.red(`Unknown provider: ${providerId}`));
|
|
70
|
+
console.log(`Available providers: ${getProviderIds().join(', ')}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await enableProvider(providerId);
|
|
75
|
+
console.log(chalk.green(`ā Enabled ${providers[providerId].name}`));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function disableCommand(providerId) {
|
|
79
|
+
if (!providerId) {
|
|
80
|
+
console.error(chalk.red('Error: Provider ID required'));
|
|
81
|
+
console.log(`Available providers: ${getProviderIds().join(', ')}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!providers[providerId]) {
|
|
86
|
+
console.error(chalk.red(`Unknown provider: ${providerId}`));
|
|
87
|
+
console.log(`Available providers: ${getProviderIds().join(', ')}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await disableProvider(providerId);
|
|
92
|
+
console.log(chalk.green(`ā Disabled ${providers[providerId].name}`));
|
|
93
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
const { prompt } = Enquirer;
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { platform } from 'os';
|
|
7
|
+
import { createSkill } from '../lib/skills.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the command to open a file in default editor
|
|
11
|
+
*/
|
|
12
|
+
function getEditorCommand() {
|
|
13
|
+
// Check for $EDITOR environment variable first
|
|
14
|
+
if (process.env.EDITOR) {
|
|
15
|
+
return process.env.EDITOR;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Fall back to platform defaults
|
|
19
|
+
switch (platform()) {
|
|
20
|
+
case 'darwin':
|
|
21
|
+
return 'open -t'; // Opens in default text editor on Mac
|
|
22
|
+
case 'win32':
|
|
23
|
+
return 'notepad';
|
|
24
|
+
default:
|
|
25
|
+
return process.env.VISUAL || 'xdg-open';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Open file in editor
|
|
31
|
+
*/
|
|
32
|
+
function openInEditor(filePath) {
|
|
33
|
+
const editor = getEditorCommand();
|
|
34
|
+
const parts = editor.split(' ');
|
|
35
|
+
const cmd = parts[0];
|
|
36
|
+
const args = [...parts.slice(1), filePath];
|
|
37
|
+
|
|
38
|
+
return spawn(cmd, args, {
|
|
39
|
+
detached: true,
|
|
40
|
+
stdio: 'ignore'
|
|
41
|
+
}).unref();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function createCommand(name, options) {
|
|
45
|
+
console.log(chalk.bold('\nš§ Create a new skill\n'));
|
|
46
|
+
|
|
47
|
+
let skillName = name;
|
|
48
|
+
let description = options.description;
|
|
49
|
+
|
|
50
|
+
// Interactive mode if name not provided
|
|
51
|
+
if (!skillName) {
|
|
52
|
+
const answers = await prompt([
|
|
53
|
+
{
|
|
54
|
+
type: 'input',
|
|
55
|
+
name: 'name',
|
|
56
|
+
message: 'Skill name (lowercase, hyphens allowed):',
|
|
57
|
+
validate: (value) => {
|
|
58
|
+
if (!value) return 'Name is required';
|
|
59
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
60
|
+
return 'Name must be lowercase with hyphens only';
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: 'input',
|
|
67
|
+
name: 'description',
|
|
68
|
+
message: 'Short description for auto-discovery (max 200 chars):',
|
|
69
|
+
validate: (value) => {
|
|
70
|
+
if (!value) return 'Description is required';
|
|
71
|
+
if (value.length > 200) return 'Description must be 200 characters or less';
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
skillName = answers.name;
|
|
78
|
+
description = answers.description;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Validate inputs
|
|
82
|
+
if (!skillName) {
|
|
83
|
+
console.error(chalk.red('Error: Skill name is required'));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!description) {
|
|
88
|
+
console.error(chalk.red('Error: Description is required (use -d or --description)'));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const spinner = ora('Creating skill...').start();
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await createSkill(skillName, description);
|
|
96
|
+
spinner.succeed(chalk.green(`Skill '${skillName}' created!`));
|
|
97
|
+
|
|
98
|
+
console.log(chalk.dim(`\nLocation: ${result.path}`));
|
|
99
|
+
|
|
100
|
+
// Ask if user wants to open in editor
|
|
101
|
+
const { openEditor } = await prompt({
|
|
102
|
+
type: 'confirm',
|
|
103
|
+
name: 'openEditor',
|
|
104
|
+
message: 'Open SKILL.md in editor to write instructions?',
|
|
105
|
+
initial: true
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (openEditor) {
|
|
109
|
+
console.log(chalk.dim(`\nOpening ${result.skillMdPath}...\n`));
|
|
110
|
+
openInEditor(result.skillMdPath);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(chalk.yellow('\nWhen ready, install with:'));
|
|
114
|
+
console.log(` ${chalk.cyan(`dotai skill install ${skillName}`)}\n`);
|
|
115
|
+
|
|
116
|
+
} catch (err) {
|
|
117
|
+
spinner.fail(chalk.red(`Failed to create skill: ${err.message}`));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
const { prompt } = Enquirer;
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import { installSkill, listCentralSkills, importSkill } from '../lib/skills.js';
|
|
7
|
+
import { loadConfig } from '../lib/config.js';
|
|
8
|
+
import { providers, getProviderIds } from '../providers/index.js';
|
|
9
|
+
|
|
10
|
+
export async function installCommand(skillNameOrPath, options) {
|
|
11
|
+
console.log(chalk.bold('\nš¦ Install skill to providers\n'));
|
|
12
|
+
|
|
13
|
+
const config = await loadConfig();
|
|
14
|
+
let skillName = skillNameOrPath;
|
|
15
|
+
|
|
16
|
+
// Check if it's a path to a skill folder
|
|
17
|
+
if (skillNameOrPath && await fs.pathExists(skillNameOrPath)) {
|
|
18
|
+
const spinner = ora('Importing skill from path...').start();
|
|
19
|
+
try {
|
|
20
|
+
const imported = await importSkill(skillNameOrPath);
|
|
21
|
+
spinner.succeed(`Imported skill '${imported.name}'`);
|
|
22
|
+
skillName = imported.name;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
spinner.fail(chalk.red(`Failed to import: ${err.message}`));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// If no skill name, show interactive picker
|
|
30
|
+
if (!skillName) {
|
|
31
|
+
const skills = await listCentralSkills();
|
|
32
|
+
if (skills.length === 0) {
|
|
33
|
+
console.log(chalk.yellow('No skills found in your repository.'));
|
|
34
|
+
console.log(`Create one with: ${chalk.cyan('dotai skill create')}\n`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const answer = await prompt({
|
|
39
|
+
type: 'select',
|
|
40
|
+
name: 'skill',
|
|
41
|
+
message: 'Select a skill to install:',
|
|
42
|
+
choices: skills.map(s => ({
|
|
43
|
+
name: s.name,
|
|
44
|
+
message: `${s.name} - ${chalk.dim(s.description || 'No description')}`,
|
|
45
|
+
value: s.name
|
|
46
|
+
}))
|
|
47
|
+
});
|
|
48
|
+
skillName = answer.skill;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Determine which providers to install to
|
|
52
|
+
let targetProviders = options.providers
|
|
53
|
+
? options.providers.split(',').map(p => p.trim())
|
|
54
|
+
: config.enabledProviders;
|
|
55
|
+
|
|
56
|
+
// If --all flag, use all providers
|
|
57
|
+
if (options.all) {
|
|
58
|
+
targetProviders = getProviderIds();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Interactive provider selection if requested
|
|
62
|
+
if (options.interactive) {
|
|
63
|
+
const answer = await prompt({
|
|
64
|
+
type: 'multiselect',
|
|
65
|
+
name: 'providers',
|
|
66
|
+
message: 'Select providers to install to:',
|
|
67
|
+
choices: Object.values(providers).map(p => ({
|
|
68
|
+
name: p.id,
|
|
69
|
+
message: `${p.name} - ${chalk.dim(p.description)}`,
|
|
70
|
+
value: p.id
|
|
71
|
+
})),
|
|
72
|
+
initial: targetProviders
|
|
73
|
+
});
|
|
74
|
+
targetProviders = answer.providers;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Determine scope
|
|
78
|
+
const scope = options.project ? 'project' : 'global';
|
|
79
|
+
|
|
80
|
+
console.log(chalk.dim(`Installing '${skillName}' to ${scope} scope...\n`));
|
|
81
|
+
|
|
82
|
+
const spinner = ora('Installing...').start();
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const results = await installSkill(skillName, targetProviders, scope);
|
|
86
|
+
|
|
87
|
+
spinner.stop();
|
|
88
|
+
|
|
89
|
+
// Show results
|
|
90
|
+
let successCount = 0;
|
|
91
|
+
let failCount = 0;
|
|
92
|
+
|
|
93
|
+
for (const result of results) {
|
|
94
|
+
const provider = providers[result.providerId];
|
|
95
|
+
if (result.success) {
|
|
96
|
+
successCount++;
|
|
97
|
+
console.log(chalk.green(` ā ${provider.name}`));
|
|
98
|
+
console.log(chalk.dim(` ${result.targetPath}`));
|
|
99
|
+
} else {
|
|
100
|
+
failCount++;
|
|
101
|
+
console.log(chalk.red(` ā ${provider.name}: ${result.error}`));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
if (successCount > 0) {
|
|
108
|
+
console.log(chalk.green(`Installed to ${successCount} provider(s)`));
|
|
109
|
+
}
|
|
110
|
+
if (failCount > 0) {
|
|
111
|
+
console.log(chalk.red(`Failed for ${failCount} provider(s)`));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('');
|
|
115
|
+
|
|
116
|
+
} catch (err) {
|
|
117
|
+
spinner.fail(chalk.red(`Failed to install: ${err.message}`));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { listCentralSkills, getSkillInstallStatus } from '../lib/skills.js';
|
|
4
|
+
import { getSkillsRepoDir } from '../lib/paths.js';
|
|
5
|
+
import { providers } from '../providers/index.js';
|
|
6
|
+
|
|
7
|
+
export async function listCommand(options) {
|
|
8
|
+
console.log(chalk.bold('\nš Skills Repository\n'));
|
|
9
|
+
|
|
10
|
+
const skills = await listCentralSkills();
|
|
11
|
+
|
|
12
|
+
if (skills.length === 0) {
|
|
13
|
+
console.log(chalk.yellow('No skills found in your repository.'));
|
|
14
|
+
console.log(chalk.dim(`Location: ${getSkillsRepoDir()}`));
|
|
15
|
+
console.log(`\nCreate one with: ${chalk.cyan('dotai skill create')}\n`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Show skills with installation status
|
|
20
|
+
for (const skill of skills) {
|
|
21
|
+
console.log(chalk.bold.white(skill.name));
|
|
22
|
+
console.log(chalk.dim(` ${skill.description || 'No description'}`));
|
|
23
|
+
|
|
24
|
+
if (options.verbose) {
|
|
25
|
+
console.log(chalk.dim(` Path: ${skill.path}`));
|
|
26
|
+
|
|
27
|
+
// Show installation status
|
|
28
|
+
const status = await getSkillInstallStatus(skill.name);
|
|
29
|
+
const installed = [];
|
|
30
|
+
|
|
31
|
+
for (const [providerId, providerStatus] of Object.entries(status)) {
|
|
32
|
+
if (providerStatus.global || providerStatus.project) {
|
|
33
|
+
const scopes = [];
|
|
34
|
+
if (providerStatus.global) scopes.push('global');
|
|
35
|
+
if (providerStatus.project) scopes.push('project');
|
|
36
|
+
installed.push(`${providerStatus.name} (${scopes.join(', ')})`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (installed.length > 0) {
|
|
41
|
+
console.log(chalk.green(` Installed: ${installed.join(', ')}`));
|
|
42
|
+
} else {
|
|
43
|
+
console.log(chalk.yellow(' Not installed anywhere'));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(chalk.dim(`Total: ${skills.length} skill(s)`));
|
|
51
|
+
console.log(chalk.dim(`Location: ${getSkillsRepoDir()}\n`));
|
|
52
|
+
|
|
53
|
+
if (!options.verbose) {
|
|
54
|
+
console.log(chalk.dim(`Use ${chalk.cyan('dotai skill list -v')} to see installation status\n`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function listProvidersCommand() {
|
|
59
|
+
console.log(chalk.bold('\nš Supported Providers\n'));
|
|
60
|
+
|
|
61
|
+
for (const provider of Object.values(providers)) {
|
|
62
|
+
console.log(chalk.bold.white(provider.name) + chalk.dim(` (${provider.id})`));
|
|
63
|
+
console.log(chalk.dim(` ${provider.description}`));
|
|
64
|
+
console.log(chalk.dim(` Global: ${provider.globalPath()}`));
|
|
65
|
+
console.log(chalk.dim(` Project: ${provider.projectPath}/`));
|
|
66
|
+
console.log('');
|
|
67
|
+
}
|
|
68
|
+
}
|