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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { platform } from 'os';
|
|
4
|
+
import Enquirer from 'enquirer';
|
|
5
|
+
const { prompt } = Enquirer;
|
|
6
|
+
import { listCentralSkills, getCentralSkill } from '../lib/skills.js';
|
|
7
|
+
import { getSkillsRepoDir } from '../lib/paths.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Open a skill folder or file in the system default editor/file manager
|
|
11
|
+
*/
|
|
12
|
+
export async function openCommand(skillName, options) {
|
|
13
|
+
let skill;
|
|
14
|
+
|
|
15
|
+
// If no skill name, show interactive picker
|
|
16
|
+
if (!skillName) {
|
|
17
|
+
const skills = await listCentralSkills();
|
|
18
|
+
if (skills.length === 0) {
|
|
19
|
+
console.log(chalk.yellow('No skills found.'));
|
|
20
|
+
console.log(`Create one with: ${chalk.cyan('dotai skill create')}\n`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const answer = await prompt({
|
|
25
|
+
type: 'select',
|
|
26
|
+
name: 'skill',
|
|
27
|
+
message: 'Select a skill to open:',
|
|
28
|
+
choices: skills.map(s => ({
|
|
29
|
+
name: s.name,
|
|
30
|
+
message: `${s.name} - ${chalk.dim(s.description || 'No description')}`,
|
|
31
|
+
value: s.name
|
|
32
|
+
}))
|
|
33
|
+
});
|
|
34
|
+
skillName = answer.skill;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
skill = await getCentralSkill(skillName);
|
|
38
|
+
|
|
39
|
+
if (!skill) {
|
|
40
|
+
console.error(chalk.red(`Skill '${skillName}' not found`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const targetPath = options.file ? skill.filePath : skill.path;
|
|
45
|
+
const openCmd = getOpenCommand();
|
|
46
|
+
|
|
47
|
+
console.log(chalk.dim(`Opening: ${targetPath}\n`));
|
|
48
|
+
|
|
49
|
+
spawn(openCmd, [targetPath], {
|
|
50
|
+
detached: true,
|
|
51
|
+
stdio: 'ignore'
|
|
52
|
+
}).unref();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the platform-specific command to open files/folders
|
|
57
|
+
*/
|
|
58
|
+
function getOpenCommand() {
|
|
59
|
+
switch (platform()) {
|
|
60
|
+
case 'darwin':
|
|
61
|
+
return 'open';
|
|
62
|
+
case 'win32':
|
|
63
|
+
return 'explorer';
|
|
64
|
+
default:
|
|
65
|
+
return 'xdg-open';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Open the entire skills repository folder
|
|
71
|
+
*/
|
|
72
|
+
export async function openRepoCommand() {
|
|
73
|
+
const repoPath = getSkillsRepoDir();
|
|
74
|
+
const openCmd = getOpenCommand();
|
|
75
|
+
|
|
76
|
+
console.log(chalk.dim(`Opening skills repository: ${repoPath}\n`));
|
|
77
|
+
|
|
78
|
+
spawn(openCmd, [repoPath], {
|
|
79
|
+
detached: true,
|
|
80
|
+
stdio: 'ignore'
|
|
81
|
+
}).unref();
|
|
82
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
const { prompt } = Enquirer;
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { listCentralSkills, installSkill, getSkillInstallStatus } from '../lib/skills.js';
|
|
6
|
+
import { loadConfig } from '../lib/config.js';
|
|
7
|
+
import { providers, getProviderIds } from '../providers/index.js';
|
|
8
|
+
|
|
9
|
+
export async function syncCommand(options) {
|
|
10
|
+
console.log(chalk.bold('\n🔄 Sync skills to providers\n'));
|
|
11
|
+
|
|
12
|
+
const config = await loadConfig();
|
|
13
|
+
const skills = await listCentralSkills();
|
|
14
|
+
|
|
15
|
+
if (skills.length === 0) {
|
|
16
|
+
console.log(chalk.yellow('No skills to sync.'));
|
|
17
|
+
console.log(`Create skills with: ${chalk.cyan('dotai skill create')}\n`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Determine target providers
|
|
22
|
+
let targetProviders = options.providers
|
|
23
|
+
? options.providers.split(',').map(p => p.trim())
|
|
24
|
+
: config.enabledProviders;
|
|
25
|
+
|
|
26
|
+
if (options.all) {
|
|
27
|
+
targetProviders = getProviderIds();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Determine scope
|
|
31
|
+
const scope = options.project ? 'project' : 'global';
|
|
32
|
+
|
|
33
|
+
console.log(chalk.dim(`Syncing ${skills.length} skill(s) to ${targetProviders.length} provider(s)...\n`));
|
|
34
|
+
|
|
35
|
+
const results = {
|
|
36
|
+
installed: 0,
|
|
37
|
+
skipped: 0,
|
|
38
|
+
failed: 0
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
for (const skill of skills) {
|
|
42
|
+
const spinner = ora(`Syncing ${skill.name}...`).start();
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const installResults = await installSkill(skill.name, targetProviders, scope);
|
|
46
|
+
|
|
47
|
+
const successes = installResults.filter(r => r.success).length;
|
|
48
|
+
const failures = installResults.filter(r => !r.success).length;
|
|
49
|
+
|
|
50
|
+
results.installed += successes;
|
|
51
|
+
results.failed += failures;
|
|
52
|
+
|
|
53
|
+
if (failures === 0) {
|
|
54
|
+
spinner.succeed(`${skill.name} → ${successes} provider(s)`);
|
|
55
|
+
} else {
|
|
56
|
+
spinner.warn(`${skill.name} → ${successes} ok, ${failures} failed`);
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
spinner.fail(`${skill.name}: ${err.message}`);
|
|
60
|
+
results.failed++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(chalk.bold('Summary:'));
|
|
66
|
+
console.log(chalk.green(` ✓ Installed: ${results.installed}`));
|
|
67
|
+
if (results.failed > 0) {
|
|
68
|
+
console.log(chalk.red(` ✗ Failed: ${results.failed}`));
|
|
69
|
+
}
|
|
70
|
+
console.log('');
|
|
71
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
const { prompt } = Enquirer;
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { listCentralSkills, uninstallSkillFromProvider, getSkillInstallStatus } from '../lib/skills.js';
|
|
6
|
+
import { loadConfig } from '../lib/config.js';
|
|
7
|
+
import { providers, getProviderIds } from '../providers/index.js';
|
|
8
|
+
|
|
9
|
+
export async function uninstallCommand(skillName, options) {
|
|
10
|
+
console.log(chalk.bold('\n🗑️ Uninstall skill from providers\n'));
|
|
11
|
+
|
|
12
|
+
const config = await loadConfig();
|
|
13
|
+
|
|
14
|
+
// If no skill name, show interactive picker
|
|
15
|
+
if (!skillName) {
|
|
16
|
+
const skills = await listCentralSkills();
|
|
17
|
+
if (skills.length === 0) {
|
|
18
|
+
console.log(chalk.yellow('No skills found.'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const answer = await prompt({
|
|
23
|
+
type: 'select',
|
|
24
|
+
name: 'skill',
|
|
25
|
+
message: 'Select a skill to uninstall:',
|
|
26
|
+
choices: skills.map(s => ({
|
|
27
|
+
name: s.name,
|
|
28
|
+
message: `${s.name} - ${chalk.dim(s.description || 'No description')}`,
|
|
29
|
+
value: s.name
|
|
30
|
+
}))
|
|
31
|
+
});
|
|
32
|
+
skillName = answer.skill;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Determine which providers to uninstall from
|
|
36
|
+
let targetProviders = options.providers
|
|
37
|
+
? options.providers.split(',').map(p => p.trim())
|
|
38
|
+
: config.enabledProviders;
|
|
39
|
+
|
|
40
|
+
if (options.all) {
|
|
41
|
+
targetProviders = getProviderIds();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Determine scope
|
|
45
|
+
const scope = options.project ? 'project' : 'global';
|
|
46
|
+
|
|
47
|
+
// Confirm if not using --yes flag
|
|
48
|
+
if (!options.yes) {
|
|
49
|
+
const providerNames = targetProviders.map(id => providers[id]?.name || id).join(', ');
|
|
50
|
+
const answer = await prompt({
|
|
51
|
+
type: 'confirm',
|
|
52
|
+
name: 'confirm',
|
|
53
|
+
message: `Uninstall '${skillName}' from ${providerNames}?`,
|
|
54
|
+
initial: false
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!answer.confirm) {
|
|
58
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(chalk.dim(`Uninstalling '${skillName}' from ${scope} scope...\n`));
|
|
64
|
+
|
|
65
|
+
let removedCount = 0;
|
|
66
|
+
|
|
67
|
+
for (const providerId of targetProviders) {
|
|
68
|
+
const provider = providers[providerId];
|
|
69
|
+
if (!provider) continue;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const result = await uninstallSkillFromProvider(skillName, providerId, scope);
|
|
73
|
+
|
|
74
|
+
if (result.removed) {
|
|
75
|
+
console.log(chalk.green(` ✓ ${provider.name}`));
|
|
76
|
+
removedCount++;
|
|
77
|
+
} else {
|
|
78
|
+
console.log(chalk.dim(` - ${provider.name} (not installed)`));
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.log(chalk.red(` ✗ ${provider.name}: ${err.message}`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log('');
|
|
86
|
+
if (removedCount > 0) {
|
|
87
|
+
console.log(chalk.green(`Removed from ${removedCount} provider(s)`));
|
|
88
|
+
} else {
|
|
89
|
+
console.log(chalk.yellow('Nothing to remove'));
|
|
90
|
+
}
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { initConfig } from './lib/config.js';
|
|
6
|
+
import { createCommand } from './commands/create.js';
|
|
7
|
+
import { installCommand } from './commands/install.js';
|
|
8
|
+
import { listCommand, listProvidersCommand } from './commands/list.js';
|
|
9
|
+
import { syncCommand } from './commands/sync.js';
|
|
10
|
+
import { uninstallCommand } from './commands/uninstall.js';
|
|
11
|
+
import { configCommand, enableCommand, disableCommand } from './commands/config.js';
|
|
12
|
+
import { openCommand, openRepoCommand } from './commands/open.js';
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
// Initialize config on startup
|
|
17
|
+
await initConfig();
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.name('dotai')
|
|
21
|
+
.description('Dotfiles for AI - manage skills & MCP servers across all your AI coding assistants')
|
|
22
|
+
.version('1.0.0');
|
|
23
|
+
|
|
24
|
+
// Skill subcommand
|
|
25
|
+
const skill = program.command('skill').description('Manage AI agent skills');
|
|
26
|
+
|
|
27
|
+
skill
|
|
28
|
+
.command('create [name]')
|
|
29
|
+
.description('Create a new skill (opens editor for instructions)')
|
|
30
|
+
.option('-d, --description <desc>', 'Short description of the skill')
|
|
31
|
+
.action(createCommand);
|
|
32
|
+
|
|
33
|
+
skill
|
|
34
|
+
.command('install [skill]')
|
|
35
|
+
.description('Install a skill to providers')
|
|
36
|
+
.option('-p, --providers <list>', 'Comma-separated list of providers')
|
|
37
|
+
.option('-a, --all', 'Install to all providers')
|
|
38
|
+
.option('--project', 'Install to project scope (default: global)')
|
|
39
|
+
.option('-i, --interactive', 'Interactively select providers')
|
|
40
|
+
.action(installCommand);
|
|
41
|
+
|
|
42
|
+
skill
|
|
43
|
+
.command('uninstall [skill]')
|
|
44
|
+
.description('Uninstall a skill from providers')
|
|
45
|
+
.option('-p, --providers <list>', 'Comma-separated list of providers')
|
|
46
|
+
.option('-a, --all', 'Uninstall from all providers')
|
|
47
|
+
.option('--project', 'Uninstall from project scope (default: global)')
|
|
48
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
49
|
+
.action(uninstallCommand);
|
|
50
|
+
|
|
51
|
+
skill
|
|
52
|
+
.command('list')
|
|
53
|
+
.alias('ls')
|
|
54
|
+
.description('List all skills in your repository')
|
|
55
|
+
.option('-v, --verbose', 'Show installation status')
|
|
56
|
+
.action(listCommand);
|
|
57
|
+
|
|
58
|
+
skill
|
|
59
|
+
.command('sync')
|
|
60
|
+
.description('Sync all skills to enabled providers')
|
|
61
|
+
.option('-p, --providers <list>', 'Comma-separated list of providers')
|
|
62
|
+
.option('-a, --all', 'Sync to all providers')
|
|
63
|
+
.option('--project', 'Sync to project scope (default: global)')
|
|
64
|
+
.action(syncCommand);
|
|
65
|
+
|
|
66
|
+
skill
|
|
67
|
+
.command('open [skill]')
|
|
68
|
+
.description('Open a skill folder or file')
|
|
69
|
+
.option('-f, --file', 'Open SKILL.md file directly')
|
|
70
|
+
.action(openCommand);
|
|
71
|
+
|
|
72
|
+
// MCP subcommand (placeholder for now)
|
|
73
|
+
const mcp = program.command('mcp').description('Manage MCP servers (coming soon)');
|
|
74
|
+
|
|
75
|
+
mcp
|
|
76
|
+
.command('add')
|
|
77
|
+
.description('Add an MCP server (coming soon)')
|
|
78
|
+
.action(() => {
|
|
79
|
+
console.log(chalk.yellow('\nMCP management coming soon!\n'));
|
|
80
|
+
console.log(chalk.dim('This will let you configure MCP servers once and sync to:'));
|
|
81
|
+
console.log(chalk.dim(' • Claude Code • Claude Desktop • Cursor'));
|
|
82
|
+
console.log(chalk.dim(' • VS Code • Cline • Windsurf\n'));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
mcp
|
|
86
|
+
.command('list')
|
|
87
|
+
.description('List MCP servers (coming soon)')
|
|
88
|
+
.action(() => {
|
|
89
|
+
console.log(chalk.yellow('\nMCP management coming soon!\n'));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Config commands
|
|
93
|
+
program
|
|
94
|
+
.command('providers')
|
|
95
|
+
.description('List all supported providers')
|
|
96
|
+
.action(listProvidersCommand);
|
|
97
|
+
|
|
98
|
+
program
|
|
99
|
+
.command('config')
|
|
100
|
+
.description('Configure dotai settings')
|
|
101
|
+
.option('-s, --show', 'Show current configuration')
|
|
102
|
+
.action(configCommand);
|
|
103
|
+
|
|
104
|
+
program
|
|
105
|
+
.command('enable <provider>')
|
|
106
|
+
.description('Enable a provider')
|
|
107
|
+
.action(enableCommand);
|
|
108
|
+
|
|
109
|
+
program
|
|
110
|
+
.command('disable <provider>')
|
|
111
|
+
.description('Disable a provider')
|
|
112
|
+
.action(disableCommand);
|
|
113
|
+
|
|
114
|
+
program
|
|
115
|
+
.command('repo')
|
|
116
|
+
.description('Open the dotai repository folder')
|
|
117
|
+
.action(openRepoCommand);
|
|
118
|
+
|
|
119
|
+
// Show help if no command
|
|
120
|
+
program.parse();
|
|
121
|
+
|
|
122
|
+
if (!process.argv.slice(2).length) {
|
|
123
|
+
console.log(chalk.bold(`
|
|
124
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
125
|
+
║ dotai v1.0.0 ║
|
|
126
|
+
║ Dotfiles for AI - Skills & MCP in one place ║
|
|
127
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
128
|
+
`));
|
|
129
|
+
|
|
130
|
+
console.log(chalk.bold(' Skills - manage across Claude, Gemini, Cursor, Codex & more:'));
|
|
131
|
+
console.log(` ${chalk.cyan('dotai skill create')} Create a new skill`);
|
|
132
|
+
console.log(` ${chalk.cyan('dotai skill install <name>')} Install to all providers`);
|
|
133
|
+
console.log(` ${chalk.cyan('dotai skill list')} List your skills`);
|
|
134
|
+
console.log(` ${chalk.cyan('dotai skill sync')} Sync all skills\n`);
|
|
135
|
+
|
|
136
|
+
console.log(chalk.bold(' MCP Servers - coming soon:'));
|
|
137
|
+
console.log(` ${chalk.dim('dotai mcp add')} Add an MCP server`);
|
|
138
|
+
console.log(` ${chalk.dim('dotai mcp sync')} Sync to all apps\n`);
|
|
139
|
+
|
|
140
|
+
console.log(chalk.dim(' Run "dotai --help" for all commands\n'));
|
|
141
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import { getConfigDir, getConfigFilePath, getSkillsRepoDir } from './paths.js';
|
|
3
|
+
import { getProviderIds } from '../providers/index.js';
|
|
4
|
+
|
|
5
|
+
const defaultConfig = {
|
|
6
|
+
enabledProviders: getProviderIds(),
|
|
7
|
+
defaultScope: 'global', // 'global' or 'project'
|
|
8
|
+
skillsRepo: null, // Custom skills repo path (null = use default)
|
|
9
|
+
createdAt: new Date().toISOString()
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initialize config directory and files
|
|
14
|
+
*/
|
|
15
|
+
export async function initConfig() {
|
|
16
|
+
const configDir = getConfigDir();
|
|
17
|
+
const skillsRepoDir = getSkillsRepoDir();
|
|
18
|
+
const configFile = getConfigFilePath();
|
|
19
|
+
|
|
20
|
+
// Create directories
|
|
21
|
+
await fs.ensureDir(configDir);
|
|
22
|
+
await fs.ensureDir(skillsRepoDir);
|
|
23
|
+
|
|
24
|
+
// Create config file if it doesn't exist
|
|
25
|
+
if (!await fs.pathExists(configFile)) {
|
|
26
|
+
await fs.writeJson(configFile, defaultConfig, { spaces: 2 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return loadConfig();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load config from file
|
|
34
|
+
*/
|
|
35
|
+
export async function loadConfig() {
|
|
36
|
+
const configFile = getConfigFilePath();
|
|
37
|
+
|
|
38
|
+
if (!await fs.pathExists(configFile)) {
|
|
39
|
+
return { ...defaultConfig };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const config = await fs.readJson(configFile);
|
|
44
|
+
return { ...defaultConfig, ...config };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error('Error loading config:', err.message);
|
|
47
|
+
return { ...defaultConfig };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Save config to file
|
|
53
|
+
*/
|
|
54
|
+
export async function saveConfig(config) {
|
|
55
|
+
const configFile = getConfigFilePath();
|
|
56
|
+
await fs.ensureDir(getConfigDir());
|
|
57
|
+
await fs.writeJson(configFile, config, { spaces: 2 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Update specific config values
|
|
62
|
+
*/
|
|
63
|
+
export async function updateConfig(updates) {
|
|
64
|
+
const config = await loadConfig();
|
|
65
|
+
const newConfig = { ...config, ...updates, updatedAt: new Date().toISOString() };
|
|
66
|
+
await saveConfig(newConfig);
|
|
67
|
+
return newConfig;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Enable a provider
|
|
72
|
+
*/
|
|
73
|
+
export async function enableProvider(providerId) {
|
|
74
|
+
const config = await loadConfig();
|
|
75
|
+
if (!config.enabledProviders.includes(providerId)) {
|
|
76
|
+
config.enabledProviders.push(providerId);
|
|
77
|
+
await saveConfig(config);
|
|
78
|
+
}
|
|
79
|
+
return config;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Disable a provider
|
|
84
|
+
*/
|
|
85
|
+
export async function disableProvider(providerId) {
|
|
86
|
+
const config = await loadConfig();
|
|
87
|
+
config.enabledProviders = config.enabledProviders.filter(id => id !== providerId);
|
|
88
|
+
await saveConfig(config);
|
|
89
|
+
return config;
|
|
90
|
+
}
|
package/src/lib/paths.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { platform } from 'process';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the user's home directory (cross-platform)
|
|
7
|
+
*/
|
|
8
|
+
export function getHomeDir() {
|
|
9
|
+
return homedir();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the dotai config directory
|
|
14
|
+
* Mac/Linux: ~/.dotai
|
|
15
|
+
* Windows: %USERPROFILE%\.dotai
|
|
16
|
+
*/
|
|
17
|
+
export function getConfigDir() {
|
|
18
|
+
return join(getHomeDir(), '.dotai');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the central skills repository directory
|
|
23
|
+
*/
|
|
24
|
+
export function getSkillsRepoDir() {
|
|
25
|
+
return join(getConfigDir(), 'skills');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the config file path
|
|
30
|
+
*/
|
|
31
|
+
export function getConfigFilePath() {
|
|
32
|
+
return join(getConfigDir(), 'config.json');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if running on Windows
|
|
37
|
+
*/
|
|
38
|
+
export function isWindows() {
|
|
39
|
+
return platform === 'win32';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Normalize path for the current OS
|
|
44
|
+
*/
|
|
45
|
+
export function normalizePath(p) {
|
|
46
|
+
if (isWindows()) {
|
|
47
|
+
return p.replace(/\//g, '\\');
|
|
48
|
+
}
|
|
49
|
+
return p;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Expand ~ to home directory
|
|
54
|
+
*/
|
|
55
|
+
export function expandHome(p) {
|
|
56
|
+
if (p.startsWith('~')) {
|
|
57
|
+
return join(getHomeDir(), p.slice(1));
|
|
58
|
+
}
|
|
59
|
+
return p;
|
|
60
|
+
}
|