@lightdash/cli 0.2396.0 → 0.2397.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.
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type AgentType = 'claude' | 'cursor' | 'codex';
|
|
2
|
+
type InstallSkillsOptions = {
|
|
3
|
+
verbose: boolean;
|
|
4
|
+
agent: AgentType;
|
|
5
|
+
global: boolean;
|
|
6
|
+
path?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const installSkillsHandler: (options: InstallSkillsOptions) => Promise<void>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=installSkills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installSkills.d.ts","sourceRoot":"","sources":["../../src/handlers/installSkills.ts"],"names":[],"mappings":"AAYA,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE/C,KAAK,oBAAoB,GAAG;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAsMF,eAAO,MAAM,oBAAoB,YACpB,oBAAoB,KAC9B,OAAO,CAAC,IAAI,CA8Dd,CAAC"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installSkillsHandler = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
7
|
+
const os = tslib_1.__importStar(require("os"));
|
|
8
|
+
const path = tslib_1.__importStar(require("path"));
|
|
9
|
+
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
10
|
+
const styles = tslib_1.__importStar(require("../styles"));
|
|
11
|
+
const GITHUB_API_BASE = 'https://api.github.com/repos/lightdash/lightdash/contents';
|
|
12
|
+
const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/lightdash/lightdash/main';
|
|
13
|
+
function getAgentSkillsDir(agent) {
|
|
14
|
+
switch (agent) {
|
|
15
|
+
case 'claude':
|
|
16
|
+
return '.claude/skills';
|
|
17
|
+
case 'cursor':
|
|
18
|
+
return '.cursor/skills';
|
|
19
|
+
case 'codex':
|
|
20
|
+
return '.codex/skills';
|
|
21
|
+
default:
|
|
22
|
+
throw new Error(`Unknown agent type: ${agent}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function findGitRoot(startDir) {
|
|
26
|
+
let currentDir = startDir;
|
|
27
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
28
|
+
if (fs.existsSync(path.join(currentDir, '.git'))) {
|
|
29
|
+
return currentDir;
|
|
30
|
+
}
|
|
31
|
+
currentDir = path.dirname(currentDir);
|
|
32
|
+
}
|
|
33
|
+
// Check root directory
|
|
34
|
+
if (fs.existsSync(path.join(currentDir, '.git'))) {
|
|
35
|
+
return currentDir;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function getInstallPath(options) {
|
|
40
|
+
const skillsDir = getAgentSkillsDir(options.agent);
|
|
41
|
+
// If explicit path provided, use it
|
|
42
|
+
if (options.path) {
|
|
43
|
+
return path.join(options.path, skillsDir);
|
|
44
|
+
}
|
|
45
|
+
// If global, use home directory
|
|
46
|
+
if (options.global) {
|
|
47
|
+
return path.join(os.homedir(), skillsDir);
|
|
48
|
+
}
|
|
49
|
+
// Project install: find git root
|
|
50
|
+
const cwd = process.cwd();
|
|
51
|
+
const gitRoot = findGitRoot(cwd);
|
|
52
|
+
if (gitRoot) {
|
|
53
|
+
globalState_1.default.debug(`> Found git root at: ${gitRoot}`);
|
|
54
|
+
return path.join(gitRoot, skillsDir);
|
|
55
|
+
}
|
|
56
|
+
// No git root found, use current directory
|
|
57
|
+
globalState_1.default.debug(`> No git root found, using current directory: ${cwd}`);
|
|
58
|
+
return path.join(cwd, skillsDir);
|
|
59
|
+
}
|
|
60
|
+
async function fetchGitHubDirectory(repoPath) {
|
|
61
|
+
const url = `${GITHUB_API_BASE}/${repoPath}`;
|
|
62
|
+
globalState_1.default.debug(`> Fetching GitHub directory: ${url}`);
|
|
63
|
+
const response = await (0, node_fetch_1.default)(url, {
|
|
64
|
+
headers: {
|
|
65
|
+
Accept: 'application/vnd.github.v3+json',
|
|
66
|
+
'User-Agent': 'lightdash-cli',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(`Failed to fetch GitHub directory: ${response.status} ${response.statusText}`);
|
|
71
|
+
}
|
|
72
|
+
return response.json();
|
|
73
|
+
}
|
|
74
|
+
async function fetchFileContent(downloadUrl) {
|
|
75
|
+
globalState_1.default.debug(`> Fetching file: ${downloadUrl}`);
|
|
76
|
+
const response = await (0, node_fetch_1.default)(downloadUrl, {
|
|
77
|
+
headers: {
|
|
78
|
+
'User-Agent': 'lightdash-cli',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`);
|
|
83
|
+
}
|
|
84
|
+
return response.text();
|
|
85
|
+
}
|
|
86
|
+
async function resolveSymlinkTarget(symlinkPath, target) {
|
|
87
|
+
// Resolve the symlink relative to its location
|
|
88
|
+
const symlinkDir = path.dirname(symlinkPath);
|
|
89
|
+
const resolvedPath = path.posix.normalize(path.posix.join(symlinkDir, target));
|
|
90
|
+
return resolvedPath;
|
|
91
|
+
}
|
|
92
|
+
/* eslint-disable no-await-in-loop */
|
|
93
|
+
// Sequential downloads are intentional to avoid GitHub rate limits and handle symlinks
|
|
94
|
+
async function downloadSkillFiles(repoPath, localDir, visited = new Set()) {
|
|
95
|
+
// Prevent infinite loops with symlinks
|
|
96
|
+
if (visited.has(repoPath)) {
|
|
97
|
+
globalState_1.default.debug(`> Skipping already visited path: ${repoPath}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
visited.add(repoPath);
|
|
101
|
+
const items = await fetchGitHubDirectory(repoPath);
|
|
102
|
+
for (const item of items) {
|
|
103
|
+
const localPath = path.join(localDir, item.name);
|
|
104
|
+
if (item.type === 'dir') {
|
|
105
|
+
fs.mkdirSync(localPath, { recursive: true });
|
|
106
|
+
await downloadSkillFiles(item.path, localPath, visited);
|
|
107
|
+
}
|
|
108
|
+
else if (item.type === 'symlink' && item.target) {
|
|
109
|
+
// Resolve the symlink and fetch the actual content
|
|
110
|
+
const resolvedPath = await resolveSymlinkTarget(item.path, item.target);
|
|
111
|
+
globalState_1.default.debug(`> Resolving symlink: ${item.path} -> ${resolvedPath}`);
|
|
112
|
+
// Check if the target is a directory or file by fetching it
|
|
113
|
+
try {
|
|
114
|
+
const targetItems = await fetchGitHubDirectory(resolvedPath);
|
|
115
|
+
// It's a directory, create it and download contents
|
|
116
|
+
fs.mkdirSync(localPath, { recursive: true });
|
|
117
|
+
for (const targetItem of targetItems) {
|
|
118
|
+
const targetLocalPath = path.join(localPath, targetItem.name);
|
|
119
|
+
if (targetItem.type === 'dir') {
|
|
120
|
+
fs.mkdirSync(targetLocalPath, { recursive: true });
|
|
121
|
+
await downloadSkillFiles(targetItem.path, targetLocalPath, visited);
|
|
122
|
+
}
|
|
123
|
+
else if (targetItem.download_url) {
|
|
124
|
+
const content = await fetchFileContent(targetItem.download_url);
|
|
125
|
+
fs.writeFileSync(targetLocalPath, content);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// It's a file, fetch its content directly
|
|
131
|
+
const downloadUrl = `${GITHUB_RAW_BASE}/${resolvedPath}`;
|
|
132
|
+
const content = await fetchFileContent(downloadUrl);
|
|
133
|
+
// Ensure parent directory exists
|
|
134
|
+
fs.mkdirSync(path.dirname(localPath), { recursive: true });
|
|
135
|
+
fs.writeFileSync(localPath, content);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (item.type === 'file' && item.download_url) {
|
|
139
|
+
const content = await fetchFileContent(item.download_url);
|
|
140
|
+
// Ensure parent directory exists
|
|
141
|
+
fs.mkdirSync(path.dirname(localPath), { recursive: true });
|
|
142
|
+
fs.writeFileSync(localPath, content);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/* eslint-enable no-await-in-loop */
|
|
147
|
+
async function listAvailableSkills() {
|
|
148
|
+
const items = await fetchGitHubDirectory('skills');
|
|
149
|
+
return items.filter((item) => item.type === 'dir').map((item) => item.name);
|
|
150
|
+
}
|
|
151
|
+
const installSkillsHandler = async (options) => {
|
|
152
|
+
globalState_1.default.setVerbose(options.verbose);
|
|
153
|
+
const installPath = getInstallPath(options);
|
|
154
|
+
console.error(styles.title('\n⚡ Lightdash Skills Installer\n'));
|
|
155
|
+
console.error(`Agent: ${styles.bold(options.agent)}`);
|
|
156
|
+
console.error(`Scope: ${styles.bold(options.global ? 'global' : 'project')}`);
|
|
157
|
+
console.error(`Install path: ${styles.bold(installPath)}\n`);
|
|
158
|
+
const spinner = globalState_1.default.startSpinner('Fetching available skills...');
|
|
159
|
+
try {
|
|
160
|
+
const skills = await listAvailableSkills();
|
|
161
|
+
if (skills.length === 0) {
|
|
162
|
+
spinner.fail('No skills found in the repository');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
spinner.text = `Found ${skills.length} skill(s): ${skills.join(', ')}`;
|
|
166
|
+
spinner.succeed();
|
|
167
|
+
// Create install directory
|
|
168
|
+
fs.mkdirSync(installPath, { recursive: true });
|
|
169
|
+
// Install skills sequentially to provide meaningful progress feedback
|
|
170
|
+
/* eslint-disable no-await-in-loop */
|
|
171
|
+
for (const skill of skills) {
|
|
172
|
+
const skillSpinner = globalState_1.default.startSpinner(`Installing skill: ${skill}...`);
|
|
173
|
+
const skillLocalPath = path.join(installPath, skill);
|
|
174
|
+
try {
|
|
175
|
+
// Remove existing skill directory if it exists
|
|
176
|
+
if (fs.existsSync(skillLocalPath)) {
|
|
177
|
+
fs.rmSync(skillLocalPath, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
fs.mkdirSync(skillLocalPath, { recursive: true });
|
|
180
|
+
await downloadSkillFiles(`skills/${skill}`, skillLocalPath);
|
|
181
|
+
skillSpinner.succeed(`Installed skill: ${skill}`);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
185
|
+
skillSpinner.fail(`Failed to install skill ${skill}: ${errorMessage}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/* eslint-enable no-await-in-loop */
|
|
189
|
+
console.error(styles.success('\n✓ Skills installed successfully!\n'));
|
|
190
|
+
console.error(`Skills are available at: ${styles.bold(installPath)}\n`);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
194
|
+
spinner.fail(`Failed to fetch skills: ${errorMessage}`);
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
exports.installSkillsHandler = installSkillsHandler;
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const download_1 = require("./handlers/download");
|
|
|
15
15
|
const generate_1 = require("./handlers/generate");
|
|
16
16
|
const generateExposures_1 = require("./handlers/generateExposures");
|
|
17
17
|
const getProject_1 = require("./handlers/getProject");
|
|
18
|
+
const installSkills_1 = require("./handlers/installSkills");
|
|
18
19
|
const lint_1 = require("./handlers/lint");
|
|
19
20
|
const listProjects_1 = require("./handlers/listProjects");
|
|
20
21
|
const login_1 = require("./handlers/login");
|
|
@@ -433,6 +434,35 @@ commander_1.program
|
|
|
433
434
|
.option('--page-size <number>', 'Number of rows per page (default: 500, max: 5000)', parseIntArgument)
|
|
434
435
|
.option('--verbose', 'Show detailed output', false)
|
|
435
436
|
.action(sql_1.sqlHandler);
|
|
437
|
+
commander_1.program
|
|
438
|
+
.command('install-skills')
|
|
439
|
+
.description('Installs Lightdash skills for AI coding assistants (Claude, Cursor, Codex)')
|
|
440
|
+
.addHelpText('after', `
|
|
441
|
+
${styles.bold('Examples:')}
|
|
442
|
+
${styles.title('⚡')}️lightdash ${styles.bold('install-skills')} ${styles.secondary('-- installs skills for Claude at git root (default)')}
|
|
443
|
+
${styles.title('⚡')}️lightdash ${styles.bold('install-skills')} --agent cursor ${styles.secondary('-- installs skills for Cursor')}
|
|
444
|
+
${styles.title('⚡')}️lightdash ${styles.bold('install-skills')} --global ${styles.secondary('-- installs skills globally to ~/.claude/skills/')}
|
|
445
|
+
${styles.title('⚡')}️lightdash ${styles.bold('install-skills')} --agent codex --global ${styles.secondary('-- installs skills globally for Codex')}
|
|
446
|
+
${styles.title('⚡')}️lightdash ${styles.bold('install-skills')} --path ./my-project ${styles.secondary('-- installs skills to a specific path')}
|
|
447
|
+
|
|
448
|
+
${styles.bold('Installation paths:')}
|
|
449
|
+
${styles.secondary('Project-level (default):')}
|
|
450
|
+
.claude/skills/ (Claude)
|
|
451
|
+
.cursor/skills/ (Cursor)
|
|
452
|
+
.codex/skills/ (Codex)
|
|
453
|
+
|
|
454
|
+
${styles.secondary('Global (--global):')}
|
|
455
|
+
~/.claude/skills/ (Claude)
|
|
456
|
+
~/.cursor/skills/ (Cursor)
|
|
457
|
+
~/.codex/skills/ (Codex)
|
|
458
|
+
`)
|
|
459
|
+
.option('--verbose', 'Show detailed output', false)
|
|
460
|
+
.addOption(new commander_1.Option('--agent <agent>', 'Target agent for skill installation')
|
|
461
|
+
.choices(['claude', 'cursor', 'codex'])
|
|
462
|
+
.default('claude'))
|
|
463
|
+
.option('--global', 'Install skills globally to home directory instead of project', false)
|
|
464
|
+
.option('--path <path>', 'Override the install path (skills directory will be created inside)', undefined)
|
|
465
|
+
.action(installSkills_1.installSkillsHandler);
|
|
436
466
|
const errorHandler = (err) => {
|
|
437
467
|
// Use error message with fallback for safety
|
|
438
468
|
const errorMessage = (0, common_1.getErrorMessage)(err) || 'An unexpected error occurred';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2397.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"unique-names-generator": "^4.7.1",
|
|
39
39
|
"uuid": "^11.0.3",
|
|
40
40
|
"yaml": "^2.7.0",
|
|
41
|
-
"@lightdash/common": "0.
|
|
42
|
-
"@lightdash/warehouses": "0.
|
|
41
|
+
"@lightdash/common": "0.2397.1",
|
|
42
|
+
"@lightdash/warehouses": "0.2397.1"
|
|
43
43
|
},
|
|
44
44
|
"description": "Lightdash CLI tool",
|
|
45
45
|
"devDependencies": {
|