@corbat-tech/coco 1.2.3 → 1.3.0
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/README.md +56 -6
- package/dist/cli/index.js +2832 -1828
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +430 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -117,6 +117,8 @@ function getApiKey(provider) {
|
|
|
117
117
|
return process.env["KIMI_API_KEY"] ?? process.env["MOONSHOT_API_KEY"];
|
|
118
118
|
case "lmstudio":
|
|
119
119
|
return process.env["LMSTUDIO_API_KEY"] ?? "lm-studio";
|
|
120
|
+
case "ollama":
|
|
121
|
+
return process.env["OLLAMA_API_KEY"] ?? "ollama";
|
|
120
122
|
case "codex":
|
|
121
123
|
return void 0;
|
|
122
124
|
default:
|
|
@@ -133,6 +135,8 @@ function getBaseUrl(provider) {
|
|
|
133
135
|
return process.env["KIMI_BASE_URL"] ?? "https://api.moonshot.ai/v1";
|
|
134
136
|
case "lmstudio":
|
|
135
137
|
return process.env["LMSTUDIO_BASE_URL"] ?? "http://localhost:1234/v1";
|
|
138
|
+
case "ollama":
|
|
139
|
+
return process.env["OLLAMA_BASE_URL"] ?? "http://localhost:11434/v1";
|
|
136
140
|
case "codex":
|
|
137
141
|
return "https://chatgpt.com/backend-api/codex/responses";
|
|
138
142
|
default:
|
|
@@ -144,22 +148,24 @@ function getDefaultModel(provider) {
|
|
|
144
148
|
case "anthropic":
|
|
145
149
|
return process.env["ANTHROPIC_MODEL"] ?? "claude-opus-4-6-20260115";
|
|
146
150
|
case "openai":
|
|
147
|
-
return process.env["OPENAI_MODEL"] ?? "gpt-5.
|
|
151
|
+
return process.env["OPENAI_MODEL"] ?? "gpt-5.3-codex";
|
|
148
152
|
case "gemini":
|
|
149
153
|
return process.env["GEMINI_MODEL"] ?? "gemini-3-flash-preview";
|
|
150
154
|
case "kimi":
|
|
151
155
|
return process.env["KIMI_MODEL"] ?? "kimi-k2.5";
|
|
152
156
|
case "lmstudio":
|
|
153
157
|
return process.env["LMSTUDIO_MODEL"] ?? "local-model";
|
|
158
|
+
case "ollama":
|
|
159
|
+
return process.env["OLLAMA_MODEL"] ?? "llama3.1";
|
|
154
160
|
case "codex":
|
|
155
|
-
return process.env["CODEX_MODEL"] ?? "gpt-5.
|
|
161
|
+
return process.env["CODEX_MODEL"] ?? "gpt-5.3-codex";
|
|
156
162
|
default:
|
|
157
|
-
return "gpt-5.
|
|
163
|
+
return "gpt-5.3-codex";
|
|
158
164
|
}
|
|
159
165
|
}
|
|
160
166
|
function getDefaultProvider() {
|
|
161
167
|
const provider = process.env["COCO_PROVIDER"]?.toLowerCase();
|
|
162
|
-
if (provider && ["anthropic", "openai", "codex", "gemini", "kimi", "lmstudio"].includes(provider)) {
|
|
168
|
+
if (provider && ["anthropic", "openai", "codex", "gemini", "kimi", "lmstudio", "ollama"].includes(provider)) {
|
|
163
169
|
return provider;
|
|
164
170
|
}
|
|
165
171
|
return "anthropic";
|
|
@@ -3894,10 +3900,10 @@ var CoverageAnalyzer = class {
|
|
|
3894
3900
|
join(this.projectPath, ".coverage", "coverage-summary.json"),
|
|
3895
3901
|
join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
|
|
3896
3902
|
];
|
|
3897
|
-
for (const
|
|
3903
|
+
for (const path37 of possiblePaths) {
|
|
3898
3904
|
try {
|
|
3899
|
-
await access(
|
|
3900
|
-
const content = await readFile(
|
|
3905
|
+
await access(path37, constants.R_OK);
|
|
3906
|
+
const content = await readFile(path37, "utf-8");
|
|
3901
3907
|
const report = JSON.parse(content);
|
|
3902
3908
|
return parseCoverageSummary(report);
|
|
3903
3909
|
} catch {
|
|
@@ -10326,7 +10332,7 @@ function createAnthropicProvider(config) {
|
|
|
10326
10332
|
}
|
|
10327
10333
|
return provider;
|
|
10328
10334
|
}
|
|
10329
|
-
var DEFAULT_MODEL2 = "gpt-5.
|
|
10335
|
+
var DEFAULT_MODEL2 = "gpt-5.3-codex";
|
|
10330
10336
|
var CONTEXT_WINDOWS2 = {
|
|
10331
10337
|
// OpenAI models
|
|
10332
10338
|
"gpt-4o": 128e3,
|
|
@@ -10337,6 +10343,10 @@ var CONTEXT_WINDOWS2 = {
|
|
|
10337
10343
|
o1: 2e5,
|
|
10338
10344
|
"o1-mini": 128e3,
|
|
10339
10345
|
"o3-mini": 2e5,
|
|
10346
|
+
"o4-mini": 2e5,
|
|
10347
|
+
// GPT-4.1 series (Feb 2026)
|
|
10348
|
+
"gpt-4.1": 1048576,
|
|
10349
|
+
"gpt-4.1-mini": 1048576,
|
|
10340
10350
|
// GPT-5 series (2025-2026)
|
|
10341
10351
|
"gpt-5": 4e5,
|
|
10342
10352
|
"gpt-5.2": 4e5,
|
|
@@ -10344,6 +10354,7 @@ var CONTEXT_WINDOWS2 = {
|
|
|
10344
10354
|
"gpt-5.2-thinking": 4e5,
|
|
10345
10355
|
"gpt-5.2-instant": 4e5,
|
|
10346
10356
|
"gpt-5.2-pro": 4e5,
|
|
10357
|
+
"gpt-5.3-codex": 4e5,
|
|
10347
10358
|
// Kimi/Moonshot models
|
|
10348
10359
|
"kimi-k2.5": 262144,
|
|
10349
10360
|
"kimi-k2-0324": 131072,
|
|
@@ -11139,10 +11150,11 @@ async function getCachedADCToken() {
|
|
|
11139
11150
|
|
|
11140
11151
|
// src/providers/codex.ts
|
|
11141
11152
|
var CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
|
|
11142
|
-
var DEFAULT_MODEL3 = "gpt-5.
|
|
11153
|
+
var DEFAULT_MODEL3 = "gpt-5.3-codex";
|
|
11143
11154
|
var CONTEXT_WINDOWS3 = {
|
|
11144
|
-
"gpt-5-codex": 2e5,
|
|
11155
|
+
"gpt-5.3-codex": 2e5,
|
|
11145
11156
|
"gpt-5.2-codex": 2e5,
|
|
11157
|
+
"gpt-5-codex": 2e5,
|
|
11146
11158
|
"gpt-5.1-codex": 2e5,
|
|
11147
11159
|
"gpt-5": 2e5,
|
|
11148
11160
|
"gpt-5.2": 2e5,
|
|
@@ -11890,6 +11902,11 @@ async function createProvider(type, config = {}) {
|
|
|
11890
11902
|
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "http://localhost:1234/v1";
|
|
11891
11903
|
mergedConfig.apiKey = mergedConfig.apiKey ?? "lm-studio";
|
|
11892
11904
|
break;
|
|
11905
|
+
case "ollama":
|
|
11906
|
+
provider = new OpenAIProvider();
|
|
11907
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "http://localhost:11434/v1";
|
|
11908
|
+
mergedConfig.apiKey = mergedConfig.apiKey ?? "ollama";
|
|
11909
|
+
break;
|
|
11893
11910
|
default:
|
|
11894
11911
|
throw new ProviderError(`Unknown provider type: ${type}`, {
|
|
11895
11912
|
provider: type
|
|
@@ -12011,9 +12028,9 @@ function createInitialState(config) {
|
|
|
12011
12028
|
}
|
|
12012
12029
|
async function loadExistingState(projectPath) {
|
|
12013
12030
|
try {
|
|
12014
|
-
const
|
|
12031
|
+
const fs36 = await import('fs/promises');
|
|
12015
12032
|
const statePath = `${projectPath}/.coco/state/project.json`;
|
|
12016
|
-
const content = await
|
|
12033
|
+
const content = await fs36.readFile(statePath, "utf-8");
|
|
12017
12034
|
const data = JSON.parse(content);
|
|
12018
12035
|
data.createdAt = new Date(data.createdAt);
|
|
12019
12036
|
data.updatedAt = new Date(data.updatedAt);
|
|
@@ -12023,13 +12040,13 @@ async function loadExistingState(projectPath) {
|
|
|
12023
12040
|
}
|
|
12024
12041
|
}
|
|
12025
12042
|
async function saveState(state) {
|
|
12026
|
-
const
|
|
12043
|
+
const fs36 = await import('fs/promises');
|
|
12027
12044
|
const statePath = `${state.path}/.coco/state`;
|
|
12028
|
-
await
|
|
12045
|
+
await fs36.mkdir(statePath, { recursive: true });
|
|
12029
12046
|
const filePath = `${statePath}/project.json`;
|
|
12030
12047
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
12031
|
-
await
|
|
12032
|
-
await
|
|
12048
|
+
await fs36.writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
12049
|
+
await fs36.rename(tmpPath, filePath);
|
|
12033
12050
|
}
|
|
12034
12051
|
function getPhaseExecutor(phase) {
|
|
12035
12052
|
switch (phase) {
|
|
@@ -12088,20 +12105,20 @@ async function createPhaseContext(config, state) {
|
|
|
12088
12105
|
};
|
|
12089
12106
|
const tools = {
|
|
12090
12107
|
file: {
|
|
12091
|
-
async read(
|
|
12092
|
-
const
|
|
12093
|
-
return
|
|
12108
|
+
async read(path37) {
|
|
12109
|
+
const fs36 = await import('fs/promises');
|
|
12110
|
+
return fs36.readFile(path37, "utf-8");
|
|
12094
12111
|
},
|
|
12095
|
-
async write(
|
|
12096
|
-
const
|
|
12112
|
+
async write(path37, content) {
|
|
12113
|
+
const fs36 = await import('fs/promises');
|
|
12097
12114
|
const nodePath = await import('path');
|
|
12098
|
-
await
|
|
12099
|
-
await
|
|
12115
|
+
await fs36.mkdir(nodePath.dirname(path37), { recursive: true });
|
|
12116
|
+
await fs36.writeFile(path37, content, "utf-8");
|
|
12100
12117
|
},
|
|
12101
|
-
async exists(
|
|
12102
|
-
const
|
|
12118
|
+
async exists(path37) {
|
|
12119
|
+
const fs36 = await import('fs/promises');
|
|
12103
12120
|
try {
|
|
12104
|
-
await
|
|
12121
|
+
await fs36.access(path37);
|
|
12105
12122
|
return true;
|
|
12106
12123
|
} catch {
|
|
12107
12124
|
return false;
|
|
@@ -12114,9 +12131,9 @@ async function createPhaseContext(config, state) {
|
|
|
12114
12131
|
},
|
|
12115
12132
|
bash: {
|
|
12116
12133
|
async exec(command, options = {}) {
|
|
12117
|
-
const { execa:
|
|
12134
|
+
const { execa: execa11 } = await import('execa');
|
|
12118
12135
|
try {
|
|
12119
|
-
const result = await
|
|
12136
|
+
const result = await execa11(command, {
|
|
12120
12137
|
shell: true,
|
|
12121
12138
|
cwd: options.cwd || state.path,
|
|
12122
12139
|
timeout: options.timeout,
|
|
@@ -12139,8 +12156,8 @@ async function createPhaseContext(config, state) {
|
|
|
12139
12156
|
},
|
|
12140
12157
|
git: {
|
|
12141
12158
|
async status() {
|
|
12142
|
-
const { execa:
|
|
12143
|
-
const result = await
|
|
12159
|
+
const { execa: execa11 } = await import('execa');
|
|
12160
|
+
const result = await execa11("git", ["status", "--porcelain", "-b"], { cwd: state.path });
|
|
12144
12161
|
const lines = result.stdout.split("\n");
|
|
12145
12162
|
const branchLine = lines[0] || "";
|
|
12146
12163
|
const branch = branchLine.replace("## ", "").split("...")[0] || "main";
|
|
@@ -12153,24 +12170,24 @@ async function createPhaseContext(config, state) {
|
|
|
12153
12170
|
};
|
|
12154
12171
|
},
|
|
12155
12172
|
async commit(message, files) {
|
|
12156
|
-
const { execa:
|
|
12173
|
+
const { execa: execa11 } = await import('execa');
|
|
12157
12174
|
if (files && files.length > 0) {
|
|
12158
|
-
await
|
|
12175
|
+
await execa11("git", ["add", ...files], { cwd: state.path });
|
|
12159
12176
|
}
|
|
12160
|
-
await
|
|
12177
|
+
await execa11("git", ["commit", "-m", message], { cwd: state.path });
|
|
12161
12178
|
},
|
|
12162
12179
|
async push() {
|
|
12163
|
-
const { execa:
|
|
12164
|
-
await
|
|
12180
|
+
const { execa: execa11 } = await import('execa');
|
|
12181
|
+
await execa11("git", ["push"], { cwd: state.path });
|
|
12165
12182
|
}
|
|
12166
12183
|
},
|
|
12167
12184
|
test: {
|
|
12168
12185
|
async run(pattern) {
|
|
12169
|
-
const { execa:
|
|
12186
|
+
const { execa: execa11 } = await import('execa');
|
|
12170
12187
|
try {
|
|
12171
12188
|
const args = ["test", "--reporter=json"];
|
|
12172
12189
|
if (pattern) args.push(pattern);
|
|
12173
|
-
await
|
|
12190
|
+
await execa11("pnpm", args, { cwd: state.path });
|
|
12174
12191
|
return {
|
|
12175
12192
|
passed: 0,
|
|
12176
12193
|
failed: 0,
|
|
@@ -12250,9 +12267,9 @@ async function createSnapshot(state) {
|
|
|
12250
12267
|
var MAX_CHECKPOINT_VERSIONS = 5;
|
|
12251
12268
|
async function getCheckpointFiles(state, phase) {
|
|
12252
12269
|
try {
|
|
12253
|
-
const
|
|
12270
|
+
const fs36 = await import('fs/promises');
|
|
12254
12271
|
const checkpointDir = `${state.path}/.coco/checkpoints`;
|
|
12255
|
-
const files = await
|
|
12272
|
+
const files = await fs36.readdir(checkpointDir);
|
|
12256
12273
|
const phaseFiles = files.filter((f) => f.startsWith(`snapshot-pre-${phase}-`) && f.endsWith(".json")).sort((a, b) => {
|
|
12257
12274
|
const tsA = parseInt(a.split("-").pop()?.replace(".json", "") ?? "0", 10);
|
|
12258
12275
|
const tsB = parseInt(b.split("-").pop()?.replace(".json", "") ?? "0", 10);
|
|
@@ -12265,11 +12282,11 @@ async function getCheckpointFiles(state, phase) {
|
|
|
12265
12282
|
}
|
|
12266
12283
|
async function cleanupOldCheckpoints(state, phase) {
|
|
12267
12284
|
try {
|
|
12268
|
-
const
|
|
12285
|
+
const fs36 = await import('fs/promises');
|
|
12269
12286
|
const files = await getCheckpointFiles(state, phase);
|
|
12270
12287
|
if (files.length > MAX_CHECKPOINT_VERSIONS) {
|
|
12271
12288
|
const filesToDelete = files.slice(MAX_CHECKPOINT_VERSIONS);
|
|
12272
|
-
await Promise.all(filesToDelete.map((f) =>
|
|
12289
|
+
await Promise.all(filesToDelete.map((f) => fs36.unlink(f).catch(() => {
|
|
12273
12290
|
})));
|
|
12274
12291
|
}
|
|
12275
12292
|
} catch {
|
|
@@ -12277,13 +12294,13 @@ async function cleanupOldCheckpoints(state, phase) {
|
|
|
12277
12294
|
}
|
|
12278
12295
|
async function saveSnapshot(state, snapshotId) {
|
|
12279
12296
|
try {
|
|
12280
|
-
const
|
|
12297
|
+
const fs36 = await import('fs/promises');
|
|
12281
12298
|
const snapshotPath = `${state.path}/.coco/checkpoints/snapshot-${snapshotId}.json`;
|
|
12282
12299
|
const snapshotDir = `${state.path}/.coco/checkpoints`;
|
|
12283
|
-
await
|
|
12300
|
+
await fs36.mkdir(snapshotDir, { recursive: true });
|
|
12284
12301
|
const createdAt = state.createdAt instanceof Date ? state.createdAt.toISOString() : String(state.createdAt);
|
|
12285
12302
|
const updatedAt = state.updatedAt instanceof Date ? state.updatedAt.toISOString() : String(state.updatedAt);
|
|
12286
|
-
await
|
|
12303
|
+
await fs36.writeFile(
|
|
12287
12304
|
snapshotPath,
|
|
12288
12305
|
JSON.stringify(
|
|
12289
12306
|
{
|
|
@@ -12381,7 +12398,7 @@ function generateId() {
|
|
|
12381
12398
|
return `proj_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
|
|
12382
12399
|
}
|
|
12383
12400
|
var ProviderConfigSchema = z.object({
|
|
12384
|
-
type: z.enum(["anthropic", "openai", "gemini", "kimi"]).default("anthropic"),
|
|
12401
|
+
type: z.enum(["anthropic", "openai", "gemini", "kimi", "lmstudio", "ollama"]).default("anthropic"),
|
|
12385
12402
|
apiKey: z.string().optional(),
|
|
12386
12403
|
model: z.string().default("claude-sonnet-4-20250514"),
|
|
12387
12404
|
maxTokens: z.number().min(1).max(2e5).default(8192),
|
|
@@ -12469,6 +12486,22 @@ var ToolsConfigSchema = z.object({
|
|
|
12469
12486
|
threshold: z.number().min(0).max(1).default(0.3)
|
|
12470
12487
|
}).optional()
|
|
12471
12488
|
});
|
|
12489
|
+
var ShipConfigSchema = z.object({
|
|
12490
|
+
/** Default base branch for PRs */
|
|
12491
|
+
defaultBaseBranch: z.string().default("main"),
|
|
12492
|
+
/** Auto-detect version bump from commit history */
|
|
12493
|
+
autoDetectBump: z.boolean().default(true),
|
|
12494
|
+
/** Use squash merge for PRs */
|
|
12495
|
+
squashMerge: z.boolean().default(true),
|
|
12496
|
+
/** Delete feature branch after merge */
|
|
12497
|
+
deleteBranchAfterMerge: z.boolean().default(true),
|
|
12498
|
+
/** Create PRs as draft by default */
|
|
12499
|
+
draftPr: z.boolean().default(false),
|
|
12500
|
+
/** CI check timeout in ms (default 10 minutes) */
|
|
12501
|
+
ciCheckTimeoutMs: z.number().default(6e5),
|
|
12502
|
+
/** CI check poll interval in ms (default 15 seconds) */
|
|
12503
|
+
ciCheckPollMs: z.number().default(15e3)
|
|
12504
|
+
});
|
|
12472
12505
|
var CocoConfigSchema = z.object({
|
|
12473
12506
|
project: ProjectConfigSchema,
|
|
12474
12507
|
provider: ProviderConfigSchema.default({
|
|
@@ -12495,7 +12528,8 @@ var CocoConfigSchema = z.object({
|
|
|
12495
12528
|
stack: StackConfigSchema.optional(),
|
|
12496
12529
|
integrations: IntegrationsConfigSchema.optional(),
|
|
12497
12530
|
mcp: MCPConfigSchema.optional(),
|
|
12498
|
-
tools: ToolsConfigSchema.optional()
|
|
12531
|
+
tools: ToolsConfigSchema.optional(),
|
|
12532
|
+
ship: ShipConfigSchema.optional()
|
|
12499
12533
|
});
|
|
12500
12534
|
function createDefaultConfigObject(projectName, language = "typescript") {
|
|
12501
12535
|
return {
|
|
@@ -16582,9 +16616,9 @@ async function fileExists(filePath) {
|
|
|
16582
16616
|
return false;
|
|
16583
16617
|
}
|
|
16584
16618
|
}
|
|
16585
|
-
async function fileExists2(
|
|
16619
|
+
async function fileExists2(path37) {
|
|
16586
16620
|
try {
|
|
16587
|
-
await access(
|
|
16621
|
+
await access(path37);
|
|
16588
16622
|
return true;
|
|
16589
16623
|
} catch {
|
|
16590
16624
|
return false;
|
|
@@ -16674,7 +16708,7 @@ async function detectMaturity(cwd) {
|
|
|
16674
16708
|
if (!hasLintConfig && hasPackageJson) {
|
|
16675
16709
|
try {
|
|
16676
16710
|
const pkgRaw = await import('fs/promises').then(
|
|
16677
|
-
(
|
|
16711
|
+
(fs36) => fs36.readFile(join(cwd, "package.json"), "utf-8")
|
|
16678
16712
|
);
|
|
16679
16713
|
const pkg = JSON.parse(pkgRaw);
|
|
16680
16714
|
if (pkg.scripts?.lint || pkg.scripts?.["lint:fix"]) {
|
|
@@ -20128,6 +20162,351 @@ var recommendBranchTool = defineTool({
|
|
|
20128
20162
|
}
|
|
20129
20163
|
});
|
|
20130
20164
|
var gitEnhancedTools = [analyzeRepoHealthTool, getCommitStatsTool, recommendBranchTool];
|
|
20165
|
+
async function ghExec(args, cwd) {
|
|
20166
|
+
try {
|
|
20167
|
+
const result = await execa("gh", args, {
|
|
20168
|
+
cwd: cwd ?? process.cwd(),
|
|
20169
|
+
timeout: 6e4
|
|
20170
|
+
});
|
|
20171
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
20172
|
+
} catch (error) {
|
|
20173
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20174
|
+
throw new ToolError(`gh command failed: ${message}`, {
|
|
20175
|
+
tool: "github",
|
|
20176
|
+
cause: error instanceof Error ? error : void 0
|
|
20177
|
+
});
|
|
20178
|
+
}
|
|
20179
|
+
}
|
|
20180
|
+
var ghCheckAuthTool = defineTool({
|
|
20181
|
+
name: "gh_check_auth",
|
|
20182
|
+
description: "Check if the GitHub CLI is installed and authenticated.",
|
|
20183
|
+
category: "git",
|
|
20184
|
+
parameters: z.object({
|
|
20185
|
+
cwd: z.string().optional()
|
|
20186
|
+
}),
|
|
20187
|
+
async execute({ cwd }) {
|
|
20188
|
+
try {
|
|
20189
|
+
const { stdout } = await ghExec(["auth", "status"], cwd);
|
|
20190
|
+
const userMatch = stdout.match(/Logged in to .+ as (\S+)/);
|
|
20191
|
+
return {
|
|
20192
|
+
authenticated: true,
|
|
20193
|
+
user: userMatch?.[1]
|
|
20194
|
+
};
|
|
20195
|
+
} catch {
|
|
20196
|
+
return { authenticated: false, error: "gh CLI not authenticated. Run: gh auth login" };
|
|
20197
|
+
}
|
|
20198
|
+
}
|
|
20199
|
+
});
|
|
20200
|
+
var ghRepoInfoTool = defineTool({
|
|
20201
|
+
name: "gh_repo_info",
|
|
20202
|
+
description: "Get GitHub repository information (name, default branch, URL).",
|
|
20203
|
+
category: "git",
|
|
20204
|
+
parameters: z.object({
|
|
20205
|
+
cwd: z.string().optional()
|
|
20206
|
+
}),
|
|
20207
|
+
async execute({ cwd }) {
|
|
20208
|
+
const { stdout } = await ghExec(
|
|
20209
|
+
["repo", "view", "--json", "name,nameWithOwner,defaultBranchRef,url,isPrivate"],
|
|
20210
|
+
cwd
|
|
20211
|
+
);
|
|
20212
|
+
const data = JSON.parse(stdout);
|
|
20213
|
+
return {
|
|
20214
|
+
name: data.name,
|
|
20215
|
+
fullName: data.nameWithOwner,
|
|
20216
|
+
defaultBranch: data.defaultBranchRef.name,
|
|
20217
|
+
url: data.url,
|
|
20218
|
+
private: data.isPrivate
|
|
20219
|
+
};
|
|
20220
|
+
}
|
|
20221
|
+
});
|
|
20222
|
+
var ghPrCreateTool = defineTool({
|
|
20223
|
+
name: "gh_pr_create",
|
|
20224
|
+
description: "Create a GitHub pull request.",
|
|
20225
|
+
category: "git",
|
|
20226
|
+
parameters: z.object({
|
|
20227
|
+
title: z.string().describe("PR title"),
|
|
20228
|
+
body: z.string().describe("PR body (markdown)"),
|
|
20229
|
+
base: z.string().optional().describe("Base branch (default: repo default)"),
|
|
20230
|
+
draft: z.boolean().optional().default(false),
|
|
20231
|
+
cwd: z.string().optional()
|
|
20232
|
+
}),
|
|
20233
|
+
async execute({ title, body, base, draft, cwd }) {
|
|
20234
|
+
const args = ["pr", "create", "--title", title, "--body", body];
|
|
20235
|
+
if (base) args.push("--base", base);
|
|
20236
|
+
if (draft) args.push("--draft");
|
|
20237
|
+
const { stdout } = await ghExec(args, cwd);
|
|
20238
|
+
const url = stdout.trim();
|
|
20239
|
+
const numberMatch = url.match(/\/pull\/(\d+)/);
|
|
20240
|
+
return {
|
|
20241
|
+
number: numberMatch ? parseInt(numberMatch[1], 10) : 0,
|
|
20242
|
+
url
|
|
20243
|
+
};
|
|
20244
|
+
}
|
|
20245
|
+
});
|
|
20246
|
+
var ghPrMergeTool = defineTool({
|
|
20247
|
+
name: "gh_pr_merge",
|
|
20248
|
+
description: "Merge a GitHub pull request.",
|
|
20249
|
+
category: "git",
|
|
20250
|
+
parameters: z.object({
|
|
20251
|
+
number: z.number().describe("PR number"),
|
|
20252
|
+
method: z.enum(["squash", "merge", "rebase"]).optional().default("squash"),
|
|
20253
|
+
deleteBranch: z.boolean().optional().default(true),
|
|
20254
|
+
subject: z.string().optional().describe("Merge commit subject line"),
|
|
20255
|
+
body: z.string().optional().describe("Merge commit body"),
|
|
20256
|
+
cwd: z.string().optional()
|
|
20257
|
+
}),
|
|
20258
|
+
async execute({ number, method, deleteBranch, subject, body, cwd }) {
|
|
20259
|
+
const args = ["pr", "merge", String(number), `--${method}`];
|
|
20260
|
+
if (deleteBranch) args.push("--delete-branch");
|
|
20261
|
+
if (subject) args.push("--subject", subject);
|
|
20262
|
+
if (body) args.push("--body", body);
|
|
20263
|
+
await ghExec(args, cwd);
|
|
20264
|
+
return { merged: true, method };
|
|
20265
|
+
}
|
|
20266
|
+
});
|
|
20267
|
+
var ghPrChecksTool = defineTool({
|
|
20268
|
+
name: "gh_pr_checks",
|
|
20269
|
+
description: "Get CI check statuses for a pull request.",
|
|
20270
|
+
category: "git",
|
|
20271
|
+
parameters: z.object({
|
|
20272
|
+
number: z.number().describe("PR number"),
|
|
20273
|
+
cwd: z.string().optional()
|
|
20274
|
+
}),
|
|
20275
|
+
async execute({ number, cwd }) {
|
|
20276
|
+
const { stdout } = await ghExec(
|
|
20277
|
+
["pr", "checks", String(number), "--json", "name,state,conclusion,detailsUrl"],
|
|
20278
|
+
cwd
|
|
20279
|
+
);
|
|
20280
|
+
const raw = JSON.parse(stdout);
|
|
20281
|
+
const checks = raw.map((c) => {
|
|
20282
|
+
let status = "pending";
|
|
20283
|
+
if (c.state === "SUCCESS" || c.conclusion === "SUCCESS") status = "pass";
|
|
20284
|
+
else if (c.state === "FAILURE" || c.conclusion === "FAILURE") status = "fail";
|
|
20285
|
+
else if (c.state === "SKIPPED" || c.conclusion === "SKIPPED") status = "skipping";
|
|
20286
|
+
return {
|
|
20287
|
+
name: c.name,
|
|
20288
|
+
status,
|
|
20289
|
+
conclusion: c.conclusion || c.state,
|
|
20290
|
+
url: c.detailsUrl
|
|
20291
|
+
};
|
|
20292
|
+
});
|
|
20293
|
+
return {
|
|
20294
|
+
checks,
|
|
20295
|
+
allPassed: checks.length > 0 && checks.every((c) => c.status === "pass" || c.status === "skipping"),
|
|
20296
|
+
anyFailed: checks.some((c) => c.status === "fail"),
|
|
20297
|
+
anyPending: checks.some((c) => c.status === "pending")
|
|
20298
|
+
};
|
|
20299
|
+
}
|
|
20300
|
+
});
|
|
20301
|
+
var ghPrListTool = defineTool({
|
|
20302
|
+
name: "gh_pr_list",
|
|
20303
|
+
description: "List pull requests, optionally filtered by head branch.",
|
|
20304
|
+
category: "git",
|
|
20305
|
+
parameters: z.object({
|
|
20306
|
+
head: z.string().optional().describe("Filter by head branch name"),
|
|
20307
|
+
state: z.string().optional().default("open"),
|
|
20308
|
+
cwd: z.string().optional()
|
|
20309
|
+
}),
|
|
20310
|
+
async execute({ head, state, cwd }) {
|
|
20311
|
+
const args = ["pr", "list", "--json", "number,title,url,state", "--state", state];
|
|
20312
|
+
if (head) args.push("--head", head);
|
|
20313
|
+
const { stdout } = await ghExec(args, cwd);
|
|
20314
|
+
const raw = JSON.parse(stdout);
|
|
20315
|
+
return { prs: raw };
|
|
20316
|
+
}
|
|
20317
|
+
});
|
|
20318
|
+
var ghReleaseCreateTool = defineTool({
|
|
20319
|
+
name: "gh_release_create",
|
|
20320
|
+
description: "Create a GitHub release with notes.",
|
|
20321
|
+
category: "git",
|
|
20322
|
+
parameters: z.object({
|
|
20323
|
+
tag: z.string().describe("Tag name (e.g., v1.2.3)"),
|
|
20324
|
+
title: z.string().optional().describe("Release title"),
|
|
20325
|
+
notes: z.string().optional().describe("Release notes (markdown)"),
|
|
20326
|
+
draft: z.boolean().optional().default(false),
|
|
20327
|
+
prerelease: z.boolean().optional().default(false),
|
|
20328
|
+
cwd: z.string().optional()
|
|
20329
|
+
}),
|
|
20330
|
+
async execute({ tag, title, notes, draft, prerelease, cwd }) {
|
|
20331
|
+
const args = ["release", "create", tag];
|
|
20332
|
+
if (title) args.push("--title", title);
|
|
20333
|
+
if (notes) args.push("--notes", notes);
|
|
20334
|
+
if (draft) args.push("--draft");
|
|
20335
|
+
if (prerelease) args.push("--prerelease");
|
|
20336
|
+
const { stdout } = await ghExec(args, cwd);
|
|
20337
|
+
return { url: stdout.trim(), tag };
|
|
20338
|
+
}
|
|
20339
|
+
});
|
|
20340
|
+
var githubTools = [
|
|
20341
|
+
ghCheckAuthTool,
|
|
20342
|
+
ghRepoInfoTool,
|
|
20343
|
+
ghPrCreateTool,
|
|
20344
|
+
ghPrMergeTool,
|
|
20345
|
+
ghPrChecksTool,
|
|
20346
|
+
ghPrListTool,
|
|
20347
|
+
ghReleaseCreateTool
|
|
20348
|
+
];
|
|
20349
|
+
var INTERPRETER_MAP = {
|
|
20350
|
+
".py": ["python3"],
|
|
20351
|
+
".sh": ["bash"],
|
|
20352
|
+
".bash": ["bash"],
|
|
20353
|
+
".zsh": ["zsh"],
|
|
20354
|
+
".js": ["node"],
|
|
20355
|
+
".ts": ["npx", "tsx"],
|
|
20356
|
+
".rb": ["ruby"],
|
|
20357
|
+
".pl": ["perl"],
|
|
20358
|
+
".lua": ["lua"],
|
|
20359
|
+
".php": ["php"]
|
|
20360
|
+
};
|
|
20361
|
+
var BLOCKED_PATHS2 = ["/etc", "/var", "/usr", "/root", "/sys", "/proc", "/boot", "/dev"];
|
|
20362
|
+
var BLOCKED_EXEC_PATTERNS = [
|
|
20363
|
+
/\.env(?:\.\w+)?$/,
|
|
20364
|
+
/\.pem$/,
|
|
20365
|
+
/\.key$/,
|
|
20366
|
+
/id_rsa/,
|
|
20367
|
+
/credentials\.\w+$/i,
|
|
20368
|
+
/secrets?\.\w+$/i
|
|
20369
|
+
];
|
|
20370
|
+
var DANGEROUS_ARG_PATTERNS = [
|
|
20371
|
+
/\brm\s+-rf\s+\/(?!\w)/,
|
|
20372
|
+
/\bsudo\s+rm/,
|
|
20373
|
+
/\bdd\s+if=.*of=\/dev\//,
|
|
20374
|
+
/`[^`]+`/,
|
|
20375
|
+
/\$\([^)]+\)/,
|
|
20376
|
+
/\beval\s+/,
|
|
20377
|
+
/\bcurl\s+.*\|\s*(ba)?sh/
|
|
20378
|
+
];
|
|
20379
|
+
function getSystemOpenCommand() {
|
|
20380
|
+
return process.platform === "darwin" ? "open" : "xdg-open";
|
|
20381
|
+
}
|
|
20382
|
+
function hasNullByte2(str) {
|
|
20383
|
+
return str.includes("\0");
|
|
20384
|
+
}
|
|
20385
|
+
function isBlockedPath(absolute) {
|
|
20386
|
+
for (const blocked of BLOCKED_PATHS2) {
|
|
20387
|
+
const normalizedBlocked = path14__default.normalize(blocked);
|
|
20388
|
+
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path14__default.sep)) {
|
|
20389
|
+
return blocked;
|
|
20390
|
+
}
|
|
20391
|
+
}
|
|
20392
|
+
return void 0;
|
|
20393
|
+
}
|
|
20394
|
+
function isBlockedExecFile(filePath) {
|
|
20395
|
+
return BLOCKED_EXEC_PATTERNS.some((p4) => p4.test(filePath));
|
|
20396
|
+
}
|
|
20397
|
+
function hasDangerousArgs(args) {
|
|
20398
|
+
const joined = args.join(" ");
|
|
20399
|
+
return DANGEROUS_ARG_PATTERNS.some((p4) => p4.test(joined));
|
|
20400
|
+
}
|
|
20401
|
+
function getInterpreter(ext) {
|
|
20402
|
+
return INTERPRETER_MAP[ext.toLowerCase()];
|
|
20403
|
+
}
|
|
20404
|
+
async function isExecutable(filePath) {
|
|
20405
|
+
try {
|
|
20406
|
+
await fs14__default.access(filePath, fs14__default.constants.X_OK);
|
|
20407
|
+
return true;
|
|
20408
|
+
} catch {
|
|
20409
|
+
return false;
|
|
20410
|
+
}
|
|
20411
|
+
}
|
|
20412
|
+
var openFileTool = defineTool({
|
|
20413
|
+
name: "open_file",
|
|
20414
|
+
description: `Open a file with the system application or execute a script/binary.
|
|
20415
|
+
|
|
20416
|
+
Mode "open" (default): Opens the file with the OS default application.
|
|
20417
|
+
- HTML files \u2192 browser
|
|
20418
|
+
- Images \u2192 image viewer
|
|
20419
|
+
- PDFs \u2192 PDF reader
|
|
20420
|
+
- Directories \u2192 file manager
|
|
20421
|
+
|
|
20422
|
+
Mode "exec": Executes a script or binary.
|
|
20423
|
+
- .py \u2192 python3, .sh \u2192 bash, .js \u2192 node, .ts \u2192 npx tsx
|
|
20424
|
+
- .rb \u2192 ruby, .pl \u2192 perl, .lua \u2192 lua, .php \u2192 php
|
|
20425
|
+
- Binaries with +x permissions \u2192 direct execution
|
|
20426
|
+
|
|
20427
|
+
Examples:
|
|
20428
|
+
- Open in browser: { "path": "docs/index.html" }
|
|
20429
|
+
- View image: { "path": "screenshot.png" }
|
|
20430
|
+
- Run script: { "path": "scripts/setup.sh", "mode": "exec" }
|
|
20431
|
+
- Run with args: { "path": "deploy.py", "mode": "exec", "args": ["--env", "staging"] }`,
|
|
20432
|
+
category: "bash",
|
|
20433
|
+
parameters: z.object({
|
|
20434
|
+
path: z.string().describe("File path to open or execute"),
|
|
20435
|
+
mode: z.enum(["open", "exec"]).optional().default("open").describe("open = system app, exec = run script"),
|
|
20436
|
+
args: z.array(z.string()).optional().default([]).describe("Arguments for exec mode"),
|
|
20437
|
+
cwd: z.string().optional().describe("Working directory"),
|
|
20438
|
+
timeout: z.number().optional().describe("Timeout in ms for exec mode (default 120000)")
|
|
20439
|
+
}),
|
|
20440
|
+
async execute({ path: filePath, mode = "open", args = [], cwd, timeout }) {
|
|
20441
|
+
const start = performance.now();
|
|
20442
|
+
if (!filePath || hasNullByte2(filePath)) {
|
|
20443
|
+
throw new ToolError("Invalid file path", { tool: "open_file" });
|
|
20444
|
+
}
|
|
20445
|
+
const workDir = cwd ?? process.cwd();
|
|
20446
|
+
const absolute = path14__default.isAbsolute(filePath) ? path14__default.normalize(filePath) : path14__default.resolve(workDir, filePath);
|
|
20447
|
+
const blockedBy = isBlockedPath(absolute);
|
|
20448
|
+
if (blockedBy) {
|
|
20449
|
+
throw new ToolError(`Access to system path '${blockedBy}' is not allowed`, {
|
|
20450
|
+
tool: "open_file"
|
|
20451
|
+
});
|
|
20452
|
+
}
|
|
20453
|
+
try {
|
|
20454
|
+
await fs14__default.access(absolute);
|
|
20455
|
+
} catch {
|
|
20456
|
+
throw new ToolError(`File not found: ${absolute}`, { tool: "open_file" });
|
|
20457
|
+
}
|
|
20458
|
+
if (mode === "open") {
|
|
20459
|
+
const cmd = getSystemOpenCommand();
|
|
20460
|
+
await execa(cmd, [absolute], { timeout: 1e4 });
|
|
20461
|
+
return {
|
|
20462
|
+
action: "opened",
|
|
20463
|
+
path: absolute,
|
|
20464
|
+
resolvedCommand: cmd,
|
|
20465
|
+
duration: performance.now() - start
|
|
20466
|
+
};
|
|
20467
|
+
}
|
|
20468
|
+
if (isBlockedExecFile(absolute)) {
|
|
20469
|
+
throw new ToolError(`Execution of sensitive file is blocked: ${path14__default.basename(absolute)}`, {
|
|
20470
|
+
tool: "open_file"
|
|
20471
|
+
});
|
|
20472
|
+
}
|
|
20473
|
+
if (args.length > 0 && hasDangerousArgs(args)) {
|
|
20474
|
+
throw new ToolError("Arguments contain dangerous patterns", { tool: "open_file" });
|
|
20475
|
+
}
|
|
20476
|
+
const ext = path14__default.extname(absolute);
|
|
20477
|
+
const interpreter = getInterpreter(ext);
|
|
20478
|
+
const executable = await isExecutable(absolute);
|
|
20479
|
+
let command;
|
|
20480
|
+
let cmdArgs;
|
|
20481
|
+
if (interpreter) {
|
|
20482
|
+
command = interpreter[0];
|
|
20483
|
+
cmdArgs = [...interpreter.slice(1), absolute, ...args];
|
|
20484
|
+
} else if (executable) {
|
|
20485
|
+
command = absolute;
|
|
20486
|
+
cmdArgs = [...args];
|
|
20487
|
+
} else {
|
|
20488
|
+
throw new ToolError(
|
|
20489
|
+
`Cannot execute '${path14__default.basename(absolute)}': no known interpreter for '${ext || "(no extension)"}' and file is not executable`,
|
|
20490
|
+
{ tool: "open_file" }
|
|
20491
|
+
);
|
|
20492
|
+
}
|
|
20493
|
+
const result = await execa(command, cmdArgs, {
|
|
20494
|
+
cwd: workDir,
|
|
20495
|
+
timeout: timeout ?? 12e4,
|
|
20496
|
+
reject: false
|
|
20497
|
+
});
|
|
20498
|
+
return {
|
|
20499
|
+
action: "executed",
|
|
20500
|
+
path: absolute,
|
|
20501
|
+
resolvedCommand: interpreter ? interpreter.join(" ") : absolute,
|
|
20502
|
+
stdout: result.stdout || void 0,
|
|
20503
|
+
stderr: result.stderr || void 0,
|
|
20504
|
+
exitCode: result.exitCode ?? 0,
|
|
20505
|
+
duration: performance.now() - start
|
|
20506
|
+
};
|
|
20507
|
+
}
|
|
20508
|
+
});
|
|
20509
|
+
var openTools = [openFileTool];
|
|
20131
20510
|
init_allowed_paths();
|
|
20132
20511
|
var BLOCKED_SYSTEM_PATHS = [
|
|
20133
20512
|
"/etc",
|
|
@@ -20259,6 +20638,8 @@ function registerAllTools(registry) {
|
|
|
20259
20638
|
...contextEnhancerTools,
|
|
20260
20639
|
...skillEnhancerTools,
|
|
20261
20640
|
...gitEnhancedTools,
|
|
20641
|
+
...githubTools,
|
|
20642
|
+
...openTools,
|
|
20262
20643
|
...authorizePathTools
|
|
20263
20644
|
];
|
|
20264
20645
|
for (const tool of allTools) {
|