@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 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: dirname4 } = await import("path");
807
- await mkdir4(dirname4(filePath), { recursive: true });
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 readFile11, writeFile as writeFile12, mkdir as mkdir10, access as access4, constants as constants4 } from "fs/promises";
938
- import { join as join17 } from "path";
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 = join17(this.config.configPath, "config", "tools.json");
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 access4(this.toolsConfigPath, constants4.R_OK);
1050
- const content = await readFile11(this.toolsConfigPath, "utf-8");
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 = join17(this.config.configPath, "config");
1061
- await mkdir10(configDir, { recursive: true });
1062
- await writeFile12(this.toolsConfigPath, JSON.stringify(configs, null, 2));
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
- init_memory();
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/core/sync-engine.ts
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
- init_paths();
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
- import { readFile as readFile7, readdir as readdir7, writeFile as writeFile8, stat } from "fs/promises";
4377
- import { join as join13 } from "path";
4378
- import { createHash } from "crypto";
4379
- var VersionManager = class {
4380
- config;
4381
- constructor(config) {
4382
- this.config = config;
4383
- }
4384
- /**
4385
- * Get current installed version
4386
- */
4387
- async getCurrentVersion() {
4388
- const versionPath = join13(paths.globalConfig(), ".version.json");
4389
- try {
4390
- const content = await readFile7(versionPath, "utf-8");
4391
- return JSON.parse(content);
4392
- } catch {
4393
- return null;
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
- * Detect changes between versions
4448
- */
4449
- async detectChanges() {
4450
- const globalSkillsPath = paths.skills(paths.globalConfig());
4451
- const projectSkillsPath = paths.skills(this.config.configPath);
4452
- const sourceSkills = await this.loadSkillHashes(globalSkillsPath);
4453
- const userSkills = await this.loadSkillHashes(projectSkillsPath);
4454
- const newSkills = [];
4455
- const modifiedSkills = [];
4456
- const removedSkills = [];
4457
- const conflicts = [];
4458
- const installedSkills = /* @__PURE__ */ new Map();
4459
- const installedPath = join13(paths.globalConfig(), ".installed-skills.json");
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 installedData = await readFile7(installedPath, "utf-8");
4462
- const installedList = JSON.parse(installedData);
4463
- installedList.forEach((skill) => {
4464
- installedSkills.set(skill.name, skill);
4465
- });
4466
- } catch {
4467
- }
4468
- for (const sourceSkill of sourceSkills) {
4469
- const installed = installedSkills.get(sourceSkill.name);
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
- for (const [name, installedSkill] of installedSkills.entries()) {
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
- * Load skill hashes from directory
4504
- */
4505
- async loadSkillHashes(skillsPath) {
4506
- const hashes = [];
4507
- try {
4508
- const loadFromDir = async (dir) => {
4509
- const files = await readdir7(dir);
4510
- for (const file of files) {
4511
- const filePath = join13(dir, file);
4512
- const stats = await stat(filePath);
4513
- if (stats.isDirectory()) {
4514
- await loadFromDir(filePath);
4515
- } else if (file.endsWith(".md")) {
4516
- const hash = await this.calculateSkillHash(filePath);
4517
- hashes.push({
4518
- path: filePath,
4519
- name: file.replace(".md", ""),
4520
- hash,
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
- * Calculate hash for a skill file
4534
- */
4535
- async calculateSkillHash(filePath) {
4536
- try {
4537
- const content = await readFile7(filePath, "utf-8");
4538
- return createHash("sha256").update(content).digest("hex");
4539
- } catch {
4540
- return "";
4541
- }
4542
- }
4543
- /**
4544
- * Extract category from path
4545
- */
4546
- extractCategory(filePath, basePath) {
4547
- const relative = filePath.replace(basePath + "/", "");
4548
- const parts = relative.split("/");
4549
- if (parts.length > 1) {
4550
- return parts[0];
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
- return "uncategorized";
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
- * Save installed skills info
4556
- */
4557
- async saveInstalledSkills(skills) {
4558
- const installedPath = join13(paths.globalConfig(), ".installed-skills.json");
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 writeFile8(installedPath, JSON.stringify(skills, null, 2));
4561
- } catch (error) {
4562
- logger.error("Failed to save installed skills info:", error);
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
- * Update version file
4567
- */
4568
- async updateVersion(version, migration) {
4569
- const current = await this.getCurrentVersion() || {
4570
- installedVersion: "0.0.0",
4571
- lastSynced: (/* @__PURE__ */ new Date()).toISOString(),
4572
- packageVersion: "0.0.0",
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
- // src/core/backup-manager.ts
4595
- init_esm_shims();
4596
- init_logger();
4597
- import { readFile as readFile8, writeFile as writeFile9, readdir as readdir8, stat as stat2, unlink, mkdir as mkdir8 } from "fs/promises";
4598
- import { join as join14, dirname as dirname2 } from "path";
4599
- import { createHash as createHash2 } from "crypto";
4600
- var BackupManager = class {
4601
- configPath;
4602
- backupsDir;
4603
- maxBackups;
4604
- constructor(configPath, maxBackups = 5) {
4605
- this.configPath = configPath;
4606
- this.backupsDir = join14(configPath, ".backups");
4607
- this.maxBackups = maxBackups;
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
- * Create backup before update
4611
- */
4612
- async createBackup(fromVersion, toVersion) {
4613
- try {
4614
- await mkdir8(this.backupsDir, { recursive: true });
4615
- const backupId = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
4616
- const backupPath = join14(this.backupsDir, `${backupId}-v${toVersion}`);
4617
- await mkdir8(backupPath, { recursive: true });
4618
- logger.info(`Creating backup: ${backupPath}`);
4619
- const files = [];
4620
- const backupItems = [
4621
- "skills/",
4622
- "aikit.json",
4623
- "AGENTS.md",
4624
- "config/"
4625
- ];
4626
- for (const item of backupItems) {
4627
- const files2 = await this.backupItem(this.configPath, item, backupPath);
4628
- files2.push(...files2);
4629
- }
4630
- const manifest = {
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
- * Backup a file or directory
4650
- */
4651
- async backupItem(sourceDir, item, targetDir) {
4652
- const sourcePath = join14(sourceDir, item);
4653
- const targetPath = join14(targetDir, item);
4654
- const files = [];
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
- const stats = await stat2(sourcePath);
4657
- if (stats.isDirectory()) {
4658
- await mkdir8(targetPath, { recursive: true });
4659
- const entries = await readdir8(sourcePath);
4660
- for (const entry of entries) {
4661
- const entryFiles = await this.backupItem(sourcePath, entry, targetPath);
4662
- files.push(...entryFiles);
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
- } else if (stats.isFile()) {
4665
- await mkdir8(dirname2(targetPath), { recursive: true });
4666
- await this.copyFile(sourcePath, targetPath);
4667
- const hash = await this.calculateHash(targetPath);
4668
- files.push({
4669
- path: item,
4670
- hash,
4671
- size: stats.size
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.debug(`Could not backup ${item}:`, error);
4963
+ logger.error("Failed to initialize AIKit:", error);
4964
+ process.exit(1);
4676
4965
  }
4677
- return files;
4678
- }
4679
- /**
4680
- * Copy file with hash calculation
4681
- */
4682
- async copyFile(source, target) {
4683
- const content = await readFile8(source);
4684
- await writeFile9(target, content);
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
- * Calculate file hash
5011
+ * Get current installed version
4688
5012
  */
4689
- async calculateHash(filePath) {
5013
+ async getCurrentVersion() {
5014
+ const versionPath = join14(paths.globalConfig(), ".version.json");
4690
5015
  try {
4691
- const content = await readFile8(filePath);
4692
- return createHash2("sha256").update(content).digest("hex");
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
- * List available backups
5023
+ * Get package version from package.json
4699
5024
  */
4700
- async listBackups() {
5025
+ getPackageVersion() {
4701
5026
  try {
4702
- const entries = await readdir8(this.backupsDir);
4703
- const backups = [];
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
- * Calculate backup directory size
5034
+ * Check for updates
4729
5035
  */
4730
- async calculateBackupSize(backupPath) {
4731
- let totalSize = 0;
4732
- try {
4733
- const calculate = async (dir) => {
4734
- const entries = await readdir8(dir);
4735
- for (const entry of entries) {
4736
- const entryPath = join14(dir, entry);
4737
- const stats = await stat2(entryPath);
4738
- if (stats.isDirectory()) {
4739
- await calculate(entryPath);
4740
- } else {
4741
- totalSize += stats.size;
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
- return totalSize;
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
- * Restore from backup
5073
+ * Detect changes between versions
4752
5074
  */
4753
- async restoreBackup(backupId) {
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 backups = await this.listBackups();
4756
- const backup = backups.find((b) => b.manifest.backupId === backupId);
4757
- if (!backup) {
4758
- logger.error(`Backup not found: ${backupId}`);
4759
- return false;
4760
- }
4761
- logger.info(`Restoring from backup: ${backupId}`);
4762
- const isValid = await this.validateBackup(backup);
4763
- if (!isValid) {
4764
- logger.error("Backup validation failed");
4765
- return false;
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
- for (const file of backup.manifest.files) {
4768
- const sourcePath = join14(backup.path, file.path);
4769
- const targetPath = join14(this.configPath, file.path);
4770
- await mkdir8(dirname2(targetPath), { recursive: true });
4771
- await this.copyFile(sourcePath, targetPath);
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
- * Validate backup integrity
5129
+ * Load skill hashes from directory
4782
5130
  */
4783
- async validateBackup(backup) {
5131
+ async loadSkillHashes(skillsPath) {
5132
+ const hashes = [];
4784
5133
  try {
4785
- const manifestPath = join14(backup.path, "backup-manifest.json");
4786
- await readFile8(manifestPath, "utf-8");
4787
- for (const file of backup.manifest.files) {
4788
- const filePath = join14(backup.path, file.path);
4789
- await stat2(filePath);
4790
- const currentHash = await this.calculateHash(filePath);
4791
- if (currentHash !== file.hash) {
4792
- logger.warn(`File hash mismatch: ${file.path}`);
4793
- return false;
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
- return true;
5151
+ };
5152
+ await loadFromDir(skillsPath);
4797
5153
  } catch (error) {
4798
- logger.debug("Backup validation failed:", error);
4799
- return false;
5154
+ logger.debug(`Could not load skills from ${skillsPath}:`, error);
4800
5155
  }
5156
+ return hashes;
4801
5157
  }
4802
5158
  /**
4803
- * Delete backup
5159
+ * Calculate hash for a skill file
4804
5160
  */
4805
- async deleteBackup(backupId) {
5161
+ async calculateSkillHash(filePath) {
4806
5162
  try {
4807
- const backups = await this.listBackups();
4808
- const backup = backups.find((b) => b.manifest.backupId === backupId);
4809
- if (!backup) {
4810
- return false;
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
- * Remove directory recursively
5170
+ * Extract category from path
4831
5171
  */
4832
- async removeDirectory(dirPath) {
4833
- const entries = await readdir8(dirPath);
4834
- for (const entry of entries) {
4835
- const entryPath = join14(dirPath, entry);
4836
- const stats = await stat2(entryPath);
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
- await unlink(dirPath);
5178
+ return "uncategorized";
4844
5179
  }
4845
5180
  /**
4846
- * Cleanup old backups (keep only maxBackups)
5181
+ * Save installed skills info
4847
5182
  */
4848
- async cleanupOldBackups() {
5183
+ async saveInstalledSkills(skills) {
5184
+ const installedPath = join14(paths.globalConfig(), ".installed-skills.json");
4849
5185
  try {
4850
- const backups = await this.listBackups();
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 cleanup old backups:", error);
5188
+ logger.error("Failed to save installed skills info:", error);
4861
5189
  }
4862
5190
  }
4863
5191
  /**
4864
- * Format backup size for display
5192
+ * Update version file
4865
5193
  */
4866
- formatSize(bytes) {
4867
- const units = ["B", "KB", "MB", "GB"];
4868
- let size = bytes;
4869
- let unitIndex = 0;
4870
- while (size >= 1024 && unitIndex < units.length - 1) {
4871
- size /= 1024;
4872
- unitIndex++;
4873
- }
4874
- return `${size.toFixed(2)} ${units[unitIndex]}`;
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/migration-manager.ts
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 readdir9 } from "fs/promises";
4882
- import { join as join15 } from "path";
4883
- var MigrationManager = class {
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
- migrationsDir;
4886
- constructor(configPath) {
5228
+ backupsDir;
5229
+ maxBackups;
5230
+ constructor(configPath, maxBackups = 5) {
4887
5231
  this.configPath = configPath;
4888
- this.migrationsDir = join15(process.cwd(), "src/core/migrations");
5232
+ this.backupsDir = join15(configPath, ".backups");
5233
+ this.maxBackups = maxBackups;
4889
5234
  }
4890
5235
  /**
4891
- * Load all available migrations
5236
+ * Create backup before update
4892
5237
  */
4893
- async loadMigrations() {
4894
- const migrations = [];
5238
+ async createBackup(fromVersion, toVersion) {
4895
5239
  try {
4896
- const files = await readdir9(this.migrationsDir);
4897
- for (const file of files) {
4898
- if (file.endsWith(".js") && file.startsWith("migrate-")) {
4899
- try {
4900
- const module = await import(join15(this.migrationsDir, file));
4901
- const migration = module.default || module.migration;
4902
- if (migration) {
4903
- migrations.push(migration);
4904
- }
4905
- } catch (error) {
4906
- logger.warn(`Failed to load migration ${file}:`, error);
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.debug("Could not load migrations:", error);
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
- * Get applied migrations
5275
+ * Backup a file or directory
4917
5276
  */
4918
- async getAppliedMigrations() {
4919
- const migrationHistoryPath = join15(this.configPath, ".migration-history.json");
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 content = await readFile9(migrationHistoryPath, "utf-8");
4922
- const history = JSON.parse(content);
4923
- return history.filter((m) => m.status === "completed").map((m) => m.to);
4924
- } catch {
4925
- return [];
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
- * Run pending migrations
5306
+ * Copy file with hash calculation
4930
5307
  */
4931
- async runPendingMigrations() {
4932
- const appliedMigrations = await this.getAppliedMigrations();
4933
- const allMigrations = await this.loadMigrations();
4934
- const pendingMigrations = allMigrations.filter(
4935
- (m) => !appliedMigrations.includes(m.version)
4936
- );
4937
- if (pendingMigrations.length === 0) {
4938
- logger.info("No pending migrations");
4939
- return { success: true, applied: [], failed: [] };
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
- logger.info(`Running ${pendingMigrations.length} pending migration(s)...`);
4942
- const applied = [];
4943
- const failed = [];
4944
- const migrationHistory = [];
4945
- for (const migration of pendingMigrations) {
4946
- try {
4947
- logger.info(`Running migration: ${migration.version}`);
4948
- logger.info(` ${migration.description}`);
4949
- await migration.up();
4950
- applied.push(migration.version);
4951
- migrationHistory.push({
4952
- from: "previous",
4953
- to: migration.version,
4954
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4955
- status: "completed"
4956
- });
4957
- logger.success(`\u2713 Migration completed: ${migration.version}`);
4958
- } catch (error) {
4959
- logger.error(`\u2717 Migration failed: ${migration.version}`, error);
4960
- failed.push(migration.version);
4961
- migrationHistory.push({
4962
- from: "previous",
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
- * Rollback migration
5354
+ * Calculate backup directory size
4979
5355
  */
4980
- async rollbackMigration(version) {
5356
+ async calculateBackupSize(backupPath) {
5357
+ let totalSize = 0;
4981
5358
  try {
4982
- const applied = await this.getAppliedMigrations();
4983
- if (!applied.includes(version)) {
4984
- logger.error(`Migration not found in applied list: ${version}`);
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
- const allMigrations = await this.loadMigrations();
4988
- const migration = allMigrations.find((m) => m.version === version);
4989
- if (!migration) {
4990
- logger.error(`Migration file not found: ${version}`);
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
- logger.info(`Rolling back migration: ${version}`);
4994
- await migration.down();
4995
- await this.updateMigrationHistoryStatus(version, "rolled-back");
4996
- logger.success(`\u2713 Migration rolled back: ${version}`);
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 rollback migration:", error);
5402
+ logger.error("Failed to restore backup:", error);
5000
5403
  return false;
5001
5404
  }
5002
5405
  }
5003
5406
  /**
5004
- * Update migration history
5407
+ * Validate backup integrity
5005
5408
  */
5006
- async updateMigrationHistory(entries) {
5007
- const historyPath = join15(this.configPath, ".migration-history.json");
5409
+ async validateBackup(backup) {
5008
5410
  try {
5009
- let history = [];
5010
- try {
5011
- const content = await readFile9(historyPath, "utf-8");
5012
- history = JSON.parse(content);
5013
- } catch {
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
- history.push(...entries);
5016
- await writeFile10(historyPath, JSON.stringify(history, null, 2));
5422
+ return true;
5017
5423
  } catch (error) {
5018
- logger.error("Failed to update migration history:", error);
5424
+ logger.debug("Backup validation failed:", error);
5425
+ return false;
5019
5426
  }
5020
5427
  }
5021
5428
  /**
5022
- * Update migration history status
5429
+ * Delete backup
5023
5430
  */
5024
- async updateMigrationHistoryStatus(version, status) {
5025
- const historyPath = join15(this.configPath, ".migration-history.json");
5431
+ async deleteBackup(backupId) {
5026
5432
  try {
5027
- const content = await readFile9(historyPath, "utf-8");
5028
- const history = JSON.parse(content);
5029
- const updated = history.map(
5030
- (m) => m.to === version ? { ...m, status } : m
5031
- );
5032
- await writeFile10(historyPath, JSON.stringify(updated, null, 2));
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 update migration history status:", error);
5451
+ logger.error("Failed to delete backup:", error);
5452
+ return false;
5035
5453
  }
5036
5454
  }
5037
5455
  /**
5038
- * Get migration history
5456
+ * Remove directory recursively
5039
5457
  */
5040
- async getMigrationHistory() {
5041
- const historyPath = join15(this.configPath, ".migration-history.json");
5042
- try {
5043
- const content = await readFile9(historyPath, "utf-8");
5044
- return JSON.parse(content);
5045
- } catch {
5046
- return [];
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
- * Check if migration is needed for version
5472
+ * Cleanup old backups (keep only maxBackups)
5051
5473
  */
5052
- async needsMigration(_fromVersion) {
5053
- const applied = await this.getAppliedMigrations();
5054
- const allMigrations = await this.loadMigrations();
5055
- const pendingMigrations = allMigrations.filter(
5056
- (m) => !applied.includes(m.version)
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(chalk2.green("\u2713 Your AIKit is up to date"));
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(chalk2.bold("\n\u{1F50D} Previewing update...\n"));
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(chalk2.green("\u2713 No updates available"));
5727
+ console.log(chalk3.green("\u2713 No updates available"));
5102
5728
  return false;
5103
5729
  }
5104
5730
  await this.displayChanges(changes);
5105
- console.log(chalk2.yellow("\n\u26A0\uFE0F This is a preview - no changes will be made."));
5106
- console.log(chalk2.gray("Use `aikit sync apply` to apply these changes."));
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(chalk2.green("\u2713 Already up to date"));
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 inquirer.prompt([{
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(chalk2.yellow("Update cancelled"));
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(chalk2.bold("\n\u{1F4E6} Creating backup..."));
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(chalk2.bold("\n\u{1F504} Running migrations..."));
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(chalk2.bold("\n\u{1F4DD} Updating skills..."));
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(chalk2.green("\n\u2705 Update complete!"));
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(chalk2.red("\n\u274C Update failed"));
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(chalk2.bold("\n\u{1F504} Rollback...\n"));
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(chalk2.yellow("No backups available"));
5835
+ console.log(chalk3.yellow("No backups available"));
5210
5836
  return false;
5211
5837
  }
5212
- const { selectedBackup } = await inquirer.prompt([{
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(chalk2.yellow("No backup ID provided"));
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(chalk2.green("\u2713 Rollback complete"));
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(chalk2.bold("\n\u{1F4E2} New version available!\n"));
5243
- console.log(` ${chalk2.cyan("Current:")} ${changes.fromVersion}`);
5244
- console.log(` ${chalk2.cyan("Latest:")} ${changes.toVersion}
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(chalk2.bold("\u{1F4CA} Changes detected:\n"));
5877
+ console.log(chalk3.bold("\u{1F4CA} Changes detected:\n"));
5252
5878
  if (changes.newSkills.length > 0) {
5253
- console.log(chalk2.green(" New Skills:"));
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(chalk2.yellow(" Updated Skills:"));
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(chalk2.red(" Removed Skills:"));
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(chalk2.bold.red(" \u26A0\uFE0F Conflicts:"));
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(chalk2.bold.red(`
5907
+ console.log(chalk3.bold.red(`
5282
5908
  \u26A0\uFE0F Conflict detected: ${conflict.skillName}
5283
5909
  `));
5284
- console.log(chalk2.yellow("Your version differs from official version."));
5285
- const { action } = await inquirer.prompt([{
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(chalk2.yellow(" Your version will be preserved as -custom.md"));
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(chalk2.green(` + ${skill.name}`));
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(chalk2.yellow(` ~ ${skill.name}`));
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(chalk2.red(` - ${skill.name} (archived)`));
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 = join16(sourceDir, skill.category, `${skill.name}.md`);
5353
- const targetPath = join16(targetDir, skill.category, `${skill.name}.md`);
5354
- await mkdir9(dirname3(targetPath), { recursive: true });
5355
- await copyFile(sourcePath, targetPath);
5356
- }
5357
- /**
5358
- * Archive a removed skill
5359
- */
5360
- async archiveSkill(targetDir, skill) {
5361
- const sourcePath = join16(targetDir, skill.category, `${skill.name}.md`);
5362
- const targetPath = join16(targetDir, skill.category, `${skill.name}-deprecated.md`);
5363
- try {
5364
- const content = await readFile10(sourcePath, "utf-8");
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
- **Step 5: Report results to user**
5994
+ Deprecation date: ${(/* @__PURE__ */ new Date()).toISOString()}
5995
+ Reason: Check release notes for replacement
5996
+ ---
6419
5997
 
6420
- ## Important Reminders
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
- - The URL is ALREADY in the command input - extract it from full message!
6423
- - URL may be split across multiple lines - combine them into one complete URL
6424
- - Do NOT ask for URL again - it's always in the input
6425
- - Do NOT wait - start immediately
6426
- - Use MCP tool \`read_figma_design\` which uses Figma API directly`;
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
- opencodeCommands[commandName] = `# Command: /${cmd.name}
6429
-
6430
- ## Description
6431
- ${cmd.description}
6432
-
6433
- ## Usage
6434
- \`${cmd.usage}\`
6435
-
6436
- ## Examples
6437
- ${examples}
6438
-
6439
- ## Workflow
6440
- ${cmd.content}
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
- **Category**: ${cmd.category}`;
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
- let count = 0;
6446
- for (const [name, content] of Object.entries(opencodeCommands)) {
6447
- const filePath = join18(opencodeCommandDir, `${name}.md`);
6448
- await writeFile13(filePath, content.trim());
6449
- logger.info(` \u2713 Created /${name} command`);
6450
- count++;
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
- function groupBy(array, keyFn) {
6462
- return array.reduce((acc, item) => {
6463
- const key = keyFn(item);
6464
- if (!acc[key]) acc[key] = [];
6465
- acc[key].push(item);
6466
- return acc;
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