cc-codeconductor 0.2.6 → 0.2.8

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.
Files changed (31) hide show
  1. package/README.md +18 -1
  2. package/dist/index.js +782 -24
  3. package/package.json +2 -1
  4. package/presets/opencode/README.md +7 -7
  5. package/presets/opencode/agents/architect.md +0 -1
  6. package/presets/opencode/agents/docs.md +0 -1
  7. package/presets/opencode/agents/implementer.md +0 -1
  8. package/presets/opencode/agents/orchestrator.md +0 -1
  9. package/presets/opencode/agents/repo-explorer.md +0 -1
  10. package/presets/opencode/agents/reviewer.md +0 -1
  11. package/presets/opencode/agents/task-coach.md +0 -1
  12. package/presets/opencode/agents/tester.md +0 -1
  13. package/presets/opencode/opencode.jsonc +2 -11
  14. package/presets/opencode/prompts/v0.2.0/docs.md +1 -1
  15. package/presets/opencode/prompts/v0.2.0/repo-explorer.md +1 -1
  16. package/presets/opencode/prompts/v0.2.0/reviewer.md +1 -1
  17. package/presets/opencode/prompts/v0.2.0/task-coach.md +1 -1
  18. package/presets/seo-hotel/commands/cc-seo-audit.md +87 -0
  19. package/presets/seo-hotel/skills/astro-seo/SKILL.md +245 -0
  20. package/presets/seo-hotel/skills/geo-readiness/SKILL.md +221 -0
  21. package/presets/seo-hotel/skills/off-page/SKILL.md +201 -0
  22. package/presets/seo-hotel/skills/schema-validator/SKILL.md +325 -0
  23. package/presets/seo-hotel/skills/seo-audit/SKILL.md +191 -0
  24. package/src/presets/manifests/agy.yml +9 -0
  25. package/src/presets/manifests/opencode.yml +2 -1
  26. package/src/presets/models/agy.yml +59 -0
  27. package/src/presets/models/claude.yml +3 -3
  28. package/src/presets/models/codex.yml +3 -3
  29. package/src/presets/models/cursor.yml +3 -3
  30. package/src/presets/models/gemini.yml +3 -3
  31. package/src/presets/models/opencode.yml +11 -46
