@selfagency/beans-mcp 0.5.0 → 0.6.1

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/index.cjs CHANGED
@@ -22905,6 +22905,26 @@ Output: ${stdout.slice(0, 1e3)}`
22905
22905
  });
22906
22906
  return { initialized: true };
22907
22907
  }
22908
+ async archive() {
22909
+ const { stdout } = await execFileAsync(this.cliPath, ["archive", "--json"], {
22910
+ cwd: this.workspaceRoot,
22911
+ env: this.getSafeEnv(),
22912
+ maxBuffer: 10 * 1024 * 1024,
22913
+ timeout: 3e4
22914
+ });
22915
+ this.invalidateCache();
22916
+ if (!stdout.trim()) {
22917
+ return { archived: true };
22918
+ }
22919
+ try {
22920
+ return JSON.parse(stdout);
22921
+ } catch {
22922
+ return { archived: true, output: stdout.trim() };
22923
+ }
22924
+ }
22925
+ async queryGraphql(query, variables) {
22926
+ return this.executeGraphQL(query, variables);
22927
+ }
22908
22928
  async list(options) {
22909
22929
  const filter = {};
22910
22930
  if (options?.status && options.status.length > 0) {
@@ -23059,32 +23079,39 @@ Output: ${stdout.slice(0, 1e3)}`
23059
23079
  return { deleted: true, beanId };
23060
23080
  }
23061
23081
  async bulkCreate(beans, defaultParent) {
23062
- const results = [];
23063
- for (const item of beans) {
23064
- try {
23065
- const bean = await this.create({
23082
+ const settled = await Promise.allSettled(
23083
+ beans.map(
23084
+ async (item) => this.create({
23066
23085
  ...item,
23067
23086
  parent: item.parent ?? defaultParent
23068
- });
23069
- results.push({ bean });
23070
- } catch (error48) {
23071
- results.push({ error: error48.message });
23072
- }
23073
- }
23074
- return results;
23087
+ })
23088
+ )
23089
+ );
23090
+ return settled.map(
23091
+ (result) => result.status === "fulfilled" ? { bean: result.value } : { error: result.reason instanceof Error ? result.reason.message : String(result.reason) }
23092
+ );
23075
23093
  }
