codemini-cli 0.2.6 → 0.2.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemini-cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
5
5
  "keywords": [
6
6
  "cli",
package/src/cli.js CHANGED
@@ -4,7 +4,7 @@ import { handleConfig } from './commands/config.js';
4
4
  import { handleDoctor } from './commands/doctor.js';
5
5
  import { handleSkill } from './commands/skill.js';
6
6
 
7
- const VERSION = '0.2.6';
7
+ const VERSION = '0.2.7';
8
8
 
9
9
  function printHelp() {
10
10
  console.log(`codemini ${VERSION}
@@ -7,7 +7,10 @@ function safeJsonParse(raw) {
7
7
  try {
8
8
  return JSON.parse(raw);
9
9
  } catch {
10
- return {};
10
+ return {
11
+ _raw: String(raw),
12
+ _invalid_json: true
13
+ };
11
14
  }
12
15
  }
13
16
 
package/src/core/tools.js CHANGED
@@ -755,7 +755,7 @@ async function readFile(root, args) {
755
755
  }
756
756
 
757
757
  async function writeFile(root, args) {
758
- const rawPath = String(args?.path || '').trim();
758
+ const rawPath = String(args?.path || args?.file_path || '').trim();
759
759
  if (!rawPath) {
760
760
  throw new Error('write requires a file path like weather/WeatherForecast.js');
761
761
  }
@@ -1536,7 +1536,7 @@ async function openTarget(root, args) {
1536
1536
  }
1537
1537
 
1538
1538
  function normalizeEditTargetArgs(args = {}) {
1539
- const file = String(args?.file || args?.path || '').trim();
1539
+ const file = String(args?.file || args?.path || args?.file_path || '').trim();
1540
1540
  const nestedEdit = args?.edit && typeof args.edit === 'object' ? args.edit : null;
1541
1541
  if (nestedEdit) {
1542
1542
  const normalizedEdit = { ...nestedEdit };
@@ -1561,6 +1561,8 @@ function normalizeEditTargetArgs(args = {}) {
1561
1561
  new_content: args?.new_content ?? args?.content,
1562
1562
  old_text: args?.old_text,
1563
1563
  new_text: args?.new_text,
1564
+ old_string: args?.old_string,
1565
+ new_string: args?.new_string,
1564
1566
  anchor_text: args?.anchor_text,
1565
1567
  content: args?.content
1566
1568
  }
@@ -1573,6 +1575,12 @@ async function editTarget(root, args) {
1573
1575
  const astTarget = normalized.ast_target;
1574
1576
  const edit = normalized.edit || {};
1575
1577
  let kind = String(edit.kind || '').trim();
1578
+ if (edit.old_text == null && edit.old_string != null) {
1579
+ edit.old_text = edit.old_string;
1580
+ }
1581
+ if (edit.new_text == null && edit.new_string != null) {
1582
+ edit.new_text = edit.new_string;
1583
+ }
1576
1584
  const hasContent = edit.new_content != null || edit.content != null;
1577
1585
  const hasTargetHint = Boolean(edit.symbol || args?.symbol || edit.line || args?.line || edit.target);
1578
1586
  if (!kind) {
@@ -1586,7 +1594,14 @@ async function editTarget(root, args) {
1586
1594
  kind = 'rewrite_file';
1587
1595
  }
1588
1596
  }
1589
- if (!file || !kind) throw new Error('edit requires file and edit.kind');
1597
+ if (!file || !kind) {
1598
+ const recentFile = String(args?.recent_file || '').trim();
1599
+ const rawArgs = typeof args?._raw === 'string' && args._raw.trim() ? ` Raw tool arguments: ${args._raw.trim()}.` : '';
1600
+ const hint = recentFile
1601
+ ? ` If you meant the recently read file ${recentFile}, use edit with {file:"${recentFile}", old_text:"...", new_text:"..."} for a text replacement, or {file:"${recentFile}", edit:{kind:"rewrite_file", new_content:"..."}} for a full rewrite.`
1602
+ : ' Use edit with {file:"path", old_text:"...", new_text:"..."} for a text replacement, or {file:"path", edit:{kind:"rewrite_file", new_content:"..."}} for a full rewrite.';
1603
+ throw new Error(`edit requires file and edit.kind.${rawArgs}${hint}`);
1604
+ }
1590
1605
  if (astTarget) {
1591
1606
  if (kind !== 'replace_block') {
1592
1607
  throw new Error('AST-scoped edit only supports replace_block');
@@ -1659,6 +1674,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
1659
1674
  };
1660
1675
  const astSelectionCache = new Map();
1661
1676
  let lastAstTarget = null;
1677
+ let lastReadPath = '';
1662
1678
  const rememberAstSelection = (filePath, astTarget) => {
1663
1679
  const key = String(filePath || '').trim();
1664
1680
  if (!key || !astTarget) return;
@@ -1812,15 +1828,18 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
1812
1828
  function: {
1813
1829
  name: 'edit',
1814
1830
  description:
1815
- 'Edit existing files. Use block edits, exact replacements, or anchored inserts. When ast_target is provided, keep the edit constrained to that node. Read first unless the exact target is already known. Prefer this over write for code changes.',
1831
+ 'Edit existing files. Prefer one of these shapes: 1) {file, old_text, new_text} for exact text replacement, 2) {file, symbol, edit:{kind:"replace_block", new_content:"..."}} for block replacement, 3) {file, anchor_text, position:"before"|"after", content:"..."} for inserts. Demo-style aliases {file_path, old_string, new_string} are also accepted. Read first unless the exact target is already known. Prefer this over write for existing code changes.',
1816
1832
  parameters: {
1817
1833
  type: 'object',
1818
1834
  properties: {
1819
1835
  file: { type: 'string', description: 'File path to edit' },
1820
1836
  path: { type: 'string', description: 'Alias for file' },
1837
+ file_path: { type: 'string', description: 'Alias for file, compatible with simpler demo-style tool calls' },
1821
1838
  new_content: { type: 'string', description: 'Replacement content' },
1822
1839
  old_text: { type: 'string', description: 'Exact text to replace' },
1823
1840
  new_text: { type: 'string', description: 'Replacement text' },
1841
+ old_string: { type: 'string', description: 'Alias for old_text' },
1842
+ new_string: { type: 'string', description: 'Alias for new_text' },
1824
1843
  anchor_text: { type: 'string', description: 'Anchor text for inserts' },
1825
1844
  content: { type: 'string', description: 'Content to insert or append' },
1826
1845
  position: { type: 'string', description: 'before or after' },
@@ -1840,11 +1859,12 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
1840
1859
  function: {
1841
1860
  name: 'write',
1842
1861
  description:
1843
- 'Create a new file or overwrite a file. Always include path and content. Use this for new files or explicit full rewrites only. If the file path is not decided yet, do not call write yet. Prefer edit for existing code changes.',
1862
+ 'Create a new file or overwrite a file. Always include path (or file_path) and content. Use this for new files or explicit full rewrites only. Example: {path:"src/page.html", content:"..."} . If the file path is not decided yet, do not call write yet. Prefer edit for existing code changes.',
1844
1863
  parameters: {
1845
1864
  type: 'object',
1846
1865
  properties: {
1847
1866
  path: { type: 'string', description: 'Required file path like src/app.js or pages/index.html. Never omit this.' },
1867
+ file_path: { type: 'string', description: 'Alias for path, compatible with simpler demo-style tool calls' },
1848
1868
  content: { type: 'string', description: 'Content to write' },
1849
1869
  append: { type: 'boolean', description: 'Append instead of overwrite' },
1850
1870
  full_file_rewrite: { type: 'boolean', description: 'Set true for whole-file rewrites' }
@@ -2049,6 +2069,10 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
2049
2069
  typeof args?.max_chars === 'number'
2050
2070
  ? args.max_chars
2051
2071
  : config.context?.read_file_max_chars ?? 24000
2072
+ }).then((result) => {
2073
+ const readPath = String(result?.path || args?.path || '').trim();
2074
+ if (readPath) lastReadPath = readPath;
2075
+ return result;
2052
2076
  }),
2053
2077
  grep: (args) => grep(workspaceRoot, args),
2054
2078
  glob: (args) => glob(workspaceRoot, args),
@@ -2069,7 +2093,10 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
2069
2093
  await ensureProjectIndex();
2070
2094
  const normalizedKind = String(args?.edit?.kind || args?.kind || '').trim();
2071
2095
  const astTarget = resolveCachedAstTarget(args, { requireAstScope: normalizedKind === 'replace_block' });
2072
- const result = await editTarget(workspaceRoot, astTarget ? { ...args, ast_target: astTarget } : args);
2096
+ const result = await editTarget(
2097
+ workspaceRoot,
2098
+ astTarget ? { ...args, ast_target: astTarget, recent_file: lastReadPath } : { ...args, recent_file: lastReadPath }
2099
+ );
2073
2100
  if (result?.path) await refreshProjectFile(result.path);
2074
2101
  return result;
2075
2102
  },