opencode-gbk-tools 0.1.6 → 0.1.8

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.
@@ -16457,14 +16457,14 @@ function buildNoMatchMessage(content, oldString) {
16457
16457
  getNearestContext(content, oldString)
16458
16458
  ].join("\n");
16459
16459
  }
16460
- function tryLooseBlockReplace(content, oldString, newString) {
16461
- const normalizedContent = normalizeNewlines(content);
16462
- const contentLines = splitNormalizedLines(content);
16463
- const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
16464
- const newLines = splitNormalizedLines(newString);
16465
- if (oldLines.length === 0) {
16466
- return null;
16467
- }
16460
+ function hasLineNumberPrefixes(lines) {
16461
+ const nonEmpty = lines.filter((l) => l.trim().length > 0);
16462
+ return nonEmpty.length > 0 && nonEmpty.every((l) => /^\d+: /.test(l));
16463
+ }
16464
+ function stripLineNumberPrefixes(lines) {
16465
+ return lines.map((l) => l.replace(/^\d+: /, ""));
16466
+ }
16467
+ function matchLooseBlock(contentLines, oldLines, newLines, newlineStyle, content) {
16468
16468
  for (let start = 0; start < contentLines.length; start += 1) {
16469
16469
  let contentIndex = start;
16470
16470
  let oldIndex = 0;
@@ -16494,12 +16494,32 @@ function tryLooseBlockReplace(content, oldString, newString) {
16494
16494
  ].join("\n");
16495
16495
  return {
16496
16496
  occurrencesBefore: 1,
16497
- content: detectNewlineStyle(content) === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
16497
+ content: newlineStyle === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
16498
16498
  };
16499
16499
  }
16500
16500
  }
16501
16501
  return null;
16502
16502
  }