23076
23094
  async bulkUpdate(beans, defaultParent) {
23077
- const results = [];
23078
- for (const { beanId, ...updates } of beans) {
23079
- try {
23095
+ const settled = await Promise.allSettled(
23096
+ beans.map(async ({ beanId, ...updates }) => {
23080
23097
  const resolvedParent = updates.parent ?? (updates.clearParent ? void 0 : defaultParent);
23081
23098
  const bean = await this.update(beanId, { ...updates, parent: resolvedParent });
23082
- results.push({ beanId, bean });
23083
- } catch (error48) {
23084
- results.push({ beanId, error: error48.message });
23099
+ return { beanId, bean };
23100
+ })
23101
+ );
23102
+ return settled.map((result, index) => {
23103
+ const beanId = beans[index]?.beanId;
23104
+ if (!beanId) {
23105
+ return { beanId: "unknown", error: "Unknown bean id" };
23085
23106
  }
23086
- }
23087
- return results;
23107
+ if (result.status === "fulfilled") {
23108
+ return result.value;
23109
+ }
23110
+ return {
23111
+ beanId,
23112
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
23113
+ };
23114
+ });
23088
23115
  }
23089
23116
  async openConfig() {
23090
23117
  const configPath = (0, import_node_path2.join)(this.workspaceRoot, ".beans.yml");
@@ -23119,9 +23146,12 @@ Output: ${stdout.slice(0, 1e3)}`
23119
23146
  const outputPath = (0, import_node_path2.resolve)(
23120
23147
  process.env.BEANS_VSCODE_OUTPUT_LOG || (0, import_node_path2.join)(this.workspaceRoot, ".vscode", "logs", "beans-output.log")
23121
23148
  );
23122
- const isWithinWorkspace = isPathWithinRoot(this.workspaceRoot, outputPath);
23149
+ const canonicalOutputPath = await (0, import_promises.realpath)(outputPath).catch(() => outputPath);
23150
+ const canonicalWorkspaceRoot = await (0, import_promises.realpath)(this.workspaceRoot).catch(() => (0, import_node_path2.resolve)(this.workspaceRoot));
23151
+ const isWithinWorkspace = isPathWithinRoot(canonicalWorkspaceRoot, canonicalOutputPath);
23123
23152
  const vscodeLogDir = process.env.BEANS_VSCODE_LOG_DIR || this.logDir ? (0, import_node_path2.resolve)(process.env.BEANS_VSCODE_LOG_DIR || this.logDir || "") : void 0;
23124
- const isWithinVscodeLogDir = vscodeLogDir ? isPathWithinRoot(vscodeLogDir, outputPath) : false;
23153
+ const canonicalVscodeLogDir = vscodeLogDir ? await (0, import_promises.realpath)(vscodeLogDir).catch(() => (0, import_node_path2.resolve)(vscodeLogDir)) : void 0;
23154
+ const isWithinVscodeLogDir = canonicalVscodeLogDir ? isPathWithinRoot(canonicalVscodeLogDir, canonicalOutputPath) : false;
23125
23155
  if (!isWithinWorkspace && !isWithinVscodeLogDir) {
23126
23156
  throw new Error("Output log path must stay within the workspace or VS Code log directory");
23127
23157
  }
@@ -23200,6 +23230,105 @@ Output: ${stdout.slice(0, 1e3)}`
23200
23230
  escapeForYamlDoubleQuoted(value) {
23201
23231
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
23202
23232
  }
23233
+ shouldQuoteFrontmatterValue(value) {
23234
+ return !/^[A-Za-z0-9._-]+$/.test(value);
23235
+ }
23236
+ parseFrontmatterLine(line) {
23237
+ const separatorIndex = line.indexOf(":");
23238
+ if (separatorIndex <= 0) {
23239
+ return null;
23240
+ }
23241
+ const key = line.slice(0, separatorIndex).trim();
23242
+ if (key.length === 0) {
23243
+ return null;
23244
+ }
23245
+ for (const character of key) {
23246
+ const isAlphaNumericUnderscore = character >= "a" && character <= "z" || character >= "A" && character <= "Z" || character >= "0" && character <= "9" || character === "_";
23247
+ if (!isAlphaNumericUnderscore) {
23248
+ return null;
23249
+ }
23250
+ }
23251
+ const rawValue = line.slice(separatorIndex + 1).trimStart();
23252
+ return { key, rawValue };
23253
+ }
23254
+ buildFrontmatterIndex(frontmatterLines) {
23255
+ const indexByKey = /* @__PURE__ */ new Map();
23256
+ frontmatterLines.forEach((line, index) => {
23257
+ const parsed = this.parseFrontmatterLine(line);
23258
+ if (!parsed) {
23259
+ return;
23260
+ }
23261
+ indexByKey.set(parsed.key, index);
23262
+ });
23263
+ return indexByKey;
23264
+ }
23265
+ serializeFrontmatterValue(key, value) {
23266
+ if (Array.isArray(value)) {
23267
+ return JSON.stringify(value);
23268
+ }
23269
+ if (key === "title") {
23270
+ return this.normalizeFrontmatterTitleValue(value);
23271
+ }
23272
+ if (this.shouldQuoteFrontmatterValue(value)) {
23273
+ return `"${this.escapeForYamlDoubleQuoted(value)}"`;
23274
+ }
23275
+ return value;
23276
+ }
23277
+ deserializeFrontmatterValue(value) {
23278
+ const trimmed = value.trim();
23279
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
23280
+ try {
23281
+ const parsed = JSON.parse(trimmed);
23282
+ if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
23283
+ return parsed;
23284
+ }
23285
+ } catch {
23286
+ }
23287
+ }
23288
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
23289
+ return trimmed.slice(1, -1).replaceAll('\\"', '"').replaceAll("\\\\", "\\").replaceAll("''", "'");
23290
+ }
23291
+ return trimmed;
23292
+ }
23293
+ splitFrontmatterDocument(content) {
23294
+ const crlfOpen = content.startsWith("---\r\n");
23295
+ const lfOpen = content.startsWith("---\n");
23296
+ const eol = crlfOpen ? "\r\n" : "\n";
23297
+ if (!crlfOpen && !lfOpen) {
23298
+ return { eol: "\n", hasFrontmatter: false, frontmatterLines: [], body: content };
23299
+ }
23300
+ const openEnd = `---${eol}`.length;
23301
+ const closeMarker = `${eol}---`;
23302
+ const closeIdx = content.indexOf(closeMarker, openEnd);
23303
+ if (closeIdx === -1) {
23304
+ return { eol, hasFrontmatter: false, frontmatterLines: [], body: content };
23305
+ }
23306
+ const frontmatter = content.slice(openEnd, closeIdx);
23307
+ const body = content.slice(closeIdx + closeMarker.length);
23308
+ return {
23309
+ eol,
23310
+ hasFrontmatter: true,
23311
+ frontmatterLines: frontmatter.length > 0 ? frontmatter.split(eol) : [],
23312
+ body
23313
+ };
23314
+ }
23315
+ parseFrontmatterFields(frontmatterLines) {
23316
+ const fields = {};
23317
+ for (const line of frontmatterLines) {
23318
+ const parsed = this.parseFrontmatterLine(line);
23319
+ if (!parsed) {
23320
+ continue;
23321
+ }
23322
+ const { valuePart } = this.splitYamlInlineComment(parsed.rawValue);
23323
+ fields[parsed.key] = this.deserializeFrontmatterValue(valuePart);
23324
+ }
23325
+ return fields;
23326
+ }
23327
+ async writeFileAtomically(absolutePath, content) {
23328
+ const tempPath = `${absolutePath}.tmp-${process.pid}-${Date.now()}`;
23329
+ await (0, import_promises.writeFile)(tempPath, content, "utf8");
23330
+ await (0, import_promises.rename)(tempPath, absolutePath);
23331
+ }
23203
23332
  /**
23204
23333
  * Normalise a raw YAML title value to a double-quoted scalar.
23205
23334
  * Handles: empty, already double-quoted, single-quoted (unescaping `''`),
@@ -23270,14 +23399,66 @@ Output: ${stdout.slice(0, 1e3)}`
23270
23399
  await (0, import_promises.writeFile)(absolutePath, fixed, "utf8");
23271
23400
  return { path: absolutePath, bytes: Buffer.byteLength(fixed, "utf8") };
23272
23401
  }
23402
+ async updateBeanFrontmatter(relativePath, updates) {
23403
+ const absolutePath = this.resolveBeanFilePath(relativePath);
23404
+ const content = await (0, import_promises.readFile)(absolutePath, "utf8");
23405
+ const { eol, hasFrontmatter, frontmatterLines, body } = this.splitFrontmatterDocument(content);
23406
+ const updatedFields = Object.entries(updates).filter(([, value]) => value !== void 0).map(([key]) => key);
23407
+ if (updatedFields.length === 0) {
23408
+ throw new Error("At least one frontmatter field update is required");
23409
+ }
23410
+ const nextLines = [...frontmatterLines];
23411
+ let indexByKey = this.buildFrontmatterIndex(nextLines);
23412
+ for (const [key, value] of Object.entries(updates)) {
23413
+ if (value === void 0) {
23414
+ continue;
23415
+ }
23416
+ const existingIndex = indexByKey.get(key);
23417
+ if (value === null) {
23418
+ if (existingIndex !== void 0) {
23419
+ nextLines.splice(existingIndex, 1);
23420
+ indexByKey = this.buildFrontmatterIndex(nextLines);
23421
+ }
23422
+ continue;
23423
+ }
23424
+ const serialized = `${key}: ${this.serializeFrontmatterValue(key, value)}`;
23425
+ if (existingIndex !== void 0) {
23426
+ const existingLine = nextLines[existingIndex] ?? "";
23427
+ const existingParsed = this.parseFrontmatterLine(existingLine);
23428
+ const commentPart = existingParsed ? this.splitYamlInlineComment(existingParsed.rawValue).commentPart : "";
23429
+ nextLines[existingIndex] = `${serialized}${commentPart}`;
23430
+ } else {
23431
+ nextLines.push(serialized);
23432
+ indexByKey.set(key, nextLines.length - 1);
23433
+ }
23434
+ }
23435
+ const frontmatterBlock = nextLines.length > 0 ? nextLines.join(eol) : "";
23436
+ const nextContent = hasFrontmatter ? `---${eol}${frontmatterBlock}${eol}---${body}` : `---${eol}${frontmatterBlock}${eol}---${eol}${body}`;
23437
+ const fixed = this.quoteFrontmatterTitles(nextContent);
23438
+ await this.writeFileAtomically(absolutePath, fixed);
23439
+ return {
23440
+ path: absolutePath,
23441
+ bytes: Buffer.byteLength(fixed, "utf8"),
23442
+ updatedFields,
23443
+ frontmatter: this.parseFrontmatterFields(this.splitFrontmatterDocument(fixed).frontmatterLines)
23444
+ };
23445
+ }
23273
23446
  async createBeanFile(relativePath, content, options) {
23274
23447
  const absolutePath = this.resolveBeanFilePath(relativePath);
23275
23448
  const fixed = this.quoteFrontmatterTitles(content);
23276
23449
  await (0, import_promises.mkdir)((0, import_node_path2.dirname)(absolutePath), { recursive: true });
23277
- await (0, import_promises.writeFile)(absolutePath, fixed, {
23278
- encoding: "utf8",
23279
- flag: options?.overwrite ? "w" : "wx"
23280
- });
23450
+ try {
23451
+ await (0, import_promises.writeFile)(absolutePath, fixed, {
23452
+ encoding: "utf8",
23453
+ flag: options?.overwrite ? "w" : "wx"
23454
+ });
23455
+ } catch (error48) {
23456
+ const maybeNodeError = error48;
23457
+ if (maybeNodeError.code === "EEXIST" && !options?.overwrite) {
23458
+ throw new Error("Bean file already exists. Pass overwrite=true to replace it.");
23459
+ }
23460
+ throw error48;
23461
+ }
23281
23462
  return {
23282
23463
  path: absolutePath,
23283
23464
  bytes: Buffer.byteLength(fixed, "utf8"),
@@ -23408,6 +23589,7 @@ __export(src_exports, {
23408
23589
  DEFAULT_MCP_PORT: () => DEFAULT_MCP_PORT,
23409
23590
  MAX_ID_LENGTH: () => MAX_ID_LENGTH,
23410
23591
  MAX_METADATA_LENGTH: () => MAX_METADATA_LENGTH,
23592
+ MAX_PATH_LENGTH: () => MAX_PATH_LENGTH,
23411
23593
  MAX_TITLE_LENGTH: () => MAX_TITLE_LENGTH,
23412
23594
  createBeansMcpServer: () => createBeansMcpServer,
23413
23595
  isPathWithinRoot: () => isPathWithinRoot,
@@ -31385,14 +31567,16 @@ var import_node_util2 = require("util");
31385
31567
  // package.json
31386
31568
  var package_default = {
31387
31569
  name: "@selfagency/beans-mcp",
31388
- version: "0.5.0",
31570
+ version: "0.6.1",
31389
31571
  private: false,
31390
31572
  description: "MCP (Model Context Protocol) server for Beans issue tracker",
31391
- author: {
31392
- name: "Daniel Sieradski",
31393
- email: "daniel@self.agency",
31394
- url: "https://self.agency"
31395
- },
31573
+ keywords: [
31574
+ "ai",
31575
+ "beans",
31576
+ "issue-tracker",
31577
+ "mcp",
31578
+ "model-context-protocol"
31579
+ ],
31396
31580
  homepage: "https://github.com/selfagency/beans-mcp",
31397
31581
  bugs: {
31398
31582
  url: "https://github.com/selfagency/beans-mcp/issues"
@@ -31401,15 +31585,12 @@ var package_default = {
31401
31585
  type: "git",
31402
31586
  url: "git+https://github.com/selfagency/beans-mcp.git"
31403
31587
  },
31404
- mcpName: "io.github.selfagency/beans-mcp",
31405
- keywords: [
31406
- "beans",
31407
- "mcp",
31408
- "model-context-protocol",
31409
- "issue-tracker",
31410
- "ai"
31411
- ],
31412
31588
  license: "MIT",
31589
+ author: {
31590
+ name: "Daniel Sieradski",
31591
+ email: "daniel@self.agency",
31592
+ url: "https://self.agency"
31593
+ },
31413
31594
  type: "module",
31414
31595
  exports: {
31415
31596
  ".": {
@@ -31424,46 +31605,57 @@ var package_default = {
31424
31605
  bin: {
31425
31606
  "beans-mcp": "dist/beans-mcp-server.cjs"
31426
31607
  },
31608
+ files: [
31609
+ "dist",
31610
+ "skills",
31611
+ "README.md",
31612
+ "LICENSE.txt"
31613
+ ],
31427
31614
  scripts: {
31428
31615
  build: "tsup",
31616
+ postbuild: "node ./scripts/write-dist-package.js",
31617
+ "docs:build": "vitepress build docs",
31618
+ "docs:dev": "vitepress dev docs",
31619
+ "docs:preview": "vitepress preview docs",
31429
31620
  format: "oxfmt",
31430
- "lint:fix": "oxlint --fix",
31431
31621
  lint: "oxlint",
31432
- postbuild: "node ./scripts/write-dist-package.js",
31622
+ "lint:fix": "oxlint --fix",
31433
31623
  prepare: "husky",
31434
31624
  release: "zx ./scripts/release.js",
31625
+ test: "vitest run",
31435
31626
  "test:coverage": "vitest run --coverage",
31436
31627
  "test:watch": "vitest",
31437
- test: "vitest run",
31438
31628
  "type-check": "tsc --noEmit"
31439
31629
  },
31630
+ "lint-staged": {
31631
+ "src/**/*.ts": [
31632
+ "pnpm run lint:fix",
31633
+ "pnpm run format"
31634
+ ]
31635
+ },
31440
31636
  devDependencies: {
31441
31637
  "@modelcontextprotocol/sdk": "^1.29.0",
31442
31638
  "@octokit/rest": "^22.0.1",
31443
- "@types/node": "25.5.2",
31444
- "@vitest/coverage-v8": "^4.1.2",
31445
- "@vitest/ui": "4.1.2",
31639
+ "@types/node": "25.6.0",
31640
+ "@vitest/coverage-v8": "^4.1.4",
31641
+ "@vitest/ui": "4.1.4",
31446
31642
  husky: "^9.1.7",
31447
31643
  "lint-staged": "^16.4.0",
31448
31644
  ora: "^9.3.0",
31449
- oxfmt: "^0.43.0",
31450
- oxlint: "^1.58.0",
31451
- "oxlint-tsgolint": "^0.20.0",
31645
+ oxfmt: "^0.45.0",
31646
+ oxlint: "^1.60.0",
31647
+ "oxlint-tsgolint": "^0.21.1",
31452
31648
  tsup: "8.5.1",
31453
- typescript: "6.0.2",
31454
- vitest: "4.1.2",
31649
+ typescript: "6.0.3",
31650
+ vitepress: "^1.6.4",
31651
+ vitest: "4.1.4",
31455
31652
  zod: "4.3.6",
31456
31653
  zx: "^8.8.5"
31457
31654
  },
