@corbat-tech/coco 1.2.3 → 1.4.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/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.2-codex";
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.2-codex";
161
+ return process.env["CODEX_MODEL"] ?? "gpt-5.3-codex";
156
162
  default:
157
- return "gpt-5.2-codex";
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 path36 of possiblePaths) {
3903
+ for (const path37 of possiblePaths) {
3898
3904
  try {
3899
- await access(path36, constants.R_OK);
3900
- const content = await readFile(path36, "utf-8");
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.2-codex";
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.2-codex";
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 fs35 = await import('fs/promises');
12031
+ const fs36 = await import('fs/promises');
12015
12032
  const statePath = `${projectPath}/.coco/state/project.json`;
12016
- const content = await fs35.readFile(statePath, "utf-8");
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 fs35 = await import('fs/promises');
12043
+ const fs36 = await import('fs/promises');
12027
12044
  const statePath = `${state.path}/.coco/state`;
12028
- await fs35.mkdir(statePath, { recursive: true });
12045
+ await fs36.mkdir(statePath, { recursive: true });
12029
12046
  const filePath = `${statePath}/project.json`;
12030
12047
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
12031
- await fs35.writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8");
12032
- await fs35.rename(tmpPath, filePath);
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(path36) {
12092
- const fs35 = await import('fs/promises');
12093
- return fs35.readFile(path36, "utf-8");
12108
+ async read(path37) {
12109
+ const fs36 = await import('fs/promises');
12110
+ return fs36.readFile(path37, "utf-8");
12094
12111
  },
12095
- async write(path36, content) {
12096
- const fs35 = await import('fs/promises');
12112
+ async write(path37, content) {
12113
+ const fs36 = await import('fs/promises');
12097
12114
  const nodePath = await import('path');
12098
- await fs35.mkdir(nodePath.dirname(path36), { recursive: true });
12099
- await fs35.writeFile(path36, content, "utf-8");
12115
+ await fs36.mkdir(nodePath.dirname(path37), { recursive: true });
12116
+ await fs36.writeFile(path37, content, "utf-8");
12100
12117
  },
12101
- async exists(path36) {
12102
- const fs35 = await import('fs/promises');
12118
+ async exists(path37) {
12119
+ const fs36 = await import('fs/promises');
12103
12120
  try {
12104
- await fs35.access(path36);
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: execa9 } = await import('execa');
12134
+ const { execa: execa11 } = await import('execa');
12118
12135
  try {
12119
- const result = await execa9(command, {
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: execa9 } = await import('execa');
12143
- const result = await execa9("git", ["status", "--porcelain", "-b"], { cwd: state.path });
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: execa9 } = await import('execa');
12173
+ const { execa: execa11 } = await import('execa');
12157
12174
  if (files && files.length > 0) {
12158
- await execa9("git", ["add", ...files], { cwd: state.path });
12175
+ await execa11("git", ["add", ...files], { cwd: state.path });
12159
12176
  }
12160
- await execa9("git", ["commit", "-m", message], { cwd: state.path });
12177
+ await execa11("git", ["commit", "-m", message], { cwd: state.path });
12161
12178
  },
12162
12179
  async push() {
12163
- const { execa: execa9 } = await import('execa');
12164
- await execa9("git", ["push"], { cwd: state.path });
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: execa9 } = await import('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 execa9("pnpm", args, { cwd: state.path });
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 fs35 = await import('fs/promises');
12270
+ const fs36 = await import('fs/promises');
12254
12271
  const checkpointDir = `${state.path}/.coco/checkpoints`;
12255
- const files = await fs35.readdir(checkpointDir);
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 fs35 = await import('fs/promises');
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) => fs35.unlink(f).catch(() => {
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 fs35 = await import('fs/promises');
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 fs35.mkdir(snapshotDir, { recursive: true });
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 fs35.writeFile(
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(path36) {
16619
+ async function fileExists2(path37) {
16586
16620
  try {
16587
- await access(path36);
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
- (fs35) => fs35.readFile(join(cwd, "package.json"), "utf-8")
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) {