@mockingbird_konwlage_cli/cli 1.1.0 → 1.2.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/index.js +121 -4
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -11,19 +11,60 @@ const program = new Command();
|
|
|
11
11
|
program
|
|
12
12
|
.name('mockingbird-cli')
|
|
13
13
|
.description('Installs and manages AI skills for various coding clients')
|
|
14
|
-
.version('1.
|
|
14
|
+
.version('1.2.1');
|
|
15
15
|
|
|
16
16
|
program
|
|
17
17
|
.command('add <slug>')
|
|
18
18
|
.description('Add an AI skill by its Mockingbird slug or GitHub <org>/<repo>')
|
|
19
19
|
.option('--agent <agent>', 'The AI client to install the skill for (e.g., cursor, windsurf, claude-code)')
|
|
20
|
+
.option('--remote', 'Use the remote API instead of local')
|
|
20
21
|
.action(async (slug, options) => {
|
|
22
|
+
// ... (existing add logic)
|
|
21
23
|
const agent = (options.agent || 'cursor').toLowerCase();
|
|
24
|
+
const isRemote = options.remote || false;
|
|
22
25
|
|
|
23
26
|
console.log(chalk.blue(`[Mockingbird] Installing skill '${slug}' for agent '${agent}'...`));
|
|
24
27
|
|
|
25
|
-
//
|
|
26
|
-
|
|
28
|
+
// Configuration discovery logic
|
|
29
|
+
let apiBaseUrl = process.env.MOCKINGBIRD_API_URL;
|
|
30
|
+
|
|
31
|
+
if (!apiBaseUrl) {
|
|
32
|
+
// Priority 1: .mockingbirdrc
|
|
33
|
+
const rcPath = path.join(process.cwd(), '.mockingbirdrc');
|
|
34
|
+
if (fs.existsSync(rcPath)) {
|
|
35
|
+
try {
|
|
36
|
+
const rc = JSON.parse(fs.readFileSync(rcPath, 'utf8'));
|
|
37
|
+
apiBaseUrl = isRemote ? rc.RemoteApiBaseUrl : rc.ApiBaseUrl;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.warn(chalk.yellow(`[Warning] Failed to parse .mockingbirdrc: ${e.message}`));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!apiBaseUrl) {
|
|
45
|
+
// Priority 2: appsettings.Production.json (useful for server environments)
|
|
46
|
+
const prodSettingsPath = path.join(process.cwd(), 'appsettings.Production.json');
|
|
47
|
+
if (fs.existsSync(prodSettingsPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const settings = JSON.parse(fs.readFileSync(prodSettingsPath, 'utf8'));
|
|
50
|
+
apiBaseUrl = settings.ApiBaseUrl;
|
|
51
|
+
} catch (e) { }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!apiBaseUrl) {
|
|
56
|
+
// Priority 3: appsettings.json
|
|
57
|
+
const appSettingsPath = path.join(process.cwd(), 'appsettings.json');
|
|
58
|
+
if (fs.existsSync(appSettingsPath)) {
|
|
59
|
+
try {
|
|
60
|
+
const settings = JSON.parse(fs.readFileSync(appSettingsPath, 'utf8'));
|
|
61
|
+
apiBaseUrl = settings.ApiBaseUrl;
|
|
62
|
+
} catch (e) { }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Final fallback to local development port
|
|
67
|
+
const API_BASE_URL = apiBaseUrl || 'http://localhost:5287';
|
|
27
68
|
const apiUrl = `${API_BASE_URL}/api/public/skills/raw/${slug}`;
|
|
28
69
|
|
|
29
70
|
let skillContent = null;
|
|
@@ -37,7 +78,7 @@ program
|
|
|
37
78
|
console.log(chalk.red('NOT FOUND'));
|
|
38
79
|
console.error(chalk.red(`\n✖ Error: Failed to find SKILL.md for '${slug}' from Mockingbird Knowledge Base.`));
|
|
39
80
|
if (error.response && error.response.status === 404) {
|
|
40
|
-
console.error(chalk.gray(`The skill might not be registered or supported yet
|
|
81
|
+
console.error(chalk.gray(`The skill might not be registered or supported yet at ${API_BASE_URL}`));
|
|
41
82
|
} else {
|
|
42
83
|
console.error(chalk.gray(`Details: ${error.message}`));
|
|
43
84
|
}
|
|
@@ -57,6 +98,82 @@ program
|
|
|
57
98
|
}
|
|
58
99
|
});
|
|
59
100
|
|
|
101
|
+
program
|
|
102
|
+
.command('push <path>')
|
|
103
|
+
.description('Push a local skill to the Mockingbird Knowledge Base')
|
|
104
|
+
.option('--no-assets', 'Skip bundling assets (images, etc.) and only push the SKILL.md content')
|
|
105
|
+
.action(async (skillPath, options) => {
|
|
106
|
+
const fullPath = path.resolve(skillPath);
|
|
107
|
+
const skillMdPath = fs.statSync(fullPath).isDirectory()
|
|
108
|
+
? path.join(fullPath, 'SKILL.md')
|
|
109
|
+
: fullPath;
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
112
|
+
console.error(chalk.red(`✖ Error: SKILL.md not found at ${skillMdPath}`));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const skillDir = path.dirname(skillMdPath);
|
|
117
|
+
const skillName = path.basename(skillDir);
|
|
118
|
+
|
|
119
|
+
console.log(chalk.blue(`[Mockingbird] Pushing skill '${skillName}' to knowledge base...`));
|
|
120
|
+
|
|
121
|
+
let content = fs.readFileSync(skillMdPath, 'utf8');
|
|
122
|
+
|
|
123
|
+
// Inject includeAssets flag if --no-assets is used
|
|
124
|
+
if (!options.assets) {
|
|
125
|
+
console.log(chalk.yellow(`[Config] Assets will be excluded from this push.`));
|
|
126
|
+
if (content.startsWith('---')) {
|
|
127
|
+
// Insert into existing yaml
|
|
128
|
+
if (!content.includes('includeAssets:')) {
|
|
129
|
+
content = content.replace('---', `---\nincludeAssets: false`);
|
|
130
|
+
} else {
|
|
131
|
+
content = content.replace(/includeAssets:\s*true/g, 'includeAssets: false');
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
content = `---\nincludeAssets: false\n---\n\n` + content;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Deployment Logic for Dev Environment: Copy to Transmuter_Workshop/Raw_Incoming/Skills
|
|
139
|
+
// In a real production scenario, this would be a POST request to an API.
|
|
140
|
+
const SLN_ROOT = path.resolve(__dirname, '..');
|
|
141
|
+
const INCOMING_DIR = path.join(SLN_ROOT, 'Transmuter_Workshop', 'Raw_Incoming', 'Skills', 'local-publish', skillName);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
if (!fs.existsSync(INCOMING_DIR)) {
|
|
145
|
+
fs.mkdirSync(INCOMING_DIR, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (options.assets) {
|
|
149
|
+
console.log(chalk.gray(`Bundling full directory: ${skillDir} -> ${INCOMING_DIR}`));
|
|
150
|
+
copyFolderSync(skillDir, INCOMING_DIR);
|
|
151
|
+
// Overwrite with the potentially modified SKILL.md (though here it's only modified if no-assets)
|
|
152
|
+
fs.writeFileSync(path.join(INCOMING_DIR, 'SKILL.md'), content);
|
|
153
|
+
} else {
|
|
154
|
+
console.log(chalk.gray(`Bundling meta only: ${skillMdPath} -> ${INCOMING_DIR}/SKILL.md`));
|
|
155
|
+
fs.writeFileSync(path.join(INCOMING_DIR, 'SKILL.md'), content);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(chalk.green(`\n✓ Successfully pushed skill '${skillName}' to workdesk.`));
|
|
159
|
+
console.log(chalk.gray(`The Transmuter service will process it shortly.`));
|
|
160
|
+
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(chalk.red(`\n✖ Error: Failed to push skill. ${error.message}`));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
function copyFolderSync(from, to) {
|
|
167
|
+
fs.mkdirSync(to, { recursive: true });
|
|
168
|
+
fs.readdirSync(from).forEach(element => {
|
|
169
|
+
if (fs.lstatSync(path.join(from, element)).isFile()) {
|
|
170
|
+
fs.copyFileSync(path.join(from, element), path.join(to, element));
|
|
171
|
+
} else {
|
|
172
|
+
copyFolderSync(path.join(from, element), path.join(to, element));
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
60
177
|
async function installForAgent(agent, slug, content) {
|
|
61
178
|
const cwd = process.cwd();
|
|
62
179
|
// Use the last part of the slug as a clean filename basis
|