package/dist/index.js CHANGED
@@ -7044,7 +7044,7 @@ function getExitCode(error) {
7044
7044
  // package.json
7045
7045
  var package_default = {
7046
7046
  name: "cc-codeconductor",
7047
- version: "0.2.6",
7047
+ version: "0.2.8",
7048
7048
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
7049
7049
  keywords: [
7050
7050
  "ai",
@@ -7073,6 +7073,7 @@ var package_default = {
7073
7073
  },
7074
7074
  type: "module",
7075
7075
  bin: {
7076
+ "cc-codeconductor": "./dist/index.js",
7076
7077
  codeconductor: "./dist/index.js"
7077
7078
  },
7078
7079
  scripts: {
@@ -7131,6 +7132,13 @@ async function detectProject(rootDir) {
7131
7132
  runtimes.push("python");
7132
7133
  frameworks.push("django");
7133
7134
  }
7135
+ const phpSignals = await detectPhp(rootDir);
7136
+ if (phpSignals.length > 0) {
7137
+ signals.push(...phpSignals);
7138
+ languages.push("php");
7139
+ runtimes.push("php");
7140
+ packageManagers.push("composer");
7141
+ }
7134
7142
  const astroSignals = await detectAstro(rootDir);
7135
7143
  if (astroSignals.length > 0) {
7136
7144
  signals.push(...astroSignals);
@@ -7217,6 +7225,22 @@ async function detectAstro(rootDir) {
7217
7225
  }
7218
7226
  return signals;
7219
7227
  }
7228
+ async function detectPhp(rootDir) {
7229
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7230
+ const signals = [];
7231
+ if (await fileExists2(rootDir, "composer.json")) {
7232
+ signals.push("composer.json");
7233
+ }
7234
+ try {
7235
+ const { readdir } = await import("node:fs/promises");
7236
+ const entries = await readdir(rootDir, { withFileTypes: true });
7237
+ const hasPhpFiles = entries.some((entry) => entry.isFile() && entry.name.endsWith(".php"));
7238
+ if (hasPhpFiles) {
7239
+ signals.push("*.php");
7240
+ }
7241
+ } catch {}
7242
+ return signals;
7243
+ }
7220
7244
 
7221
7245
  // src/commands/detect.command.ts
7222
7246
  async function detectCommand(options) {
@@ -11346,7 +11370,7 @@ var CodeConductorConfigSchema = exports_external.object({
11346
11370
  profile: exports_external.string().optional()
11347
11371
  }),
11348
11372
  defaults: exports_external.object({
11349
- target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor"]),
11373
+ target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor", "agy"]),
11350
11374
  overwrite: exports_external.boolean()
11351
11375
  }),
11352
11376
  presets: exports_external.object({
@@ -11366,6 +11390,7 @@ var RunnerTargetSchema = exports_external.enum([
11366
11390
  "codex",
11367
11391
  "gemini",
11368
11392
  "cursor",
11393
+ "agy",
11369
11394
  "all"
11370
11395
  ]);
11371
11396
  var InstallStrategySchema = exports_external.enum([
@@ -11383,20 +11408,23 @@ var ManifestEntrySchema = exports_external.object({
11383
11408
  template: exports_external.boolean().optional()
11384
11409
  });
11385
11410
  var InstallManifestSchema = exports_external.object({
11386
- target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor"]),
11411
+ target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor", "agy"]),
11387
11412
  entries: exports_external.array(ManifestEntrySchema)
11388
11413
  });
11389
11414
  var ToolProviderNamesSchema = exports_external.record(exports_external.string(), exports_external.string());
11415
+ var PermissionProviderNamesSchema = exports_external.record(exports_external.string(), exports_external.string());
11390
11416
  var ModelConfigSchema = exports_external.object({
11391
- target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor"]),
11417
+ target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor", "agy"]),
11392
11418
  agents: exports_external.record(exports_external.string(), exports_external.object({
11393
11419
  claude: exports_external.string().optional(),
11394
11420
  opencode: exports_external.string().optional(),
11395
11421
  codex: exports_external.string().optional(),
11396
11422
  gemini: exports_external.string().optional(),
11397
- cursor: exports_external.string().optional()
11423
+ cursor: exports_external.string().optional(),
11424
+ agy: exports_external.string().optional()
11398
11425
  })),
11399
- tools: exports_external.record(exports_external.string(), ToolProviderNamesSchema).optional()
11426
+ tools: exports_external.record(exports_external.string(), ToolProviderNamesSchema).optional(),
11427
+ permissions: PermissionProviderNamesSchema.optional()
11400
11428
  });
11401
11429
  function validateCouncilSpec(data) {
11402
11430
  return CouncilSpecSchema.parse(data);
@@ -11720,6 +11748,8 @@ function resolveAssets(target) {
11720
11748
  return ["agents", "prompts/v0.2.0"];
11721
11749
  case "cursor":
11722
11750
  return ["agents", "prompts/v0.2.0"];
11751
+ case "agy":
11752
+ return ["agents", "prompts/v0.2.0"];
11723
11753
  }
11724
11754
  }
11725
11755
 
@@ -11903,7 +11933,27 @@ function getResponsibilities(agentId) {
11903
11933
  devil: `- Challenge assumptions
11904
11934
  - Find edge cases
11905
11935
  - Identify failure modes
11906
- - Stress test solutions`
11936
+ - Stress test solutions`,
11937
+ "seo-auditor": `- Audit technical SEO
11938
+ - Check meta tags and headings
11939
+ - Validate crawl directives
11940
+ - Assess page speed signals`,
11941
+ "schema-validator": `- Validate Schema.org markup
11942
+ - Check JSON-LD syntax
11943
+ - Verify required properties
11944
+ - Suggest structured data fixes`,
11945
+ "geo-specialist": `- Assess AI-search readiness
11946
+ - Validate llms.txt
11947
+ - Check citable content
11948
+ - Review GEO optimization`,
11949
+ "content-strategist": `- Plan content marketing
11950
+ - Guide off-page SEO
11951
+ - Suggest backlink strategy
11952
+ - Review hotel copywriting`,
11953
+ "astro-specialist": `- Validate Astro SEO patterns
11954
+ - Check static generation
11955
+ - Review Islands Architecture
11956
+ - Optimize image handling`
11907
11957
  };
11908
11958
  return responsibilities[agentId] || "- Provide critical review";
11909
11959
  }
@@ -12081,7 +12131,7 @@ function createCodexInstaller(spec) {
12081
12131
  function generateOpenCodeFiles(spec) {
12082
12132
  const files = [];
12083
12133
  files.push({
12084
- path: ".opencode/commands/council.md",
12134
+ path: ".opencode/commands/cc-council.md",
12085
12135
  content: generateCouncilCommand(spec),
12086
12136
  overwrite: false
12087
12137
  });
@@ -12093,17 +12143,20 @@ function generateOpenCodeFiles(spec) {
12093
12143
  for (const agent of spec.agents) {
12094
12144
  files.push({
12095
12145
  path: `.opencode/agents/council-${agent.id}.md`,
12096
- content: generateAgentContent(agent),
12146
+ content: generateOpenCodeAgentContent(agent),
12097
12147
  overwrite: false
12098
12148
  });
12099
12149
  }
12100
12150
  return files;
12101
12151
  }
12102
12152
  function generateCouncilCommand(spec) {
12103
- return `# Council Command
12153
+ return `---
12154
+ description: ${yamlString(spec.description)}
12155
+ agent: council-lead
12156
+ subtask: true
12157
+ ---
12104
12158
 
12105
- ## Description
12106
- ${spec.description}
12159
+ Run the CodeConductor council for multi-perspective analysis.
12107
12160
 
12108
12161
  ## Version
12109
12162
  ${spec.version}
@@ -12112,12 +12165,25 @@ ${spec.version}
12112
12165
  ${spec.agents.map((a) => `- ${a.role} (${a.id})`).join(`
12113
12166
  `)}
12114
12167
 
12115
- ## Usage
12116
- Invoke this command to get multi-perspective analysis from the council.
12168
+ ## Instructions
12169
+ Coordinate with the council agents and synthesize their perspectives into the configured output contract.
12117
12170
  `;
12118
12171
  }
12119
12172
  function generateCouncilLead(spec) {
12120
- return `# Council Lead Agent
12173
+ return `---
12174
+ description: ${yamlString(`${spec.description} Council lead. Coordinates council members and synthesizes recommendations.`)}
12175
+ mode: subagent
12176
+ permission:
12177
+ read: allow
12178
+ edit: deny
12179
+ bash: deny
12180
+ glob: allow
12181
+ grep: allow
12182
+ webfetch: deny
12183
+ websearch: deny
12184
+ ---
12185
+
12186
+ # Council Lead Agent
12121
12187
 
12122
12188
  ## Role
12123
12189
  Coordinates the council and synthesizes perspectives
@@ -12136,6 +12202,37 @@ ${spec.agents.map((a) => `- ${a.role}: ${a.focus.join(", ")}`).join(`
12136
12202
  - Provide final recommendation
12137
12203
  `;
12138
12204
  }
12205
+ function generateOpenCodeAgentContent(agent) {
12206
+ return `---
12207
+ description: ${yamlString(`${agent.role} council agent. Focus: ${agent.focus.join(", ")}. Context: ${agent.context}. Model hint: ${agent.modelHint}.`)}
12208
+ mode: subagent
12209
+ permission:
12210
+ ${generatePermissionBlock(agent.context)}
12211
+ ---
12212
+
12213
+ ${generateAgentContent(agent)}`;
12214
+ }
12215
+ function generatePermissionBlock(context) {
12216
+ if (context === "repo-readonly") {
12217
+ return ` read: allow
12218
+ edit: deny
12219
+ bash: deny
12220
+ glob: allow
12221
+ grep: allow
12222
+ webfetch: deny
12223
+ websearch: deny`;
12224
+ }
12225
+ return ` read: deny
12226
+ edit: deny
12227
+ bash: deny
12228
+ glob: deny
12229
+ grep: deny
12230
+ webfetch: deny
12231
+ websearch: deny`;
12232
+ }
12233
+ function yamlString(value) {
12234
+ return JSON.stringify(value);
12235
+ }
12139
12236
 
12140
12237
  // src/adapters/opencode/opencode-installer.ts
12141
12238
  class OpenCodeInstaller {
@@ -12319,7 +12416,7 @@ function renderTemplate(content, modelConfig, filePath) {
12319
12416
  const agentModels = modelConfig.agents[agentRole];
12320
12417
  const targetModel = agentModels[modelConfig.target];
12321
12418
  let result2 = content.replace(/\{\{MODEL\}\}/g, targetModel ?? "").replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "");
12322
- if (modelConfig.tools) {
12419
+ if (modelConfig.tools || modelConfig.permissions) {
12323
12420
  result2 = substituteToolNames(result2, modelConfig);
12324
12421
  }
12325
12422
  return result2;
@@ -12348,19 +12445,27 @@ function renderTemplate(content, modelConfig, filePath) {
12348
12445
  }
12349
12446
  }
12350
12447
  }
12351
- if (modelConfig.tools) {
12448
+ if (modelConfig.tools || modelConfig.permissions) {
12352
12449
  result = substituteToolNames(result, modelConfig);
12353
12450
  }
12354
12451
  return result;
12355
12452
  }
12356
12453
  function substituteToolNames(content, modelConfig) {
12357
- if (!modelConfig.tools)
12454
+ if (!modelConfig.tools && !modelConfig.permissions)
12358
12455
  return content;
12359
12456
  const target = modelConfig.target;
12360
12457
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
12361
12458
  if (!fmMatch)
12362
12459
  return content;
12363
12460
  const frontmatter = fmMatch[1];
12461
+ if (target === "opencode" && modelConfig.permissions) {
12462
+ const updatedFrontmatter2 = frontmatter.replace(/^tools:\s*(.+)\n?/m, "");
12463
+ return content.replace(fmMatch[0], `---
12464
+ ${updatedFrontmatter2}
12465
+ ---`);
12466
+ }
12467
+ if (!modelConfig.tools)
12468
+ return content;
12364
12469
  const updatedFrontmatter = frontmatter.replace(/^tools:\s*(.+)$/m, (_match, toolsLine) => {
12365
12470
  const baseNames = toolsLine.split(",").map((t) => t.trim());
12366
12471
  const mappedNames = baseNames.map((baseName) => {
@@ -12389,7 +12494,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
12389
12494
  const incomingContent = isTemplate && modelConfig ? renderTemplate(content, modelConfig, srcPath) : content;
12390
12495
  let finalContent = incomingContent;
12391
12496
  let action = "written";
12392
- if (strategy === "append" && !force) {
12497
+ if (strategy === "append") {
12393
12498
  let existing = "";
12394
12499
  try {
12395
12500
  existing = await readFile4(destPath, "utf-8");
@@ -12402,7 +12507,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
12402
12507
  ` + incomingContent;
12403
12508
  }
12404
12509
  action = "appended";
12405
- } else if (strategy === "merge-json" && !force) {
12510
+ } else if (strategy === "merge-json") {
12406
12511
  let existing = {};
12407
12512
  try {
12408
12513
  existing = JSON.parse(await readFile4(destPath, "utf-8"));
@@ -12503,8 +12608,8 @@ async function loadCouncilPreset(projectRoot) {
12503
12608
  }
12504
12609
 
12505
12610
  // src/core/runner/runner-target.ts
12506
- var RUNNER_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor", "all"];
12507
- var INDIVIDUAL_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor"];
12611
+ var RUNNER_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor", "agy", "all"];
12612
+ var INDIVIDUAL_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor", "agy"];
12508
12613
  function isRunnerTarget(value) {
12509
12614
  return RUNNER_TARGETS.includes(value);
12510
12615
  }
@@ -12660,6 +12765,638 @@ async function installPresetCommand(options) {
12660
12765
  }
12661
12766
  }
12662
12767
 
12768
+ // src/commands/install-lsp.command.ts
12769
+ import { homedir as homedir4 } from "node:os";
12770
+ import { resolve as resolve8 } from "node:path";
12771
+
12772
+ // src/core/lsp/lsp-config-utils.ts
12773
+ function getLspCommand(lspId) {
12774
+ switch (lspId) {
12775
+ case "typescript":
12776
+ return { command: "typescript-language-server", args: ["--stdio"] };
12777
+ case "php":
12778
+ return { command: "intelephense", args: ["--stdio"] };
12779
+ case "python":
12780
+ return { command: "pylsp", args: [] };
12781
+ case "kotlin":
12782
+ return { command: "kotlin-language-server", args: [] };
12783
+ default:
12784
+ return;
12785
+ }
12786
+ }
12787
+
12788
+ // src/adapters/agy/agy-lsp-generator.ts
12789
+ class AgyLspGenerator {
12790
+ name = "agy-lsp";
12791
+ target = "agy";
12792
+ generate(installedLsps) {
12793
+ const successfulLsps = installedLsps.filter((lsp) => lsp.status !== "failed");
12794
+ if (successfulLsps.length === 0) {
12795
+ return [];
12796
+ }
12797
+ const sections = [
12798
+ "# Agy LSP Configuration (experimental)",
12799
+ "# NOTE: Agy config format may change as the tool evolves",
12800
+ ""
12801
+ ];
12802
+ for (const lsp of successfulLsps) {
12803
+ const config = getLspCommand(lsp.lspId);
12804
+ if (config) {
12805
+ sections.push(`${lsp.lspId}:`);
12806
+ sections.push(` command: ${config.command}`);
12807
+ if (config.args.length > 0) {
12808
+ sections.push(` args: [${config.args.join(", ")}]`);
12809
+ }
12810
+ sections.push("");
12811
+ }
12812
+ }
12813
+ return [
12814
+ {
12815
+ path: ".agy/tools.yaml",
12816
+ content: sections.join(`
12817
+ `),
12818
+ overwrite: false
12819
+ }
12820
+ ];
12821
+ }
12822
+ async isAvailable() {
12823
+ return true;
12824
+ }
12825
+ }
12826
+ function createAgyLspGenerator() {
12827
+ return new AgyLspGenerator;
12828
+ }
12829
+
12830
+ // src/adapters/claude/claude-lsp-generator.ts
12831
+ class ClaudeLspGenerator {
12832
+ name = "claude-lsp";
12833
+ target = "claude";
12834
+ generate(installedLsps) {
12835
+ const successfulLsps = installedLsps.filter((lsp) => lsp.status !== "failed");
12836
+ if (successfulLsps.length === 0) {
12837
+ return [];
12838
+ }
12839
+ const mcpServers = {};
12840
+ for (const lsp of successfulLsps) {
12841
+ const config = getLspCommand(lsp.lspId);
12842
+ if (config) {
12843
+ mcpServers[lsp.lspId] = { command: config.command, args: [...config.args] };
12844
+ }
12845
+ }
12846
+ const content = JSON.stringify({
12847
+ mcpServers
12848
+ }, null, 2);
12849
+ return [
12850
+ {
12851
+ path: ".claude/settings.json",
12852
+ content,
12853
+ overwrite: false
12854
+ }
12855
+ ];
12856
+ }
12857
+ async isAvailable() {
12858
+ return true;
12859
+ }
12860
+ }
12861
+ function createClaudeLspGenerator() {
12862
+ return new ClaudeLspGenerator;
12863
+ }
12864
+
12865
+ // src/adapters/codex/codex-lsp-generator.ts
12866
+ var MCP_STARTUP_TIMEOUT_SEC = 120;
12867
+
12868
+ class CodexLspGenerator {
12869
+ name = "codex-lsp";
12870
+ target = "codex";
12871
+ generate(installedLsps) {
12872
+ const successfulLsps = installedLsps.filter((lsp) => lsp.status !== "failed");
12873
+ if (successfulLsps.length === 0) {
12874
+ return [];
12875
+ }
12876
+ const sections = ["# Codex LSP Configuration", ""];
12877
+ for (const lsp of successfulLsps) {
12878
+ const config = getLspCommand(lsp.lspId);
12879
+ if (config) {
12880
+ sections.push(`[mcp_servers.${lsp.lspId}]`);
12881
+ sections.push(`command = "${config.command}"`);
12882
+ if (config.args.length > 0) {
12883
+ sections.push(`args = [${config.args.map((a) => `"${a}"`).join(", ")}]`);
12884
+ }
12885
+ sections.push(`startup_timeout_sec = ${MCP_STARTUP_TIMEOUT_SEC}`);
12886
+ sections.push("");
12887
+ }
12888
+ }
12889
+ return [
12890
+ {
12891
+ path: ".codex/config.toml",
12892
+ content: sections.join(`
12893
+ `),
12894
+ overwrite: false
12895
+ }
12896
+ ];
12897
+ }
12898
+ async isAvailable() {
12899
+ return true;
12900
+ }
12901
+ }
12902
+ function createCodexLspGenerator() {
12903
+ return new CodexLspGenerator;
12904
+ }
12905
+
12906
+ // src/adapters/cursor/cursor-lsp-generator.ts
12907
+ class CursorLspGenerator {
12908
+ name = "cursor-lsp";
12909
+ target = "cursor";
12910
+ generate(installedLsps) {
12911
+ const successfulLsps = installedLsps.filter((lsp) => lsp.status !== "failed");
12912
+ if (successfulLsps.length === 0) {
12913
+ return [];
12914
+ }
12915
+ const mcpServers = {};
12916
+ for (const lsp of successfulLsps) {
12917
+ const config = getLspCommand(lsp.lspId);
12918
+ if (config) {
12919
+ mcpServers[lsp.lspId] = { command: config.command, args: [...config.args] };
12920
+ }
12921
+ }
12922
+ const content = JSON.stringify({
12923
+ mcpServers
12924
+ }, null, 2);
12925
+ return [
12926
+ {
12927
+ path: ".cursor/mcp.json",
12928
+ content,
12929
+ overwrite: false
12930
+ }
12931
+ ];
12932
+ }
12933
+ async isAvailable() {
12934
+ return true;
12935
+ }
12936
+ }
12937
+ function createCursorLspGenerator() {
12938
+ return new CursorLspGenerator;
12939
+ }
12940
+
12941
+ // src/adapters/gemini/gemini-lsp-generator.ts
12942
+ class GeminiLspGenerator {
12943
+ name = "gemini-lsp";
12944
+ target = "gemini";
12945
+ generate(installedLsps) {
12946
+ const successfulLsps = installedLsps.filter((lsp) => lsp.status !== "failed");
12947
+ if (successfulLsps.length === 0) {
12948
+ return [];
12949
+ }
12950
+ const mcpServers = {};
12951
+ for (const lsp of successfulLsps) {
12952
+ const config = getLspCommand(lsp.lspId);
12953
+ if (config) {
12954
+ mcpServers[lsp.lspId] = { command: config.command, args: [...config.args] };
12955
+ }
12956
+ }
12957
+ const content = JSON.stringify({
12958
+ mcpServers
12959
+ }, null, 2);
12960
+ return [
12961
+ {
12962
+ path: ".gemini/settings.json",
12963
+ content,
12964
+ overwrite: false
12965
+ }
12966
+ ];
12967
+ }
12968
+ async isAvailable() {
12969
+ return true;
12970
+ }
12971
+ }
12972
+ function createGeminiLspGenerator() {
12973
+ return new GeminiLspGenerator;
12974
+ }
12975
+
12976
+ // src/adapters/opencode/opencode-lsp-generator.ts
12977
+ class OpenCodeLspGenerator {
12978
+ name = "opencode-lsp";
12979
+ target = "opencode";
12980
+ generate(installedLsps) {
12981
+ const successfulLsps = installedLsps.filter((lsp) => lsp.status !== "failed");
12982
+ if (successfulLsps.length === 0) {
12983
+ return [];
12984
+ }
12985
+ const mcp = {};
12986
+ for (const lsp of successfulLsps) {
12987
+ const config = getLspCommand(lsp.lspId);
12988
+ if (config) {
12989
+ mcp[lsp.lspId] = {
12990
+ type: "local",
12991
+ command: [config.command, ...config.args],
12992
+ enabled: true,
12993
+ timeout: 120000
12994
+ };
12995
+ }
12996
+ }
12997
+ const content = JSON.stringify({
12998
+ $schema: "https://opencode.ai/config.json",
12999
+ mcp
13000
+ }, null, 2);
13001
+ return [
13002
+ {
13003
+ path: ".opencode/opencode.json",
13004
+ content,
13005
+ overwrite: false
13006
+ }
13007
+ ];
13008
+ }
13009
+ async isAvailable() {
13010
+ return true;
13011
+ }
13012
+ }
13013
+ function createOpenCodeLspGenerator() {
13014
+ return new OpenCodeLspGenerator;
13015
+ }
13016
+
13017
+ // src/core/lsp/lsp-installer.ts
13018
+ import { execFile } from "node:child_process";
13019
+ import { access as access5, mkdir as mkdir5 } from "node:fs/promises";
13020
+ import { homedir as homedir3 } from "node:os";
13021
+ import { join as join4 } from "node:path";
13022
+ import { promisify } from "node:util";
13023
+ var execFileAsync = promisify(execFile);
13024
+
13025
+ class LspInstaller {
13026
+ lspBinDir;
13027
+ constructor() {
13028
+ this.lspBinDir = join4(homedir3(), ".codeconductor", "lsp", "bin");
13029
+ }
13030
+ async checkInstalled(def) {
13031
+ try {
13032
+ const { stdout } = await execFileAsync("which", [def.binaryName], { timeout: 5000 });
13033
+ const path = stdout.trim();
13034
+ if (path) {
13035
+ const version = await this.getVersion(def);
13036
+ return { installed: true, version, path };
13037
+ }
13038
+ } catch {}
13039
+ if (def.packageManager === "npm" && def.npmDetect) {
13040
+ try {
13041
+ const { stdout } = await execFileAsync("npm", ["list", "-g", def.npmDetect, "--depth=0"], { timeout: 1e4 });
13042
+ if (stdout.includes(def.npmDetect)) {
13043
+ const version = this.parseVersionFromNpmList(stdout, def.npmDetect);
13044
+ return { installed: true, version };
13045
+ }
13046
+ } catch {}
13047
+ }
13048
+ if (def.packageManager === "pip" && def.pipDetect) {
13049
+ try {
13050
+ const { stdout } = await execFileAsync("pip", ["show", def.pipDetect], { timeout: 1e4 });
13051
+ if (stdout.includes("Version:")) {
13052
+ const version = this.parseVersionFromPipShow(stdout);
13053
+ return { installed: true, version };
13054
+ }
13055
+ } catch {}
13056
+ }
13057
+ return { installed: false };
13058
+ }
13059
+ async installLsp(def) {
13060
+ const status = await this.checkInstalled(def);
13061
+ if (status.installed) {
13062
+ return {
13063
+ lspId: def.id,
13064
+ status: "already-installed",
13065
+ version: status.version
13066
+ };
13067
+ }
13068
+ try {
13069
+ switch (def.packageManager) {
13070
+ case "npm":
13071
+ await this.installNpm(def);
13072
+ break;
13073
+ case "pip":
13074
+ await this.installPip(def);
13075
+ break;
13076
+ case "binary":
13077
+ await this.installBinary(def);
13078
+ break;
13079
+ }
13080
+ const newStatus = await this.checkInstalled(def);
13081
+ return {
13082
+ lspId: def.id,
13083
+ status: "installed",
13084
+ version: newStatus.version
13085
+ };
13086
+ } catch (error) {
13087
+ return {
13088
+ lspId: def.id,
13089
+ status: "failed",
13090
+ error: error instanceof Error ? error.message : String(error)
13091
+ };
13092
+ }
13093
+ }
13094
+ async installAll(lsps, options) {
13095
+ const results = [];
13096
+ for (const lsp of lsps) {
13097
+ if (options.dryRun) {
13098
+ const status = await this.checkInstalled(lsp);
13099
+ results.push({
13100
+ lspId: lsp.id,
13101
+ status: status.installed ? "already-installed" : "installed",
13102
+ version: status.version
13103
+ });
13104
+ } else {
13105
+ const result = await this.installLsp(lsp);
13106
+ results.push(result);
13107
+ }
13108
+ }
13109
+ return {
13110
+ results,
13111
+ allSucceeded: results.every((r) => r.status !== "failed")
13112
+ };
13113
+ }
13114
+ async getVersion(def) {
13115
+ try {
13116
+ const { stdout } = await execFileAsync(def.binaryName, [def.versionFlag], { timeout: 5000 });
13117
+ return stdout.trim().split(`
13118
+ `)[0];
13119
+ } catch {
13120
+ return;
13121
+ }
13122
+ }
13123
+ parseVersionFromNpmList(output, packageName) {
13124
+ const match = output.match(new RegExp(`${packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}@([\\d.]+)`));
13125
+ return match?.[1];
13126
+ }
13127
+ parseVersionFromPipShow(output) {
13128
+ const match = output.match(/Version:\s*([\d.]+)/);
13129
+ return match?.[1];
13130
+ }
13131
+ async installNpm(def) {
13132
+ try {
13133
+ await execFileAsync("npm", ["install", "-g", def.package], { timeout: 120000 });
13134
+ } catch (error) {
13135
+ throw new Error(`Failed to install ${def.serverName}: ${error instanceof Error ? error.message : String(error)}`);
13136
+ }
13137
+ }
13138
+ async installPip(def) {
13139
+ try {
13140
+ await execFileAsync("pip", ["install", "--user", def.package], { timeout: 120000 });
13141
+ } catch (error) {
13142
+ throw new Error(`Failed to install ${def.serverName}: ${error instanceof Error ? error.message : String(error)}`);
13143
+ }
13144
+ }
13145
+ async installBinary(def) {
13146
+ if (!def.binaryPlatforms) {
13147
+ throw new Error(`No binary platforms defined for ${def.serverName}`);
13148
+ }
13149
+ const platformKey = `${process.platform}-${process.arch}`;
13150
+ const binary = def.binaryPlatforms[platformKey];
13151
+ if (!binary) {
13152
+ throw new Error(`No binary available for platform: ${platformKey}`);
13153
+ }
13154
+ await mkdir5(this.lspBinDir, { recursive: true });
13155
+ const destPath = join4(this.lspBinDir, def.binaryName);
13156
+ try {
13157
+ await access5(destPath);
13158
+ return;
13159
+ } catch {}
13160
+ const { execSync } = await import("node:child_process");
13161
+ const isWindows = process.platform === "win32";
13162
+ const extractCmd = binary.url.endsWith(".zip") ? `curl -L "${binary.url}" | tar -xz -C "${this.lspBinDir}"` : `curl -L "${binary.url}" | tar -xz -C "${this.lspBinDir}"`;
13163
+ try {
13164
+ execSync(extractCmd, { timeout: 120000, stdio: "pipe" });
13165
+ if (!isWindows) {
13166
+ execSync(`chmod +x "${destPath}"`, { stdio: "pipe" });
13167
+ }
13168
+ } catch (error) {
13169
+ throw new Error(`Failed to download ${def.serverName}: ${error instanceof Error ? error.message : String(error)}`);
13170
+ }
13171
+ }
13172
+ }
13173
+ function createLspInstaller() {
13174
+ return new LspInstaller;
13175
+ }
13176
+
13177
+ // src/core/lsp/lsp-registry.ts
13178
+ var LSP_DEFINITIONS = [
13179
+ {
13180
+ id: "typescript",
13181
+ language: "typescript",
13182
+ serverName: "TypeScript Language Server",
13183
+ packageManager: "npm",
13184
+ package: "typescript-language-server",
13185
+ binaryName: "typescript-language-server",
13186
+ installCmd: "npm install -g typescript-language-server",
13187
+ versionFlag: "--version",
13188
+ npmDetect: "typescript-language-server"
13189
+ },
13190
+ {
13191
+ id: "php",
13192
+ language: "php",
13193
+ serverName: "Intelephense",
13194
+ packageManager: "npm",
13195
+ package: "@bmewburn/vscode-intelephense-client",
13196
+ binaryName: "intelephense",
13197
+ installCmd: "npm install -g @bmewburn/vscode-intelephense-client",
13198
+ versionFlag: "--version",
13199
+ npmDetect: "@bmewburn/vscode-intelephense-client"
13200
+ },
13201
+ {
13202
+ id: "python",
13203
+ language: "python",
13204
+ serverName: "Python LSP Server",
13205
+ packageManager: "pip",
13206
+ package: "python-lsp-server",
13207
+ binaryName: "pylsp",
13208
+ installCmd: "pip install --user python-lsp-server",
13209
+ versionFlag: "--version",
13210
+ pipDetect: "python-lsp-server"
13211
+ },
13212
+ {
13213
+ id: "kotlin",
13214
+ language: "kotlin",
13215
+ serverName: "Kotlin Language Server",
13216
+ packageManager: "binary",
13217
+ package: "kotlin-language-server",
13218
+ binaryName: "kotlin-language-server",
13219
+ installCmd: "Download from GitHub releases",
13220
+ versionFlag: "--version",
13221
+ binaryPlatforms: {
13222
+ "linux-x64": {
13223
+ url: "https://github.com/fwcd/kotlin-language-server/releases/latest/download/server-linux-x64.tar.gz"
13224
+ },
13225
+ "linux-arm64": {
13226
+ url: "https://github.com/fwcd/kotlin-language-server/releases/latest/download/server-linux-arm64.tar.gz"
13227
+ },
13228
+ "darwin-x64": {
13229
+ url: "https://github.com/fwcd/kotlin-language-server/releases/latest/download/server-macos-x64.tar.gz"
13230
+ },
13231
+ "darwin-arm64": {
13232
+ url: "https://github.com/fwcd/kotlin-language-server/releases/latest/download/server-macos-arm64.tar.gz"
13233
+ },
13234
+ "win32-x64": {
13235
+ url: "https://github.com/fwcd/kotlin-language-server/releases/latest/download/server-windows-x64.zip"
13236
+ }
13237
+ }
13238
+ }
13239
+ ];
13240
+ function resolveLsps(languages) {
13241
+ const languageToLspId = {
13242
+ typescript: "typescript",
13243
+ javascript: "typescript",
13244
+ php: "php",
13245
+ python: "python",
13246
+ java: "kotlin",
13247
+ kotlin: "kotlin"
13248
+ };
13249
+ const resolvedIds = new Set;
13250
+ for (const lang of languages) {
13251
+ const lspId = languageToLspId[lang.toLowerCase()];
13252
+ if (lspId) {
13253
+ resolvedIds.add(lspId);
13254
+ }
13255
+ }
13256
+ return LSP_DEFINITIONS.filter((def) => resolvedIds.has(def.id));
13257
+ }
13258
+
13259
+ // src/commands/install-lsp.command.ts
13260
+ async function installLspCommand(options) {
13261
+ const { target, lang, dryRun, force, global: isGlobal, output, projectRoot } = options;
13262
+ const baseDir = isGlobal ? homedir4() : projectRoot;
13263
+ try {
13264
+ const runnerTarget = parseRunnerTarget(target);
13265
+ const targets = getIndividualTargets(runnerTarget);
13266
+ let languages;
13267
+ if (lang && lang.length > 0) {
13268
+ languages = lang;
13269
+ } else {
13270
+ const profile = await detectProject(projectRoot);
13271
+ languages = profile.languages;
13272
+ }
13273
+ if (languages.length === 0) {
13274
+ return {
13275
+ code: 1,
13276
+ data: {
13277
+ success: false,
13278
+ command: "install",
13279
+ subcommand: "lsp",
13280
+ errors: ["No languages detected. Use --lang to specify languages manually."]
13281
+ }
13282
+ };
13283
+ }
13284
+ const lsps = resolveLsps(languages);
13285
+ if (lsps.length === 0) {
13286
+ return {
13287
+ code: 1,
13288
+ data: {
13289
+ success: false,
13290
+ command: "install",
13291
+ subcommand: "lsp",
13292
+ errors: [`No LSP servers available for languages: ${languages.join(", ")}`]
13293
+ }
13294
+ };
13295
+ }
13296
+ const installer = createLspInstaller();
13297
+ const installReport = await installer.installAll(lsps, { dryRun });
13298
+ const writeOptions = { dryRun, force };
13299
+ const allConfigResults = [];
13300
+ for (const t of targets) {
13301
+ const generator = getLspConfigGenerator(t);
13302
+ if (!generator) {
13303
+ continue;
13304
+ }
13305
+ const generatedFiles = generator.generate(installReport.results);
13306
+ const resolvedFiles = generatedFiles.map((f) => ({
13307
+ ...f,
13308
+ path: resolve8(baseDir, f.path)
13309
+ }));
13310
+ const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
13311
+ for (const result of results) {
13312
+ allConfigResults.push({
13313
+ target: t,
13314
+ path: result.path,
13315
+ success: result.success,
13316
+ error: result.error
13317
+ });
13318
+ }
13319
+ }
13320
+ const errors3 = allConfigResults.filter((r) => !r.success).map((r) => `${r.path}: ${r.error}`);
13321
+ if (output === "json") {
13322
+ return {
13323
+ code: errors3.length > 0 ? 2 : 0,
13324
+ data: {
13325
+ success: errors3.length === 0,
13326
+ command: "install",
13327
+ subcommand: "lsp",
13328
+ targets,
13329
+ languages: [...languages],
13330
+ global: isGlobal,
13331
+ dryRun,
13332
+ lspResults: installReport.results,
13333
+ configResults: allConfigResults
13334
+ }
13335
+ };
13336
+ }
13337
+ console.log(`
13338
+ LSP Installation:`);
13339
+ for (const result of installReport.results) {
13340
+ const icon = result.status === "already-installed" ? "✓" : result.status === "installed" ? "+" : "✗";
13341
+ const version = result.version ? ` (${result.version})` : "";
13342
+ const error = result.error ? `: ${result.error}` : "";
13343
+ console.log(` ${icon} ${result.lspId}${version}${error}`);
13344
+ }
13345
+ console.log(`
13346
+ Config Files:`);
13347
+ for (const result of allConfigResults) {
13348
+ const icon = result.success ? "✓" : "✗";
13349
+ const error = result.error ? `: ${result.error}` : "";
13350
+ console.log(` ${icon} ${result.target}: ${result.path}${error}`);
13351
+ }
13352
+ const note = dryRun ? " (dry-run)" : "";
13353
+ console.log(`
13354
+ ${installReport.results.length} LSPs processed, ${allConfigResults.length} config files${note}`);
13355
+ return {
13356
+ code: errors3.length > 0 ? 2 : 0,
13357
+ data: {
13358
+ success: errors3.length === 0,
13359
+ command: "install",
13360
+ subcommand: "lsp",
13361
+ targets,
13362
+ languages: [...languages],
13363
+ global: isGlobal,
13364
+ dryRun,
13365
+ lspResults: installReport.results,
13366
+ configResults: allConfigResults
13367
+ }
13368
+ };
13369
+ } catch (error) {
13370
+ return {
13371
+ code: 1,
13372
+ data: {
13373
+ success: false,
13374
+ command: "install",
13375
+ subcommand: "lsp",
13376
+ errors: [String(error)]
13377
+ }
13378
+ };
13379
+ }
13380
+ }
13381
+ function getLspConfigGenerator(target) {
13382
+ switch (target) {
13383
+ case "opencode":
13384
+ return createOpenCodeLspGenerator();
13385
+ case "claude":
13386
+ return createClaudeLspGenerator();
13387
+ case "codex":
13388
+ return createCodexLspGenerator();
13389
+ case "gemini":
13390
+ return createGeminiLspGenerator();
13391
+ case "cursor":
13392
+ return createCursorLspGenerator();
13393
+ case "agy":
13394
+ return createAgyLspGenerator();
13395
+ default:
13396
+ return;
13397
+ }
13398
+ }
13399
+
12663
13400
  // src/commands/update.command.ts
12664
13401
  async function updateCommand(options) {
12665
13402
  const { dryRun, force, output, projectRoot } = options;
@@ -12832,7 +13569,12 @@ function parseArgs(args) {
12832
13569
  const arg = remaining[i];
12833
13570
  if (arg.startsWith("--")) {
12834
13571
  const [key, value] = arg.slice(2).split("=");
12835
- if (value !== undefined) {
13572
+ if (key === "lang") {
13573
+ const langValue = value !== undefined ? value : remaining[++i];
13574
+ if (langValue && typeof langValue === "string") {
13575
+ options[key] = langValue.split(",").map((s) => s.trim());
13576
+ }
13577
+ } else if (value !== undefined) {
12836
13578
  options[key] = value;
12837
13579
  } else if (remaining[i + 1] && !remaining[i + 1].startsWith("-")) {
12838
13580
  options[key] = remaining[++i];
@@ -12856,6 +13598,7 @@ Commands:
12856
13598
  detect Detect project stack and recommended presets
12857
13599
  install council Install generated council spec files to runner targets
12858
13600
  install preset Install full preset (agents, prompts, skills, commands)
13601
+ install lsp Install and configure LSP servers for AI coding tools
12859
13602
  doctor Validate configuration and generated files
12860
13603
  update Update installed presets
12861
13604
 
@@ -12866,6 +13609,7 @@ Options:
12866
13609
  --force Allow overwriting existing files
12867
13610
  --global Install to home directory (~/.claude, ~/.opencode, etc.)
12868
13611
  --output, -o Output mode: human or json
13612
+ --lang Comma-separated list of languages (e.g., typescript,php,python)
12869
13613
 
12870
13614
  Examples:
12871
13615
  npx cc-codeconductor init
@@ -12880,6 +13624,9 @@ Examples:
12880
13624
  npx cc-codeconductor install council --target claude
12881
13625
  npx cc-codeconductor install council --target codex
12882
13626
  npx cc-codeconductor install council --target all
13627
+ npx cc-codeconductor install lsp --target opencode
13628
+ npx cc-codeconductor install lsp --target all --lang typescript,python
13629
+ npx cc-codeconductor install lsp --target claude --dry-run
12883
13630
  npx cc-codeconductor doctor
12884
13631
  npx cc-codeconductor update --dry-run
12885
13632
  `;
@@ -12904,7 +13651,7 @@ async function routeCommand(args, projectRoot) {
12904
13651
  });
12905
13652
  case "install": {
12906
13653
  const isGlobal = options.global === true || options.global === "true";
12907
- const VALID_TARGETS = ["opencode", "claude", "codex", "all"];
13654
+ const VALID_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor", "agy", "all"];
12908
13655
  let resolvedSubcommand = subcommand;
12909
13656
  let target = options.target;
12910
13657
  if (!target && subcommand && VALID_TARGETS.includes(subcommand)) {
@@ -12912,6 +13659,17 @@ async function routeCommand(args, projectRoot) {
12912
13659
  resolvedSubcommand = undefined;
12913
13660
  }
12914
13661
  target = target || "opencode";
13662
+ if (resolvedSubcommand === "lsp") {
13663
+ return installLspCommand({
13664
+ projectRoot,
13665
+ target,
13666
+ lang: options.lang,
13667
+ dryRun: flags.dryRun,
13668
+ force: flags.force,
13669
+ global: isGlobal,
13670
+ output: flags.output
13671
+ });
13672
+ }
12915
13673
  if (resolvedSubcommand === "preset") {
12916
13674
  return installPresetCommand({
12917
13675
  projectRoot,