31458
31655
  engines: {
31459
31656
  node: ">=18"
31460
31657
  },
31461
- "lint-staged": {
31462
- "src/**/*.ts": [
31463
- "pnpm run lint:fix",
31464
- "pnpm run format"
31465
- ]
31466
- }
31658
+ mcpName: "io.github.selfagency/beans-mcp"
31467
31659
  };
31468
31660
 
31469
31661
  // src/internal/queryHelpers.ts
@@ -31628,6 +31820,8 @@ var MAX_PATH_LENGTH = 1024;
31628
31820
  init_utils();
31629
31821
  var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
31630
31822
  var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
31823
+ var CLOSED_STATUSES = /* @__PURE__ */ new Set(["completed", "scrapped"]);
31824
+ var BEAN_ID_HINT = "Missing required field `beanId`. Did you mean `beanId`?";
31631
31825
  function getSafeCliEnv(env) {
31632
31826
  const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
31633
31827
  const safeEnv = {};
@@ -31648,7 +31842,8 @@ function extractVersionFromOutput(output) {
31648
31842
  if (!trimmed) {
31649
31843
  return null;
31650
31844
  }
31651
- const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
31845
+ const versionRegex = /(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
31846
+ const match = versionRegex.exec(trimmed);
31652
31847
  return match?.[1] ?? null;
31653
31848
  }
31654
31849
  async function detectBeansCliVersion(cliPath, workspaceRoot) {
@@ -31665,10 +31860,10 @@ ${stderr}`);
31665
31860
  return null;
31666
31861
  }
31667
31862
  }
31668
- async function getBeanById(backend, beanId) {
31863
+ async function getBeanById(backend, beanId, beans) {
31669
31864
  try {
31670
- const beans = await backend.list();
31671
- const found = beans.find((b) => b.id === beanId);
31865
+ const allBeans = beans ?? await backend.list();
31866
+ const found = allBeans.find((b) => b.id === beanId);
31672
31867
  if (!found) {
31673
31868
  throw new Error(`Bean not found: ${beanId}`);
31674
31869
  }
@@ -31677,12 +31872,111 @@ async function getBeanById(backend, beanId) {
31677
31872
  throw new Error(`Failed to fetch bean ${beanId}: ${error48.message}`);
31678
31873
  }
31679
31874
  }
31875
+ function collectDescendantBeans(beans, rootBeanId) {
31876
+ const byParent = /* @__PURE__ */ new Map();
31877
+ const byId = new Map(beans.map((bean) => [bean.id, bean]));
31878
+ for (const bean of beans) {
31879
+ if (!bean.parentId) {
31880
+ continue;
31881
+ }
31882
+ const children = byParent.get(bean.parentId) ?? [];
31883
+ children.push(bean.id);
31884
+ byParent.set(bean.parentId, children);
31885
+ }
31886
+ const queue = [...byParent.get(rootBeanId) ?? []];
31887
+ const visited = /* @__PURE__ */ new Set();
31888
+ const descendants = [];
31889
+ while (queue.length > 0) {
31890
+ const currentId = queue.shift();
31891
+ if (!currentId || visited.has(currentId)) {
31892
+ continue;
31893
+ }
31894
+ visited.add(currentId);
31895
+ const currentBean = byId.get(currentId);
31896
+ if (!currentBean) {
31897
+ continue;
31898
+ }
31899
+ descendants.push(currentBean);
31900
+ const children = byParent.get(currentId);
31901
+ if (children && children.length > 0) {
31902
+ queue.push(...children);
31903
+ }
31904
+ }
31905
+ return descendants;
31906
+ }
31907
+ async function cascadeStatusToDescendants(backend, rootBeanId, targetStatus, options) {
31908
+ const beans = options?.beans ?? await backend.list();
31909
+ const descendants = collectDescendantBeans(beans, rootBeanId);
31910
+ const updatedBeanIds = [];
31911
+ const skippedBeanIds = [];
31912
+ const errors = [];
31913
+ const toUpdate = [];
31914
+ for (const bean of descendants) {
31915
+ if (options?.onlyCurrentStatuses && !options.onlyCurrentStatuses.has(bean.status)) {
31916
+ skippedBeanIds.push(bean.id);
31917
+ continue;
31918
+ }
31919
+ toUpdate.push(bean);
31920
+ }
31921
+ const settled = await Promise.allSettled(
31922
+ toUpdate.map(async (bean) => backend.update(bean.id, { status: targetStatus }))
31923
+ );
31924
+ settled.forEach((result, index) => {
31925
+ const bean = toUpdate[index];
31926
+ if (!bean) {
31927
+ return;
31928
+ }
31929
+ if (result.status === "fulfilled") {
31930
+ updatedBeanIds.push(bean.id);
31931
+ return;
31932
+ }
31933
+ errors.push({
31934
+ beanId: bean.id,
31935
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
31936
+ });
31937
+ });
31938
+ return {
31939
+ totalDescendants: descendants.length,
31940
+ updatedBeanIds,
31941
+ skippedBeanIds,
31942
+ errors
31943
+ };
31944
+ }
31945
+ function completeMarkdownTasks(body) {
31946
+ const lines = body.split(/\r?\n/);
31947
+ let totalTaskCount = 0;
31948
+ let updatedTaskCount = 0;
31949
+ const taskLinePattern = /^\s*(?:[-*+]|\d+\.)\s+\[[ xX]\]/;
31950
+ const uncheckedTaskLinePattern = /^(\s*(?:[-*+]|\d+\.)\s+\[)\s(\].*)$/;
31951
+ const nextLines = lines.map((line) => {
31952
+ if (!taskLinePattern.test(line)) {
31953
+ return line;
31954
+ }
31955
+ totalTaskCount += 1;
31956
+ const uncheckedMatch = uncheckedTaskLinePattern.exec(line);
31957
+ if (!uncheckedMatch) {
31958
+ return line;
31959
+ }
31960
+ updatedTaskCount += 1;
31961
+ return `${uncheckedMatch[1]}x${uncheckedMatch[2]}`;
31962
+ });
31963
+ const nextBody = nextLines.join("\n");
31964
+ return { nextBody, totalTaskCount, updatedTaskCount };
31965
+ }
31680
31966
  function initHandler(backend) {
31681
31967
  return async ({ prefix }) => {
31682
31968
  const result = await backend.init(prefix);
31683
31969
  return makeTextAndStructured(result);
31684
31970
  };
31685
31971
  }
31972
+ function archiveHandler(backend) {
31973
+ return async () => {
31974
+ if (typeof backend.archive !== "function") {
31975
+ throw new TypeError("Archive is not supported by the current backend");
31976
+ }
31977
+ return makeTextAndStructured(await backend.archive());
31978
+ };
31979
+ }
31686
31980
  function viewHandler(backend) {
31687
31981
  return async ({ beanId, beanIds }) => {
31688
31982
  const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
@@ -31720,7 +32014,14 @@ async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
31720
32014
  }
31721
32015
  }
31722
32016
  function createHandler(backend) {
31723
- return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
32017
+ return async (input) => {
32018
+ const bean = await backend.create(input);
32019
+ const warnings = input.description !== void 0 ? ["`description` is deprecated; use `body` instead."] : void 0;
32020
+ return makeTextAndStructured({
32021
+ bean,
32022
+ ...warnings ? { warnings } : {}
32023
+ });
32024
+ };
31724
32025
  }
31725
32026
  function editHandler(backend) {
31726
32027
  return async ({
@@ -31734,18 +32035,30 @@ function reopenHandler(backend) {
31734
32035
  requiredCurrentStatus,
31735
32036
  targetStatus
31736
32037
  }) => {
31737
- const bean = await getBeanById(backend, beanId);
31738
- if (bean.status !== requiredCurrentStatus) {
31739
- throw new Error(`Bean ${beanId} is not ${requiredCurrentStatus}`);
32038
+ const beans = await backend.list();
32039
+ const bean = await getBeanById(backend, beanId, beans);
32040
+ if (bean.status === requiredCurrentStatus) {
32041
+ const updatedParentBean = await backend.update(beanId, { status: targetStatus });
32042
+ const cascade = await cascadeStatusToDescendants(backend, beanId, targetStatus, {
32043
+ onlyCurrentStatuses: CLOSED_STATUSES,
32044
+ beans
32045
+ });
32046
+ return makeTextAndStructured({
32047
+ bean: updatedParentBean,
32048
+ cascade: {
32049
+ totalDescendants: cascade.totalDescendants,
32050
+ updatedBeanIds: cascade.updatedBeanIds,
32051
+ skippedBeanIds: cascade.skippedBeanIds,
32052
+ errors: cascade.errors
32053
+ }
32054
+ });
31740
32055
  }
31741
- return makeTextAndStructured({
31742
- bean: await backend.update(beanId, { status: targetStatus })
31743
- });
32056
+ throw new Error(`Bean ${beanId} is not ${requiredCurrentStatus}`);
31744
32057
  };
31745
32058
  }
31746
32059
  function updateHandler(backend) {
31747
- return async (input) => makeTextAndStructured({
31748
- bean: await backend.update(input.beanId, {
32060
+ return async (input) => {
32061
+ const updatedBean = await backend.update(input.beanId, {
31749
32062
  status: input.status,
31750
32063
  type: input.type,
31751
32064
  priority: input.priority,
@@ -31757,8 +32070,37 @@ function updateHandler(backend) {
31757
32070
  bodyAppend: input.bodyAppend,
31758
32071
  bodyReplace: input.bodyReplace,
31759
32072
  ifMatch: input.ifMatch
31760
- })
31761
- });
32073
+ });
32074
+ const closeStatus = input.status;
32075
+ const shouldCascadeClose = Boolean(closeStatus && CLOSED_STATUSES.has(closeStatus));
32076
+ const cascade = shouldCascadeClose ? await cascadeStatusToDescendants(backend, input.beanId, closeStatus, {
32077
+ beans: await backend.list()
32078
+ }) : null;
32079
+ return makeTextAndStructured({
32080
+ bean: updatedBean,
32081
+ ...cascade ? {
32082
+ cascade: {
32083
+ totalDescendants: cascade.totalDescendants,
32084
+ updatedBeanIds: cascade.updatedBeanIds,
32085
+ skippedBeanIds: cascade.skippedBeanIds,
32086
+ errors: cascade.errors
32087
+ }
32088
+ } : {}
32089
+ });
32090
+ };
32091
+ }
32092
+ function completeTasksHandler(backend) {
32093
+ return async ({ beanId }) => {
32094
+ const bean = await getBeanById(backend, beanId);
32095
+ const { nextBody, totalTaskCount, updatedTaskCount } = completeMarkdownTasks(bean.body || "");
32096
+ const updatedBean = updatedTaskCount > 0 ? await backend.update(beanId, { body: nextBody }) : bean;
32097
+ return makeTextAndStructured({
32098
+ bean: updatedBean,
32099
+ totalTaskCount,
32100
+ updatedTaskCount,
32101
+ unchangedTaskCount: totalTaskCount - updatedTaskCount
32102
+ });
32103
+ };
31762
32104
  }
31763
32105
  function deleteHandler(backend) {
31764
32106
  return async ({ beanId, beanIds, force }) => {
@@ -31812,11 +32154,17 @@ function deleteHandler(backend) {
31812
32154
  function bulkCreateHandler(backend) {
31813
32155
  return async (input) => {
31814
32156
  const results = await backend.bulkCreate(input.beans, input.parent);
32157
+ const deprecatedDescriptionCount = input.beans.filter((bean) => bean.description !== void 0).length;
31815
32158
  return makeTextAndStructured({
31816
32159
  results,
31817
32160
  requestedCount: input.beans.length,
31818
32161
  successCount: results.filter((r) => r.bean).length,
31819
- failedCount: results.filter((r) => r.error).length
32162
+ failedCount: results.filter((r) => r.error).length,
32163
+ ...deprecatedDescriptionCount > 0 ? {
32164
+ warnings: [
32165
+ `Found ${deprecatedDescriptionCount} bean(s) using deprecated field \`description\`; use \`body\` instead.`
32166
+ ]
32167
+ } : {}
31820
32168
  });
