cc-codeconductor 0.2.7 → 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 +781 -24
  3. package/package.json +1 -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.7",
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",
@@ -7132,6 +7132,13 @@ async function detectProject(rootDir) {
7132
7132
  runtimes.push("python");
7133
7133
  frameworks.push("django");
7134
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
+ }
7135
7142
  const astroSignals = await detectAstro(rootDir);
7136
7143
  if (astroSignals.length > 0) {
7137
7144
  signals.push(...astroSignals);
@@ -7218,6 +7225,22 @@ async function detectAstro(rootDir) {
7218
7225
  }
7219
7226
  return signals;
7220
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
+ }
7221
7244
 
7222
7245
  // src/commands/detect.command.ts
7223
7246
  async function detectCommand(options) {
@@ -11347,7 +11370,7 @@ var CodeConductorConfigSchema = exports_external.object({
11347
11370
  profile: exports_external.string().optional()
11348
11371
  }),
11349
11372
  defaults: exports_external.object({
11350
- target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor"]),
11373
+ target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor", "agy"]),
11351
11374
  overwrite: exports_external.boolean()
11352
11375
  }),
11353
11376
  presets: exports_external.object({
@@ -11367,6 +11390,7 @@ var RunnerTargetSchema = exports_external.enum([
11367
11390
  "codex",
11368
11391
  "gemini",
11369
11392
  "cursor",
11393
+ "agy",
11370
11394
  "all"
11371
11395
  ]);
11372
11396
  var InstallStrategySchema = exports_external.enum([
@@ -11384,20 +11408,23 @@ var ManifestEntrySchema = exports_external.object({
11384
11408
  template: exports_external.boolean().optional()
11385
11409
  });
11386
11410
  var InstallManifestSchema = exports_external.object({
11387
- target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor"]),
11411
+ target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor", "agy"]),
11388
11412
  entries: exports_external.array(ManifestEntrySchema)
11389
11413
  });
11390
11414
  var ToolProviderNamesSchema = exports_external.record(exports_external.string(), exports_external.string());
11415
+ var PermissionProviderNamesSchema = exports_external.record(exports_external.string(), exports_external.string());
11391
11416
  var ModelConfigSchema = exports_external.object({
11392
- target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor"]),
11417
+ target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor", "agy"]),
11393
11418
  agents: exports_external.record(exports_external.string(), exports_external.object({
11394
11419
  claude: exports_external.string().optional(),
11395
11420
  opencode: exports_external.string().optional(),
11396
11421
  codex: exports_external.string().optional(),
11397
11422
  gemini: exports_external.string().optional(),
11398
- cursor: exports_external.string().optional()
11423
+ cursor: exports_external.string().optional(),
11424
+ agy: exports_external.string().optional()
11399
11425
  })),
11400
- tools: exports_external.record(exports_external.string(), ToolProviderNamesSchema).optional()
11426
+ tools: exports_external.record(exports_external.string(), ToolProviderNamesSchema).optional(),
11427
+ permissions: PermissionProviderNamesSchema.optional()
11401
11428
  });
11402
11429
  function validateCouncilSpec(data) {
11403
11430
  return CouncilSpecSchema.parse(data);
@@ -11721,6 +11748,8 @@ function resolveAssets(target) {
11721
11748
  return ["agents", "prompts/v0.2.0"];
11722
11749
  case "cursor":
11723
11750
  return ["agents", "prompts/v0.2.0"];
11751
+ case "agy":
11752
+ return ["agents", "prompts/v0.2.0"];
11724
11753
  }
11725
11754
  }
11726
11755
 
@@ -11904,7 +11933,27 @@ function getResponsibilities(agentId) {
11904
11933
  devil: `- Challenge assumptions
11905
11934
  - Find edge cases
11906
11935
  - Identify failure modes
11907
- - 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`
11908
11957
  };
11909
11958
  return responsibilities[agentId] || "- Provide critical review";
11910
11959
  }
@@ -12082,7 +12131,7 @@ function createCodexInstaller(spec) {
12082
12131
  function generateOpenCodeFiles(spec) {
12083
12132
  const files = [];
12084
12133
  files.push({
12085
- path: ".opencode/commands/council.md",
12134
+ path: ".opencode/commands/cc-council.md",
12086
12135
  content: generateCouncilCommand(spec),
12087
12136
  overwrite: false
12088
12137
  });
@@ -12094,17 +12143,20 @@ function generateOpenCodeFiles(spec) {
12094
12143
  for (const agent of spec.agents) {
12095
12144
  files.push({
12096
12145
  path: `.opencode/agents/council-${agent.id}.md`,
12097
- content: generateAgentContent(agent),
12146
+ content: generateOpenCodeAgentContent(agent),
12098
12147
  overwrite: false
12099
12148
  });
12100
12149
  }
12101
12150
  return files;
12102
12151
  }
12103
12152
  function generateCouncilCommand(spec) {
12104
- return `# Council Command
12153
+ return `---
12154
+ description: ${yamlString(spec.description)}
12155
+ agent: council-lead
12156
+ subtask: true
12157
+ ---
12105
12158
 
12106
- ## Description
12107
- ${spec.description}
12159
+ Run the CodeConductor council for multi-perspective analysis.
12108
12160
 
12109
12161
  ## Version
12110
12162
  ${spec.version}
@@ -12113,12 +12165,25 @@ ${spec.version}
12113
12165
  ${spec.agents.map((a) => `- ${a.role} (${a.id})`).join(`
12114
12166
  `)}
12115
12167
 
12116
- ## Usage
12117
- 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.
12118
12170
  `;
12119
12171
  }
12120
12172
  function generateCouncilLead(spec) {
12121
- 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
12122
12187
 
12123
12188
  ## Role
12124
12189
  Coordinates the council and synthesizes perspectives
@@ -12137,6 +12202,37 @@ ${spec.agents.map((a) => `- ${a.role}: ${a.focus.join(", ")}`).join(`
12137
12202
  - Provide final recommendation
12138
12203
  `;
12139
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
+ }
12140
12236
 
12141
12237
  // src/adapters/opencode/opencode-installer.ts
12142
12238
  class OpenCodeInstaller {
@@ -12320,7 +12416,7 @@ function renderTemplate(content, modelConfig, filePath) {
12320
12416
  const agentModels = modelConfig.agents[agentRole];
12321
12417
  const targetModel = agentModels[modelConfig.target];
12322
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 ?? "");
12323
- if (modelConfig.tools) {
12419
+ if (modelConfig.tools || modelConfig.permissions) {
12324
12420
  result2 = substituteToolNames(result2, modelConfig);
12325
12421
  }
12326
12422
  return result2;
@@ -12349,19 +12445,27 @@ function renderTemplate(content, modelConfig, filePath) {
12349
12445
  }
12350
12446
  }
12351
12447
  }
12352
- if (modelConfig.tools) {
12448
+ if (modelConfig.tools || modelConfig.permissions) {
12353
12449
  result = substituteToolNames(result, modelConfig);
12354
12450
  }
12355
12451
  return result;
12356
12452
  }
12357
12453
  function substituteToolNames(content, modelConfig) {
12358
- if (!modelConfig.tools)
12454
+ if (!modelConfig.tools && !modelConfig.permissions)
12359
12455
  return content;
12360
12456
  const target = modelConfig.target;
12361
12457
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
12362
12458
  if (!fmMatch)
12363
12459
  return content;
12364
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;
12365
12469
  const updatedFrontmatter = frontmatter.replace(/^tools:\s*(.+)$/m, (_match, toolsLine) => {
12366
12470
  const baseNames = toolsLine.split(",").map((t) => t.trim());
12367
12471
  const mappedNames = baseNames.map((baseName) => {
@@ -12390,7 +12494,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
12390
12494
  const incomingContent = isTemplate && modelConfig ? renderTemplate(content, modelConfig, srcPath) : content;
12391
12495
  let finalContent = incomingContent;
12392
12496
  let action = "written";
12393
- if (strategy === "append" && !force) {
12497
+ if (strategy === "append") {
12394
12498
  let existing = "";
12395
12499
  try {
12396
12500
  existing = await readFile4(destPath, "utf-8");
@@ -12403,7 +12507,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
12403
12507
  ` + incomingContent;
12404
12508
  }
12405
12509
  action = "appended";
12406
- } else if (strategy === "merge-json" && !force) {
12510
+ } else if (strategy === "merge-json") {
12407
12511
  let existing = {};
12408
12512
  try {
12409
12513
  existing = JSON.parse(await readFile4(destPath, "utf-8"));
@@ -12504,8 +12608,8 @@ async function loadCouncilPreset(projectRoot) {
12504
12608
  }
12505
12609
 
12506
12610
  // src/core/runner/runner-target.ts
12507
- var RUNNER_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor", "all"];
12508
- 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"];
12509
12613
  function isRunnerTarget(value) {
12510
12614
  return RUNNER_TARGETS.includes(value);
12511
12615
  }
@@ -12661,6 +12765,638 @@ async function installPresetCommand(options) {
12661
12765
  }
12662
12766
  }
12663
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
+
12664
13400
  // src/commands/update.command.ts
12665
13401
  async function updateCommand(options) {
12666
13402
  const { dryRun, force, output, projectRoot } = options;
@@ -12833,7 +13569,12 @@ function parseArgs(args) {
12833
13569
  const arg = remaining[i];
12834
13570
  if (arg.startsWith("--")) {
12835
13571
  const [key, value] = arg.slice(2).split("=");
12836
- 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) {
12837
13578
  options[key] = value;
12838
13579
  } else if (remaining[i + 1] && !remaining[i + 1].startsWith("-")) {
12839
13580
  options[key] = remaining[++i];
@@ -12857,6 +13598,7 @@ Commands:
12857
13598
  detect Detect project stack and recommended presets
12858
13599
  install council Install generated council spec files to runner targets
12859
13600
  install preset Install full preset (agents, prompts, skills, commands)
13601
+ install lsp Install and configure LSP servers for AI coding tools
12860
13602
  doctor Validate configuration and generated files
12861
13603
  update Update installed presets
12862
13604
 
@@ -12867,6 +13609,7 @@ Options:
12867
13609
  --force Allow overwriting existing files
12868
13610
  --global Install to home directory (~/.claude, ~/.opencode, etc.)
12869
13611
  --output, -o Output mode: human or json
13612
+ --lang Comma-separated list of languages (e.g., typescript,php,python)
12870
13613
 
12871
13614
  Examples:
12872
13615
  npx cc-codeconductor init
@@ -12881,6 +13624,9 @@ Examples:
12881
13624
  npx cc-codeconductor install council --target claude
12882
13625
  npx cc-codeconductor install council --target codex
12883
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
12884
13630
  npx cc-codeconductor doctor
12885
13631
  npx cc-codeconductor update --dry-run
12886
13632
  `;
@@ -12905,7 +13651,7 @@ async function routeCommand(args, projectRoot) {
12905
13651
  });
12906
13652
  case "install": {
12907
13653
  const isGlobal = options.global === true || options.global === "true";
12908
- const VALID_TARGETS = ["opencode", "claude", "codex", "all"];
13654
+ const VALID_TARGETS = ["opencode", "claude", "codex", "gemini", "cursor", "agy", "all"];
12909
13655
  let resolvedSubcommand = subcommand;
12910
13656
  let target = options.target;
12911
13657
  if (!target && subcommand && VALID_TARGETS.includes(subcommand)) {
@@ -12913,6 +13659,17 @@ async function routeCommand(args, projectRoot) {
12913
13659
  resolvedSubcommand = undefined;
12914
13660
  }
12915
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
+ }
12916
13673
  if (resolvedSubcommand === "preset") {
12917
13674
  return installPresetCommand({
12918
13675
  projectRoot,