16503
+ function tryLooseBlockReplace(content, oldString, newString) {
16504
+ const contentLines = splitNormalizedLines(content);
16505
+ const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
16506
+ const newLines = splitNormalizedLines(newString);
16507
+ const newlineStyle = detectNewlineStyle(content);
16508
+ if (oldLines.length === 0) {
16509
+ return null;
16510
+ }
16511
+ const result = matchLooseBlock(contentLines, oldLines, newLines, newlineStyle, content);
16512
+ if (result !== null) {
16513
+ return result;
16514
+ }
16515
+ if (hasLineNumberPrefixes(oldLines)) {
16516
+ const strippedOldLines = trimTrailingEmptyLines(stripLineNumberPrefixes(oldLines));
16517
+ if (strippedOldLines.length > 0) {
16518
+ return matchLooseBlock(contentLines, strippedOldLines, newLines, newlineStyle, content);
16519
+ }
16520
+ }
16521
+ return null;
16522
+ }
16503
16523
  function countOccurrences(text, target) {
16504
16524
  if (target.length === 0) {
16505
16525
  throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
@@ -16725,12 +16745,28 @@ var gbk_edit_default = tool({
16725
16745
  Reads the FULL file content regardless of file size \u2014 not limited by gbk_read's line window.
16726
16746
  Safe to use on files with more than 2000 lines.
16727
16747
 
16748
+ CRITICAL \u2014 do NOT include line number prefixes in oldString or newString:
16749
+ gbk_read output looks like "3787: SENDMSG 0 content". The "3787: " is a navigation prefix, NOT file content.
16750
+ oldString must be the raw file content: "SENDMSG 0 content" (no line number prefix).
16751
+ Including line numbers in oldString will cause GBK_NO_MATCH even if the content exists.
16752
+
16753
+ CRITICAL \u2014 do NOT use bare newlines or whitespace-only as oldString:
16754
+ Using oldString="
16755
+ " or oldString="" to append content will always fail.
16756
+ To append content to the END of a file, use gbk_write with append=true instead:
16757
+ gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
16758
+
16759
+ Recommended workflow for large files (when gbk_read returned truncated=true):
16760
+ 1. gbk_search(pattern) \u2192 find exact lineNumber
16761
+ 2. gbk_read(offset=<lineNumber>, limit=20) \u2192 get the exact block (strip "N: " prefixes)
16762
+ 3. gbk_edit(oldString=<content without prefixes>, newString=<new content>)
16763
+
16728
16764
  For large files, use 'startLine'/'endLine' or 'startAnchor'/'endAnchor' to narrow the search scope
16729
16765
  and avoid false matches. Scoped edits also improve performance on very large files.`,
16730
16766
  args: {
16731
16767
  filePath: tool.schema.string().describe("Target file path"),
16732
- oldString: tool.schema.string().describe("Exact text to replace (must match file content, not gbk_read output with line numbers)"),
16733
- newString: tool.schema.string().describe("Replacement text"),
16768
+ oldString: tool.schema.string().describe("Exact text to replace \u2014 raw file content only, no 'N: ' line number prefixes from gbk_read output"),
16769
+ newString: tool.schema.string().describe("Replacement text \u2014 raw content only, no line number prefixes"),
16734
16770
  replaceAll: tool.schema.boolean().optional().describe("Replace all occurrences (default: false, requires unique match)"),
16735
16771
  startLine: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Restrict edit scope to 1-based start line (inclusive)"),
16736
16772
  endLine: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Restrict edit scope to 1-based end line (inclusive)"),
@@ -16609,14 +16609,23 @@ var gbk_read_default = tool({
16609
16609
  Returns up to 'limit' lines (default 2000) starting from 'offset'.
16610
16610
  When the file has more lines than the window, 'truncated' is true and 'totalLines' shows the full count.
16611
16611
 
16612
- IMPORTANT: If 'truncated' is true, the returned content is incomplete.
16613
- DO NOT use the returned content as 'oldString' for gbk_edit on a truncated file.
16614
- To edit content beyond the visible window, use gbk_edit with 'startLine'/'endLine' to target the exact range,
16615
- or read the specific range first with 'offset' set to the desired line number.`,
16612
+ IMPORTANT \u2014 line number format: each output line is prefixed with "N: " (e.g. "3787: content").
16613
+ These prefixes are for navigation only. Strip them before using any line as 'oldString' in gbk_edit.
16614
+
16615
+ IMPORTANT \u2014 do NOT use large limits to read the whole file:
16616
+ - Setting limit=40000 or similar to "read everything" is WRONG. It produces an enormous response
16617
+ that is unreliable to process and may be truncated by the protocol.
16618
+ - To find content in a large file, use gbk_search instead.
16619
+ - To read a specific section, set offset=<lineNumber> and limit=<small number like 10-30>.
16620
+
16621
+ Workflow when truncated=true:
16622
+ 1. gbk_search(pattern) \u2192 get exact lineNumber
16623
+ 2. gbk_read(offset=<lineNumber>, limit=20) \u2192 get the exact block
16624
+ 3. gbk_edit(oldString=<exact content without line prefixes>) \u2192 edit reliably`,
16616
16625
  args: {
16617
16626
  filePath: tool.schema.string().describe("Target file path"),
16618
16627
  offset: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("1-based start line (default: 1)"),
16619
- limit: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Number of lines to read (default: 2000). Use -1 to apply the default."),
16628
+ limit: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Number of lines to read (default: 2000, max recommended: 200). Use -1 to apply the default."),
16620
16629
  tail: tool.schema.boolean().optional().describe("Read last N lines instead of offset-based window"),
16621
16630
  encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding (default: gbk)"),
16622
16631
  allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
@@ -16314,9 +16314,49 @@ async function writeGbkFile(input) {
16314
16314
  const encoding = input.encoding ?? "gbk";
16315
16315
  const createDirectories = input.createDirectories ?? true;
16316
16316
  const overwrite = input.overwrite ?? false;
16317
+ const append = input.append ?? false;
16317
16318
  const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
16318
16319
  const parent = path2.dirname(candidatePath);
16319
16320
  assertEncodingSupported(encoding);
16321
+ if (append) {
16322
+ try {
16323
+ const parentStat = await fs2.stat(parent);
16324
+ if (!parentStat.isDirectory()) {
16325
+ throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
16326
+ }
16327
+ } catch (error45) {
16328
+ if (error45 instanceof Error && "code" in error45 && error45.code === "ENOENT") {
16329
+ if (!createDirectories) {
16330
+ throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
16331
+ }
16332
+ await fs2.mkdir(parent, { recursive: true });
16333
+ } else if (error45 instanceof Error && "code" in error45) {
16334
+ throw error45;
16335
+ }
16336
+ }
16337
+ let existingContent = "";
16338
+ let existed = false;
16339
+ try {
16340
+ const existingBuffer = await fs2.readFile(candidatePath);
16341
+ existingContent = import_iconv_lite.default.decode(existingBuffer, encoding);
16342
+ existed = true;
16343
+ } catch (error45) {
16344
+ if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
16345
+ throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${candidatePath}`, error45);
16346
+ }
16347
+ }
16348
+ const combined = existingContent + input.content;
16349
+ const buffer = import_iconv_lite.default.encode(combined, encoding);
16350
+ await fs2.writeFile(candidatePath, buffer);
16351
+ return {
16352
+ filePath: candidatePath,
16353
+ encoding,
16354
+ bytesWritten: buffer.byteLength,
16355
+ created: !existed,
16356
+ overwritten: false,
16357
+ appended: true
16358
+ };
16359
+ }
16320
16360
  try {
16321
16361
  const stat = await fs2.stat(candidatePath);
16322
16362
  if (stat.isDirectory()) {
@@ -16370,13 +16410,24 @@ async function writeGbkFile(input) {
16370
16410
 
16371
16411
  // src/tools/gbk_write.ts
16372
16412
  var gbk_write_default = tool({
16373
- description: "Write GBK encoded text files",
16413
+ description: `Write GBK encoded text files.
16414
+
16415
+ **append=true** (recommended for adding content to existing files):
16416
+ - Reads the existing file content and appends new content at the end.
16417
+ - Works whether the file exists or not (creates it if missing).
16418
+ - Use this whenever you want to add lines/content to an existing GBK file.
16419
+ - Example: gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
16420
+
16421
+ **overwrite=true**: Replace the entire file with new content.
16422
+
16423
+ **Default (overwrite=false, append=false)**: Fails if the file already exists.`,
16374
16424
  args: {
16375
16425
  filePath: tool.schema.string().describe("Target file path"),
16376
- content: tool.schema.string().describe("Text content to write"),
16426
+ content: tool.schema.string().describe("Text content to write (or append)"),
16377
16427
  encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
16378
16428
  createDirectories: tool.schema.boolean().optional().describe("Create parent directories"),
16379
- overwrite: tool.schema.boolean().optional().describe("Overwrite existing file"),
16429
+ overwrite: tool.schema.boolean().optional().describe("Overwrite existing file (replaces entire content)"),
16430
+ append: tool.schema.boolean().optional().describe("Append content to existing file instead of overwriting; creates the file if it does not exist"),
16380
16431
  allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
16381
16432
  },
16382
16433
  async execute(args, context) {
@@ -16508,14 +16508,14 @@ function buildNoMatchMessage(content, oldString) {
16508
16508
  getNearestContext(content, oldString)
16509
16509
  ].join("\n");
16510
16510
  }
16511
- function tryLooseBlockReplace(content, oldString, newString) {
16512
- const normalizedContent = normalizeNewlines(content);
16513
- const contentLines = splitNormalizedLines(content);
16514
- const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
16515
- const newLines = splitNormalizedLines(newString);
16516
- if (oldLines.length === 0) {
16517
- return null;
16518
- }
16511
+ function hasLineNumberPrefixes(lines) {
16512
+ const nonEmpty = lines.filter((l) => l.trim().length > 0);
16513
+ return nonEmpty.length > 0 && nonEmpty.every((l) => /^\d+: /.test(l));
16514
+ }
16515
+ function stripLineNumberPrefixes(lines) {
16516
+ return lines.map((l) => l.replace(/^\d+: /, ""));
16517
+ }
16518
+ function matchLooseBlock(contentLines, oldLines, newLines, newlineStyle, content) {
16519
16519
  for (let start = 0; start < contentLines.length; start += 1) {
16520
16520
  let contentIndex = start;
16521
16521
  let oldIndex = 0;
@@ -16545,12 +16545,32 @@ function tryLooseBlockReplace(content, oldString, newString) {
16545
16545
  ].join("\n");
16546
16546
  return {
16547
16547
  occurrencesBefore: 1,
16548
- content: detectNewlineStyle(content) === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
16548
+ content: newlineStyle === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
16549
16549
  };
16550
16550
  }
16551
16551
  }
16552
16552
  return null;
16553
16553
  }
16554
+ function tryLooseBlockReplace(content, oldString, newString) {
16555
+ const contentLines = splitNormalizedLines(content);
16556
+ const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
16557
+ const newLines = splitNormalizedLines(newString);
16558
+ const newlineStyle = detectNewlineStyle(content);
16559
+ if (oldLines.length === 0) {
16560
+ return null;
16561
+ }
16562
+ const result = matchLooseBlock(contentLines, oldLines, newLines, newlineStyle, content);
16563
+ if (result !== null) {
16564
+ return result;
16565
+ }
16566
+ if (hasLineNumberPrefixes(oldLines)) {
16567
+ const strippedOldLines = trimTrailingEmptyLines(stripLineNumberPrefixes(oldLines));
16568
+ if (strippedOldLines.length > 0) {
16569
+ return matchLooseBlock(contentLines, strippedOldLines, newLines, newlineStyle, content);
16570
+ }
16571
+ }
16572
+ return null;
16573
+ }
16554
16574
  function countOccurrences(text, target) {
16555
16575
  if (target.length === 0) {
16556
16576
  throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
@@ -16964,9 +16984,49 @@ async function writeGbkFile(input) {
16964
16984
  const encoding = input.encoding ?? "gbk";
16965
16985
  const createDirectories = input.createDirectories ?? true;
16966
16986
  const overwrite = input.overwrite ?? false;
16987
+ const append = input.append ?? false;
16967
16988
  const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
16968
16989
  const parent = path2.dirname(candidatePath);
16969
16990
  assertEncodingSupported(encoding);
16991
+ if (append) {
16992
+ try {
16993
+ const parentStat = await fs2.stat(parent);
16994
+ if (!parentStat.isDirectory()) {
16995
+ throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
16996
+ }
16997
+ } catch (error45) {
16998
+ if (error45 instanceof Error && "code" in error45 && error45.code === "ENOENT") {
16999
+ if (!createDirectories) {
17000
+ throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
17001
+ }
17002
+ await fs2.mkdir(parent, { recursive: true });
17003
+ } else if (error45 instanceof Error && "code" in error45) {
17004
+ throw error45;
17005
+ }
17006
+ }
17007
+ let existingContent = "";
17008
+ let existed = false;
17009
+ try {
17010
+ const existingBuffer = await fs2.readFile(candidatePath);
17011
+ existingContent = import_iconv_lite.default.decode(existingBuffer, encoding);
17012
+ existed = true;
17013
+ } catch (error45) {
17014
+ if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
17015
+ throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${candidatePath}`, error45);
17016
+ }
17017
+ }
17018
+ const combined = existingContent + input.content;
17019
+ const buffer = import_iconv_lite.default.encode(combined, encoding);
17020
+ await fs2.writeFile(candidatePath, buffer);
17021
+ return {
17022
+ filePath: candidatePath,
17023
+ encoding,
17024
+ bytesWritten: buffer.byteLength,
17025
+ created: !existed,
17026
+ overwritten: false,
17027
+ appended: true
17028
+ };
17029
+ }
16970
17030
  try {
16971
17031
  const stat = await fs2.stat(candidatePath);
16972
17032
  if (stat.isDirectory()) {
@@ -17025,12 +17085,28 @@ var gbk_edit_default = tool({
17025
17085
  Reads the FULL file content regardless of file size \u2014 not limited by gbk_read's line window.
17026
17086
  Safe to use on files with more than 2000 lines.
17027
17087
 
17088
+ CRITICAL \u2014 do NOT include line number prefixes in oldString or newString:
17089
+ gbk_read output looks like "3787: SENDMSG 0 content". The "3787: " is a navigation prefix, NOT file content.
17090
+ oldString must be the raw file content: "SENDMSG 0 content" (no line number prefix).
17091
+ Including line numbers in oldString will cause GBK_NO_MATCH even if the content exists.
17092
+
17093
+ CRITICAL \u2014 do NOT use bare newlines or whitespace-only as oldString:
17094
+ Using oldString="
17095
+ " or oldString="" to append content will always fail.
17096
+ To append content to the END of a file, use gbk_write with append=true instead:
17097
+ gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
17098
+
17099
+ Recommended workflow for large files (when gbk_read returned truncated=true):
17100
+ 1. gbk_search(pattern) \u2192 find exact lineNumber
17101
+ 2. gbk_read(offset=<lineNumber>, limit=20) \u2192 get the exact block (strip "N: " prefixes)
17102
+ 3. gbk_edit(oldString=<content without prefixes>, newString=<new content>)
17103
+
17028
17104
  For large files, use 'startLine'/'endLine' or 'startAnchor'/'endAnchor' to narrow the search scope
17029
17105
  and avoid false matches. Scoped edits also improve performance on very large files.`,
17030
17106
  args: {
17031
17107
  filePath: tool.schema.string().describe("Target file path"),
17032
- oldString: tool.schema.string().describe("Exact text to replace (must match file content, not gbk_read output with line numbers)"),
17033
- newString: tool.schema.string().describe("Replacement text"),
17108
+ oldString: tool.schema.string().describe("Exact text to replace \u2014 raw file content only, no 'N: ' line number prefixes from gbk_read output"),
17109
+ newString: tool.schema.string().describe("Replacement text \u2014 raw content only, no line number prefixes"),
17034
17110
  replaceAll: tool.schema.boolean().optional().describe("Replace all occurrences (default: false, requires unique match)"),
17035
17111
  startLine: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Restrict edit scope to 1-based start line (inclusive)"),
17036
17112
  endLine: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Restrict edit scope to 1-based end line (inclusive)"),
@@ -17052,14 +17128,23 @@ var gbk_read_default = tool({
17052
17128
  Returns up to 'limit' lines (default 2000) starting from 'offset'.
17053
17129
  When the file has more lines than the window, 'truncated' is true and 'totalLines' shows the full count.
17054
17130
 
17055
- IMPORTANT: If 'truncated' is true, the returned content is incomplete.
17056
- DO NOT use the returned content as 'oldString' for gbk_edit on a truncated file.
17057
- To edit content beyond the visible window, use gbk_edit with 'startLine'/'endLine' to target the exact range,
17058
- or read the specific range first with 'offset' set to the desired line number.`,
17131
+ IMPORTANT \u2014 line number format: each output line is prefixed with "N: " (e.g. "3787: content").
17132
+ These prefixes are for navigation only. Strip them before using any line as 'oldString' in gbk_edit.
17133
+
17134
+ IMPORTANT \u2014 do NOT use large limits to read the whole file:
17135
+ - Setting limit=40000 or similar to "read everything" is WRONG. It produces an enormous response
17136
+ that is unreliable to process and may be truncated by the protocol.
17137
+ - To find content in a large file, use gbk_search instead.
17138
+ - To read a specific section, set offset=<lineNumber> and limit=<small number like 10-30>.
17139
+
17140
+ Workflow when truncated=true:
17141
+ 1. gbk_search(pattern) \u2192 get exact lineNumber
17142
+ 2. gbk_read(offset=<lineNumber>, limit=20) \u2192 get the exact block
17143
+ 3. gbk_edit(oldString=<exact content without line prefixes>) \u2192 edit reliably`,
17059
17144
  args: {
17060
17145
  filePath: tool.schema.string().describe("Target file path"),
17061
17146
  offset: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("1-based start line (default: 1)"),
17062
- limit: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Number of lines to read (default: 2000). Use -1 to apply the default."),
17147
+ limit: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Number of lines to read (default: 2000, max recommended: 200). Use -1 to apply the default."),
17063
17148
  tail: tool.schema.boolean().optional().describe("Read last N lines instead of offset-based window"),
17064
17149
  encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding (default: gbk)"),
17065
17150
  allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
@@ -17099,13 +17184,24 @@ Workflow for large files:
17099
17184
 
17100
17185
  // src/tools/gbk_write.ts
17101
17186
  var gbk_write_default = tool({
17102
- description: "Write GBK encoded text files",
17187
+ description: `Write GBK encoded text files.
17188
+
17189
+ **append=true** (recommended for adding content to existing files):
17190
+ - Reads the existing file content and appends new content at the end.
17191
+ - Works whether the file exists or not (creates it if missing).
17192
+ - Use this whenever you want to add lines/content to an existing GBK file.
17193
+ - Example: gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
17194
+
17195
+ **overwrite=true**: Replace the entire file with new content.
17196
+
17197
+ **Default (overwrite=false, append=false)**: Fails if the file already exists.`,
17103
17198
  args: {
17104
17199
  filePath: tool.schema.string().describe("Target file path"),
17105
- content: tool.schema.string().describe("Text content to write"),
17200
+ content: tool.schema.string().describe("Text content to write (or append)"),
17106
17201
  encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
17107
17202
  createDirectories: tool.schema.boolean().optional().describe("Create parent directories"),
17108
- overwrite: tool.schema.boolean().optional().describe("Overwrite existing file"),
17203
+ overwrite: tool.schema.boolean().optional().describe("Overwrite existing file (replaces entire content)"),
17204
+ append: tool.schema.boolean().optional().describe("Append content to existing file instead of overwriting; creates the file if it does not exist"),
17109
17205
  allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
17110
17206
  },
17111
17207
  async execute(args, context) {
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "manifestVersion": 1,
3
3
  "packageName": "opencode-gbk-tools",
4
- "packageVersion": "0.1.6",
4
+ "packageVersion": "0.1.8",
5
5
  "artifacts": [
6
6
  {
7
7
  "relativePath": "tools/gbk_edit.js",
8
8
  "kind": "tool",
9
- "expectedHash": "3d0eb2fab5e3eca869923c2a3cc00e58ed429a4be4dbed22d13e5a5343bd5acc",
9
+ "expectedHash": "68d8398eae66592166de62e1bdce35ac51a6c22f27e6b48eca83c62370bc44f5",
10
10
  "hashAlgorithm": "sha256"
11
11
  },
12
12
  {
13
13
  "relativePath": "tools/gbk_read.js",
14
14
  "kind": "tool",
15
- "expectedHash": "799c53835bfe7237c5bf1e4858a67d20fffe00f90958ef48b72877cb95c1c07f",
15
+ "expectedHash": "90c0c3ca519000bcc1fc65f84615a5a280866a45104d08678f3ed92dbfda9f45",
16
16
  "hashAlgorithm": "sha256"
17
17
  },
18
18
  {
@@ -24,7 +24,7 @@
24
24
  {
25
25
  "relativePath": "tools/gbk_write.js",
26
26
  "kind": "tool",
27
- "expectedHash": "fcb35107b599a9d2a60a1549372ef39f9b4e7f3759b5242faca3ed4b0170330f",
27
+ "expectedHash": "fa69eecf0c6133ebcd670fa81983a2acb5d011128f1ebc4a99fea4f5f1c37136",
28
28
  "hashAlgorithm": "sha256"
29
29
  },
30
30
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gbk-tools",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "GBK/GB18030 custom tools and agent for OpenCode",
5
5
  "type": "module",
6
6
  "license": "MIT",