31821
32169
  };
31822
32170
  }
@@ -31832,14 +32180,24 @@ function bulkUpdateHandler(backend) {
31832
32180
  };
31833
32181
  }
31834
32182
  function queryHandler(backend) {
31835
- return async (opts) => handleQueryOperation(backend, opts);
32183
+ return async (opts) => {
32184
+ if (opts.operation === "graphql") {
32185
+ if (typeof backend.queryGraphql !== "function") {
32186
+ throw new TypeError("GraphQL passthrough is not supported by the current backend");
32187
+ }
32188
+ const result = await backend.queryGraphql(opts.graphql || "", opts.variables);
32189
+ return makeTextAndStructured({ data: result.data, errors: result.errors ?? [] });
32190
+ }
32191
+ return handleQueryOperation(backend, opts);
32192
+ };
31836
32193
  }
31837
32194
  function beanFileHandler(backend) {
31838
32195
  return async ({
31839
32196
  operation,
31840
32197
  path,
31841
32198
  content,
31842
- overwrite
32199
+ overwrite,
32200
+ fields
31843
32201
  }) => {
31844
32202
  if (operation === "read") {
31845
32203
  return makeTextAndStructured(await backend.readBeanFile(path));
@@ -31850,6 +32208,9 @@ function beanFileHandler(backend) {
31850
32208
  if (operation === "create") {
31851
32209
  return makeTextAndStructured(await backend.createBeanFile(path, content || "", { overwrite }));
31852
32210
  }
32211
+ if (operation === "update_frontmatter") {
32212
+ return makeTextAndStructured(await backend.updateBeanFrontmatter(path, fields || {}));
32213
+ }
31853
32214
  if (operation === "delete") {
31854
32215
  return makeTextAndStructured(await backend.deleteBeanFile(path));
31855
32216
  }
@@ -31884,6 +32245,21 @@ function registerTools(server, backend) {
31884
32245
  },
31885
32246
  initHandler(backend)
31886
32247
  );
32248
+ server.registerTool(
32249
+ "beans_archive",
32250
+ {
32251
+ title: "Archive Beans",
32252
+ description: "Archive completed or scrapped beans, equivalent to the beans CLI archive command.",
32253
+ inputSchema: external_exports3.object({}),
32254
+ annotations: {
32255
+ readOnlyHint: false,
32256
+ destructiveHint: false,
32257
+ idempotentHint: false,
32258
+ openWorldHint: false
32259
+ }
32260
+ },
32261
+ archiveHandler(backend)
32262
+ );
31887
32263
  server.registerTool(
31888
32264
  "beans_view",
31889
32265
  {
@@ -31893,7 +32269,7 @@ function registerTools(server, backend) {
31893
32269
  beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31894
32270
  beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
31895
32271
  }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31896
- message: "Either beanId or beanIds must be provided"
32272
+ message: `Either beanId or beanIds must be provided. ${BEAN_ID_HINT}`
31897
32273
  }),
31898
32274
  annotations: {
31899
32275
  readOnlyHint: true,
@@ -31933,7 +32309,7 @@ function registerTools(server, backend) {
31933
32309
  title: "Edit Bean Metadata",
31934
32310
  description: "Update bean metadata fields (status/type/priority/parent/blocking).",
31935
32311
  inputSchema: external_exports3.object({
31936
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32312
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31937
32313
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31938
32314
  type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31939
32315
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
@@ -31941,7 +32317,11 @@ function registerTools(server, backend) {
31941
32317
  clearParent: external_exports3.boolean().optional(),
31942
32318
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31943
32319
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional()
31944
- }),
32320
+ }).superRefine((input, ctx) => {
32321
+ if (!input.beanId) {
32322
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32323
+ }
32324
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
31945
32325
  annotations: {
31946
32326
  readOnlyHint: false,
31947
32327
  destructiveHint: false,
@@ -31957,10 +32337,14 @@ function registerTools(server, backend) {
31957
32337
  title: "Reopen Bean",
31958
32338
  description: "Reopen a completed or scrapped bean into a non-closed status.",
31959
32339
  inputSchema: external_exports3.object({
31960
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32340
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31961
32341
  requiredCurrentStatus: external_exports3.enum(["completed", "scrapped"]),
31962
32342
  targetStatus: external_exports3.string().max(MAX_METADATA_LENGTH).default("todo")
31963
- }),
32343
+ }).superRefine((input, ctx) => {
32344
+ if (!input.beanId) {
32345
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32346
+ }
32347
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
31964
32348
  annotations: {
31965
32349
  readOnlyHint: false,
31966
32350
  destructiveHint: false,
@@ -31976,7 +32360,7 @@ function registerTools(server, backend) {
31976
32360
  title: "Update Bean",
31977
32361
  description: "Update bean metadata fields (status/type/priority/parent/blocking). Consolidated replacement for per-field update tools.",
31978
32362
  inputSchema: external_exports3.object({
31979
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32363
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31980
32364
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31981
32365
  type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31982
32366
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
@@ -31993,12 +32377,16 @@ function registerTools(server, backend) {
31993
32377
  })
31994
32378
  ).optional(),
31995
32379
  ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
32380
+ }).superRefine((input, ctx) => {
32381
+ if (!input.beanId) {
32382
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32383
+ }
31996
32384
  }).refine(
31997
32385
  (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
31998
32386
  {
31999
32387
  message: "body cannot be combined with bodyAppend/bodyReplace"
32000
32388
  }
32001
- ),
32389
+ ).transform((input) => ({ ...input, beanId: input.beanId })),
32002
32390
  annotations: {
32003
32391
  readOnlyHint: false,
32004
32392
  destructiveHint: false,
@@ -32018,7 +32406,7 @@ function registerTools(server, backend) {
32018
32406
  beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
32019
32407
  force: external_exports3.boolean().default(false)
32020
32408
  }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
32021
- message: "Either beanId or beanIds must be provided"
32409
+ message: `Either beanId or beanIds must be provided. ${BEAN_ID_HINT}`
32022
32410
  }),
32023
32411
  annotations: {
32024
32412
  readOnlyHint: false,
@@ -32057,7 +32445,7 @@ function registerTools(server, backend) {
32057
32445
  bulkCreateHandler(backend)
32058
32446
  );
32059
32447
  const beanUpdateItemSchema = external_exports3.object({
32060
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32448
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
32061
32449
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32062
32450
  type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32063
32451
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
@@ -32069,10 +32457,14 @@ function registerTools(server, backend) {
32069
32457
  bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32070
32458
  bodyReplace: external_exports3.array(external_exports3.object({ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH), new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH) })).optional(),
32071
32459
  ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
32460
+ }).superRefine((input, ctx) => {
32461
+ if (!input.beanId) {
32462
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32463
+ }
32072
32464
  }).refine(
32073
32465
  (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
32074
32466
  { message: "body cannot be combined with bodyAppend/bodyReplace" }
32075
- );
32467
+ ).transform((input) => ({ ...input, beanId: input.beanId }));
32076
32468
  server.registerTool(
32077
32469
  "beans_bulk_update",
32078
32470
  {
@@ -32091,25 +32483,56 @@ function registerTools(server, backend) {
32091
32483
  },
32092
32484
  bulkUpdateHandler(backend)
32093
32485
  );
32486
+ server.registerTool(
32487
+ "beans_complete_tasks",
32488
+ {
32489
+ title: "Complete Markdown Tasks",
32490
+ description: "Mark all markdown checklist tasks within a bean as completed.",
32491
+ inputSchema: external_exports3.object({
32492
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional()
32493
+ }).superRefine((input, ctx) => {
32494
+ if (!input.beanId) {
32495
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32496
+ }
32497
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
32498
+ annotations: {
32499
+ readOnlyHint: false,
32500
+ destructiveHint: false,
32501
+ idempotentHint: true,
32502
+ openWorldHint: false
32503
+ }
32504
+ },
32505
+ completeTasksHandler(backend)
32506
+ );
32094
32507
  server.registerTool(
32095
32508
  "beans_query",
32096
32509
  {
32097
32510
  title: "Query Beans",
32098
32511
  description: "Unified query tool for refresh, filter, search, and sort operations.",
32099
32512
  inputSchema: external_exports3.object({
32100
- operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
32513
+ operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config", "graphql"]).default("refresh"),
32101
32514
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
32102
32515
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32103
32516
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32104
32517
  search: external_exports3.string().max(MAX_TITLE_LENGTH).optional(),
32105
32518
  includeClosed: external_exports3.boolean().optional(),
32106
32519
  tags: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32520
+ graphql: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32521
+ variables: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional(),
32107
32522
  writeToWorkspaceInstructions: external_exports3.boolean().optional()
32523
+ }).superRefine((input, ctx) => {
32524
+ if (input.operation === "graphql" && (!input.graphql || input.graphql.trim().length === 0)) {
32525
+ ctx.addIssue({
32526
+ code: external_exports3.ZodIssueCode.custom,
32527
+ path: ["graphql"],
32528
+ message: "graphql query string is required when operation is graphql"
32529
+ });
32530
+ }
32108
32531
  }),
32109
32532
  annotations: {
32110
- readOnlyHint: true,
32533
+ readOnlyHint: false,
32111
32534
  destructiveHint: false,
32112
- idempotentHint: true,
32535
+ idempotentHint: false,
32113
32536
  openWorldHint: false
32114
32537
  }
32115
32538
  },
@@ -32121,10 +32544,33 @@ function registerTools(server, backend) {
32121
32544
  title: "Bean File Operations",
32122
32545
  description: "Read, create, edit, or delete files under .beans (operation param).",
32123
32546
  inputSchema: external_exports3.object({
32124
- operation: external_exports3.enum(["read", "edit", "create", "delete"]),
32547
+ operation: external_exports3.enum(["read", "edit", "create", "delete", "update_frontmatter"]),
32125
32548
  path: external_exports3.string().min(1).max(MAX_PATH_LENGTH),
32126
32549
  content: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32127
- overwrite: external_exports3.boolean().optional()
32550
+ overwrite: external_exports3.boolean().optional(),
32551
+ fields: external_exports3.object({
32552
+ title: external_exports3.string().max(MAX_TITLE_LENGTH).optional(),
32553
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32554
+ type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32555
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32556
+ parent_id: external_exports3.string().max(MAX_ID_LENGTH).nullable().optional(),
32557
+ tags: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32558
+ blocking_ids: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).nullable().optional(),
32559
+ blocked_by_ids: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).nullable().optional(),
32560
+ pr: external_exports3.string().max(MAX_TITLE_LENGTH).nullable().optional(),
32561
+ branch: external_exports3.string().max(MAX_TITLE_LENGTH).nullable().optional()
32562
+ }).optional()
32563
+ }).superRefine((input, ctx) => {
32564
+ if (input.operation === "update_frontmatter") {
32565
+ const fieldCount = Object.values(input.fields || {}).filter((value) => value !== void 0).length;
32566
+ if (fieldCount === 0) {
32567
+ ctx.addIssue({
32568
+ code: external_exports3.ZodIssueCode.custom,
32569
+ path: ["fields"],
32570
+ message: "At least one frontmatter field update is required"
32571
+ });
32572
+ }
32573
+ }
32128
32574
  }),
32129
32575
  annotations: {
32130
32576
  readOnlyHint: false,
@@ -32164,6 +32610,18 @@ var MutableBackend = class {
32164
32610
  init(prefix) {
32165
32611
  return this.inner.init(prefix);
32166
32612
  }
32613
+ archive() {
32614
+ if (typeof this.inner.archive === "function") {
32615
+ return this.inner.archive();
32616
+ }
32617
+ throw new TypeError("Archive is not supported by backend");
32618
+ }
32619
+ queryGraphql(query, variables) {
32620
+ if (typeof this.inner.queryGraphql === "function") {
32621
+ return this.inner.queryGraphql(query, variables);
32622
+ }
32623
+ throw new TypeError("GraphQL passthrough is not supported by backend");
32624
+ }
32167
32625
  list(opts) {
32168
32626
  return this.inner.list(opts);
32169
32627
  }
@@ -32203,6 +32661,9 @@ var MutableBackend = class {
32203
32661
  editBeanFile(path, content) {
32204
32662
  return this.inner.editBeanFile(path, content);
32205
32663
  }
32664
+ updateBeanFrontmatter(path, updates) {
32665
+ return this.inner.updateBeanFrontmatter(path, updates);
32666
+ }
32206
32667
  createBeanFile(path, content, opts) {
32207
32668
  return this.inner.createBeanFile(path, content, opts);
32208
32669
  }
@@ -32269,6 +32730,16 @@ function parseCliArgs(argv) {
32269
32730
  const envPort = Number.parseInt(process.env.BEANS_VSCODE_MCP_PORT || process.env.BEANS_MCP_PORT || "", 10);
32270
32731
  let port = Number.isInteger(envPort) && envPort > 0 ? envPort : DEFAULT_MCP_PORT;
32271
32732
  let logDir;
32733
+ const parseStrictPositiveInt = (raw, flagName) => {
32734
+ if (!/^\d+$/.test(raw)) {
32735
+ throw new Error(`Invalid value for ${flagName}: ${raw}`);
32736
+ }
32737
+ const parsed = Number.parseInt(raw, 10);
32738
+ if (!Number.isInteger(parsed) || parsed <= 0) {
32739
+ throw new Error(`Invalid value for ${flagName}: ${raw}`);
32740
+ }
32741
+ return parsed;
32742
+ };
32272
32743
  for (let i = 0; i < argv.length; i += 1) {
32273
32744
  const arg = argv[i];
32274
32745
  if ((arg === "--workspace" || arg === "--workspace-root") && argv[i + 1]) {
@@ -32282,10 +32753,7 @@ function parseCliArgs(argv) {
32282
32753
  }
32283
32754
  i += 1;
32284
32755
  } else if (arg === "--port" && argv[i + 1]) {
32285
- const parsedPort = Number.parseInt(argv[i + 1], 10);
32286
- if (Number.isInteger(parsedPort) && parsedPort > 0) {
32287
- port = parsedPort;
32288
- }
32756
+ port = parseStrictPositiveInt(argv[i + 1], "--port");
32289
32757
  i += 1;
32290
32758
  } else if (arg === "--log-dir" && argv[i + 1]) {
32291
32759
  logDir = argv[i + 1];
@@ -32349,6 +32817,7 @@ init_utils();
32349
32817
  DEFAULT_MCP_PORT,
32350
32818
  MAX_ID_LENGTH,
32351
32819
  MAX_METADATA_LENGTH,
32820
+ MAX_PATH_LENGTH,
32352
32821
  MAX_TITLE_LENGTH,
32353
32822
  createBeansMcpServer,
32354
32823
  isPathWithinRoot,