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 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
+ }