@the-aico/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/LICENSE +201 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3331 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command10 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/utils/errors.ts
|
|
7
|
+
import kleur from "kleur";
|
|
8
|
+
import { ZodError } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/utils/error-suggestions.ts
|
|
11
|
+
var defaultSuggestions = {
|
|
12
|
+
// Network errors
|
|
13
|
+
NETWORK_ERROR: "Check your internet connection or try using a proxy with --proxy option.",
|
|
14
|
+
FETCH_ERROR: "Verify the registry URL is correct and accessible.",
|
|
15
|
+
TIMEOUT: "The request timed out. Try again or check your network.",
|
|
16
|
+
REGISTRY_UNAVAILABLE: "The registry server is unavailable. Try again later.",
|
|
17
|
+
// Resource errors
|
|
18
|
+
NOT_FOUND: "The requested resource was not found.",
|
|
19
|
+
SKILL_NOT_FOUND: "Use `npx aico list --skills` to see available skills.",
|
|
20
|
+
EMPLOYEE_NOT_FOUND: "Use `npx aico list` to see available employees.",
|
|
21
|
+
// Configuration errors
|
|
22
|
+
NOT_INITIALIZED: "Run `npx aico init` to initialize the project.",
|
|
23
|
+
CONFIG_INVALID: "Check your aico.json for syntax errors or run `npx aico init --force`.",
|
|
24
|
+
CONFIG_EXISTS: "Use `npx aico init --force` to overwrite existing config.",
|
|
25
|
+
CONFIG_PARSE_ERROR: "Your aico.json contains invalid JSON. Check for syntax errors.",
|
|
26
|
+
// Dependency errors
|
|
27
|
+
DEPENDENCY_MISSING: "Install the missing dependency first, or use `--no-deps` to skip.",
|
|
28
|
+
CIRCULAR_DEPENDENCY: "Check your skill dependencies for circular references.",
|
|
29
|
+
// File errors
|
|
30
|
+
FILE_EXISTS: "Use `--overwrite` to replace existing files.",
|
|
31
|
+
FILE_NOT_FOUND: "Verify the file path is correct.",
|
|
32
|
+
PERMISSION_DENIED: "Check file permissions or run with appropriate privileges.",
|
|
33
|
+
WRITE_ERROR: "Unable to write file. Check disk space and permissions.",
|
|
34
|
+
// Platform errors
|
|
35
|
+
PLATFORM_NOT_SUPPORTED: "Supported platforms: claude-code, codex.",
|
|
36
|
+
// Validation errors
|
|
37
|
+
VALIDATION_ERROR: "Check the input format and try again.",
|
|
38
|
+
INVALID_ARGUMENT: "Check the command arguments and options."
|
|
39
|
+
};
|
|
40
|
+
function getSuggestion(code) {
|
|
41
|
+
return defaultSuggestions[code] || "Please check the error details above.";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/utils/errors.ts
|
|
45
|
+
var AicoError = class extends Error {
|
|
46
|
+
code;
|
|
47
|
+
suggestion;
|
|
48
|
+
context;
|
|
49
|
+
cause;
|
|
50
|
+
constructor(message, code, options) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "AicoError";
|
|
53
|
+
this.code = code;
|
|
54
|
+
this.suggestion = options?.suggestion || getSuggestion(code);
|
|
55
|
+
this.context = options?.context;
|
|
56
|
+
this.cause = options?.cause;
|
|
57
|
+
}
|
|
58
|
+
toJSON() {
|
|
59
|
+
return {
|
|
60
|
+
name: this.name,
|
|
61
|
+
message: this.message,
|
|
62
|
+
code: this.code,
|
|
63
|
+
suggestion: this.suggestion,
|
|
64
|
+
context: this.context
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function handleError(error, options = {}) {
|
|
69
|
+
const { verbose = false, exitCode = 1 } = options;
|
|
70
|
+
console.error("");
|
|
71
|
+
if (error instanceof AicoError) {
|
|
72
|
+
console.error(kleur.red("\u2716 Error:"), error.message);
|
|
73
|
+
console.error("");
|
|
74
|
+
console.error(kleur.dim(" Code:"), error.code);
|
|
75
|
+
console.error("");
|
|
76
|
+
console.error(kleur.cyan(" Suggestion:"));
|
|
77
|
+
console.error(` ${error.suggestion}`);
|
|
78
|
+
if (error.context && verbose) {
|
|
79
|
+
console.error("");
|
|
80
|
+
console.error(kleur.dim(" Context:"));
|
|
81
|
+
for (const [key, value] of Object.entries(error.context)) {
|
|
82
|
+
console.error(` ${key}: ${JSON.stringify(value)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (error.cause && verbose) {
|
|
86
|
+
console.error("");
|
|
87
|
+
console.error(kleur.dim(" Caused by:"));
|
|
88
|
+
if (error.cause instanceof Error) {
|
|
89
|
+
console.error(` ${error.cause.message}`);
|
|
90
|
+
} else {
|
|
91
|
+
console.error(` ${String(error.cause)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else if (error instanceof ZodError) {
|
|
95
|
+
console.error(kleur.red("\u2716 Validation Error:"));
|
|
96
|
+
console.error("");
|
|
97
|
+
for (const issue of error.issues) {
|
|
98
|
+
const path9 = issue.path.length > 0 ? issue.path.join(".") : "value";
|
|
99
|
+
console.error(` ${kleur.yellow(path9)}: ${issue.message}`);
|
|
100
|
+
}
|
|
101
|
+
console.error("");
|
|
102
|
+
console.error(kleur.cyan(" Suggestion:"));
|
|
103
|
+
console.error(
|
|
104
|
+
" Check the configuration file format or command arguments."
|
|
105
|
+
);
|
|
106
|
+
} else if (error instanceof Error) {
|
|
107
|
+
console.error(kleur.red("\u2716 Error:"), error.message);
|
|
108
|
+
if (verbose && error.stack) {
|
|
109
|
+
console.error("");
|
|
110
|
+
console.error(kleur.dim(" Stack trace:"));
|
|
111
|
+
const stackLines = error.stack.split("\n").slice(1);
|
|
112
|
+
for (const line of stackLines.slice(0, 5)) {
|
|
113
|
+
console.error(kleur.dim(line));
|
|
114
|
+
}
|
|
115
|
+
if (stackLines.length > 5) {
|
|
116
|
+
console.error(kleur.dim(` ... ${stackLines.length - 5} more lines`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
console.error(kleur.red("\u2716 Unknown error:"), String(error));
|
|
121
|
+
}
|
|
122
|
+
console.error("");
|
|
123
|
+
console.error(kleur.dim(" If the problem persists, please open an issue:"));
|
|
124
|
+
console.error(kleur.dim(" https://github.com/yellinzero/aico/issues"));
|
|
125
|
+
console.error("");
|
|
126
|
+
process.exit(exitCode);
|
|
127
|
+
}
|
|
128
|
+
function notInitializedError() {
|
|
129
|
+
return new AicoError(
|
|
130
|
+
"No aico.json found in current directory.",
|
|
131
|
+
"NOT_INITIALIZED"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
function employeeNotFoundError(name) {
|
|
135
|
+
return new AicoError(
|
|
136
|
+
`Employee '${name}' not found in registry.`,
|
|
137
|
+
"EMPLOYEE_NOT_FOUND",
|
|
138
|
+
{ context: { name } }
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
function skillNotFoundError(name) {
|
|
142
|
+
return new AicoError(
|
|
143
|
+
`Skill '${name}' not found in registry.`,
|
|
144
|
+
"SKILL_NOT_FOUND",
|
|
145
|
+
{ context: { name } }
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function platformNotSupportedError(platform) {
|
|
149
|
+
return new AicoError(
|
|
150
|
+
`Platform '${platform}' is not supported.`,
|
|
151
|
+
"PLATFORM_NOT_SUPPORTED",
|
|
152
|
+
{ context: { platform } }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
function registryFetchError(url, status) {
|
|
156
|
+
return new AicoError(
|
|
157
|
+
`Failed to fetch from registry: ${url} (status: ${status})`,
|
|
158
|
+
"FETCH_ERROR",
|
|
159
|
+
{ context: { url, status } }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/commands/init.ts
|
|
164
|
+
import { Command } from "commander";
|
|
165
|
+
import prompts from "prompts";
|
|
166
|
+
import { z as z2 } from "zod";
|
|
167
|
+
|
|
168
|
+
// src/utils/config.ts
|
|
169
|
+
import fs from "fs-extra";
|
|
170
|
+
import os from "os";
|
|
171
|
+
import path from "path";
|
|
172
|
+
|
|
173
|
+
// src/schema/config.ts
|
|
174
|
+
import { z } from "zod";
|
|
175
|
+
var DEFAULT_REGISTRY_URL = "https://the-aico.com/r/{name}.json";
|
|
176
|
+
var DEFAULT_SCHEMA_URL = "https://the-aico.com/schema/config.json";
|
|
177
|
+
var platformSchema = z.enum(["claude-code", "codex"]);
|
|
178
|
+
var languageSchema = z.string().default("en");
|
|
179
|
+
var platformPathsSchema = z.object({
|
|
180
|
+
skills: z.string(),
|
|
181
|
+
commands: z.string()
|
|
182
|
+
});
|
|
183
|
+
var employeeStateSchema = z.object({
|
|
184
|
+
platforms: z.array(platformSchema),
|
|
185
|
+
installedAt: z.string(),
|
|
186
|
+
version: z.string().optional(),
|
|
187
|
+
// Track installed skills and commands for update detection
|
|
188
|
+
skills: z.array(z.string()).optional(),
|
|
189
|
+
commands: z.array(z.string()).optional()
|
|
190
|
+
});
|
|
191
|
+
var skillStateSchema = z.object({
|
|
192
|
+
version: z.string(),
|
|
193
|
+
installedAt: z.string(),
|
|
194
|
+
source: z.enum(["standalone", "employee"]),
|
|
195
|
+
// standalone = single install, employee = part of employee
|
|
196
|
+
platforms: z.array(platformSchema)
|
|
197
|
+
});
|
|
198
|
+
var sharedSkillStateSchema = z.object({
|
|
199
|
+
version: z.string(),
|
|
200
|
+
installedAt: z.string(),
|
|
201
|
+
platforms: z.array(platformSchema),
|
|
202
|
+
// Track which employees depend on this shared skill
|
|
203
|
+
// When this array becomes empty, the shared skill can be removed
|
|
204
|
+
usedBy: z.array(z.string())
|
|
205
|
+
});
|
|
206
|
+
var registryConfigSchema = z.union([
|
|
207
|
+
z.string().refine((s) => s.includes("{name}"), {
|
|
208
|
+
message: "Registry URL must include {name} placeholder"
|
|
209
|
+
}),
|
|
210
|
+
z.object({
|
|
211
|
+
url: z.string().refine((s) => s.includes("{name}"), {
|
|
212
|
+
message: "Registry URL must include {name} placeholder"
|
|
213
|
+
}),
|
|
214
|
+
headers: z.record(z.string()).optional()
|
|
215
|
+
})
|
|
216
|
+
]);
|
|
217
|
+
var configSchema = z.object({
|
|
218
|
+
$schema: z.string().optional(),
|
|
219
|
+
language: languageSchema.optional(),
|
|
220
|
+
defaultPlatform: platformSchema,
|
|
221
|
+
platforms: z.record(platformSchema, platformPathsSchema),
|
|
222
|
+
employees: z.record(z.string(), employeeStateSchema).default({}),
|
|
223
|
+
skills: z.record(z.string(), skillStateSchema).default({}),
|
|
224
|
+
// Standalone skills
|
|
225
|
+
sharedSkills: z.record(z.string(), sharedSkillStateSchema).default({}),
|
|
226
|
+
// Shared skills with reference tracking
|
|
227
|
+
registries: z.record(
|
|
228
|
+
z.string().refine((key) => key.startsWith("@"), {
|
|
229
|
+
message: "Registry key must start with @"
|
|
230
|
+
}),
|
|
231
|
+
registryConfigSchema
|
|
232
|
+
).default({
|
|
233
|
+
"@the-aico": DEFAULT_REGISTRY_URL
|
|
234
|
+
})
|
|
235
|
+
});
|
|
236
|
+
var DEFAULT_PLATFORMS = {
|
|
237
|
+
"claude-code": {
|
|
238
|
+
skills: ".claude/skills",
|
|
239
|
+
commands: ".claude/commands"
|
|
240
|
+
},
|
|
241
|
+
// Codex 平台配置:
|
|
242
|
+
// - skills: 项目目录 .codex/skills/(Codex 原生支持项目级 skills)
|
|
243
|
+
// - commands (prompts): 全局 ~/.codex/prompts/(一次安装所有项目可用)
|
|
244
|
+
// 调用方式:/prompts:aico.{employee}.{command}
|
|
245
|
+
// 用户可在 aico.json 中覆盖路径
|
|
246
|
+
codex: {
|
|
247
|
+
skills: ".codex/skills",
|
|
248
|
+
commands: "~/.codex/prompts"
|
|
249
|
+
// Codex custom prompts (全局)
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
function createDefaultConfig(defaultPlatform = "claude-code") {
|
|
253
|
+
return {
|
|
254
|
+
$schema: DEFAULT_SCHEMA_URL,
|
|
255
|
+
defaultPlatform,
|
|
256
|
+
platforms: DEFAULT_PLATFORMS,
|
|
257
|
+
employees: {},
|
|
258
|
+
skills: {},
|
|
259
|
+
sharedSkills: {},
|
|
260
|
+
registries: {
|
|
261
|
+
"@the-aico": DEFAULT_REGISTRY_URL
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/utils/config.ts
|
|
267
|
+
var CONFIG_FILENAME = "aico.json";
|
|
268
|
+
function expandTilde(filePath) {
|
|
269
|
+
if (filePath.startsWith("~/") || filePath === "~") {
|
|
270
|
+
return path.join(os.homedir(), filePath.slice(1));
|
|
271
|
+
}
|
|
272
|
+
return filePath;
|
|
273
|
+
}
|
|
274
|
+
function getConfigPath(cwd) {
|
|
275
|
+
return path.resolve(cwd, CONFIG_FILENAME);
|
|
276
|
+
}
|
|
277
|
+
async function configExists(cwd) {
|
|
278
|
+
const configPath = getConfigPath(cwd);
|
|
279
|
+
return fs.pathExists(configPath);
|
|
280
|
+
}
|
|
281
|
+
async function getConfig(cwd) {
|
|
282
|
+
const configPath = getConfigPath(cwd);
|
|
283
|
+
if (!await fs.pathExists(configPath)) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const raw = await fs.readJson(configPath);
|
|
287
|
+
return configSchema.parse(raw);
|
|
288
|
+
}
|
|
289
|
+
async function writeConfig(cwd, config) {
|
|
290
|
+
const configPath = getConfigPath(cwd);
|
|
291
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
292
|
+
}
|
|
293
|
+
async function updateEmployees(cwd, employeeName, platforms, skills, commands) {
|
|
294
|
+
const config = await getConfig(cwd);
|
|
295
|
+
if (!config) {
|
|
296
|
+
throw new Error("Config not found");
|
|
297
|
+
}
|
|
298
|
+
config.employees[employeeName] = {
|
|
299
|
+
platforms,
|
|
300
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
301
|
+
skills,
|
|
302
|
+
commands
|
|
303
|
+
};
|
|
304
|
+
await writeConfig(cwd, config);
|
|
305
|
+
}
|
|
306
|
+
async function removeEmployee(cwd, employeeName) {
|
|
307
|
+
const config = await getConfig(cwd);
|
|
308
|
+
if (!config) {
|
|
309
|
+
throw new Error("Config not found");
|
|
310
|
+
}
|
|
311
|
+
delete config.employees[employeeName];
|
|
312
|
+
await writeConfig(cwd, config);
|
|
313
|
+
}
|
|
314
|
+
function resolvePlatformPaths(cwd, config, platform) {
|
|
315
|
+
const paths = config.platforms[platform];
|
|
316
|
+
if (!paths) {
|
|
317
|
+
throw new Error(`Platform ${platform} not configured`);
|
|
318
|
+
}
|
|
319
|
+
const skillsPath = expandTilde(paths.skills);
|
|
320
|
+
const commandsPath = expandTilde(paths.commands);
|
|
321
|
+
return {
|
|
322
|
+
skillsDir: path.isAbsolute(skillsPath) ? skillsPath : path.resolve(cwd, skillsPath),
|
|
323
|
+
commandsDir: path.isAbsolute(commandsPath) ? commandsPath : path.resolve(cwd, commandsPath)
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async function updateSkill(cwd, skillFullName, version, platforms, source = "standalone") {
|
|
327
|
+
const config = await getConfig(cwd);
|
|
328
|
+
if (!config) {
|
|
329
|
+
throw new Error("Config not found");
|
|
330
|
+
}
|
|
331
|
+
if (!config.skills) {
|
|
332
|
+
config.skills = {};
|
|
333
|
+
}
|
|
334
|
+
config.skills[skillFullName] = {
|
|
335
|
+
version,
|
|
336
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
337
|
+
source,
|
|
338
|
+
platforms
|
|
339
|
+
};
|
|
340
|
+
await writeConfig(cwd, config);
|
|
341
|
+
}
|
|
342
|
+
async function removeSkill(cwd, skillFullName) {
|
|
343
|
+
const config = await getConfig(cwd);
|
|
344
|
+
if (!config) {
|
|
345
|
+
throw new Error("Config not found");
|
|
346
|
+
}
|
|
347
|
+
if (config.skills) {
|
|
348
|
+
delete config.skills[skillFullName];
|
|
349
|
+
}
|
|
350
|
+
await writeConfig(cwd, config);
|
|
351
|
+
}
|
|
352
|
+
async function getInstalledSkills(cwd) {
|
|
353
|
+
const config = await getConfig(cwd);
|
|
354
|
+
if (!config) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
const skills = /* @__PURE__ */ new Set();
|
|
358
|
+
if (config.skills) {
|
|
359
|
+
for (const skillName of Object.keys(config.skills)) {
|
|
360
|
+
skills.add(skillName);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const empState of Object.values(config.employees)) {
|
|
364
|
+
if (empState.skills) {
|
|
365
|
+
for (const skillName of empState.skills) {
|
|
366
|
+
skills.add(skillName);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return Array.from(skills);
|
|
371
|
+
}
|
|
372
|
+
async function addSharedSkillReference(cwd, sharedSkillFullName, employeeName, version, platforms) {
|
|
373
|
+
const config = await getConfig(cwd);
|
|
374
|
+
if (!config) {
|
|
375
|
+
throw new Error("Config not found");
|
|
376
|
+
}
|
|
377
|
+
if (!config.sharedSkills) {
|
|
378
|
+
config.sharedSkills = {};
|
|
379
|
+
}
|
|
380
|
+
const existing = config.sharedSkills[sharedSkillFullName];
|
|
381
|
+
if (existing) {
|
|
382
|
+
if (!existing.usedBy.includes(employeeName)) {
|
|
383
|
+
existing.usedBy.push(employeeName);
|
|
384
|
+
}
|
|
385
|
+
const allPlatforms = /* @__PURE__ */ new Set([...existing.platforms, ...platforms]);
|
|
386
|
+
existing.platforms = Array.from(allPlatforms);
|
|
387
|
+
await writeConfig(cwd, config);
|
|
388
|
+
return { isNew: false };
|
|
389
|
+
}
|
|
390
|
+
config.sharedSkills[sharedSkillFullName] = {
|
|
391
|
+
version,
|
|
392
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
393
|
+
platforms,
|
|
394
|
+
usedBy: [employeeName]
|
|
395
|
+
};
|
|
396
|
+
await writeConfig(cwd, config);
|
|
397
|
+
return { isNew: true };
|
|
398
|
+
}
|
|
399
|
+
async function removeSharedSkillReference(cwd, sharedSkillFullName, employeeName) {
|
|
400
|
+
const config = await getConfig(cwd);
|
|
401
|
+
if (!config) {
|
|
402
|
+
throw new Error("Config not found");
|
|
403
|
+
}
|
|
404
|
+
const existing = config.sharedSkills?.[sharedSkillFullName];
|
|
405
|
+
if (!existing) {
|
|
406
|
+
return { shouldUninstall: false, remainingUsers: [] };
|
|
407
|
+
}
|
|
408
|
+
existing.usedBy = existing.usedBy.filter((e) => e !== employeeName);
|
|
409
|
+
if (existing.usedBy.length === 0) {
|
|
410
|
+
delete config.sharedSkills[sharedSkillFullName];
|
|
411
|
+
await writeConfig(cwd, config);
|
|
412
|
+
return { shouldUninstall: true, remainingUsers: [] };
|
|
413
|
+
}
|
|
414
|
+
await writeConfig(cwd, config);
|
|
415
|
+
return { shouldUninstall: false, remainingUsers: existing.usedBy };
|
|
416
|
+
}
|
|
417
|
+
async function getSharedSkillState(cwd, sharedSkillFullName) {
|
|
418
|
+
const config = await getConfig(cwd);
|
|
419
|
+
if (!config) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
return config.sharedSkills?.[sharedSkillFullName] ?? null;
|
|
423
|
+
}
|
|
424
|
+
async function isSharedSkillInstalled(cwd, sharedSkillFullName) {
|
|
425
|
+
const state = await getSharedSkillState(cwd, sharedSkillFullName);
|
|
426
|
+
return state !== null;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/utils/logger.ts
|
|
430
|
+
import kleur2 from "kleur";
|
|
431
|
+
import ora from "ora";
|
|
432
|
+
var logger = {
|
|
433
|
+
info: (msg) => {
|
|
434
|
+
console.log(kleur2.blue("\u2139"), msg);
|
|
435
|
+
},
|
|
436
|
+
success: (msg) => {
|
|
437
|
+
console.log(kleur2.green("\u2713"), msg);
|
|
438
|
+
},
|
|
439
|
+
warn: (msg) => {
|
|
440
|
+
console.log(kleur2.yellow("\u26A0"), msg);
|
|
441
|
+
},
|
|
442
|
+
error: (msg) => {
|
|
443
|
+
console.log(kleur2.red("\u2716"), msg);
|
|
444
|
+
},
|
|
445
|
+
log: (msg) => {
|
|
446
|
+
console.log(msg);
|
|
447
|
+
},
|
|
448
|
+
break: () => {
|
|
449
|
+
console.log();
|
|
450
|
+
},
|
|
451
|
+
dim: (msg) => {
|
|
452
|
+
console.log(kleur2.dim(msg));
|
|
453
|
+
},
|
|
454
|
+
bold: (msg) => {
|
|
455
|
+
console.log(kleur2.bold(msg));
|
|
456
|
+
},
|
|
457
|
+
highlight: (msg) => {
|
|
458
|
+
return kleur2.cyan(msg);
|
|
459
|
+
},
|
|
460
|
+
table: (rows) => {
|
|
461
|
+
for (const row of rows) {
|
|
462
|
+
console.log(" " + row.join(" "));
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
// Format functions (return string without printing)
|
|
466
|
+
formatSuccess: (msg) => {
|
|
467
|
+
return kleur2.green(msg);
|
|
468
|
+
},
|
|
469
|
+
formatError: (msg) => {
|
|
470
|
+
return kleur2.red(msg);
|
|
471
|
+
},
|
|
472
|
+
formatDim: (msg) => {
|
|
473
|
+
return kleur2.dim(msg);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
function spinner(text) {
|
|
477
|
+
return ora({
|
|
478
|
+
text,
|
|
479
|
+
spinner: "dots"
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/commands/init.ts
|
|
484
|
+
var initOptionsSchema = z2.object({
|
|
485
|
+
cwd: z2.string(),
|
|
486
|
+
defaultPlatform: platformSchema.optional(),
|
|
487
|
+
force: z2.boolean()
|
|
488
|
+
});
|
|
489
|
+
async function runInit(options) {
|
|
490
|
+
const { cwd, defaultPlatform, force } = options;
|
|
491
|
+
if (await configExists(cwd)) {
|
|
492
|
+
if (!force) {
|
|
493
|
+
const { overwrite } = await prompts({
|
|
494
|
+
type: "confirm",
|
|
495
|
+
name: "overwrite",
|
|
496
|
+
message: "aico.json already exists. Overwrite?",
|
|
497
|
+
initial: false
|
|
498
|
+
});
|
|
499
|
+
if (!overwrite) {
|
|
500
|
+
logger.info("Initialization cancelled.");
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const platform = defaultPlatform ?? "claude-code";
|
|
506
|
+
const config = createDefaultConfig(platform);
|
|
507
|
+
const s = spinner("Creating aico.json...").start();
|
|
508
|
+
await writeConfig(cwd, config);
|
|
509
|
+
s.succeed("Created aico.json");
|
|
510
|
+
logger.break();
|
|
511
|
+
logger.success("Project initialized!");
|
|
512
|
+
logger.dim(`Default platform: ${platform}`);
|
|
513
|
+
logger.break();
|
|
514
|
+
logger.info("Next steps:");
|
|
515
|
+
logger.log(` ${logger.highlight("npx aico add pm")} Add the PM employee`);
|
|
516
|
+
logger.log(
|
|
517
|
+
` ${logger.highlight("npx aico list")} View available employees`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
var init = new Command().name("init").description("Initialize aico in your project").option(
|
|
521
|
+
"-p, --default-platform <platform>",
|
|
522
|
+
"Default platform (claude-code, codex)"
|
|
523
|
+
).option("-f, --force", "Overwrite existing configuration", false).option("-c, --cwd <cwd>", "Working directory", process.cwd()).action(async (opts) => {
|
|
524
|
+
try {
|
|
525
|
+
const options = initOptionsSchema.parse({
|
|
526
|
+
cwd: opts.cwd,
|
|
527
|
+
defaultPlatform: opts.defaultPlatform,
|
|
528
|
+
force: opts.force
|
|
529
|
+
});
|
|
530
|
+
await runInit(options);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
handleError(error);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// src/commands/add.ts
|
|
537
|
+
import { Command as Command2 } from "commander";
|
|
538
|
+
import prompts3 from "prompts";
|
|
539
|
+
import { z as z6 } from "zod";
|
|
540
|
+
|
|
541
|
+
// src/registry/client.ts
|
|
542
|
+
import path3 from "path";
|
|
543
|
+
|
|
544
|
+
// src/schema/employee.ts
|
|
545
|
+
import { z as z4 } from "zod";
|
|
546
|
+
|
|
547
|
+
// src/schema/skill.ts
|
|
548
|
+
import YAML from "js-yaml";
|
|
549
|
+
import { z as z3 } from "zod";
|
|
550
|
+
var skillFrontmatterSchema = z3.object({
|
|
551
|
+
name: z3.string().regex(/^[a-z0-9-]+$/, "Skill name must be hyphen-case"),
|
|
552
|
+
description: z3.string(),
|
|
553
|
+
version: z3.string().optional(),
|
|
554
|
+
category: z3.enum(["pm", "frontend", "backend", "devops", "general"]).optional(),
|
|
555
|
+
tags: z3.array(z3.string()).optional(),
|
|
556
|
+
dependencies: z3.array(z3.string()).optional(),
|
|
557
|
+
mcpDependencies: z3.array(
|
|
558
|
+
z3.object({
|
|
559
|
+
name: z3.string(),
|
|
560
|
+
package: z3.string(),
|
|
561
|
+
description: z3.string(),
|
|
562
|
+
docsUrl: z3.string().url()
|
|
563
|
+
})
|
|
564
|
+
).optional()
|
|
565
|
+
});
|
|
566
|
+
var skillFileTypeSchema = z3.enum([
|
|
567
|
+
"skill",
|
|
568
|
+
"reference",
|
|
569
|
+
"asset",
|
|
570
|
+
"script"
|
|
571
|
+
]);
|
|
572
|
+
var skillFileSchema = z3.object({
|
|
573
|
+
path: z3.string().min(1),
|
|
574
|
+
type: skillFileTypeSchema,
|
|
575
|
+
content: z3.string()
|
|
576
|
+
});
|
|
577
|
+
var skillCategorySchema = z3.enum([
|
|
578
|
+
"pm",
|
|
579
|
+
"frontend",
|
|
580
|
+
"backend",
|
|
581
|
+
"devops",
|
|
582
|
+
"general"
|
|
583
|
+
]);
|
|
584
|
+
var skillSchema = z3.object({
|
|
585
|
+
name: z3.string().regex(/^[a-z0-9-]+$/, "Skill name must be hyphen-case"),
|
|
586
|
+
namespace: z3.string().regex(
|
|
587
|
+
/^@[a-z0-9-]+\/[a-z0-9_-]+$/,
|
|
588
|
+
"Namespace must be @registry/employee format"
|
|
589
|
+
),
|
|
590
|
+
fullName: z3.string().regex(
|
|
591
|
+
/^@[a-z0-9-]+\/[a-z0-9_-]+\/[a-z0-9-]+$/,
|
|
592
|
+
"Full name must be @registry/employee/skill format"
|
|
593
|
+
),
|
|
594
|
+
version: z3.string().regex(/^\d+\.\d+\.\d+$/, "Version must be semver format"),
|
|
595
|
+
description: z3.string(),
|
|
596
|
+
category: skillCategorySchema,
|
|
597
|
+
tags: z3.array(z3.string()).default([]),
|
|
598
|
+
dependencies: z3.array(z3.string()).default([]),
|
|
599
|
+
mcpDependencies: z3.array(
|
|
600
|
+
z3.object({
|
|
601
|
+
name: z3.string(),
|
|
602
|
+
package: z3.string(),
|
|
603
|
+
description: z3.string(),
|
|
604
|
+
docsUrl: z3.string()
|
|
605
|
+
})
|
|
606
|
+
).optional(),
|
|
607
|
+
files: z3.array(skillFileSchema)
|
|
608
|
+
});
|
|
609
|
+
var skillSummarySchema = z3.object({
|
|
610
|
+
name: z3.string(),
|
|
611
|
+
namespace: z3.string(),
|
|
612
|
+
fullName: z3.string(),
|
|
613
|
+
version: z3.string(),
|
|
614
|
+
description: z3.string(),
|
|
615
|
+
category: z3.string(),
|
|
616
|
+
tags: z3.array(z3.string())
|
|
617
|
+
});
|
|
618
|
+
var mcpDependencySchema = z3.object({
|
|
619
|
+
name: z3.string(),
|
|
620
|
+
package: z3.string(),
|
|
621
|
+
description: z3.string(),
|
|
622
|
+
docsUrl: z3.string().url()
|
|
623
|
+
});
|
|
624
|
+
function removeBOM(content) {
|
|
625
|
+
if (content.charCodeAt(0) === 65279) {
|
|
626
|
+
return content.slice(1);
|
|
627
|
+
}
|
|
628
|
+
return content;
|
|
629
|
+
}
|
|
630
|
+
function parseSkillFrontmatter(content) {
|
|
631
|
+
content = removeBOM(content);
|
|
632
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
633
|
+
const match = content.match(frontmatterRegex);
|
|
634
|
+
if (!match) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
const yamlContent = match[1] ?? "";
|
|
638
|
+
let data;
|
|
639
|
+
try {
|
|
640
|
+
data = YAML.load(yamlContent);
|
|
641
|
+
} catch (error) {
|
|
642
|
+
const yamlError = error;
|
|
643
|
+
logger.error(`Invalid YAML syntax: ${yamlError.message}`);
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
const result = skillFrontmatterSchema.safeParse(data);
|
|
647
|
+
if (!result.success) {
|
|
648
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
649
|
+
logger.error(`Invalid frontmatter: ${issues}`);
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
return result.data;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/schema/employee.ts
|
|
656
|
+
var fileTypeSchema = z4.enum(["skill", "command", "doc"]);
|
|
657
|
+
var employeeFileSourceSchema = z4.object({
|
|
658
|
+
path: z4.string().min(1),
|
|
659
|
+
type: fileTypeSchema
|
|
660
|
+
});
|
|
661
|
+
var employeeFileSchema = employeeFileSourceSchema.extend({
|
|
662
|
+
content: z4.string().min(1)
|
|
663
|
+
});
|
|
664
|
+
var skillDefSourceSchema = z4.object({
|
|
665
|
+
name: z4.string().regex(/^[a-z0-9-]+$/, "Skill name must be hyphen-case"),
|
|
666
|
+
files: z4.array(employeeFileSourceSchema).min(1)
|
|
667
|
+
});
|
|
668
|
+
var commandDefSourceSchema = z4.object({
|
|
669
|
+
name: z4.string().regex(/^[a-z0-9-]+$/, "Command name must be hyphen-case"),
|
|
670
|
+
files: z4.array(employeeFileSourceSchema).min(1)
|
|
671
|
+
});
|
|
672
|
+
var docDefSourceSchema = z4.object({
|
|
673
|
+
name: z4.string(),
|
|
674
|
+
files: z4.array(employeeFileSourceSchema).min(1)
|
|
675
|
+
});
|
|
676
|
+
var employeeSourceSchema = z4.object({
|
|
677
|
+
$schema: z4.string().optional(),
|
|
678
|
+
name: z4.string().regex(/^[a-z0-9-]+$/, "Employee name must be hyphen-case"),
|
|
679
|
+
role: z4.string().min(1),
|
|
680
|
+
description: z4.string().optional(),
|
|
681
|
+
skills: z4.array(skillDefSourceSchema).default([]),
|
|
682
|
+
commands: z4.array(commandDefSourceSchema).default([]),
|
|
683
|
+
docs: z4.array(docDefSourceSchema).default([]),
|
|
684
|
+
dependencies: z4.array(z4.string()).default([])
|
|
685
|
+
});
|
|
686
|
+
var skillDefSchema = z4.object({
|
|
687
|
+
name: z4.string().regex(/^[a-z0-9-]+$/, "Skill name must be hyphen-case"),
|
|
688
|
+
files: z4.array(employeeFileSchema).min(1)
|
|
689
|
+
});
|
|
690
|
+
var commandDefSchema = z4.object({
|
|
691
|
+
name: z4.string().regex(/^[a-z0-9-]+$/, "Command name must be hyphen-case"),
|
|
692
|
+
files: z4.array(employeeFileSchema).min(1)
|
|
693
|
+
});
|
|
694
|
+
var docDefSchema = z4.object({
|
|
695
|
+
name: z4.string(),
|
|
696
|
+
files: z4.array(employeeFileSchema).min(1)
|
|
697
|
+
});
|
|
698
|
+
var employeeSchema = z4.object({
|
|
699
|
+
$schema: z4.string().optional(),
|
|
700
|
+
name: z4.string().regex(/^[a-z0-9-]+$/, "Employee name must be hyphen-case"),
|
|
701
|
+
role: z4.string().min(1),
|
|
702
|
+
description: z4.string().optional(),
|
|
703
|
+
skills: z4.array(skillDefSchema).default([]),
|
|
704
|
+
commands: z4.array(commandDefSchema).default([]),
|
|
705
|
+
docs: z4.array(docDefSchema).default([]),
|
|
706
|
+
dependencies: z4.array(z4.string()).default([])
|
|
707
|
+
});
|
|
708
|
+
var employeeSummarySchema = z4.object({
|
|
709
|
+
name: z4.string(),
|
|
710
|
+
namespace: z4.string().optional(),
|
|
711
|
+
fullName: z4.string().optional(),
|
|
712
|
+
role: z4.string(),
|
|
713
|
+
description: z4.string().optional(),
|
|
714
|
+
version: z4.string().optional(),
|
|
715
|
+
skillCount: z4.number().optional(),
|
|
716
|
+
commandCount: z4.number().optional(),
|
|
717
|
+
category: skillCategorySchema.optional()
|
|
718
|
+
});
|
|
719
|
+
var employeeExtendedSchema = z4.object({
|
|
720
|
+
$schema: z4.string().optional(),
|
|
721
|
+
name: z4.string().regex(/^[a-z0-9-]+$/, "Employee name must be hyphen-case"),
|
|
722
|
+
namespace: z4.string().regex(/^@[a-z0-9-]+$/, "Namespace must be @registry format"),
|
|
723
|
+
fullName: z4.string().regex(
|
|
724
|
+
/^@[a-z0-9-]+\/[a-z0-9-]+$/,
|
|
725
|
+
"Full name must be @registry/employee format"
|
|
726
|
+
),
|
|
727
|
+
role: z4.string().min(1),
|
|
728
|
+
description: z4.string().optional(),
|
|
729
|
+
version: z4.string().regex(/^\d+\.\d+\.\d+$/, "Version must be semver format"),
|
|
730
|
+
category: skillCategorySchema,
|
|
731
|
+
skills: z4.array(z4.string()),
|
|
732
|
+
// Array of skill fullNames: ["@the-aico/pm/brainstorming", ...]
|
|
733
|
+
commands: z4.array(commandDefSchema).default([]),
|
|
734
|
+
docs: z4.array(docDefSchema).default([])
|
|
735
|
+
});
|
|
736
|
+
var registryIndexSchema = z4.object({
|
|
737
|
+
employees: z4.array(employeeSummarySchema)
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
// src/schema/registry.ts
|
|
741
|
+
import { z as z5 } from "zod";
|
|
742
|
+
var skillsIndexSchema = z5.array(skillSummarySchema);
|
|
743
|
+
var employeesIndexSchema = z5.array(employeeSummarySchema);
|
|
744
|
+
var registryIndexV2Schema = z5.object({
|
|
745
|
+
version: z5.literal("2.0").default("2.0"),
|
|
746
|
+
skills: skillsIndexSchema,
|
|
747
|
+
employees: employeesIndexSchema
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// src/registry/local.ts
|
|
751
|
+
import fs2 from "fs-extra";
|
|
752
|
+
import path2 from "path";
|
|
753
|
+
async function loadLocalEmployee(registryDir, employeeName) {
|
|
754
|
+
const employeePath = path2.join(registryDir, `${employeeName}.json`);
|
|
755
|
+
if (!await fs2.pathExists(employeePath)) {
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
const raw = await fs2.readJson(employeePath);
|
|
759
|
+
return employeeSchema.parse(raw);
|
|
760
|
+
}
|
|
761
|
+
async function loadLocalIndex(registryDir) {
|
|
762
|
+
const indexPath = path2.join(registryDir, "index.json");
|
|
763
|
+
if (!await fs2.pathExists(indexPath)) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
const raw = await fs2.readJson(indexPath);
|
|
767
|
+
return registryIndexSchema.parse(raw);
|
|
768
|
+
}
|
|
769
|
+
async function loadLocalSkill(registryDir, skillFullName) {
|
|
770
|
+
const parts = skillFullName.replace("@", "").split("/");
|
|
771
|
+
if (parts.length !== 3) {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
const [registry, employee, skill] = parts;
|
|
775
|
+
const skillPath = path2.join(registryDir, "skills", registry, employee, `${skill}.json`);
|
|
776
|
+
if (!await fs2.pathExists(skillPath)) {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
const raw = await fs2.readJson(skillPath);
|
|
780
|
+
return skillSchema.parse(raw);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/utils/parse-target.ts
|
|
784
|
+
function parseTarget(input, defaultRegistry = "@the-aico") {
|
|
785
|
+
const trimmed = input.trim();
|
|
786
|
+
if (trimmed.startsWith("@")) {
|
|
787
|
+
const parts = trimmed.split("/");
|
|
788
|
+
if (parts.length === 2) {
|
|
789
|
+
const [registry, name] = parts;
|
|
790
|
+
return {
|
|
791
|
+
type: "employee",
|
|
792
|
+
registry,
|
|
793
|
+
namespace: registry,
|
|
794
|
+
name,
|
|
795
|
+
fullName: trimmed
|
|
796
|
+
};
|
|
797
|
+
} else if (parts.length === 3) {
|
|
798
|
+
const [registry, employee, skill] = parts;
|
|
799
|
+
return {
|
|
800
|
+
type: "skill",
|
|
801
|
+
registry,
|
|
802
|
+
namespace: `${registry}/${employee}`,
|
|
803
|
+
name: skill,
|
|
804
|
+
fullName: trimmed
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
type: "employee",
|
|
810
|
+
registry: defaultRegistry,
|
|
811
|
+
namespace: defaultRegistry,
|
|
812
|
+
name: trimmed,
|
|
813
|
+
fullName: `${defaultRegistry}/${trimmed}`
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function parseSkillFullName(fullName) {
|
|
817
|
+
const match = fullName.match(/^(@[a-z0-9-]+)\/([a-z0-9_-]+)\/([a-z0-9-]+)$/);
|
|
818
|
+
if (!match || !match[1] || !match[2] || !match[3]) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
registry: match[1],
|
|
823
|
+
employee: match[2],
|
|
824
|
+
skill: match[3]
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/registry/client.ts
|
|
829
|
+
function resolveRegistryUrl(config, employeeName) {
|
|
830
|
+
if (typeof config === "string") {
|
|
831
|
+
return config.replace("{name}", employeeName);
|
|
832
|
+
}
|
|
833
|
+
return config.url.replace("{name}", employeeName);
|
|
834
|
+
}
|
|
835
|
+
function getRegistryHeaders(config) {
|
|
836
|
+
if (typeof config === "object" && config.headers) {
|
|
837
|
+
return config.headers;
|
|
838
|
+
}
|
|
839
|
+
return {};
|
|
840
|
+
}
|
|
841
|
+
function parseEmployeeRef(ref) {
|
|
842
|
+
if (ref.startsWith("@")) {
|
|
843
|
+
const parts = ref.split("/");
|
|
844
|
+
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
845
|
+
return { registry: parts[0], name: parts[1] };
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return { registry: "@the-aico", name: ref };
|
|
849
|
+
}
|
|
850
|
+
async function fetchEmployee(employeeRef, config, cwd) {
|
|
851
|
+
const { registry, name } = parseEmployeeRef(employeeRef);
|
|
852
|
+
const registryConfig = config.registries[registry];
|
|
853
|
+
if (!registryConfig) {
|
|
854
|
+
throw new Error(`Registry '${registry}' not found in config`);
|
|
855
|
+
}
|
|
856
|
+
const url = resolveRegistryUrl(registryConfig, name);
|
|
857
|
+
if (url.startsWith("file://")) {
|
|
858
|
+
const localPath = url.replace("file://", "");
|
|
859
|
+
const resolvedPath = path3.isAbsolute(localPath) ? localPath : path3.resolve(cwd, localPath);
|
|
860
|
+
const registryDir = path3.dirname(resolvedPath);
|
|
861
|
+
const employee = await loadLocalEmployee(registryDir, name);
|
|
862
|
+
if (!employee) {
|
|
863
|
+
throw employeeNotFoundError(employeeRef);
|
|
864
|
+
}
|
|
865
|
+
return employee;
|
|
866
|
+
}
|
|
867
|
+
const headers = getRegistryHeaders(registryConfig);
|
|
868
|
+
const response = await fetch(url, { headers });
|
|
869
|
+
if (!response.ok) {
|
|
870
|
+
if (response.status === 404) {
|
|
871
|
+
throw employeeNotFoundError(employeeRef);
|
|
872
|
+
}
|
|
873
|
+
throw registryFetchError(url, response.status);
|
|
874
|
+
}
|
|
875
|
+
const json = await response.json();
|
|
876
|
+
return employeeSchema.parse(json);
|
|
877
|
+
}
|
|
878
|
+
async function fetchRegistryIndex(config, cwd, registryName = "@the-aico") {
|
|
879
|
+
const registryConfig = config.registries[registryName];
|
|
880
|
+
if (!registryConfig) {
|
|
881
|
+
throw new Error(`Registry '${registryName}' not found in config`);
|
|
882
|
+
}
|
|
883
|
+
const url = resolveRegistryUrl(registryConfig, "index");
|
|
884
|
+
if (url.startsWith("file://")) {
|
|
885
|
+
const localPath = url.replace("file://", "").replace("/index.json", "");
|
|
886
|
+
const resolvedPath = path3.isAbsolute(localPath) ? localPath : path3.resolve(cwd, localPath);
|
|
887
|
+
const index = await loadLocalIndex(resolvedPath);
|
|
888
|
+
if (!index) {
|
|
889
|
+
return { employees: [] };
|
|
890
|
+
}
|
|
891
|
+
return index;
|
|
892
|
+
}
|
|
893
|
+
const headers = getRegistryHeaders(registryConfig);
|
|
894
|
+
const response = await fetch(url.replace("{name}.json", "index.json"), { headers });
|
|
895
|
+
if (!response.ok) {
|
|
896
|
+
if (response.status === 404) {
|
|
897
|
+
return { employees: [] };
|
|
898
|
+
}
|
|
899
|
+
throw registryFetchError(url, response.status);
|
|
900
|
+
}
|
|
901
|
+
const json = await response.json();
|
|
902
|
+
return registryIndexSchema.parse(json);
|
|
903
|
+
}
|
|
904
|
+
function buildSkillUrl(registryConfig, fullName) {
|
|
905
|
+
const parsed = parseSkillFullName(fullName);
|
|
906
|
+
if (!parsed) {
|
|
907
|
+
throw new Error(`Invalid skill full name: ${fullName}`);
|
|
908
|
+
}
|
|
909
|
+
const baseUrl = typeof registryConfig === "string" ? registryConfig.replace("{name}.json", "") : registryConfig.url.replace("{name}.json", "");
|
|
910
|
+
return `${baseUrl}skills/${parsed.registry.replace("@", "")}/${parsed.employee}/${parsed.skill}.json`;
|
|
911
|
+
}
|
|
912
|
+
async function fetchSkill(fullName, config, cwd) {
|
|
913
|
+
const parsed = parseSkillFullName(fullName);
|
|
914
|
+
if (!parsed) {
|
|
915
|
+
throw new Error(`Invalid skill full name: ${fullName}`);
|
|
916
|
+
}
|
|
917
|
+
const registryConfig = config.registries[parsed.registry];
|
|
918
|
+
if (!registryConfig) {
|
|
919
|
+
throw new Error(`Registry '${parsed.registry}' not found in config`);
|
|
920
|
+
}
|
|
921
|
+
const url = buildSkillUrl(registryConfig, fullName);
|
|
922
|
+
if (url.startsWith("file://")) {
|
|
923
|
+
const localPath = url.replace("file://", "");
|
|
924
|
+
const registryDir = path3.resolve(cwd, localPath.split("/skills/")[0] ?? "");
|
|
925
|
+
const skill = await loadLocalSkill(registryDir, fullName);
|
|
926
|
+
if (!skill) {
|
|
927
|
+
throw skillNotFoundError(fullName);
|
|
928
|
+
}
|
|
929
|
+
return skill;
|
|
930
|
+
}
|
|
931
|
+
const headers = getRegistryHeaders(registryConfig);
|
|
932
|
+
const response = await fetch(url, { headers });
|
|
933
|
+
if (!response.ok) {
|
|
934
|
+
if (response.status === 404) {
|
|
935
|
+
throw skillNotFoundError(fullName);
|
|
936
|
+
}
|
|
937
|
+
throw registryFetchError(url, response.status);
|
|
938
|
+
}
|
|
939
|
+
const json = await response.json();
|
|
940
|
+
return skillSchema.parse(json);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/installer/index.ts
|
|
944
|
+
import fs3 from "fs-extra";
|
|
945
|
+
import path4 from "path";
|
|
946
|
+
import prompts2 from "prompts";
|
|
947
|
+
|
|
948
|
+
// src/installer/platforms/claude-code.ts
|
|
949
|
+
var claudeCodeAdapter = {
|
|
950
|
+
name: "claude-code",
|
|
951
|
+
getSkillDirName(employeeName, skillName) {
|
|
952
|
+
return `aico-${employeeName}-${skillName}`;
|
|
953
|
+
},
|
|
954
|
+
getCommandFileName(employeeName, commandName) {
|
|
955
|
+
return `${employeeName}.${commandName}.md`;
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/installer/platforms/codex.ts
|
|
960
|
+
var codexAdapter = {
|
|
961
|
+
name: "codex",
|
|
962
|
+
/**
|
|
963
|
+
* Codex skill directory name
|
|
964
|
+
* Uses the same naming convention as Claude Code: aico-{employee}-{skill}
|
|
965
|
+
*/
|
|
966
|
+
getSkillDirName(employeeName, skillName) {
|
|
967
|
+
return `aico-${employeeName}-${skillName}`;
|
|
968
|
+
},
|
|
969
|
+
/**
|
|
970
|
+
* Codex command file name (Custom Prompts)
|
|
971
|
+
*
|
|
972
|
+
* Codex custom prompts are located in .codex/prompts/ directory
|
|
973
|
+
* The file name (without .md) becomes the command name
|
|
974
|
+
*
|
|
975
|
+
* Naming convention: aico.{employee}.{command}.md
|
|
976
|
+
* Invocation: /prompts:aico.{employee}.{command}
|
|
977
|
+
*
|
|
978
|
+
* Example:
|
|
979
|
+
* - File: .codex/prompts/aico.pm.plan.md
|
|
980
|
+
* - Invocation: /prompts:aico.pm.plan
|
|
981
|
+
*/
|
|
982
|
+
getCommandFileName(employeeName, commandName) {
|
|
983
|
+
return `aico.${employeeName}.${commandName}.md`;
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
// src/installer/index.ts
|
|
988
|
+
var adapters = {
|
|
989
|
+
"claude-code": claudeCodeAdapter,
|
|
990
|
+
codex: codexAdapter
|
|
991
|
+
};
|
|
992
|
+
async function checkConflicts(employee, config, cwd, platforms) {
|
|
993
|
+
const conflicts = [];
|
|
994
|
+
for (const platform of platforms) {
|
|
995
|
+
const adapter = adapters[platform];
|
|
996
|
+
const { skillsDir, commandsDir } = resolvePlatformPaths(
|
|
997
|
+
cwd,
|
|
998
|
+
config,
|
|
999
|
+
platform
|
|
1000
|
+
);
|
|
1001
|
+
for (const skill of employee.skills) {
|
|
1002
|
+
const skillDirName = adapter.getSkillDirName(employee.name, skill.name);
|
|
1003
|
+
const skillPath = path4.join(skillsDir, skillDirName);
|
|
1004
|
+
if (await fs3.pathExists(skillPath)) {
|
|
1005
|
+
conflicts.push({ path: skillPath, type: "skill" });
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
for (const command of employee.commands) {
|
|
1009
|
+
const commandFileName = adapter.getCommandFileName(
|
|
1010
|
+
employee.name,
|
|
1011
|
+
command.name
|
|
1012
|
+
);
|
|
1013
|
+
const commandPath = path4.join(commandsDir, commandFileName);
|
|
1014
|
+
if (await fs3.pathExists(commandPath)) {
|
|
1015
|
+
conflicts.push({ path: commandPath, type: "command" });
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return conflicts;
|
|
1020
|
+
}
|
|
1021
|
+
async function confirmOverwrite(conflicts) {
|
|
1022
|
+
const { proceed } = await prompts2({
|
|
1023
|
+
type: "confirm",
|
|
1024
|
+
name: "proceed",
|
|
1025
|
+
message: `${conflicts.length} file(s) will be overwritten. Continue?`,
|
|
1026
|
+
initial: false
|
|
1027
|
+
});
|
|
1028
|
+
return proceed;
|
|
1029
|
+
}
|
|
1030
|
+
async function installEmployee(employee, config, cwd, platforms, options) {
|
|
1031
|
+
const results = [];
|
|
1032
|
+
for (const platform of platforms) {
|
|
1033
|
+
const result = await installEmployeeToPlatform(
|
|
1034
|
+
employee,
|
|
1035
|
+
config,
|
|
1036
|
+
cwd,
|
|
1037
|
+
platform,
|
|
1038
|
+
options
|
|
1039
|
+
);
|
|
1040
|
+
results.push(result);
|
|
1041
|
+
}
|
|
1042
|
+
return results;
|
|
1043
|
+
}
|
|
1044
|
+
async function installEmployeeToPlatform(employee, config, cwd, platform, _options) {
|
|
1045
|
+
const adapter = adapters[platform];
|
|
1046
|
+
const { skillsDir, commandsDir } = resolvePlatformPaths(
|
|
1047
|
+
cwd,
|
|
1048
|
+
config,
|
|
1049
|
+
platform
|
|
1050
|
+
);
|
|
1051
|
+
await fs3.ensureDir(skillsDir);
|
|
1052
|
+
await fs3.ensureDir(commandsDir);
|
|
1053
|
+
let skillsInstalled = 0;
|
|
1054
|
+
let commandsInstalled = 0;
|
|
1055
|
+
for (const skill of employee.skills) {
|
|
1056
|
+
const skillDirName = adapter.getSkillDirName(employee.name, skill.name);
|
|
1057
|
+
const skillDir = path4.join(skillsDir, skillDirName);
|
|
1058
|
+
await fs3.ensureDir(skillDir);
|
|
1059
|
+
for (const file of skill.files) {
|
|
1060
|
+
const filePath = path4.join(skillDir, path4.basename(file.path));
|
|
1061
|
+
let content = file.content;
|
|
1062
|
+
if (file.path.endsWith("SKILL.md")) {
|
|
1063
|
+
content = updateSkillName(content, skillDirName);
|
|
1064
|
+
}
|
|
1065
|
+
await fs3.writeFile(filePath, content, "utf-8");
|
|
1066
|
+
}
|
|
1067
|
+
skillsInstalled++;
|
|
1068
|
+
}
|
|
1069
|
+
for (const command of employee.commands) {
|
|
1070
|
+
const commandFileName = adapter.getCommandFileName(
|
|
1071
|
+
employee.name,
|
|
1072
|
+
command.name
|
|
1073
|
+
);
|
|
1074
|
+
for (const file of command.files) {
|
|
1075
|
+
const filePath = path4.join(commandsDir, commandFileName);
|
|
1076
|
+
await fs3.writeFile(filePath, file.content, "utf-8");
|
|
1077
|
+
}
|
|
1078
|
+
commandsInstalled++;
|
|
1079
|
+
}
|
|
1080
|
+
return {
|
|
1081
|
+
platform,
|
|
1082
|
+
skillsInstalled,
|
|
1083
|
+
commandsInstalled
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
function updateSkillName(content, newName) {
|
|
1087
|
+
return content.replace(/^(---\n[\s\S]*?name:\s*).+$/m, `$1${newName}`);
|
|
1088
|
+
}
|
|
1089
|
+
async function uninstallEmployee(employeeName, config, cwd, platforms) {
|
|
1090
|
+
for (const platform of platforms) {
|
|
1091
|
+
const { skillsDir, commandsDir } = resolvePlatformPaths(
|
|
1092
|
+
cwd,
|
|
1093
|
+
config,
|
|
1094
|
+
platform
|
|
1095
|
+
);
|
|
1096
|
+
const skillPrefix = `aico-${employeeName}-`;
|
|
1097
|
+
if (await fs3.pathExists(skillsDir)) {
|
|
1098
|
+
const entries = await fs3.readdir(skillsDir);
|
|
1099
|
+
for (const entry of entries) {
|
|
1100
|
+
if (entry.startsWith(skillPrefix)) {
|
|
1101
|
+
await fs3.remove(path4.join(skillsDir, entry));
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (await fs3.pathExists(commandsDir)) {
|
|
1106
|
+
const entries = await fs3.readdir(commandsDir);
|
|
1107
|
+
const commandPrefix = platform === "codex" ? `aico.${employeeName}.` : `${employeeName}.`;
|
|
1108
|
+
for (const entry of entries) {
|
|
1109
|
+
if (entry.startsWith(commandPrefix) && entry.endsWith(".md")) {
|
|
1110
|
+
await fs3.remove(path4.join(commandsDir, entry));
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// src/installer/skill-installer.ts
|
|
1118
|
+
import fs4 from "fs-extra";
|
|
1119
|
+
import path5 from "path";
|
|
1120
|
+
function getSkillDirName(fullNameOrName) {
|
|
1121
|
+
if (fullNameOrName.startsWith("@")) {
|
|
1122
|
+
if (fullNameOrName.includes("/_shared/")) {
|
|
1123
|
+
const skillName = fullNameOrName.split("/").pop() || "";
|
|
1124
|
+
return `aico-${skillName}`;
|
|
1125
|
+
}
|
|
1126
|
+
const normalized = fullNameOrName.replace(/^@/, "").replace(/\//g, "-");
|
|
1127
|
+
return normalized;
|
|
1128
|
+
}
|
|
1129
|
+
return fullNameOrName;
|
|
1130
|
+
}
|
|
1131
|
+
async function installSkill(skill, config, cwd, options) {
|
|
1132
|
+
const { skillsDir } = resolvePlatformPaths(cwd, config, options.platform);
|
|
1133
|
+
const skillDirName = getSkillDirName(skill.fullName);
|
|
1134
|
+
const skillDir = path5.join(skillsDir, skillDirName);
|
|
1135
|
+
if (await fs4.pathExists(skillDir)) {
|
|
1136
|
+
if (!options.overwrite) {
|
|
1137
|
+
return {
|
|
1138
|
+
skill,
|
|
1139
|
+
installed: false,
|
|
1140
|
+
skipped: true,
|
|
1141
|
+
reason: "Already exists",
|
|
1142
|
+
path: skillDir
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
await fs4.remove(skillDir);
|
|
1146
|
+
}
|
|
1147
|
+
await fs4.ensureDir(skillDir);
|
|
1148
|
+
for (const file of skill.files) {
|
|
1149
|
+
const filePath = path5.join(skillDir, file.path);
|
|
1150
|
+
let content = file.content;
|
|
1151
|
+
if (file.path === "SKILL.md" || file.path.endsWith("/SKILL.md")) {
|
|
1152
|
+
content = updateSkillFrontmatterName(content, skillDirName);
|
|
1153
|
+
}
|
|
1154
|
+
await fs4.writeFile(filePath, content, "utf-8");
|
|
1155
|
+
}
|
|
1156
|
+
return {
|
|
1157
|
+
skill,
|
|
1158
|
+
installed: true,
|
|
1159
|
+
skipped: false,
|
|
1160
|
+
path: skillDir
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
function updateSkillFrontmatterName(content, newName) {
|
|
1164
|
+
return content.replace(/^(---\n[\s\S]*?name:\s*).+$/m, `$1${newName}`);
|
|
1165
|
+
}
|
|
1166
|
+
async function uninstallSkill(skillFullName, config, cwd, options) {
|
|
1167
|
+
const { skillsDir } = resolvePlatformPaths(cwd, config, options.platform);
|
|
1168
|
+
const skillDirName = getSkillDirName(skillFullName);
|
|
1169
|
+
const skillDir = path5.join(skillsDir, skillDirName);
|
|
1170
|
+
if (!await fs4.pathExists(skillDir)) {
|
|
1171
|
+
return {
|
|
1172
|
+
skillName: skillFullName,
|
|
1173
|
+
removed: false,
|
|
1174
|
+
files: []
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
const files = [];
|
|
1178
|
+
const entries = await fs4.readdir(skillDir);
|
|
1179
|
+
for (const entry of entries) {
|
|
1180
|
+
files.push(path5.join(skillDir, entry));
|
|
1181
|
+
}
|
|
1182
|
+
if (options.dryRun) {
|
|
1183
|
+
return {
|
|
1184
|
+
skillName: skillFullName,
|
|
1185
|
+
removed: false,
|
|
1186
|
+
files
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
await fs4.remove(skillDir);
|
|
1190
|
+
return {
|
|
1191
|
+
skillName: skillFullName,
|
|
1192
|
+
removed: true,
|
|
1193
|
+
files
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// src/registry/resolver.ts
|
|
1198
|
+
var DependencyResolver = class {
|
|
1199
|
+
constructor(fetchSkill2) {
|
|
1200
|
+
this.fetchSkill = fetchSkill2;
|
|
1201
|
+
}
|
|
1202
|
+
cache = /* @__PURE__ */ new Map();
|
|
1203
|
+
/**
|
|
1204
|
+
* Resolve all dependencies for given skills
|
|
1205
|
+
* Returns skills in topological order (dependencies first)
|
|
1206
|
+
*/
|
|
1207
|
+
async resolve(skillNames) {
|
|
1208
|
+
const allSkills = [];
|
|
1209
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1210
|
+
for (const name of skillNames) {
|
|
1211
|
+
const skills = await this.fetchWithDependencies(name, visited);
|
|
1212
|
+
allSkills.push(...skills);
|
|
1213
|
+
}
|
|
1214
|
+
const uniqueSkills = this.deduplicateSkills(allSkills);
|
|
1215
|
+
const sortedSkills = this.topologicalSort(uniqueSkills);
|
|
1216
|
+
const skillMap = new Map(uniqueSkills.map((s) => [s.fullName, s]));
|
|
1217
|
+
const trees = skillNames.map((name) => ({
|
|
1218
|
+
root: name,
|
|
1219
|
+
dependencies: [this.buildDependencyTree(name, skillMap)]
|
|
1220
|
+
}));
|
|
1221
|
+
return {
|
|
1222
|
+
skills: sortedSkills,
|
|
1223
|
+
tree: trees[0]
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Recursively fetch a skill and all its dependencies
|
|
1228
|
+
*/
|
|
1229
|
+
async fetchWithDependencies(skillName, visited = /* @__PURE__ */ new Set(), path9 = []) {
|
|
1230
|
+
if (path9.includes(skillName)) {
|
|
1231
|
+
const cycle = [...path9, skillName].join(" \u2192 ");
|
|
1232
|
+
throw new AicoError(
|
|
1233
|
+
`Circular dependency detected: ${cycle}`,
|
|
1234
|
+
"CIRCULAR_DEPENDENCY",
|
|
1235
|
+
{ suggestion: "Check skill dependencies for cycles." }
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
if (visited.has(skillName)) {
|
|
1239
|
+
return [];
|
|
1240
|
+
}
|
|
1241
|
+
visited.add(skillName);
|
|
1242
|
+
let skill = this.cache.get(skillName);
|
|
1243
|
+
if (!skill) {
|
|
1244
|
+
skill = await this.fetchSkill(skillName);
|
|
1245
|
+
this.cache.set(skillName, skill);
|
|
1246
|
+
}
|
|
1247
|
+
const allSkills = [];
|
|
1248
|
+
for (const dep of skill.dependencies) {
|
|
1249
|
+
const depSkills = await this.fetchWithDependencies(
|
|
1250
|
+
dep,
|
|
1251
|
+
visited,
|
|
1252
|
+
[...path9, skillName]
|
|
1253
|
+
);
|
|
1254
|
+
allSkills.push(...depSkills);
|
|
1255
|
+
}
|
|
1256
|
+
allSkills.push(skill);
|
|
1257
|
+
return allSkills;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Remove duplicate skills (keep first occurrence)
|
|
1261
|
+
*/
|
|
1262
|
+
deduplicateSkills(skills) {
|
|
1263
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1264
|
+
for (const skill of skills) {
|
|
1265
|
+
if (!seen.has(skill.fullName)) {
|
|
1266
|
+
seen.set(skill.fullName, skill);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return Array.from(seen.values());
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Topological sort using Kahn's algorithm
|
|
1273
|
+
* Returns skills in dependency order (install dependencies first)
|
|
1274
|
+
*/
|
|
1275
|
+
topologicalSort(skills) {
|
|
1276
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
1277
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1278
|
+
const skillMap = /* @__PURE__ */ new Map();
|
|
1279
|
+
for (const skill of skills) {
|
|
1280
|
+
skillMap.set(skill.fullName, skill);
|
|
1281
|
+
if (!inDegree.has(skill.fullName)) {
|
|
1282
|
+
inDegree.set(skill.fullName, 0);
|
|
1283
|
+
}
|
|
1284
|
+
if (!graph.has(skill.fullName)) {
|
|
1285
|
+
graph.set(skill.fullName, []);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
for (const skill of skills) {
|
|
1289
|
+
for (const dep of skill.dependencies) {
|
|
1290
|
+
if (skillMap.has(dep)) {
|
|
1291
|
+
const edges = graph.get(dep) ?? [];
|
|
1292
|
+
edges.push(skill.fullName);
|
|
1293
|
+
graph.set(dep, edges);
|
|
1294
|
+
inDegree.set(skill.fullName, (inDegree.get(skill.fullName) ?? 0) + 1);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const queue = [];
|
|
1299
|
+
for (const [name, degree] of inDegree.entries()) {
|
|
1300
|
+
if (degree === 0) {
|
|
1301
|
+
queue.push(name);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
const result = [];
|
|
1305
|
+
while (queue.length > 0) {
|
|
1306
|
+
const node = queue.shift();
|
|
1307
|
+
const skill = skillMap.get(node);
|
|
1308
|
+
if (skill) {
|
|
1309
|
+
result.push(skill);
|
|
1310
|
+
}
|
|
1311
|
+
for (const neighbor of graph.get(node) ?? []) {
|
|
1312
|
+
const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
|
|
1313
|
+
inDegree.set(neighbor, newDegree);
|
|
1314
|
+
if (newDegree === 0) {
|
|
1315
|
+
queue.push(neighbor);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (result.length !== skills.length) {
|
|
1320
|
+
throw new AicoError(
|
|
1321
|
+
"Circular dependency detected in skill graph",
|
|
1322
|
+
"CIRCULAR_DEPENDENCY",
|
|
1323
|
+
{ suggestion: "Check skill dependencies for cycles." }
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
return result;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Build dependency tree for display
|
|
1330
|
+
*/
|
|
1331
|
+
buildDependencyTree(skillName, skillMap, depth = 0, visited = /* @__PURE__ */ new Set()) {
|
|
1332
|
+
const skill = skillMap.get(skillName);
|
|
1333
|
+
if (!skill) {
|
|
1334
|
+
return {
|
|
1335
|
+
fullName: skillName,
|
|
1336
|
+
version: "unknown",
|
|
1337
|
+
children: [],
|
|
1338
|
+
depth
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
if (visited.has(skillName)) {
|
|
1342
|
+
return {
|
|
1343
|
+
fullName: skillName + " (circular)",
|
|
1344
|
+
version: skill.version,
|
|
1345
|
+
children: [],
|
|
1346
|
+
depth
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
visited.add(skillName);
|
|
1350
|
+
const children = skill.dependencies.filter((dep) => skillMap.has(dep)).map((dep) => this.buildDependencyTree(dep, skillMap, depth + 1, new Set(visited)));
|
|
1351
|
+
return {
|
|
1352
|
+
fullName: skillName,
|
|
1353
|
+
version: skill.version,
|
|
1354
|
+
children,
|
|
1355
|
+
depth
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
function formatDependencyTree(node, prefix = "") {
|
|
1360
|
+
const lines = [];
|
|
1361
|
+
lines.push(`${node.fullName} (${node.version})`);
|
|
1362
|
+
node.children.forEach((child, index) => {
|
|
1363
|
+
const isLast = index === node.children.length - 1;
|
|
1364
|
+
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
1365
|
+
const extension = isLast ? " " : "\u2502 ";
|
|
1366
|
+
lines.push(`${prefix}${connector}${child.fullName} (${child.version})`);
|
|
1367
|
+
if (child.children.length > 0) {
|
|
1368
|
+
const childLines = formatDependencyTreeRecursive(child.children, prefix + extension);
|
|
1369
|
+
lines.push(...childLines);
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
return lines.join("\n");
|
|
1373
|
+
}
|
|
1374
|
+
function formatDependencyTreeRecursive(nodes, prefix) {
|
|
1375
|
+
const lines = [];
|
|
1376
|
+
nodes.forEach((node, index) => {
|
|
1377
|
+
const isLast = index === nodes.length - 1;
|
|
1378
|
+
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
1379
|
+
const extension = isLast ? " " : "\u2502 ";
|
|
1380
|
+
lines.push(`${prefix}${connector}${node.fullName} (${node.version})`);
|
|
1381
|
+
if (node.children.length > 0) {
|
|
1382
|
+
lines.push(...formatDependencyTreeRecursive(node.children, prefix + extension));
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
return lines;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// src/utils/constraint-docs.ts
|
|
1389
|
+
import fs5 from "fs-extra";
|
|
1390
|
+
import path6 from "path";
|
|
1391
|
+
var CONSTRAINT_DOCS = {
|
|
1392
|
+
pm: ["docs/reference/pm/constitution.md"],
|
|
1393
|
+
frontend: ["docs/reference/frontend/constraints.md"],
|
|
1394
|
+
backend: ["docs/reference/backend/constraints.md"]
|
|
1395
|
+
};
|
|
1396
|
+
async function checkConstraintDocs(employeeName, cwd) {
|
|
1397
|
+
const docs = CONSTRAINT_DOCS[employeeName] || [];
|
|
1398
|
+
const existing = [];
|
|
1399
|
+
const missing = [];
|
|
1400
|
+
for (const doc of docs) {
|
|
1401
|
+
const fullPath = path6.join(cwd, doc);
|
|
1402
|
+
if (await fs5.pathExists(fullPath)) {
|
|
1403
|
+
existing.push(doc);
|
|
1404
|
+
} else {
|
|
1405
|
+
missing.push(doc);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return { existing, missing };
|
|
1409
|
+
}
|
|
1410
|
+
function showConstraintDocHints(employeeName, existing, missing) {
|
|
1411
|
+
if (missing.length === 0 && existing.length === 0) {
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
const initCommand = `/${employeeName}.init`;
|
|
1415
|
+
if (missing.length > 0) {
|
|
1416
|
+
logger.break();
|
|
1417
|
+
logger.info(`\u{1F4A1} Tip: Run ${initCommand} to create constraint documents:`);
|
|
1418
|
+
for (const doc of missing) {
|
|
1419
|
+
logger.dim(` - ${doc}`);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (existing.length > 0) {
|
|
1423
|
+
logger.break();
|
|
1424
|
+
logger.info("\u{1F4C4} Existing constraint documents:");
|
|
1425
|
+
for (const doc of existing) {
|
|
1426
|
+
logger.dim(` - ${doc}`);
|
|
1427
|
+
}
|
|
1428
|
+
logger.dim(` Run ${initCommand} to update these if needed.`);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// src/commands/add.ts
|
|
1433
|
+
var collectedMCPDeps = /* @__PURE__ */ new Map();
|
|
1434
|
+
var constraintDocInfo = /* @__PURE__ */ new Map();
|
|
1435
|
+
var addOptionsSchema = z6.object({
|
|
1436
|
+
items: z6.array(z6.string()),
|
|
1437
|
+
cwd: z6.string(),
|
|
1438
|
+
platforms: z6.array(platformSchema).optional(),
|
|
1439
|
+
overwrite: z6.boolean(),
|
|
1440
|
+
yes: z6.boolean(),
|
|
1441
|
+
noDeps: z6.boolean()
|
|
1442
|
+
});
|
|
1443
|
+
async function selectEmployeesInteractively(cwd) {
|
|
1444
|
+
const config = await getConfig(cwd);
|
|
1445
|
+
if (!config) {
|
|
1446
|
+
throw notInitializedError();
|
|
1447
|
+
}
|
|
1448
|
+
const s = spinner("Fetching available employees...").start();
|
|
1449
|
+
try {
|
|
1450
|
+
const index = await fetchRegistryIndex(config, cwd);
|
|
1451
|
+
s.stop();
|
|
1452
|
+
if (index.employees.length === 0) {
|
|
1453
|
+
logger.warn("No employees available in registry.");
|
|
1454
|
+
return [];
|
|
1455
|
+
}
|
|
1456
|
+
const installedNames = new Set(Object.keys(config.employees));
|
|
1457
|
+
const choices = index.employees.map((emp) => ({
|
|
1458
|
+
title: installedNames.has(emp.name) ? `${emp.name} (${emp.role}) [installed]` : `${emp.name} (${emp.role})`,
|
|
1459
|
+
value: emp.name,
|
|
1460
|
+
description: emp.description,
|
|
1461
|
+
disabled: false
|
|
1462
|
+
}));
|
|
1463
|
+
logger.break();
|
|
1464
|
+
logger.info("Available employees:");
|
|
1465
|
+
logger.break();
|
|
1466
|
+
const { selected } = await prompts3({
|
|
1467
|
+
type: "multiselect",
|
|
1468
|
+
name: "selected",
|
|
1469
|
+
message: "Select employees to add",
|
|
1470
|
+
choices,
|
|
1471
|
+
hint: "- Space to select, Enter to confirm",
|
|
1472
|
+
instructions: false
|
|
1473
|
+
});
|
|
1474
|
+
return selected ?? [];
|
|
1475
|
+
} catch (error) {
|
|
1476
|
+
s.fail("Failed to fetch employee list");
|
|
1477
|
+
throw error;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
async function runAdd(options) {
|
|
1481
|
+
const { items, cwd, overwrite, yes } = options;
|
|
1482
|
+
const config = await getConfig(cwd);
|
|
1483
|
+
if (!config) {
|
|
1484
|
+
throw notInitializedError();
|
|
1485
|
+
}
|
|
1486
|
+
let platforms;
|
|
1487
|
+
if (options.platforms && options.platforms.length > 0) {
|
|
1488
|
+
platforms = options.platforms;
|
|
1489
|
+
} else {
|
|
1490
|
+
platforms = [config.defaultPlatform];
|
|
1491
|
+
}
|
|
1492
|
+
for (const platform of platforms) {
|
|
1493
|
+
if (!config.platforms[platform]) {
|
|
1494
|
+
throw platformNotSupportedError(platform);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
const targets = items.map((item) => parseTarget(item));
|
|
1498
|
+
const employees = targets.filter((t) => t.type === "employee");
|
|
1499
|
+
const skills = targets.filter((t) => t.type === "skill");
|
|
1500
|
+
for (const target of employees) {
|
|
1501
|
+
await addEmployee(target, config, cwd, platforms, overwrite, yes);
|
|
1502
|
+
}
|
|
1503
|
+
if (skills.length > 0) {
|
|
1504
|
+
await addSkillsWithDeps(
|
|
1505
|
+
skills,
|
|
1506
|
+
config,
|
|
1507
|
+
cwd,
|
|
1508
|
+
platforms,
|
|
1509
|
+
overwrite,
|
|
1510
|
+
options.yes,
|
|
1511
|
+
options.noDeps
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
logger.break();
|
|
1515
|
+
logger.success("Installation complete!");
|
|
1516
|
+
logger.dim(`Installed to: ${platforms.join(", ")}`);
|
|
1517
|
+
showMCPDependencyHints();
|
|
1518
|
+
showConstraintDocumentHints();
|
|
1519
|
+
}
|
|
1520
|
+
async function addEmployee(target, config, cwd, platforms, overwrite, yes) {
|
|
1521
|
+
if (!config) return;
|
|
1522
|
+
const s = spinner(`Adding ${target.name}...`).start();
|
|
1523
|
+
try {
|
|
1524
|
+
const employee = await fetchEmployee(target.fullName, config, cwd);
|
|
1525
|
+
s.text = `Installing ${employee.name} (${employee.role})...`;
|
|
1526
|
+
const conflicts = await checkConflicts(employee, config, cwd, platforms);
|
|
1527
|
+
let shouldOverwrite = overwrite;
|
|
1528
|
+
if (conflicts.length > 0 && !overwrite) {
|
|
1529
|
+
s.stop();
|
|
1530
|
+
if (!yes) {
|
|
1531
|
+
logger.warn(`Found ${conflicts.length} existing file(s):`);
|
|
1532
|
+
for (const conflict of conflicts.slice(0, 5)) {
|
|
1533
|
+
logger.dim(` - ${conflict.path}`);
|
|
1534
|
+
}
|
|
1535
|
+
if (conflicts.length > 5) {
|
|
1536
|
+
logger.dim(` ... and ${conflicts.length - 5} more`);
|
|
1537
|
+
}
|
|
1538
|
+
const confirmed = await confirmOverwrite(conflicts);
|
|
1539
|
+
if (!confirmed) {
|
|
1540
|
+
logger.info(`Skipped ${target.name}`);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
shouldOverwrite = true;
|
|
1544
|
+
}
|
|
1545
|
+
s.start();
|
|
1546
|
+
}
|
|
1547
|
+
const isUpdate = config.employees[employee.name] !== void 0;
|
|
1548
|
+
if (shouldOverwrite && isUpdate) {
|
|
1549
|
+
await uninstallEmployee(employee.name, config, cwd, platforms);
|
|
1550
|
+
}
|
|
1551
|
+
await installEmployee(employee, config, cwd, platforms, {
|
|
1552
|
+
overwrite: shouldOverwrite
|
|
1553
|
+
});
|
|
1554
|
+
const installedSharedSkills = [];
|
|
1555
|
+
if (employee.dependencies && employee.dependencies.length > 0) {
|
|
1556
|
+
s.text = `Installing shared skills for ${employee.name}...`;
|
|
1557
|
+
for (const depFullName of employee.dependencies) {
|
|
1558
|
+
const alreadyInstalled = await isSharedSkillInstalled(cwd, depFullName);
|
|
1559
|
+
if (!alreadyInstalled) {
|
|
1560
|
+
try {
|
|
1561
|
+
const sharedSkill = await fetchSkill(depFullName, config, cwd);
|
|
1562
|
+
for (const platform of platforms) {
|
|
1563
|
+
await installSkill(sharedSkill, config, cwd, {
|
|
1564
|
+
platform,
|
|
1565
|
+
overwrite: shouldOverwrite
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
installedSharedSkills.push(depFullName);
|
|
1569
|
+
} catch (err) {
|
|
1570
|
+
s.warn(`Failed to install shared skill ${depFullName}`);
|
|
1571
|
+
logger.dim(
|
|
1572
|
+
` Error: ${err instanceof Error ? err.message : String(err)}`
|
|
1573
|
+
);
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
await addSharedSkillReference(
|
|
1578
|
+
cwd,
|
|
1579
|
+
depFullName,
|
|
1580
|
+
employee.name,
|
|
1581
|
+
"1.0.0",
|
|
1582
|
+
// TODO: get version from skill
|
|
1583
|
+
platforms
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const skillNames = employee.skills.map((sk) => sk.name);
|
|
1588
|
+
const commandNames = employee.commands.map((c) => c.name);
|
|
1589
|
+
await updateEmployees(
|
|
1590
|
+
cwd,
|
|
1591
|
+
employee.name,
|
|
1592
|
+
platforms,
|
|
1593
|
+
skillNames,
|
|
1594
|
+
commandNames
|
|
1595
|
+
);
|
|
1596
|
+
s.succeed(`Added ${employee.name} (${employee.role})`);
|
|
1597
|
+
logger.dim(` Skills: ${employee.skills.map((sk) => sk.name).join(", ")}`);
|
|
1598
|
+
if (employee.commands.length > 0) {
|
|
1599
|
+
logger.dim(
|
|
1600
|
+
` Commands: ${employee.commands.map((c) => `/${employee.name}.${c.name}`).join(", ")}`
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
if (installedSharedSkills.length > 0) {
|
|
1604
|
+
logger.dim(
|
|
1605
|
+
` Shared skills: ${installedSharedSkills.map((s2) => s2.split("/").pop()).join(", ")}`
|
|
1606
|
+
);
|
|
1607
|
+
} else if (employee.dependencies && employee.dependencies.length > 0) {
|
|
1608
|
+
logger.dim(
|
|
1609
|
+
` Shared skills: ${employee.dependencies.map((s2) => s2.split("/").pop()).join(", ")} (already installed)`
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
for (const skill of employee.skills) {
|
|
1613
|
+
const skillFile = skill.files.find((f) => f.path.endsWith("SKILL.md"));
|
|
1614
|
+
if (skillFile) {
|
|
1615
|
+
const frontmatter = parseSkillFrontmatter(skillFile.content);
|
|
1616
|
+
if (frontmatter?.mcpDependencies) {
|
|
1617
|
+
for (const dep of frontmatter.mcpDependencies) {
|
|
1618
|
+
collectedMCPDeps.set(dep.name, dep);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
const constraintInfo = await checkConstraintDocs(employee.name, cwd);
|
|
1624
|
+
constraintDocInfo.set(employee.name, constraintInfo);
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
s.fail(`Failed to add ${target.name}`);
|
|
1627
|
+
throw error;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
async function addSkillsWithDeps(targets, config, cwd, platforms, overwrite, yes, noDeps) {
|
|
1631
|
+
if (!config) return;
|
|
1632
|
+
const skillNames = targets.map((t) => t.fullName);
|
|
1633
|
+
if (noDeps) {
|
|
1634
|
+
logger.warn("Skipping dependencies. Some skills may not work correctly.");
|
|
1635
|
+
for (const target of targets) {
|
|
1636
|
+
await addSingleSkill(target.fullName, config, cwd, platforms, overwrite);
|
|
1637
|
+
}
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const s = spinner("Resolving dependencies...").start();
|
|
1641
|
+
try {
|
|
1642
|
+
const resolver = new DependencyResolver(
|
|
1643
|
+
(name) => fetchSkill(name, config, cwd)
|
|
1644
|
+
);
|
|
1645
|
+
const { skills, tree } = await resolver.resolve(skillNames);
|
|
1646
|
+
s.stop();
|
|
1647
|
+
logger.break();
|
|
1648
|
+
logger.log(formatDependencyTree(tree.dependencies[0]));
|
|
1649
|
+
logger.break();
|
|
1650
|
+
const depCount = skills.length - skillNames.length;
|
|
1651
|
+
if (depCount > 0 && !yes) {
|
|
1652
|
+
const { confirmed } = await prompts3({
|
|
1653
|
+
type: "confirm",
|
|
1654
|
+
name: "confirmed",
|
|
1655
|
+
message: `Install ${skills.length} skill(s) (${depCount} dependencies)?`,
|
|
1656
|
+
initial: true
|
|
1657
|
+
});
|
|
1658
|
+
if (!confirmed) {
|
|
1659
|
+
logger.info("Cancelled.");
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
for (const skill of skills) {
|
|
1664
|
+
await addSingleSkill(
|
|
1665
|
+
skill.fullName,
|
|
1666
|
+
config,
|
|
1667
|
+
cwd,
|
|
1668
|
+
platforms,
|
|
1669
|
+
overwrite,
|
|
1670
|
+
skill
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
s.fail("Failed to resolve dependencies");
|
|
1675
|
+
throw error;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async function addSingleSkill(fullName, config, cwd, platforms, overwrite, prefetchedSkill) {
|
|
1679
|
+
if (!config) return;
|
|
1680
|
+
const s = spinner(`Adding skill ${fullName}...`).start();
|
|
1681
|
+
try {
|
|
1682
|
+
const skill = prefetchedSkill ?? await fetchSkill(fullName, config, cwd);
|
|
1683
|
+
s.text = `Installing ${skill.name}...`;
|
|
1684
|
+
for (const platform of platforms) {
|
|
1685
|
+
const result = await installSkill(skill, config, cwd, {
|
|
1686
|
+
platform,
|
|
1687
|
+
overwrite
|
|
1688
|
+
});
|
|
1689
|
+
if (result.skipped && !overwrite) {
|
|
1690
|
+
s.warn(
|
|
1691
|
+
`Skill ${skill.name} already exists (use --overwrite to replace)`
|
|
1692
|
+
);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
await updateSkill(cwd, fullName, skill.version, platforms, "standalone");
|
|
1697
|
+
s.succeed(`Added skill ${skill.fullName}`);
|
|
1698
|
+
logger.dim(` Version: ${skill.version}`);
|
|
1699
|
+
logger.dim(` Category: ${skill.category}`);
|
|
1700
|
+
if (skill.mcpDependencies) {
|
|
1701
|
+
for (const dep of skill.mcpDependencies) {
|
|
1702
|
+
collectedMCPDeps.set(dep.name, dep);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
s.fail(`Failed to add skill ${fullName}`);
|
|
1707
|
+
throw error;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
function showMCPDependencyHints() {
|
|
1711
|
+
if (collectedMCPDeps.size === 0) {
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
logger.break();
|
|
1715
|
+
logger.warn(`Some skills require MCP servers to be configured:`);
|
|
1716
|
+
logger.break();
|
|
1717
|
+
for (const [name, dep] of collectedMCPDeps) {
|
|
1718
|
+
logger.info(` ${name}`);
|
|
1719
|
+
logger.dim(` ${dep.description}`);
|
|
1720
|
+
logger.dim(` Package: ${dep.package}`);
|
|
1721
|
+
logger.dim(` Docs: ${dep.docsUrl}`);
|
|
1722
|
+
logger.break();
|
|
1723
|
+
}
|
|
1724
|
+
logger.info(
|
|
1725
|
+
"To configure MCP servers, add them to your claude_desktop_config.json"
|
|
1726
|
+
);
|
|
1727
|
+
logger.dim("See: https://docs.claude.ai/mcp");
|
|
1728
|
+
collectedMCPDeps.clear();
|
|
1729
|
+
}
|
|
1730
|
+
function showConstraintDocumentHints() {
|
|
1731
|
+
if (constraintDocInfo.size === 0) {
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
for (const [employeeName, info] of constraintDocInfo) {
|
|
1735
|
+
showConstraintDocHints(employeeName, info.existing, info.missing);
|
|
1736
|
+
}
|
|
1737
|
+
constraintDocInfo.clear();
|
|
1738
|
+
}
|
|
1739
|
+
var add = new Command2().name("add").description("Add employees or skills to your project").argument(
|
|
1740
|
+
"[items...]",
|
|
1741
|
+
"Employee or skill names (e.g., pm, @the-aico/pm/brainstorming)"
|
|
1742
|
+
).option(
|
|
1743
|
+
"-p, --platform <platform>",
|
|
1744
|
+
"Target platform (can be used multiple times)",
|
|
1745
|
+
(value, previous) => {
|
|
1746
|
+
const result = platformSchema.safeParse(value);
|
|
1747
|
+
if (!result.success) {
|
|
1748
|
+
throw new Error(
|
|
1749
|
+
`Invalid platform: ${value}. Available: claude-code, codex`
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
return [...previous, result.data];
|
|
1753
|
+
},
|
|
1754
|
+
[]
|
|
1755
|
+
).option("-o, --overwrite", "Overwrite existing files", false).option("-y, --yes", "Skip confirmation prompts", false).option("--no-deps", "Skip dependency installation", false).option("-c, --cwd <cwd>", "Working directory", process.cwd()).action(async (items, opts) => {
|
|
1756
|
+
try {
|
|
1757
|
+
let selectedItems = items;
|
|
1758
|
+
if (items.length === 0) {
|
|
1759
|
+
selectedItems = await selectEmployeesInteractively(opts.cwd);
|
|
1760
|
+
if (selectedItems.length === 0) {
|
|
1761
|
+
logger.info("No employees selected.");
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
const options = addOptionsSchema.parse({
|
|
1766
|
+
items: selectedItems,
|
|
1767
|
+
cwd: opts.cwd,
|
|
1768
|
+
platforms: opts.platform.length > 0 ? opts.platform : void 0,
|
|
1769
|
+
overwrite: opts.overwrite,
|
|
1770
|
+
yes: opts.yes,
|
|
1771
|
+
noDeps: opts.noDeps ?? false
|
|
1772
|
+
});
|
|
1773
|
+
await runAdd(options);
|
|
1774
|
+
} catch (error) {
|
|
1775
|
+
handleError(error);
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
// src/commands/remove.ts
|
|
1780
|
+
import { Command as Command3 } from "commander";
|
|
1781
|
+
import prompts4 from "prompts";
|
|
1782
|
+
import { z as z7 } from "zod";
|
|
1783
|
+
var removeOptionsSchema = z7.object({
|
|
1784
|
+
items: z7.array(z7.string()),
|
|
1785
|
+
cwd: z7.string(),
|
|
1786
|
+
platforms: z7.array(platformSchema).optional(),
|
|
1787
|
+
yes: z7.boolean(),
|
|
1788
|
+
force: z7.boolean(),
|
|
1789
|
+
dryRun: z7.boolean()
|
|
1790
|
+
});
|
|
1791
|
+
async function runRemove(options) {
|
|
1792
|
+
const { items, cwd, yes, force, dryRun } = options;
|
|
1793
|
+
const config = await getConfig(cwd);
|
|
1794
|
+
if (!config) {
|
|
1795
|
+
throw notInitializedError();
|
|
1796
|
+
}
|
|
1797
|
+
const targets = items.map((item) => parseTarget(item));
|
|
1798
|
+
const employees = targets.filter((t) => t.type === "employee");
|
|
1799
|
+
const skills = targets.filter((t) => t.type === "skill");
|
|
1800
|
+
for (const target of employees) {
|
|
1801
|
+
await removeEmployeeItem(
|
|
1802
|
+
target,
|
|
1803
|
+
config,
|
|
1804
|
+
cwd,
|
|
1805
|
+
options.platforms,
|
|
1806
|
+
yes,
|
|
1807
|
+
dryRun
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
for (const target of skills) {
|
|
1811
|
+
await removeSkillItem(
|
|
1812
|
+
target,
|
|
1813
|
+
config,
|
|
1814
|
+
cwd,
|
|
1815
|
+
options.platforms,
|
|
1816
|
+
yes,
|
|
1817
|
+
force,
|
|
1818
|
+
dryRun
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
logger.break();
|
|
1822
|
+
if (dryRun) {
|
|
1823
|
+
logger.info("Dry run complete. No files were removed.");
|
|
1824
|
+
} else {
|
|
1825
|
+
logger.success("Removal complete!");
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
async function removeEmployeeItem(target, config, cwd, platformsOpt, yes, dryRun) {
|
|
1829
|
+
if (!config) return;
|
|
1830
|
+
const employeeName = target.name;
|
|
1831
|
+
const installedState = config.employees[employeeName];
|
|
1832
|
+
let platforms;
|
|
1833
|
+
if (installedState) {
|
|
1834
|
+
if (platformsOpt && platformsOpt.length > 0) {
|
|
1835
|
+
platforms = platformsOpt.filter(
|
|
1836
|
+
(p) => installedState.platforms.includes(p)
|
|
1837
|
+
);
|
|
1838
|
+
if (platforms.length === 0) {
|
|
1839
|
+
logger.warn(
|
|
1840
|
+
`Employee '${employeeName}' is not installed on specified platform(s). Skipping.`
|
|
1841
|
+
);
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
} else {
|
|
1845
|
+
platforms = installedState.platforms;
|
|
1846
|
+
}
|
|
1847
|
+
} else {
|
|
1848
|
+
if (platformsOpt && platformsOpt.length > 0) {
|
|
1849
|
+
platforms = platformsOpt;
|
|
1850
|
+
} else {
|
|
1851
|
+
platforms = Object.keys(config.platforms);
|
|
1852
|
+
}
|
|
1853
|
+
logger.dim(
|
|
1854
|
+
`Employee '${employeeName}' not in config, will attempt to remove files from: ${platforms.join(", ")}`
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
if (!yes && !dryRun) {
|
|
1858
|
+
const { proceed } = await prompts4({
|
|
1859
|
+
type: "confirm",
|
|
1860
|
+
name: "proceed",
|
|
1861
|
+
message: `Remove ${employeeName} from ${platforms.join(", ")}?`,
|
|
1862
|
+
initial: true
|
|
1863
|
+
});
|
|
1864
|
+
if (!proceed) {
|
|
1865
|
+
logger.info(`Skipped ${employeeName}`);
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (dryRun) {
|
|
1870
|
+
logger.info(`Would remove employee: ${employeeName}`);
|
|
1871
|
+
logger.dim(` Platforms: ${platforms.join(", ")}`);
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const s = spinner(`Removing ${employeeName}...`).start();
|
|
1875
|
+
try {
|
|
1876
|
+
await uninstallEmployee(employeeName, config, cwd, platforms);
|
|
1877
|
+
let employeeDeps = [];
|
|
1878
|
+
try {
|
|
1879
|
+
const employee = await fetchEmployee(target.fullName, config, cwd);
|
|
1880
|
+
employeeDeps = employee.dependencies || [];
|
|
1881
|
+
} catch {
|
|
1882
|
+
if (config.sharedSkills) {
|
|
1883
|
+
for (const [skillName, state] of Object.entries(config.sharedSkills)) {
|
|
1884
|
+
if (state.usedBy.includes(employeeName)) {
|
|
1885
|
+
employeeDeps.push(skillName);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
const removedSharedSkills = [];
|
|
1891
|
+
const keptSharedSkills = [];
|
|
1892
|
+
for (const depFullName of employeeDeps) {
|
|
1893
|
+
const { shouldUninstall, remainingUsers } = await removeSharedSkillReference(cwd, depFullName, employeeName);
|
|
1894
|
+
if (shouldUninstall) {
|
|
1895
|
+
for (const platform of platforms) {
|
|
1896
|
+
await uninstallSkill(depFullName, config, cwd, { platform });
|
|
1897
|
+
}
|
|
1898
|
+
removedSharedSkills.push(depFullName);
|
|
1899
|
+
} else if (remainingUsers.length > 0) {
|
|
1900
|
+
keptSharedSkills.push({ name: depFullName, users: remainingUsers });
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
if (installedState) {
|
|
1904
|
+
await removeEmployee(cwd, employeeName);
|
|
1905
|
+
}
|
|
1906
|
+
s.succeed(`Removed ${employeeName}`);
|
|
1907
|
+
logger.dim(` Removed from: ${platforms.join(", ")}`);
|
|
1908
|
+
if (removedSharedSkills.length > 0) {
|
|
1909
|
+
logger.dim(
|
|
1910
|
+
` Removed shared skills: ${removedSharedSkills.map((s2) => s2.split("/").pop()).join(", ")}`
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
if (keptSharedSkills.length > 0) {
|
|
1914
|
+
for (const { name, users } of keptSharedSkills) {
|
|
1915
|
+
logger.dim(
|
|
1916
|
+
` Kept shared skill '${name.split("/").pop()}' (still used by: ${users.join(", ")})`
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
} catch (error) {
|
|
1921
|
+
s.fail(`Failed to remove ${employeeName}`);
|
|
1922
|
+
throw error;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
async function removeSkillItem(target, config, cwd, platformsOpt, yes, force, dryRun) {
|
|
1926
|
+
if (!config) return;
|
|
1927
|
+
const skillFullName = target.fullName;
|
|
1928
|
+
const installedState = config.skills?.[skillFullName];
|
|
1929
|
+
let parentEmployee = null;
|
|
1930
|
+
for (const [empName, empState] of Object.entries(config.employees)) {
|
|
1931
|
+
if (empState.skills?.includes(target.name)) {
|
|
1932
|
+
parentEmployee = empName;
|
|
1933
|
+
break;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
if (parentEmployee && !force) {
|
|
1937
|
+
logger.warn(
|
|
1938
|
+
`Skill '${skillFullName}' is part of employee '${parentEmployee}'.`
|
|
1939
|
+
);
|
|
1940
|
+
logger.dim(
|
|
1941
|
+
"Use --force to remove it anyway, or remove the entire employee."
|
|
1942
|
+
);
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
let platforms;
|
|
1946
|
+
if (installedState) {
|
|
1947
|
+
if (platformsOpt && platformsOpt.length > 0) {
|
|
1948
|
+
platforms = platformsOpt.filter(
|
|
1949
|
+
(p) => installedState.platforms.includes(p)
|
|
1950
|
+
);
|
|
1951
|
+
if (platforms.length === 0) {
|
|
1952
|
+
logger.warn(
|
|
1953
|
+
`Skill '${skillFullName}' is not installed on specified platform(s). Skipping.`
|
|
1954
|
+
);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
} else {
|
|
1958
|
+
platforms = installedState.platforms;
|
|
1959
|
+
}
|
|
1960
|
+
} else {
|
|
1961
|
+
if (platformsOpt && platformsOpt.length > 0) {
|
|
1962
|
+
platforms = platformsOpt;
|
|
1963
|
+
} else {
|
|
1964
|
+
platforms = [config.defaultPlatform];
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
if (!yes && !dryRun) {
|
|
1968
|
+
const { proceed } = await prompts4({
|
|
1969
|
+
type: "confirm",
|
|
1970
|
+
name: "proceed",
|
|
1971
|
+
message: `Remove skill ${skillFullName} from ${platforms.join(", ")}?`,
|
|
1972
|
+
initial: true
|
|
1973
|
+
});
|
|
1974
|
+
if (!proceed) {
|
|
1975
|
+
logger.info(`Skipped ${skillFullName}`);
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
if (dryRun) {
|
|
1980
|
+
for (const platform of platforms) {
|
|
1981
|
+
const result = await uninstallSkill(skillFullName, config, cwd, {
|
|
1982
|
+
platform,
|
|
1983
|
+
dryRun: true
|
|
1984
|
+
});
|
|
1985
|
+
logger.info(`Would remove skill: ${skillFullName}`);
|
|
1986
|
+
logger.dim(` Platform: ${platform}`);
|
|
1987
|
+
logger.dim(` Files: ${result.files.length}`);
|
|
1988
|
+
for (const file of result.files) {
|
|
1989
|
+
logger.dim(` - ${file}`);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
const s = spinner(`Removing skill ${skillFullName}...`).start();
|
|
1995
|
+
try {
|
|
1996
|
+
for (const platform of platforms) {
|
|
1997
|
+
const result = await uninstallSkill(skillFullName, config, cwd, {
|
|
1998
|
+
platform
|
|
1999
|
+
});
|
|
2000
|
+
if (!result.removed) {
|
|
2001
|
+
logger.dim(` Skill not found on ${platform}`);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
if (installedState) {
|
|
2005
|
+
await removeSkill(cwd, skillFullName);
|
|
2006
|
+
}
|
|
2007
|
+
s.succeed(`Removed skill ${skillFullName}`);
|
|
2008
|
+
logger.dim(` Removed from: ${platforms.join(", ")}`);
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
s.fail(`Failed to remove skill ${skillFullName}`);
|
|
2011
|
+
throw error;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
var remove = new Command3().name("remove").description("Remove employees or skills from your project").argument("[items...]", "Employee or skill names to remove").option(
|
|
2015
|
+
"-p, --platform <platform>",
|
|
2016
|
+
"Remove from specific platform only",
|
|
2017
|
+
(value, previous) => {
|
|
2018
|
+
const result = platformSchema.safeParse(value);
|
|
2019
|
+
if (!result.success) {
|
|
2020
|
+
throw new Error(
|
|
2021
|
+
`Invalid platform: ${value}. Available: claude-code, codex`
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
return [...previous, result.data];
|
|
2025
|
+
},
|
|
2026
|
+
[]
|
|
2027
|
+
).option("-y, --yes", "Skip confirmation prompts", false).option("-f, --force", "Force remove (ignore dependency warnings)", false).option("--dry-run", "Preview changes without removing", false).option("-c, --cwd <cwd>", "Working directory", process.cwd()).action(async (items, opts) => {
|
|
2028
|
+
try {
|
|
2029
|
+
if (items.length === 0) {
|
|
2030
|
+
logger.error(
|
|
2031
|
+
"Please specify at least one employee or skill to remove."
|
|
2032
|
+
);
|
|
2033
|
+
logger.dim("Examples:");
|
|
2034
|
+
logger.dim(
|
|
2035
|
+
" npx aico remove pm # Remove employee"
|
|
2036
|
+
);
|
|
2037
|
+
logger.dim(
|
|
2038
|
+
" npx aico remove @the-aico/pm/brainstorming # Remove skill"
|
|
2039
|
+
);
|
|
2040
|
+
process.exit(1);
|
|
2041
|
+
}
|
|
2042
|
+
const options = removeOptionsSchema.parse({
|
|
2043
|
+
items,
|
|
2044
|
+
cwd: opts.cwd,
|
|
2045
|
+
platforms: opts.platform.length > 0 ? opts.platform : void 0,
|
|
2046
|
+
yes: opts.yes,
|
|
2047
|
+
force: opts.force,
|
|
2048
|
+
dryRun: opts.dryRun
|
|
2049
|
+
});
|
|
2050
|
+
await runRemove(options);
|
|
2051
|
+
} catch (error) {
|
|
2052
|
+
handleError(error);
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
// src/commands/list.ts
|
|
2057
|
+
import { Command as Command4 } from "commander";
|
|
2058
|
+
import { z as z8 } from "zod";
|
|
2059
|
+
var listOptionsSchema = z8.object({
|
|
2060
|
+
cwd: z8.string(),
|
|
2061
|
+
installed: z8.boolean()
|
|
2062
|
+
});
|
|
2063
|
+
async function runList(options) {
|
|
2064
|
+
const { cwd, installed } = options;
|
|
2065
|
+
const config = await getConfig(cwd);
|
|
2066
|
+
if (!config) {
|
|
2067
|
+
throw notInitializedError();
|
|
2068
|
+
}
|
|
2069
|
+
if (installed) {
|
|
2070
|
+
const employees = Object.entries(config.employees);
|
|
2071
|
+
if (employees.length === 0) {
|
|
2072
|
+
logger.info("No employees installed.");
|
|
2073
|
+
logger.dim("Run `npx aico add <employee>` to add an employee.");
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
logger.bold("Installed employees:");
|
|
2077
|
+
logger.break();
|
|
2078
|
+
for (const [name, state] of employees) {
|
|
2079
|
+
const platforms = state.platforms.join(", ");
|
|
2080
|
+
const date = new Date(state.installedAt).toLocaleDateString();
|
|
2081
|
+
logger.log(
|
|
2082
|
+
` ${logger.highlight(name.padEnd(15))} ${platforms.padEnd(20)} ${date}`
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
} else {
|
|
2086
|
+
logger.info("Fetching employee list...");
|
|
2087
|
+
try {
|
|
2088
|
+
const index = await fetchRegistryIndex(config, cwd);
|
|
2089
|
+
if (index.employees.length === 0) {
|
|
2090
|
+
logger.warn("No employees found in registry.");
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
logger.break();
|
|
2094
|
+
logger.bold("Available employees:");
|
|
2095
|
+
logger.break();
|
|
2096
|
+
for (const employee of index.employees) {
|
|
2097
|
+
const installed2 = config.employees[employee.name] ? "\u2713" : " ";
|
|
2098
|
+
const desc = employee.description ?? employee.role;
|
|
2099
|
+
logger.log(
|
|
2100
|
+
` ${installed2} ${logger.highlight(employee.name.padEnd(15))} ${desc}`
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
logger.break();
|
|
2104
|
+
logger.dim("Use `npx aico add <employee>` to add an employee.");
|
|
2105
|
+
logger.dim("Use `npx aico list --installed` to see installed employees.");
|
|
2106
|
+
} catch {
|
|
2107
|
+
logger.warn(
|
|
2108
|
+
"Could not fetch registry. Showing installed employees only."
|
|
2109
|
+
);
|
|
2110
|
+
logger.break();
|
|
2111
|
+
const employees = Object.entries(config.employees);
|
|
2112
|
+
if (employees.length === 0) {
|
|
2113
|
+
logger.info("No employees installed.");
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
for (const [name, state] of employees) {
|
|
2117
|
+
logger.log(
|
|
2118
|
+
` ${logger.highlight(name.padEnd(15))} ${state.platforms.join(", ")}`
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
var list = new Command4().name("list").description("List available or installed employees").option("-i, --installed", "Show only installed employees", false).option("-c, --cwd <cwd>", "Working directory", process.cwd()).action(async (opts) => {
|
|
2125
|
+
try {
|
|
2126
|
+
const options = listOptionsSchema.parse({
|
|
2127
|
+
cwd: opts.cwd,
|
|
2128
|
+
installed: opts.installed
|
|
2129
|
+
});
|
|
2130
|
+
await runList(options);
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
handleError(error);
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
// src/commands/build.ts
|
|
2137
|
+
import { Command as Command5 } from "commander";
|
|
2138
|
+
import fs6 from "fs-extra";
|
|
2139
|
+
import path7 from "path";
|
|
2140
|
+
import glob from "fast-glob";
|
|
2141
|
+
import { z as z9 } from "zod";
|
|
2142
|
+
var buildOptionsSchema = z9.object({
|
|
2143
|
+
cwd: z9.string(),
|
|
2144
|
+
employeesDir: z9.string(),
|
|
2145
|
+
outputDir: z9.string(),
|
|
2146
|
+
registry: z9.string(),
|
|
2147
|
+
version: z9.string()
|
|
2148
|
+
});
|
|
2149
|
+
function inferCategory(employeeName) {
|
|
2150
|
+
const categoryMap = {
|
|
2151
|
+
pm: "pm",
|
|
2152
|
+
frontend: "frontend",
|
|
2153
|
+
backend: "backend",
|
|
2154
|
+
devops: "devops",
|
|
2155
|
+
_shared: "general"
|
|
2156
|
+
};
|
|
2157
|
+
return categoryMap[employeeName] ?? "general";
|
|
2158
|
+
}
|
|
2159
|
+
async function buildSharedSkills(employeesPath, outputPath, registry, version, skillsIndex) {
|
|
2160
|
+
const sharedDir = path7.join(employeesPath, "_shared", "skills");
|
|
2161
|
+
if (!await fs6.pathExists(sharedDir)) {
|
|
2162
|
+
return 0;
|
|
2163
|
+
}
|
|
2164
|
+
const skillDirs = await glob("*/SKILL.md", {
|
|
2165
|
+
cwd: sharedDir
|
|
2166
|
+
});
|
|
2167
|
+
if (skillDirs.length === 0) {
|
|
2168
|
+
return 0;
|
|
2169
|
+
}
|
|
2170
|
+
const namespace = `${registry}/_shared`;
|
|
2171
|
+
for (const skillPath of skillDirs) {
|
|
2172
|
+
const skillName = path7.dirname(skillPath);
|
|
2173
|
+
const skillFullName = `${namespace}/${skillName}`;
|
|
2174
|
+
const skillFilePath = path7.join(sharedDir, skillPath);
|
|
2175
|
+
const content = await fs6.readFile(skillFilePath, "utf-8");
|
|
2176
|
+
const frontmatter = parseSkillFrontmatter(content);
|
|
2177
|
+
if (!frontmatter) {
|
|
2178
|
+
logger.warn(`Shared skill ${skillName} missing frontmatter, skipping`);
|
|
2179
|
+
continue;
|
|
2180
|
+
}
|
|
2181
|
+
const fullSkill = {
|
|
2182
|
+
name: skillName,
|
|
2183
|
+
namespace,
|
|
2184
|
+
fullName: skillFullName,
|
|
2185
|
+
version,
|
|
2186
|
+
description: frontmatter.description,
|
|
2187
|
+
category: "general",
|
|
2188
|
+
tags: [],
|
|
2189
|
+
dependencies: [],
|
|
2190
|
+
files: [
|
|
2191
|
+
{
|
|
2192
|
+
path: "SKILL.md",
|
|
2193
|
+
type: "skill",
|
|
2194
|
+
content
|
|
2195
|
+
}
|
|
2196
|
+
]
|
|
2197
|
+
};
|
|
2198
|
+
const skillOutputDir = path7.join(
|
|
2199
|
+
outputPath,
|
|
2200
|
+
"skills",
|
|
2201
|
+
registry.replace("@", ""),
|
|
2202
|
+
"_shared"
|
|
2203
|
+
);
|
|
2204
|
+
await fs6.ensureDir(skillOutputDir);
|
|
2205
|
+
await fs6.writeJson(
|
|
2206
|
+
path7.join(skillOutputDir, `${skillName}.json`),
|
|
2207
|
+
fullSkill,
|
|
2208
|
+
{ spaces: 2 }
|
|
2209
|
+
);
|
|
2210
|
+
skillsIndex.push({
|
|
2211
|
+
name: skillName,
|
|
2212
|
+
namespace,
|
|
2213
|
+
fullName: skillFullName,
|
|
2214
|
+
version,
|
|
2215
|
+
description: frontmatter.description,
|
|
2216
|
+
category: "general",
|
|
2217
|
+
tags: []
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
return skillDirs.length;
|
|
2221
|
+
}
|
|
2222
|
+
async function runBuild(options) {
|
|
2223
|
+
const { cwd, employeesDir, outputDir, registry, version } = options;
|
|
2224
|
+
const employeesPath = path7.resolve(cwd, employeesDir);
|
|
2225
|
+
const outputPath = path7.resolve(cwd, outputDir);
|
|
2226
|
+
if (await fs6.pathExists(outputPath)) {
|
|
2227
|
+
await fs6.emptyDir(outputPath);
|
|
2228
|
+
}
|
|
2229
|
+
const employeeFiles = await glob("*/employee.json", {
|
|
2230
|
+
cwd: employeesPath,
|
|
2231
|
+
ignore: ["_template/**"]
|
|
2232
|
+
});
|
|
2233
|
+
if (employeeFiles.length === 0) {
|
|
2234
|
+
logger.warn("No employees found to build.");
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
const s = spinner(`Building ${employeeFiles.length} employee(s)...`).start();
|
|
2238
|
+
const skillsIndex = [];
|
|
2239
|
+
const employeesIndex = [];
|
|
2240
|
+
const legacyIndex = [];
|
|
2241
|
+
for (const employeeFile of employeeFiles) {
|
|
2242
|
+
const employeeName = path7.dirname(employeeFile);
|
|
2243
|
+
const employeeDir = path7.join(employeesPath, employeeName);
|
|
2244
|
+
try {
|
|
2245
|
+
const employeeJson = await fs6.readJson(
|
|
2246
|
+
path7.join(employeeDir, "employee.json")
|
|
2247
|
+
);
|
|
2248
|
+
const employeeSource = employeeSourceSchema.parse(employeeJson);
|
|
2249
|
+
const category = inferCategory(employeeName);
|
|
2250
|
+
const namespace = `${registry}/${employeeName}`;
|
|
2251
|
+
const employeeFullName = `${registry}/${employeeName}`;
|
|
2252
|
+
const employee = {
|
|
2253
|
+
...employeeSource,
|
|
2254
|
+
skills: employeeSource.skills.map((sk) => ({
|
|
2255
|
+
...sk,
|
|
2256
|
+
files: sk.files.map((f) => ({ ...f, content: "" }))
|
|
2257
|
+
})),
|
|
2258
|
+
commands: employeeSource.commands.map((c) => ({
|
|
2259
|
+
...c,
|
|
2260
|
+
files: c.files.map((f) => ({ ...f, content: "" }))
|
|
2261
|
+
})),
|
|
2262
|
+
docs: employeeSource.docs.map((d) => ({
|
|
2263
|
+
...d,
|
|
2264
|
+
files: d.files.map((f) => ({ ...f, content: "" }))
|
|
2265
|
+
}))
|
|
2266
|
+
};
|
|
2267
|
+
const skillFullNames = [];
|
|
2268
|
+
for (const skill of employee.skills) {
|
|
2269
|
+
const skillFullName = `${namespace}/${skill.name}`;
|
|
2270
|
+
skillFullNames.push(skillFullName);
|
|
2271
|
+
const skillFiles = [];
|
|
2272
|
+
let skillDescription = "";
|
|
2273
|
+
for (const file of skill.files) {
|
|
2274
|
+
const filePath = path7.join(employeeDir, file.path);
|
|
2275
|
+
if (await fs6.pathExists(filePath)) {
|
|
2276
|
+
const content = await fs6.readFile(filePath, "utf-8");
|
|
2277
|
+
file.content = content;
|
|
2278
|
+
if (file.path.endsWith("SKILL.md")) {
|
|
2279
|
+
const frontmatter = parseSkillFrontmatter(content);
|
|
2280
|
+
if (frontmatter) {
|
|
2281
|
+
skillDescription = frontmatter.description;
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
skillFiles.push({
|
|
2285
|
+
path: path7.basename(file.path),
|
|
2286
|
+
type: file.type === "skill" ? "skill" : "reference",
|
|
2287
|
+
content
|
|
2288
|
+
});
|
|
2289
|
+
} else {
|
|
2290
|
+
throw new Error(`Skill file not found: ${file.path}`);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
const fullSkill = {
|
|
2294
|
+
name: skill.name,
|
|
2295
|
+
namespace,
|
|
2296
|
+
fullName: skillFullName,
|
|
2297
|
+
version,
|
|
2298
|
+
description: skillDescription,
|
|
2299
|
+
category,
|
|
2300
|
+
tags: [],
|
|
2301
|
+
dependencies: [],
|
|
2302
|
+
files: skillFiles
|
|
2303
|
+
};
|
|
2304
|
+
const skillOutputDir = path7.join(
|
|
2305
|
+
outputPath,
|
|
2306
|
+
"skills",
|
|
2307
|
+
registry.replace("@", ""),
|
|
2308
|
+
employeeName
|
|
2309
|
+
);
|
|
2310
|
+
await fs6.ensureDir(skillOutputDir);
|
|
2311
|
+
await fs6.writeJson(
|
|
2312
|
+
path7.join(skillOutputDir, `${skill.name}.json`),
|
|
2313
|
+
fullSkill,
|
|
2314
|
+
{ spaces: 2 }
|
|
2315
|
+
);
|
|
2316
|
+
skillsIndex.push({
|
|
2317
|
+
name: skill.name,
|
|
2318
|
+
namespace,
|
|
2319
|
+
fullName: skillFullName,
|
|
2320
|
+
version,
|
|
2321
|
+
description: skillDescription,
|
|
2322
|
+
category,
|
|
2323
|
+
tags: []
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
for (const command of employee.commands) {
|
|
2327
|
+
for (const file of command.files) {
|
|
2328
|
+
const filePath = path7.join(employeeDir, file.path);
|
|
2329
|
+
if (await fs6.pathExists(filePath)) {
|
|
2330
|
+
file.content = await fs6.readFile(filePath, "utf-8");
|
|
2331
|
+
} else {
|
|
2332
|
+
throw new Error(`Command file not found: ${file.path}`);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
for (const doc of employee.docs) {
|
|
2337
|
+
for (const file of doc.files) {
|
|
2338
|
+
const filePath = path7.join(employeeDir, file.path);
|
|
2339
|
+
if (await fs6.pathExists(filePath)) {
|
|
2340
|
+
file.content = await fs6.readFile(filePath, "utf-8");
|
|
2341
|
+
} else {
|
|
2342
|
+
throw new Error(`Doc file not found: ${file.path}`);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
const legacyOutputPath = path7.join(outputPath, `${employee.name}.json`);
|
|
2347
|
+
await fs6.ensureDir(outputPath);
|
|
2348
|
+
await fs6.writeJson(legacyOutputPath, employee, { spaces: 2 });
|
|
2349
|
+
const employeeExtended = {
|
|
2350
|
+
$schema: "https://the-aico.com/schema/employee.json",
|
|
2351
|
+
name: employee.name,
|
|
2352
|
+
namespace: registry,
|
|
2353
|
+
fullName: employeeFullName,
|
|
2354
|
+
role: employee.role,
|
|
2355
|
+
description: employee.description,
|
|
2356
|
+
version,
|
|
2357
|
+
category,
|
|
2358
|
+
skills: skillFullNames,
|
|
2359
|
+
commands: employee.commands,
|
|
2360
|
+
docs: employee.docs
|
|
2361
|
+
};
|
|
2362
|
+
const employeesOutputDir = path7.join(outputPath, "employees");
|
|
2363
|
+
await fs6.ensureDir(employeesOutputDir);
|
|
2364
|
+
await fs6.writeJson(
|
|
2365
|
+
path7.join(employeesOutputDir, `${employee.name}.json`),
|
|
2366
|
+
employeeExtended,
|
|
2367
|
+
{ spaces: 2 }
|
|
2368
|
+
);
|
|
2369
|
+
employeesIndex.push({
|
|
2370
|
+
name: employee.name,
|
|
2371
|
+
namespace: registry,
|
|
2372
|
+
fullName: employeeFullName,
|
|
2373
|
+
role: employee.role,
|
|
2374
|
+
description: employee.description,
|
|
2375
|
+
version,
|
|
2376
|
+
skillCount: employee.skills.length,
|
|
2377
|
+
commandCount: employee.commands.length,
|
|
2378
|
+
category
|
|
2379
|
+
});
|
|
2380
|
+
legacyIndex.push({
|
|
2381
|
+
name: employee.name,
|
|
2382
|
+
role: employee.role,
|
|
2383
|
+
description: employee.description
|
|
2384
|
+
});
|
|
2385
|
+
s.text = `Built ${employee.name}`;
|
|
2386
|
+
} catch (error) {
|
|
2387
|
+
s.fail(`Failed to build ${employeeName}`);
|
|
2388
|
+
throw error;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
s.text = "Building shared skills...";
|
|
2392
|
+
const sharedSkillCount = await buildSharedSkills(
|
|
2393
|
+
employeesPath,
|
|
2394
|
+
outputPath,
|
|
2395
|
+
registry,
|
|
2396
|
+
version,
|
|
2397
|
+
skillsIndex
|
|
2398
|
+
);
|
|
2399
|
+
if (sharedSkillCount > 0) {
|
|
2400
|
+
s.text = `Built ${sharedSkillCount} shared skill(s)`;
|
|
2401
|
+
}
|
|
2402
|
+
const skillsIndexPath = path7.join(outputPath, "skills", "index.json");
|
|
2403
|
+
await fs6.ensureDir(path7.dirname(skillsIndexPath));
|
|
2404
|
+
await fs6.writeJson(skillsIndexPath, skillsIndex, { spaces: 2 });
|
|
2405
|
+
const employeesIndexPath = path7.join(outputPath, "employees", "index.json");
|
|
2406
|
+
await fs6.ensureDir(path7.dirname(employeesIndexPath));
|
|
2407
|
+
await fs6.writeJson(employeesIndexPath, employeesIndex, { spaces: 2 });
|
|
2408
|
+
const legacyIndexPath = path7.join(outputPath, "index.json");
|
|
2409
|
+
await fs6.writeJson(
|
|
2410
|
+
legacyIndexPath,
|
|
2411
|
+
{ employees: legacyIndex },
|
|
2412
|
+
{ spaces: 2 }
|
|
2413
|
+
);
|
|
2414
|
+
s.succeed(`Built ${employeeFiles.length} employee(s) to ${outputDir}/`);
|
|
2415
|
+
logger.break();
|
|
2416
|
+
logger.success("Registry build complete!");
|
|
2417
|
+
logger.dim(` Skills: ${skillsIndex.length} (${sharedSkillCount} shared)`);
|
|
2418
|
+
logger.dim(` Employees: ${employeesIndex.length}`);
|
|
2419
|
+
logger.break();
|
|
2420
|
+
for (const emp of employeesIndex) {
|
|
2421
|
+
logger.dim(` - ${emp.name}: ${emp.role} (${emp.skillCount} skills)`);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
var build = new Command5().name("build").description("Build registry from employees directory").option(
|
|
2425
|
+
"-e, --employees-dir <dir>",
|
|
2426
|
+
"Employees source directory",
|
|
2427
|
+
"employees"
|
|
2428
|
+
).option("-o, --output-dir <dir>", "Output directory", "registry").option("-r, --registry <name>", "Registry namespace", "@the-aico").option("-v, --version <version>", "Version number", "1.0.0").option("-c, --cwd <cwd>", "Working directory", process.cwd()).action(async (opts) => {
|
|
2429
|
+
try {
|
|
2430
|
+
const options = buildOptionsSchema.parse({
|
|
2431
|
+
cwd: opts.cwd,
|
|
2432
|
+
employeesDir: opts.employeesDir,
|
|
2433
|
+
outputDir: opts.outputDir,
|
|
2434
|
+
registry: opts.registry,
|
|
2435
|
+
version: opts.version
|
|
2436
|
+
});
|
|
2437
|
+
await runBuild(options);
|
|
2438
|
+
} catch (error) {
|
|
2439
|
+
handleError(error);
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
|
|
2443
|
+
// src/commands/diff.ts
|
|
2444
|
+
import fs7 from "fs-extra";
|
|
2445
|
+
import path8 from "path";
|
|
2446
|
+
import { Command as Command6 } from "commander";
|
|
2447
|
+
import { diffLines } from "diff";
|
|
2448
|
+
import { z as z10 } from "zod";
|
|
2449
|
+
var adapters2 = {
|
|
2450
|
+
"claude-code": claudeCodeAdapter,
|
|
2451
|
+
codex: codexAdapter
|
|
2452
|
+
};
|
|
2453
|
+
var diffOptionsSchema = z10.object({
|
|
2454
|
+
employee: z10.string().optional(),
|
|
2455
|
+
cwd: z10.string()
|
|
2456
|
+
});
|
|
2457
|
+
async function compareFile(localPath, registryContent) {
|
|
2458
|
+
if (!await fs7.pathExists(localPath)) {
|
|
2459
|
+
return null;
|
|
2460
|
+
}
|
|
2461
|
+
const localContent = await fs7.readFile(localPath, "utf-8");
|
|
2462
|
+
const patch = diffLines(localContent, registryContent);
|
|
2463
|
+
const hasChanges = patch.some((part) => part.added || part.removed);
|
|
2464
|
+
return hasChanges ? patch : null;
|
|
2465
|
+
}
|
|
2466
|
+
async function diffEmployee(employeeName, cwd, config) {
|
|
2467
|
+
if (!config) return null;
|
|
2468
|
+
const installedState = config.employees[employeeName];
|
|
2469
|
+
if (!installedState) {
|
|
2470
|
+
return null;
|
|
2471
|
+
}
|
|
2472
|
+
let registryEmployee;
|
|
2473
|
+
try {
|
|
2474
|
+
registryEmployee = await fetchEmployee(employeeName, config, cwd);
|
|
2475
|
+
} catch {
|
|
2476
|
+
return null;
|
|
2477
|
+
}
|
|
2478
|
+
const platform = installedState.platforms[0] || config.defaultPlatform;
|
|
2479
|
+
const adapter = adapters2[platform];
|
|
2480
|
+
const { skillsDir, commandsDir } = resolvePlatformPaths(
|
|
2481
|
+
cwd,
|
|
2482
|
+
config,
|
|
2483
|
+
platform
|
|
2484
|
+
);
|
|
2485
|
+
const installedSkills = installedState.skills || [];
|
|
2486
|
+
const installedCommands = installedState.commands || [];
|
|
2487
|
+
const registrySkills = registryEmployee.skills.map((s) => s.name);
|
|
2488
|
+
const registryCommands = registryEmployee.commands.map((c) => c.name);
|
|
2489
|
+
const skillsAdded = registrySkills.filter(
|
|
2490
|
+
(s) => !installedSkills.includes(s)
|
|
2491
|
+
);
|
|
2492
|
+
const skillsRemoved = installedSkills.filter(
|
|
2493
|
+
(s) => !registrySkills.includes(s)
|
|
2494
|
+
);
|
|
2495
|
+
const commandsAdded = registryCommands.filter(
|
|
2496
|
+
(c) => !installedCommands.includes(c)
|
|
2497
|
+
);
|
|
2498
|
+
const commandsRemoved = installedCommands.filter(
|
|
2499
|
+
(c) => !registryCommands.includes(c)
|
|
2500
|
+
);
|
|
2501
|
+
const fileChanges = [];
|
|
2502
|
+
for (const skill of registryEmployee.skills) {
|
|
2503
|
+
const skillDirName = adapter.getSkillDirName(employeeName, skill.name);
|
|
2504
|
+
for (const file of skill.files) {
|
|
2505
|
+
const localPath = path8.join(
|
|
2506
|
+
skillsDir,
|
|
2507
|
+
skillDirName,
|
|
2508
|
+
path8.basename(file.path)
|
|
2509
|
+
);
|
|
2510
|
+
let registryContent = file.content;
|
|
2511
|
+
if (file.path.endsWith("SKILL.md")) {
|
|
2512
|
+
registryContent = registryContent.replace(
|
|
2513
|
+
/^(---\n[\s\S]*?name:\s*).+$/m,
|
|
2514
|
+
`$1${skillDirName}`
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
if (!await fs7.pathExists(localPath)) {
|
|
2518
|
+
if (!skillsAdded.includes(skill.name)) {
|
|
2519
|
+
fileChanges.push({
|
|
2520
|
+
filePath: localPath,
|
|
2521
|
+
type: "added"
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
} else {
|
|
2525
|
+
const patch = await compareFile(localPath, registryContent);
|
|
2526
|
+
if (patch) {
|
|
2527
|
+
fileChanges.push({
|
|
2528
|
+
filePath: localPath,
|
|
2529
|
+
type: "modified",
|
|
2530
|
+
patch
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
for (const command of registryEmployee.commands) {
|
|
2537
|
+
const commandFileName = adapter.getCommandFileName(
|
|
2538
|
+
employeeName,
|
|
2539
|
+
command.name
|
|
2540
|
+
);
|
|
2541
|
+
for (const file of command.files) {
|
|
2542
|
+
const localPath = path8.join(commandsDir, commandFileName);
|
|
2543
|
+
if (!await fs7.pathExists(localPath)) {
|
|
2544
|
+
if (!commandsAdded.includes(command.name)) {
|
|
2545
|
+
fileChanges.push({
|
|
2546
|
+
filePath: localPath,
|
|
2547
|
+
type: "added"
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
} else {
|
|
2551
|
+
const patch = await compareFile(localPath, file.content);
|
|
2552
|
+
if (patch) {
|
|
2553
|
+
fileChanges.push({
|
|
2554
|
+
filePath: localPath,
|
|
2555
|
+
type: "modified",
|
|
2556
|
+
patch
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
for (const skillName of skillsRemoved) {
|
|
2563
|
+
const skillDirName = adapter.getSkillDirName(employeeName, skillName);
|
|
2564
|
+
const skillPath = path8.join(skillsDir, skillDirName);
|
|
2565
|
+
if (await fs7.pathExists(skillPath)) {
|
|
2566
|
+
fileChanges.push({
|
|
2567
|
+
filePath: skillPath,
|
|
2568
|
+
type: "removed"
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
for (const commandName of commandsRemoved) {
|
|
2573
|
+
const commandFileName = adapter.getCommandFileName(
|
|
2574
|
+
employeeName,
|
|
2575
|
+
commandName
|
|
2576
|
+
);
|
|
2577
|
+
const commandPath = path8.join(commandsDir, commandFileName);
|
|
2578
|
+
if (await fs7.pathExists(commandPath)) {
|
|
2579
|
+
fileChanges.push({
|
|
2580
|
+
filePath: commandPath,
|
|
2581
|
+
type: "removed"
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
const hasChanges = skillsAdded.length > 0 || skillsRemoved.length > 0 || commandsAdded.length > 0 || commandsRemoved.length > 0 || fileChanges.length > 0;
|
|
2586
|
+
return {
|
|
2587
|
+
name: employeeName,
|
|
2588
|
+
hasChanges,
|
|
2589
|
+
skillsAdded,
|
|
2590
|
+
skillsRemoved,
|
|
2591
|
+
commandsAdded,
|
|
2592
|
+
commandsRemoved,
|
|
2593
|
+
fileChanges
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
function printDiff(patch) {
|
|
2597
|
+
for (const part of patch) {
|
|
2598
|
+
if (part.added) {
|
|
2599
|
+
process.stdout.write(logger.formatSuccess(part.value));
|
|
2600
|
+
} else if (part.removed) {
|
|
2601
|
+
process.stdout.write(logger.formatError(part.value));
|
|
2602
|
+
} else {
|
|
2603
|
+
process.stdout.write(part.value);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
async function runDiff(options) {
|
|
2608
|
+
const { cwd, employee } = options;
|
|
2609
|
+
const config = await getConfig(cwd);
|
|
2610
|
+
if (!config) {
|
|
2611
|
+
throw notInitializedError();
|
|
2612
|
+
}
|
|
2613
|
+
const installedEmployees = Object.keys(config.employees);
|
|
2614
|
+
if (installedEmployees.length === 0) {
|
|
2615
|
+
logger.info("No employees installed.");
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
if (employee) {
|
|
2619
|
+
if (!config.employees[employee]) {
|
|
2620
|
+
logger.error(`Employee '${employee}' is not installed.`);
|
|
2621
|
+
logger.dim(`Installed employees: ${installedEmployees.join(", ")}`);
|
|
2622
|
+
process.exit(1);
|
|
2623
|
+
}
|
|
2624
|
+
const s2 = spinner(`Checking ${employee} for updates...`).start();
|
|
2625
|
+
const diff2 = await diffEmployee(employee, cwd, config);
|
|
2626
|
+
s2.stop();
|
|
2627
|
+
if (!diff2) {
|
|
2628
|
+
logger.error(`Failed to check updates for ${employee}`);
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
if (!diff2.hasChanges) {
|
|
2632
|
+
logger.success(`${employee} is up to date.`);
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
logger.info(`Changes for ${employee}:`);
|
|
2636
|
+
logger.break();
|
|
2637
|
+
if (diff2.skillsAdded.length > 0) {
|
|
2638
|
+
logger.success(` New skills: ${diff2.skillsAdded.join(", ")}`);
|
|
2639
|
+
}
|
|
2640
|
+
if (diff2.skillsRemoved.length > 0) {
|
|
2641
|
+
logger.warn(` Removed skills: ${diff2.skillsRemoved.join(", ")}`);
|
|
2642
|
+
}
|
|
2643
|
+
if (diff2.commandsAdded.length > 0) {
|
|
2644
|
+
logger.success(` New commands: ${diff2.commandsAdded.join(", ")}`);
|
|
2645
|
+
}
|
|
2646
|
+
if (diff2.commandsRemoved.length > 0) {
|
|
2647
|
+
logger.warn(` Removed commands: ${diff2.commandsRemoved.join(", ")}`);
|
|
2648
|
+
}
|
|
2649
|
+
for (const change of diff2.fileChanges) {
|
|
2650
|
+
const relativePath = path8.relative(cwd, change.filePath);
|
|
2651
|
+
if (change.type === "modified") {
|
|
2652
|
+
logger.info(` Modified: ${relativePath}`);
|
|
2653
|
+
if (change.patch) {
|
|
2654
|
+
logger.break();
|
|
2655
|
+
printDiff(change.patch);
|
|
2656
|
+
logger.break();
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
logger.break();
|
|
2661
|
+
logger.dim(`Run 'aico add ${employee} --overwrite' to update.`);
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
const s = spinner("Checking for updates...").start();
|
|
2665
|
+
const employeesWithUpdates = [];
|
|
2666
|
+
for (const employeeName of installedEmployees) {
|
|
2667
|
+
const diff2 = await diffEmployee(employeeName, cwd, config);
|
|
2668
|
+
if (diff2?.hasChanges) {
|
|
2669
|
+
employeesWithUpdates.push(diff2);
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
s.stop();
|
|
2673
|
+
if (employeesWithUpdates.length === 0) {
|
|
2674
|
+
logger.success("All employees are up to date.");
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
logger.info("The following employees have updates available:");
|
|
2678
|
+
logger.break();
|
|
2679
|
+
for (const diff2 of employeesWithUpdates) {
|
|
2680
|
+
logger.log(` ${diff2.name}`);
|
|
2681
|
+
if (diff2.skillsAdded.length > 0) {
|
|
2682
|
+
logger.dim(` + ${diff2.skillsAdded.length} new skill(s)`);
|
|
2683
|
+
}
|
|
2684
|
+
if (diff2.skillsRemoved.length > 0) {
|
|
2685
|
+
logger.dim(` - ${diff2.skillsRemoved.length} removed skill(s)`);
|
|
2686
|
+
}
|
|
2687
|
+
if (diff2.commandsAdded.length > 0) {
|
|
2688
|
+
logger.dim(` + ${diff2.commandsAdded.length} new command(s)`);
|
|
2689
|
+
}
|
|
2690
|
+
if (diff2.commandsRemoved.length > 0) {
|
|
2691
|
+
logger.dim(` - ${diff2.commandsRemoved.length} removed command(s)`);
|
|
2692
|
+
}
|
|
2693
|
+
const modifiedCount = diff2.fileChanges.filter(
|
|
2694
|
+
(f) => f.type === "modified"
|
|
2695
|
+
).length;
|
|
2696
|
+
if (modifiedCount > 0) {
|
|
2697
|
+
logger.dim(` ~ ${modifiedCount} modified file(s)`);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
logger.break();
|
|
2701
|
+
logger.dim(`Run 'aico diff <employee>' to see detailed changes.`);
|
|
2702
|
+
logger.dim(`Run 'aico add <employee> --overwrite' to update.`);
|
|
2703
|
+
}
|
|
2704
|
+
var diff = new Command6().name("diff").description("Check for updates against the registry").argument("[employee]", "Employee name to check").option("-c, --cwd <cwd>", "Working directory", process.cwd()).action(async (employee, opts) => {
|
|
2705
|
+
try {
|
|
2706
|
+
const options = diffOptionsSchema.parse({
|
|
2707
|
+
employee,
|
|
2708
|
+
cwd: opts.cwd
|
|
2709
|
+
});
|
|
2710
|
+
await runDiff(options);
|
|
2711
|
+
} catch (error) {
|
|
2712
|
+
handleError(error);
|
|
2713
|
+
}
|
|
2714
|
+
});
|
|
2715
|
+
|
|
2716
|
+
// src/commands/update.ts
|
|
2717
|
+
import { Command as Command7 } from "commander";
|
|
2718
|
+
import prompts5 from "prompts";
|
|
2719
|
+
import { z as z11 } from "zod";
|
|
2720
|
+
import kleur3 from "kleur";
|
|
2721
|
+
|
|
2722
|
+
// src/utils/version.ts
|
|
2723
|
+
function compareVersions(a, b) {
|
|
2724
|
+
const partsA = a.split(".").map(Number);
|
|
2725
|
+
const partsB = b.split(".").map(Number);
|
|
2726
|
+
for (let i = 0; i < 3; i++) {
|
|
2727
|
+
const partA = partsA[i] ?? 0;
|
|
2728
|
+
const partB = partsB[i] ?? 0;
|
|
2729
|
+
if (partA < partB) return -1;
|
|
2730
|
+
if (partA > partB) return 1;
|
|
2731
|
+
}
|
|
2732
|
+
return 0;
|
|
2733
|
+
}
|
|
2734
|
+
function hasUpdate(current, latest) {
|
|
2735
|
+
return compareVersions(current, latest) < 0;
|
|
2736
|
+
}
|
|
2737
|
+
function formatVersionChange(current, latest) {
|
|
2738
|
+
if (hasUpdate(current, latest)) {
|
|
2739
|
+
return `${current} \u2192 ${latest}`;
|
|
2740
|
+
}
|
|
2741
|
+
return `${current} (up to date)`;
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
// src/commands/update.ts
|
|
2745
|
+
var updateOptionsSchema = z11.object({
|
|
2746
|
+
skills: z11.array(z11.string()),
|
|
2747
|
+
cwd: z11.string(),
|
|
2748
|
+
yes: z11.boolean(),
|
|
2749
|
+
dryRun: z11.boolean()
|
|
2750
|
+
});
|
|
2751
|
+
async function runUpdate(options) {
|
|
2752
|
+
const { skills, cwd, yes, dryRun } = options;
|
|
2753
|
+
const config = await getConfig(cwd);
|
|
2754
|
+
if (!config) {
|
|
2755
|
+
throw notInitializedError();
|
|
2756
|
+
}
|
|
2757
|
+
logger.info("Checking for updates...");
|
|
2758
|
+
let skillsToCheck;
|
|
2759
|
+
if (skills.length > 0) {
|
|
2760
|
+
skillsToCheck = skills.map((s2) => parseTarget(s2).fullName);
|
|
2761
|
+
} else {
|
|
2762
|
+
skillsToCheck = await getInstalledSkills(cwd);
|
|
2763
|
+
}
|
|
2764
|
+
if (skillsToCheck.length === 0) {
|
|
2765
|
+
logger.info("No skills installed.");
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
const s = spinner("Fetching latest versions...").start();
|
|
2769
|
+
const updateResults = [];
|
|
2770
|
+
for (const skillName of skillsToCheck) {
|
|
2771
|
+
try {
|
|
2772
|
+
const currentState = config.skills?.[skillName];
|
|
2773
|
+
const currentVersion = currentState?.version ?? "0.0.0";
|
|
2774
|
+
const latestSkill = await fetchSkill(skillName, config, cwd);
|
|
2775
|
+
updateResults.push({
|
|
2776
|
+
skillName,
|
|
2777
|
+
currentVersion,
|
|
2778
|
+
latestVersion: latestSkill.version,
|
|
2779
|
+
hasUpdate: hasUpdate(currentVersion, latestSkill.version)
|
|
2780
|
+
});
|
|
2781
|
+
} catch {
|
|
2782
|
+
logger.dim(` Could not check ${skillName}`);
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
s.stop();
|
|
2786
|
+
const updatesAvailable = updateResults.filter((r) => r.hasUpdate);
|
|
2787
|
+
if (updatesAvailable.length === 0) {
|
|
2788
|
+
logger.success("All skills are up to date!");
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2791
|
+
logger.break();
|
|
2792
|
+
logger.log(formatUpdateTable(updateResults));
|
|
2793
|
+
logger.break();
|
|
2794
|
+
if (dryRun) {
|
|
2795
|
+
logger.info(`${updatesAvailable.length} update(s) available.`);
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2798
|
+
if (!yes) {
|
|
2799
|
+
const { confirmed } = await prompts5({
|
|
2800
|
+
type: "confirm",
|
|
2801
|
+
name: "confirmed",
|
|
2802
|
+
message: `Update ${updatesAvailable.length} skill(s)?`,
|
|
2803
|
+
initial: true
|
|
2804
|
+
});
|
|
2805
|
+
if (!confirmed) {
|
|
2806
|
+
logger.info("Cancelled.");
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
for (const result of updatesAvailable) {
|
|
2811
|
+
const updateSpinner = spinner(`Updating ${result.skillName}...`).start();
|
|
2812
|
+
try {
|
|
2813
|
+
const latestSkill = await fetchSkill(result.skillName, config, cwd);
|
|
2814
|
+
const platform = config.defaultPlatform;
|
|
2815
|
+
await uninstallSkill(result.skillName, config, cwd, { platform });
|
|
2816
|
+
await installSkill(latestSkill, config, cwd, {
|
|
2817
|
+
platform,
|
|
2818
|
+
overwrite: true
|
|
2819
|
+
});
|
|
2820
|
+
await updateSkill(
|
|
2821
|
+
cwd,
|
|
2822
|
+
result.skillName,
|
|
2823
|
+
latestSkill.version,
|
|
2824
|
+
[platform],
|
|
2825
|
+
"standalone"
|
|
2826
|
+
);
|
|
2827
|
+
updateSpinner.succeed(
|
|
2828
|
+
`Updated ${result.skillName} (${formatVersionChange(result.currentVersion, result.latestVersion)})`
|
|
2829
|
+
);
|
|
2830
|
+
} catch (error) {
|
|
2831
|
+
updateSpinner.fail(`Failed to update ${result.skillName}`);
|
|
2832
|
+
throw error;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
logger.break();
|
|
2836
|
+
logger.success("Update complete!");
|
|
2837
|
+
}
|
|
2838
|
+
function formatUpdateTable(results) {
|
|
2839
|
+
const maxNameLen = Math.max(...results.map((r) => r.skillName.length), 20);
|
|
2840
|
+
const maxVerLen = 10;
|
|
2841
|
+
const lines = [];
|
|
2842
|
+
const header = [
|
|
2843
|
+
"Skill".padEnd(maxNameLen),
|
|
2844
|
+
"Current".padEnd(maxVerLen),
|
|
2845
|
+
"Latest".padEnd(maxVerLen),
|
|
2846
|
+
"Status"
|
|
2847
|
+
].join(" \u2502 ");
|
|
2848
|
+
const separator = [
|
|
2849
|
+
"\u2500".repeat(maxNameLen),
|
|
2850
|
+
"\u2500".repeat(maxVerLen),
|
|
2851
|
+
"\u2500".repeat(maxVerLen),
|
|
2852
|
+
"\u2500".repeat(16)
|
|
2853
|
+
].join("\u2500\u253C\u2500");
|
|
2854
|
+
lines.push("\u250C" + separator.replace(/─┼─/g, "\u2500\u252C\u2500") + "\u2510");
|
|
2855
|
+
lines.push("\u2502 " + header + " \u2502");
|
|
2856
|
+
lines.push("\u251C" + separator + "\u2524");
|
|
2857
|
+
for (const r of results) {
|
|
2858
|
+
const status = r.hasUpdate ? kleur3.yellow("update available") : kleur3.green("up to date");
|
|
2859
|
+
const row = [
|
|
2860
|
+
r.skillName.padEnd(maxNameLen),
|
|
2861
|
+
r.currentVersion.padEnd(maxVerLen),
|
|
2862
|
+
r.latestVersion.padEnd(maxVerLen),
|
|
2863
|
+
status
|
|
2864
|
+
].join(" \u2502 ");
|
|
2865
|
+
lines.push("\u2502 " + row + " \u2502");
|
|
2866
|
+
}
|
|
2867
|
+
lines.push("\u2514" + separator.replace(/─┼─/g, "\u2500\u2534\u2500") + "\u2518");
|
|
2868
|
+
return lines.join("\n");
|
|
2869
|
+
}
|
|
2870
|
+
var update = new Command7().name("update").description("Update installed skills to latest versions").argument("[skills...]", "Skill names to update (all if empty)").option("--dry-run", "Preview updates without applying", false).option("-y, --yes", "Skip confirmation", false).option("-c, --cwd <cwd>", "Working directory", process.cwd()).action(async (skills, opts) => {
|
|
2871
|
+
try {
|
|
2872
|
+
const options = updateOptionsSchema.parse({
|
|
2873
|
+
skills,
|
|
2874
|
+
cwd: opts.cwd,
|
|
2875
|
+
yes: opts.yes,
|
|
2876
|
+
dryRun: opts.dryRun
|
|
2877
|
+
});
|
|
2878
|
+
await runUpdate(options);
|
|
2879
|
+
} catch (error) {
|
|
2880
|
+
handleError(error);
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
|
|
2884
|
+
// src/commands/search.ts
|
|
2885
|
+
import { Command as Command8 } from "commander";
|
|
2886
|
+
import kleur4 from "kleur";
|
|
2887
|
+
function searchItems(query, items) {
|
|
2888
|
+
const q = query.toLowerCase();
|
|
2889
|
+
return items.filter((item) => {
|
|
2890
|
+
if (item.name.toLowerCase().includes(q)) return true;
|
|
2891
|
+
if (item.fullName.toLowerCase().includes(q)) return true;
|
|
2892
|
+
if (item.description.toLowerCase().includes(q)) return true;
|
|
2893
|
+
if (item.tags?.some((tag) => tag.toLowerCase().includes(q))) return true;
|
|
2894
|
+
if (item.category?.toLowerCase().includes(q)) return true;
|
|
2895
|
+
return false;
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
function scoreItem(item, query) {
|
|
2899
|
+
const q = query.toLowerCase();
|
|
2900
|
+
let score = 0;
|
|
2901
|
+
if (item.name.toLowerCase() === q) {
|
|
2902
|
+
score += 100;
|
|
2903
|
+
} else if (item.name.toLowerCase().includes(q)) {
|
|
2904
|
+
score += 50;
|
|
2905
|
+
}
|
|
2906
|
+
if (item.tags?.some((tag) => tag.toLowerCase() === q)) {
|
|
2907
|
+
score += 30;
|
|
2908
|
+
}
|
|
2909
|
+
if (item.category?.toLowerCase() === q) {
|
|
2910
|
+
score += 20;
|
|
2911
|
+
}
|
|
2912
|
+
if (item.description.toLowerCase().includes(q)) {
|
|
2913
|
+
score += 10;
|
|
2914
|
+
}
|
|
2915
|
+
return score;
|
|
2916
|
+
}
|
|
2917
|
+
function sortByRelevance(items, query) {
|
|
2918
|
+
return items.map((item) => ({ ...item, score: scoreItem(item, query) })).sort((a, b) => b.score - a.score);
|
|
2919
|
+
}
|
|
2920
|
+
function filterByCategory(items, category) {
|
|
2921
|
+
return items.filter((item) => item.category === category);
|
|
2922
|
+
}
|
|
2923
|
+
function filterByType(items, type) {
|
|
2924
|
+
return items.filter((item) => item.type === type);
|
|
2925
|
+
}
|
|
2926
|
+
function formatTable(items) {
|
|
2927
|
+
if (items.length === 0) {
|
|
2928
|
+
return kleur4.yellow("No results found.");
|
|
2929
|
+
}
|
|
2930
|
+
const nameWidth = Math.max(
|
|
2931
|
+
"Name".length,
|
|
2932
|
+
...items.map((i) => i.fullName.length)
|
|
2933
|
+
);
|
|
2934
|
+
const typeWidth = Math.max("Type".length, ...items.map((i) => i.type.length));
|
|
2935
|
+
const catWidth = Math.max(
|
|
2936
|
+
"Category".length,
|
|
2937
|
+
...items.map((i) => (i.category || "-").length)
|
|
2938
|
+
);
|
|
2939
|
+
const descWidth = 40;
|
|
2940
|
+
const header = [
|
|
2941
|
+
"Name".padEnd(nameWidth),
|
|
2942
|
+
"Type".padEnd(typeWidth),
|
|
2943
|
+
"Category".padEnd(catWidth),
|
|
2944
|
+
"Description"
|
|
2945
|
+
].join(" \u2502 ");
|
|
2946
|
+
const separator = [
|
|
2947
|
+
"\u2500".repeat(nameWidth),
|
|
2948
|
+
"\u2500".repeat(typeWidth),
|
|
2949
|
+
"\u2500".repeat(catWidth),
|
|
2950
|
+
"\u2500".repeat(descWidth)
|
|
2951
|
+
].join("\u2500\u253C\u2500");
|
|
2952
|
+
const rows = items.map((item) => {
|
|
2953
|
+
const desc = item.description.length > descWidth ? item.description.slice(0, descWidth - 3) + "..." : item.description;
|
|
2954
|
+
return [
|
|
2955
|
+
item.fullName.padEnd(nameWidth),
|
|
2956
|
+
item.type.padEnd(typeWidth),
|
|
2957
|
+
(item.category || "-").padEnd(catWidth),
|
|
2958
|
+
desc
|
|
2959
|
+
].join(" \u2502 ");
|
|
2960
|
+
});
|
|
2961
|
+
return [kleur4.bold(header), separator, ...rows].join("\n");
|
|
2962
|
+
}
|
|
2963
|
+
function formatJSON(items) {
|
|
2964
|
+
const output = {
|
|
2965
|
+
count: items.length,
|
|
2966
|
+
results: items.map((item) => ({
|
|
2967
|
+
name: item.name,
|
|
2968
|
+
fullName: item.fullName,
|
|
2969
|
+
type: item.type,
|
|
2970
|
+
category: item.category,
|
|
2971
|
+
description: item.description,
|
|
2972
|
+
tags: item.tags,
|
|
2973
|
+
install: item.type === "employee" ? `npx aico add ${item.name}` : `npx aico add ${item.fullName}`
|
|
2974
|
+
}))
|
|
2975
|
+
};
|
|
2976
|
+
return JSON.stringify(output, null, 2);
|
|
2977
|
+
}
|
|
2978
|
+
var search = new Command8().name("search").description("Search for skills and employees").argument("<query>", "Search query").option(
|
|
2979
|
+
"-c, --category <category>",
|
|
2980
|
+
"Filter by category (pm, frontend, backend)"
|
|
2981
|
+
).option("-t, --type <type>", "Filter by type (skill, employee)").option("--json", "Output as JSON").option("--limit <n>", "Limit results", "10").option("--cwd <cwd>", "Working directory", process.cwd()).action(async (query, opts) => {
|
|
2982
|
+
try {
|
|
2983
|
+
const config = await getConfig(opts.cwd);
|
|
2984
|
+
if (!config) {
|
|
2985
|
+
throw notInitializedError();
|
|
2986
|
+
}
|
|
2987
|
+
logger.info("Searching...");
|
|
2988
|
+
const index = await fetchRegistryIndex(config, opts.cwd);
|
|
2989
|
+
const items = [
|
|
2990
|
+
...index.employees.map((emp) => ({
|
|
2991
|
+
name: emp.name,
|
|
2992
|
+
fullName: emp.fullName || emp.name,
|
|
2993
|
+
description: emp.description || "",
|
|
2994
|
+
category: emp.category,
|
|
2995
|
+
type: "employee"
|
|
2996
|
+
}))
|
|
2997
|
+
];
|
|
2998
|
+
let results = searchItems(query, items);
|
|
2999
|
+
if (opts.category) {
|
|
3000
|
+
results = filterByCategory(results, opts.category);
|
|
3001
|
+
}
|
|
3002
|
+
if (opts.type) {
|
|
3003
|
+
results = filterByType(results, opts.type);
|
|
3004
|
+
}
|
|
3005
|
+
results = sortByRelevance(results, query);
|
|
3006
|
+
const limit = parseInt(opts.limit, 10);
|
|
3007
|
+
results = results.slice(0, limit);
|
|
3008
|
+
logger.break();
|
|
3009
|
+
if (opts.json) {
|
|
3010
|
+
logger.log(formatJSON(results));
|
|
3011
|
+
} else {
|
|
3012
|
+
logger.log(
|
|
3013
|
+
kleur4.cyan(`Found ${results.length} result(s) for "${query}":`)
|
|
3014
|
+
);
|
|
3015
|
+
logger.break();
|
|
3016
|
+
logger.log(formatTable(results));
|
|
3017
|
+
logger.break();
|
|
3018
|
+
if (results.length > 0) {
|
|
3019
|
+
logger.dim("Install: npx aico add <name>");
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
} catch (error) {
|
|
3023
|
+
handleError(error);
|
|
3024
|
+
}
|
|
3025
|
+
});
|
|
3026
|
+
|
|
3027
|
+
// src/commands/check.ts
|
|
3028
|
+
import { Command as Command9 } from "commander";
|
|
3029
|
+
import kleur5 from "kleur";
|
|
3030
|
+
import { existsSync, readdirSync, statSync } from "fs";
|
|
3031
|
+
import { join } from "path";
|
|
3032
|
+
function checkNodeVersion() {
|
|
3033
|
+
const version = process.version;
|
|
3034
|
+
const major = parseInt(version.slice(1).split(".")[0] ?? "0", 10);
|
|
3035
|
+
if (major >= 20) {
|
|
3036
|
+
return {
|
|
3037
|
+
name: "Node.js",
|
|
3038
|
+
status: "pass",
|
|
3039
|
+
message: version
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
if (major >= 18) {
|
|
3043
|
+
return {
|
|
3044
|
+
name: "Node.js",
|
|
3045
|
+
status: "warn",
|
|
3046
|
+
message: `${version} (recommend >= 20)`
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
return {
|
|
3050
|
+
name: "Node.js",
|
|
3051
|
+
status: "fail",
|
|
3052
|
+
message: `${version} (requires >= 18)`
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
async function checkConfig(cwd) {
|
|
3056
|
+
const configPath = join(cwd, "aico.json");
|
|
3057
|
+
if (!existsSync(configPath)) {
|
|
3058
|
+
return {
|
|
3059
|
+
name: "aico.json",
|
|
3060
|
+
status: "fail",
|
|
3061
|
+
message: "not found"
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
try {
|
|
3065
|
+
const config = await getConfig(cwd);
|
|
3066
|
+
if (!config) {
|
|
3067
|
+
return {
|
|
3068
|
+
name: "aico.json",
|
|
3069
|
+
status: "fail",
|
|
3070
|
+
message: "invalid"
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
if (!config.defaultPlatform) {
|
|
3074
|
+
return {
|
|
3075
|
+
name: "aico.json",
|
|
3076
|
+
status: "warn",
|
|
3077
|
+
message: "missing defaultPlatform"
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3080
|
+
return {
|
|
3081
|
+
name: "aico.json",
|
|
3082
|
+
status: "pass",
|
|
3083
|
+
message: "valid"
|
|
3084
|
+
};
|
|
3085
|
+
} catch {
|
|
3086
|
+
return {
|
|
3087
|
+
name: "aico.json",
|
|
3088
|
+
status: "fail",
|
|
3089
|
+
message: "parse error"
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
function checkPlatformDirs(cwd, platform, platformConfig) {
|
|
3094
|
+
const children = [];
|
|
3095
|
+
const skillsPath = join(cwd, platformConfig.skills);
|
|
3096
|
+
if (existsSync(skillsPath)) {
|
|
3097
|
+
children.push({
|
|
3098
|
+
name: platformConfig.skills,
|
|
3099
|
+
status: "pass",
|
|
3100
|
+
message: "exists"
|
|
3101
|
+
});
|
|
3102
|
+
} else {
|
|
3103
|
+
children.push({
|
|
3104
|
+
name: platformConfig.skills,
|
|
3105
|
+
status: "warn",
|
|
3106
|
+
message: "not found (will be created)"
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
const commandsPath = join(cwd, platformConfig.commands);
|
|
3110
|
+
if (existsSync(commandsPath)) {
|
|
3111
|
+
children.push({
|
|
3112
|
+
name: platformConfig.commands,
|
|
3113
|
+
status: "pass",
|
|
3114
|
+
message: "exists"
|
|
3115
|
+
});
|
|
3116
|
+
} else {
|
|
3117
|
+
children.push({
|
|
3118
|
+
name: platformConfig.commands,
|
|
3119
|
+
status: "warn",
|
|
3120
|
+
message: "not found (will be created)"
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
const allPass = children.every((c) => c.status === "pass");
|
|
3124
|
+
const hasError = children.some((c) => c.status === "fail");
|
|
3125
|
+
return {
|
|
3126
|
+
name: `Platform: ${platform}`,
|
|
3127
|
+
status: hasError ? "fail" : allPass ? "pass" : "warn",
|
|
3128
|
+
message: "",
|
|
3129
|
+
children
|
|
3130
|
+
};
|
|
3131
|
+
}
|
|
3132
|
+
async function checkRegistry(registries) {
|
|
3133
|
+
const children = [];
|
|
3134
|
+
for (const [name, registry] of Object.entries(registries)) {
|
|
3135
|
+
const urlTemplate = typeof registry === "string" ? registry : registry.url;
|
|
3136
|
+
const testUrl = urlTemplate.replace("{name}", "index");
|
|
3137
|
+
try {
|
|
3138
|
+
const response = await fetch(testUrl, {
|
|
3139
|
+
method: "HEAD",
|
|
3140
|
+
signal: AbortSignal.timeout(5e3)
|
|
3141
|
+
});
|
|
3142
|
+
if (response.ok) {
|
|
3143
|
+
children.push({
|
|
3144
|
+
name,
|
|
3145
|
+
status: "pass",
|
|
3146
|
+
message: "reachable"
|
|
3147
|
+
});
|
|
3148
|
+
} else {
|
|
3149
|
+
children.push({
|
|
3150
|
+
name,
|
|
3151
|
+
status: "fail",
|
|
3152
|
+
message: `HTTP ${response.status}`
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
} catch {
|
|
3156
|
+
children.push({
|
|
3157
|
+
name,
|
|
3158
|
+
status: "fail",
|
|
3159
|
+
message: "unreachable"
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
const hasError = children.some((c) => c.status === "fail");
|
|
3164
|
+
return {
|
|
3165
|
+
name: "Registry",
|
|
3166
|
+
status: hasError ? "fail" : "pass",
|
|
3167
|
+
message: "",
|
|
3168
|
+
children
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
function countDirectory(dir) {
|
|
3172
|
+
if (!existsSync(dir)) return 0;
|
|
3173
|
+
try {
|
|
3174
|
+
return readdirSync(dir).filter((item) => {
|
|
3175
|
+
const itemPath = join(dir, item);
|
|
3176
|
+
try {
|
|
3177
|
+
return statSync(itemPath).isDirectory() || item.endsWith(".md");
|
|
3178
|
+
} catch {
|
|
3179
|
+
return false;
|
|
3180
|
+
}
|
|
3181
|
+
}).length;
|
|
3182
|
+
} catch {
|
|
3183
|
+
return 0;
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
function checkInstalled(cwd, platformConfig) {
|
|
3187
|
+
const skillsCount = countDirectory(join(cwd, platformConfig.skills));
|
|
3188
|
+
const commandsCount = countDirectory(join(cwd, platformConfig.commands));
|
|
3189
|
+
const message = `${skillsCount} skills, ${commandsCount} commands`;
|
|
3190
|
+
return {
|
|
3191
|
+
name: "Installed",
|
|
3192
|
+
status: skillsCount > 0 || commandsCount > 0 ? "pass" : "skip",
|
|
3193
|
+
message
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
function getSymbol(status) {
|
|
3197
|
+
const symbols = {
|
|
3198
|
+
pass: kleur5.green("\u25CF"),
|
|
3199
|
+
fail: kleur5.red("\u2716"),
|
|
3200
|
+
warn: kleur5.yellow("\u25D0"),
|
|
3201
|
+
skip: kleur5.gray("\u25CB")
|
|
3202
|
+
};
|
|
3203
|
+
return symbols[status];
|
|
3204
|
+
}
|
|
3205
|
+
function formatResults(results) {
|
|
3206
|
+
const lines = [];
|
|
3207
|
+
results.forEach((result, index) => {
|
|
3208
|
+
const isLast = index === results.length - 1;
|
|
3209
|
+
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
3210
|
+
const extension = isLast ? " " : "\u2502 ";
|
|
3211
|
+
const symbol = getSymbol(result.status);
|
|
3212
|
+
const message = result.message ? kleur5.dim(` (${result.message})`) : "";
|
|
3213
|
+
lines.push(`${connector}${symbol} ${result.name}${message}`);
|
|
3214
|
+
if (result.children) {
|
|
3215
|
+
result.children.forEach((child, childIndex) => {
|
|
3216
|
+
const childIsLast = childIndex === result.children.length - 1;
|
|
3217
|
+
const childConnector = childIsLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
3218
|
+
const childSymbol = getSymbol(child.status);
|
|
3219
|
+
const childMessage = child.message ? kleur5.dim(` (${child.message})`) : "";
|
|
3220
|
+
lines.push(
|
|
3221
|
+
`${extension}${childConnector}${childSymbol} ${child.name}${childMessage}`
|
|
3222
|
+
);
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
return lines.join("\n");
|
|
3227
|
+
}
|
|
3228
|
+
var check = new Command9().name("check").description("Check environment and configuration").option("--json", "Output as JSON").option("--cwd <cwd>", "Working directory", process.cwd()).action(async (opts) => {
|
|
3229
|
+
try {
|
|
3230
|
+
const cwd = opts.cwd;
|
|
3231
|
+
logger.break();
|
|
3232
|
+
logger.bold(kleur5.cyan("AICO Environment Check"));
|
|
3233
|
+
logger.break();
|
|
3234
|
+
const results = [];
|
|
3235
|
+
results.push(checkNodeVersion());
|
|
3236
|
+
const configResult = await checkConfig(cwd);
|
|
3237
|
+
results.push(configResult);
|
|
3238
|
+
if (configResult.status === "fail") {
|
|
3239
|
+
results.push({
|
|
3240
|
+
name: "Platform",
|
|
3241
|
+
status: "skip",
|
|
3242
|
+
message: "skipped (no config)"
|
|
3243
|
+
});
|
|
3244
|
+
results.push({
|
|
3245
|
+
name: "Registry",
|
|
3246
|
+
status: "skip",
|
|
3247
|
+
message: "skipped (no config)"
|
|
3248
|
+
});
|
|
3249
|
+
results.push({
|
|
3250
|
+
name: "Installed",
|
|
3251
|
+
status: "skip",
|
|
3252
|
+
message: "skipped (no config)"
|
|
3253
|
+
});
|
|
3254
|
+
} else {
|
|
3255
|
+
const config = await getConfig(cwd);
|
|
3256
|
+
const platformConfig = config.platforms[config.defaultPlatform];
|
|
3257
|
+
if (platformConfig) {
|
|
3258
|
+
results.push(
|
|
3259
|
+
checkPlatformDirs(cwd, config.defaultPlatform, platformConfig)
|
|
3260
|
+
);
|
|
3261
|
+
results.push(await checkRegistry(config.registries));
|
|
3262
|
+
results.push(checkInstalled(cwd, platformConfig));
|
|
3263
|
+
} else {
|
|
3264
|
+
results.push({
|
|
3265
|
+
name: "Platform",
|
|
3266
|
+
status: "fail",
|
|
3267
|
+
message: "default platform not found in config"
|
|
3268
|
+
});
|
|
3269
|
+
results.push({
|
|
3270
|
+
name: "Registry",
|
|
3271
|
+
status: "skip",
|
|
3272
|
+
message: "skipped (platform missing)"
|
|
3273
|
+
});
|
|
3274
|
+
results.push({
|
|
3275
|
+
name: "Installed",
|
|
3276
|
+
status: "skip",
|
|
3277
|
+
message: "skipped (platform missing)"
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
if (opts.json) {
|
|
3282
|
+
logger.log(JSON.stringify(results, null, 2));
|
|
3283
|
+
} else {
|
|
3284
|
+
logger.log(formatResults(results));
|
|
3285
|
+
logger.break();
|
|
3286
|
+
const hasError = results.some((r) => r.status === "fail");
|
|
3287
|
+
const hasWarn = results.some((r) => r.status === "warn");
|
|
3288
|
+
if (hasError) {
|
|
3289
|
+
logger.log(
|
|
3290
|
+
kleur5.red("\u2716 Some checks failed. Run `npx aico init` to fix.")
|
|
3291
|
+
);
|
|
3292
|
+
} else if (hasWarn) {
|
|
3293
|
+
logger.log(
|
|
3294
|
+
kleur5.yellow("\u25D0 Some checks have warnings. AICO should work.")
|
|
3295
|
+
);
|
|
3296
|
+
} else {
|
|
3297
|
+
logger.log(kleur5.green("\u2713 All checks passed! AICO is ready to use."));
|
|
3298
|
+
}
|
|
3299
|
+
logger.break();
|
|
3300
|
+
}
|
|
3301
|
+
} catch (error) {
|
|
3302
|
+
handleError(error);
|
|
3303
|
+
}
|
|
3304
|
+
});
|
|
3305
|
+
|
|
3306
|
+
// src/index.ts
|
|
3307
|
+
var VERSION = "1.0.0";
|
|
3308
|
+
var globalVerbose = false;
|
|
3309
|
+
async function main() {
|
|
3310
|
+
const program = new Command10().name("aico").description(
|
|
3311
|
+
"AI employee management tool - Build your AI team in seconds, start working immediately"
|
|
3312
|
+
).version(VERSION).option("-v, --verbose", "Show detailed error information").option("--proxy <url>", "Use HTTP/HTTPS proxy for requests");
|
|
3313
|
+
program.hook("preAction", (thisCommand) => {
|
|
3314
|
+
const opts = thisCommand.opts();
|
|
3315
|
+
globalVerbose = opts.verbose ?? false;
|
|
3316
|
+
});
|
|
3317
|
+
program.addCommand(init);
|
|
3318
|
+
program.addCommand(add);
|
|
3319
|
+
program.addCommand(remove);
|
|
3320
|
+
program.addCommand(update);
|
|
3321
|
+
program.addCommand(list);
|
|
3322
|
+
program.addCommand(diff);
|
|
3323
|
+
program.addCommand(build);
|
|
3324
|
+
program.addCommand(search);
|
|
3325
|
+
program.addCommand(check);
|
|
3326
|
+
await program.parseAsync(process.argv);
|
|
3327
|
+
}
|
|
3328
|
+
main().catch((error) => {
|
|
3329
|
+
handleError(error, { verbose: globalVerbose });
|
|
3330
|
+
});
|
|
3331
|
+
//# sourceMappingURL=index.js.map
|