@corbat-tech/coco 2.27.0 → 2.27.2
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 +567 -394
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +26 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2324,13 +2324,20 @@ async function isADCConfigured() {
|
|
|
2324
2324
|
}
|
|
2325
2325
|
async function runGcloudADCLogin() {
|
|
2326
2326
|
try {
|
|
2327
|
-
await execAsync(
|
|
2328
|
-
timeout:
|
|
2329
|
-
//
|
|
2327
|
+
await execAsync(ADC_LOGIN_COMMAND, {
|
|
2328
|
+
timeout: 3e5
|
|
2329
|
+
// 5 minutes for interactive auth
|
|
2330
2330
|
});
|
|
2331
2331
|
return true;
|
|
2332
2332
|
} catch {
|
|
2333
|
-
|
|
2333
|
+
try {
|
|
2334
|
+
await execAsync(`${ADC_LOGIN_COMMAND} --no-launch-browser`, {
|
|
2335
|
+
timeout: 3e5
|
|
2336
|
+
});
|
|
2337
|
+
return true;
|
|
2338
|
+
} catch {
|
|
2339
|
+
return false;
|
|
2340
|
+
}
|
|
2334
2341
|
}
|
|
2335
2342
|
}
|
|
2336
2343
|
async function getGeminiADCKey() {
|
|
@@ -2504,7 +2511,7 @@ function getApiKey(provider) {
|
|
|
2504
2511
|
case "gemini":
|
|
2505
2512
|
return process.env["GEMINI_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
|
|
2506
2513
|
case "vertex":
|
|
2507
|
-
return
|
|
2514
|
+
return process.env["VERTEX_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
|
|
2508
2515
|
case "kimi":
|
|
2509
2516
|
return process.env["KIMI_API_KEY"] ?? process.env["MOONSHOT_API_KEY"];
|
|
2510
2517
|
case "kimi-code":
|
|
@@ -6117,21 +6124,26 @@ var init_vertex = __esm({
|
|
|
6117
6124
|
config = {};
|
|
6118
6125
|
project = "";
|
|
6119
6126
|
location = DEFAULT_LOCATION;
|
|
6127
|
+
apiKey;
|
|
6120
6128
|
retryConfig = DEFAULT_RETRY_CONFIG;
|
|
6121
6129
|
async initialize(config) {
|
|
6122
6130
|
this.config = config;
|
|
6123
6131
|
this.project = config.project ?? process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
|
|
6124
6132
|
this.location = config.location ?? process.env["VERTEX_LOCATION"] ?? process.env["GOOGLE_CLOUD_LOCATION"] ?? DEFAULT_LOCATION;
|
|
6133
|
+
this.apiKey = config.apiKey ?? process.env["VERTEX_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
|
|
6125
6134
|
if (!this.project.trim()) {
|
|
6126
6135
|
throw new ProviderError(
|
|
6127
6136
|
"Vertex AI project not configured. Set provider.project, VERTEX_PROJECT, or GOOGLE_CLOUD_PROJECT.",
|
|
6128
6137
|
{ provider: this.id }
|
|
6129
6138
|
);
|
|
6130
6139
|
}
|
|
6140
|
+
if (this.apiKey?.trim()) {
|
|
6141
|
+
return;
|
|
6142
|
+
}
|
|
6131
6143
|
const token = await getCachedADCToken();
|
|
6132
6144
|
if (!token) {
|
|
6133
6145
|
throw new ProviderError(
|
|
6134
|
-
"Vertex AI
|
|
6146
|
+
"Vertex AI authentication is not configured. Set VERTEX_API_KEY (or GOOGLE_API_KEY), or run `gcloud auth application-default login`.",
|
|
6135
6147
|
{ provider: this.id }
|
|
6136
6148
|
);
|
|
6137
6149
|
}
|
|
@@ -6250,10 +6262,17 @@ var init_vertex = __esm({
|
|
|
6250
6262
|
return `${this.getResolvedBaseUrl()}/projects/${encodeURIComponent(this.project)}/locations/${encodeURIComponent(this.location)}/publishers/google/models/${encodeURIComponent(this.getModel(model))}:${action}`;
|
|
6251
6263
|
}
|
|
6252
6264
|
async getHeaders() {
|
|
6265
|
+
if (this.apiKey?.trim()) {
|
|
6266
|
+
return {
|
|
6267
|
+
"Content-Type": "application/json",
|
|
6268
|
+
"x-goog-api-key": this.apiKey,
|
|
6269
|
+
"x-goog-user-project": this.project
|
|
6270
|
+
};
|
|
6271
|
+
}
|
|
6253
6272
|
const token = await getCachedADCToken();
|
|
6254
6273
|
if (!token) {
|
|
6255
6274
|
throw new ProviderError(
|
|
6256
|
-
"Vertex AI
|
|
6275
|
+
"Vertex AI token is unavailable. Re-authenticate with gcloud or configure VERTEX_API_KEY.",
|
|
6257
6276
|
{ provider: this.id }
|
|
6258
6277
|
);
|
|
6259
6278
|
}
|
|
@@ -20329,11 +20348,11 @@ async function runMergeRelease(ctx) {
|
|
|
20329
20348
|
}
|
|
20330
20349
|
const tagName = ctx.newVersion ? `v${ctx.newVersion}` : void 0;
|
|
20331
20350
|
const mergeMsg = tagName ? `Merge PR #${ctx.prNumber} to ${ctx.profile.defaultBranch} and create release ${tagName}?` : `Merge PR #${ctx.prNumber} to ${ctx.profile.defaultBranch}?`;
|
|
20332
|
-
const
|
|
20351
|
+
const confirm23 = await p26.confirm({
|
|
20333
20352
|
message: mergeMsg,
|
|
20334
20353
|
initialValue: true
|
|
20335
20354
|
});
|
|
20336
|
-
if (p26.isCancel(
|
|
20355
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
20337
20356
|
return {
|
|
20338
20357
|
step: "merge-release",
|
|
20339
20358
|
status: "skipped",
|
|
@@ -22508,9 +22527,10 @@ var init_lifecycle = __esm({
|
|
|
22508
22527
|
init_errors2();
|
|
22509
22528
|
init_logger();
|
|
22510
22529
|
init_version();
|
|
22511
|
-
MCPServerManager = class {
|
|
22530
|
+
MCPServerManager = class _MCPServerManager {
|
|
22512
22531
|
connections = /* @__PURE__ */ new Map();
|
|
22513
22532
|
logger = getLogger();
|
|
22533
|
+
static STOP_TIMEOUT_MS = 5e3;
|
|
22514
22534
|
/**
|
|
22515
22535
|
* Create transport for a server config
|
|
22516
22536
|
*/
|
|
@@ -22597,7 +22617,15 @@ var init_lifecycle = __esm({
|
|
|
22597
22617
|
}
|
|
22598
22618
|
this.logger.info(`Stopping MCP server: ${name}`);
|
|
22599
22619
|
try {
|
|
22600
|
-
await
|
|
22620
|
+
await Promise.race([
|
|
22621
|
+
connection.transport.disconnect(),
|
|
22622
|
+
new Promise(
|
|
22623
|
+
(_, reject) => setTimeout(
|
|
22624
|
+
() => reject(new Error("MCP disconnect timeout")),
|
|
22625
|
+
_MCPServerManager.STOP_TIMEOUT_MS
|
|
22626
|
+
)
|
|
22627
|
+
)
|
|
22628
|
+
]);
|
|
22601
22629
|
} catch (error) {
|
|
22602
22630
|
this.logger.error(
|
|
22603
22631
|
`Error disconnecting server '${name}': ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -29186,13 +29214,14 @@ var PROVIDER_DEFINITIONS = {
|
|
|
29186
29214
|
functionCalling: true,
|
|
29187
29215
|
vision: true
|
|
29188
29216
|
},
|
|
29189
|
-
// Updated:
|
|
29217
|
+
// Updated: April 2026 — from docs.github.com/en/copilot/reference/ai-models/supported-models
|
|
29218
|
+
// Premium request multipliers in descriptions are for paid Copilot plans.
|
|
29190
29219
|
models: [
|
|
29191
29220
|
// Anthropic models
|
|
29192
29221
|
{
|
|
29193
29222
|
id: "claude-sonnet-4.6",
|
|
29194
29223
|
name: "Claude Sonnet 4.6",
|
|
29195
|
-
description: "
|
|
29224
|
+
description: "Balanced Claude model via Copilot \u2014 Premium x1",
|
|
29196
29225
|
contextWindow: 2e5,
|
|
29197
29226
|
maxOutputTokens: 64e3,
|
|
29198
29227
|
recommended: true
|
|
@@ -29200,28 +29229,28 @@ var PROVIDER_DEFINITIONS = {
|
|
|
29200
29229
|
{
|
|
29201
29230
|
id: "claude-opus-4.6",
|
|
29202
29231
|
name: "Claude Opus 4.6",
|
|
29203
|
-
description: "
|
|
29232
|
+
description: "Most capable Claude model via Copilot \u2014 Premium x3",
|
|
29204
29233
|
contextWindow: 2e5,
|
|
29205
29234
|
maxOutputTokens: 128e3
|
|
29206
29235
|
},
|
|
29207
29236
|
{
|
|
29208
29237
|
id: "claude-sonnet-4.5",
|
|
29209
29238
|
name: "Claude Sonnet 4.5",
|
|
29210
|
-
description: "Previous balanced Claude model via Copilot",
|
|
29239
|
+
description: "Previous balanced Claude model via Copilot \u2014 Premium x1",
|
|
29211
29240
|
contextWindow: 2e5,
|
|
29212
29241
|
maxOutputTokens: 64e3
|
|
29213
29242
|
},
|
|
29214
29243
|
{
|
|
29215
29244
|
id: "claude-opus-4.5",
|
|
29216
29245
|
name: "Claude Opus 4.5",
|
|
29217
|
-
description: "Previous flagship Claude model via Copilot",
|
|
29246
|
+
description: "Previous flagship Claude model via Copilot \u2014 Premium x3",
|
|
29218
29247
|
contextWindow: 2e5,
|
|
29219
29248
|
maxOutputTokens: 64e3
|
|
29220
29249
|
},
|
|
29221
29250
|
{
|
|
29222
29251
|
id: "claude-haiku-4.5",
|
|
29223
29252
|
name: "Claude Haiku 4.5",
|
|
29224
|
-
description: "Fast
|
|
29253
|
+
description: "Fast low-cost Claude model via Copilot \u2014 Premium x0.33",
|
|
29225
29254
|
contextWindow: 2e5,
|
|
29226
29255
|
maxOutputTokens: 64e3
|
|
29227
29256
|
},
|
|
@@ -29229,7 +29258,7 @@ var PROVIDER_DEFINITIONS = {
|
|
|
29229
29258
|
{
|
|
29230
29259
|
id: "gpt-5.4-codex",
|
|
29231
29260
|
name: "GPT-5.4 Codex",
|
|
29232
|
-
description: "
|
|
29261
|
+
description: "Latest coding model via Copilot \u2014 Premium x1",
|
|
29233
29262
|
contextWindow: 4e5,
|
|
29234
29263
|
maxOutputTokens: 128e3,
|
|
29235
29264
|
recommended: true
|
|
@@ -29237,28 +29266,28 @@ var PROVIDER_DEFINITIONS = {
|
|
|
29237
29266
|
{
|
|
29238
29267
|
id: "gpt-5.3-codex",
|
|
29239
29268
|
name: "GPT-5.3 Codex",
|
|
29240
|
-
description: "
|
|
29269
|
+
description: "Previous coding model via Copilot \u2014 Premium x1",
|
|
29241
29270
|
contextWindow: 4e5,
|
|
29242
29271
|
maxOutputTokens: 128e3
|
|
29243
29272
|
},
|
|
29244
29273
|
{
|
|
29245
29274
|
id: "gpt-5.2-codex",
|
|
29246
29275
|
name: "GPT-5.2 Codex",
|
|
29247
|
-
description: "
|
|
29276
|
+
description: "Previous coding model via Copilot \u2014 Premium x1",
|
|
29248
29277
|
contextWindow: 4e5,
|
|
29249
29278
|
maxOutputTokens: 128e3
|
|
29250
29279
|
},
|
|
29251
29280
|
{
|
|
29252
29281
|
id: "gpt-5.1-codex-max",
|
|
29253
29282
|
name: "GPT-5.1 Codex Max",
|
|
29254
|
-
description: "Frontier agentic coding model via Copilot",
|
|
29283
|
+
description: "Frontier agentic coding model via Copilot \u2014 Premium x1",
|
|
29255
29284
|
contextWindow: 4e5,
|
|
29256
29285
|
maxOutputTokens: 128e3
|
|
29257
29286
|
},
|
|
29258
29287
|
{
|
|
29259
29288
|
id: "gpt-4.1",
|
|
29260
29289
|
name: "GPT-4.1",
|
|
29261
|
-
description: "OpenAI long-context model via Copilot (1M)",
|
|
29290
|
+
description: "OpenAI long-context model via Copilot (1M) \u2014 Premium x0",
|
|
29262
29291
|
contextWindow: 1048576,
|
|
29263
29292
|
maxOutputTokens: 32768
|
|
29264
29293
|
},
|
|
@@ -29266,21 +29295,21 @@ var PROVIDER_DEFINITIONS = {
|
|
|
29266
29295
|
{
|
|
29267
29296
|
id: "gemini-3.1-pro-preview",
|
|
29268
29297
|
name: "Gemini 3.1 Pro",
|
|
29269
|
-
description: "Google's latest model via Copilot (1M)",
|
|
29298
|
+
description: "Google's latest model via Copilot (1M) \u2014 Premium x1",
|
|
29270
29299
|
contextWindow: 1e6,
|
|
29271
29300
|
maxOutputTokens: 64e3
|
|
29272
29301
|
},
|
|
29273
29302
|
{
|
|
29274
29303
|
id: "gemini-3-flash-preview",
|
|
29275
29304
|
name: "Gemini 3 Flash",
|
|
29276
|
-
description: "Google's fast model via Copilot (1M)",
|
|
29305
|
+
description: "Google's fast model via Copilot (1M) \u2014 Premium x0.33",
|
|
29277
29306
|
contextWindow: 1e6,
|
|
29278
29307
|
maxOutputTokens: 64e3
|
|
29279
29308
|
},
|
|
29280
29309
|
{
|
|
29281
29310
|
id: "gemini-2.5-pro",
|
|
29282
29311
|
name: "Gemini 2.5 Pro",
|
|
29283
|
-
description: "Google stable model via Copilot (1M)",
|
|
29312
|
+
description: "Google stable model via Copilot (1M) \u2014 Premium x1",
|
|
29284
29313
|
contextWindow: 1048576,
|
|
29285
29314
|
maxOutputTokens: 65536
|
|
29286
29315
|
}
|
|
@@ -29419,8 +29448,8 @@ var PROVIDER_DEFINITIONS = {
|
|
|
29419
29448
|
name: "Google Vertex AI Gemini",
|
|
29420
29449
|
emoji: "\u2601\uFE0F",
|
|
29421
29450
|
description: "Gemini on Vertex AI with GCP project, IAM and ADC",
|
|
29422
|
-
envVar: "
|
|
29423
|
-
apiKeyUrl: "https://
|
|
29451
|
+
envVar: "VERTEX_API_KEY",
|
|
29452
|
+
apiKeyUrl: "https://cloud.google.com/vertex-ai/generative-ai/docs/start/api-keys",
|
|
29424
29453
|
docsUrl: "https://cloud.google.com/vertex-ai/generative-ai/docs/start/quickstart",
|
|
29425
29454
|
baseUrl: "https://aiplatform.googleapis.com/v1",
|
|
29426
29455
|
supportsCustomModels: true,
|
|
@@ -30098,7 +30127,7 @@ function getConfiguredProviders() {
|
|
|
30098
30127
|
return hasLocalProviderConfig(p45.id);
|
|
30099
30128
|
}
|
|
30100
30129
|
if (p45.id === "vertex") {
|
|
30101
|
-
return !!(process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"]);
|
|
30130
|
+
return !!(process.env["VERTEX_API_KEY"] ?? process.env["GOOGLE_API_KEY"] ?? process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"]);
|
|
30102
30131
|
}
|
|
30103
30132
|
return !!process.env[p45.envVar];
|
|
30104
30133
|
});
|
|
@@ -30451,11 +30480,11 @@ async function runRemoveServer(name, options) {
|
|
|
30451
30480
|
process.exit(1);
|
|
30452
30481
|
}
|
|
30453
30482
|
if (!options.yes) {
|
|
30454
|
-
const
|
|
30483
|
+
const confirm23 = await p26.confirm({
|
|
30455
30484
|
message: `Remove server '${name}'?`,
|
|
30456
30485
|
initialValue: false
|
|
30457
30486
|
});
|
|
30458
|
-
if (p26.isCancel(
|
|
30487
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
30459
30488
|
p26.outro("Cancelled");
|
|
30460
30489
|
return;
|
|
30461
30490
|
}
|
|
@@ -30727,10 +30756,10 @@ async function runRemove(name, options) {
|
|
|
30727
30756
|
return;
|
|
30728
30757
|
}
|
|
30729
30758
|
if (!options.yes) {
|
|
30730
|
-
const
|
|
30759
|
+
const confirm23 = await p26.confirm({
|
|
30731
30760
|
message: `Remove skill "${name}" from ${targetDir}?`
|
|
30732
30761
|
});
|
|
30733
|
-
if (p26.isCancel(
|
|
30762
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
30734
30763
|
p26.log.info("Cancelled.");
|
|
30735
30764
|
p26.outro("");
|
|
30736
30765
|
return;
|
|
@@ -33282,6 +33311,301 @@ function showToolsHelp() {
|
|
|
33282
33311
|
|
|
33283
33312
|
// src/cli/repl/commands/clear.ts
|
|
33284
33313
|
init_session();
|
|
33314
|
+
async function getGitContext(projectPath) {
|
|
33315
|
+
const controller = new AbortController();
|
|
33316
|
+
const timer = setTimeout(() => controller.abort(), 3e3);
|
|
33317
|
+
try {
|
|
33318
|
+
const git = simpleGit({ baseDir: projectPath, abort: controller.signal });
|
|
33319
|
+
const status = await git.status();
|
|
33320
|
+
clearTimeout(timer);
|
|
33321
|
+
return {
|
|
33322
|
+
branch: status.current ?? "HEAD",
|
|
33323
|
+
isDirty: !status.isClean(),
|
|
33324
|
+
staged: status.staged.length,
|
|
33325
|
+
modified: status.modified.length,
|
|
33326
|
+
untracked: status.not_added.length,
|
|
33327
|
+
ahead: status.ahead,
|
|
33328
|
+
behind: status.behind
|
|
33329
|
+
};
|
|
33330
|
+
} catch {
|
|
33331
|
+
clearTimeout(timer);
|
|
33332
|
+
return null;
|
|
33333
|
+
}
|
|
33334
|
+
}
|
|
33335
|
+
function formatGitLine(ctx) {
|
|
33336
|
+
const branchColor = ctx.isDirty ? chalk.yellow : chalk.green;
|
|
33337
|
+
const parts = [chalk.dim("\u{1F33F} ") + branchColor(ctx.branch)];
|
|
33338
|
+
const changes = [];
|
|
33339
|
+
if (ctx.staged > 0) changes.push(chalk.green(`+${ctx.staged}`));
|
|
33340
|
+
if (ctx.modified > 0) changes.push(chalk.yellow(`~${ctx.modified}`));
|
|
33341
|
+
if (ctx.untracked > 0) changes.push(chalk.dim(`?${ctx.untracked}`));
|
|
33342
|
+
if (ctx.ahead > 0) changes.push(chalk.cyan(`\u2191${ctx.ahead}`));
|
|
33343
|
+
if (ctx.behind > 0) changes.push(chalk.red(`\u2193${ctx.behind}`));
|
|
33344
|
+
if (changes.length > 0) parts.push(changes.join(" "));
|
|
33345
|
+
return parts.join(" \u2022 ");
|
|
33346
|
+
}
|
|
33347
|
+
function formatGitShort(ctx) {
|
|
33348
|
+
const branch = ctx.isDirty ? chalk.yellow(ctx.branch) : chalk.green(ctx.branch);
|
|
33349
|
+
const dirty = ctx.isDirty ? chalk.yellow(" \u25CF") : "";
|
|
33350
|
+
return chalk.dim("\u{1F33F} ") + branch + dirty;
|
|
33351
|
+
}
|
|
33352
|
+
|
|
33353
|
+
// src/cli/repl/startup-panel.ts
|
|
33354
|
+
init_version();
|
|
33355
|
+
init_trust_store();
|
|
33356
|
+
init_env();
|
|
33357
|
+
|
|
33358
|
+
// src/cli/repl/quality-loop.ts
|
|
33359
|
+
init_paths();
|
|
33360
|
+
var qualityLoopEnabled = true;
|
|
33361
|
+
var hintShown = false;
|
|
33362
|
+
function isQualityLoop() {
|
|
33363
|
+
return qualityLoopEnabled;
|
|
33364
|
+
}
|
|
33365
|
+
function setQualityLoop(enabled) {
|
|
33366
|
+
qualityLoopEnabled = enabled;
|
|
33367
|
+
}
|
|
33368
|
+
function wasHintShown() {
|
|
33369
|
+
return hintShown;
|
|
33370
|
+
}
|
|
33371
|
+
function markHintShown() {
|
|
33372
|
+
hintShown = true;
|
|
33373
|
+
}
|
|
33374
|
+
function looksLikeFeatureRequest(input) {
|
|
33375
|
+
const trimmed = input.trim();
|
|
33376
|
+
if (trimmed.length < 20) return false;
|
|
33377
|
+
if (trimmed.endsWith("?") && trimmed.length < 80) return false;
|
|
33378
|
+
const featureKeywords = [
|
|
33379
|
+
/\bimplement/i,
|
|
33380
|
+
/\bcreate\b/i,
|
|
33381
|
+
/\bbuild\b/i,
|
|
33382
|
+
/\badd\b.*\b(feature|function|component|endpoint|service|module|class)/i,
|
|
33383
|
+
/\brefactor/i,
|
|
33384
|
+
/\bmigrate/i,
|
|
33385
|
+
/\bsetup\b/i,
|
|
33386
|
+
/\bintegrate/i,
|
|
33387
|
+
/\bwrite\b.*\b(code|function|test|module)/i,
|
|
33388
|
+
/\bdevelop/i,
|
|
33389
|
+
/\bdesign\b/i,
|
|
33390
|
+
/\bfix\b.*\b(bug|issue|error|problem)/i,
|
|
33391
|
+
/\bupdate\b.*\b(function|component|service|module)/i,
|
|
33392
|
+
/\bgenerate\b/i,
|
|
33393
|
+
/\bconvert\b/i
|
|
33394
|
+
];
|
|
33395
|
+
return featureKeywords.some((re) => re.test(trimmed));
|
|
33396
|
+
}
|
|
33397
|
+
function formatQualityLoopHint() {
|
|
33398
|
+
return chalk.dim(" tip: ") + chalk.magenta("/quality") + chalk.dim(" enables auto-test & iterate until quality converges");
|
|
33399
|
+
}
|
|
33400
|
+
function formatQualityResult(result) {
|
|
33401
|
+
const lines = [];
|
|
33402
|
+
const scores = result.scoreHistory;
|
|
33403
|
+
const progressStr = scores.map((s) => String(s)).join(" \u2192 ");
|
|
33404
|
+
const convergedLabel = result.converged ? chalk.green("converged") : chalk.yellow("max iterations");
|
|
33405
|
+
lines.push("");
|
|
33406
|
+
lines.push(
|
|
33407
|
+
chalk.magenta("\u2500\u2500 Quality: ") + chalk.white(progressStr) + chalk.dim(` (${convergedLabel})`) + chalk.magenta(" \u2500\u2500")
|
|
33408
|
+
);
|
|
33409
|
+
const parts = [];
|
|
33410
|
+
if (result.testsPassed !== void 0 && result.testsTotal !== void 0) {
|
|
33411
|
+
const testsColor = result.testsPassed === result.testsTotal ? chalk.green : chalk.yellow;
|
|
33412
|
+
parts.push(testsColor(`Tests: ${result.testsPassed}/${result.testsTotal}`));
|
|
33413
|
+
}
|
|
33414
|
+
if (result.coverage !== void 0) {
|
|
33415
|
+
const covColor = result.coverage >= 80 ? chalk.green : chalk.yellow;
|
|
33416
|
+
parts.push(covColor(`Coverage: ${result.coverage}%`));
|
|
33417
|
+
}
|
|
33418
|
+
if (result.securityScore !== void 0) {
|
|
33419
|
+
const secColor = result.securityScore === 100 ? chalk.green : chalk.red;
|
|
33420
|
+
parts.push(secColor(`Security: ${result.securityScore}`));
|
|
33421
|
+
}
|
|
33422
|
+
parts.push(chalk.dim(`Iterations: ${result.iterations}`));
|
|
33423
|
+
if (result.durationMs !== void 0) {
|
|
33424
|
+
const secs = (result.durationMs / 1e3).toFixed(1);
|
|
33425
|
+
parts.push(chalk.dim(`Time: ${secs}s`));
|
|
33426
|
+
}
|
|
33427
|
+
lines.push(" " + parts.join(" "));
|
|
33428
|
+
lines.push("");
|
|
33429
|
+
return lines.join("\n");
|
|
33430
|
+
}
|
|
33431
|
+
async function loadQualityLoopPreference() {
|
|
33432
|
+
try {
|
|
33433
|
+
const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
|
|
33434
|
+
const config = JSON.parse(content);
|
|
33435
|
+
const value = config.qualityLoop ?? config.cocoMode;
|
|
33436
|
+
if (typeof value === "boolean") {
|
|
33437
|
+
qualityLoopEnabled = value;
|
|
33438
|
+
return value;
|
|
33439
|
+
}
|
|
33440
|
+
} catch {
|
|
33441
|
+
}
|
|
33442
|
+
return true;
|
|
33443
|
+
}
|
|
33444
|
+
async function saveQualityLoopPreference(enabled) {
|
|
33445
|
+
try {
|
|
33446
|
+
let config = {};
|
|
33447
|
+
try {
|
|
33448
|
+
const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
|
|
33449
|
+
config = JSON.parse(content);
|
|
33450
|
+
} catch {
|
|
33451
|
+
}
|
|
33452
|
+
config.qualityLoop = enabled;
|
|
33453
|
+
await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2) + "\n");
|
|
33454
|
+
} catch {
|
|
33455
|
+
}
|
|
33456
|
+
}
|
|
33457
|
+
function parseQualityLoopReport(content) {
|
|
33458
|
+
const marker = "QUALITY_LOOP_REPORT";
|
|
33459
|
+
const idx = content.indexOf(marker);
|
|
33460
|
+
if (idx === -1) return null;
|
|
33461
|
+
const block = content.slice(idx);
|
|
33462
|
+
const getField = (name) => {
|
|
33463
|
+
const match = block.match(new RegExp(`${name}:\\s*(.+)`));
|
|
33464
|
+
return match?.[1]?.trim();
|
|
33465
|
+
};
|
|
33466
|
+
const scoreHistoryRaw = getField("score_history");
|
|
33467
|
+
if (!scoreHistoryRaw) return null;
|
|
33468
|
+
const scores = scoreHistoryRaw.replace(/[[\]]/g, "").split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
|
|
33469
|
+
if (scores.length === 0) return null;
|
|
33470
|
+
const testsPassed = parseInt(getField("tests_passed") ?? "", 10);
|
|
33471
|
+
const testsTotal = parseInt(getField("tests_total") ?? "", 10);
|
|
33472
|
+
const coverage = parseInt(getField("coverage") ?? "", 10);
|
|
33473
|
+
const security = parseInt(getField("security") ?? "", 10);
|
|
33474
|
+
const iterations = parseInt(getField("iterations") ?? "", 10) || scores.length;
|
|
33475
|
+
const converged = getField("converged") === "true";
|
|
33476
|
+
return {
|
|
33477
|
+
converged,
|
|
33478
|
+
scoreHistory: scores,
|
|
33479
|
+
finalScore: scores[scores.length - 1] ?? 0,
|
|
33480
|
+
iterations,
|
|
33481
|
+
testsPassed: isNaN(testsPassed) ? void 0 : testsPassed,
|
|
33482
|
+
testsTotal: isNaN(testsTotal) ? void 0 : testsTotal,
|
|
33483
|
+
coverage: isNaN(coverage) ? void 0 : coverage,
|
|
33484
|
+
securityScore: isNaN(security) ? void 0 : security
|
|
33485
|
+
};
|
|
33486
|
+
}
|
|
33487
|
+
function getQualityLoopSystemPrompt() {
|
|
33488
|
+
return `
|
|
33489
|
+
## Quality Loop Mode (ACTIVE)
|
|
33490
|
+
|
|
33491
|
+
You are operating in quality loop mode. After implementing code changes, you MUST follow this iteration cycle:
|
|
33492
|
+
|
|
33493
|
+
1. **Implement** the requested changes (code + tests)
|
|
33494
|
+
2. **Run tests** using the run_tests or bash_exec tool
|
|
33495
|
+
3. **Self-review**: Analyze your code against these 12 quality dimensions:
|
|
33496
|
+
- Correctness, Completeness, Robustness, Readability
|
|
33497
|
+
- Maintainability, Complexity, Duplication, Test Coverage
|
|
33498
|
+
- Test Quality, Security, Documentation, Style
|
|
33499
|
+
4. **Score** your implementation 0-100 for each dimension
|
|
33500
|
+
5. **If issues found**: Fix them and go back to step 2
|
|
33501
|
+
6. **If quality is good** (overall \u2265 85 and improving < 2 points): Stop and report
|
|
33502
|
+
|
|
33503
|
+
After completing the cycle, output a quality summary in this exact format:
|
|
33504
|
+
|
|
33505
|
+
\`\`\`
|
|
33506
|
+
QUALITY_LOOP_REPORT
|
|
33507
|
+
score_history: [first_score, ..., final_score]
|
|
33508
|
+
tests_passed: X
|
|
33509
|
+
tests_total: Y
|
|
33510
|
+
coverage: Z
|
|
33511
|
+
security: 100
|
|
33512
|
+
iterations: N
|
|
33513
|
+
converged: true|false
|
|
33514
|
+
\`\`\`
|
|
33515
|
+
|
|
33516
|
+
Key rules:
|
|
33517
|
+
- Always write tests alongside code
|
|
33518
|
+
- Run tests after every change
|
|
33519
|
+
- Minimum 2 iterations before declaring convergence
|
|
33520
|
+
- Maximum 10 iterations
|
|
33521
|
+
- Fix critical issues before moving on
|
|
33522
|
+
- Report honestly - don't inflate scores`;
|
|
33523
|
+
}
|
|
33524
|
+
|
|
33525
|
+
// src/cli/repl/startup-panel.ts
|
|
33526
|
+
async function renderStartupPanel(session, gitCtx, mcpServers = []) {
|
|
33527
|
+
const trustStore = createTrustStore();
|
|
33528
|
+
await trustStore.init();
|
|
33529
|
+
const trustLevel = trustStore.getLevel(session.projectPath);
|
|
33530
|
+
const boxWidth = 41;
|
|
33531
|
+
const innerWidth = boxWidth - 2;
|
|
33532
|
+
const versionText = `v${VERSION}`;
|
|
33533
|
+
const subtitleText = "open source \u2022 corbat.tech";
|
|
33534
|
+
const boxLine = (content) => {
|
|
33535
|
+
const pad = Math.max(0, innerWidth - stringWidth2(content));
|
|
33536
|
+
return chalk.magenta("\u2502") + content + " ".repeat(pad) + chalk.magenta("\u2502");
|
|
33537
|
+
};
|
|
33538
|
+
const titleLeftRaw = " COCO";
|
|
33539
|
+
const titleRightRaw = versionText + " ";
|
|
33540
|
+
const titleLeftStyled = " " + chalk.bold.white("COCO");
|
|
33541
|
+
const titleGap = Math.max(1, innerWidth - stringWidth2(titleLeftRaw) - stringWidth2(titleRightRaw));
|
|
33542
|
+
const titleContent = titleLeftStyled + " ".repeat(titleGap) + chalk.dim(titleRightRaw);
|
|
33543
|
+
const taglineText = "code that converges to quality";
|
|
33544
|
+
const taglineContent = " " + chalk.magenta(taglineText) + " ";
|
|
33545
|
+
const subtitleContent = " " + chalk.dim(subtitleText) + " ";
|
|
33546
|
+
console.log();
|
|
33547
|
+
console.log(chalk.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
|
|
33548
|
+
console.log(" " + boxLine(titleContent));
|
|
33549
|
+
console.log(" " + boxLine(taglineContent));
|
|
33550
|
+
console.log(" " + boxLine(subtitleContent));
|
|
33551
|
+
console.log(chalk.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
|
|
33552
|
+
const maxPathLen = 50;
|
|
33553
|
+
let displayPath = session.projectPath;
|
|
33554
|
+
if (displayPath.length > maxPathLen) {
|
|
33555
|
+
displayPath = "..." + displayPath.slice(-maxPathLen + 3);
|
|
33556
|
+
}
|
|
33557
|
+
const lastSep = displayPath.lastIndexOf("/");
|
|
33558
|
+
const parentPath = lastSep > 0 ? displayPath.slice(0, lastSep + 1) : "";
|
|
33559
|
+
const projectName = lastSep > 0 ? displayPath.slice(lastSep + 1) : displayPath;
|
|
33560
|
+
const providerName = session.config.provider.type;
|
|
33561
|
+
const configuredModel = session.config.provider.model?.trim();
|
|
33562
|
+
const modelName = configuredModel && !["default", "none", "null", "undefined"].includes(configuredModel.toLowerCase()) ? configuredModel : getDefaultModel(session.config.provider.type);
|
|
33563
|
+
const trustText = trustLevel === "full" ? "full" : trustLevel === "write" ? "write" : trustLevel === "read" ? "read" : "";
|
|
33564
|
+
console.log();
|
|
33565
|
+
console.log(chalk.dim(` \u{1F4C1} ${parentPath}`) + chalk.magenta.bold(projectName));
|
|
33566
|
+
console.log(
|
|
33567
|
+
chalk.dim(` \u{1F916} ${providerName}/`) + chalk.magenta(modelName) + (trustText ? chalk.dim(` \u2022 \u{1F510} ${trustText}`) : "")
|
|
33568
|
+
);
|
|
33569
|
+
if (gitCtx) {
|
|
33570
|
+
console.log(` ${formatGitLine(gitCtx)}`);
|
|
33571
|
+
}
|
|
33572
|
+
const cocoStatus = isQualityLoop() ? chalk.magenta(" \u{1F504} quality mode: ") + chalk.green.bold("on") + chalk.dim(" \u2014 iterates until quality \u2265 85. /quality to disable") : chalk.dim(" \u{1F4A1} /quality on \u2014 enable auto-test & quality iteration");
|
|
33573
|
+
console.log(cocoStatus);
|
|
33574
|
+
const skillTotal = session.skillRegistry?.size ?? 0;
|
|
33575
|
+
const hasSomething = skillTotal > 0 || mcpServers.length > 0;
|
|
33576
|
+
if (hasSomething) {
|
|
33577
|
+
if (skillTotal > 0) {
|
|
33578
|
+
const allMeta = session.skillRegistry.getAllMetadata();
|
|
33579
|
+
const builtinCount = allMeta.filter((s) => s.scope === "builtin").length;
|
|
33580
|
+
const projectCount = skillTotal - builtinCount;
|
|
33581
|
+
const parts = [];
|
|
33582
|
+
if (builtinCount > 0) parts.push(`${builtinCount} builtin`);
|
|
33583
|
+
if (projectCount > 0) parts.push(`${projectCount} project`);
|
|
33584
|
+
const detail = parts.length > 0 ? ` (${parts.join(" \xB7 ")})` : "";
|
|
33585
|
+
console.log(chalk.green(" \u2713") + chalk.dim(` Skills: ${skillTotal} loaded${detail}`));
|
|
33586
|
+
} else {
|
|
33587
|
+
console.log(chalk.dim(" \xB7 Skills: none loaded"));
|
|
33588
|
+
}
|
|
33589
|
+
if (mcpServers.length > 0) {
|
|
33590
|
+
const names = mcpServers.join(", ");
|
|
33591
|
+
console.log(
|
|
33592
|
+
chalk.green(" \u2713") + chalk.dim(
|
|
33593
|
+
` MCP: ${names} (${mcpServers.length} server${mcpServers.length === 1 ? "" : "s"} active)`
|
|
33594
|
+
)
|
|
33595
|
+
);
|
|
33596
|
+
}
|
|
33597
|
+
}
|
|
33598
|
+
console.log();
|
|
33599
|
+
console.log(
|
|
33600
|
+
chalk.dim(" Type your request or ") + chalk.magenta("/help") + chalk.dim(" for commands")
|
|
33601
|
+
);
|
|
33602
|
+
const pasteHint = process.platform === "darwin" ? chalk.dim(" \u{1F4CB} \u2318V paste text \u2022 \u2303V paste image") : chalk.dim(" \u{1F4CB} Ctrl+V paste image from clipboard");
|
|
33603
|
+
console.log(pasteHint);
|
|
33604
|
+
console.log();
|
|
33605
|
+
}
|
|
33606
|
+
|
|
33607
|
+
// src/cli/repl/commands/clear.ts
|
|
33608
|
+
init_env();
|
|
33285
33609
|
var clearCommand = {
|
|
33286
33610
|
name: "clear",
|
|
33287
33611
|
aliases: ["c"],
|
|
@@ -33289,7 +33613,22 @@ var clearCommand = {
|
|
|
33289
33613
|
usage: "/clear",
|
|
33290
33614
|
async execute(_args, session) {
|
|
33291
33615
|
clearSession(session);
|
|
33292
|
-
|
|
33616
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
33617
|
+
const projectPath = session.projectPath || process.cwd();
|
|
33618
|
+
const gitCtx = await getGitContext(projectPath);
|
|
33619
|
+
const panelSession = {
|
|
33620
|
+
...session,
|
|
33621
|
+
projectPath,
|
|
33622
|
+
config: session.config ?? {
|
|
33623
|
+
provider: {
|
|
33624
|
+
type: "anthropic",
|
|
33625
|
+
model: getDefaultModel("anthropic"),
|
|
33626
|
+
maxTokens: 8192
|
|
33627
|
+
}
|
|
33628
|
+
}
|
|
33629
|
+
};
|
|
33630
|
+
await renderStartupPanel(panelSession, gitCtx);
|
|
33631
|
+
console.log(chalk.dim("Context cleared.\n"));
|
|
33293
33632
|
return false;
|
|
33294
33633
|
}
|
|
33295
33634
|
};
|
|
@@ -33763,6 +34102,12 @@ async function setupProviderWithAuth(provider) {
|
|
|
33763
34102
|
showProviderInfo(provider);
|
|
33764
34103
|
const apiKey = await requestApiKey(provider);
|
|
33765
34104
|
if (!apiKey) return null;
|
|
34105
|
+
let vertexSettings;
|
|
34106
|
+
if (provider.id === "vertex") {
|
|
34107
|
+
const settings = await promptVertexSettings();
|
|
34108
|
+
if (!settings) return null;
|
|
34109
|
+
vertexSettings = settings;
|
|
34110
|
+
}
|
|
33766
34111
|
let baseUrl;
|
|
33767
34112
|
if (provider.askForCustomUrl) {
|
|
33768
34113
|
const wantsCustomUrl = await p26.confirm({
|
|
@@ -33786,7 +34131,7 @@ async function setupProviderWithAuth(provider) {
|
|
|
33786
34131
|
}
|
|
33787
34132
|
const model = await selectModel(provider);
|
|
33788
34133
|
if (!model) return null;
|
|
33789
|
-
const valid = await testConnection(provider, apiKey, model, baseUrl);
|
|
34134
|
+
const valid = await testConnection(provider, apiKey, model, baseUrl, vertexSettings);
|
|
33790
34135
|
if (!valid) {
|
|
33791
34136
|
const retry = await p26.confirm({
|
|
33792
34137
|
message: "Would you like to try again?",
|
|
@@ -33801,7 +34146,9 @@ async function setupProviderWithAuth(provider) {
|
|
|
33801
34146
|
type: provider.id,
|
|
33802
34147
|
model,
|
|
33803
34148
|
apiKey,
|
|
33804
|
-
baseUrl
|
|
34149
|
+
baseUrl,
|
|
34150
|
+
project: vertexSettings?.project,
|
|
34151
|
+
location: vertexSettings?.location
|
|
33805
34152
|
};
|
|
33806
34153
|
}
|
|
33807
34154
|
async function setupGcloudADC(provider) {
|
|
@@ -33817,11 +34164,6 @@ async function setupGcloudADC(provider) {
|
|
|
33817
34164
|
p26.log.error("gcloud CLI is not installed");
|
|
33818
34165
|
console.log(chalk.dim(" Install it from: https://cloud.google.com/sdk/docs/install"));
|
|
33819
34166
|
console.log();
|
|
33820
|
-
if (provider.id === "vertex") {
|
|
33821
|
-
console.log(chalk.dim(" Vertex AI requires gcloud ADC plus a Google Cloud project."));
|
|
33822
|
-
console.log();
|
|
33823
|
-
return null;
|
|
33824
|
-
}
|
|
33825
34167
|
const useFallback2 = await p26.confirm({
|
|
33826
34168
|
message: "Use API key instead?",
|
|
33827
34169
|
initialValue: true
|
|
@@ -33832,25 +34174,37 @@ async function setupGcloudADC(provider) {
|
|
|
33832
34174
|
if (!apiKey2) return null;
|
|
33833
34175
|
const model2 = await selectModel(provider);
|
|
33834
34176
|
if (!model2) return null;
|
|
33835
|
-
|
|
34177
|
+
let vertexSettings2;
|
|
34178
|
+
if (provider.id === "vertex") {
|
|
34179
|
+
const settings = await promptVertexSettings();
|
|
34180
|
+
if (!settings) return null;
|
|
34181
|
+
vertexSettings2 = settings;
|
|
34182
|
+
}
|
|
34183
|
+
const valid2 = await testConnection(provider, apiKey2, model2, void 0, vertexSettings2);
|
|
33836
34184
|
if (!valid2) return null;
|
|
33837
|
-
return {
|
|
34185
|
+
return {
|
|
34186
|
+
type: provider.id,
|
|
34187
|
+
model: model2,
|
|
34188
|
+
apiKey: apiKey2,
|
|
34189
|
+
project: vertexSettings2?.project,
|
|
34190
|
+
location: vertexSettings2?.location
|
|
34191
|
+
};
|
|
33838
34192
|
}
|
|
33839
|
-
|
|
34193
|
+
let adc = await inspectADC();
|
|
33840
34194
|
if (adc.status === "ok" && adc.token) {
|
|
33841
34195
|
console.log(chalk.green(" \u2713 gcloud ADC is already configured!"));
|
|
33842
34196
|
console.log();
|
|
33843
34197
|
p26.log.success("Authentication verified");
|
|
33844
|
-
const
|
|
33845
|
-
if (provider.id === "vertex" && !
|
|
34198
|
+
const vertexSettings2 = provider.id === "vertex" ? await promptVertexSettings() : void 0;
|
|
34199
|
+
if (provider.id === "vertex" && !vertexSettings2) return null;
|
|
33846
34200
|
const model2 = await selectModel(provider);
|
|
33847
34201
|
if (!model2) return null;
|
|
33848
34202
|
return {
|
|
33849
34203
|
type: provider.id,
|
|
33850
34204
|
model: model2,
|
|
33851
34205
|
apiKey: "__gcloud_adc__",
|
|
33852
|
-
project:
|
|
33853
|
-
location:
|
|
34206
|
+
project: vertexSettings2?.project,
|
|
34207
|
+
location: vertexSettings2?.location
|
|
33854
34208
|
};
|
|
33855
34209
|
}
|
|
33856
34210
|
console.log(chalk.yellow(" No reusable gcloud ADC session was found for Coco."));
|
|
@@ -33859,6 +34213,36 @@ async function setupGcloudADC(provider) {
|
|
|
33859
34213
|
console.log(chalk.dim(` ${adc.message}`));
|
|
33860
34214
|
console.log();
|
|
33861
34215
|
}
|
|
34216
|
+
const runLoginNow = await p26.confirm({
|
|
34217
|
+
message: "Authenticate with gcloud now from Coco?",
|
|
34218
|
+
initialValue: true
|
|
34219
|
+
});
|
|
34220
|
+
if (p26.isCancel(runLoginNow)) return null;
|
|
34221
|
+
if (runLoginNow) {
|
|
34222
|
+
p26.log.step("Running `gcloud auth application-default login`...");
|
|
34223
|
+
const loginOk = await runGcloudADCLogin();
|
|
34224
|
+
if (loginOk) {
|
|
34225
|
+
adc = await inspectADC();
|
|
34226
|
+
if (adc.status === "ok" && adc.token) {
|
|
34227
|
+
console.log(chalk.green(" \u2713 gcloud ADC is now configured."));
|
|
34228
|
+
console.log();
|
|
34229
|
+
p26.log.success("Authentication verified");
|
|
34230
|
+
const vertexSettings2 = provider.id === "vertex" ? await promptVertexSettings() : void 0;
|
|
34231
|
+
if (provider.id === "vertex" && !vertexSettings2) return null;
|
|
34232
|
+
const model2 = await selectModel(provider);
|
|
34233
|
+
if (!model2) return null;
|
|
34234
|
+
return {
|
|
34235
|
+
type: provider.id,
|
|
34236
|
+
model: model2,
|
|
34237
|
+
apiKey: "__gcloud_adc__",
|
|
34238
|
+
project: vertexSettings2?.project,
|
|
34239
|
+
location: vertexSettings2?.location
|
|
34240
|
+
};
|
|
34241
|
+
}
|
|
34242
|
+
}
|
|
34243
|
+
p26.log.error("Could not complete gcloud ADC login from Coco.");
|
|
34244
|
+
console.log();
|
|
34245
|
+
}
|
|
33862
34246
|
console.log(chalk.dim(" Check the current machine-wide ADC state with:"));
|
|
33863
34247
|
console.log(chalk.cyan(" $ gcloud auth application-default print-access-token"));
|
|
33864
34248
|
console.log();
|
|
@@ -33871,13 +34255,6 @@ async function setupGcloudADC(provider) {
|
|
|
33871
34255
|
}
|
|
33872
34256
|
console.log(chalk.dim(" Coco will reuse the login on the next attempt if ADC is valid."));
|
|
33873
34257
|
console.log();
|
|
33874
|
-
if (provider.id === "vertex") {
|
|
33875
|
-
console.log(
|
|
33876
|
-
chalk.dim(" Vertex AI does not use API keys in Coco. Configure ADC, then retry.")
|
|
33877
|
-
);
|
|
33878
|
-
console.log();
|
|
33879
|
-
return null;
|
|
33880
|
-
}
|
|
33881
34258
|
const useFallback = await p26.confirm({
|
|
33882
34259
|
message: "Use API key for now?",
|
|
33883
34260
|
initialValue: true
|
|
@@ -33888,9 +34265,21 @@ async function setupGcloudADC(provider) {
|
|
|
33888
34265
|
if (!apiKey) return null;
|
|
33889
34266
|
const model = await selectModel(provider);
|
|
33890
34267
|
if (!model) return null;
|
|
33891
|
-
|
|
34268
|
+
let vertexSettings;
|
|
34269
|
+
if (provider.id === "vertex") {
|
|
34270
|
+
const settings = await promptVertexSettings();
|
|
34271
|
+
if (!settings) return null;
|
|
34272
|
+
vertexSettings = settings;
|
|
34273
|
+
}
|
|
34274
|
+
const valid = await testConnection(provider, apiKey, model, void 0, vertexSettings);
|
|
33892
34275
|
if (!valid) return null;
|
|
33893
|
-
return {
|
|
34276
|
+
return {
|
|
34277
|
+
type: provider.id,
|
|
34278
|
+
model,
|
|
34279
|
+
apiKey,
|
|
34280
|
+
project: vertexSettings?.project,
|
|
34281
|
+
location: vertexSettings?.location
|
|
34282
|
+
};
|
|
33894
34283
|
}
|
|
33895
34284
|
async function promptVertexSettings() {
|
|
33896
34285
|
const projectDefault = process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
|
|
@@ -34424,8 +34813,8 @@ async function testConnection(provider, apiKey, model, baseUrl, vertexSettings)
|
|
|
34424
34813
|
process.env[`${provider.id.toUpperCase()}_BASE_URL`] = baseUrl;
|
|
34425
34814
|
}
|
|
34426
34815
|
if (provider.id === "vertex") {
|
|
34427
|
-
if (vertexSettings?.project) ;
|
|
34428
|
-
if (vertexSettings?.location) ;
|
|
34816
|
+
if (vertexSettings?.project) process.env["VERTEX_PROJECT"] = vertexSettings.project;
|
|
34817
|
+
if (vertexSettings?.location) process.env["VERTEX_LOCATION"] = vertexSettings.location;
|
|
34429
34818
|
}
|
|
34430
34819
|
const testProvider = await createProvider(provider.id, {
|
|
34431
34820
|
model,
|
|
@@ -34724,8 +35113,9 @@ async function ensureConfiguredV2(config) {
|
|
|
34724
35113
|
});
|
|
34725
35114
|
for (const prov of configuredProviders) {
|
|
34726
35115
|
try {
|
|
35116
|
+
const rememberedModel = await getLastUsedModel(prov.id);
|
|
34727
35117
|
const recommended = getRecommendedModel(prov.id);
|
|
34728
|
-
const model = recommended?.id || prov.models[0]?.id || "";
|
|
35118
|
+
const model = rememberedModel || recommended?.id || prov.models[0]?.id || "";
|
|
34729
35119
|
let providerId = prov.id;
|
|
34730
35120
|
if (prov.id === "openai" && hasOpenAIOAuthTokens && !process.env[prov.envVar]) {
|
|
34731
35121
|
const tokenResult = await getOrRefreshOAuthToken("openai");
|
|
@@ -34953,7 +35343,7 @@ async function switchProvider(initialProvider, session) {
|
|
|
34953
35343
|
`));
|
|
34954
35344
|
return false;
|
|
34955
35345
|
}
|
|
34956
|
-
const supportsApiKey = newProvider.
|
|
35346
|
+
const supportsApiKey = newProvider.requiresApiKey !== false;
|
|
34957
35347
|
const apiKey = supportsApiKey ? process.env[newProvider.envVar] : void 0;
|
|
34958
35348
|
const hasOAuth = supportsOAuth(newProvider.id) || newProvider.supportsOAuth;
|
|
34959
35349
|
const hasGcloudADC = newProvider.supportsGcloudADC;
|
|
@@ -35087,7 +35477,10 @@ Using existing OAuth session...`));
|
|
|
35087
35477
|
if (!adcResult) return false;
|
|
35088
35478
|
selectedAuthMethod = "gcloud";
|
|
35089
35479
|
if (newProvider.id === "vertex") {
|
|
35090
|
-
const settings = await promptVertexSettings2(
|
|
35480
|
+
const settings = await promptVertexSettings2({
|
|
35481
|
+
project: session.config.provider.project,
|
|
35482
|
+
location: session.config.provider.location
|
|
35483
|
+
});
|
|
35091
35484
|
if (!settings) return false;
|
|
35092
35485
|
vertexSettings = settings;
|
|
35093
35486
|
}
|
|
@@ -35108,6 +35501,14 @@ Using existing API key...`));
|
|
|
35108
35501
|
selectedAuthMethod = "apikey";
|
|
35109
35502
|
newApiKeyForSaving = key;
|
|
35110
35503
|
}
|
|
35504
|
+
if (newProvider.id === "vertex") {
|
|
35505
|
+
const settings = await promptVertexSettings2({
|
|
35506
|
+
project: session.config.provider.project,
|
|
35507
|
+
location: session.config.provider.location
|
|
35508
|
+
});
|
|
35509
|
+
if (!settings) return false;
|
|
35510
|
+
vertexSettings = settings;
|
|
35511
|
+
}
|
|
35111
35512
|
} else if (authChoice === "remove") {
|
|
35112
35513
|
const removeOptions = [];
|
|
35113
35514
|
if (oauthConnected) {
|
|
@@ -35164,7 +35565,10 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
|
|
|
35164
35565
|
if (!adcResult) return false;
|
|
35165
35566
|
selectedAuthMethod = "gcloud";
|
|
35166
35567
|
if (newProvider.id === "vertex") {
|
|
35167
|
-
const settings = await promptVertexSettings2(
|
|
35568
|
+
const settings = await promptVertexSettings2({
|
|
35569
|
+
project: session.config.provider.project,
|
|
35570
|
+
location: session.config.provider.location
|
|
35571
|
+
});
|
|
35168
35572
|
if (!settings) return false;
|
|
35169
35573
|
vertexSettings = settings;
|
|
35170
35574
|
}
|
|
@@ -35215,7 +35619,9 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
|
|
|
35215
35619
|
await saveConfiguration({
|
|
35216
35620
|
type: userFacingProviderId,
|
|
35217
35621
|
model: newModel,
|
|
35218
|
-
apiKey: newApiKeyForSaving
|
|
35622
|
+
apiKey: newApiKeyForSaving,
|
|
35623
|
+
project: vertexSettings?.project,
|
|
35624
|
+
location: vertexSettings?.location
|
|
35219
35625
|
});
|
|
35220
35626
|
} else {
|
|
35221
35627
|
await saveProviderPreference(userFacingProviderId, newModel, {
|
|
@@ -35255,8 +35661,26 @@ async function setupGcloudADCForProvider(_provider) {
|
|
|
35255
35661
|
return true;
|
|
35256
35662
|
}
|
|
35257
35663
|
console.log(chalk.yellow("\n No reusable gcloud ADC session was found for Coco."));
|
|
35258
|
-
|
|
35259
|
-
|
|
35664
|
+
console.log();
|
|
35665
|
+
if (adc.message) console.log(chalk.dim(` ${adc.message}`));
|
|
35666
|
+
console.log();
|
|
35667
|
+
const runLoginNow = await p26.confirm({
|
|
35668
|
+
message: "Authenticate with gcloud now from Coco?",
|
|
35669
|
+
initialValue: true
|
|
35670
|
+
});
|
|
35671
|
+
if (p26.isCancel(runLoginNow)) return false;
|
|
35672
|
+
if (runLoginNow) {
|
|
35673
|
+
p26.log.step("Running `gcloud auth application-default login`...");
|
|
35674
|
+
const loginOk = await runGcloudADCLogin();
|
|
35675
|
+
if (loginOk) {
|
|
35676
|
+
const refreshed = await inspectADC();
|
|
35677
|
+
if (refreshed.status === "ok" && refreshed.token) {
|
|
35678
|
+
console.log(chalk.green(" \u2713 gcloud ADC is now configured.\n"));
|
|
35679
|
+
return true;
|
|
35680
|
+
}
|
|
35681
|
+
}
|
|
35682
|
+
p26.log.error("Could not complete gcloud ADC login from Coco.");
|
|
35683
|
+
console.log();
|
|
35260
35684
|
}
|
|
35261
35685
|
console.log(chalk.dim("\n Check the current ADC state with:"));
|
|
35262
35686
|
console.log(chalk.cyan(" $ gcloud auth application-default print-access-token"));
|
|
@@ -35269,9 +35693,9 @@ async function setupGcloudADCForProvider(_provider) {
|
|
|
35269
35693
|
console.log();
|
|
35270
35694
|
return false;
|
|
35271
35695
|
}
|
|
35272
|
-
async function promptVertexSettings2() {
|
|
35273
|
-
const projectDefault = process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
|
|
35274
|
-
const locationDefault = process.env["VERTEX_LOCATION"] ?? process.env["GOOGLE_CLOUD_LOCATION"] ?? "global";
|
|
35696
|
+
async function promptVertexSettings2(defaults) {
|
|
35697
|
+
const projectDefault = defaults?.project ?? process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
|
|
35698
|
+
const locationDefault = defaults?.location ?? process.env["VERTEX_LOCATION"] ?? process.env["GOOGLE_CLOUD_LOCATION"] ?? "global";
|
|
35275
35699
|
const project = await p26.text({
|
|
35276
35700
|
message: "Google Cloud project ID:",
|
|
35277
35701
|
placeholder: projectDefault || "my-gcp-project",
|
|
@@ -35980,11 +36404,11 @@ async function revokeTrust(session, trustStore) {
|
|
|
35980
36404
|
p26.log.info("This project is not currently trusted");
|
|
35981
36405
|
return;
|
|
35982
36406
|
}
|
|
35983
|
-
const
|
|
36407
|
+
const confirm23 = await p26.confirm({
|
|
35984
36408
|
message: "Revoke all access to this project?",
|
|
35985
36409
|
initialValue: false
|
|
35986
36410
|
});
|
|
35987
|
-
if (p26.isCancel(
|
|
36411
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
35988
36412
|
p26.outro("Cancelled");
|
|
35989
36413
|
return;
|
|
35990
36414
|
}
|
|
@@ -36101,11 +36525,11 @@ var initCommand = {
|
|
|
36101
36525
|
p26.log.message(` Description: ${description}`);
|
|
36102
36526
|
}
|
|
36103
36527
|
p26.log.message("");
|
|
36104
|
-
const
|
|
36528
|
+
const confirm23 = await p26.confirm({
|
|
36105
36529
|
message: "Create project?",
|
|
36106
36530
|
initialValue: true
|
|
36107
36531
|
});
|
|
36108
|
-
if (p26.isCancel(
|
|
36532
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
36109
36533
|
p26.outro("Cancelled");
|
|
36110
36534
|
return false;
|
|
36111
36535
|
}
|
|
@@ -37331,11 +37755,11 @@ async function runInteractiveMode(session) {
|
|
|
37331
37755
|
);
|
|
37332
37756
|
}
|
|
37333
37757
|
console.log();
|
|
37334
|
-
const
|
|
37758
|
+
const confirm23 = await p26.confirm({
|
|
37335
37759
|
message: "Proceed with restoration?",
|
|
37336
37760
|
initialValue: true
|
|
37337
37761
|
});
|
|
37338
|
-
if (p26.isCancel(
|
|
37762
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
37339
37763
|
p26.outro("Cancelled");
|
|
37340
37764
|
return false;
|
|
37341
37765
|
}
|
|
@@ -37378,11 +37802,11 @@ async function runDirectMode(session, checkpointId) {
|
|
|
37378
37802
|
console.log(`${chalk.dim("Conversation:")} ${checkpoint.conversation?.messageCount} messages`);
|
|
37379
37803
|
}
|
|
37380
37804
|
console.log();
|
|
37381
|
-
const
|
|
37805
|
+
const confirm23 = await p26.confirm({
|
|
37382
37806
|
message: "Restore this checkpoint?",
|
|
37383
37807
|
initialValue: true
|
|
37384
37808
|
});
|
|
37385
|
-
if (p26.isCancel(
|
|
37809
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
37386
37810
|
p26.outro("Cancelled");
|
|
37387
37811
|
return false;
|
|
37388
37812
|
}
|
|
@@ -37833,11 +38257,11 @@ async function runInteractiveMode2(session) {
|
|
|
37833
38257
|
return false;
|
|
37834
38258
|
}
|
|
37835
38259
|
displaySessionDetails(selectedSession);
|
|
37836
|
-
const
|
|
38260
|
+
const confirm23 = await p26.confirm({
|
|
37837
38261
|
message: "Resume this session?",
|
|
37838
38262
|
initialValue: true
|
|
37839
38263
|
});
|
|
37840
|
-
if (p26.isCancel(
|
|
38264
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
37841
38265
|
p26.outro("Cancelled");
|
|
37842
38266
|
return false;
|
|
37843
38267
|
}
|
|
@@ -37855,11 +38279,11 @@ async function runDirectMode2(session, sessionId) {
|
|
|
37855
38279
|
return false;
|
|
37856
38280
|
}
|
|
37857
38281
|
displaySessionDetails(targetSession);
|
|
37858
|
-
const
|
|
38282
|
+
const confirm23 = await p26.confirm({
|
|
37859
38283
|
message: "Resume this session?",
|
|
37860
38284
|
initialValue: true
|
|
37861
38285
|
});
|
|
37862
|
-
if (p26.isCancel(
|
|
38286
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
37863
38287
|
p26.outro("Cancelled");
|
|
37864
38288
|
return false;
|
|
37865
38289
|
}
|
|
@@ -38872,7 +39296,9 @@ async function loadPermissionPreferences() {
|
|
|
38872
39296
|
recommendedAllowlistApplied: config.recommendedAllowlistApplied,
|
|
38873
39297
|
recommendedAllowlistDismissed: config.recommendedAllowlistDismissed,
|
|
38874
39298
|
recommendedAllowlistPrompted: config.recommendedAllowlistPrompted,
|
|
38875
|
-
recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects
|
|
39299
|
+
recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects,
|
|
39300
|
+
recommendedAllowlistAppliedProjects: config.recommendedAllowlistAppliedProjects,
|
|
39301
|
+
recommendedAllowlistDismissedProjects: config.recommendedAllowlistDismissedProjects
|
|
38876
39302
|
};
|
|
38877
39303
|
} catch {
|
|
38878
39304
|
return {};
|
|
@@ -38892,7 +39318,27 @@ async function savePermissionPreference(key, value) {
|
|
|
38892
39318
|
} catch {
|
|
38893
39319
|
}
|
|
38894
39320
|
}
|
|
38895
|
-
|
|
39321
|
+
function isRecommendedAllowlistAppliedForProject(prefs, projectPath) {
|
|
39322
|
+
const projectKey = getProjectPreferenceKey(projectPath);
|
|
39323
|
+
if (prefs.recommendedAllowlistAppliedProjects?.[projectKey] === true) {
|
|
39324
|
+
return true;
|
|
39325
|
+
}
|
|
39326
|
+
if (prefs.recommendedAllowlistApplied === true && !prefs.recommendedAllowlistAppliedProjects) {
|
|
39327
|
+
return true;
|
|
39328
|
+
}
|
|
39329
|
+
return false;
|
|
39330
|
+
}
|
|
39331
|
+
function isRecommendedAllowlistDismissedForProject(prefs, projectPath) {
|
|
39332
|
+
const projectKey = getProjectPreferenceKey(projectPath);
|
|
39333
|
+
if (prefs.recommendedAllowlistDismissedProjects?.[projectKey] === true) {
|
|
39334
|
+
return true;
|
|
39335
|
+
}
|
|
39336
|
+
if (prefs.recommendedAllowlistDismissed === true && !prefs.recommendedAllowlistDismissedProjects) {
|
|
39337
|
+
return true;
|
|
39338
|
+
}
|
|
39339
|
+
return false;
|
|
39340
|
+
}
|
|
39341
|
+
async function saveProjectPermissionPreference(key, projectPath, value) {
|
|
38896
39342
|
try {
|
|
38897
39343
|
let config = {};
|
|
38898
39344
|
try {
|
|
@@ -38900,12 +39346,12 @@ async function markPermissionSuggestionShownForProject(projectPath) {
|
|
|
38900
39346
|
config = JSON.parse(content);
|
|
38901
39347
|
} catch {
|
|
38902
39348
|
}
|
|
38903
|
-
const
|
|
38904
|
-
|
|
38905
|
-
|
|
39349
|
+
const projectKey = getProjectPreferenceKey(projectPath);
|
|
39350
|
+
const currentMap = config[key] ?? {};
|
|
39351
|
+
config[key] = {
|
|
39352
|
+
...currentMap,
|
|
39353
|
+
[projectKey]: value
|
|
38906
39354
|
};
|
|
38907
|
-
config.recommendedAllowlistPromptedProjects = promptedProjects;
|
|
38908
|
-
config.recommendedAllowlistPrompted = true;
|
|
38909
39355
|
await fs35__default.mkdir(path39__default.dirname(CONFIG_PATHS.config), { recursive: true });
|
|
38910
39356
|
await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2), "utf-8");
|
|
38911
39357
|
} catch {
|
|
@@ -38913,26 +39359,26 @@ async function markPermissionSuggestionShownForProject(projectPath) {
|
|
|
38913
39359
|
}
|
|
38914
39360
|
async function shouldShowPermissionSuggestion(projectPath = process.cwd()) {
|
|
38915
39361
|
const prefs = await loadPermissionPreferences();
|
|
38916
|
-
if (prefs
|
|
38917
|
-
return false;
|
|
38918
|
-
}
|
|
38919
|
-
if (prefs.recommendedAllowlistApplied) {
|
|
39362
|
+
if (isRecommendedAllowlistDismissedForProject(prefs, projectPath)) {
|
|
38920
39363
|
return false;
|
|
38921
39364
|
}
|
|
38922
|
-
|
|
38923
|
-
if (prefs.recommendedAllowlistPromptedProjects?.[projectKey]) {
|
|
39365
|
+
if (isRecommendedAllowlistAppliedForProject(prefs, projectPath)) {
|
|
38924
39366
|
return false;
|
|
38925
39367
|
}
|
|
38926
39368
|
return true;
|
|
38927
39369
|
}
|
|
38928
|
-
async function applyRecommendedPermissions() {
|
|
39370
|
+
async function applyRecommendedPermissions(projectPath = process.cwd()) {
|
|
38929
39371
|
for (const tool of [...RECOMMENDED_GLOBAL, ...RECOMMENDED_PROJECT]) {
|
|
38930
39372
|
await saveTrustedTool(tool, null, true);
|
|
38931
39373
|
}
|
|
38932
|
-
await
|
|
39374
|
+
await saveProjectPermissionPreference("recommendedAllowlistAppliedProjects", projectPath, true);
|
|
39375
|
+
await saveProjectPermissionPreference(
|
|
39376
|
+
"recommendedAllowlistDismissedProjects",
|
|
39377
|
+
projectPath,
|
|
39378
|
+
false
|
|
39379
|
+
);
|
|
38933
39380
|
}
|
|
38934
39381
|
async function showPermissionSuggestion(projectPath = process.cwd()) {
|
|
38935
|
-
await markPermissionSuggestionShownForProject(projectPath);
|
|
38936
39382
|
console.log();
|
|
38937
39383
|
console.log(chalk.magenta.bold(" \u{1F4CB} Recommended Permissions"));
|
|
38938
39384
|
console.log();
|
|
@@ -38959,8 +39405,11 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
|
|
|
38959
39405
|
return;
|
|
38960
39406
|
}
|
|
38961
39407
|
if (action === "dismiss") {
|
|
38962
|
-
await
|
|
38963
|
-
|
|
39408
|
+
await saveProjectPermissionPreference(
|
|
39409
|
+
"recommendedAllowlistDismissedProjects",
|
|
39410
|
+
projectPath,
|
|
39411
|
+
true
|
|
39412
|
+
);
|
|
38964
39413
|
console.log(chalk.dim(" Won't show again. Use /permissions to apply later."));
|
|
38965
39414
|
return;
|
|
38966
39415
|
}
|
|
@@ -38974,8 +39423,7 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
|
|
|
38974
39423
|
return;
|
|
38975
39424
|
}
|
|
38976
39425
|
}
|
|
38977
|
-
await applyRecommendedPermissions();
|
|
38978
|
-
await savePermissionPreference("recommendedAllowlistPrompted", true);
|
|
39426
|
+
await applyRecommendedPermissions(projectPath);
|
|
38979
39427
|
console.log(chalk.green(" \u2713 Recommended permissions applied"));
|
|
38980
39428
|
console.log(chalk.dim(" Use /permissions to review or modify anytime."));
|
|
38981
39429
|
}
|
|
@@ -39066,7 +39514,7 @@ async function showStatus(session) {
|
|
|
39066
39514
|
console.log(chalk.magenta.bold(" \u{1F510} Tool Permissions"));
|
|
39067
39515
|
console.log();
|
|
39068
39516
|
const allowCount = RECOMMENDED_GLOBAL.length + RECOMMENDED_PROJECT.length;
|
|
39069
|
-
if (prefs.
|
|
39517
|
+
if (isRecommendedAllowlistAppliedForProject(prefs, session.projectPath)) {
|
|
39070
39518
|
console.log(
|
|
39071
39519
|
chalk.green(" \u2713 Recommended allowlist applied") + chalk.dim(` (${allowCount} allow, ${RECOMMENDED_DENY.length} deny)`)
|
|
39072
39520
|
);
|
|
@@ -39108,7 +39556,7 @@ async function showStatus(session) {
|
|
|
39108
39556
|
console.log();
|
|
39109
39557
|
}
|
|
39110
39558
|
async function applyRecommended(session) {
|
|
39111
|
-
await applyRecommendedPermissions();
|
|
39559
|
+
await applyRecommendedPermissions(session.projectPath);
|
|
39112
39560
|
for (const tool of RECOMMENDED_GLOBAL) {
|
|
39113
39561
|
session.trustedTools.add(tool);
|
|
39114
39562
|
}
|
|
@@ -39158,177 +39606,18 @@ async function resetPermissions(session) {
|
|
|
39158
39606
|
} catch {
|
|
39159
39607
|
}
|
|
39160
39608
|
await savePermissionPreference("recommendedAllowlistApplied", false);
|
|
39161
|
-
|
|
39162
|
-
|
|
39163
|
-
|
|
39164
|
-
|
|
39165
|
-
init_paths();
|
|
39166
|
-
var qualityLoopEnabled = true;
|
|
39167
|
-
var hintShown = false;
|
|
39168
|
-
function isQualityLoop() {
|
|
39169
|
-
return qualityLoopEnabled;
|
|
39170
|
-
}
|
|
39171
|
-
function setQualityLoop(enabled) {
|
|
39172
|
-
qualityLoopEnabled = enabled;
|
|
39173
|
-
}
|
|
39174
|
-
function wasHintShown() {
|
|
39175
|
-
return hintShown;
|
|
39176
|
-
}
|
|
39177
|
-
function markHintShown() {
|
|
39178
|
-
hintShown = true;
|
|
39179
|
-
}
|
|
39180
|
-
function looksLikeFeatureRequest(input) {
|
|
39181
|
-
const trimmed = input.trim();
|
|
39182
|
-
if (trimmed.length < 20) return false;
|
|
39183
|
-
if (trimmed.endsWith("?") && trimmed.length < 80) return false;
|
|
39184
|
-
const featureKeywords = [
|
|
39185
|
-
/\bimplement/i,
|
|
39186
|
-
/\bcreate\b/i,
|
|
39187
|
-
/\bbuild\b/i,
|
|
39188
|
-
/\badd\b.*\b(feature|function|component|endpoint|service|module|class)/i,
|
|
39189
|
-
/\brefactor/i,
|
|
39190
|
-
/\bmigrate/i,
|
|
39191
|
-
/\bsetup\b/i,
|
|
39192
|
-
/\bintegrate/i,
|
|
39193
|
-
/\bwrite\b.*\b(code|function|test|module)/i,
|
|
39194
|
-
/\bdevelop/i,
|
|
39195
|
-
/\bdesign\b/i,
|
|
39196
|
-
/\bfix\b.*\b(bug|issue|error|problem)/i,
|
|
39197
|
-
/\bupdate\b.*\b(function|component|service|module)/i,
|
|
39198
|
-
/\bgenerate\b/i,
|
|
39199
|
-
/\bconvert\b/i
|
|
39200
|
-
];
|
|
39201
|
-
return featureKeywords.some((re) => re.test(trimmed));
|
|
39202
|
-
}
|
|
39203
|
-
function formatQualityLoopHint() {
|
|
39204
|
-
return chalk.dim(" tip: ") + chalk.magenta("/quality") + chalk.dim(" enables auto-test & iterate until quality converges");
|
|
39205
|
-
}
|
|
39206
|
-
function formatQualityResult(result) {
|
|
39207
|
-
const lines = [];
|
|
39208
|
-
const scores = result.scoreHistory;
|
|
39209
|
-
const progressStr = scores.map((s) => String(s)).join(" \u2192 ");
|
|
39210
|
-
const convergedLabel = result.converged ? chalk.green("converged") : chalk.yellow("max iterations");
|
|
39211
|
-
lines.push("");
|
|
39212
|
-
lines.push(
|
|
39213
|
-
chalk.magenta("\u2500\u2500 Quality: ") + chalk.white(progressStr) + chalk.dim(` (${convergedLabel})`) + chalk.magenta(" \u2500\u2500")
|
|
39609
|
+
await saveProjectPermissionPreference(
|
|
39610
|
+
"recommendedAllowlistAppliedProjects",
|
|
39611
|
+
session.projectPath,
|
|
39612
|
+
false
|
|
39214
39613
|
);
|
|
39215
|
-
|
|
39216
|
-
|
|
39217
|
-
|
|
39218
|
-
|
|
39219
|
-
|
|
39220
|
-
|
|
39221
|
-
const covColor = result.coverage >= 80 ? chalk.green : chalk.yellow;
|
|
39222
|
-
parts.push(covColor(`Coverage: ${result.coverage}%`));
|
|
39223
|
-
}
|
|
39224
|
-
if (result.securityScore !== void 0) {
|
|
39225
|
-
const secColor = result.securityScore === 100 ? chalk.green : chalk.red;
|
|
39226
|
-
parts.push(secColor(`Security: ${result.securityScore}`));
|
|
39227
|
-
}
|
|
39228
|
-
parts.push(chalk.dim(`Iterations: ${result.iterations}`));
|
|
39229
|
-
if (result.durationMs !== void 0) {
|
|
39230
|
-
const secs = (result.durationMs / 1e3).toFixed(1);
|
|
39231
|
-
parts.push(chalk.dim(`Time: ${secs}s`));
|
|
39232
|
-
}
|
|
39233
|
-
lines.push(" " + parts.join(" "));
|
|
39234
|
-
lines.push("");
|
|
39235
|
-
return lines.join("\n");
|
|
39236
|
-
}
|
|
39237
|
-
async function loadQualityLoopPreference() {
|
|
39238
|
-
try {
|
|
39239
|
-
const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
|
|
39240
|
-
const config = JSON.parse(content);
|
|
39241
|
-
const value = config.qualityLoop ?? config.cocoMode;
|
|
39242
|
-
if (typeof value === "boolean") {
|
|
39243
|
-
qualityLoopEnabled = value;
|
|
39244
|
-
return value;
|
|
39245
|
-
}
|
|
39246
|
-
} catch {
|
|
39247
|
-
}
|
|
39248
|
-
return true;
|
|
39249
|
-
}
|
|
39250
|
-
async function saveQualityLoopPreference(enabled) {
|
|
39251
|
-
try {
|
|
39252
|
-
let config = {};
|
|
39253
|
-
try {
|
|
39254
|
-
const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
|
|
39255
|
-
config = JSON.parse(content);
|
|
39256
|
-
} catch {
|
|
39257
|
-
}
|
|
39258
|
-
config.qualityLoop = enabled;
|
|
39259
|
-
await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2) + "\n");
|
|
39260
|
-
} catch {
|
|
39261
|
-
}
|
|
39262
|
-
}
|
|
39263
|
-
function parseQualityLoopReport(content) {
|
|
39264
|
-
const marker = "QUALITY_LOOP_REPORT";
|
|
39265
|
-
const idx = content.indexOf(marker);
|
|
39266
|
-
if (idx === -1) return null;
|
|
39267
|
-
const block = content.slice(idx);
|
|
39268
|
-
const getField = (name) => {
|
|
39269
|
-
const match = block.match(new RegExp(`${name}:\\s*(.+)`));
|
|
39270
|
-
return match?.[1]?.trim();
|
|
39271
|
-
};
|
|
39272
|
-
const scoreHistoryRaw = getField("score_history");
|
|
39273
|
-
if (!scoreHistoryRaw) return null;
|
|
39274
|
-
const scores = scoreHistoryRaw.replace(/[[\]]/g, "").split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
|
|
39275
|
-
if (scores.length === 0) return null;
|
|
39276
|
-
const testsPassed = parseInt(getField("tests_passed") ?? "", 10);
|
|
39277
|
-
const testsTotal = parseInt(getField("tests_total") ?? "", 10);
|
|
39278
|
-
const coverage = parseInt(getField("coverage") ?? "", 10);
|
|
39279
|
-
const security = parseInt(getField("security") ?? "", 10);
|
|
39280
|
-
const iterations = parseInt(getField("iterations") ?? "", 10) || scores.length;
|
|
39281
|
-
const converged = getField("converged") === "true";
|
|
39282
|
-
return {
|
|
39283
|
-
converged,
|
|
39284
|
-
scoreHistory: scores,
|
|
39285
|
-
finalScore: scores[scores.length - 1] ?? 0,
|
|
39286
|
-
iterations,
|
|
39287
|
-
testsPassed: isNaN(testsPassed) ? void 0 : testsPassed,
|
|
39288
|
-
testsTotal: isNaN(testsTotal) ? void 0 : testsTotal,
|
|
39289
|
-
coverage: isNaN(coverage) ? void 0 : coverage,
|
|
39290
|
-
securityScore: isNaN(security) ? void 0 : security
|
|
39291
|
-
};
|
|
39292
|
-
}
|
|
39293
|
-
function getQualityLoopSystemPrompt() {
|
|
39294
|
-
return `
|
|
39295
|
-
## Quality Loop Mode (ACTIVE)
|
|
39296
|
-
|
|
39297
|
-
You are operating in quality loop mode. After implementing code changes, you MUST follow this iteration cycle:
|
|
39298
|
-
|
|
39299
|
-
1. **Implement** the requested changes (code + tests)
|
|
39300
|
-
2. **Run tests** using the run_tests or bash_exec tool
|
|
39301
|
-
3. **Self-review**: Analyze your code against these 12 quality dimensions:
|
|
39302
|
-
- Correctness, Completeness, Robustness, Readability
|
|
39303
|
-
- Maintainability, Complexity, Duplication, Test Coverage
|
|
39304
|
-
- Test Quality, Security, Documentation, Style
|
|
39305
|
-
4. **Score** your implementation 0-100 for each dimension
|
|
39306
|
-
5. **If issues found**: Fix them and go back to step 2
|
|
39307
|
-
6. **If quality is good** (overall \u2265 85 and improving < 2 points): Stop and report
|
|
39308
|
-
|
|
39309
|
-
After completing the cycle, output a quality summary in this exact format:
|
|
39310
|
-
|
|
39311
|
-
\`\`\`
|
|
39312
|
-
QUALITY_LOOP_REPORT
|
|
39313
|
-
score_history: [first_score, ..., final_score]
|
|
39314
|
-
tests_passed: X
|
|
39315
|
-
tests_total: Y
|
|
39316
|
-
coverage: Z
|
|
39317
|
-
security: 100
|
|
39318
|
-
iterations: N
|
|
39319
|
-
converged: true|false
|
|
39320
|
-
\`\`\`
|
|
39321
|
-
|
|
39322
|
-
Key rules:
|
|
39323
|
-
- Always write tests alongside code
|
|
39324
|
-
- Run tests after every change
|
|
39325
|
-
- Minimum 2 iterations before declaring convergence
|
|
39326
|
-
- Maximum 10 iterations
|
|
39327
|
-
- Fix critical issues before moving on
|
|
39328
|
-
- Report honestly - don't inflate scores`;
|
|
39614
|
+
await saveProjectPermissionPreference(
|
|
39615
|
+
"recommendedAllowlistDismissedProjects",
|
|
39616
|
+
session.projectPath,
|
|
39617
|
+
false
|
|
39618
|
+
);
|
|
39619
|
+
console.log(chalk.green(" \u2713 All tool permissions reset."));
|
|
39329
39620
|
}
|
|
39330
|
-
|
|
39331
|
-
// src/cli/repl/commands/quality.ts
|
|
39332
39621
|
var qualityCommand = {
|
|
39333
39622
|
name: "quality",
|
|
39334
39623
|
aliases: ["coco"],
|
|
@@ -40099,11 +40388,11 @@ Response format (JSON only, no prose):
|
|
|
40099
40388
|
}
|
|
40100
40389
|
}
|
|
40101
40390
|
if (!options?.skipConfirmation) {
|
|
40102
|
-
const
|
|
40391
|
+
const confirm23 = await p26.confirm({
|
|
40103
40392
|
message: "Start building with this plan?",
|
|
40104
40393
|
initialValue: true
|
|
40105
40394
|
});
|
|
40106
|
-
if (p26.isCancel(
|
|
40395
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
40107
40396
|
cancel5("Build cancelled.");
|
|
40108
40397
|
}
|
|
40109
40398
|
}
|
|
@@ -41661,8 +41950,8 @@ Examples:
|
|
|
41661
41950
|
recursive: z.boolean().optional().default(false).describe("Delete directories recursively"),
|
|
41662
41951
|
confirm: z.boolean().optional().describe("Must be true to confirm deletion")
|
|
41663
41952
|
}),
|
|
41664
|
-
async execute({ path: filePath, recursive, confirm:
|
|
41665
|
-
if (
|
|
41953
|
+
async execute({ path: filePath, recursive, confirm: confirm23 }) {
|
|
41954
|
+
if (confirm23 !== true) {
|
|
41666
41955
|
throw new ToolError(
|
|
41667
41956
|
"Deletion requires explicit confirmation. Set confirm: true to proceed.",
|
|
41668
41957
|
{ tool: "delete_file" }
|
|
@@ -48645,11 +48934,11 @@ var buildAppCommand = {
|
|
|
48645
48934
|
return false;
|
|
48646
48935
|
}
|
|
48647
48936
|
if (!isAutonomous && !parsed.skipConfirmation) {
|
|
48648
|
-
const
|
|
48937
|
+
const confirm23 = await p26.confirm({
|
|
48649
48938
|
message: `Build "${spec.projectName}" with ${spec.sprints.length} sprints?`,
|
|
48650
48939
|
initialValue: true
|
|
48651
48940
|
});
|
|
48652
|
-
if (p26.isCancel(
|
|
48941
|
+
if (p26.isCancel(confirm23) || !confirm23) {
|
|
48653
48942
|
p26.cancel("Build cancelled.");
|
|
48654
48943
|
return false;
|
|
48655
48944
|
}
|
|
@@ -53610,44 +53899,6 @@ function createIntentRecognizer(config = {}) {
|
|
|
53610
53899
|
// src/cli/repl/index.ts
|
|
53611
53900
|
init_env();
|
|
53612
53901
|
init_allowed_paths();
|
|
53613
|
-
async function getGitContext(projectPath) {
|
|
53614
|
-
const controller = new AbortController();
|
|
53615
|
-
const timer = setTimeout(() => controller.abort(), 3e3);
|
|
53616
|
-
try {
|
|
53617
|
-
const git = simpleGit({ baseDir: projectPath, abort: controller.signal });
|
|
53618
|
-
const status = await git.status();
|
|
53619
|
-
clearTimeout(timer);
|
|
53620
|
-
return {
|
|
53621
|
-
branch: status.current ?? "HEAD",
|
|
53622
|
-
isDirty: !status.isClean(),
|
|
53623
|
-
staged: status.staged.length,
|
|
53624
|
-
modified: status.modified.length,
|
|
53625
|
-
untracked: status.not_added.length,
|
|
53626
|
-
ahead: status.ahead,
|
|
53627
|
-
behind: status.behind
|
|
53628
|
-
};
|
|
53629
|
-
} catch {
|
|
53630
|
-
clearTimeout(timer);
|
|
53631
|
-
return null;
|
|
53632
|
-
}
|
|
53633
|
-
}
|
|
53634
|
-
function formatGitLine(ctx) {
|
|
53635
|
-
const branchColor = ctx.isDirty ? chalk.yellow : chalk.green;
|
|
53636
|
-
const parts = [chalk.dim("\u{1F33F} ") + branchColor(ctx.branch)];
|
|
53637
|
-
const changes = [];
|
|
53638
|
-
if (ctx.staged > 0) changes.push(chalk.green(`+${ctx.staged}`));
|
|
53639
|
-
if (ctx.modified > 0) changes.push(chalk.yellow(`~${ctx.modified}`));
|
|
53640
|
-
if (ctx.untracked > 0) changes.push(chalk.dim(`?${ctx.untracked}`));
|
|
53641
|
-
if (ctx.ahead > 0) changes.push(chalk.cyan(`\u2191${ctx.ahead}`));
|
|
53642
|
-
if (ctx.behind > 0) changes.push(chalk.red(`\u2193${ctx.behind}`));
|
|
53643
|
-
if (changes.length > 0) parts.push(changes.join(" "));
|
|
53644
|
-
return parts.join(" \u2022 ");
|
|
53645
|
-
}
|
|
53646
|
-
function formatGitShort(ctx) {
|
|
53647
|
-
const branch = ctx.isDirty ? chalk.yellow(ctx.branch) : chalk.green(ctx.branch);
|
|
53648
|
-
const dirty = ctx.isDirty ? chalk.yellow(" \u25CF") : "";
|
|
53649
|
-
return chalk.dim("\u{1F33F} ") + branch + dirty;
|
|
53650
|
-
}
|
|
53651
53902
|
|
|
53652
53903
|
// src/cli/repl/status-bar.ts
|
|
53653
53904
|
init_env();
|
|
@@ -54752,85 +55003,7 @@ ${imagePrompts}`.trim() : imagePrompts;
|
|
|
54752
55003
|
process.off("SIGTERM", sigtermHandler);
|
|
54753
55004
|
}
|
|
54754
55005
|
async function printWelcome(session, gitCtx, mcpManager) {
|
|
54755
|
-
|
|
54756
|
-
await trustStore.init();
|
|
54757
|
-
const trustLevel = trustStore.getLevel(session.projectPath);
|
|
54758
|
-
const boxWidth = 41;
|
|
54759
|
-
const innerWidth = boxWidth - 2;
|
|
54760
|
-
const versionText = `v${VERSION}`;
|
|
54761
|
-
const subtitleText = "open source \u2022 corbat.tech";
|
|
54762
|
-
const boxLine = (content) => {
|
|
54763
|
-
const pad = Math.max(0, innerWidth - stringWidth2(content));
|
|
54764
|
-
return chalk.magenta("\u2502") + content + " ".repeat(pad) + chalk.magenta("\u2502");
|
|
54765
|
-
};
|
|
54766
|
-
const titleLeftRaw = " COCO";
|
|
54767
|
-
const titleRightRaw = versionText + " ";
|
|
54768
|
-
const titleLeftStyled = " " + chalk.bold.white("COCO");
|
|
54769
|
-
const titleGap = Math.max(1, innerWidth - stringWidth2(titleLeftRaw) - stringWidth2(titleRightRaw));
|
|
54770
|
-
const titleContent = titleLeftStyled + " ".repeat(titleGap) + chalk.dim(titleRightRaw);
|
|
54771
|
-
const taglineText = "code that converges to quality";
|
|
54772
|
-
const taglineContent = " " + chalk.magenta(taglineText) + " ";
|
|
54773
|
-
const subtitleContent = " " + chalk.dim(subtitleText) + " ";
|
|
54774
|
-
console.log();
|
|
54775
|
-
console.log(chalk.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
|
|
54776
|
-
console.log(" " + boxLine(titleContent));
|
|
54777
|
-
console.log(" " + boxLine(taglineContent));
|
|
54778
|
-
console.log(" " + boxLine(subtitleContent));
|
|
54779
|
-
console.log(chalk.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
|
|
54780
|
-
const maxPathLen = 50;
|
|
54781
|
-
let displayPath = session.projectPath;
|
|
54782
|
-
if (displayPath.length > maxPathLen) {
|
|
54783
|
-
displayPath = "..." + displayPath.slice(-maxPathLen + 3);
|
|
54784
|
-
}
|
|
54785
|
-
const lastSep = displayPath.lastIndexOf("/");
|
|
54786
|
-
const parentPath = lastSep > 0 ? displayPath.slice(0, lastSep + 1) : "";
|
|
54787
|
-
const projectName = lastSep > 0 ? displayPath.slice(lastSep + 1) : displayPath;
|
|
54788
|
-
const providerName = session.config.provider.type;
|
|
54789
|
-
const configuredModel = session.config.provider.model?.trim();
|
|
54790
|
-
const modelName = configuredModel && !["default", "none", "null", "undefined"].includes(configuredModel.toLowerCase()) ? configuredModel : getDefaultModel(session.config.provider.type);
|
|
54791
|
-
const trustText = trustLevel === "full" ? "full" : trustLevel === "write" ? "write" : trustLevel === "read" ? "read" : "";
|
|
54792
|
-
console.log();
|
|
54793
|
-
console.log(chalk.dim(` \u{1F4C1} ${parentPath}`) + chalk.magenta.bold(projectName));
|
|
54794
|
-
console.log(
|
|
54795
|
-
chalk.dim(` \u{1F916} ${providerName}/`) + chalk.magenta(modelName) + (trustText ? chalk.dim(` \u2022 \u{1F510} ${trustText}`) : "")
|
|
54796
|
-
);
|
|
54797
|
-
if (gitCtx) {
|
|
54798
|
-
console.log(` ${formatGitLine(gitCtx)}`);
|
|
54799
|
-
}
|
|
54800
|
-
const cocoStatus = isQualityLoop() ? chalk.magenta(" \u{1F504} quality mode: ") + chalk.green.bold("on") + chalk.dim(" \u2014 iterates until quality \u2265 85. /quality to disable") : chalk.dim(" \u{1F4A1} /quality on \u2014 enable auto-test & quality iteration");
|
|
54801
|
-
console.log(cocoStatus);
|
|
54802
|
-
const skillTotal = session.skillRegistry?.size ?? 0;
|
|
54803
|
-
const mcpServers = mcpManager?.getConnectedServers() ?? [];
|
|
54804
|
-
const hasSomething = skillTotal > 0 || mcpServers.length > 0;
|
|
54805
|
-
if (hasSomething) {
|
|
54806
|
-
if (skillTotal > 0) {
|
|
54807
|
-
const allMeta = session.skillRegistry.getAllMetadata();
|
|
54808
|
-
const builtinCount = allMeta.filter((s) => s.scope === "builtin").length;
|
|
54809
|
-
const projectCount = skillTotal - builtinCount;
|
|
54810
|
-
const parts = [];
|
|
54811
|
-
if (builtinCount > 0) parts.push(`${builtinCount} builtin`);
|
|
54812
|
-
if (projectCount > 0) parts.push(`${projectCount} project`);
|
|
54813
|
-
const detail = parts.length > 0 ? ` (${parts.join(" \xB7 ")})` : "";
|
|
54814
|
-
console.log(chalk.green(" \u2713") + chalk.dim(` Skills: ${skillTotal} loaded${detail}`));
|
|
54815
|
-
} else {
|
|
54816
|
-
console.log(chalk.dim(" \xB7 Skills: none loaded"));
|
|
54817
|
-
}
|
|
54818
|
-
if (mcpServers.length > 0) {
|
|
54819
|
-
const names = mcpServers.join(", ");
|
|
54820
|
-
console.log(
|
|
54821
|
-
chalk.green(" \u2713") + chalk.dim(
|
|
54822
|
-
` MCP: ${names} (${mcpServers.length} server${mcpServers.length === 1 ? "" : "s"} active)`
|
|
54823
|
-
)
|
|
54824
|
-
);
|
|
54825
|
-
}
|
|
54826
|
-
}
|
|
54827
|
-
console.log();
|
|
54828
|
-
console.log(
|
|
54829
|
-
chalk.dim(" Type your request or ") + chalk.magenta("/help") + chalk.dim(" for commands")
|
|
54830
|
-
);
|
|
54831
|
-
const pasteHint = process.platform === "darwin" ? chalk.dim(" \u{1F4CB} \u2318V paste text \u2022 \u2303V paste image") : chalk.dim(" \u{1F4CB} Ctrl+V paste image from clipboard");
|
|
54832
|
-
console.log(pasteHint);
|
|
54833
|
-
console.log();
|
|
55006
|
+
await renderStartupPanel(session, gitCtx, mcpManager?.getConnectedServers() ?? []);
|
|
54834
55007
|
}
|
|
54835
55008
|
async function checkProjectTrust(projectPath) {
|
|
54836
55009
|
const trustStore = createTrustStore();
|