@corbat-tech/coco 2.25.8 → 2.25.10

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/index.js CHANGED
@@ -121,6 +121,7 @@ function createDefaultConfigObject(projectName, language = "typescript") {
121
121
  temperature: 0,
122
122
  timeout: 12e4
123
123
  },
124
+ providerModels: {},
124
125
  quality: {
125
126
  minScore: 85,
126
127
  minCoverage: 80,
@@ -300,6 +301,7 @@ var init_schema = __esm({
300
301
  temperature: 0,
301
302
  timeout: 12e4
302
303
  }),
304
+ providerModels: z.record(z.string(), z.string()).optional(),
303
305
  quality: QualityConfigSchema.default({
304
306
  minScore: 85,
305
307
  minCoverage: 80,
@@ -597,6 +599,7 @@ function deepMergeConfig(base, override) {
597
599
  ...override,
598
600
  project: { ...base.project, ...override.project },
599
601
  provider: { ...base.provider, ...override.provider },
602
+ providerModels: { ...base.providerModels, ...override.providerModels },
600
603
  quality: { ...base.quality, ...override.quality },
601
604
  persistence: { ...base.persistence, ...override.persistence },
602
605
  // Merge optional sections only if present in either base or override
@@ -2543,7 +2546,7 @@ function getDefaultModel(provider) {
2543
2546
  case "codex":
2544
2547
  return process.env["CODEX_MODEL"] ?? "codex-mini-latest";
2545
2548
  case "copilot":
2546
- return process.env["COPILOT_MODEL"] ?? "gpt-4o-copilot";
2549
+ return process.env["COPILOT_MODEL"] ?? "claude-sonnet-4.6";
2547
2550
  case "groq":
2548
2551
  return process.env["GROQ_MODEL"] ?? "llama-3.3-70b-versatile";
2549
2552
  case "openrouter":
@@ -2562,6 +2565,11 @@ function getDefaultModel(provider) {
2562
2565
  return "claude-sonnet-4-6";
2563
2566
  }
2564
2567
  }
2568
+ function normalizeConfiguredModel(model) {
2569
+ if (typeof model !== "string") return void 0;
2570
+ const trimmed = model.trim();
2571
+ return trimmed.length > 0 ? trimmed : void 0;
2572
+ }
2565
2573
  function getDefaultProvider() {
2566
2574
  const envProvider = process.env["COCO_PROVIDER"]?.toLowerCase();
2567
2575
  if (envProvider && VALID_PROVIDERS.includes(envProvider)) {
@@ -2583,8 +2591,12 @@ async function getLastUsedProvider() {
2583
2591
  async function getLastUsedModel(provider) {
2584
2592
  try {
2585
2593
  const config = await loadConfig(CONFIG_PATHS.config);
2594
+ const perProviderModel = normalizeConfiguredModel(config.providerModels?.[provider]);
2595
+ if (perProviderModel) {
2596
+ return perProviderModel;
2597
+ }
2586
2598
  if (config.provider.type === provider) {
2587
- return config.provider.model;
2599
+ return normalizeConfiguredModel(config.provider.model);
2588
2600
  }
2589
2601
  } catch {
2590
2602
  }
@@ -2621,8 +2633,14 @@ async function saveProviderPreference(provider, model) {
2621
2633
  };
2622
2634
  }
2623
2635
  config.provider.type = provider;
2624
- if (model) {
2625
- config.provider.model = model;
2636
+ const normalizedModel = normalizeConfiguredModel(model);
2637
+ const persistedModel = normalizedModel ?? getDefaultModel(provider);
2638
+ config.providerModels = {
2639
+ ...config.providerModels,
2640
+ [provider]: persistedModel
2641
+ };
2642
+ if (normalizedModel) {
2643
+ config.provider.model = normalizedModel;
2626
2644
  } else {
2627
2645
  config.provider.model = getDefaultModel(provider);
2628
2646
  }
@@ -2719,11 +2737,25 @@ async function migrateOldPreferences() {
2719
2737
  }
2720
2738
  if (oldPrefs.provider && VALID_PROVIDERS.includes(oldPrefs.provider)) {
2721
2739
  config.provider.type = oldPrefs.provider;
2740
+ config.providerModels = {
2741
+ ...config.providerModels
2742
+ };
2743
+ for (const [providerName, modelName] of Object.entries(oldPrefs.models ?? {})) {
2744
+ if (VALID_PROVIDERS.includes(providerName)) {
2745
+ const normalized = normalizeConfiguredModel(modelName);
2746
+ if (normalized) {
2747
+ config.providerModels[providerName] = normalized;
2748
+ }
2749
+ }
2750
+ }
2722
2751
  const modelForProvider = oldPrefs.provider ? oldPrefs.models?.[oldPrefs.provider] : void 0;
2723
- if (modelForProvider) {
2724
- config.provider.model = modelForProvider;
2752
+ const normalizedMigratedModel = normalizeConfiguredModel(modelForProvider);
2753
+ if (normalizedMigratedModel) {
2754
+ config.provider.model = normalizedMigratedModel;
2755
+ config.providerModels[oldPrefs.provider] = normalizedMigratedModel;
2725
2756
  } else {
2726
2757
  config.provider.model = getDefaultModel(oldPrefs.provider);
2758
+ config.providerModels[oldPrefs.provider] = config.provider.model;
2727
2759
  }
2728
2760
  await saveConfig(config, void 0, true);
2729
2761
  }
@@ -5474,6 +5506,11 @@ var init_codex = __esm({
5474
5506
  };
5475
5507
  }
5476
5508
  });
5509
+ function normalizeModel(model) {
5510
+ if (typeof model !== "string") return void 0;
5511
+ const trimmed = model.trim();
5512
+ return trimmed.length > 0 ? trimmed : void 0;
5513
+ }
5477
5514
  function createCopilotProvider() {
5478
5515
  return new CopilotProvider();
5479
5516
  }
@@ -5528,7 +5565,7 @@ var init_copilot2 = __esm({
5528
5565
  async initialize(config) {
5529
5566
  this.config = {
5530
5567
  ...config,
5531
- model: config.model ?? DEFAULT_MODEL4
5568
+ model: normalizeModel(config.model) ?? DEFAULT_MODEL4
5532
5569
  };
5533
5570
  const tokenResult = await getValidCopilotToken();
5534
5571
  if (tokenResult) {
@@ -6855,12 +6892,17 @@ __export(providers_exports, {
6855
6892
  listProviders: () => listProviders,
6856
6893
  withRetry: () => withRetry
6857
6894
  });
6895
+ function normalizeProviderModel(model) {
6896
+ if (typeof model !== "string") return void 0;
6897
+ const trimmed = model.trim();
6898
+ return trimmed.length > 0 ? trimmed : void 0;
6899
+ }
6858
6900
  async function createProvider(type, config = {}) {
6859
6901
  let provider;
6860
6902
  const mergedConfig = {
6861
6903
  apiKey: config.apiKey ?? getApiKey(type),
6862
6904
  baseUrl: config.baseUrl ?? getBaseUrl(type),
6863
- model: config.model ?? getDefaultModel(type),
6905
+ model: normalizeProviderModel(config.model) ?? getDefaultModel(type),
6864
6906
  maxTokens: config.maxTokens,
6865
6907
  temperature: config.temperature,
6866
6908
  timeout: config.timeout
@@ -9783,7 +9825,7 @@ function generateToolCatalog(registry) {
9783
9825
  }
9784
9826
  async function createDefaultReplConfig() {
9785
9827
  const providerType = await getLastUsedProvider();
9786
- const model = await getLastUsedModel(providerType) ?? getDefaultModel(providerType);
9828
+ const model = await getLastUsedModel(providerType) || getDefaultModel(providerType);
9787
9829
  return {
9788
9830
  provider: {
9789
9831
  type: providerType,
@@ -34598,8 +34640,9 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
34598
34640
  newApiKeyForSaving = key;
34599
34641
  }
34600
34642
  }
34643
+ const rememberedModel = await getLastUsedModel(newProvider.id);
34601
34644
  const recommendedModel = getRecommendedModel(newProvider.id);
34602
- const newModel = recommendedModel?.id || newProvider.models[0]?.id || "";
34645
+ const newModel = rememberedModel || recommendedModel?.id || newProvider.models[0]?.id || "";
34603
34646
  const spinner18 = p26.spinner();
34604
34647
  spinner18.start(`Connecting to ${newProvider.name}...`);
34605
34648
  try {
@@ -40566,9 +40609,20 @@ function hasNullByte2(str) {
40566
40609
  }
40567
40610
  function normalizePath2(filePath) {
40568
40611
  let normalized = filePath.replace(/\0/g, "");
40612
+ const home = process.env.HOME || process.env.USERPROFILE;
40613
+ if (home && normalized.startsWith("~")) {
40614
+ if (normalized === "~") {
40615
+ normalized = home;
40616
+ } else if (normalized.startsWith("~/") || normalized.startsWith(`~${path39__default.sep}`)) {
40617
+ normalized = path39__default.join(home, normalized.slice(2));
40618
+ }
40619
+ }
40569
40620
  normalized = path39__default.normalize(normalized);
40570
40621
  return normalized;
40571
40622
  }
40623
+ function resolveUserPath(filePath) {
40624
+ return path39__default.resolve(normalizePath2(filePath));
40625
+ }
40572
40626
  function isWithinDirectory(targetPath, baseDir) {
40573
40627
  const normalizedTarget = path39__default.normalize(targetPath);
40574
40628
  const normalizedBase = path39__default.normalize(baseDir);
@@ -40601,7 +40655,7 @@ function isPathAllowed(filePath, operation) {
40601
40655
  return { allowed: false, reason: "Path contains invalid characters" };
40602
40656
  }
40603
40657
  const normalized = normalizePath2(filePath);
40604
- const absolute = path39__default.resolve(normalized);
40658
+ const absolute = resolveUserPath(normalized);
40605
40659
  const cwd = process.cwd();
40606
40660
  for (const blocked of BLOCKED_PATHS2) {
40607
40661
  const normalizedBlocked = path39__default.normalize(blocked);
@@ -40660,7 +40714,7 @@ function isENOENT(error) {
40660
40714
  return error.code === "ENOENT";
40661
40715
  }
40662
40716
  async function enrichENOENT(filePath, operation) {
40663
- const absPath = path39__default.resolve(filePath);
40717
+ const absPath = resolveUserPath(filePath);
40664
40718
  const suggestions = await suggestSimilarFilesDeep(absPath, process.cwd());
40665
40719
  const hint = formatSuggestions(suggestions, path39__default.dirname(absPath));
40666
40720
  const action = operation === "read" ? "Use glob or list_dir to find the correct path." : "Check that the parent directory exists.";
@@ -40668,7 +40722,7 @@ async function enrichENOENT(filePath, operation) {
40668
40722
  ${action}`;
40669
40723
  }
40670
40724
  async function enrichDirENOENT(dirPath) {
40671
- const absPath = path39__default.resolve(dirPath);
40725
+ const absPath = resolveUserPath(dirPath);
40672
40726
  const suggestions = await suggestSimilarDirsDeep(absPath, process.cwd());
40673
40727
  const hint = formatSuggestions(suggestions, path39__default.dirname(absPath));
40674
40728
  return `Directory not found: ${dirPath}${hint}
@@ -40691,7 +40745,7 @@ Examples:
40691
40745
  async execute({ path: filePath, encoding, maxSize }) {
40692
40746
  validatePath(filePath, "read");
40693
40747
  try {
40694
- const absolutePath = path39__default.resolve(filePath);
40748
+ const absolutePath = resolveUserPath(filePath);
40695
40749
  const stats = await fs35__default.stat(absolutePath);
40696
40750
  const maxBytes = maxSize ?? DEFAULT_MAX_FILE_SIZE;
40697
40751
  let truncated = false;
@@ -40750,7 +40804,7 @@ Examples:
40750
40804
  async execute({ path: filePath, content, createDirs, dryRun }) {
40751
40805
  validatePath(filePath, "write");
40752
40806
  try {
40753
- const absolutePath = path39__default.resolve(filePath);
40807
+ const absolutePath = resolveUserPath(filePath);
40754
40808
  let wouldCreate = false;
40755
40809
  try {
40756
40810
  await fs35__default.access(absolutePath);
@@ -40812,7 +40866,7 @@ Examples:
40812
40866
  async execute({ path: filePath, oldText, newText, all, dryRun }) {
40813
40867
  validatePath(filePath, "write");
40814
40868
  try {
40815
- const absolutePath = path39__default.resolve(filePath);
40869
+ const absolutePath = resolveUserPath(filePath);
40816
40870
  let content = await fs35__default.readFile(absolutePath, "utf-8");
40817
40871
  let replacements = 0;
40818
40872
  if (all) {
@@ -40933,7 +40987,7 @@ Examples:
40933
40987
  }),
40934
40988
  async execute({ path: filePath }) {
40935
40989
  try {
40936
- const absolutePath = path39__default.resolve(filePath);
40990
+ const absolutePath = resolveUserPath(filePath);
40937
40991
  const stats = await fs35__default.stat(absolutePath);
40938
40992
  return {
40939
40993
  exists: true,
@@ -40964,7 +41018,7 @@ Examples:
40964
41018
  }),
40965
41019
  async execute({ path: dirPath, recursive }) {
40966
41020
  try {
40967
- const absolutePath = path39__default.resolve(dirPath);
41021
+ const absolutePath = resolveUserPath(dirPath);
40968
41022
  const entries = [];
40969
41023
  async function listDir(dir, prefix = "") {
40970
41024
  const items = await fs35__default.readdir(dir, { withFileTypes: true });
@@ -41024,7 +41078,7 @@ Examples:
41024
41078
  }
41025
41079
  validatePath(filePath, "delete");
41026
41080
  try {
41027
- const absolutePath = path39__default.resolve(filePath);
41081
+ const absolutePath = resolveUserPath(filePath);
41028
41082
  const stats = await fs35__default.stat(absolutePath);
41029
41083
  if (stats.isDirectory()) {
41030
41084
  if (!recursive) {
@@ -41040,7 +41094,7 @@ Examples:
41040
41094
  } catch (error) {
41041
41095
  if (error instanceof ToolError) throw error;
41042
41096
  if (error.code === "ENOENT") {
41043
- return { deleted: false, path: path39__default.resolve(filePath) };
41097
+ return { deleted: false, path: resolveUserPath(filePath) };
41044
41098
  }
41045
41099
  throw new FileSystemError(`Failed to delete: ${filePath}`, {
41046
41100
  path: filePath,
@@ -41068,8 +41122,8 @@ Examples:
41068
41122
  validatePath(source, "read");
41069
41123
  validatePath(destination, "write");
41070
41124
  try {
41071
- const srcPath = path39__default.resolve(source);
41072
- const destPath = path39__default.resolve(destination);
41125
+ const srcPath = resolveUserPath(source);
41126
+ const destPath = resolveUserPath(destination);
41073
41127
  if (!overwrite) {
41074
41128
  try {
41075
41129
  await fs35__default.access(destPath);
@@ -41129,8 +41183,8 @@ Examples:
41129
41183
  validatePath(source, "delete");
41130
41184
  validatePath(destination, "write");
41131
41185
  try {
41132
- const srcPath = path39__default.resolve(source);
41133
- const destPath = path39__default.resolve(destination);
41186
+ const srcPath = resolveUserPath(source);
41187
+ const destPath = resolveUserPath(destination);
41134
41188
  if (!overwrite) {
41135
41189
  try {
41136
41190
  await fs35__default.access(destPath);
@@ -41216,7 +41270,7 @@ Examples:
41216
41270
  }),
41217
41271
  async execute({ path: dirPath, depth, showHidden, dirsOnly }) {
41218
41272
  try {
41219
- const absolutePath = path39__default.resolve(dirPath ?? ".");
41273
+ const absolutePath = resolveUserPath(dirPath ?? ".");
41220
41274
  let totalFiles = 0;
41221
41275
  let totalDirs = 0;
41222
41276
  const lines = [path39__default.basename(absolutePath) + "/"];
@@ -49682,6 +49736,43 @@ function getAllCommands() {
49682
49736
 
49683
49737
  // src/cli/repl/input/handler.ts
49684
49738
  var HISTORY_FILE = path39.join(os4.homedir(), ".coco", "history");
49739
+ function navigateHistory(direction, state) {
49740
+ const { currentLine, historyIndex, sessionHistory, tempLine } = state;
49741
+ if (direction === "up") {
49742
+ if (sessionHistory.length === 0) return null;
49743
+ let nextIndex = historyIndex;
49744
+ let nextTempLine = tempLine;
49745
+ if (nextIndex === -1) {
49746
+ nextTempLine = currentLine;
49747
+ nextIndex = sessionHistory.length - 1;
49748
+ } else if (nextIndex > 0) {
49749
+ nextIndex--;
49750
+ }
49751
+ return {
49752
+ currentLine: sessionHistory[nextIndex] ?? "",
49753
+ cursorPos: 0,
49754
+ historyIndex: nextIndex,
49755
+ tempLine: nextTempLine
49756
+ };
49757
+ }
49758
+ if (historyIndex === -1) return null;
49759
+ if (historyIndex < sessionHistory.length - 1) {
49760
+ const nextIndex = historyIndex + 1;
49761
+ const nextLine = sessionHistory[nextIndex] ?? "";
49762
+ return {
49763
+ currentLine: nextLine,
49764
+ cursorPos: nextLine.length,
49765
+ historyIndex: nextIndex,
49766
+ tempLine
49767
+ };
49768
+ }
49769
+ return {
49770
+ currentLine: tempLine,
49771
+ cursorPos: tempLine.length,
49772
+ historyIndex: -1,
49773
+ tempLine
49774
+ };
49775
+ }
49685
49776
  async function handleOptionC(copyFn = copyToClipboard, getLastBlockFn = getLastBlock) {
49686
49777
  const block = getLastBlockFn();
49687
49778
  if (!block) return null;
@@ -50255,14 +50346,18 @@ function createInputHandler(_session) {
50255
50346
  cursorPos = 0;
50256
50347
  render();
50257
50348
  } else if (sessionHistory.length > 0) {
50258
- if (historyIndex === -1) {
50259
- tempLine = currentLine;
50260
- historyIndex = sessionHistory.length - 1;
50261
- } else if (historyIndex > 0) {
50262
- historyIndex--;
50349
+ const nextState = navigateHistory("up", {
50350
+ currentLine,
50351
+ historyIndex,
50352
+ sessionHistory,
50353
+ tempLine
50354
+ });
50355
+ if (nextState) {
50356
+ currentLine = nextState.currentLine;
50357
+ cursorPos = nextState.cursorPos;
50358
+ historyIndex = nextState.historyIndex;
50359
+ tempLine = nextState.tempLine;
50263
50360
  }
50264
- currentLine = sessionHistory[historyIndex] ?? "";
50265
- cursorPos = currentLine.length;
50266
50361
  render();
50267
50362
  }
50268
50363
  return;
@@ -50284,14 +50379,18 @@ function createInputHandler(_session) {
50284
50379
  cursorPos = currentLine.length;
50285
50380
  render();
50286
50381
  } else if (historyIndex !== -1) {
50287
- if (historyIndex < sessionHistory.length - 1) {
50288
- historyIndex++;
50289
- currentLine = sessionHistory[historyIndex] ?? "";
50290
- } else {
50291
- historyIndex = -1;
50292
- currentLine = tempLine;
50382
+ const nextState = navigateHistory("down", {
50383
+ currentLine,
50384
+ historyIndex,
50385
+ sessionHistory,
50386
+ tempLine
50387
+ });
50388
+ if (nextState) {
50389
+ currentLine = nextState.currentLine;
50390
+ cursorPos = nextState.cursorPos;
50391
+ historyIndex = nextState.historyIndex;
50392
+ tempLine = nextState.tempLine;
50293
50393
  }
50294
- cursorPos = currentLine.length;
50295
50394
  render();
50296
50395
  }
50297
50396
  return;
@@ -53002,6 +53101,8 @@ async function startRepl(options = {}) {
53002
53101
  );
53003
53102
  }
53004
53103
  let mcpManager = null;
53104
+ let configuredMcpServers = [];
53105
+ const registeredMcpServers = /* @__PURE__ */ new Set();
53005
53106
  const logger2 = (await Promise.resolve().then(() => (init_logger(), logger_exports))).getLogger();
53006
53107
  try {
53007
53108
  const { getMCPServerManager: getMCPServerManager2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
@@ -53018,6 +53119,7 @@ async function startRepl(options = {}) {
53018
53119
  cocoConfigServers.filter((s) => s.enabled !== false),
53019
53120
  projectServers.filter((s) => s.enabled !== false)
53020
53121
  );
53122
+ configuredMcpServers = enabledServers;
53021
53123
  if (enabledServers.length > 0) {
53022
53124
  mcpManager = getMCPServerManager2();
53023
53125
  let connections;
@@ -53037,6 +53139,7 @@ async function startRepl(options = {}) {
53037
53139
  for (const connection of connections.values()) {
53038
53140
  try {
53039
53141
  const wrapped = await registerMCPTools2(toolRegistry, connection.name, connection.client);
53142
+ registeredMcpServers.add(connection.name);
53040
53143
  if (wrapped.length === 0) {
53041
53144
  logger2.warn(
53042
53145
  `[MCP] Server '${connection.name}' connected but exposed 0 tools (check server auth/scopes).`
@@ -53065,6 +53168,42 @@ async function startRepl(options = {}) {
53065
53168
  `[MCP] Initialization failed: ${mcpError instanceof Error ? mcpError.message : String(mcpError)}`
53066
53169
  );
53067
53170
  }
53171
+ async function ensureRequestedMcpConnections(message) {
53172
+ if (!mcpManager || configuredMcpServers.length === 0) return;
53173
+ const normalizedMessage = message.toLowerCase();
53174
+ const explicitlyRequestsMcp = /\bmcp\b/.test(normalizedMessage) || /\b(use|using|usa|usar|utiliza|utilizar)\b.{0,24}\bmcp\b/.test(normalizedMessage);
53175
+ const matchingServers = configuredMcpServers.filter((server) => {
53176
+ if (mcpManager?.getConnection(server.name)) return false;
53177
+ if (explicitlyRequestsMcp) return true;
53178
+ const loweredName = server.name.toLowerCase();
53179
+ if (normalizedMessage.includes(loweredName)) return true;
53180
+ if (loweredName.includes("atlassian")) {
53181
+ return /\b(atlassian|jira|confluence)\b/.test(normalizedMessage);
53182
+ }
53183
+ return false;
53184
+ });
53185
+ for (const server of matchingServers) {
53186
+ try {
53187
+ const connection = await mcpManager.startServer(server);
53188
+ if (!registeredMcpServers.has(connection.name)) {
53189
+ await (await Promise.resolve().then(() => (init_tools(), tools_exports))).registerMCPTools(
53190
+ toolRegistry,
53191
+ connection.name,
53192
+ connection.client
53193
+ );
53194
+ registeredMcpServers.add(connection.name);
53195
+ }
53196
+ } catch (error) {
53197
+ logger2.warn(
53198
+ `[MCP] On-demand connect failed for '${server.name}': ${error instanceof Error ? error.message : String(error)}`
53199
+ );
53200
+ }
53201
+ }
53202
+ }
53203
+ function extractMessageText(content) {
53204
+ if (typeof content === "string") return content;
53205
+ return content.filter((block) => block.type === "text").map((block) => block.text).join(" ");
53206
+ }
53068
53207
  let hookRegistry;
53069
53208
  let hookExecutor;
53070
53209
  try {
@@ -53512,6 +53651,7 @@ ${imagePrompts}`.trim() : imagePrompts;
53512
53651
  let streamStarted = false;
53513
53652
  let llmCallCount = 0;
53514
53653
  let lastToolGroup = null;
53654
+ await ensureRequestedMcpConnections(extractMessageText(effectiveMessage));
53515
53655
  const result = await executeAgentTurn(session, effectiveMessage, provider, toolRegistry, {
53516
53656
  onStream: (chunk) => {
53517
53657
  if (!streamStarted) {