@tdsoft-tech/aikit 0.1.12 → 0.1.13
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/dist/cli.js +1695 -1749
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +77 -1
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -803,8 +803,8 @@ var init_memory = __esm({
|
|
|
803
803
|
const subDir = type === "observation" ? "observations" : type === "handoff" ? "handoffs" : type === "research" ? "research" : "";
|
|
804
804
|
filePath = join8(memoryPath, subDir, `${key}.md`);
|
|
805
805
|
}
|
|
806
|
-
const { dirname:
|
|
807
|
-
await mkdir4(
|
|
806
|
+
const { dirname: dirname5 } = await import("path");
|
|
807
|
+
await mkdir4(dirname5(filePath), { recursive: true });
|
|
808
808
|
if (options?.append) {
|
|
809
809
|
try {
|
|
810
810
|
const existing = await readFile5(filePath, "utf-8");
|
|
@@ -934,8 +934,8 @@ var tool_config_exports = {};
|
|
|
934
934
|
__export(tool_config_exports, {
|
|
935
935
|
ToolConfigManager: () => ToolConfigManager
|
|
936
936
|
});
|
|
937
|
-
import { readFile as
|
|
938
|
-
import { join as
|
|
937
|
+
import { readFile as readFile12, writeFile as writeFile13, mkdir as mkdir11, access as access5, constants as constants4 } from "fs/promises";
|
|
938
|
+
import { join as join18 } from "path";
|
|
939
939
|
import { z as z3 } from "zod";
|
|
940
940
|
var ToolConfigSchema, REGISTERED_TOOLS, ToolConfigManager;
|
|
941
941
|
var init_tool_config = __esm({
|
|
@@ -963,7 +963,7 @@ var init_tool_config = __esm({
|
|
|
963
963
|
toolsConfigPath;
|
|
964
964
|
constructor(config) {
|
|
965
965
|
this.config = config;
|
|
966
|
-
this.toolsConfigPath =
|
|
966
|
+
this.toolsConfigPath = join18(this.config.configPath, "config", "tools.json");
|
|
967
967
|
}
|
|
968
968
|
/**
|
|
969
969
|
* Get all registered tools with their current status
|
|
@@ -1046,8 +1046,8 @@ var init_tool_config = __esm({
|
|
|
1046
1046
|
*/
|
|
1047
1047
|
async loadConfigs() {
|
|
1048
1048
|
try {
|
|
1049
|
-
await
|
|
1050
|
-
const content = await
|
|
1049
|
+
await access5(this.toolsConfigPath, constants4.R_OK);
|
|
1050
|
+
const content = await readFile12(this.toolsConfigPath, "utf-8");
|
|
1051
1051
|
return JSON.parse(content);
|
|
1052
1052
|
} catch {
|
|
1053
1053
|
return {};
|
|
@@ -1057,9 +1057,9 @@ var init_tool_config = __esm({
|
|
|
1057
1057
|
* Save configurations
|
|
1058
1058
|
*/
|
|
1059
1059
|
async saveConfigs(configs) {
|
|
1060
|
-
const configDir =
|
|
1061
|
-
await
|
|
1062
|
-
await
|
|
1060
|
+
const configDir = join18(this.config.configPath, "config");
|
|
1061
|
+
await mkdir11(configDir, { recursive: true });
|
|
1062
|
+
await writeFile13(this.toolsConfigPath, JSON.stringify(configs, null, 2));
|
|
1063
1063
|
}
|
|
1064
1064
|
};
|
|
1065
1065
|
}
|
|
@@ -1196,9 +1196,10 @@ var init_figma_oauth = __esm({
|
|
|
1196
1196
|
|
|
1197
1197
|
// src/cli.ts
|
|
1198
1198
|
init_esm_shims();
|
|
1199
|
+
|
|
1200
|
+
// src/cli/index.ts
|
|
1201
|
+
init_esm_shims();
|
|
1199
1202
|
import { Command } from "commander";
|
|
1200
|
-
import chalk3 from "chalk";
|
|
1201
|
-
import inquirer2 from "inquirer";
|
|
1202
1203
|
|
|
1203
1204
|
// src/index.ts
|
|
1204
1205
|
init_esm_shims();
|
|
@@ -1266,7 +1267,8 @@ var ConfigSchema = z.object({
|
|
|
1266
1267
|
context7: z.boolean().default(false),
|
|
1267
1268
|
githubGrep: z.boolean().default(false),
|
|
1268
1269
|
gkg: z.boolean().default(false)
|
|
1269
|
-
}).optional()
|
|
1270
|
+
}).optional(),
|
|
1271
|
+
mode: z.string().default("build").optional()
|
|
1270
1272
|
});
|
|
1271
1273
|
var Config = class {
|
|
1272
1274
|
config;
|
|
@@ -1300,6 +1302,9 @@ var Config = class {
|
|
|
1300
1302
|
get antiHallucination() {
|
|
1301
1303
|
return this.config.antiHallucination;
|
|
1302
1304
|
}
|
|
1305
|
+
get mode() {
|
|
1306
|
+
return this.config.mode;
|
|
1307
|
+
}
|
|
1303
1308
|
get configPath() {
|
|
1304
1309
|
return this.config.configPath;
|
|
1305
1310
|
}
|
|
@@ -4240,8 +4245,13 @@ init_logger();
|
|
|
4240
4245
|
init_logger();
|
|
4241
4246
|
init_paths();
|
|
4242
4247
|
|
|
4243
|
-
// src/cli.ts
|
|
4244
|
-
|
|
4248
|
+
// src/cli/commands/index.ts
|
|
4249
|
+
init_esm_shims();
|
|
4250
|
+
|
|
4251
|
+
// src/cli/commands/init.ts
|
|
4252
|
+
init_esm_shims();
|
|
4253
|
+
import chalk2 from "chalk";
|
|
4254
|
+
import inquirer from "inquirer";
|
|
4245
4255
|
|
|
4246
4256
|
// src/utils/cli-detector.ts
|
|
4247
4257
|
init_esm_shims();
|
|
@@ -4358,702 +4368,1318 @@ var CliDetector = class {
|
|
|
4358
4368
|
}
|
|
4359
4369
|
};
|
|
4360
4370
|
|
|
4361
|
-
// src/cli.ts
|
|
4371
|
+
// src/cli/commands/init.ts
|
|
4362
4372
|
init_logger();
|
|
4363
4373
|
init_paths();
|
|
4364
4374
|
|
|
4365
|
-
// src/
|
|
4366
|
-
init_esm_shims();
|
|
4367
|
-
import { readFile as readFile10, writeFile as writeFile11, copyFile, mkdir as mkdir9 } from "fs/promises";
|
|
4368
|
-
import { join as join16, dirname as dirname3 } from "path";
|
|
4369
|
-
import inquirer from "inquirer";
|
|
4370
|
-
import chalk2 from "chalk";
|
|
4371
|
-
|
|
4372
|
-
// src/core/version-manager.ts
|
|
4375
|
+
// src/cli/helpers.ts
|
|
4373
4376
|
init_esm_shims();
|
|
4374
|
-
|
|
4377
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4378
|
+
import { mkdir as mkdir8, writeFile as writeFile8, readFile as readFile7, access as access4 } from "fs/promises";
|
|
4379
|
+
import { join as join13, dirname as dirname2 } from "path";
|
|
4380
|
+
import { homedir as homedir3 } from "os";
|
|
4381
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4382
|
+
import { execSync as execSync2 } from "child_process";
|
|
4375
4383
|
init_logger();
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
* Get package version from package.json
|
|
4398
|
-
*/
|
|
4399
|
-
getPackageVersion() {
|
|
4400
|
-
try {
|
|
4401
|
-
const packageJson = __require(join13(process.cwd(), "package.json"));
|
|
4402
|
-
return packageJson.version || "0.0.0";
|
|
4403
|
-
} catch {
|
|
4404
|
-
return "0.0.0";
|
|
4405
|
-
}
|
|
4406
|
-
}
|
|
4407
|
-
/**
|
|
4408
|
-
* Check for updates
|
|
4409
|
-
*/
|
|
4410
|
-
async checkForUpdates() {
|
|
4411
|
-
const installed = await this.getCurrentVersion();
|
|
4412
|
-
const packageVersion = this.getPackageVersion();
|
|
4413
|
-
if (!installed) {
|
|
4414
|
-
return {
|
|
4415
|
-
hasUpdate: true,
|
|
4416
|
-
fromVersion: "none",
|
|
4417
|
-
toVersion: packageVersion,
|
|
4418
|
-
newSkills: [],
|
|
4419
|
-
modifiedSkills: [],
|
|
4420
|
-
removedSkills: [],
|
|
4421
|
-
conflicts: [],
|
|
4422
|
-
configChanges: ["Initial version tracking"]
|
|
4423
|
-
};
|
|
4424
|
-
}
|
|
4425
|
-
const hasUpdate = installed.installedVersion !== packageVersion;
|
|
4426
|
-
if (!hasUpdate) {
|
|
4427
|
-
return {
|
|
4428
|
-
hasUpdate: false,
|
|
4429
|
-
fromVersion: installed.installedVersion,
|
|
4430
|
-
toVersion: packageVersion,
|
|
4431
|
-
newSkills: [],
|
|
4432
|
-
modifiedSkills: [],
|
|
4433
|
-
removedSkills: [],
|
|
4434
|
-
conflicts: [],
|
|
4435
|
-
configChanges: []
|
|
4436
|
-
};
|
|
4437
|
-
}
|
|
4438
|
-
const changes = await this.detectChanges();
|
|
4439
|
-
return {
|
|
4440
|
-
hasUpdate: true,
|
|
4441
|
-
fromVersion: installed.installedVersion,
|
|
4442
|
-
toVersion: packageVersion,
|
|
4443
|
-
...changes
|
|
4444
|
-
};
|
|
4384
|
+
init_paths();
|
|
4385
|
+
async function initializeConfig(configDir, _isGlobal) {
|
|
4386
|
+
const dirs = [
|
|
4387
|
+
"",
|
|
4388
|
+
"skills",
|
|
4389
|
+
"agents",
|
|
4390
|
+
"commands",
|
|
4391
|
+
"commands/build",
|
|
4392
|
+
"commands/git",
|
|
4393
|
+
"commands/plan",
|
|
4394
|
+
"commands/research",
|
|
4395
|
+
"tools",
|
|
4396
|
+
"plugins",
|
|
4397
|
+
"memory",
|
|
4398
|
+
"memory/_templates",
|
|
4399
|
+
"memory/handoffs",
|
|
4400
|
+
"memory/observations",
|
|
4401
|
+
"memory/research"
|
|
4402
|
+
];
|
|
4403
|
+
for (const dir of dirs) {
|
|
4404
|
+
await mkdir8(join13(configDir, dir), { recursive: true });
|
|
4445
4405
|
}
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4406
|
+
const defaultConfig = {
|
|
4407
|
+
version: getVersion(),
|
|
4408
|
+
skills: { enabled: true },
|
|
4409
|
+
agents: { enabled: true, default: "build" },
|
|
4410
|
+
commands: { enabled: true },
|
|
4411
|
+
tools: { enabled: true },
|
|
4412
|
+
plugins: { enabled: true },
|
|
4413
|
+
memory: { enabled: true },
|
|
4414
|
+
beads: { enabled: true },
|
|
4415
|
+
antiHallucination: { enabled: true }
|
|
4416
|
+
};
|
|
4417
|
+
await writeFile8(
|
|
4418
|
+
join13(configDir, "aikit.json"),
|
|
4419
|
+
JSON.stringify(defaultConfig, null, 2)
|
|
4420
|
+
);
|
|
4421
|
+
const agentsMd = `# AIKit Agent Rules
|
|
4422
|
+
|
|
4423
|
+
## Build Commands
|
|
4424
|
+
- \`npm run build\` - Build the project
|
|
4425
|
+
- \`npm run test\` - Run tests
|
|
4426
|
+
- \`npm run lint\` - Run linting
|
|
4427
|
+
|
|
4428
|
+
## Code Style
|
|
4429
|
+
- Use 2 spaces for indentation
|
|
4430
|
+
- Use single quotes for strings
|
|
4431
|
+
- Add trailing commas
|
|
4432
|
+
|
|
4433
|
+
## Naming Conventions
|
|
4434
|
+
- Variables: camelCase
|
|
4435
|
+
- Components: PascalCase
|
|
4436
|
+
- Files: kebab-case
|
|
4437
|
+
|
|
4438
|
+
## Project-Specific Rules
|
|
4439
|
+
Add your project-specific rules here.
|
|
4440
|
+
`;
|
|
4441
|
+
await writeFile8(join13(configDir, "AGENTS.md"), agentsMd);
|
|
4442
|
+
}
|
|
4443
|
+
async function configureMcpServer(projectPath) {
|
|
4444
|
+
const currentFile = fileURLToPath3(import.meta.url);
|
|
4445
|
+
const currentDir = dirname2(currentFile);
|
|
4446
|
+
const aikitPath = join13(currentDir, "..", "..");
|
|
4447
|
+
const mcpServerPath = join13(aikitPath, "dist", "mcp-server.js");
|
|
4448
|
+
const configLocations = [
|
|
4449
|
+
// Global config (most common)
|
|
4450
|
+
join13(homedir3(), ".config", "opencode", "opencode.json"),
|
|
4451
|
+
// Project-level config
|
|
4452
|
+
join13(projectPath, ".opencode", "opencode.json"),
|
|
4453
|
+
// Alternative global location
|
|
4454
|
+
join13(homedir3(), ".opencode", "opencode.json")
|
|
4455
|
+
];
|
|
4456
|
+
const mcpServerConfig = {
|
|
4457
|
+
type: "local",
|
|
4458
|
+
command: ["node", mcpServerPath],
|
|
4459
|
+
environment: {}
|
|
4460
|
+
};
|
|
4461
|
+
for (const configPath of configLocations) {
|
|
4460
4462
|
try {
|
|
4461
|
-
const
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
const user = userSkills.find((s) => s.name === sourceSkill.name);
|
|
4471
|
-
if (!installed) {
|
|
4472
|
-
newSkills.push(sourceSkill);
|
|
4473
|
-
} else if (installed.hash !== sourceSkill.hash) {
|
|
4474
|
-
modifiedSkills.push(sourceSkill);
|
|
4475
|
-
if (user && user.hash !== installed.hash) {
|
|
4476
|
-
conflicts.push({
|
|
4477
|
-
skillName: sourceSkill.name,
|
|
4478
|
-
userHash: user.hash,
|
|
4479
|
-
sourceHash: sourceSkill.hash,
|
|
4480
|
-
installedHash: installed.hash,
|
|
4481
|
-
userModified: user.hash !== installed.hash,
|
|
4482
|
-
sourceModified: sourceSkill.hash !== installed.hash
|
|
4483
|
-
});
|
|
4463
|
+
const configDir = join13(configPath, "..");
|
|
4464
|
+
await mkdir8(configDir, { recursive: true });
|
|
4465
|
+
let config = {};
|
|
4466
|
+
if (existsSync5(configPath)) {
|
|
4467
|
+
try {
|
|
4468
|
+
const existing = await readFile7(configPath, "utf-8");
|
|
4469
|
+
config = JSON.parse(existing);
|
|
4470
|
+
} catch {
|
|
4471
|
+
config = {};
|
|
4484
4472
|
}
|
|
4485
4473
|
}
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
const existsInSource = sourceSkills.find((s) => s.name === name);
|
|
4489
|
-
if (!existsInSource) {
|
|
4490
|
-
removedSkills.push(installedSkill);
|
|
4474
|
+
if (!config.mcp) {
|
|
4475
|
+
config.mcp = {};
|
|
4491
4476
|
}
|
|
4477
|
+
config.mcp.aikit = mcpServerConfig;
|
|
4478
|
+
await writeFile8(configPath, JSON.stringify(config, null, 2));
|
|
4479
|
+
logger.success(`
|
|
4480
|
+
\u2705 MCP server configured: ${configPath}`);
|
|
4481
|
+
logger.info(` Server: node ${mcpServerPath}`);
|
|
4482
|
+
return;
|
|
4483
|
+
} catch {
|
|
4484
|
+
continue;
|
|
4492
4485
|
}
|
|
4493
|
-
return {
|
|
4494
|
-
newSkills,
|
|
4495
|
-
modifiedSkills,
|
|
4496
|
-
removedSkills,
|
|
4497
|
-
conflicts,
|
|
4498
|
-
configChanges: []
|
|
4499
|
-
// Will be detected separately
|
|
4500
|
-
};
|
|
4501
4486
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
category: this.extractCategory(dir, skillsPath)
|
|
4522
|
-
});
|
|
4523
|
-
}
|
|
4524
|
-
}
|
|
4525
|
-
};
|
|
4526
|
-
await loadFromDir(skillsPath);
|
|
4527
|
-
} catch (error) {
|
|
4528
|
-
logger.debug(`Could not load skills from ${skillsPath}:`, error);
|
|
4487
|
+
const instructionsPath = join13(projectPath, ".opencode", "MCP_SETUP.md");
|
|
4488
|
+
await mkdir8(join13(projectPath, ".opencode"), { recursive: true });
|
|
4489
|
+
await writeFile8(instructionsPath, `# AIKit MCP Server Configuration
|
|
4490
|
+
|
|
4491
|
+
## Automatic Setup Failed
|
|
4492
|
+
|
|
4493
|
+
Please manually configure the MCP server in OpenCode.
|
|
4494
|
+
|
|
4495
|
+
## Configuration
|
|
4496
|
+
|
|
4497
|
+
Add this to your OpenCode configuration file (\`~/.config/opencode/opencode.json\`):
|
|
4498
|
+
|
|
4499
|
+
\`\`\`json
|
|
4500
|
+
{
|
|
4501
|
+
"mcpServers": {
|
|
4502
|
+
"aikit": {
|
|
4503
|
+
"command": "node",
|
|
4504
|
+
"args": ["${mcpServerPath}"],
|
|
4505
|
+
"env": {}
|
|
4529
4506
|
}
|
|
4530
|
-
return hashes;
|
|
4531
4507
|
}
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4508
|
+
}
|
|
4509
|
+
\`\`\`
|
|
4510
|
+
|
|
4511
|
+
## After Configuration
|
|
4512
|
+
|
|
4513
|
+
1. Restart OpenCode completely
|
|
4514
|
+
2. OpenCode will automatically start the MCP server
|
|
4515
|
+
3. Tools will be available via MCP protocol
|
|
4516
|
+
4. You can use tools like \`tool_read_figma_design\` directly
|
|
4517
|
+
|
|
4518
|
+
## Verify
|
|
4519
|
+
|
|
4520
|
+
After restarting OpenCode, check:
|
|
4521
|
+
- MCP server is running (check OpenCode settings)
|
|
4522
|
+
- Tools are discoverable (OpenCode should list them)
|
|
4523
|
+
- You can call tools via MCP protocol
|
|
4524
|
+
`);
|
|
4525
|
+
logger.warn(`
|
|
4526
|
+
\u26A0\uFE0F Could not auto-configure MCP server. See: ${instructionsPath}`);
|
|
4527
|
+
}
|
|
4528
|
+
async function installCliTool(tool) {
|
|
4529
|
+
try {
|
|
4530
|
+
logger.info(`Installing ${tool.displayName}...`);
|
|
4531
|
+
switch (tool.name) {
|
|
4532
|
+
case "opencode" /* OPENCODE */:
|
|
4533
|
+
await installToOpenCode(paths.opencodeConfig());
|
|
4534
|
+
break;
|
|
4535
|
+
case "claude" /* CLAUDE */:
|
|
4536
|
+
execSync2("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
|
|
4537
|
+
break;
|
|
4538
|
+
case "github" /* GITHUB */:
|
|
4539
|
+
execSync2("npm install -g gh", { stdio: "inherit" });
|
|
4540
|
+
break;
|
|
4551
4541
|
}
|
|
4552
|
-
|
|
4542
|
+
logger.success(`\u2713 ${tool.displayName} installed`);
|
|
4543
|
+
return true;
|
|
4544
|
+
} catch (error) {
|
|
4545
|
+
logger.error(`Failed to install ${tool.displayName}:`, error);
|
|
4546
|
+
return false;
|
|
4553
4547
|
}
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4548
|
+
}
|
|
4549
|
+
var AGENT_FILES = {
|
|
4550
|
+
rush: `---
|
|
4551
|
+
description: Fast execution for small/urgent changes with minimal planning.
|
|
4552
|
+
mode: subagent
|
|
4553
|
+
tools:
|
|
4554
|
+
"*": true
|
|
4555
|
+
---
|
|
4556
|
+
|
|
4557
|
+
Use for quick fixes, hotfixes, or tiny edits. Keep scope minimal and verify quickly.`,
|
|
4558
|
+
review: `---
|
|
4559
|
+
description: Code review and quality/security auditing agent.
|
|
4560
|
+
mode: subagent
|
|
4561
|
+
tools:
|
|
4562
|
+
"*": true
|
|
4563
|
+
---
|
|
4564
|
+
|
|
4565
|
+
Use to review correctness, security, performance, maintainability, and tests. Be specific
|
|
4566
|
+
about issues and suggest concrete fixes.`,
|
|
4567
|
+
scout: `---
|
|
4568
|
+
description: Research agent for external docs, patterns, and references.
|
|
4569
|
+
mode: subagent
|
|
4570
|
+
tools:
|
|
4571
|
+
"*": true
|
|
4572
|
+
---
|
|
4573
|
+
|
|
4574
|
+
Use to look up docs, examples, best practices. Summarize findings concisely and cite sources.`,
|
|
4575
|
+
explore: `---
|
|
4576
|
+
description: Codebase navigation agent (search, grep, structure understanding).
|
|
4577
|
+
mode: subagent
|
|
4578
|
+
tools:
|
|
4579
|
+
"*": true
|
|
4580
|
+
---
|
|
4581
|
+
|
|
4582
|
+
Use to locate files, patterns, dependencies, and gather quick context in the repo.`,
|
|
4583
|
+
vision: `---
|
|
4584
|
+
description: Visual analysis agent for mockups, screenshots, PDFs, diagrams.
|
|
4585
|
+
mode: subagent
|
|
4586
|
+
tools:
|
|
4587
|
+
"*": true
|
|
4588
|
+
---
|
|
4589
|
+
|
|
4590
|
+
Use to interpret visual assets (components, layout, colors, typography) and translate to tasks.`,
|
|
4591
|
+
"one-shot": `---
|
|
4592
|
+
description: End-to-end autonomous task execution (beta). Complete tasks from start to finish.
|
|
4593
|
+
mode: subagent
|
|
4594
|
+
tools:
|
|
4595
|
+
"*": true
|
|
4596
|
+
---
|
|
4597
|
+
|
|
4598
|
+
\u26A0\uFE0F BETA: This mode is experimental. Use for straightforward tasks first.
|
|
4599
|
+
|
|
4600
|
+
## One-Shot Mode - Autonomous Task Execution
|
|
4601
|
+
|
|
4602
|
+
Execute tasks end-to-end with minimal intervention:
|
|
4603
|
+
|
|
4604
|
+
### Workflow Phases
|
|
4605
|
+
1. **REQUIREMENTS** - Gather task type, scope, dependencies, success criteria
|
|
4606
|
+
2. **PLANNING** - Create detailed plan, recommend skills/tools, create tracking bead
|
|
4607
|
+
3. **COMPLEXITY** - Auto-split if: >30min, >10 files, >500 lines, >2 sub-systems
|
|
4608
|
+
4. **EXECUTION** - Parallel tasks (max 3), dynamic agent delegation
|
|
4609
|
+
5. **TESTING** - Run until pass: typecheck \u2192 test \u2192 lint \u2192 build (max 3 retries)
|
|
4610
|
+
6. **VERIFICATION** - Quality gates \u2713 \u2192 Manual verification \u2192 Deployment approval
|
|
4611
|
+
7. **COMPLETION** - Generate proof, update tracking, collect feedback
|
|
4612
|
+
|
|
4613
|
+
### Quality Gates (ALL must pass)
|
|
4614
|
+
- \`npm run typecheck\` - No type errors
|
|
4615
|
+
- \`npm run test\` - All tests pass
|
|
4616
|
+
- \`npm run lint\` - No lint errors
|
|
4617
|
+
- \`npm run build\` - Build succeeds
|
|
4618
|
+
|
|
4619
|
+
### Error Recovery (3 Levels)
|
|
4620
|
+
- **Level 1**: Auto-fix (type errors, lint --fix)
|
|
4621
|
+
- **Level 2**: Alternative approach via @review
|
|
4622
|
+
- **Level 3**: User intervention + follow-up task
|
|
4623
|
+
|
|
4624
|
+
### Delegates To
|
|
4625
|
+
@planner for planning, @build for implementation, @review for code review,
|
|
4626
|
+
@scout for research, @explore for navigation, @vision for visual analysis.
|
|
4627
|
+
|
|
4628
|
+
### Best Use Cases
|
|
4629
|
+
- Straightforward features with clear scope
|
|
4630
|
+
- Bug fixes with known reproduction steps
|
|
4631
|
+
- Refactoring with defined boundaries
|
|
4632
|
+
|
|
4633
|
+
### Consider Alternatives For
|
|
4634
|
+
- Complex multi-system features \u2192 Use /plan + /implement
|
|
4635
|
+
- Exploratory research \u2192 Use /research first
|
|
4636
|
+
- Critical production changes \u2192 Manual with /review`
|
|
4637
|
+
};
|
|
4638
|
+
function generateAnalyzeFigmaCommand() {
|
|
4639
|
+
return `# Command: /analyze-figma
|
|
4640
|
+
|
|
4641
|
+
## Description
|
|
4642
|
+
Analyze a Figma design and extract design tokens
|
|
4643
|
+
|
|
4644
|
+
## Usage
|
|
4645
|
+
\`/analyze-figma <figma-url>\`
|
|
4646
|
+
|
|
4647
|
+
## Examples
|
|
4648
|
+
- \`/analyze-figma https://www.figma.com/design/...\`
|
|
4649
|
+
|
|
4650
|
+
## \u26A0\uFE0F CRITICAL: Extract URL FIRST!
|
|
4651
|
+
|
|
4652
|
+
**BEFORE ANYTHING ELSE**: Look at the user's FULL input message (all lines) and find the Figma URL. It's ALWAYS there - never ask for it!
|
|
4653
|
+
|
|
4654
|
+
**The URL pattern**: Look for text containing \`figma.com/design/\` anywhere in the user's message.
|
|
4655
|
+
|
|
4656
|
+
**Example of what user input looks like**:
|
|
4657
|
+
\`\`\`
|
|
4658
|
+
/analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/
|
|
4659
|
+
Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0
|
|
4660
|
+
\`\`\`
|
|
4661
|
+
|
|
4662
|
+
**Extract the complete URL** (combine if split):
|
|
4663
|
+
\`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0\`
|
|
4664
|
+
|
|
4665
|
+
## Workflow
|
|
4666
|
+
|
|
4667
|
+
**IMPORTANT**: When user provides a Figma URL, you MUST immediately:
|
|
4668
|
+
|
|
4669
|
+
**Step 1: Extract URL from User Input**
|
|
4670
|
+
|
|
4671
|
+
**CRITICAL**: The URL is ALWAYS in the user's input message! DO NOT ask for it - just extract it!
|
|
4672
|
+
|
|
4673
|
+
**MANDATORY**: You MUST extract the URL before proceeding. This is not optional!
|
|
4674
|
+
|
|
4675
|
+
**How to Extract**:
|
|
4676
|
+
1. **Read the ENTIRE user input message** - look at ALL lines, not just the first line
|
|
4677
|
+
2. **Search for ANY text containing** \`figma.com/design/\` - this is the URL
|
|
4678
|
+
3. **URL may appear in different formats**:
|
|
4679
|
+
- On same line: \`/analyze-figma https://www.figma.com/design/...\`
|
|
4680
|
+
- Split across lines
|
|
4681
|
+
4. **Extract the COMPLETE URL**:
|
|
4682
|
+
- Start from \`https://\` or \`http://\`
|
|
4683
|
+
- Include everything until the end of the line or next whitespace
|
|
4684
|
+
- If URL is split, combine ALL parts into one complete URL
|
|
4685
|
+
5. **Include ALL query parameters**: \`?node-id=...\`, \`&t=...\`, etc.
|
|
4686
|
+
|
|
4687
|
+
**CRITICAL RULES**:
|
|
4688
|
+
- \u2705 DO: Read the ENTIRE user message (all lines)
|
|
4689
|
+
- \u2705 DO: Look for \`figma.com/design/\` anywhere in the message
|
|
4690
|
+
- \u2705 DO: Combine split lines into one URL
|
|
4691
|
+
- \u274C DO NOT: Ask user for URL - it's ALWAYS in the input
|
|
4692
|
+
- \u274C DO NOT: Skip this step - URL extraction is MANDATORY
|
|
4693
|
+
- \u274C DO NOT: Proceed without extracting URL first
|
|
4694
|
+
|
|
4695
|
+
**Step 2: Check Tool Configuration**
|
|
4696
|
+
|
|
4697
|
+
Before calling the tool, verify that Figma tool is configured:
|
|
4698
|
+
- If not configured, inform user to run: \`aikit skills figma-analysis config\`
|
|
4699
|
+
- The tool requires a Figma Personal Access Token
|
|
4700
|
+
|
|
4701
|
+
**Step 3: Call MCP Tool read_figma_design**
|
|
4702
|
+
|
|
4703
|
+
Use the MCP tool \`read_figma_design\` with the extracted URL:
|
|
4704
|
+
\`\`\`
|
|
4705
|
+
Use tool: read_figma_design
|
|
4706
|
+
Arguments: { "url": "[extracted URL]" }
|
|
4707
|
+
\`\`\`
|
|
4708
|
+
|
|
4709
|
+
**Step 4: Format and Save**
|
|
4710
|
+
|
|
4711
|
+
Format extracted tokens as structured markdown and save using memory-update tool.
|
|
4712
|
+
|
|
4713
|
+
**Step 5: Report Results**
|
|
4714
|
+
|
|
4715
|
+
Report what was extracted:
|
|
4716
|
+
- Number of screens found
|
|
4717
|
+
- Number of colors in palette
|
|
4718
|
+
- Typography styles found
|
|
4719
|
+
- Components identified
|
|
4720
|
+
|
|
4721
|
+
## Critical Instructions
|
|
4722
|
+
|
|
4723
|
+
- **DO NOT** ask user to "share the Figma URL" - they already provided it in the command
|
|
4724
|
+
- **DO NOT** wait for confirmation - just start analyzing immediately
|
|
4725
|
+
- **DO** extract URL from full user input message
|
|
4726
|
+
- **DO** call MCP tool \`read_figma_design\` immediately
|
|
4727
|
+
- **DO** save to memory automatically`;
|
|
4728
|
+
}
|
|
4729
|
+
async function installToOpenCode(_opencodePath) {
|
|
4730
|
+
const projectPath = process.cwd();
|
|
4731
|
+
const opencodeCommandDir = join13(projectPath, ".opencode", "command");
|
|
4732
|
+
const aikitDir = join13(projectPath, ".aikit");
|
|
4733
|
+
const opencodeAgentDir = join13(paths.opencodeConfig(), "agent");
|
|
4734
|
+
await mkdir8(opencodeCommandDir, { recursive: true });
|
|
4735
|
+
await mkdir8(join13(aikitDir, "skills"), { recursive: true });
|
|
4736
|
+
await mkdir8(opencodeAgentDir, { recursive: true });
|
|
4737
|
+
for (const [name, content] of Object.entries(AGENT_FILES)) {
|
|
4738
|
+
const filePath = join13(opencodeAgentDir, `${name}.md`);
|
|
4559
4739
|
try {
|
|
4560
|
-
await
|
|
4561
|
-
|
|
4562
|
-
|
|
4740
|
+
await access4(filePath);
|
|
4741
|
+
const existingContent = await readFile7(filePath, "utf8");
|
|
4742
|
+
if (!existingContent.includes("mode: subagent")) {
|
|
4743
|
+
const matter3 = await import("gray-matter");
|
|
4744
|
+
const { data: frontmatter, content: body } = matter3.default(existingContent);
|
|
4745
|
+
frontmatter.mode = "subagent";
|
|
4746
|
+
const updatedContent = matter3.default.stringify(body, frontmatter);
|
|
4747
|
+
await writeFile8(filePath, updatedContent, "utf8");
|
|
4748
|
+
}
|
|
4749
|
+
} catch {
|
|
4750
|
+
await writeFile8(filePath, content, "utf8");
|
|
4563
4751
|
}
|
|
4564
4752
|
}
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
migrationHistory: []
|
|
4574
|
-
};
|
|
4575
|
-
const updated = {
|
|
4576
|
-
installedVersion: version,
|
|
4577
|
-
lastSynced: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4578
|
-
packageVersion: this.getPackageVersion(),
|
|
4579
|
-
migrationHistory: migration ? [...current.migrationHistory, migration] : current.migrationHistory
|
|
4580
|
-
};
|
|
4581
|
-
const versionPath = join13(paths.globalConfig(), ".version.json");
|
|
4582
|
-
await writeFile8(versionPath, JSON.stringify(updated, null, 2));
|
|
4583
|
-
}
|
|
4584
|
-
/**
|
|
4585
|
-
* Check if migration is needed
|
|
4586
|
-
*/
|
|
4587
|
-
async needsMigration() {
|
|
4588
|
-
const current = await this.getCurrentVersion();
|
|
4589
|
-
const packageVersion = this.getPackageVersion();
|
|
4590
|
-
return current?.installedVersion !== packageVersion;
|
|
4591
|
-
}
|
|
4592
|
-
};
|
|
4753
|
+
const config = await loadConfig();
|
|
4754
|
+
const skillEngine = new SkillEngine(config);
|
|
4755
|
+
const commandRunner = new CommandRunner(config);
|
|
4756
|
+
const skills = await skillEngine.listSkills();
|
|
4757
|
+
const commands = await commandRunner.listCommands();
|
|
4758
|
+
const opencodeCommands = {};
|
|
4759
|
+
const skillsList = skills.map((s) => `| \`/${s.name.replace(/\s+/g, "-")}\` | ${s.description} |`).join("\n");
|
|
4760
|
+
opencodeCommands["skills"] = `List all available AIKit skills and how to use them.
|
|
4593
4761
|
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4762
|
+
READ .aikit/AGENTS.md
|
|
4763
|
+
|
|
4764
|
+
## Available Skills
|
|
4765
|
+
|
|
4766
|
+
| Command | Description |
|
|
4767
|
+
|---------|-------------|
|
|
4768
|
+
${skillsList}
|
|
4769
|
+
|
|
4770
|
+
Type any command to use that skill. For example: \`/test-driven-development\` or \`/tdd\`.`;
|
|
4771
|
+
for (const skill of skills) {
|
|
4772
|
+
const commandName = skill.name.replace(/\s+/g, "-").toLowerCase();
|
|
4773
|
+
const skillPath = skill.filePath;
|
|
4774
|
+
const relativePath = skillPath.startsWith(projectPath) ? skillPath.replace(projectPath, "").replace(/\\/g, "/").replace(/^\//, "") : `.aikit/skills/${skill.name.replace(/\s+/g, "-").toLowerCase()}.md`;
|
|
4775
|
+
const useWhen = skill.useWhen || `The user asks you to ${skill.name}`;
|
|
4776
|
+
opencodeCommands[commandName] = `Use the **${skill.name} skill** ${useWhen.toLowerCase()}.
|
|
4777
|
+
|
|
4778
|
+
READ ${relativePath}
|
|
4779
|
+
|
|
4780
|
+
## Description
|
|
4781
|
+
${skill.description}
|
|
4782
|
+
|
|
4783
|
+
## When to Use
|
|
4784
|
+
${useWhen}
|
|
4785
|
+
|
|
4786
|
+
## Workflow
|
|
4787
|
+
${skill.content.split("\n").slice(0, 20).join("\n")}${skill.content.split("\n").length > 20 ? "\n\n... (see full skill file for complete workflow)" : ""}
|
|
4788
|
+
|
|
4789
|
+
**IMPORTANT**: Follow this skill's workflow step by step. Do not skip steps.
|
|
4790
|
+
Complete the checklist at the end of the skill.`;
|
|
4608
4791
|
}
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
backupId,
|
|
4632
|
-
fromVersion,
|
|
4633
|
-
toVersion,
|
|
4634
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4635
|
-
files,
|
|
4636
|
-
success: true
|
|
4637
|
-
};
|
|
4638
|
-
const manifestPath = join14(backupPath, "backup-manifest.json");
|
|
4639
|
-
await writeFile9(manifestPath, JSON.stringify(manifest, null, 2));
|
|
4640
|
-
await this.cleanupOldBackups();
|
|
4641
|
-
logger.success(`\u2713 Backup created: ${backupId}`);
|
|
4642
|
-
return backupId;
|
|
4643
|
-
} catch (error) {
|
|
4644
|
-
logger.error("Failed to create backup:", error);
|
|
4645
|
-
return null;
|
|
4792
|
+
for (const cmd of commands) {
|
|
4793
|
+
if (opencodeCommands[cmd.name]) continue;
|
|
4794
|
+
const commandName = cmd.name.replace(/\//g, "").replace(/\s+/g, "-");
|
|
4795
|
+
const examples = cmd.examples.map((e) => `- \`${e}\``).join("\n");
|
|
4796
|
+
if (cmd.name === "analyze-figma") {
|
|
4797
|
+
opencodeCommands[commandName] = generateAnalyzeFigmaCommand();
|
|
4798
|
+
} else {
|
|
4799
|
+
opencodeCommands[commandName] = `# Command: /${cmd.name}
|
|
4800
|
+
|
|
4801
|
+
## Description
|
|
4802
|
+
${cmd.description}
|
|
4803
|
+
|
|
4804
|
+
## Usage
|
|
4805
|
+
\`${cmd.usage}\`
|
|
4806
|
+
|
|
4807
|
+
## Examples
|
|
4808
|
+
${examples}
|
|
4809
|
+
|
|
4810
|
+
## Workflow
|
|
4811
|
+
${cmd.content}
|
|
4812
|
+
|
|
4813
|
+
**Category**: ${cmd.category}`;
|
|
4646
4814
|
}
|
|
4647
4815
|
}
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4816
|
+
let count = 0;
|
|
4817
|
+
for (const [name, content] of Object.entries(opencodeCommands)) {
|
|
4818
|
+
const filePath = join13(opencodeCommandDir, `${name}.md`);
|
|
4819
|
+
await writeFile8(filePath, content.trim());
|
|
4820
|
+
logger.info(` \u2713 Created /${name} command`);
|
|
4821
|
+
count++;
|
|
4822
|
+
}
|
|
4823
|
+
logger.success(`
|
|
4824
|
+
Created ${count} OpenCode commands in .opencode/command/`);
|
|
4825
|
+
await configureMcpServer(projectPath);
|
|
4826
|
+
logger.info("\nUsage in OpenCode:");
|
|
4827
|
+
logger.info(" Press Ctrl+K to open command picker");
|
|
4828
|
+
logger.info(" Or type /skills to see all available skills");
|
|
4829
|
+
logger.info(` Available: ${skills.length} skills, ${commands.length} commands`);
|
|
4830
|
+
logger.info(" MCP server configured - tools available via MCP protocol");
|
|
4831
|
+
}
|
|
4832
|
+
function groupBy(array, keyFn) {
|
|
4833
|
+
return array.reduce((acc, item) => {
|
|
4834
|
+
const key = keyFn(item);
|
|
4835
|
+
if (!acc[key]) acc[key] = [];
|
|
4836
|
+
acc[key].push(item);
|
|
4837
|
+
return acc;
|
|
4838
|
+
}, {});
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
// src/cli/commands/init.ts
|
|
4842
|
+
function registerInitCommand(program2) {
|
|
4843
|
+
program2.command("init").description("Initialize AIKit configuration").option("-g, --global", "Initialize global configuration").option("-p, --project", "Initialize project-level configuration").action(async (options) => {
|
|
4844
|
+
const configDir = options.global ? paths.globalConfig() : paths.projectConfig();
|
|
4845
|
+
console.log(chalk2.bold("\n\u{1F680} AIKit Setup\n"));
|
|
4846
|
+
logger.info(`Initializing AIKit in ${configDir}...`);
|
|
4655
4847
|
try {
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
const
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4848
|
+
await initializeConfig(configDir, options.global);
|
|
4849
|
+
logger.success("\u2713 Configuration created");
|
|
4850
|
+
if (!options.global) {
|
|
4851
|
+
const config = await loadConfig();
|
|
4852
|
+
const engine = new SkillEngine(config);
|
|
4853
|
+
const result = await engine.syncSkillsToProject();
|
|
4854
|
+
if (result.count > 0) {
|
|
4855
|
+
logger.success(`\u2713 Synced ${result.count} skills`);
|
|
4663
4856
|
}
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
const
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4857
|
+
console.log(chalk2.bold("\n\u{1F50D} Checking CLI tools...\n"));
|
|
4858
|
+
const cliTools = await CliDetector.checkAll();
|
|
4859
|
+
const installableTools = CliDetector.filterInstallable(cliTools);
|
|
4860
|
+
for (const tool of cliTools) {
|
|
4861
|
+
const status = tool.installed ? chalk2.green("\u2713 Installed") : chalk2.yellow("\u2717 Not installed");
|
|
4862
|
+
const version = tool.version ? chalk2.gray(` (${tool.version})`) : "";
|
|
4863
|
+
console.log(` ${status} ${chalk2.cyan(tool.displayName)}${version}`);
|
|
4864
|
+
}
|
|
4865
|
+
if (installableTools.length > 0) {
|
|
4866
|
+
console.log();
|
|
4867
|
+
const { action } = await inquirer.prompt([
|
|
4868
|
+
{
|
|
4869
|
+
type: "list",
|
|
4870
|
+
name: "action",
|
|
4871
|
+
message: "How would you like to proceed?",
|
|
4872
|
+
choices: [
|
|
4873
|
+
{
|
|
4874
|
+
name: "all",
|
|
4875
|
+
value: "all",
|
|
4876
|
+
short: "a",
|
|
4877
|
+
message: "Install all missing CLI tools"
|
|
4878
|
+
},
|
|
4879
|
+
{
|
|
4880
|
+
name: "select",
|
|
4881
|
+
value: "select",
|
|
4882
|
+
short: "s",
|
|
4883
|
+
message: "Select specific tools to install (use space to select, Enter to confirm)"
|
|
4884
|
+
},
|
|
4885
|
+
{
|
|
4886
|
+
name: "skip",
|
|
4887
|
+
value: "skip",
|
|
4888
|
+
short: "n",
|
|
4889
|
+
message: "Skip CLI tool installation"
|
|
4890
|
+
}
|
|
4891
|
+
],
|
|
4892
|
+
default: "all"
|
|
4893
|
+
}
|
|
4894
|
+
]);
|
|
4895
|
+
if (action === "skip") {
|
|
4896
|
+
console.log();
|
|
4897
|
+
logger.info("Skipping CLI tool installation");
|
|
4898
|
+
} else if (action === "all") {
|
|
4899
|
+
console.log();
|
|
4900
|
+
logger.info(`Installing ${installableTools.length} CLI tool(s)...`);
|
|
4901
|
+
for (const tool of installableTools) {
|
|
4902
|
+
await installCliTool(tool);
|
|
4903
|
+
}
|
|
4904
|
+
console.log();
|
|
4905
|
+
logger.success("\u2713 CLI tools installed");
|
|
4906
|
+
} else {
|
|
4907
|
+
const { installTools } = await inquirer.prompt([
|
|
4908
|
+
{
|
|
4909
|
+
type: "checkbox",
|
|
4910
|
+
name: "installTools",
|
|
4911
|
+
message: "Select CLI tools to install (press Enter to skip):",
|
|
4912
|
+
choices: installableTools.map((tool) => ({
|
|
4913
|
+
name: tool.name,
|
|
4914
|
+
value: tool,
|
|
4915
|
+
checked: true
|
|
4916
|
+
// Default to install all
|
|
4917
|
+
}))
|
|
4918
|
+
}
|
|
4919
|
+
]);
|
|
4920
|
+
if (installTools.length > 0) {
|
|
4921
|
+
console.log();
|
|
4922
|
+
logger.info(`Installing ${installTools.length} CLI tool(s)...`);
|
|
4923
|
+
for (const tool of installTools) {
|
|
4924
|
+
await installCliTool(tool);
|
|
4925
|
+
}
|
|
4926
|
+
console.log();
|
|
4927
|
+
logger.success("\u2713 CLI tools installed");
|
|
4928
|
+
} else {
|
|
4929
|
+
console.log();
|
|
4930
|
+
logger.info("Skipping CLI tool installation");
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
} else {
|
|
4934
|
+
console.log();
|
|
4935
|
+
logger.success("\u2713 All CLI tools already installed");
|
|
4936
|
+
}
|
|
4937
|
+
const beads = new BeadsIntegration();
|
|
4938
|
+
const beadsStatus = await beads.getStatus();
|
|
4939
|
+
if (!beadsStatus.initialized) {
|
|
4940
|
+
logger.info("Initializing .beads directory...");
|
|
4941
|
+
await beads.initLocal();
|
|
4942
|
+
logger.success("\u2713 .beads directory created");
|
|
4943
|
+
if (!beadsStatus.installed) {
|
|
4944
|
+
logger.info("Tip: Install Beads CLI globally for full functionality: npm install -g beads");
|
|
4945
|
+
}
|
|
4946
|
+
} else {
|
|
4947
|
+
logger.info("Beads already initialized");
|
|
4948
|
+
}
|
|
4949
|
+
const opencodePath = paths.opencodeConfig();
|
|
4950
|
+
await installToOpenCode(opencodePath);
|
|
4951
|
+
console.log(chalk2.bold("\n\u2728 AIKit is ready!\n"));
|
|
4952
|
+
console.log("Usage in OpenCode:");
|
|
4953
|
+
console.log(chalk2.cyan(" /skills") + " - List all available skills");
|
|
4954
|
+
console.log(chalk2.cyan(" /plan") + " - Create implementation plan");
|
|
4955
|
+
console.log(chalk2.cyan(" /tdd") + " - Test-driven development");
|
|
4956
|
+
console.log(chalk2.cyan(" /debug") + " - Systematic debugging");
|
|
4957
|
+
console.log(chalk2.cyan(" /review") + " - Code review checklist");
|
|
4958
|
+
console.log(chalk2.cyan(" /git") + " - Git workflow");
|
|
4959
|
+
console.log(chalk2.cyan(" /frontend-aesthetics") + " - UI/UX guidelines");
|
|
4960
|
+
console.log("\nPress " + chalk2.bold("Ctrl+K") + " in OpenCode to see all commands.\n");
|
|
4673
4961
|
}
|
|
4674
4962
|
} catch (error) {
|
|
4675
|
-
logger.
|
|
4963
|
+
logger.error("Failed to initialize AIKit:", error);
|
|
4964
|
+
process.exit(1);
|
|
4676
4965
|
}
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4966
|
+
});
|
|
4967
|
+
}
|
|
4968
|
+
|
|
4969
|
+
// src/cli/commands/install.ts
|
|
4970
|
+
init_esm_shims();
|
|
4971
|
+
init_logger();
|
|
4972
|
+
init_paths();
|
|
4973
|
+
function registerInstallCommand(program2) {
|
|
4974
|
+
program2.command("install").description("Install AIKit to OpenCode configuration").action(async () => {
|
|
4975
|
+
logger.info("Installing AIKit to OpenCode...");
|
|
4976
|
+
try {
|
|
4977
|
+
const opencodePath = paths.opencodeConfig();
|
|
4978
|
+
await installToOpenCode(opencodePath);
|
|
4979
|
+
logger.success("AIKit installed to OpenCode!");
|
|
4980
|
+
} catch (error) {
|
|
4981
|
+
logger.error("Failed to install:", error);
|
|
4982
|
+
process.exit(1);
|
|
4983
|
+
}
|
|
4984
|
+
});
|
|
4985
|
+
}
|
|
4986
|
+
|
|
4987
|
+
// src/cli/commands/sync.ts
|
|
4988
|
+
init_esm_shims();
|
|
4989
|
+
import chalk4 from "chalk";
|
|
4990
|
+
|
|
4991
|
+
// src/core/sync-engine.ts
|
|
4992
|
+
init_esm_shims();
|
|
4993
|
+
import { readFile as readFile11, writeFile as writeFile12, copyFile, mkdir as mkdir10 } from "fs/promises";
|
|
4994
|
+
import { join as join17, dirname as dirname4 } from "path";
|
|
4995
|
+
import inquirer2 from "inquirer";
|
|
4996
|
+
import chalk3 from "chalk";
|
|
4997
|
+
|
|
4998
|
+
// src/core/version-manager.ts
|
|
4999
|
+
init_esm_shims();
|
|
5000
|
+
init_paths();
|
|
5001
|
+
init_logger();
|
|
5002
|
+
import { readFile as readFile8, readdir as readdir7, writeFile as writeFile9, stat } from "fs/promises";
|
|
5003
|
+
import { join as join14 } from "path";
|
|
5004
|
+
import { createHash } from "crypto";
|
|
5005
|
+
var VersionManager = class {
|
|
5006
|
+
config;
|
|
5007
|
+
constructor(config) {
|
|
5008
|
+
this.config = config;
|
|
4685
5009
|
}
|
|
4686
5010
|
/**
|
|
4687
|
-
*
|
|
5011
|
+
* Get current installed version
|
|
4688
5012
|
*/
|
|
4689
|
-
async
|
|
5013
|
+
async getCurrentVersion() {
|
|
5014
|
+
const versionPath = join14(paths.globalConfig(), ".version.json");
|
|
4690
5015
|
try {
|
|
4691
|
-
const content = await readFile8(
|
|
4692
|
-
return
|
|
5016
|
+
const content = await readFile8(versionPath, "utf-8");
|
|
5017
|
+
return JSON.parse(content);
|
|
4693
5018
|
} catch {
|
|
4694
|
-
return
|
|
5019
|
+
return null;
|
|
4695
5020
|
}
|
|
4696
5021
|
}
|
|
4697
5022
|
/**
|
|
4698
|
-
*
|
|
5023
|
+
* Get package version from package.json
|
|
4699
5024
|
*/
|
|
4700
|
-
|
|
5025
|
+
getPackageVersion() {
|
|
4701
5026
|
try {
|
|
4702
|
-
const
|
|
4703
|
-
|
|
4704
|
-
for (const entry of entries) {
|
|
4705
|
-
const backupPath = join14(this.backupsDir, entry);
|
|
4706
|
-
const manifestPath = join14(backupPath, "backup-manifest.json");
|
|
4707
|
-
try {
|
|
4708
|
-
const manifestContent = await readFile8(manifestPath, "utf-8");
|
|
4709
|
-
const manifest = JSON.parse(manifestContent);
|
|
4710
|
-
const size = await this.calculateBackupSize(backupPath);
|
|
4711
|
-
backups.push({
|
|
4712
|
-
manifest,
|
|
4713
|
-
path: backupPath,
|
|
4714
|
-
size
|
|
4715
|
-
});
|
|
4716
|
-
} catch {
|
|
4717
|
-
}
|
|
4718
|
-
}
|
|
4719
|
-
backups.sort(
|
|
4720
|
-
(a, b) => new Date(b.manifest.timestamp).getTime() - new Date(a.manifest.timestamp).getTime()
|
|
4721
|
-
);
|
|
4722
|
-
return backups;
|
|
5027
|
+
const packageJson = __require(join14(process.cwd(), "package.json"));
|
|
5028
|
+
return packageJson.version || "0.0.0";
|
|
4723
5029
|
} catch {
|
|
4724
|
-
return
|
|
5030
|
+
return "0.0.0";
|
|
4725
5031
|
}
|
|
4726
5032
|
}
|
|
4727
5033
|
/**
|
|
4728
|
-
*
|
|
5034
|
+
* Check for updates
|
|
4729
5035
|
*/
|
|
4730
|
-
async
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
}
|
|
5036
|
+
async checkForUpdates() {
|
|
5037
|
+
const installed = await this.getCurrentVersion();
|
|
5038
|
+
const packageVersion = this.getPackageVersion();
|
|
5039
|
+
if (!installed) {
|
|
5040
|
+
return {
|
|
5041
|
+
hasUpdate: true,
|
|
5042
|
+
fromVersion: "none",
|
|
5043
|
+
toVersion: packageVersion,
|
|
5044
|
+
newSkills: [],
|
|
5045
|
+
modifiedSkills: [],
|
|
5046
|
+
removedSkills: [],
|
|
5047
|
+
conflicts: [],
|
|
5048
|
+
configChanges: ["Initial version tracking"]
|
|
4744
5049
|
};
|
|
4745
|
-
await calculate(backupPath);
|
|
4746
|
-
} catch {
|
|
4747
5050
|
}
|
|
4748
|
-
|
|
5051
|
+
const hasUpdate = installed.installedVersion !== packageVersion;
|
|
5052
|
+
if (!hasUpdate) {
|
|
5053
|
+
return {
|
|
5054
|
+
hasUpdate: false,
|
|
5055
|
+
fromVersion: installed.installedVersion,
|
|
5056
|
+
toVersion: packageVersion,
|
|
5057
|
+
newSkills: [],
|
|
5058
|
+
modifiedSkills: [],
|
|
5059
|
+
removedSkills: [],
|
|
5060
|
+
conflicts: [],
|
|
5061
|
+
configChanges: []
|
|
5062
|
+
};
|
|
5063
|
+
}
|
|
5064
|
+
const changes = await this.detectChanges();
|
|
5065
|
+
return {
|
|
5066
|
+
hasUpdate: true,
|
|
5067
|
+
fromVersion: installed.installedVersion,
|
|
5068
|
+
toVersion: packageVersion,
|
|
5069
|
+
...changes
|
|
5070
|
+
};
|
|
4749
5071
|
}
|
|
4750
5072
|
/**
|
|
4751
|
-
*
|
|
5073
|
+
* Detect changes between versions
|
|
4752
5074
|
*/
|
|
4753
|
-
async
|
|
5075
|
+
async detectChanges() {
|
|
5076
|
+
const globalSkillsPath = paths.skills(paths.globalConfig());
|
|
5077
|
+
const projectSkillsPath = paths.skills(this.config.configPath);
|
|
5078
|
+
const sourceSkills = await this.loadSkillHashes(globalSkillsPath);
|
|
5079
|
+
const userSkills = await this.loadSkillHashes(projectSkillsPath);
|
|
5080
|
+
const newSkills = [];
|
|
5081
|
+
const modifiedSkills = [];
|
|
5082
|
+
const removedSkills = [];
|
|
5083
|
+
const conflicts = [];
|
|
5084
|
+
const installedSkills = /* @__PURE__ */ new Map();
|
|
5085
|
+
const installedPath = join14(paths.globalConfig(), ".installed-skills.json");
|
|
4754
5086
|
try {
|
|
4755
|
-
const
|
|
4756
|
-
const
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
5087
|
+
const installedData = await readFile8(installedPath, "utf-8");
|
|
5088
|
+
const installedList = JSON.parse(installedData);
|
|
5089
|
+
installedList.forEach((skill) => {
|
|
5090
|
+
installedSkills.set(skill.name, skill);
|
|
5091
|
+
});
|
|
5092
|
+
} catch {
|
|
5093
|
+
}
|
|
5094
|
+
for (const sourceSkill of sourceSkills) {
|
|
5095
|
+
const installed = installedSkills.get(sourceSkill.name);
|
|
5096
|
+
const user = userSkills.find((s) => s.name === sourceSkill.name);
|
|
5097
|
+
if (!installed) {
|
|
5098
|
+
newSkills.push(sourceSkill);
|
|
5099
|
+
} else if (installed.hash !== sourceSkill.hash) {
|
|
5100
|
+
modifiedSkills.push(sourceSkill);
|
|
5101
|
+
if (user && user.hash !== installed.hash) {
|
|
5102
|
+
conflicts.push({
|
|
5103
|
+
skillName: sourceSkill.name,
|
|
5104
|
+
userHash: user.hash,
|
|
5105
|
+
sourceHash: sourceSkill.hash,
|
|
5106
|
+
installedHash: installed.hash,
|
|
5107
|
+
userModified: user.hash !== installed.hash,
|
|
5108
|
+
sourceModified: sourceSkill.hash !== installed.hash
|
|
5109
|
+
});
|
|
5110
|
+
}
|
|
4766
5111
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
5112
|
+
}
|
|
5113
|
+
for (const [name, installedSkill] of installedSkills.entries()) {
|
|
5114
|
+
const existsInSource = sourceSkills.find((s) => s.name === name);
|
|
5115
|
+
if (!existsInSource) {
|
|
5116
|
+
removedSkills.push(installedSkill);
|
|
4772
5117
|
}
|
|
4773
|
-
logger.success(`\u2713 Backup restored: ${backupId}`);
|
|
4774
|
-
return true;
|
|
4775
|
-
} catch (error) {
|
|
4776
|
-
logger.error("Failed to restore backup:", error);
|
|
4777
|
-
return false;
|
|
4778
5118
|
}
|
|
5119
|
+
return {
|
|
5120
|
+
newSkills,
|
|
5121
|
+
modifiedSkills,
|
|
5122
|
+
removedSkills,
|
|
5123
|
+
conflicts,
|
|
5124
|
+
configChanges: []
|
|
5125
|
+
// Will be detected separately
|
|
5126
|
+
};
|
|
4779
5127
|
}
|
|
4780
5128
|
/**
|
|
4781
|
-
*
|
|
5129
|
+
* Load skill hashes from directory
|
|
4782
5130
|
*/
|
|
4783
|
-
async
|
|
5131
|
+
async loadSkillHashes(skillsPath) {
|
|
5132
|
+
const hashes = [];
|
|
4784
5133
|
try {
|
|
4785
|
-
const
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
5134
|
+
const loadFromDir = async (dir) => {
|
|
5135
|
+
const files = await readdir7(dir);
|
|
5136
|
+
for (const file of files) {
|
|
5137
|
+
const filePath = join14(dir, file);
|
|
5138
|
+
const stats = await stat(filePath);
|
|
5139
|
+
if (stats.isDirectory()) {
|
|
5140
|
+
await loadFromDir(filePath);
|
|
5141
|
+
} else if (file.endsWith(".md")) {
|
|
5142
|
+
const hash = await this.calculateSkillHash(filePath);
|
|
5143
|
+
hashes.push({
|
|
5144
|
+
path: filePath,
|
|
5145
|
+
name: file.replace(".md", ""),
|
|
5146
|
+
hash,
|
|
5147
|
+
category: this.extractCategory(dir, skillsPath)
|
|
5148
|
+
});
|
|
5149
|
+
}
|
|
4794
5150
|
}
|
|
4795
|
-
}
|
|
4796
|
-
|
|
5151
|
+
};
|
|
5152
|
+
await loadFromDir(skillsPath);
|
|
4797
5153
|
} catch (error) {
|
|
4798
|
-
logger.debug(
|
|
4799
|
-
return false;
|
|
5154
|
+
logger.debug(`Could not load skills from ${skillsPath}:`, error);
|
|
4800
5155
|
}
|
|
5156
|
+
return hashes;
|
|
4801
5157
|
}
|
|
4802
5158
|
/**
|
|
4803
|
-
*
|
|
5159
|
+
* Calculate hash for a skill file
|
|
4804
5160
|
*/
|
|
4805
|
-
async
|
|
5161
|
+
async calculateSkillHash(filePath) {
|
|
4806
5162
|
try {
|
|
4807
|
-
const
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
}
|
|
4812
|
-
const entries = await readdir8(backup.path);
|
|
4813
|
-
for (const entry of entries) {
|
|
4814
|
-
const entryPath = join14(backup.path, entry);
|
|
4815
|
-
const stats = await stat2(entryPath);
|
|
4816
|
-
if (stats.isDirectory()) {
|
|
4817
|
-
await this.removeDirectory(entryPath);
|
|
4818
|
-
} else {
|
|
4819
|
-
await unlink(entryPath);
|
|
4820
|
-
}
|
|
4821
|
-
}
|
|
4822
|
-
logger.success(`\u2713 Backup deleted: ${backupId}`);
|
|
4823
|
-
return true;
|
|
4824
|
-
} catch (error) {
|
|
4825
|
-
logger.error("Failed to delete backup:", error);
|
|
4826
|
-
return false;
|
|
5163
|
+
const content = await readFile8(filePath, "utf-8");
|
|
5164
|
+
return createHash("sha256").update(content).digest("hex");
|
|
5165
|
+
} catch {
|
|
5166
|
+
return "";
|
|
4827
5167
|
}
|
|
4828
5168
|
}
|
|
4829
5169
|
/**
|
|
4830
|
-
*
|
|
5170
|
+
* Extract category from path
|
|
4831
5171
|
*/
|
|
4832
|
-
|
|
4833
|
-
const
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
if (stats.isDirectory()) {
|
|
4838
|
-
await this.removeDirectory(entryPath);
|
|
4839
|
-
} else {
|
|
4840
|
-
await unlink(entryPath);
|
|
4841
|
-
}
|
|
5172
|
+
extractCategory(filePath, basePath) {
|
|
5173
|
+
const relative = filePath.replace(basePath + "/", "");
|
|
5174
|
+
const parts = relative.split("/");
|
|
5175
|
+
if (parts.length > 1) {
|
|
5176
|
+
return parts[0];
|
|
4842
5177
|
}
|
|
4843
|
-
|
|
5178
|
+
return "uncategorized";
|
|
4844
5179
|
}
|
|
4845
5180
|
/**
|
|
4846
|
-
*
|
|
5181
|
+
* Save installed skills info
|
|
4847
5182
|
*/
|
|
4848
|
-
async
|
|
5183
|
+
async saveInstalledSkills(skills) {
|
|
5184
|
+
const installedPath = join14(paths.globalConfig(), ".installed-skills.json");
|
|
4849
5185
|
try {
|
|
4850
|
-
|
|
4851
|
-
if (backups.length <= this.maxBackups) {
|
|
4852
|
-
return;
|
|
4853
|
-
}
|
|
4854
|
-
const toDelete = backups.slice(this.maxBackups);
|
|
4855
|
-
for (const backup of toDelete) {
|
|
4856
|
-
await this.deleteBackup(backup.manifest.backupId);
|
|
4857
|
-
}
|
|
4858
|
-
logger.info(`Cleaned up ${toDelete.length} old backup(s)`);
|
|
5186
|
+
await writeFile9(installedPath, JSON.stringify(skills, null, 2));
|
|
4859
5187
|
} catch (error) {
|
|
4860
|
-
logger.error("Failed to
|
|
5188
|
+
logger.error("Failed to save installed skills info:", error);
|
|
4861
5189
|
}
|
|
4862
5190
|
}
|
|
4863
5191
|
/**
|
|
4864
|
-
*
|
|
5192
|
+
* Update version file
|
|
4865
5193
|
*/
|
|
4866
|
-
|
|
4867
|
-
const
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
5194
|
+
async updateVersion(version, migration) {
|
|
5195
|
+
const current = await this.getCurrentVersion() || {
|
|
5196
|
+
installedVersion: "0.0.0",
|
|
5197
|
+
lastSynced: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5198
|
+
packageVersion: "0.0.0",
|
|
5199
|
+
migrationHistory: []
|
|
5200
|
+
};
|
|
5201
|
+
const updated = {
|
|
5202
|
+
installedVersion: version,
|
|
5203
|
+
lastSynced: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5204
|
+
packageVersion: this.getPackageVersion(),
|
|
5205
|
+
migrationHistory: migration ? [...current.migrationHistory, migration] : current.migrationHistory
|
|
5206
|
+
};
|
|
5207
|
+
const versionPath = join14(paths.globalConfig(), ".version.json");
|
|
5208
|
+
await writeFile9(versionPath, JSON.stringify(updated, null, 2));
|
|
5209
|
+
}
|
|
5210
|
+
/**
|
|
5211
|
+
* Check if migration is needed
|
|
5212
|
+
*/
|
|
5213
|
+
async needsMigration() {
|
|
5214
|
+
const current = await this.getCurrentVersion();
|
|
5215
|
+
const packageVersion = this.getPackageVersion();
|
|
5216
|
+
return current?.installedVersion !== packageVersion;
|
|
4875
5217
|
}
|
|
4876
5218
|
};
|
|
4877
5219
|
|
|
4878
|
-
// src/core/
|
|
5220
|
+
// src/core/backup-manager.ts
|
|
4879
5221
|
init_esm_shims();
|
|
4880
5222
|
init_logger();
|
|
4881
|
-
import { readFile as readFile9, writeFile as writeFile10, readdir as
|
|
4882
|
-
import { join as join15 } from "path";
|
|
4883
|
-
|
|
5223
|
+
import { readFile as readFile9, writeFile as writeFile10, readdir as readdir8, stat as stat2, unlink, mkdir as mkdir9 } from "fs/promises";
|
|
5224
|
+
import { join as join15, dirname as dirname3 } from "path";
|
|
5225
|
+
import { createHash as createHash2 } from "crypto";
|
|
5226
|
+
var BackupManager = class {
|
|
4884
5227
|
configPath;
|
|
4885
|
-
|
|
4886
|
-
|
|
5228
|
+
backupsDir;
|
|
5229
|
+
maxBackups;
|
|
5230
|
+
constructor(configPath, maxBackups = 5) {
|
|
4887
5231
|
this.configPath = configPath;
|
|
4888
|
-
this.
|
|
5232
|
+
this.backupsDir = join15(configPath, ".backups");
|
|
5233
|
+
this.maxBackups = maxBackups;
|
|
4889
5234
|
}
|
|
4890
5235
|
/**
|
|
4891
|
-
*
|
|
5236
|
+
* Create backup before update
|
|
4892
5237
|
*/
|
|
4893
|
-
async
|
|
4894
|
-
const migrations = [];
|
|
5238
|
+
async createBackup(fromVersion, toVersion) {
|
|
4895
5239
|
try {
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
5240
|
+
await mkdir9(this.backupsDir, { recursive: true });
|
|
5241
|
+
const backupId = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
5242
|
+
const backupPath = join15(this.backupsDir, `${backupId}-v${toVersion}`);
|
|
5243
|
+
await mkdir9(backupPath, { recursive: true });
|
|
5244
|
+
logger.info(`Creating backup: ${backupPath}`);
|
|
5245
|
+
const files = [];
|
|
5246
|
+
const backupItems = [
|
|
5247
|
+
"skills/",
|
|
5248
|
+
"aikit.json",
|
|
5249
|
+
"AGENTS.md",
|
|
5250
|
+
"config/"
|
|
5251
|
+
];
|
|
5252
|
+
for (const item of backupItems) {
|
|
5253
|
+
const files2 = await this.backupItem(this.configPath, item, backupPath);
|
|
5254
|
+
files2.push(...files2);
|
|
4909
5255
|
}
|
|
5256
|
+
const manifest = {
|
|
5257
|
+
backupId,
|
|
5258
|
+
fromVersion,
|
|
5259
|
+
toVersion,
|
|
5260
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5261
|
+
files,
|
|
5262
|
+
success: true
|
|
5263
|
+
};
|
|
5264
|
+
const manifestPath = join15(backupPath, "backup-manifest.json");
|
|
5265
|
+
await writeFile10(manifestPath, JSON.stringify(manifest, null, 2));
|
|
5266
|
+
await this.cleanupOldBackups();
|
|
5267
|
+
logger.success(`\u2713 Backup created: ${backupId}`);
|
|
5268
|
+
return backupId;
|
|
4910
5269
|
} catch (error) {
|
|
4911
|
-
logger.
|
|
5270
|
+
logger.error("Failed to create backup:", error);
|
|
5271
|
+
return null;
|
|
4912
5272
|
}
|
|
4913
|
-
return migrations.sort((a, b) => a.version.localeCompare(b.version));
|
|
4914
5273
|
}
|
|
4915
5274
|
/**
|
|
4916
|
-
*
|
|
5275
|
+
* Backup a file or directory
|
|
4917
5276
|
*/
|
|
4918
|
-
async
|
|
4919
|
-
const
|
|
5277
|
+
async backupItem(sourceDir, item, targetDir) {
|
|
5278
|
+
const sourcePath = join15(sourceDir, item);
|
|
5279
|
+
const targetPath = join15(targetDir, item);
|
|
5280
|
+
const files = [];
|
|
4920
5281
|
try {
|
|
4921
|
-
const
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
5282
|
+
const stats = await stat2(sourcePath);
|
|
5283
|
+
if (stats.isDirectory()) {
|
|
5284
|
+
await mkdir9(targetPath, { recursive: true });
|
|
5285
|
+
const entries = await readdir8(sourcePath);
|
|
5286
|
+
for (const entry of entries) {
|
|
5287
|
+
const entryFiles = await this.backupItem(sourcePath, entry, targetPath);
|
|
5288
|
+
files.push(...entryFiles);
|
|
5289
|
+
}
|
|
5290
|
+
} else if (stats.isFile()) {
|
|
5291
|
+
await mkdir9(dirname3(targetPath), { recursive: true });
|
|
5292
|
+
await this.copyFile(sourcePath, targetPath);
|
|
5293
|
+
const hash = await this.calculateHash(targetPath);
|
|
5294
|
+
files.push({
|
|
5295
|
+
path: item,
|
|
5296
|
+
hash,
|
|
5297
|
+
size: stats.size
|
|
5298
|
+
});
|
|
5299
|
+
}
|
|
5300
|
+
} catch (error) {
|
|
5301
|
+
logger.debug(`Could not backup ${item}:`, error);
|
|
4926
5302
|
}
|
|
5303
|
+
return files;
|
|
4927
5304
|
}
|
|
4928
5305
|
/**
|
|
4929
|
-
*
|
|
5306
|
+
* Copy file with hash calculation
|
|
4930
5307
|
*/
|
|
4931
|
-
async
|
|
4932
|
-
const
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
5308
|
+
async copyFile(source, target) {
|
|
5309
|
+
const content = await readFile9(source);
|
|
5310
|
+
await writeFile10(target, content);
|
|
5311
|
+
}
|
|
5312
|
+
/**
|
|
5313
|
+
* Calculate file hash
|
|
5314
|
+
*/
|
|
5315
|
+
async calculateHash(filePath) {
|
|
5316
|
+
try {
|
|
5317
|
+
const content = await readFile9(filePath);
|
|
5318
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
5319
|
+
} catch {
|
|
5320
|
+
return "";
|
|
4940
5321
|
}
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
to: migration.version,
|
|
4964
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4965
|
-
status: "failed"
|
|
4966
|
-
});
|
|
4967
|
-
break;
|
|
5322
|
+
}
|
|
5323
|
+
/**
|
|
5324
|
+
* List available backups
|
|
5325
|
+
*/
|
|
5326
|
+
async listBackups() {
|
|
5327
|
+
try {
|
|
5328
|
+
const entries = await readdir8(this.backupsDir);
|
|
5329
|
+
const backups = [];
|
|
5330
|
+
for (const entry of entries) {
|
|
5331
|
+
const backupPath = join15(this.backupsDir, entry);
|
|
5332
|
+
const manifestPath = join15(backupPath, "backup-manifest.json");
|
|
5333
|
+
try {
|
|
5334
|
+
const manifestContent = await readFile9(manifestPath, "utf-8");
|
|
5335
|
+
const manifest = JSON.parse(manifestContent);
|
|
5336
|
+
const size = await this.calculateBackupSize(backupPath);
|
|
5337
|
+
backups.push({
|
|
5338
|
+
manifest,
|
|
5339
|
+
path: backupPath,
|
|
5340
|
+
size
|
|
5341
|
+
});
|
|
5342
|
+
} catch {
|
|
5343
|
+
}
|
|
4968
5344
|
}
|
|
5345
|
+
backups.sort(
|
|
5346
|
+
(a, b) => new Date(b.manifest.timestamp).getTime() - new Date(a.manifest.timestamp).getTime()
|
|
5347
|
+
);
|
|
5348
|
+
return backups;
|
|
5349
|
+
} catch {
|
|
5350
|
+
return [];
|
|
4969
5351
|
}
|
|
4970
|
-
await this.updateMigrationHistory(migrationHistory);
|
|
4971
|
-
return {
|
|
4972
|
-
success: failed.length === 0,
|
|
4973
|
-
applied,
|
|
4974
|
-
failed
|
|
4975
|
-
};
|
|
4976
5352
|
}
|
|
4977
5353
|
/**
|
|
4978
|
-
*
|
|
5354
|
+
* Calculate backup directory size
|
|
4979
5355
|
*/
|
|
4980
|
-
async
|
|
5356
|
+
async calculateBackupSize(backupPath) {
|
|
5357
|
+
let totalSize = 0;
|
|
4981
5358
|
try {
|
|
4982
|
-
const
|
|
4983
|
-
|
|
4984
|
-
|
|
5359
|
+
const calculate = async (dir) => {
|
|
5360
|
+
const entries = await readdir8(dir);
|
|
5361
|
+
for (const entry of entries) {
|
|
5362
|
+
const entryPath = join15(dir, entry);
|
|
5363
|
+
const stats = await stat2(entryPath);
|
|
5364
|
+
if (stats.isDirectory()) {
|
|
5365
|
+
await calculate(entryPath);
|
|
5366
|
+
} else {
|
|
5367
|
+
totalSize += stats.size;
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
};
|
|
5371
|
+
await calculate(backupPath);
|
|
5372
|
+
} catch {
|
|
5373
|
+
}
|
|
5374
|
+
return totalSize;
|
|
5375
|
+
}
|
|
5376
|
+
/**
|
|
5377
|
+
* Restore from backup
|
|
5378
|
+
*/
|
|
5379
|
+
async restoreBackup(backupId) {
|
|
5380
|
+
try {
|
|
5381
|
+
const backups = await this.listBackups();
|
|
5382
|
+
const backup = backups.find((b) => b.manifest.backupId === backupId);
|
|
5383
|
+
if (!backup) {
|
|
5384
|
+
logger.error(`Backup not found: ${backupId}`);
|
|
4985
5385
|
return false;
|
|
4986
5386
|
}
|
|
4987
|
-
|
|
4988
|
-
const
|
|
4989
|
-
if (!
|
|
4990
|
-
logger.error(
|
|
5387
|
+
logger.info(`Restoring from backup: ${backupId}`);
|
|
5388
|
+
const isValid = await this.validateBackup(backup);
|
|
5389
|
+
if (!isValid) {
|
|
5390
|
+
logger.error("Backup validation failed");
|
|
4991
5391
|
return false;
|
|
4992
5392
|
}
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
5393
|
+
for (const file of backup.manifest.files) {
|
|
5394
|
+
const sourcePath = join15(backup.path, file.path);
|
|
5395
|
+
const targetPath = join15(this.configPath, file.path);
|
|
5396
|
+
await mkdir9(dirname3(targetPath), { recursive: true });
|
|
5397
|
+
await this.copyFile(sourcePath, targetPath);
|
|
5398
|
+
}
|
|
5399
|
+
logger.success(`\u2713 Backup restored: ${backupId}`);
|
|
4997
5400
|
return true;
|
|
4998
5401
|
} catch (error) {
|
|
4999
|
-
logger.error("Failed to
|
|
5402
|
+
logger.error("Failed to restore backup:", error);
|
|
5000
5403
|
return false;
|
|
5001
5404
|
}
|
|
5002
5405
|
}
|
|
5003
5406
|
/**
|
|
5004
|
-
*
|
|
5407
|
+
* Validate backup integrity
|
|
5005
5408
|
*/
|
|
5006
|
-
async
|
|
5007
|
-
const historyPath = join15(this.configPath, ".migration-history.json");
|
|
5409
|
+
async validateBackup(backup) {
|
|
5008
5410
|
try {
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5411
|
+
const manifestPath = join15(backup.path, "backup-manifest.json");
|
|
5412
|
+
await readFile9(manifestPath, "utf-8");
|
|
5413
|
+
for (const file of backup.manifest.files) {
|
|
5414
|
+
const filePath = join15(backup.path, file.path);
|
|
5415
|
+
await stat2(filePath);
|
|
5416
|
+
const currentHash = await this.calculateHash(filePath);
|
|
5417
|
+
if (currentHash !== file.hash) {
|
|
5418
|
+
logger.warn(`File hash mismatch: ${file.path}`);
|
|
5419
|
+
return false;
|
|
5420
|
+
}
|
|
5014
5421
|
}
|
|
5015
|
-
|
|
5016
|
-
await writeFile10(historyPath, JSON.stringify(history, null, 2));
|
|
5422
|
+
return true;
|
|
5017
5423
|
} catch (error) {
|
|
5018
|
-
logger.
|
|
5424
|
+
logger.debug("Backup validation failed:", error);
|
|
5425
|
+
return false;
|
|
5019
5426
|
}
|
|
5020
5427
|
}
|
|
5021
5428
|
/**
|
|
5022
|
-
*
|
|
5429
|
+
* Delete backup
|
|
5023
5430
|
*/
|
|
5024
|
-
async
|
|
5025
|
-
const historyPath = join15(this.configPath, ".migration-history.json");
|
|
5431
|
+
async deleteBackup(backupId) {
|
|
5026
5432
|
try {
|
|
5027
|
-
const
|
|
5028
|
-
const
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
await
|
|
5433
|
+
const backups = await this.listBackups();
|
|
5434
|
+
const backup = backups.find((b) => b.manifest.backupId === backupId);
|
|
5435
|
+
if (!backup) {
|
|
5436
|
+
return false;
|
|
5437
|
+
}
|
|
5438
|
+
const entries = await readdir8(backup.path);
|
|
5439
|
+
for (const entry of entries) {
|
|
5440
|
+
const entryPath = join15(backup.path, entry);
|
|
5441
|
+
const stats = await stat2(entryPath);
|
|
5442
|
+
if (stats.isDirectory()) {
|
|
5443
|
+
await this.removeDirectory(entryPath);
|
|
5444
|
+
} else {
|
|
5445
|
+
await unlink(entryPath);
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
logger.success(`\u2713 Backup deleted: ${backupId}`);
|
|
5449
|
+
return true;
|
|
5033
5450
|
} catch (error) {
|
|
5034
|
-
logger.error("Failed to
|
|
5451
|
+
logger.error("Failed to delete backup:", error);
|
|
5452
|
+
return false;
|
|
5035
5453
|
}
|
|
5036
5454
|
}
|
|
5037
5455
|
/**
|
|
5038
|
-
*
|
|
5456
|
+
* Remove directory recursively
|
|
5039
5457
|
*/
|
|
5040
|
-
async
|
|
5041
|
-
const
|
|
5042
|
-
|
|
5043
|
-
const
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5458
|
+
async removeDirectory(dirPath) {
|
|
5459
|
+
const entries = await readdir8(dirPath);
|
|
5460
|
+
for (const entry of entries) {
|
|
5461
|
+
const entryPath = join15(dirPath, entry);
|
|
5462
|
+
const stats = await stat2(entryPath);
|
|
5463
|
+
if (stats.isDirectory()) {
|
|
5464
|
+
await this.removeDirectory(entryPath);
|
|
5465
|
+
} else {
|
|
5466
|
+
await unlink(entryPath);
|
|
5467
|
+
}
|
|
5047
5468
|
}
|
|
5469
|
+
await unlink(dirPath);
|
|
5048
5470
|
}
|
|
5049
5471
|
/**
|
|
5050
|
-
*
|
|
5472
|
+
* Cleanup old backups (keep only maxBackups)
|
|
5051
5473
|
*/
|
|
5052
|
-
async
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5474
|
+
async cleanupOldBackups() {
|
|
5475
|
+
try {
|
|
5476
|
+
const backups = await this.listBackups();
|
|
5477
|
+
if (backups.length <= this.maxBackups) {
|
|
5478
|
+
return;
|
|
5479
|
+
}
|
|
5480
|
+
const toDelete = backups.slice(this.maxBackups);
|
|
5481
|
+
for (const backup of toDelete) {
|
|
5482
|
+
await this.deleteBackup(backup.manifest.backupId);
|
|
5483
|
+
}
|
|
5484
|
+
logger.info(`Cleaned up ${toDelete.length} old backup(s)`);
|
|
5485
|
+
} catch (error) {
|
|
5486
|
+
logger.error("Failed to cleanup old backups:", error);
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
/**
|
|
5490
|
+
* Format backup size for display
|
|
5491
|
+
*/
|
|
5492
|
+
formatSize(bytes) {
|
|
5493
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
5494
|
+
let size = bytes;
|
|
5495
|
+
let unitIndex = 0;
|
|
5496
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
5497
|
+
size /= 1024;
|
|
5498
|
+
unitIndex++;
|
|
5499
|
+
}
|
|
5500
|
+
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
|
5501
|
+
}
|
|
5502
|
+
};
|
|
5503
|
+
|
|
5504
|
+
// src/core/migration-manager.ts
|
|
5505
|
+
init_esm_shims();
|
|
5506
|
+
init_logger();
|
|
5507
|
+
import { readFile as readFile10, writeFile as writeFile11, readdir as readdir9 } from "fs/promises";
|
|
5508
|
+
import { join as join16 } from "path";
|
|
5509
|
+
var MigrationManager = class {
|
|
5510
|
+
configPath;
|
|
5511
|
+
migrationsDir;
|
|
5512
|
+
constructor(configPath) {
|
|
5513
|
+
this.configPath = configPath;
|
|
5514
|
+
this.migrationsDir = join16(process.cwd(), "src/core/migrations");
|
|
5515
|
+
}
|
|
5516
|
+
/**
|
|
5517
|
+
* Load all available migrations
|
|
5518
|
+
*/
|
|
5519
|
+
async loadMigrations() {
|
|
5520
|
+
const migrations = [];
|
|
5521
|
+
try {
|
|
5522
|
+
const files = await readdir9(this.migrationsDir);
|
|
5523
|
+
for (const file of files) {
|
|
5524
|
+
if (file.endsWith(".js") && file.startsWith("migrate-")) {
|
|
5525
|
+
try {
|
|
5526
|
+
const module = await import(join16(this.migrationsDir, file));
|
|
5527
|
+
const migration = module.default || module.migration;
|
|
5528
|
+
if (migration) {
|
|
5529
|
+
migrations.push(migration);
|
|
5530
|
+
}
|
|
5531
|
+
} catch (error) {
|
|
5532
|
+
logger.warn(`Failed to load migration ${file}:`, error);
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
} catch (error) {
|
|
5537
|
+
logger.debug("Could not load migrations:", error);
|
|
5538
|
+
}
|
|
5539
|
+
return migrations.sort((a, b) => a.version.localeCompare(b.version));
|
|
5540
|
+
}
|
|
5541
|
+
/**
|
|
5542
|
+
* Get applied migrations
|
|
5543
|
+
*/
|
|
5544
|
+
async getAppliedMigrations() {
|
|
5545
|
+
const migrationHistoryPath = join16(this.configPath, ".migration-history.json");
|
|
5546
|
+
try {
|
|
5547
|
+
const content = await readFile10(migrationHistoryPath, "utf-8");
|
|
5548
|
+
const history = JSON.parse(content);
|
|
5549
|
+
return history.filter((m) => m.status === "completed").map((m) => m.to);
|
|
5550
|
+
} catch {
|
|
5551
|
+
return [];
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
/**
|
|
5555
|
+
* Run pending migrations
|
|
5556
|
+
*/
|
|
5557
|
+
async runPendingMigrations() {
|
|
5558
|
+
const appliedMigrations = await this.getAppliedMigrations();
|
|
5559
|
+
const allMigrations = await this.loadMigrations();
|
|
5560
|
+
const pendingMigrations = allMigrations.filter(
|
|
5561
|
+
(m) => !appliedMigrations.includes(m.version)
|
|
5562
|
+
);
|
|
5563
|
+
if (pendingMigrations.length === 0) {
|
|
5564
|
+
logger.info("No pending migrations");
|
|
5565
|
+
return { success: true, applied: [], failed: [] };
|
|
5566
|
+
}
|
|
5567
|
+
logger.info(`Running ${pendingMigrations.length} pending migration(s)...`);
|
|
5568
|
+
const applied = [];
|
|
5569
|
+
const failed = [];
|
|
5570
|
+
const migrationHistory = [];
|
|
5571
|
+
for (const migration of pendingMigrations) {
|
|
5572
|
+
try {
|
|
5573
|
+
logger.info(`Running migration: ${migration.version}`);
|
|
5574
|
+
logger.info(` ${migration.description}`);
|
|
5575
|
+
await migration.up();
|
|
5576
|
+
applied.push(migration.version);
|
|
5577
|
+
migrationHistory.push({
|
|
5578
|
+
from: "previous",
|
|
5579
|
+
to: migration.version,
|
|
5580
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5581
|
+
status: "completed"
|
|
5582
|
+
});
|
|
5583
|
+
logger.success(`\u2713 Migration completed: ${migration.version}`);
|
|
5584
|
+
} catch (error) {
|
|
5585
|
+
logger.error(`\u2717 Migration failed: ${migration.version}`, error);
|
|
5586
|
+
failed.push(migration.version);
|
|
5587
|
+
migrationHistory.push({
|
|
5588
|
+
from: "previous",
|
|
5589
|
+
to: migration.version,
|
|
5590
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5591
|
+
status: "failed"
|
|
5592
|
+
});
|
|
5593
|
+
break;
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
await this.updateMigrationHistory(migrationHistory);
|
|
5597
|
+
return {
|
|
5598
|
+
success: failed.length === 0,
|
|
5599
|
+
applied,
|
|
5600
|
+
failed
|
|
5601
|
+
};
|
|
5602
|
+
}
|
|
5603
|
+
/**
|
|
5604
|
+
* Rollback migration
|
|
5605
|
+
*/
|
|
5606
|
+
async rollbackMigration(version) {
|
|
5607
|
+
try {
|
|
5608
|
+
const applied = await this.getAppliedMigrations();
|
|
5609
|
+
if (!applied.includes(version)) {
|
|
5610
|
+
logger.error(`Migration not found in applied list: ${version}`);
|
|
5611
|
+
return false;
|
|
5612
|
+
}
|
|
5613
|
+
const allMigrations = await this.loadMigrations();
|
|
5614
|
+
const migration = allMigrations.find((m) => m.version === version);
|
|
5615
|
+
if (!migration) {
|
|
5616
|
+
logger.error(`Migration file not found: ${version}`);
|
|
5617
|
+
return false;
|
|
5618
|
+
}
|
|
5619
|
+
logger.info(`Rolling back migration: ${version}`);
|
|
5620
|
+
await migration.down();
|
|
5621
|
+
await this.updateMigrationHistoryStatus(version, "rolled-back");
|
|
5622
|
+
logger.success(`\u2713 Migration rolled back: ${version}`);
|
|
5623
|
+
return true;
|
|
5624
|
+
} catch (error) {
|
|
5625
|
+
logger.error("Failed to rollback migration:", error);
|
|
5626
|
+
return false;
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
5629
|
+
/**
|
|
5630
|
+
* Update migration history
|
|
5631
|
+
*/
|
|
5632
|
+
async updateMigrationHistory(entries) {
|
|
5633
|
+
const historyPath = join16(this.configPath, ".migration-history.json");
|
|
5634
|
+
try {
|
|
5635
|
+
let history = [];
|
|
5636
|
+
try {
|
|
5637
|
+
const content = await readFile10(historyPath, "utf-8");
|
|
5638
|
+
history = JSON.parse(content);
|
|
5639
|
+
} catch {
|
|
5640
|
+
}
|
|
5641
|
+
history.push(...entries);
|
|
5642
|
+
await writeFile11(historyPath, JSON.stringify(history, null, 2));
|
|
5643
|
+
} catch (error) {
|
|
5644
|
+
logger.error("Failed to update migration history:", error);
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
/**
|
|
5648
|
+
* Update migration history status
|
|
5649
|
+
*/
|
|
5650
|
+
async updateMigrationHistoryStatus(version, status) {
|
|
5651
|
+
const historyPath = join16(this.configPath, ".migration-history.json");
|
|
5652
|
+
try {
|
|
5653
|
+
const content = await readFile10(historyPath, "utf-8");
|
|
5654
|
+
const history = JSON.parse(content);
|
|
5655
|
+
const updated = history.map(
|
|
5656
|
+
(m) => m.to === version ? { ...m, status } : m
|
|
5657
|
+
);
|
|
5658
|
+
await writeFile11(historyPath, JSON.stringify(updated, null, 2));
|
|
5659
|
+
} catch (error) {
|
|
5660
|
+
logger.error("Failed to update migration history status:", error);
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
/**
|
|
5664
|
+
* Get migration history
|
|
5665
|
+
*/
|
|
5666
|
+
async getMigrationHistory() {
|
|
5667
|
+
const historyPath = join16(this.configPath, ".migration-history.json");
|
|
5668
|
+
try {
|
|
5669
|
+
const content = await readFile10(historyPath, "utf-8");
|
|
5670
|
+
return JSON.parse(content);
|
|
5671
|
+
} catch {
|
|
5672
|
+
return [];
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
/**
|
|
5676
|
+
* Check if migration is needed for version
|
|
5677
|
+
*/
|
|
5678
|
+
async needsMigration(_fromVersion) {
|
|
5679
|
+
const applied = await this.getAppliedMigrations();
|
|
5680
|
+
const allMigrations = await this.loadMigrations();
|
|
5681
|
+
const pendingMigrations = allMigrations.filter(
|
|
5682
|
+
(m) => !applied.includes(m.version)
|
|
5057
5683
|
);
|
|
5058
5684
|
return pendingMigrations.length > 0;
|
|
5059
5685
|
}
|
|
@@ -5080,7 +5706,7 @@ var SyncEngine = class {
|
|
|
5080
5706
|
if (changes.hasUpdate) {
|
|
5081
5707
|
this.displayUpdateInfo(changes);
|
|
5082
5708
|
} else {
|
|
5083
|
-
console.log(
|
|
5709
|
+
console.log(chalk3.green("\u2713 Your AIKit is up to date"));
|
|
5084
5710
|
console.log(` Installed: ${changes.fromVersion}`);
|
|
5085
5711
|
console.log(` Latest: ${changes.toVersion}`);
|
|
5086
5712
|
}
|
|
@@ -5095,15 +5721,15 @@ var SyncEngine = class {
|
|
|
5095
5721
|
*/
|
|
5096
5722
|
async previewUpdate() {
|
|
5097
5723
|
try {
|
|
5098
|
-
console.log(
|
|
5724
|
+
console.log(chalk3.bold("\n\u{1F50D} Previewing update...\n"));
|
|
5099
5725
|
const changes = await this.versionManager.checkForUpdates();
|
|
5100
5726
|
if (!changes.hasUpdate) {
|
|
5101
|
-
console.log(
|
|
5727
|
+
console.log(chalk3.green("\u2713 No updates available"));
|
|
5102
5728
|
return false;
|
|
5103
5729
|
}
|
|
5104
5730
|
await this.displayChanges(changes);
|
|
5105
|
-
console.log(
|
|
5106
|
-
console.log(
|
|
5731
|
+
console.log(chalk3.yellow("\n\u26A0\uFE0F This is a preview - no changes will be made."));
|
|
5732
|
+
console.log(chalk3.gray("Use `aikit sync apply` to apply these changes."));
|
|
5107
5733
|
return true;
|
|
5108
5734
|
} catch (error) {
|
|
5109
5735
|
logger.error("Failed to preview update:", error);
|
|
@@ -5117,7 +5743,7 @@ var SyncEngine = class {
|
|
|
5117
5743
|
try {
|
|
5118
5744
|
const changes = await this.versionManager.checkForUpdates();
|
|
5119
5745
|
if (!changes.hasUpdate) {
|
|
5120
|
-
console.log(
|
|
5746
|
+
console.log(chalk3.green("\u2713 Already up to date"));
|
|
5121
5747
|
return {
|
|
5122
5748
|
success: true,
|
|
5123
5749
|
newSkills: [],
|
|
@@ -5128,14 +5754,14 @@ var SyncEngine = class {
|
|
|
5128
5754
|
}
|
|
5129
5755
|
await this.displayChanges(changes);
|
|
5130
5756
|
if (!options.force) {
|
|
5131
|
-
const { confirmed } = await
|
|
5757
|
+
const { confirmed } = await inquirer2.prompt([{
|
|
5132
5758
|
type: "confirm",
|
|
5133
5759
|
name: "confirmed",
|
|
5134
5760
|
message: "Continue with update?",
|
|
5135
5761
|
default: false
|
|
5136
5762
|
}]);
|
|
5137
5763
|
if (!confirmed) {
|
|
5138
|
-
console.log(
|
|
5764
|
+
console.log(chalk3.yellow("Update cancelled"));
|
|
5139
5765
|
return {
|
|
5140
5766
|
success: false,
|
|
5141
5767
|
newSkills: [],
|
|
@@ -5147,7 +5773,7 @@ var SyncEngine = class {
|
|
|
5147
5773
|
}
|
|
5148
5774
|
let backupId = void 0;
|
|
5149
5775
|
if (!options.dryRun && options.backup !== false) {
|
|
5150
|
-
console.log(
|
|
5776
|
+
console.log(chalk3.bold("\n\u{1F4E6} Creating backup..."));
|
|
5151
5777
|
const backupResult = await this.backupManager.createBackup(
|
|
5152
5778
|
changes.fromVersion,
|
|
5153
5779
|
changes.toVersion
|
|
@@ -5160,19 +5786,19 @@ var SyncEngine = class {
|
|
|
5160
5786
|
for (const conflict of changes.conflicts) {
|
|
5161
5787
|
await this.resolveConflict(conflict);
|
|
5162
5788
|
}
|
|
5163
|
-
console.log(
|
|
5789
|
+
console.log(chalk3.bold("\n\u{1F504} Running migrations..."));
|
|
5164
5790
|
const migrationResult = await this.migrationManager.runPendingMigrations();
|
|
5165
5791
|
if (!migrationResult.success) {
|
|
5166
5792
|
throw new Error(`Migration failed: ${migrationResult.failed.join(", ")}`);
|
|
5167
5793
|
}
|
|
5168
|
-
console.log(
|
|
5794
|
+
console.log(chalk3.bold("\n\u{1F4DD} Updating skills..."));
|
|
5169
5795
|
const updateResult = await this.updateSkills(changes, options);
|
|
5170
5796
|
await this.versionManager.updateVersion(changes.toVersion);
|
|
5171
5797
|
if (backupId) {
|
|
5172
5798
|
const allSkills = await this.versionManager.loadSkillHashes(paths.skills(paths.globalConfig()));
|
|
5173
5799
|
await this.versionManager.saveInstalledSkills(allSkills);
|
|
5174
5800
|
}
|
|
5175
|
-
console.log(
|
|
5801
|
+
console.log(chalk3.green("\n\u2705 Update complete!"));
|
|
5176
5802
|
this.displaySummary({
|
|
5177
5803
|
success: true,
|
|
5178
5804
|
backupId,
|
|
@@ -5187,7 +5813,7 @@ var SyncEngine = class {
|
|
|
5187
5813
|
};
|
|
5188
5814
|
} catch (error) {
|
|
5189
5815
|
logger.error("Update failed:", error);
|
|
5190
|
-
console.log(
|
|
5816
|
+
console.log(chalk3.red("\n\u274C Update failed"));
|
|
5191
5817
|
return {
|
|
5192
5818
|
success: false,
|
|
5193
5819
|
newSkills: [],
|
|
@@ -5202,14 +5828,14 @@ var SyncEngine = class {
|
|
|
5202
5828
|
*/
|
|
5203
5829
|
async rollback(backupId) {
|
|
5204
5830
|
try {
|
|
5205
|
-
console.log(
|
|
5831
|
+
console.log(chalk3.bold("\n\u{1F504} Rollback...\n"));
|
|
5206
5832
|
if (!backupId) {
|
|
5207
5833
|
const backups = await this.backupManager.listBackups();
|
|
5208
5834
|
if (backups.length === 0) {
|
|
5209
|
-
console.log(
|
|
5835
|
+
console.log(chalk3.yellow("No backups available"));
|
|
5210
5836
|
return false;
|
|
5211
5837
|
}
|
|
5212
|
-
const { selectedBackup } = await
|
|
5838
|
+
const { selectedBackup } = await inquirer2.prompt([{
|
|
5213
5839
|
type: "list",
|
|
5214
5840
|
name: "selectedBackup",
|
|
5215
5841
|
message: "Select backup to restore:",
|
|
@@ -5221,12 +5847,12 @@ var SyncEngine = class {
|
|
|
5221
5847
|
backupId = selectedBackup;
|
|
5222
5848
|
}
|
|
5223
5849
|
if (!backupId) {
|
|
5224
|
-
console.log(
|
|
5850
|
+
console.log(chalk3.yellow("No backup ID provided"));
|
|
5225
5851
|
return false;
|
|
5226
5852
|
}
|
|
5227
5853
|
const success = await this.backupManager.restoreBackup(backupId);
|
|
5228
5854
|
if (success) {
|
|
5229
|
-
console.log(
|
|
5855
|
+
console.log(chalk3.green("\u2713 Rollback complete"));
|
|
5230
5856
|
return true;
|
|
5231
5857
|
}
|
|
5232
5858
|
return false;
|
|
@@ -5239,36 +5865,36 @@ var SyncEngine = class {
|
|
|
5239
5865
|
* Display update information
|
|
5240
5866
|
*/
|
|
5241
5867
|
displayUpdateInfo(changes) {
|
|
5242
|
-
console.log(
|
|
5243
|
-
console.log(` ${
|
|
5244
|
-
console.log(` ${
|
|
5868
|
+
console.log(chalk3.bold("\n\u{1F4E2} New version available!\n"));
|
|
5869
|
+
console.log(` ${chalk3.cyan("Current:")} ${changes.fromVersion}`);
|
|
5870
|
+
console.log(` ${chalk3.cyan("Latest:")} ${changes.toVersion}
|
|
5245
5871
|
`);
|
|
5246
5872
|
}
|
|
5247
5873
|
/**
|
|
5248
5874
|
* Display changes summary
|
|
5249
5875
|
*/
|
|
5250
5876
|
async displayChanges(changes) {
|
|
5251
|
-
console.log(
|
|
5877
|
+
console.log(chalk3.bold("\u{1F4CA} Changes detected:\n"));
|
|
5252
5878
|
if (changes.newSkills.length > 0) {
|
|
5253
|
-
console.log(
|
|
5879
|
+
console.log(chalk3.green(" New Skills:"));
|
|
5254
5880
|
changes.newSkills.forEach((skill) => {
|
|
5255
5881
|
console.log(` + ${skill.name} (${skill.category})`);
|
|
5256
5882
|
});
|
|
5257
5883
|
}
|
|
5258
5884
|
if (changes.modifiedSkills.length > 0) {
|
|
5259
|
-
console.log(
|
|
5885
|
+
console.log(chalk3.yellow(" Updated Skills:"));
|
|
5260
5886
|
changes.modifiedSkills.forEach((skill) => {
|
|
5261
5887
|
console.log(` ~ ${skill.name}`);
|
|
5262
5888
|
});
|
|
5263
5889
|
}
|
|
5264
5890
|
if (changes.removedSkills.length > 0) {
|
|
5265
|
-
console.log(
|
|
5891
|
+
console.log(chalk3.red(" Removed Skills:"));
|
|
5266
5892
|
changes.removedSkills.forEach((skill) => {
|
|
5267
5893
|
console.log(` - ${skill.name}`);
|
|
5268
5894
|
});
|
|
5269
5895
|
}
|
|
5270
5896
|
if (changes.conflicts.length > 0) {
|
|
5271
|
-
console.log(
|
|
5897
|
+
console.log(chalk3.bold.red(" \u26A0\uFE0F Conflicts:"));
|
|
5272
5898
|
changes.conflicts.forEach((conflict) => {
|
|
5273
5899
|
console.log(` ! ${conflict.skillName} (user modified)`);
|
|
5274
5900
|
});
|
|
@@ -5278,11 +5904,11 @@ var SyncEngine = class {
|
|
|
5278
5904
|
* Resolve a conflict
|
|
5279
5905
|
*/
|
|
5280
5906
|
async resolveConflict(conflict) {
|
|
5281
|
-
console.log(
|
|
5907
|
+
console.log(chalk3.bold.red(`
|
|
5282
5908
|
\u26A0\uFE0F Conflict detected: ${conflict.skillName}
|
|
5283
5909
|
`));
|
|
5284
|
-
console.log(
|
|
5285
|
-
const { action } = await
|
|
5910
|
+
console.log(chalk3.yellow("Your version differs from official version."));
|
|
5911
|
+
const { action } = await inquirer2.prompt([{
|
|
5286
5912
|
type: "list",
|
|
5287
5913
|
name: "action",
|
|
5288
5914
|
message: "Choose action:",
|
|
@@ -5307,1164 +5933,484 @@ var SyncEngine = class {
|
|
|
5307
5933
|
if (action === "overwrite") {
|
|
5308
5934
|
return;
|
|
5309
5935
|
}
|
|
5310
|
-
console.log(
|
|
5936
|
+
console.log(chalk3.yellow(" Your version will be preserved as -custom.md"));
|
|
5311
5937
|
}
|
|
5312
5938
|
/**
|
|
5313
5939
|
* Update skills based on changes
|
|
5314
5940
|
*/
|
|
5315
5941
|
async updateSkills(changes, options) {
|
|
5316
5942
|
const globalSkillsPath = paths.skills(paths.globalConfig());
|
|
5317
|
-
const projectSkillsPath = paths.skills(this.versionManager["config"].configPath);
|
|
5318
|
-
const newSkills = [];
|
|
5319
|
-
const updatedSkills = [];
|
|
5320
|
-
const removedSkills = [];
|
|
5321
|
-
for (const skill of changes.newSkills) {
|
|
5322
|
-
if (!options.dryRun) {
|
|
5323
|
-
await this.installSkill(globalSkillsPath, skill, projectSkillsPath);
|
|
5324
|
-
}
|
|
5325
|
-
newSkills.push(skill.name);
|
|
5326
|
-
console.log(
|
|
5327
|
-
}
|
|
5328
|
-
for (const skill of changes.modifiedSkills) {
|
|
5329
|
-
if (!options.dryRun) {
|
|
5330
|
-
await this.installSkill(globalSkillsPath, skill, projectSkillsPath);
|
|
5331
|
-
}
|
|
5332
|
-
updatedSkills.push(skill.name);
|
|
5333
|
-
console.log(
|
|
5334
|
-
}
|
|
5335
|
-
for (const skill of changes.removedSkills) {
|
|
5336
|
-
if (!options.dryRun) {
|
|
5337
|
-
await this.archiveSkill(projectSkillsPath, skill);
|
|
5338
|
-
}
|
|
5339
|
-
removedSkills.push(skill.name);
|
|
5340
|
-
console.log(
|
|
5341
|
-
}
|
|
5342
|
-
return {
|
|
5343
|
-
newSkills,
|
|
5344
|
-
updatedSkills,
|
|
5345
|
-
removedSkills
|
|
5346
|
-
};
|
|
5347
|
-
}
|
|
5348
|
-
/**
|
|
5349
|
-
* Install a skill
|
|
5350
|
-
*/
|
|
5351
|
-
async installSkill(sourceDir, skill, targetDir) {
|
|
5352
|
-
const sourcePath =
|
|
5353
|
-
const targetPath =
|
|
5354
|
-
await
|
|
5355
|
-
await copyFile(sourcePath, targetPath);
|
|
5356
|
-
}
|
|
5357
|
-
/**
|
|
5358
|
-
* Archive a removed skill
|
|
5359
|
-
*/
|
|
5360
|
-
async archiveSkill(targetDir, skill) {
|
|
5361
|
-
const sourcePath =
|
|
5362
|
-
const targetPath =
|
|
5363
|
-
try {
|
|
5364
|
-
const content = await
|
|
5365
|
-
const deprecatedNotice = `---
|
|
5366
|
-
\u26A0\uFE0F DEPRECATED: This skill has been removed
|
|
5367
|
-
|
|
5368
|
-
Deprecation date: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5369
|
-
Reason: Check release notes for replacement
|
|
5370
|
-
---
|
|
5371
|
-
|
|
5372
|
-
${content}`;
|
|
5373
|
-
await mkdir9(dirname3(targetPath), { recursive: true });
|
|
5374
|
-
await writeFile11(targetPath, deprecatedNotice);
|
|
5375
|
-
} catch (error) {
|
|
5376
|
-
if (error.code === "ENOENT") {
|
|
5377
|
-
console.log(chalk2.yellow(` - ${skill.name} (not found, skipping)`));
|
|
5378
|
-
} else {
|
|
5379
|
-
throw error;
|
|
5380
|
-
}
|
|
5381
|
-
}
|
|
5382
|
-
}
|
|
5383
|
-
/**
|
|
5384
|
-
* Display sync summary
|
|
5385
|
-
*/
|
|
5386
|
-
displaySummary(result) {
|
|
5387
|
-
console.log(chalk2.bold("\n\u{1F4CB} Summary:\n"));
|
|
5388
|
-
console.log(` Updated from: ${chalk2.cyan(result.backupId || "N/A")}`);
|
|
5389
|
-
console.log(` Updated to: ${chalk2.cyan("current")}`);
|
|
5390
|
-
console.log();
|
|
5391
|
-
if (result.newSkills.length > 0) {
|
|
5392
|
-
console.log(chalk2.green(` ${result.newSkills.length} new skills installed`));
|
|
5393
|
-
}
|
|
5394
|
-
if (result.updatedSkills.length > 0) {
|
|
5395
|
-
console.log(chalk2.yellow(` ${result.updatedSkills.length} skills updated`));
|
|
5396
|
-
}
|
|
5397
|
-
if (result.removedSkills.length > 0) {
|
|
5398
|
-
console.log(chalk2.red(` ${result.removedSkills.length} skills archived`));
|
|
5399
|
-
}
|
|
5400
|
-
if (result.migrationsRun.length > 0) {
|
|
5401
|
-
console.log(chalk2.blue(` ${result.migrationsRun.length} migrations run`));
|
|
5402
|
-
}
|
|
5403
|
-
if (result.backupId) {
|
|
5404
|
-
console.log(chalk2.gray(`
|
|
5405
|
-
Rollback available: aikit sync rollback ${result.backupId}`));
|
|
5406
|
-
}
|
|
5407
|
-
}
|
|
5408
|
-
};
|
|
5409
|
-
|
|
5410
|
-
// src/cli.ts
|
|
5411
|
-
var program = new Command();
|
|
5412
|
-
program.name("aikit").description("Open-source AI coding agent toolkit for OpenCode").version(getVersion());
|
|
5413
|
-
program.command("init").description("Initialize AIKit configuration").option("-g, --global", "Initialize global configuration").option("-p, --project", "Initialize project-level configuration").action(async (options) => {
|
|
5414
|
-
const configDir = options.global ? paths.globalConfig() : paths.projectConfig();
|
|
5415
|
-
console.log(chalk3.bold("\n\u{1F680} AIKit Setup\n"));
|
|
5416
|
-
logger.info(`Initializing AIKit in ${configDir}...`);
|
|
5417
|
-
try {
|
|
5418
|
-
await initializeConfig(configDir, options.global);
|
|
5419
|
-
logger.success("\u2713 Configuration created");
|
|
5420
|
-
if (!options.global) {
|
|
5421
|
-
const config = await loadConfig();
|
|
5422
|
-
const engine = new SkillEngine(config);
|
|
5423
|
-
const result = await engine.syncSkillsToProject();
|
|
5424
|
-
if (result.count > 0) {
|
|
5425
|
-
logger.success(`\u2713 Synced ${result.count} skills`);
|
|
5426
|
-
}
|
|
5427
|
-
console.log(chalk3.bold("\n\u{1F50D} Checking CLI tools...\n"));
|
|
5428
|
-
const cliTools = await CliDetector.checkAll();
|
|
5429
|
-
const installableTools = CliDetector.filterInstallable(cliTools);
|
|
5430
|
-
for (const tool of cliTools) {
|
|
5431
|
-
const status = tool.installed ? chalk3.green("\u2713 Installed") : chalk3.yellow("\u2717 Not installed");
|
|
5432
|
-
const version = tool.version ? chalk3.gray(` (${tool.version})`) : "";
|
|
5433
|
-
console.log(` ${status} ${chalk3.cyan(tool.displayName)}${version}`);
|
|
5434
|
-
}
|
|
5435
|
-
if (installableTools.length > 0) {
|
|
5436
|
-
console.log();
|
|
5437
|
-
const { action } = await inquirer2.prompt([
|
|
5438
|
-
{
|
|
5439
|
-
type: "list",
|
|
5440
|
-
name: "action",
|
|
5441
|
-
message: "How would you like to proceed?",
|
|
5442
|
-
choices: [
|
|
5443
|
-
{
|
|
5444
|
-
name: "all",
|
|
5445
|
-
value: "all",
|
|
5446
|
-
short: "a",
|
|
5447
|
-
message: "Install all missing CLI tools"
|
|
5448
|
-
},
|
|
5449
|
-
{
|
|
5450
|
-
name: "select",
|
|
5451
|
-
value: "select",
|
|
5452
|
-
short: "s",
|
|
5453
|
-
message: "Select specific tools to install (use space to select, Enter to confirm)"
|
|
5454
|
-
},
|
|
5455
|
-
{
|
|
5456
|
-
name: "skip",
|
|
5457
|
-
value: "skip",
|
|
5458
|
-
short: "n",
|
|
5459
|
-
message: "Skip CLI tool installation"
|
|
5460
|
-
}
|
|
5461
|
-
],
|
|
5462
|
-
default: "all"
|
|
5463
|
-
}
|
|
5464
|
-
]);
|
|
5465
|
-
if (action === "skip") {
|
|
5466
|
-
console.log();
|
|
5467
|
-
logger.info("Skipping CLI tool installation");
|
|
5468
|
-
} else if (action === "all") {
|
|
5469
|
-
console.log();
|
|
5470
|
-
logger.info(`Installing ${installableTools.length} CLI tool(s)...`);
|
|
5471
|
-
for (const tool of installableTools) {
|
|
5472
|
-
await installCliTool(tool);
|
|
5473
|
-
}
|
|
5474
|
-
console.log();
|
|
5475
|
-
logger.success("\u2713 CLI tools installed");
|
|
5476
|
-
} else {
|
|
5477
|
-
const { installTools } = await inquirer2.prompt([
|
|
5478
|
-
{
|
|
5479
|
-
type: "checkbox",
|
|
5480
|
-
name: "installTools",
|
|
5481
|
-
message: "Select CLI tools to install (press Enter to skip):",
|
|
5482
|
-
choices: installableTools.map((tool) => ({
|
|
5483
|
-
name: tool.name,
|
|
5484
|
-
value: tool,
|
|
5485
|
-
checked: true
|
|
5486
|
-
// Default to install all
|
|
5487
|
-
}))
|
|
5488
|
-
}
|
|
5489
|
-
]);
|
|
5490
|
-
if (installTools.length > 0) {
|
|
5491
|
-
console.log();
|
|
5492
|
-
logger.info(`Installing ${installTools.length} CLI tool(s)...`);
|
|
5493
|
-
for (const tool of installTools) {
|
|
5494
|
-
await installCliTool(tool);
|
|
5495
|
-
}
|
|
5496
|
-
console.log();
|
|
5497
|
-
logger.success("\u2713 CLI tools installed");
|
|
5498
|
-
} else {
|
|
5499
|
-
console.log();
|
|
5500
|
-
logger.info("Skipping CLI tool installation");
|
|
5501
|
-
}
|
|
5502
|
-
}
|
|
5503
|
-
} else {
|
|
5504
|
-
console.log();
|
|
5505
|
-
logger.success("\u2713 All CLI tools already installed");
|
|
5506
|
-
}
|
|
5507
|
-
const beads = new BeadsIntegration();
|
|
5508
|
-
const beadsStatus = await beads.getStatus();
|
|
5509
|
-
if (!beadsStatus.initialized) {
|
|
5510
|
-
logger.info("Initializing .beads directory...");
|
|
5511
|
-
await beads.initLocal();
|
|
5512
|
-
logger.success("\u2713 .beads directory created");
|
|
5513
|
-
if (!beadsStatus.installed) {
|
|
5514
|
-
logger.info("Tip: Install Beads CLI globally for full functionality: npm install -g beads");
|
|
5515
|
-
}
|
|
5516
|
-
} else {
|
|
5517
|
-
logger.info("Beads already initialized");
|
|
5518
|
-
}
|
|
5519
|
-
const opencodePath = paths.opencodeConfig();
|
|
5520
|
-
await installToOpenCode(opencodePath);
|
|
5521
|
-
console.log(chalk3.bold("\n\u2728 AIKit is ready!\n"));
|
|
5522
|
-
console.log("Usage in OpenCode:");
|
|
5523
|
-
console.log(chalk3.cyan(" /skills") + " - List all available skills");
|
|
5524
|
-
console.log(chalk3.cyan(" /plan") + " - Create implementation plan");
|
|
5525
|
-
console.log(chalk3.cyan(" /tdd") + " - Test-driven development");
|
|
5526
|
-
console.log(chalk3.cyan(" /debug") + " - Systematic debugging");
|
|
5527
|
-
console.log(chalk3.cyan(" /review") + " - Code review checklist");
|
|
5528
|
-
console.log(chalk3.cyan(" /git") + " - Git workflow");
|
|
5529
|
-
console.log(chalk3.cyan(" /frontend-aesthetics") + " - UI/UX guidelines");
|
|
5530
|
-
console.log("\nPress " + chalk3.bold("Ctrl+K") + " in OpenCode to see all commands.\n");
|
|
5531
|
-
}
|
|
5532
|
-
} catch (error) {
|
|
5533
|
-
logger.error("Failed to initialize AIKit:", error);
|
|
5534
|
-
process.exit(1);
|
|
5535
|
-
}
|
|
5536
|
-
});
|
|
5537
|
-
program.command("install").description("Install AIKit to OpenCode configuration").action(async () => {
|
|
5538
|
-
logger.info("Installing AIKit to OpenCode...");
|
|
5539
|
-
try {
|
|
5540
|
-
const opencodePath = paths.opencodeConfig();
|
|
5541
|
-
await installToOpenCode(opencodePath);
|
|
5542
|
-
logger.success("AIKit installed to OpenCode!");
|
|
5543
|
-
} catch (error) {
|
|
5544
|
-
logger.error("Failed to install:", error);
|
|
5545
|
-
process.exit(1);
|
|
5546
|
-
}
|
|
5547
|
-
});
|
|
5548
|
-
program.command("sync [subcommand]").description("Update AIKit to latest version").option("--dry-run", "Preview changes without applying").option("-f, --force", "Skip confirmation prompts").option("--no-backup", "Skip creating backup").action(async (subcommand, options) => {
|
|
5549
|
-
const config = await loadConfig();
|
|
5550
|
-
const syncEngine = new SyncEngine(config);
|
|
5551
|
-
if (!subcommand) {
|
|
5552
|
-
await syncEngine.applyUpdate(options);
|
|
5553
|
-
} else {
|
|
5554
|
-
switch (subcommand) {
|
|
5555
|
-
case "check":
|
|
5556
|
-
await syncEngine.checkForUpdates();
|
|
5557
|
-
break;
|
|
5558
|
-
case "preview":
|
|
5559
|
-
await syncEngine.previewUpdate();
|
|
5560
|
-
break;
|
|
5561
|
-
case "apply":
|
|
5562
|
-
await syncEngine.applyUpdate(options);
|
|
5563
|
-
break;
|
|
5564
|
-
case "rollback":
|
|
5565
|
-
await syncEngine.rollback();
|
|
5566
|
-
break;
|
|
5567
|
-
default:
|
|
5568
|
-
logger.error(`Unknown subcommand: ${subcommand}`);
|
|
5569
|
-
console.log(chalk3.gray("Available subcommands: check, preview, apply, rollback"));
|
|
5570
|
-
process.exit(1);
|
|
5571
|
-
}
|
|
5572
|
-
}
|
|
5573
|
-
});
|
|
5574
|
-
var skillsCmd = program.command("skills").description("Manage skills");
|
|
5575
|
-
skillsCmd.command("list").description("List available skills and tools with their configuration status").action(async () => {
|
|
5576
|
-
const config = await loadConfig();
|
|
5577
|
-
const engine = new SkillEngine(config);
|
|
5578
|
-
const skills = await engine.listSkills();
|
|
5579
|
-
const { ToolConfigManager: ToolConfigManager2 } = await Promise.resolve().then(() => (init_tool_config(), tool_config_exports));
|
|
5580
|
-
const toolConfigManager = new ToolConfigManager2(config);
|
|
5581
|
-
const tools = await toolConfigManager.listTools();
|
|
5582
|
-
console.log(chalk3.bold("\n\u{1F4DA} Available Skills:\n"));
|
|
5583
|
-
for (const skill of skills) {
|
|
5584
|
-
console.log(` ${chalk3.cyan(skill.name)} - ${skill.description}`);
|
|
5585
|
-
}
|
|
5586
|
-
console.log(chalk3.bold("\n\u{1F527} Available Tools:\n"));
|
|
5587
|
-
for (const tool of tools) {
|
|
5588
|
-
let statusIcon = " ";
|
|
5589
|
-
let statusText = "";
|
|
5590
|
-
if (tool.status === "ready") {
|
|
5591
|
-
statusIcon = chalk3.green("\u2713");
|
|
5592
|
-
statusText = chalk3.gray("(ready)");
|
|
5593
|
-
} else if (tool.status === "needs_config") {
|
|
5594
|
-
statusIcon = chalk3.yellow("\u26A0");
|
|
5595
|
-
statusText = chalk3.yellow("(needs config)");
|
|
5596
|
-
} else if (tool.status === "error") {
|
|
5597
|
-
statusIcon = chalk3.red("\u2717");
|
|
5598
|
-
statusText = chalk3.red("(error)");
|
|
5599
|
-
}
|
|
5600
|
-
console.log(` ${statusIcon} ${chalk3.cyan(tool.name)} - ${tool.description} ${statusText}`);
|
|
5601
|
-
if (tool.errorMessage) {
|
|
5602
|
-
console.log(` ${chalk3.red("Error:")} ${tool.errorMessage}`);
|
|
5603
|
-
}
|
|
5604
|
-
}
|
|
5605
|
-
console.log();
|
|
5606
|
-
console.log(chalk3.gray('Tip: Use "aikit skills <tool-name> config" to configure a tool\n'));
|
|
5607
|
-
});
|
|
5608
|
-
skillsCmd.command("show <name>").description("Show skill details").action(async (name) => {
|
|
5609
|
-
const config = await loadConfig();
|
|
5610
|
-
const engine = new SkillEngine(config);
|
|
5611
|
-
const skill = await engine.getSkill(name);
|
|
5612
|
-
if (!skill) {
|
|
5613
|
-
logger.error(`Skill not found: ${name}`);
|
|
5614
|
-
process.exit(1);
|
|
5615
|
-
}
|
|
5616
|
-
console.log(chalk3.bold(`
|
|
5617
|
-
\u{1F4D6} Skill: ${skill.name}
|
|
5618
|
-
`));
|
|
5619
|
-
console.log(chalk3.gray(skill.description));
|
|
5620
|
-
console.log(chalk3.bold("\nWorkflow:"));
|
|
5621
|
-
console.log(skill.content);
|
|
5622
|
-
});
|
|
5623
|
-
skillsCmd.command("create <name>").description("Create a new skill").action(async (name) => {
|
|
5624
|
-
const config = await loadConfig();
|
|
5625
|
-
const engine = new SkillEngine(config);
|
|
5626
|
-
await engine.createSkill(name);
|
|
5627
|
-
logger.success(`Skill created: ${name}`);
|
|
5628
|
-
});
|
|
5629
|
-
skillsCmd.command("sync").description("Sync global skills to project").action(async () => {
|
|
5630
|
-
const config = await loadConfig();
|
|
5631
|
-
const engine = new SkillEngine(config);
|
|
5632
|
-
const result = await engine.syncSkillsToProject();
|
|
5633
|
-
if (result.count === 0) {
|
|
5634
|
-
logger.info("Skills already in sync or no global skills to sync");
|
|
5635
|
-
} else {
|
|
5636
|
-
console.log(chalk3.bold(`
|
|
5637
|
-
\u2713 Synced ${result.count} skills to project:
|
|
5638
|
-
`));
|
|
5639
|
-
for (const skill of result.synced) {
|
|
5640
|
-
console.log(` ${chalk3.cyan("\u2022")} ${skill}`);
|
|
5641
|
-
}
|
|
5642
|
-
console.log();
|
|
5643
|
-
}
|
|
5644
|
-
});
|
|
5645
|
-
skillsCmd.command("config <tool-name>").description("Configure a tool (e.g., config figma-analysis)").action(async (toolName) => {
|
|
5646
|
-
const config = await loadConfig();
|
|
5647
|
-
const { ToolConfigManager: ToolConfigManager2 } = await Promise.resolve().then(() => (init_tool_config(), tool_config_exports));
|
|
5648
|
-
const toolConfigManager = new ToolConfigManager2(config);
|
|
5649
|
-
const tool = await toolConfigManager.getToolConfig(toolName);
|
|
5650
|
-
if (!tool) {
|
|
5651
|
-
logger.error(`Tool not found: ${toolName}`);
|
|
5652
|
-
console.log(chalk3.gray("\nAvailable tools:"));
|
|
5653
|
-
const tools = await toolConfigManager.listTools();
|
|
5654
|
-
for (const t of tools) {
|
|
5655
|
-
console.log(` - ${chalk3.cyan(t.name)}`);
|
|
5656
|
-
}
|
|
5657
|
-
console.log();
|
|
5658
|
-
process.exit(1);
|
|
5659
|
-
}
|
|
5660
|
-
console.log(chalk3.bold(`
|
|
5661
|
-
\u{1F527} Configuring: ${tool.name}
|
|
5662
|
-
`));
|
|
5663
|
-
console.log(chalk3.gray(tool.description));
|
|
5664
|
-
console.log();
|
|
5665
|
-
if (tool.configMethod === "oauth") {
|
|
5666
|
-
if (toolName === "figma-analysis") {
|
|
5667
|
-
const { FigmaOAuth: FigmaOAuth2 } = await Promise.resolve().then(() => (init_figma_oauth(), figma_oauth_exports));
|
|
5668
|
-
const oauth = new FigmaOAuth2(toolConfigManager);
|
|
5669
|
-
try {
|
|
5670
|
-
const token = await oauth.authenticate();
|
|
5671
|
-
console.log(chalk3.gray("\nValidating token..."));
|
|
5672
|
-
const isValid = await oauth.validateToken(token);
|
|
5673
|
-
if (isValid) {
|
|
5674
|
-
logger.success(`
|
|
5675
|
-
\u2705 ${tool.name} configured successfully!`);
|
|
5676
|
-
console.log(chalk3.gray("\nYou can now use the /analyze-figma command in OpenCode.\n"));
|
|
5677
|
-
} else {
|
|
5678
|
-
await toolConfigManager.updateToolConfig(toolName, {
|
|
5679
|
-
status: "error",
|
|
5680
|
-
errorMessage: "Token validation failed"
|
|
5681
|
-
});
|
|
5682
|
-
logger.error("Token validation failed. Please try again.");
|
|
5683
|
-
process.exit(1);
|
|
5684
|
-
}
|
|
5685
|
-
} catch (error) {
|
|
5686
|
-
await toolConfigManager.updateToolConfig(toolName, {
|
|
5687
|
-
status: "error",
|
|
5688
|
-
errorMessage: error instanceof Error ? error.message : String(error)
|
|
5689
|
-
});
|
|
5690
|
-
logger.error(`Configuration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5691
|
-
process.exit(1);
|
|
5692
|
-
}
|
|
5693
|
-
} else {
|
|
5694
|
-
logger.error(`OAuth flow not implemented for tool: ${toolName}`);
|
|
5695
|
-
process.exit(1);
|
|
5696
|
-
}
|
|
5697
|
-
} else if (tool.configMethod === "manual") {
|
|
5698
|
-
logger.info("Manual configuration not yet implemented");
|
|
5699
|
-
process.exit(1);
|
|
5700
|
-
} else {
|
|
5701
|
-
logger.info(`Tool ${tool.name} does not require configuration`);
|
|
5702
|
-
}
|
|
5703
|
-
});
|
|
5704
|
-
skillsCmd.command("*").description("Configure a tool (e.g., figma-analysis config)").allowUnknownOption().action(async () => {
|
|
5705
|
-
const args = process.argv.slice(process.argv.indexOf("skills") + 1);
|
|
5706
|
-
const toolName = args[0];
|
|
5707
|
-
const action = args[1];
|
|
5708
|
-
if (action === "config" && toolName) {
|
|
5709
|
-
const config = await loadConfig();
|
|
5710
|
-
const { ToolConfigManager: ToolConfigManager2 } = await Promise.resolve().then(() => (init_tool_config(), tool_config_exports));
|
|
5711
|
-
const toolConfigManager = new ToolConfigManager2(config);
|
|
5712
|
-
const tool = await toolConfigManager.getToolConfig(toolName);
|
|
5713
|
-
if (!tool) {
|
|
5714
|
-
logger.error(`Tool not found: ${toolName}`);
|
|
5715
|
-
console.log(chalk3.gray("\nAvailable tools:"));
|
|
5716
|
-
const tools = await toolConfigManager.listTools();
|
|
5717
|
-
for (const t of tools) {
|
|
5718
|
-
console.log(` - ${chalk3.cyan(t.name)}`);
|
|
5719
|
-
}
|
|
5720
|
-
console.log();
|
|
5721
|
-
console.log(chalk3.gray("Tip: If you meant to show a skill, use: aikit skills show <name>"));
|
|
5722
|
-
console.log();
|
|
5723
|
-
process.exit(1);
|
|
5724
|
-
}
|
|
5725
|
-
console.log(chalk3.bold(`
|
|
5726
|
-
\u{1F527} Configuring: ${tool.name}
|
|
5727
|
-
`));
|
|
5728
|
-
console.log(chalk3.gray(tool.description));
|
|
5729
|
-
console.log();
|
|
5730
|
-
if (tool.configMethod === "oauth") {
|
|
5731
|
-
if (toolName === "figma-analysis") {
|
|
5732
|
-
const { FigmaOAuth: FigmaOAuth2 } = await Promise.resolve().then(() => (init_figma_oauth(), figma_oauth_exports));
|
|
5733
|
-
const oauth = new FigmaOAuth2(toolConfigManager);
|
|
5734
|
-
try {
|
|
5735
|
-
const token = await oauth.authenticate();
|
|
5736
|
-
console.log(chalk3.gray("\nValidating token..."));
|
|
5737
|
-
const isValid = await oauth.validateToken(token);
|
|
5738
|
-
if (isValid) {
|
|
5739
|
-
logger.success(`
|
|
5740
|
-
\u2705 ${tool.name} configured successfully!`);
|
|
5741
|
-
console.log(chalk3.gray("\nYou can now use the /analyze-figma command in OpenCode.\n"));
|
|
5742
|
-
} else {
|
|
5743
|
-
await toolConfigManager.updateToolConfig(toolName, {
|
|
5744
|
-
status: "error",
|
|
5745
|
-
errorMessage: "Token validation failed"
|
|
5746
|
-
});
|
|
5747
|
-
logger.error("Token validation failed. Please try again.");
|
|
5748
|
-
process.exit(1);
|
|
5749
|
-
}
|
|
5750
|
-
} catch (error) {
|
|
5751
|
-
await toolConfigManager.updateToolConfig(toolName, {
|
|
5752
|
-
status: "error",
|
|
5753
|
-
errorMessage: error instanceof Error ? error.message : String(error)
|
|
5754
|
-
});
|
|
5755
|
-
logger.error(`Configuration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5756
|
-
process.exit(1);
|
|
5757
|
-
}
|
|
5758
|
-
} else {
|
|
5759
|
-
logger.error(`OAuth flow not implemented for tool: ${toolName}`);
|
|
5760
|
-
process.exit(1);
|
|
5761
|
-
}
|
|
5762
|
-
} else if (tool.configMethod === "manual") {
|
|
5763
|
-
logger.info("Manual configuration not yet implemented");
|
|
5764
|
-
process.exit(1);
|
|
5765
|
-
} else {
|
|
5766
|
-
logger.info(`Tool ${tool.name} does not require configuration`);
|
|
5767
|
-
}
|
|
5768
|
-
} else {
|
|
5769
|
-
logger.error(`Unknown command: ${toolName || "unknown"} ${action || ""}`);
|
|
5770
|
-
console.log(chalk3.gray("\nAvailable commands:"));
|
|
5771
|
-
console.log(" aikit skills list - List all skills and tools");
|
|
5772
|
-
console.log(" aikit skills show <name> - Show skill details");
|
|
5773
|
-
console.log(" aikit skills config <tool-name> - Configure a tool");
|
|
5774
|
-
console.log(" aikit skills <tool-name> config - Configure a tool (alternative syntax)");
|
|
5775
|
-
console.log();
|
|
5776
|
-
process.exit(1);
|
|
5777
|
-
}
|
|
5778
|
-
});
|
|
5779
|
-
var agentsCmd = program.command("agents").description("Manage agents");
|
|
5780
|
-
agentsCmd.command("list").description("List available agents").action(async () => {
|
|
5781
|
-
const config = await loadConfig();
|
|
5782
|
-
const manager = new AgentManager(config);
|
|
5783
|
-
const agents = manager.listAgents();
|
|
5784
|
-
console.log(chalk3.bold("\n\u{1F916} Available Agents:\n"));
|
|
5785
|
-
for (const agent of agents) {
|
|
5786
|
-
console.log(` ${chalk3.cyan(`@${agent.name}`)} - ${agent.description}`);
|
|
5787
|
-
console.log(chalk3.gray(` Use when: ${agent.useWhen}`));
|
|
5788
|
-
}
|
|
5789
|
-
console.log();
|
|
5790
|
-
});
|
|
5791
|
-
var commandsCmd = program.command("commands").description("Manage commands");
|
|
5792
|
-
commandsCmd.command("list").description("List available commands").action(async () => {
|
|
5793
|
-
const config = await loadConfig();
|
|
5794
|
-
const runner = new CommandRunner(config);
|
|
5795
|
-
const commands = await runner.listCommands();
|
|
5796
|
-
console.log(chalk3.bold("\n\u26A1 Available Commands:\n"));
|
|
5797
|
-
const groups = groupBy(commands, (c) => c.category);
|
|
5798
|
-
for (const [category, cmds] of Object.entries(groups)) {
|
|
5799
|
-
console.log(chalk3.bold.yellow(`
|
|
5800
|
-
${category}:`));
|
|
5801
|
-
for (const cmd of cmds) {
|
|
5802
|
-
console.log(` ${chalk3.cyan(`/${cmd.name}`)} - ${cmd.description}`);
|
|
5803
|
-
}
|
|
5804
|
-
}
|
|
5805
|
-
console.log();
|
|
5806
|
-
});
|
|
5807
|
-
var toolsCmd = program.command("tools").description("Manage custom tools");
|
|
5808
|
-
toolsCmd.command("list").description("List available tools").action(async () => {
|
|
5809
|
-
const config = await loadConfig();
|
|
5810
|
-
const registry = new ToolRegistry(config);
|
|
5811
|
-
const tools = await registry.listTools();
|
|
5812
|
-
console.log(chalk3.bold("\n\u{1F527} Available Tools:\n"));
|
|
5813
|
-
for (const tool of tools) {
|
|
5814
|
-
console.log(` ${chalk3.cyan(tool.name)} - ${tool.description}`);
|
|
5815
|
-
}
|
|
5816
|
-
console.log();
|
|
5817
|
-
});
|
|
5818
|
-
var pluginsCmd = program.command("plugins").description("Manage plugins");
|
|
5819
|
-
pluginsCmd.command("list").description("List available plugins").action(async () => {
|
|
5820
|
-
const config = await loadConfig();
|
|
5821
|
-
const system = new PluginSystem(config);
|
|
5822
|
-
const plugins = await system.listPlugins();
|
|
5823
|
-
console.log(chalk3.bold("\n\u{1F50C} Available Plugins:\n"));
|
|
5824
|
-
for (const plugin of plugins) {
|
|
5825
|
-
const status = plugin.enabled ? chalk3.green("\u2713") : chalk3.gray("\u25CB");
|
|
5826
|
-
console.log(` ${status} ${chalk3.cyan(plugin.name)} - ${plugin.description}`);
|
|
5827
|
-
}
|
|
5828
|
-
console.log();
|
|
5829
|
-
});
|
|
5830
|
-
var memoryCmd = program.command("memory").description("Manage persistent memory");
|
|
5831
|
-
memoryCmd.command("list").description("List memory entries").action(async () => {
|
|
5832
|
-
const config = await loadConfig();
|
|
5833
|
-
const memory = new MemoryManager(config);
|
|
5834
|
-
const entries = await memory.list();
|
|
5835
|
-
console.log(chalk3.bold("\n\u{1F9E0} Memory Entries:\n"));
|
|
5836
|
-
for (const entry of entries) {
|
|
5837
|
-
console.log(` ${chalk3.cyan(entry.key)} - ${entry.summary}`);
|
|
5838
|
-
console.log(chalk3.gray(` Updated: ${entry.updatedAt}`));
|
|
5839
|
-
}
|
|
5840
|
-
console.log();
|
|
5841
|
-
});
|
|
5842
|
-
memoryCmd.command("read <key>").description("Read a memory entry").action(async (key) => {
|
|
5843
|
-
const config = await loadConfig();
|
|
5844
|
-
const memory = new MemoryManager(config);
|
|
5845
|
-
const content = await memory.read(key);
|
|
5846
|
-
if (!content) {
|
|
5847
|
-
logger.error(`Memory entry not found: ${key}`);
|
|
5848
|
-
process.exit(1);
|
|
5849
|
-
}
|
|
5850
|
-
console.log(content);
|
|
5851
|
-
});
|
|
5852
|
-
var beadsCmd = program.command("beads").description("Beads task management integration");
|
|
5853
|
-
beadsCmd.command("status").description("Show current Beads status").action(async () => {
|
|
5854
|
-
const beads = new BeadsIntegration();
|
|
5855
|
-
const status = await beads.getStatus();
|
|
5856
|
-
console.log(chalk3.bold("\n\u{1F4FF} Beads Status:\n"));
|
|
5857
|
-
console.log(` Active tasks: ${status.activeTasks}`);
|
|
5858
|
-
console.log(` Completed: ${status.completedTasks}`);
|
|
5859
|
-
console.log(` Current: ${status.currentTask || "None"}`);
|
|
5860
|
-
console.log();
|
|
5861
|
-
});
|
|
5862
|
-
program.command("status").description("Show AIKit status").action(async () => {
|
|
5863
|
-
console.log(chalk3.bold(`
|
|
5864
|
-
\u{1F680} AIKit v${getVersion}
|
|
5865
|
-
`));
|
|
5866
|
-
try {
|
|
5867
|
-
const config = await loadConfig();
|
|
5868
|
-
console.log(chalk3.green("\u2713 Configuration loaded"));
|
|
5869
|
-
const skillEngine = new SkillEngine(config);
|
|
5870
|
-
const skills = await skillEngine.listSkills();
|
|
5871
|
-
console.log(` Skills: ${skills.length}`);
|
|
5872
|
-
const agentManager = new AgentManager(config);
|
|
5873
|
-
const agents = agentManager.listAgents();
|
|
5874
|
-
console.log(` Agents: ${agents.length}`);
|
|
5875
|
-
const commandRunner = new CommandRunner(config);
|
|
5876
|
-
const commands = await commandRunner.listCommands();
|
|
5877
|
-
console.log(` Commands: ${commands.length}`);
|
|
5878
|
-
const toolRegistry = new ToolRegistry(config);
|
|
5879
|
-
const tools = await toolRegistry.listTools();
|
|
5880
|
-
console.log(` Tools: ${tools.length}`);
|
|
5881
|
-
const beads = new BeadsIntegration();
|
|
5882
|
-
const beadsStatus = await beads.isInstalled();
|
|
5883
|
-
console.log(` Beads: ${beadsStatus ? chalk3.green("Installed") : chalk3.yellow("Not installed")}`);
|
|
5884
|
-
} catch (error) {
|
|
5885
|
-
console.log(chalk3.yellow('\u26A0 AIKit not initialized. Run "aikit init" to get started.'));
|
|
5886
|
-
}
|
|
5887
|
-
console.log();
|
|
5888
|
-
});
|
|
5889
|
-
async function initializeConfig(configDir, _isGlobal) {
|
|
5890
|
-
const { mkdir: mkdir11, writeFile: writeFile13 } = await import("fs/promises");
|
|
5891
|
-
const { join: join18 } = await import("path");
|
|
5892
|
-
const dirs = [
|
|
5893
|
-
"",
|
|
5894
|
-
"skills",
|
|
5895
|
-
"agents",
|
|
5896
|
-
"commands",
|
|
5897
|
-
"commands/build",
|
|
5898
|
-
"commands/git",
|
|
5899
|
-
"commands/plan",
|
|
5900
|
-
"commands/research",
|
|
5901
|
-
"tools",
|
|
5902
|
-
"plugins",
|
|
5903
|
-
"memory",
|
|
5904
|
-
"memory/_templates",
|
|
5905
|
-
"memory/handoffs",
|
|
5906
|
-
"memory/observations",
|
|
5907
|
-
"memory/research"
|
|
5908
|
-
];
|
|
5909
|
-
for (const dir of dirs) {
|
|
5910
|
-
await mkdir11(join18(configDir, dir), { recursive: true });
|
|
5911
|
-
}
|
|
5912
|
-
const defaultConfig = {
|
|
5913
|
-
version: getVersion,
|
|
5914
|
-
skills: { enabled: true },
|
|
5915
|
-
agents: { enabled: true, default: "build" },
|
|
5916
|
-
commands: { enabled: true },
|
|
5917
|
-
tools: { enabled: true },
|
|
5918
|
-
plugins: { enabled: true },
|
|
5919
|
-
memory: { enabled: true },
|
|
5920
|
-
beads: { enabled: true },
|
|
5921
|
-
antiHallucination: { enabled: true }
|
|
5922
|
-
};
|
|
5923
|
-
await writeFile13(
|
|
5924
|
-
join18(configDir, "aikit.json"),
|
|
5925
|
-
JSON.stringify(defaultConfig, null, 2)
|
|
5926
|
-
);
|
|
5927
|
-
const agentsMd = `# AIKit Agent Rules
|
|
5928
|
-
|
|
5929
|
-
## Build Commands
|
|
5930
|
-
- \`npm run build\` - Build the project
|
|
5931
|
-
- \`npm run test\` - Run tests
|
|
5932
|
-
- \`npm run lint\` - Run linting
|
|
5933
|
-
|
|
5934
|
-
## Code Style
|
|
5935
|
-
- Use 2 spaces for indentation
|
|
5936
|
-
- Use single quotes for strings
|
|
5937
|
-
- Add trailing commas
|
|
5938
|
-
|
|
5939
|
-
## Naming Conventions
|
|
5940
|
-
- Variables: camelCase
|
|
5941
|
-
- Components: PascalCase
|
|
5942
|
-
- Files: kebab-case
|
|
5943
|
-
|
|
5944
|
-
## Project-Specific Rules
|
|
5945
|
-
Add your project-specific rules here.
|
|
5946
|
-
`;
|
|
5947
|
-
await writeFile13(join18(configDir, "AGENTS.md"), agentsMd);
|
|
5948
|
-
}
|
|
5949
|
-
async function configureMcpServer(projectPath) {
|
|
5950
|
-
const { mkdir: mkdir11, writeFile: writeFile13, readFile: readFile12 } = await import("fs/promises");
|
|
5951
|
-
const { join: join18 } = await import("path");
|
|
5952
|
-
const { existsSync: existsSync5 } = await import("fs");
|
|
5953
|
-
const { homedir: homedir3 } = await import("os");
|
|
5954
|
-
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
5955
|
-
const { dirname: dirname4 } = await import("path");
|
|
5956
|
-
const currentFile = fileURLToPath3(import.meta.url);
|
|
5957
|
-
const currentDir = dirname4(currentFile);
|
|
5958
|
-
const aikitPath = join18(currentDir, "..");
|
|
5959
|
-
const mcpServerPath = join18(aikitPath, "dist", "mcp-server.js");
|
|
5960
|
-
const configLocations = [
|
|
5961
|
-
// Global config (most common)
|
|
5962
|
-
join18(homedir3(), ".config", "opencode", "opencode.json"),
|
|
5963
|
-
// Project-level config
|
|
5964
|
-
join18(projectPath, ".opencode", "opencode.json"),
|
|
5965
|
-
// Alternative global location
|
|
5966
|
-
join18(homedir3(), ".opencode", "opencode.json")
|
|
5967
|
-
];
|
|
5968
|
-
const mcpServerConfig = {
|
|
5969
|
-
type: "local",
|
|
5970
|
-
command: ["node", mcpServerPath],
|
|
5971
|
-
environment: {}
|
|
5972
|
-
};
|
|
5973
|
-
for (const configPath of configLocations) {
|
|
5974
|
-
try {
|
|
5975
|
-
const configDir = join18(configPath, "..");
|
|
5976
|
-
await mkdir11(configDir, { recursive: true });
|
|
5977
|
-
let config = {};
|
|
5978
|
-
if (existsSync5(configPath)) {
|
|
5979
|
-
try {
|
|
5980
|
-
const existing = await readFile12(configPath, "utf-8");
|
|
5981
|
-
config = JSON.parse(existing);
|
|
5982
|
-
} catch {
|
|
5983
|
-
config = {};
|
|
5984
|
-
}
|
|
5985
|
-
}
|
|
5986
|
-
if (!config.mcp) {
|
|
5987
|
-
config.mcp = {};
|
|
5988
|
-
}
|
|
5989
|
-
config.mcp.aikit = mcpServerConfig;
|
|
5990
|
-
await writeFile13(configPath, JSON.stringify(config, null, 2));
|
|
5991
|
-
logger.success(`
|
|
5992
|
-
\u2705 MCP server configured: ${configPath}`);
|
|
5993
|
-
logger.info(` Server: node ${mcpServerPath}`);
|
|
5994
|
-
return;
|
|
5995
|
-
} catch (error) {
|
|
5996
|
-
continue;
|
|
5997
|
-
}
|
|
5998
|
-
}
|
|
5999
|
-
const instructionsPath = join18(projectPath, ".opencode", "MCP_SETUP.md");
|
|
6000
|
-
await mkdir11(join18(projectPath, ".opencode"), { recursive: true });
|
|
6001
|
-
await writeFile13(instructionsPath, `# AIKit MCP Server Configuration
|
|
6002
|
-
|
|
6003
|
-
## Automatic Setup Failed
|
|
6004
|
-
|
|
6005
|
-
Please manually configure the MCP server in OpenCode.
|
|
6006
|
-
|
|
6007
|
-
## Configuration
|
|
6008
|
-
|
|
6009
|
-
Add this to your OpenCode configuration file (\`~/.config/opencode/opencode.json\`):
|
|
6010
|
-
|
|
6011
|
-
\`\`\`json
|
|
6012
|
-
{
|
|
6013
|
-
"mcpServers": {
|
|
6014
|
-
"aikit": {
|
|
6015
|
-
"command": "node",
|
|
6016
|
-
"args": ["${mcpServerPath}"],
|
|
6017
|
-
"env": {}
|
|
6018
|
-
}
|
|
6019
|
-
}
|
|
6020
|
-
}
|
|
6021
|
-
\`\`\`
|
|
6022
|
-
|
|
6023
|
-
## After Configuration
|
|
6024
|
-
|
|
6025
|
-
1. Restart OpenCode completely
|
|
6026
|
-
2. OpenCode will automatically start the MCP server
|
|
6027
|
-
3. Tools will be available via MCP protocol
|
|
6028
|
-
4. You can use tools like \`tool_read_figma_design\` directly
|
|
6029
|
-
|
|
6030
|
-
## Verify
|
|
6031
|
-
|
|
6032
|
-
After restarting OpenCode, check:
|
|
6033
|
-
- MCP server is running (check OpenCode settings)
|
|
6034
|
-
- Tools are discoverable (OpenCode should list them)
|
|
6035
|
-
- You can call tools via MCP protocol
|
|
6036
|
-
`);
|
|
6037
|
-
logger.warn(`
|
|
6038
|
-
\u26A0\uFE0F Could not auto-configure MCP server. See: ${instructionsPath}`);
|
|
6039
|
-
}
|
|
6040
|
-
async function installCliTool(tool) {
|
|
6041
|
-
try {
|
|
6042
|
-
logger.info(`Installing ${tool.displayName}...`);
|
|
6043
|
-
switch (tool.name) {
|
|
6044
|
-
case "opencode" /* OPENCODE */:
|
|
6045
|
-
await installToOpenCode(paths.opencodeConfig());
|
|
6046
|
-
break;
|
|
6047
|
-
case "claude" /* CLAUDE */:
|
|
6048
|
-
const { execSync: execSync2 } = await import("child_process");
|
|
6049
|
-
execSync2("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
|
|
6050
|
-
break;
|
|
6051
|
-
case "github" /* GITHUB */:
|
|
6052
|
-
const { execSync: execGh } = await import("child_process");
|
|
6053
|
-
execGh("npm install -g gh", { stdio: "inherit" });
|
|
6054
|
-
break;
|
|
6055
|
-
}
|
|
6056
|
-
logger.success(`\u2713 ${tool.displayName} installed`);
|
|
6057
|
-
return true;
|
|
6058
|
-
} catch (error) {
|
|
6059
|
-
logger.error(`Failed to install ${tool.displayName}:`, error);
|
|
6060
|
-
return false;
|
|
6061
|
-
}
|
|
6062
|
-
}
|
|
6063
|
-
async function installToOpenCode(_opencodePath) {
|
|
6064
|
-
const { mkdir: mkdir11, writeFile: writeFile13, access: access5 } = await import("fs/promises");
|
|
6065
|
-
const { join: join18 } = await import("path");
|
|
6066
|
-
const projectPath = process.cwd();
|
|
6067
|
-
const opencodeCommandDir = join18(projectPath, ".opencode", "command");
|
|
6068
|
-
const aikitDir = join18(projectPath, ".aikit");
|
|
6069
|
-
const opencodeAgentDir = join18(paths.opencodeConfig(), "agent");
|
|
6070
|
-
await mkdir11(opencodeCommandDir, { recursive: true });
|
|
6071
|
-
await mkdir11(join18(aikitDir, "skills"), { recursive: true });
|
|
6072
|
-
await mkdir11(opencodeAgentDir, { recursive: true });
|
|
6073
|
-
const agentFiles = {
|
|
6074
|
-
agent: `---
|
|
6075
|
-
description: General-purpose default agent (OpenCode compatibility).
|
|
6076
|
-
mode: subagent
|
|
6077
|
-
tools:
|
|
6078
|
-
"*": true
|
|
6079
|
-
---
|
|
6080
|
-
|
|
6081
|
-
Use for quick tasks when no specialized agent is needed.`,
|
|
6082
|
-
planner: `---
|
|
6083
|
-
description: Strategic planner; breaks down work and coordinates specialist agents.
|
|
6084
|
-
mode: subagent
|
|
6085
|
-
tools:
|
|
6086
|
-
"*": true
|
|
6087
|
-
---
|
|
6088
|
-
|
|
6089
|
-
Use when the task is complex or multi-step. Delegates to @build for implementation,
|
|
6090
|
-
@scout for research, @review for code review/security, @explore for codebase navigation,
|
|
6091
|
-
and @vision for visual analysis.`,
|
|
6092
|
-
build: `---
|
|
6093
|
-
description: Primary builder; writes code, tests, and implements features.
|
|
6094
|
-
mode: subagent
|
|
6095
|
-
tools:
|
|
6096
|
-
"*": true
|
|
6097
|
-
---
|
|
6098
|
-
|
|
6099
|
-
Use for feature implementation, refactors, and bug fixes. Prefer TDD, small steps,
|
|
6100
|
-
and run checks after changes. Delegates to @review for audits and @explore for context.`,
|
|
6101
|
-
rush: `---
|
|
6102
|
-
description: Fast execution for small/urgent changes with minimal planning.
|
|
6103
|
-
mode: subagent
|
|
6104
|
-
tools:
|
|
6105
|
-
"*": true
|
|
6106
|
-
---
|
|
6107
|
-
|
|
6108
|
-
Use for quick fixes, hotfixes, or tiny edits. Keep scope minimal and verify quickly.`,
|
|
6109
|
-
review: `---
|
|
6110
|
-
description: Code review and quality/security auditing agent.
|
|
6111
|
-
mode: subagent
|
|
6112
|
-
tools:
|
|
6113
|
-
"*": true
|
|
6114
|
-
---
|
|
6115
|
-
|
|
6116
|
-
Use to review correctness, security, performance, maintainability, and tests. Be specific
|
|
6117
|
-
about issues and suggest concrete fixes.`,
|
|
6118
|
-
scout: `---
|
|
6119
|
-
description: Research agent for external docs, patterns, and references.
|
|
6120
|
-
mode: subagent
|
|
6121
|
-
tools:
|
|
6122
|
-
"*": true
|
|
6123
|
-
---
|
|
6124
|
-
|
|
6125
|
-
Use to look up docs, examples, best practices. Summarize findings concisely and cite sources.`,
|
|
6126
|
-
explore: `---
|
|
6127
|
-
description: Codebase navigation agent (search, grep, structure understanding).
|
|
6128
|
-
mode: subagent
|
|
6129
|
-
tools:
|
|
6130
|
-
"*": true
|
|
6131
|
-
---
|
|
6132
|
-
|
|
6133
|
-
Use to locate files, patterns, dependencies, and gather quick context in the repo.`,
|
|
6134
|
-
vision: `---
|
|
6135
|
-
description: Visual analysis agent for mockups, screenshots, PDFs, diagrams.
|
|
6136
|
-
mode: subagent
|
|
6137
|
-
tools:
|
|
6138
|
-
"*": true
|
|
6139
|
-
---
|
|
6140
|
-
|
|
6141
|
-
Use to interpret visual assets (components, layout, colors, typography) and translate to tasks.`,
|
|
6142
|
-
"one-shot": `---
|
|
6143
|
-
description: End-to-end autonomous task execution (beta). Complete tasks from start to finish.
|
|
6144
|
-
mode: subagent
|
|
6145
|
-
tools:
|
|
6146
|
-
"*": true
|
|
6147
|
-
---
|
|
6148
|
-
|
|
6149
|
-
\u26A0\uFE0F BETA: This mode is experimental. Use for straightforward tasks first.
|
|
6150
|
-
|
|
6151
|
-
## One-Shot Mode - Autonomous Task Execution
|
|
6152
|
-
|
|
6153
|
-
Execute tasks end-to-end with minimal intervention:
|
|
6154
|
-
|
|
6155
|
-
### Workflow Phases
|
|
6156
|
-
1. **REQUIREMENTS** - Gather task type, scope, dependencies, success criteria
|
|
6157
|
-
2. **PLANNING** - Create detailed plan, recommend skills/tools, create tracking bead
|
|
6158
|
-
3. **COMPLEXITY** - Auto-split if: >30min, >10 files, >500 lines, >2 sub-systems
|
|
6159
|
-
4. **EXECUTION** - Parallel tasks (max 3), dynamic agent delegation
|
|
6160
|
-
5. **TESTING** - Run until pass: typecheck \u2192 test \u2192 lint \u2192 build (max 3 retries)
|
|
6161
|
-
6. **VERIFICATION** - Quality gates \u2713 \u2192 Manual verification \u2192 Deployment approval
|
|
6162
|
-
7. **COMPLETION** - Generate proof, update tracking, collect feedback
|
|
6163
|
-
|
|
6164
|
-
### Quality Gates (ALL must pass)
|
|
6165
|
-
- \`npm run typecheck\` - No type errors
|
|
6166
|
-
- \`npm run test\` - All tests pass
|
|
6167
|
-
- \`npm run lint\` - No lint errors
|
|
6168
|
-
- \`npm run build\` - Build succeeds
|
|
6169
|
-
|
|
6170
|
-
### Error Recovery (3 Levels)
|
|
6171
|
-
- **Level 1**: Auto-fix (type errors, lint --fix)
|
|
6172
|
-
- **Level 2**: Alternative approach via @review
|
|
6173
|
-
- **Level 3**: User intervention + follow-up task
|
|
6174
|
-
|
|
6175
|
-
### Delegates To
|
|
6176
|
-
@planner for planning, @build for implementation, @review for code review,
|
|
6177
|
-
@scout for research, @explore for navigation, @vision for visual analysis.
|
|
6178
|
-
|
|
6179
|
-
### Best Use Cases
|
|
6180
|
-
- Straightforward features with clear scope
|
|
6181
|
-
- Bug fixes with known reproduction steps
|
|
6182
|
-
- Refactoring with defined boundaries
|
|
6183
|
-
|
|
6184
|
-
### Consider Alternatives For
|
|
6185
|
-
- Complex multi-system features \u2192 Use /plan + /implement
|
|
6186
|
-
- Exploratory research \u2192 Use /research first
|
|
6187
|
-
- Critical production changes \u2192 Manual with /review`
|
|
6188
|
-
};
|
|
6189
|
-
for (const [name, content] of Object.entries(agentFiles)) {
|
|
6190
|
-
const filePath = join18(opencodeAgentDir, `${name}.md`);
|
|
6191
|
-
try {
|
|
6192
|
-
await access5(filePath);
|
|
6193
|
-
} catch {
|
|
6194
|
-
await writeFile13(filePath, content, "utf8");
|
|
6195
|
-
}
|
|
6196
|
-
}
|
|
6197
|
-
const config = await loadConfig();
|
|
6198
|
-
const skillEngine = new SkillEngine(config);
|
|
6199
|
-
const commandRunner = new CommandRunner(config);
|
|
6200
|
-
const skills = await skillEngine.listSkills();
|
|
6201
|
-
const commands = await commandRunner.listCommands();
|
|
6202
|
-
const opencodeCommands = {};
|
|
6203
|
-
const skillsList = skills.map((s) => `| \`/${s.name.replace(/\s+/g, "-")}\` | ${s.description} |`).join("\n");
|
|
6204
|
-
opencodeCommands["skills"] = `List all available AIKit skills and how to use them.
|
|
6205
|
-
|
|
6206
|
-
READ .aikit/AGENTS.md
|
|
6207
|
-
|
|
6208
|
-
## Available Skills
|
|
6209
|
-
|
|
6210
|
-
| Command | Description |
|
|
6211
|
-
|---------|-------------|
|
|
6212
|
-
${skillsList}
|
|
6213
|
-
|
|
6214
|
-
Type any command to use that skill. For example: \`/test-driven-development\` or \`/tdd\`.`;
|
|
6215
|
-
for (const skill of skills) {
|
|
6216
|
-
const commandName = skill.name.replace(/\s+/g, "-").toLowerCase();
|
|
6217
|
-
const skillPath = skill.filePath;
|
|
6218
|
-
const relativePath = skillPath.startsWith(projectPath) ? skillPath.replace(projectPath, "").replace(/\\/g, "/").replace(/^\//, "") : `.aikit/skills/${skill.name.replace(/\s+/g, "-").toLowerCase()}.md`;
|
|
6219
|
-
const useWhen = skill.useWhen || `The user asks you to ${skill.name}`;
|
|
6220
|
-
opencodeCommands[commandName] = `Use the **${skill.name} skill** ${useWhen.toLowerCase()}.
|
|
6221
|
-
|
|
6222
|
-
READ ${relativePath}
|
|
6223
|
-
|
|
6224
|
-
## Description
|
|
6225
|
-
${skill.description}
|
|
6226
|
-
|
|
6227
|
-
## When to Use
|
|
6228
|
-
${useWhen}
|
|
6229
|
-
|
|
6230
|
-
## Workflow
|
|
6231
|
-
${skill.content.split("\n").slice(0, 20).join("\n")}${skill.content.split("\n").length > 20 ? "\n\n... (see full skill file for complete workflow)" : ""}
|
|
6232
|
-
|
|
6233
|
-
**IMPORTANT**: Follow this skill's workflow step by step. Do not skip steps.
|
|
6234
|
-
Complete the checklist at the end of the skill.`;
|
|
6235
|
-
}
|
|
6236
|
-
for (const cmd of commands) {
|
|
6237
|
-
if (opencodeCommands[cmd.name]) continue;
|
|
6238
|
-
const commandName = cmd.name.replace(/\//g, "").replace(/\s+/g, "-");
|
|
6239
|
-
const examples = cmd.examples.map((e) => `- \`${e}\``).join("\n");
|
|
6240
|
-
if (cmd.name === "analyze-figma") {
|
|
6241
|
-
opencodeCommands[commandName] = `# Command: /analyze-figma
|
|
6242
|
-
|
|
6243
|
-
## Description
|
|
6244
|
-
${cmd.description}
|
|
6245
|
-
|
|
6246
|
-
## Usage
|
|
6247
|
-
\`${cmd.usage}\`
|
|
6248
|
-
|
|
6249
|
-
## Examples
|
|
6250
|
-
${examples}
|
|
6251
|
-
|
|
6252
|
-
## \u26A0\uFE0F CRITICAL: Extract URL FIRST!
|
|
6253
|
-
|
|
6254
|
-
**BEFORE ANYTHING ELSE**: Look at the user's FULL input message (all lines) and find the Figma URL. It's ALWAYS there - never ask for it!
|
|
6255
|
-
|
|
6256
|
-
**The URL pattern**: Look for text containing \`figma.com/design/\` anywhere in the user's message.
|
|
6257
|
-
|
|
6258
|
-
**Example of what user input looks like**:
|
|
6259
|
-
\`\`\`
|
|
6260
|
-
/analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/
|
|
6261
|
-
Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0
|
|
6262
|
-
\`\`\`
|
|
6263
|
-
|
|
6264
|
-
**Extract the complete URL** (combine if split):
|
|
6265
|
-
\`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0\`
|
|
6266
|
-
|
|
6267
|
-
## Workflow
|
|
6268
|
-
|
|
6269
|
-
**IMPORTANT**: When user provides a Figma URL, you MUST immediately:
|
|
6270
|
-
|
|
6271
|
-
**Step 1: Extract URL from User Input**
|
|
6272
|
-
|
|
6273
|
-
**CRITICAL**: The URL is ALWAYS in the user's input message! DO NOT ask for it - just extract it!
|
|
6274
|
-
|
|
6275
|
-
**MANDATORY**: You MUST extract the URL before proceeding. This is not optional!
|
|
6276
|
-
|
|
6277
|
-
**How to Extract**:
|
|
6278
|
-
1. **Read the ENTIRE user input message** - look at ALL lines, not just the first line
|
|
6279
|
-
2. **Search for ANY text containing** \`figma.com/design/\` - this is the URL
|
|
6280
|
-
3. **URL may appear in different formats**:
|
|
6281
|
-
- On same line: \`/analyze-figma https://www.figma.com/design/...\`
|
|
6282
|
-
- Split across lines:
|
|
6283
|
-
\`\`\`
|
|
6284
|
-
/analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/
|
|
6285
|
-
Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0
|
|
6286
|
-
\`\`\`
|
|
6287
|
-
- Just the URL: \`https://www.figma.com/design/...\`
|
|
6288
|
-
4. **Extract the COMPLETE URL**:
|
|
6289
|
-
- Start from \`https://\` or \`http://\`
|
|
6290
|
-
- Include everything until the end of the line or next whitespace
|
|
6291
|
-
- If URL is split, combine ALL parts into one complete URL
|
|
6292
|
-
5. **Include ALL query parameters**: \`?node-id=...\`, \`&t=...\`, etc.
|
|
6293
|
-
|
|
6294
|
-
**REAL EXAMPLE**:
|
|
6295
|
-
\`\`\`
|
|
6296
|
-
User input:
|
|
6297
|
-
/analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/
|
|
6298
|
-
Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0
|
|
6299
|
-
\`\`\`
|
|
6300
|
-
|
|
6301
|
-
**Extract as**:
|
|
6302
|
-
\`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0\`
|
|
6303
|
-
|
|
6304
|
-
**CRITICAL RULES**:
|
|
6305
|
-
- \u2705 DO: Read the ENTIRE user message (all lines)
|
|
6306
|
-
- \u2705 DO: Look for \`figma.com/design/\` anywhere in the message
|
|
6307
|
-
- \u2705 DO: Combine split lines into one URL
|
|
6308
|
-
- \u274C DO NOT: Ask user for URL - it's ALWAYS in the input
|
|
6309
|
-
- \u274C DO NOT: Skip this step - URL extraction is MANDATORY
|
|
6310
|
-
- \u274C DO NOT: Proceed without extracting URL first
|
|
6311
|
-
|
|
6312
|
-
**If you think URL is not found**:
|
|
6313
|
-
1. Re-read the user's message line by line
|
|
6314
|
-
2. Look for ANY mention of "figma.com"
|
|
6315
|
-
3. Check if URL is split across multiple lines
|
|
6316
|
-
4. The URL is definitely there - find it!
|
|
6317
|
-
- If URL not found in current message, check previous messages
|
|
6318
|
-
|
|
6319
|
-
**Step 2: Check Tool Configuration**
|
|
6320
|
-
|
|
6321
|
-
Before calling the tool, verify that Figma tool is configured:
|
|
6322
|
-
- If not configured, inform user to run: \`aikit skills figma-analysis config\`
|
|
6323
|
-
- The tool requires a Figma Personal Access Token
|
|
6324
|
-
|
|
6325
|
-
**Step 3: Call MCP Tool read_figma_design**
|
|
6326
|
-
|
|
6327
|
-
Use the MCP tool \`read_figma_design\` (or \`tool_read_figma_design\` via MCP) with the extracted URL:
|
|
6328
|
-
\`\`\`
|
|
6329
|
-
Use tool: read_figma_design
|
|
6330
|
-
Arguments: { "url": "[extracted URL]" }
|
|
6331
|
-
\`\`\`
|
|
6332
|
-
|
|
6333
|
-
**This tool will automatically**:
|
|
6334
|
-
1. Validate the Figma URL format
|
|
6335
|
-
2. Check if Figma tool is configured
|
|
6336
|
-
3. Call Figma API to fetch design data
|
|
6337
|
-
4. Extract design tokens:
|
|
6338
|
-
- Colors (from fills and strokes, converted to hex)
|
|
6339
|
-
- Typography (font families, sizes, weights, line heights)
|
|
6340
|
-
- Spacing system (8px grid detection)
|
|
6341
|
-
- Components (from Figma components)
|
|
6342
|
-
- Screens/Frames (dimensions and names)
|
|
6343
|
-
- Breakpoints (common responsive breakpoints)
|
|
6344
|
-
5. Return formatted markdown with all extracted tokens
|
|
6345
|
-
|
|
6346
|
-
**Step 4: Format and Save**
|
|
6347
|
-
|
|
6348
|
-
Format extracted tokens as structured markdown and save using memory-update tool:
|
|
6349
|
-
\`\`\`
|
|
6350
|
-
Use tool: memory-update
|
|
6351
|
-
Arguments: {
|
|
6352
|
-
"key": "research/figma-analysis",
|
|
6353
|
-
"content": "[formatted markdown with all tokens]"
|
|
6354
|
-
}
|
|
6355
|
-
\`\`\`
|
|
6356
|
-
|
|
6357
|
-
**Step 5: Report Results**
|
|
6358
|
-
|
|
6359
|
-
Report what was extracted:
|
|
6360
|
-
- Number of screens found
|
|
6361
|
-
- Number of colors in palette
|
|
6362
|
-
- Typography styles found
|
|
6363
|
-
- Components identified
|
|
6364
|
-
- Confirm save location: \`memory/research/figma-analysis.md\`
|
|
6365
|
-
|
|
6366
|
-
## Critical Instructions
|
|
6367
|
-
|
|
6368
|
-
- **DO NOT** ask user to "share the Figma URL" - they already provided it in the command
|
|
6369
|
-
- **DO NOT** wait for confirmation - just start analyzing immediately
|
|
6370
|
-
- **DO** extract URL from full user input message
|
|
6371
|
-
- **DO** call MCP tool \`read_figma_design\` immediately
|
|
6372
|
-
- **DO** use browser MCP to navigate and snapshot
|
|
6373
|
-
- **DO** extract everything automatically without asking
|
|
6374
|
-
- **DO** save to memory automatically
|
|
6375
|
-
|
|
6376
|
-
## Error Handling
|
|
6377
|
-
|
|
6378
|
-
If the tool returns an error:
|
|
6379
|
-
1. **If "needs config"**: Guide user to run \`aikit skills figma-analysis config\`
|
|
6380
|
-
2. **If API error**:
|
|
6381
|
-
- Verify the Figma URL is correct and accessible
|
|
6382
|
-
- Ensure your API token has access to the file
|
|
6383
|
-
- Check if the file is public or you have permission to access it
|
|
6384
|
-
3. **If URL invalid**: Re-check the extracted URL format
|
|
6385
|
-
|
|
6386
|
-
## How to Parse URL from Command
|
|
6387
|
-
|
|
6388
|
-
**CRITICAL**: The Figma URL is provided in the SAME message as the command!
|
|
6389
|
-
|
|
6390
|
-
Example:
|
|
6391
|
-
- User input: \`/analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1\`
|
|
6392
|
-
- The URL is: \`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1\`
|
|
6393
|
-
|
|
6394
|
-
**Extract the URL** from the command input:
|
|
6395
|
-
- Everything after \`/analyze-figma \` (note the space) is the URL
|
|
6396
|
-
- The URL starts with \`https://\` or \`http://\`
|
|
6397
|
-
- Extract the ENTIRE URL including all query parameters
|
|
6398
|
-
- Check the FULL user message, not just command name
|
|
6399
|
-
|
|
6400
|
-
## Example Usage
|
|
6401
|
-
|
|
6402
|
-
User input: \`/analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1\`
|
|
6403
|
-
|
|
6404
|
-
**Step 1: Extract URL**
|
|
6405
|
-
- Check full user input message
|
|
6406
|
-
- Extract: \`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1\`
|
|
6407
|
-
|
|
6408
|
-
**Step 2: Call MCP Tool**
|
|
6409
|
-
\`\`\`
|
|
6410
|
-
Use tool: read_figma_design
|
|
6411
|
-
Arguments: { "url": "https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1" }
|
|
6412
|
-
\`\`\`
|
|
6413
|
-
|
|
6414
|
-
**Step 3: Tool automatically extracts tokens via Figma API**
|
|
6415
|
-
|
|
6416
|
-
**Step 4: Save to memory using memory-update tool**
|
|
5943
|
+
const projectSkillsPath = paths.skills(this.versionManager["config"].configPath);
|
|
5944
|
+
const newSkills = [];
|
|
5945
|
+
const updatedSkills = [];
|
|
5946
|
+
const removedSkills = [];
|
|
5947
|
+
for (const skill of changes.newSkills) {
|
|
5948
|
+
if (!options.dryRun) {
|
|
5949
|
+
await this.installSkill(globalSkillsPath, skill, projectSkillsPath);
|
|
5950
|
+
}
|
|
5951
|
+
newSkills.push(skill.name);
|
|
5952
|
+
console.log(chalk3.green(` + ${skill.name}`));
|
|
5953
|
+
}
|
|
5954
|
+
for (const skill of changes.modifiedSkills) {
|
|
5955
|
+
if (!options.dryRun) {
|
|
5956
|
+
await this.installSkill(globalSkillsPath, skill, projectSkillsPath);
|
|
5957
|
+
}
|
|
5958
|
+
updatedSkills.push(skill.name);
|
|
5959
|
+
console.log(chalk3.yellow(` ~ ${skill.name}`));
|
|
5960
|
+
}
|
|
5961
|
+
for (const skill of changes.removedSkills) {
|
|
5962
|
+
if (!options.dryRun) {
|
|
5963
|
+
await this.archiveSkill(projectSkillsPath, skill);
|
|
5964
|
+
}
|
|
5965
|
+
removedSkills.push(skill.name);
|
|
5966
|
+
console.log(chalk3.red(` - ${skill.name} (archived)`));
|
|
5967
|
+
}
|
|
5968
|
+
return {
|
|
5969
|
+
newSkills,
|
|
5970
|
+
updatedSkills,
|
|
5971
|
+
removedSkills
|
|
5972
|
+
};
|
|
5973
|
+
}
|
|
5974
|
+
/**
|
|
5975
|
+
* Install a skill
|
|
5976
|
+
*/
|
|
5977
|
+
async installSkill(sourceDir, skill, targetDir) {
|
|
5978
|
+
const sourcePath = join17(sourceDir, skill.category, `${skill.name}.md`);
|
|
5979
|
+
const targetPath = join17(targetDir, skill.category, `${skill.name}.md`);
|
|
5980
|
+
await mkdir10(dirname4(targetPath), { recursive: true });
|
|
5981
|
+
await copyFile(sourcePath, targetPath);
|
|
5982
|
+
}
|
|
5983
|
+
/**
|
|
5984
|
+
* Archive a removed skill
|
|
5985
|
+
*/
|
|
5986
|
+
async archiveSkill(targetDir, skill) {
|
|
5987
|
+
const sourcePath = join17(targetDir, skill.category, `${skill.name}.md`);
|
|
5988
|
+
const targetPath = join17(targetDir, skill.category, `${skill.name}-deprecated.md`);
|
|
5989
|
+
try {
|
|
5990
|
+
const content = await readFile11(sourcePath, "utf-8");
|
|
5991
|
+
const deprecatedNotice = `---
|
|
5992
|
+
\u26A0\uFE0F DEPRECATED: This skill has been removed
|
|
6417
5993
|
|
|
6418
|
-
|
|
5994
|
+
Deprecation date: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5995
|
+
Reason: Check release notes for replacement
|
|
5996
|
+
---
|
|
6419
5997
|
|
|
6420
|
-
|
|
5998
|
+
${content}`;
|
|
5999
|
+
await mkdir10(dirname4(targetPath), { recursive: true });
|
|
6000
|
+
await writeFile12(targetPath, deprecatedNotice);
|
|
6001
|
+
} catch (error) {
|
|
6002
|
+
if (error.code === "ENOENT") {
|
|
6003
|
+
console.log(chalk3.yellow(` - ${skill.name} (not found, skipping)`));
|
|
6004
|
+
} else {
|
|
6005
|
+
throw error;
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
6008
|
+
}
|
|
6009
|
+
/**
|
|
6010
|
+
* Display sync summary
|
|
6011
|
+
*/
|
|
6012
|
+
displaySummary(result) {
|
|
6013
|
+
console.log(chalk3.bold("\n\u{1F4CB} Summary:\n"));
|
|
6014
|
+
console.log(` Updated from: ${chalk3.cyan(result.backupId || "N/A")}`);
|
|
6015
|
+
console.log(` Updated to: ${chalk3.cyan("current")}`);
|
|
6016
|
+
console.log();
|
|
6017
|
+
if (result.newSkills.length > 0) {
|
|
6018
|
+
console.log(chalk3.green(` ${result.newSkills.length} new skills installed`));
|
|
6019
|
+
}
|
|
6020
|
+
if (result.updatedSkills.length > 0) {
|
|
6021
|
+
console.log(chalk3.yellow(` ${result.updatedSkills.length} skills updated`));
|
|
6022
|
+
}
|
|
6023
|
+
if (result.removedSkills.length > 0) {
|
|
6024
|
+
console.log(chalk3.red(` ${result.removedSkills.length} skills archived`));
|
|
6025
|
+
}
|
|
6026
|
+
if (result.migrationsRun.length > 0) {
|
|
6027
|
+
console.log(chalk3.blue(` ${result.migrationsRun.length} migrations run`));
|
|
6028
|
+
}
|
|
6029
|
+
if (result.backupId) {
|
|
6030
|
+
console.log(chalk3.gray(`
|
|
6031
|
+
Rollback available: aikit sync rollback ${result.backupId}`));
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
};
|
|
6421
6035
|
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
-
|
|
6426
|
-
|
|
6036
|
+
// src/cli/commands/sync.ts
|
|
6037
|
+
init_logger();
|
|
6038
|
+
function registerSyncCommand(program2) {
|
|
6039
|
+
program2.command("sync [subcommand]").description("Update AIKit to latest version").option("--dry-run", "Preview changes without applying").option("-f, --force", "Skip confirmation prompts").option("--no-backup", "Skip creating backup").action(async (subcommand, options) => {
|
|
6040
|
+
const config = await loadConfig();
|
|
6041
|
+
const syncEngine = new SyncEngine(config);
|
|
6042
|
+
if (!subcommand) {
|
|
6043
|
+
await syncEngine.applyUpdate(options);
|
|
6427
6044
|
} else {
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6045
|
+
switch (subcommand) {
|
|
6046
|
+
case "check":
|
|
6047
|
+
await syncEngine.checkForUpdates();
|
|
6048
|
+
break;
|
|
6049
|
+
case "preview":
|
|
6050
|
+
await syncEngine.previewUpdate();
|
|
6051
|
+
break;
|
|
6052
|
+
case "apply":
|
|
6053
|
+
await syncEngine.applyUpdate(options);
|
|
6054
|
+
break;
|
|
6055
|
+
case "rollback":
|
|
6056
|
+
await syncEngine.rollback();
|
|
6057
|
+
break;
|
|
6058
|
+
default:
|
|
6059
|
+
logger.error(`Unknown subcommand: ${subcommand}`);
|
|
6060
|
+
console.log(chalk4.gray("Available subcommands: check, preview, apply, rollback"));
|
|
6061
|
+
process.exit(1);
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
});
|
|
6065
|
+
}
|
|
6441
6066
|
|
|
6442
|
-
|
|
6067
|
+
// src/cli/commands/skills.ts
|
|
6068
|
+
init_esm_shims();
|
|
6069
|
+
import chalk5 from "chalk";
|
|
6070
|
+
init_logger();
|
|
6071
|
+
function registerSkillsCommand(program2) {
|
|
6072
|
+
const skillsCmd = program2.command("skills").description("Manage skills");
|
|
6073
|
+
skillsCmd.command("list").description("List available skills and tools with their configuration status").action(async () => {
|
|
6074
|
+
const config = await loadConfig();
|
|
6075
|
+
const engine = new SkillEngine(config);
|
|
6076
|
+
const skills = await engine.listSkills();
|
|
6077
|
+
const { ToolConfigManager: ToolConfigManager2 } = await Promise.resolve().then(() => (init_tool_config(), tool_config_exports));
|
|
6078
|
+
const toolConfigManager = new ToolConfigManager2(config);
|
|
6079
|
+
const tools = await toolConfigManager.listTools();
|
|
6080
|
+
console.log(chalk5.bold("\n\u{1F4DA} Available Skills:\n"));
|
|
6081
|
+
for (const skill of skills) {
|
|
6082
|
+
console.log(` ${chalk5.cyan(skill.name)} - ${skill.description}`);
|
|
6083
|
+
}
|
|
6084
|
+
console.log(chalk5.bold("\n\u{1F527} Available Tools:\n"));
|
|
6085
|
+
for (const tool of tools) {
|
|
6086
|
+
let statusIcon = " ";
|
|
6087
|
+
let statusText = "";
|
|
6088
|
+
if (tool.status === "ready") {
|
|
6089
|
+
statusIcon = chalk5.green("\u2713");
|
|
6090
|
+
statusText = chalk5.gray("(ready)");
|
|
6091
|
+
} else if (tool.status === "needs_config") {
|
|
6092
|
+
statusIcon = chalk5.yellow("\u26A0");
|
|
6093
|
+
statusText = chalk5.yellow("(needs config)");
|
|
6094
|
+
} else if (tool.status === "error") {
|
|
6095
|
+
statusIcon = chalk5.red("\u2717");
|
|
6096
|
+
statusText = chalk5.red("(error)");
|
|
6097
|
+
}
|
|
6098
|
+
console.log(` ${statusIcon} ${chalk5.cyan(tool.name)} - ${tool.description} ${statusText}`);
|
|
6099
|
+
if (tool.errorMessage) {
|
|
6100
|
+
console.log(` ${chalk5.red("Error:")} ${tool.errorMessage}`);
|
|
6101
|
+
}
|
|
6102
|
+
}
|
|
6103
|
+
console.log();
|
|
6104
|
+
console.log(chalk5.gray('Tip: Use "aikit skills <tool-name> config" to configure a tool\n'));
|
|
6105
|
+
});
|
|
6106
|
+
skillsCmd.command("show <name>").description("Show skill details").action(async (name) => {
|
|
6107
|
+
const config = await loadConfig();
|
|
6108
|
+
const engine = new SkillEngine(config);
|
|
6109
|
+
const skill = await engine.getSkill(name);
|
|
6110
|
+
if (!skill) {
|
|
6111
|
+
logger.error(`Skill not found: ${name}`);
|
|
6112
|
+
process.exit(1);
|
|
6113
|
+
}
|
|
6114
|
+
console.log(chalk5.bold(`
|
|
6115
|
+
\u{1F4D6} Skill: ${skill.name}
|
|
6116
|
+
`));
|
|
6117
|
+
console.log(chalk5.gray(skill.description));
|
|
6118
|
+
console.log(chalk5.bold("\nWorkflow:"));
|
|
6119
|
+
console.log(skill.content);
|
|
6120
|
+
});
|
|
6121
|
+
skillsCmd.command("create <name>").description("Create a new skill").action(async (name) => {
|
|
6122
|
+
const config = await loadConfig();
|
|
6123
|
+
const engine = new SkillEngine(config);
|
|
6124
|
+
await engine.createSkill(name);
|
|
6125
|
+
logger.success(`Skill created: ${name}`);
|
|
6126
|
+
});
|
|
6127
|
+
skillsCmd.command("sync").description("Sync global skills to project").action(async () => {
|
|
6128
|
+
const config = await loadConfig();
|
|
6129
|
+
const engine = new SkillEngine(config);
|
|
6130
|
+
const result = await engine.syncSkillsToProject();
|
|
6131
|
+
if (result.count === 0) {
|
|
6132
|
+
logger.info("Skills already in sync or no global skills to sync");
|
|
6133
|
+
} else {
|
|
6134
|
+
console.log(chalk5.bold(`
|
|
6135
|
+
\u2713 Synced ${result.count} skills to project:
|
|
6136
|
+
`));
|
|
6137
|
+
for (const skill of result.synced) {
|
|
6138
|
+
console.log(` ${chalk5.cyan("\u2022")} ${skill}`);
|
|
6139
|
+
}
|
|
6140
|
+
console.log();
|
|
6141
|
+
}
|
|
6142
|
+
});
|
|
6143
|
+
skillsCmd.command("config <tool-name>").description("Configure a tool (e.g., config figma-analysis)").action(async (toolName) => {
|
|
6144
|
+
await configureToolAction(toolName);
|
|
6145
|
+
});
|
|
6146
|
+
skillsCmd.command("*").description("Configure a tool (e.g., figma-analysis config)").allowUnknownOption().action(async () => {
|
|
6147
|
+
const args = process.argv.slice(process.argv.indexOf("skills") + 1);
|
|
6148
|
+
const toolName = args[0];
|
|
6149
|
+
const action = args[1];
|
|
6150
|
+
if (action === "config" && toolName) {
|
|
6151
|
+
await configureToolAction(toolName);
|
|
6152
|
+
} else {
|
|
6153
|
+
logger.error(`Unknown command: ${toolName || "unknown"} ${action || ""}`);
|
|
6154
|
+
console.log(chalk5.gray("\nAvailable commands:"));
|
|
6155
|
+
console.log(" aikit skills list - List all skills and tools");
|
|
6156
|
+
console.log(" aikit skills show <name> - Show skill details");
|
|
6157
|
+
console.log(" aikit skills config <tool-name> - Configure a tool");
|
|
6158
|
+
console.log(" aikit skills <tool-name> config - Configure a tool (alternative syntax)");
|
|
6159
|
+
console.log();
|
|
6160
|
+
process.exit(1);
|
|
6161
|
+
}
|
|
6162
|
+
});
|
|
6163
|
+
return skillsCmd;
|
|
6164
|
+
}
|
|
6165
|
+
async function configureToolAction(toolName) {
|
|
6166
|
+
const config = await loadConfig();
|
|
6167
|
+
const { ToolConfigManager: ToolConfigManager2 } = await Promise.resolve().then(() => (init_tool_config(), tool_config_exports));
|
|
6168
|
+
const toolConfigManager = new ToolConfigManager2(config);
|
|
6169
|
+
const tool = await toolConfigManager.getToolConfig(toolName);
|
|
6170
|
+
if (!tool) {
|
|
6171
|
+
logger.error(`Tool not found: ${toolName}`);
|
|
6172
|
+
console.log(chalk5.gray("\nAvailable tools:"));
|
|
6173
|
+
const tools = await toolConfigManager.listTools();
|
|
6174
|
+
for (const t of tools) {
|
|
6175
|
+
console.log(` - ${chalk5.cyan(t.name)}`);
|
|
6443
6176
|
}
|
|
6177
|
+
console.log();
|
|
6178
|
+
console.log(chalk5.gray("Tip: If you meant to show a skill, use: aikit skills show <name>"));
|
|
6179
|
+
console.log();
|
|
6180
|
+
process.exit(1);
|
|
6444
6181
|
}
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6182
|
+
console.log(chalk5.bold(`
|
|
6183
|
+
\u{1F527} Configuring: ${tool.name}
|
|
6184
|
+
`));
|
|
6185
|
+
console.log(chalk5.gray(tool.description));
|
|
6186
|
+
console.log();
|
|
6187
|
+
if (tool.configMethod === "oauth") {
|
|
6188
|
+
if (toolName === "figma-analysis") {
|
|
6189
|
+
const { FigmaOAuth: FigmaOAuth2 } = await Promise.resolve().then(() => (init_figma_oauth(), figma_oauth_exports));
|
|
6190
|
+
const oauth = new FigmaOAuth2(toolConfigManager);
|
|
6191
|
+
try {
|
|
6192
|
+
const token = await oauth.authenticate();
|
|
6193
|
+
console.log(chalk5.gray("\nValidating token..."));
|
|
6194
|
+
const isValid = await oauth.validateToken(token);
|
|
6195
|
+
if (isValid) {
|
|
6196
|
+
logger.success(`
|
|
6197
|
+
\u2705 ${tool.name} configured successfully!`);
|
|
6198
|
+
console.log(chalk5.gray("\nYou can now use the /analyze-figma command in OpenCode.\n"));
|
|
6199
|
+
} else {
|
|
6200
|
+
await toolConfigManager.updateToolConfig(toolName, {
|
|
6201
|
+
status: "error",
|
|
6202
|
+
errorMessage: "Token validation failed"
|
|
6203
|
+
});
|
|
6204
|
+
logger.error("Token validation failed. Please try again.");
|
|
6205
|
+
process.exit(1);
|
|
6206
|
+
}
|
|
6207
|
+
} catch (error) {
|
|
6208
|
+
await toolConfigManager.updateToolConfig(toolName, {
|
|
6209
|
+
status: "error",
|
|
6210
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
6211
|
+
});
|
|
6212
|
+
logger.error(`Configuration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6213
|
+
process.exit(1);
|
|
6214
|
+
}
|
|
6215
|
+
} else {
|
|
6216
|
+
logger.error(`OAuth flow not implemented for tool: ${toolName}`);
|
|
6217
|
+
process.exit(1);
|
|
6218
|
+
}
|
|
6219
|
+
} else if (tool.configMethod === "manual") {
|
|
6220
|
+
logger.info("Manual configuration not yet implemented");
|
|
6221
|
+
process.exit(1);
|
|
6222
|
+
} else {
|
|
6223
|
+
logger.info(`Tool ${tool.name} does not require configuration`);
|
|
6451
6224
|
}
|
|
6452
|
-
logger.success(`
|
|
6453
|
-
Created ${count} OpenCode commands in .opencode/command/`);
|
|
6454
|
-
await configureMcpServer(projectPath);
|
|
6455
|
-
logger.info("\nUsage in OpenCode:");
|
|
6456
|
-
logger.info(" Press Ctrl+K to open command picker");
|
|
6457
|
-
logger.info(" Or type /skills to see all available skills");
|
|
6458
|
-
logger.info(` Available: ${skills.length} skills, ${commands.length} commands`);
|
|
6459
|
-
logger.info(" MCP server configured - tools available via MCP protocol");
|
|
6460
6225
|
}
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6226
|
+
|
|
6227
|
+
// src/cli/commands/misc.ts
|
|
6228
|
+
init_esm_shims();
|
|
6229
|
+
import chalk6 from "chalk";
|
|
6230
|
+
import { readFile as readFile13, writeFile as writeFile14 } from "fs/promises";
|
|
6231
|
+
import { join as join19 } from "path";
|
|
6232
|
+
init_memory();
|
|
6233
|
+
init_logger();
|
|
6234
|
+
function registerAgentsCommand(program2) {
|
|
6235
|
+
const agentsCmd = program2.command("agents").description("Manage agents");
|
|
6236
|
+
agentsCmd.command("list").description("List available agents").action(async () => {
|
|
6237
|
+
const config = await loadConfig();
|
|
6238
|
+
const manager = new AgentManager(config);
|
|
6239
|
+
const agents = manager.listAgents();
|
|
6240
|
+
console.log(chalk6.bold("\n\u{1F916} Available Agents:\n"));
|
|
6241
|
+
for (const agent of agents) {
|
|
6242
|
+
console.log(` ${chalk6.cyan(`@${agent.name}`)} - ${agent.description}`);
|
|
6243
|
+
console.log(chalk6.gray(` Use when: ${agent.useWhen}`));
|
|
6244
|
+
}
|
|
6245
|
+
console.log();
|
|
6246
|
+
});
|
|
6247
|
+
return agentsCmd;
|
|
6248
|
+
}
|
|
6249
|
+
function registerCommandsCommand(program2) {
|
|
6250
|
+
const commandsCmd = program2.command("commands").description("Manage commands");
|
|
6251
|
+
commandsCmd.command("list").description("List available commands").action(async () => {
|
|
6252
|
+
const config = await loadConfig();
|
|
6253
|
+
const runner = new CommandRunner(config);
|
|
6254
|
+
const commands = await runner.listCommands();
|
|
6255
|
+
console.log(chalk6.bold("\n\u26A1 Available Commands:\n"));
|
|
6256
|
+
const groups = groupBy(commands, (c) => c.category);
|
|
6257
|
+
for (const [category, cmds] of Object.entries(groups)) {
|
|
6258
|
+
console.log(chalk6.bold.yellow(`
|
|
6259
|
+
${category}:`));
|
|
6260
|
+
for (const cmd of cmds) {
|
|
6261
|
+
console.log(` ${chalk6.cyan(`/${cmd.name}`)} - ${cmd.description}`);
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
console.log();
|
|
6265
|
+
});
|
|
6266
|
+
return commandsCmd;
|
|
6267
|
+
}
|
|
6268
|
+
function registerModeCommand(program2) {
|
|
6269
|
+
const modeCmd = program2.command("mode").description("Manage AIKit mode");
|
|
6270
|
+
modeCmd.command("get").description("Get current AIKit mode").action(async () => {
|
|
6271
|
+
const config = await loadConfig();
|
|
6272
|
+
const { mode } = config;
|
|
6273
|
+
console.log(chalk6.bold("\n\u{1F4CB} Current Mode:\n"));
|
|
6274
|
+
console.log(` ${chalk6.cyan(mode || "build")}`);
|
|
6275
|
+
console.log();
|
|
6276
|
+
console.log(chalk6.bold("Available Modes:\n"));
|
|
6277
|
+
console.log(` ${chalk6.cyan("plan")} - Create detailed implementation plans`);
|
|
6278
|
+
console.log(` ${chalk6.cyan("build")} - Direct execution mode`);
|
|
6279
|
+
console.log(` ${chalk6.cyan("one-shot")} - End-to-end autonomous execution`);
|
|
6280
|
+
console.log();
|
|
6281
|
+
console.log(chalk6.gray('Use "aikit mode set <mode>" to change mode.'));
|
|
6282
|
+
});
|
|
6283
|
+
modeCmd.command("set <mode>").description("Set AIKit mode (plan, build, one-shot)").action(async (mode) => {
|
|
6284
|
+
const config = await loadConfig();
|
|
6285
|
+
const configPath = config.configPath;
|
|
6286
|
+
try {
|
|
6287
|
+
const validModes = ["plan", "build", "one-shot"];
|
|
6288
|
+
if (!validModes.includes(mode)) {
|
|
6289
|
+
console.log(chalk6.red(`Invalid mode. Available modes: ${validModes.join(", ")}`));
|
|
6290
|
+
return;
|
|
6291
|
+
}
|
|
6292
|
+
const configData = JSON.parse(await readFile13(join19(configPath, "aikit.json"), "utf-8"));
|
|
6293
|
+
configData.mode = mode;
|
|
6294
|
+
await writeFile14(join19(configPath, "aikit.json"), JSON.stringify(configData, null, 2));
|
|
6295
|
+
console.log(chalk6.green(`\u2713 Mode set to: ${mode}`));
|
|
6296
|
+
console.log(chalk6.gray(`Configuration updated at: ${configPath}/aikit.json`));
|
|
6297
|
+
} catch (error) {
|
|
6298
|
+
console.log(chalk6.red(`Failed to set mode: ${error instanceof Error ? error.message : String(error)}`));
|
|
6299
|
+
}
|
|
6300
|
+
});
|
|
6301
|
+
return modeCmd;
|
|
6302
|
+
}
|
|
6303
|
+
function registerToolsCommand(program2) {
|
|
6304
|
+
const toolsCmd = program2.command("tools").description("Manage custom tools");
|
|
6305
|
+
toolsCmd.command("list").description("List available tools").action(async () => {
|
|
6306
|
+
const config = await loadConfig();
|
|
6307
|
+
const registry = new ToolRegistry(config);
|
|
6308
|
+
const tools = await registry.listTools();
|
|
6309
|
+
console.log(chalk6.bold("\n\u{1F527} Available Tools:\n"));
|
|
6310
|
+
for (const tool of tools) {
|
|
6311
|
+
console.log(` ${chalk6.cyan(tool.name)} - ${tool.description}`);
|
|
6312
|
+
}
|
|
6313
|
+
console.log();
|
|
6314
|
+
});
|
|
6315
|
+
return toolsCmd;
|
|
6316
|
+
}
|
|
6317
|
+
function registerPluginsCommand(program2) {
|
|
6318
|
+
const pluginsCmd = program2.command("plugins").description("Manage plugins");
|
|
6319
|
+
pluginsCmd.command("list").description("List available plugins").action(async () => {
|
|
6320
|
+
const config = await loadConfig();
|
|
6321
|
+
const system = new PluginSystem(config);
|
|
6322
|
+
const plugins = await system.listPlugins();
|
|
6323
|
+
console.log(chalk6.bold("\n\u{1F50C} Available Plugins:\n"));
|
|
6324
|
+
for (const plugin of plugins) {
|
|
6325
|
+
const status = plugin.enabled ? chalk6.green("\u2713") : chalk6.gray("\u25CB");
|
|
6326
|
+
console.log(` ${status} ${chalk6.cyan(plugin.name)} - ${plugin.description}`);
|
|
6327
|
+
}
|
|
6328
|
+
console.log();
|
|
6329
|
+
});
|
|
6330
|
+
return pluginsCmd;
|
|
6331
|
+
}
|
|
6332
|
+
function registerMemoryCommand(program2) {
|
|
6333
|
+
const memoryCmd = program2.command("memory").description("Manage persistent memory");
|
|
6334
|
+
memoryCmd.command("list").description("List memory entries").action(async () => {
|
|
6335
|
+
const config = await loadConfig();
|
|
6336
|
+
const memory = new MemoryManager(config);
|
|
6337
|
+
const entries = await memory.list();
|
|
6338
|
+
console.log(chalk6.bold("\n\u{1F9E0} Memory Entries:\n"));
|
|
6339
|
+
for (const entry of entries) {
|
|
6340
|
+
console.log(` ${chalk6.cyan(entry.key)} - ${entry.summary}`);
|
|
6341
|
+
console.log(chalk6.gray(` Updated: ${entry.updatedAt}`));
|
|
6342
|
+
}
|
|
6343
|
+
console.log();
|
|
6344
|
+
});
|
|
6345
|
+
memoryCmd.command("read <key>").description("Read a memory entry").action(async (key) => {
|
|
6346
|
+
const config = await loadConfig();
|
|
6347
|
+
const memory = new MemoryManager(config);
|
|
6348
|
+
const content = await memory.read(key);
|
|
6349
|
+
if (!content) {
|
|
6350
|
+
logger.error(`Memory entry not found: ${key}`);
|
|
6351
|
+
process.exit(1);
|
|
6352
|
+
}
|
|
6353
|
+
console.log(content);
|
|
6354
|
+
});
|
|
6355
|
+
return memoryCmd;
|
|
6468
6356
|
}
|
|
6357
|
+
function registerBeadsCommand(program2) {
|
|
6358
|
+
const beadsCmd = program2.command("beads").description("Beads task management integration");
|
|
6359
|
+
beadsCmd.command("status").description("Show current Beads status").action(async () => {
|
|
6360
|
+
const beads = new BeadsIntegration();
|
|
6361
|
+
const status = await beads.getStatus();
|
|
6362
|
+
console.log(chalk6.bold("\n\u{1F4FF} Beads Status:\n"));
|
|
6363
|
+
console.log(` Active tasks: ${status.activeTasks}`);
|
|
6364
|
+
console.log(` Completed: ${status.completedTasks}`);
|
|
6365
|
+
console.log(` Current: ${status.currentTask || "None"}`);
|
|
6366
|
+
console.log();
|
|
6367
|
+
});
|
|
6368
|
+
return beadsCmd;
|
|
6369
|
+
}
|
|
6370
|
+
function registerStatusCommand(program2) {
|
|
6371
|
+
program2.command("status").description("Show AIKit status").action(async () => {
|
|
6372
|
+
console.log(chalk6.bold(`
|
|
6373
|
+
\u{1F680} AIKit v${getVersion()}
|
|
6374
|
+
`));
|
|
6375
|
+
try {
|
|
6376
|
+
const config = await loadConfig();
|
|
6377
|
+
console.log(chalk6.green("\u2713 Configuration loaded"));
|
|
6378
|
+
const skillEngine = new SkillEngine(config);
|
|
6379
|
+
const skills = await skillEngine.listSkills();
|
|
6380
|
+
console.log(` Skills: ${skills.length}`);
|
|
6381
|
+
const agentManager = new AgentManager(config);
|
|
6382
|
+
const agents = agentManager.listAgents();
|
|
6383
|
+
console.log(` Agents: ${agents.length}`);
|
|
6384
|
+
const commandRunner = new CommandRunner(config);
|
|
6385
|
+
const commands = await commandRunner.listCommands();
|
|
6386
|
+
console.log(` Commands: ${commands.length}`);
|
|
6387
|
+
const toolRegistry = new ToolRegistry(config);
|
|
6388
|
+
const tools = await toolRegistry.listTools();
|
|
6389
|
+
console.log(` Tools: ${tools.length}`);
|
|
6390
|
+
const beads = new BeadsIntegration();
|
|
6391
|
+
const beadsStatus = await beads.isInstalled();
|
|
6392
|
+
console.log(` Beads: ${beadsStatus ? chalk6.green("Installed") : chalk6.yellow("Not installed")}`);
|
|
6393
|
+
} catch (error) {
|
|
6394
|
+
console.log(chalk6.yellow('\u26A0 AIKit not initialized. Run "aikit init" to get started.'));
|
|
6395
|
+
}
|
|
6396
|
+
console.log();
|
|
6397
|
+
});
|
|
6398
|
+
}
|
|
6399
|
+
|
|
6400
|
+
// src/cli/index.ts
|
|
6401
|
+
var program = new Command();
|
|
6402
|
+
program.name("aikit").description("Open-source AI coding agent toolkit for OpenCode").version(getVersion());
|
|
6403
|
+
registerInitCommand(program);
|
|
6404
|
+
registerInstallCommand(program);
|
|
6405
|
+
registerSyncCommand(program);
|
|
6406
|
+
registerSkillsCommand(program);
|
|
6407
|
+
registerAgentsCommand(program);
|
|
6408
|
+
registerCommandsCommand(program);
|
|
6409
|
+
registerModeCommand(program);
|
|
6410
|
+
registerToolsCommand(program);
|
|
6411
|
+
registerPluginsCommand(program);
|
|
6412
|
+
registerMemoryCommand(program);
|
|
6413
|
+
registerBeadsCommand(program);
|
|
6414
|
+
registerStatusCommand(program);
|
|
6469
6415
|
program.parse();
|
|
6470
6416
|
//# sourceMappingURL=cli.js.map
|