aura-basetools 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/aura-mcp.mjs +4 -4
- package/locator-lab/locator-api.mjs +142 -1
- package/locator-lab/struct-core.mjs +27 -4
- package/locator-lab/write-path.mjs +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# aura-basetools 0.13.
|
|
1
|
+
# aura-basetools 0.13.2
|
|
2
2
|
|
|
3
3
|
Byte-exact file tools for coding agents: read by intent, edit by intent, and write whole files without corrupting bytes. One engine shared by a standalone CLI and an MCP server.
|
|
4
4
|
|
|
@@ -21,7 +21,7 @@ The engine is intentionally not a general file manager: it reads and edits UTF-8
|
|
|
21
21
|
### Install
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm i -g aura-basetools@0.13.
|
|
24
|
+
npm i -g aura-basetools@0.13.2
|
|
25
25
|
aura --version
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -137,7 +137,7 @@ MIT.
|
|
|
137
137
|
### 安装
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
npm i -g aura-basetools@0.13.
|
|
140
|
+
npm i -g aura-basetools@0.13.2
|
|
141
141
|
aura --version
|
|
142
142
|
```
|
|
143
143
|
|
package/bin/aura-mcp.mjs
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* aura_basetools_transactions 列出当前工作区写事务
|
|
14
14
|
*
|
|
15
15
|
* 接法(写进各家的 MCP 配置即可):
|
|
16
|
-
* codex: codex mcp add aura -- npx -y --package aura-basetools aura mcp
|
|
17
|
-
* claude code: claude mcp add aura -- npx -y --package aura-basetools aura mcp
|
|
18
|
-
* cursor/zed: command = "npx", args = ["-y", "--package", "aura-basetools", "aura", "mcp"]
|
|
16
|
+
* codex: codex mcp add aura -- npx -y --package aura-basetools aura basetools mcp
|
|
17
|
+
* claude code: claude mcp add aura -- npx -y --package aura-basetools aura basetools mcp
|
|
18
|
+
* cursor/zed: command = "npx", args = ["-y", "--package", "aura-basetools", "aura", "basetools", "mcp"]
|
|
19
19
|
*
|
|
20
20
|
* 为什么必须带 `--package`:本包有两个 bin(`aura` / `aura-mcp`),**没有**一个叫
|
|
21
21
|
* `aura-basetools`;npx 在"多个 bin 且没有与包名同名的"时候会拒绝猜,直接报
|
|
@@ -559,7 +559,7 @@ export async function serve() {
|
|
|
559
559
|
await chain
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
-
// 直接运行本文件(`aura-mcp`,package.json 里声明的第二个 bin)也要起服务,与 `aura mcp` 等价。
|
|
562
|
+
// 直接运行本文件(`aura-mcp`,package.json 里声明的第二个 bin)也要起服务,与 `aura basetools mcp` 等价。
|
|
563
563
|
// 用 pathToFileURL 比手拼 file:// 靠得住(Windows 盘符就是那种手拼会踩的地方)。
|
|
564
564
|
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
565
565
|
await serve()
|
|
@@ -745,6 +745,22 @@ export function locate(source, request = {}) {
|
|
|
745
745
|
}
|
|
746
746
|
}
|
|
747
747
|
|
|
748
|
+
/**
|
|
749
|
+
* 符号地址落在“整行区间比声明更宽”的行上 ⇒ 拒绝,不能把行内别的代码一起删掉。
|
|
750
|
+
*
|
|
751
|
+
* 这是 `LOCATOR_LINE_SHARED` 的另一半:同族判据(同一行还有别人),但“别人”
|
|
752
|
+
* 不一定是符号表里的兄弟——`import` / 指令 / 调用语句根本不是符号。
|
|
753
|
+
*/
|
|
754
|
+
const sharedLineRefusal = (hit, shared, byWhat) => {
|
|
755
|
+
const label = clipReceiptLabel(filePath === null ? 'this file' : filePath, RECEIPT_LABEL_CHARS)
|
|
756
|
+
const advice = 'use old= with a unique fragment, handle=, or rewrite the whole file explicitly — nothing was written'
|
|
757
|
+
const message =
|
|
758
|
+
shared.side === 'header'
|
|
759
|
+
? `cannot address "${hit.name}" by ${byWhat} in ${label}: the declaration starts on line ${shared.line} (${JSON.stringify(shared.text)}), so the addressed span would begin after its header and leave it behind; use lines= covering line ${shared.line}, or ${advice}`
|
|
760
|
+
: `cannot address "${hit.name}" by ${byWhat} in ${label}: line ${shared.line} also holds ${JSON.stringify(shared.text)} ${shared.side === 'before' ? 'before' : 'after'} the declaration, so replacing the symbol's whole-line span would delete it; ${advice}`
|
|
761
|
+
return { ok: false, code: 'LOCATOR_LINE_SHARED', message, shared }
|
|
762
|
+
}
|
|
763
|
+
|
|
748
764
|
// ── 第 0 层:意图地址(T16)。调用方给一段模糊的意图串,引擎确定性地落成精确区间。
|
|
749
765
|
//
|
|
750
766
|
// 解析次序(每一档都不猜,落不下来就报歧义并给候选):
|
|
@@ -792,9 +808,12 @@ export function locate(source, request = {}) {
|
|
|
792
808
|
}
|
|
793
809
|
if (exact !== null) {
|
|
794
810
|
if (exact.spanComplete === false) return spanIncomplete(exact)
|
|
811
|
+
const shared = sharedLineContent(source, index, exact, lang)
|
|
812
|
+
if (shared !== null) return sharedLineRefusal(exact, shared, 'intent')
|
|
795
813
|
return done(exact.startLine, exact.endLine, 'intent~symbol')
|
|
796
814
|
}
|
|
797
|
-
|
|
815
|
+
// 用原始符号表而不是 `listSymbols` 投影:整行区间是否越界要看 token,投影里没有。
|
|
816
|
+
const symbols = index.symbols
|
|
798
817
|
let matches = symbols.filter((s) => s.name.includes(intent))
|
|
799
818
|
if (matches.length === 0) {
|
|
800
819
|
const lower = intent.toLowerCase()
|
|
@@ -803,6 +822,8 @@ export function locate(source, request = {}) {
|
|
|
803
822
|
if (matches.length === 1) {
|
|
804
823
|
const only = matches[0]
|
|
805
824
|
if (only.spanComplete === false) return spanIncomplete(only)
|
|
825
|
+
const shared = sharedLineContent(source, index, only, lang)
|
|
826
|
+
if (shared !== null) return sharedLineRefusal(only, shared, 'intent')
|
|
806
827
|
return done(only.startLine, only.endLine, 'intent~symbol')
|
|
807
828
|
}
|
|
808
829
|
if (matches.length > 1) {
|
|
@@ -810,6 +831,8 @@ export function locate(source, request = {}) {
|
|
|
810
831
|
const chosen = nearestSymbolBySpan(matches, near)
|
|
811
832
|
if (chosen !== null) {
|
|
812
833
|
if (chosen.spanComplete === false) return spanIncomplete(chosen)
|
|
834
|
+
const shared = sharedLineContent(source, index, chosen, lang)
|
|
835
|
+
if (shared !== null) return sharedLineRefusal(chosen, shared, 'intent')
|
|
813
836
|
return done(chosen.startLine, chosen.endLine, 'intent~symbol', true)
|
|
814
837
|
}
|
|
815
838
|
}
|
|
@@ -908,6 +931,9 @@ export function locate(source, request = {}) {
|
|
|
908
931
|
hint: directoryHint(source, lang),
|
|
909
932
|
}
|
|
910
933
|
}
|
|
934
|
+
/** 兄弟符号之外,行内还可能有 `import` / 指令 / 调用语句:同一个判据,看字节。 */
|
|
935
|
+
const shared = sharedLineContent(source, index, hit, lang)
|
|
936
|
+
if (shared !== null) return sharedLineRefusal(hit, shared, 'target')
|
|
911
937
|
return done(hit.startLine, hit.endLine, 'symbol')
|
|
912
938
|
} catch (error) {
|
|
913
939
|
return {
|
|
@@ -1022,6 +1048,121 @@ function loneCarriageReturnNote(source) {
|
|
|
1022
1048
|
return ' — this file uses lone CR (\\r) line endings, which are not treated as line breaks here (the symbol index counts lines by \\n, so a second line model would point at different bytes); address it with line_cue/anchor, or replace the whole file with lines=[1,1]'
|
|
1023
1049
|
}
|
|
1024
1050
|
|
|
1051
|
+
/** 声明前缀里本来就属于声明的标点(指针/引用/泛型/命名空间/可空修饰)。 */
|
|
1052
|
+
const DECL_PREFIX_PUNCT = new Set(['*', '&', '<', '>', ',', '::', '?', ':', '~', '.'])
|
|
1053
|
+
|
|
1054
|
+
/** 允许“包住声明”的开括号:Lisp 族的 `(defn …)`。`{` 不算(JS/C 的块不是声明本身)。 */
|
|
1055
|
+
const FORM_OPENERS = new Map([['(', ')'], ['[', ']']])
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* 整行替换会把声明之外的内容一起删掉的那些行。
|
|
1059
|
+
*
|
|
1060
|
+
* `siblingSymbolsOnSameSpan` 只看得见符号表里的兄弟;`import` / 指令 / 调用语句
|
|
1061
|
+
* 根本不是符号,却同样会被整行替换删掉(实测:`import fs from 'fs'; function alpha()`
|
|
1062
|
+
* 上 `target=alpha` 会把 import 一起删掉)。判据回到字节:
|
|
1063
|
+
* · 声明起点之前:只允许空白与**包住这个声明**的开括号(Lisp 族的 `(defn …)`);
|
|
1064
|
+
* · 声明终点之后:只允许空白、分号、注释与包住这个声明的闭括号。
|
|
1065
|
+
* 其余任何 token(`import` / `return` / 另一条语句)都说明这一个行区间不止这个声明。
|
|
1066
|
+
*
|
|
1067
|
+
* @returns `{line, side:'before'|'after'|'header', text}`,或 null(这一行只属于这个声明)
|
|
1068
|
+
*/
|
|
1069
|
+
function sharedLineContent(source, index, hit, lang) {
|
|
1070
|
+
const declStartTok = hit.declStartTok ?? hit.startTok
|
|
1071
|
+
const startToken = index.tokens[declStartTok]
|
|
1072
|
+
const endToken = index.tokens[hit.endTok]
|
|
1073
|
+
if (startToken === undefined || endToken === undefined) return null
|
|
1074
|
+
const startLine = index.lineAt(startToken.start)
|
|
1075
|
+
/**
|
|
1076
|
+
* C 家族:`$ident*` 吃不掉关键字返回类型,声明起点可能落在名字上(见 struct-core 的
|
|
1077
|
+
* `declStartUnknown`)。类型回到上一行时,行区间会把上一行留成悬空前缀——拒绝,不静默改。
|
|
1078
|
+
* 指令行(`#include`)与注解行(`@Override`)本来就不是声明头,不参与这条判据。
|
|
1079
|
+
*/
|
|
1080
|
+
if (hit.declStartUnknown === true) {
|
|
1081
|
+
const prev = index.tokens[(hit.declStartTok ?? hit.startTok) - 1]
|
|
1082
|
+
if (prev !== undefined) {
|
|
1083
|
+
const prevLine = index.lineAt(prev.end)
|
|
1084
|
+
if (prevLine === startLine - 1 && ![';', '{', '}'].includes(prev.value)) {
|
|
1085
|
+
const prevLineStart = source.lastIndexOf('\n', prev.start - 1) + 1
|
|
1086
|
+
const prevLineEnd = source.indexOf('\n', prev.end)
|
|
1087
|
+
const prevLineText = source.slice(prevLineStart, prevLineEnd === -1 ? source.length : prevLineEnd)
|
|
1088
|
+
if (!/^\s*[#@]/.test(prevLineText)) {
|
|
1089
|
+
return { line: prevLine, side: 'header', text: prevLineText.trim().slice(0, 80) }
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* 声明起点之前、同一行里的 token。只有两类可以留:
|
|
1096
|
+
* · **类型前缀**——仅当 `declStartUnknown`(C 家族把返回类型漏在声明起点之前);
|
|
1097
|
+
* · **包住声明的开括号**(Lisp 族):它的闭括号会在后缀里出现。
|
|
1098
|
+
* 其余任何 token(`;` / `{` / `=` / 字符串 / `@` / 别的语句)都说明这一行不止这个声明。
|
|
1099
|
+
*/
|
|
1100
|
+
const allowTypePrefix = hit.declStartUnknown === true
|
|
1101
|
+
const enclosing = new Map()
|
|
1102
|
+
for (let k = declStartTok - 1; k >= 0; k -= 1) {
|
|
1103
|
+
const token = index.tokens[k]
|
|
1104
|
+
if (index.lineAt(token.start) !== startLine) break
|
|
1105
|
+
if (allowTypePrefix && token.type === 'ident') continue
|
|
1106
|
+
if (allowTypePrefix && token.type === 'punct' && DECL_PREFIX_PUNCT.has(token.value)) continue
|
|
1107
|
+
if (token.type === 'punct' && FORM_OPENERS.has(token.value)) {
|
|
1108
|
+
// `close > endTok` 或 `close === endTok`:Lisp 的收尾 `)` 本身常是跨度的最后一个 token
|
|
1109
|
+
// (`(defn alpha [] 1)` 的 endTok 就是那个 `)`),开括号却仍在跨度之前。
|
|
1110
|
+
const close = index.pairs.get(k)
|
|
1111
|
+
if (close !== undefined && close >= hit.endTok) {
|
|
1112
|
+
const closer = FORM_OPENERS.get(token.value)
|
|
1113
|
+
enclosing.set(closer, (enclosing.get(closer) ?? 0) + 1)
|
|
1114
|
+
continue
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
const lineStart = source.lastIndexOf('\n', startToken.start - 1) + 1
|
|
1118
|
+
return { line: startLine, side: 'before', text: source.slice(lineStart, startToken.start).trim().slice(0, 80) }
|
|
1119
|
+
}
|
|
1120
|
+
const endLine = index.lineAt(endToken.end)
|
|
1121
|
+
const lineEnd = source.indexOf('\n', endToken.end)
|
|
1122
|
+
const after = source.slice(endToken.end, lineEnd === -1 ? source.length : lineEnd)
|
|
1123
|
+
const rest = trailingForeign(after, lang, enclosing)
|
|
1124
|
+
if (rest !== '') return { line: endLine, side: 'after', text: rest.trim().slice(0, 80) }
|
|
1125
|
+
return null
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* 声明终点之后同一行里还剩什么:空白、分号、注释、以及**包住声明的**闭括号不算别的代码。
|
|
1130
|
+
* 闭括号只在 `enclosing` 计数里出现(由前缀里那些开括号登记),所以 `function inner(){} }`
|
|
1131
|
+
* 这种“外层块的 `}` 落在同一行”不会被当成收尾。
|
|
1132
|
+
*
|
|
1133
|
+
* @returns 剩下的非平凡文本(空串 = 这一行到此为止只属于这个声明)
|
|
1134
|
+
*/
|
|
1135
|
+
function trailingForeign(text, lang, enclosing) {
|
|
1136
|
+
let rest = text
|
|
1137
|
+
const lineComments = lang?.lineComments ?? []
|
|
1138
|
+
const blockComments = lang?.blockComments ?? []
|
|
1139
|
+
for (;;) {
|
|
1140
|
+
rest = rest.replace(/^\s+/, '')
|
|
1141
|
+
if (rest === '') return ''
|
|
1142
|
+
if (lineComments.some((marker) => rest.startsWith(marker))) return ''
|
|
1143
|
+
const block = blockComments.find(([open]) => rest.startsWith(open))
|
|
1144
|
+
if (block !== undefined) {
|
|
1145
|
+
const close = rest.indexOf(block[1], block[0].length)
|
|
1146
|
+
if (close === -1) return rest
|
|
1147
|
+
rest = rest.slice(close + block[1].length)
|
|
1148
|
+
continue
|
|
1149
|
+
}
|
|
1150
|
+
// 分号单独处理:JS 家族是语句分隔符,Lisp 族已经在 lineComments 里提前返回。
|
|
1151
|
+
if (rest.startsWith(';')) {
|
|
1152
|
+
rest = rest.slice(1)
|
|
1153
|
+
continue
|
|
1154
|
+
}
|
|
1155
|
+
const closer = rest[0]
|
|
1156
|
+
const count = enclosing.get(closer) ?? 0
|
|
1157
|
+
if (count > 0) {
|
|
1158
|
+
enclosing.set(closer, count - 1)
|
|
1159
|
+
rest = rest.slice(1)
|
|
1160
|
+
continue
|
|
1161
|
+
}
|
|
1162
|
+
return rest
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1025
1166
|
/** 文件是否含**不跟 LF** 的 CR(老式 Mac 行尾)。只扫一次、不建第二套行模型。 */
|
|
1026
1167
|
function hasLoneCarriageReturn(source) {
|
|
1027
1168
|
return /\r(?!\n)/.test(source)
|
|
@@ -2278,6 +2278,13 @@ function scanBodyOpen(tokens, pairs, endTok, lang, lineAt, kind) {
|
|
|
2278
2278
|
const t = tokens[k]
|
|
2279
2279
|
const prev = tokens[k - 1]
|
|
2280
2280
|
if (t.type === 'punct' && (t.value === ';' || t.value === '}')) break
|
|
2281
|
+
/**
|
|
2282
|
+
* Lisp 族:声明本身是列表的第一个元素(`(ns alpha.core)`、`(defn f [a] a)`)。
|
|
2283
|
+
* 名字之后遇到收口 `)` 就是**包住这个声明的那对括号**收尾了——后面的 `(`(下一个 form)
|
|
2284
|
+
* 不是块体。少了这一条,`(ns alpha)` 会把下一行 `(defn …)` 的 `(` 当体,跨度扩到整个文件,
|
|
2285
|
+
* `target=alpha` 会连后面的函数一起删掉(lang-coverage 的 cljs 夹具就是这个形状)。
|
|
2286
|
+
*/
|
|
2287
|
+
if (open === '(' && t.type === 'punct' && t.value === ')') break
|
|
2281
2288
|
/**
|
|
2282
2289
|
* 块开符判定:`bodyOpen` 本身,加上**配置声明的**其它块开符(`[$ … ]` 字面量体),
|
|
2283
2290
|
* 再加上 `bodyOpens` 里显式列出的字符。
|
|
@@ -2390,7 +2397,7 @@ function headerContinues(tokens, k, prev, clauses) {
|
|
|
2390
2397
|
* 用独立的第二条规则去做会与变量规则重复命中同一个位置,靠规则顺序决定谁赢,
|
|
2391
2398
|
* 那是在用"顺序"掩盖语义,典型补丁。
|
|
2392
2399
|
*/
|
|
2393
|
-
function declarationSpan(tokens, pairs, startTok, endTok, lang, lineAt, kind, lines, termScan) {
|
|
2400
|
+
function declarationSpan(tokens, pairs, declStartTok, startTok, endTok, lang, lineAt, kind, lines, termScan) {
|
|
2394
2401
|
const open = lang.bodyOpen
|
|
2395
2402
|
let bodyOpen = null
|
|
2396
2403
|
let binding = kind
|
|
@@ -2456,10 +2463,13 @@ function declarationSpan(tokens, pairs, startTok, endTok, lang, lineAt, kind, li
|
|
|
2456
2463
|
|
|
2457
2464
|
return {
|
|
2458
2465
|
kind: binding,
|
|
2459
|
-
|
|
2466
|
+
// 声明起点是**规则起点**(含修饰符/返回类型),不是名字 token:`export function\nalpha()`
|
|
2467
|
+
// 若从名字所在行算起,整行替换会把上一行的 `export function` 留成悬空前缀。
|
|
2468
|
+
startLine: lineAt(tokens[declStartTok].start),
|
|
2460
2469
|
endLine: lineAt(tokens[last].end),
|
|
2461
2470
|
startTok,
|
|
2462
2471
|
endTok: last,
|
|
2472
|
+
declStartTok,
|
|
2463
2473
|
bodyOpen,
|
|
2464
2474
|
spanComplete,
|
|
2465
2475
|
spanNote,
|
|
@@ -2666,8 +2676,21 @@ function buildStructure(src, lang) {
|
|
|
2666
2676
|
// 会捕到参数列表里的 `a:` 这种半截 token 并报出一个不存在的符号。
|
|
2667
2677
|
// 它比“只捕标识符”的 `$name` 严——`$name` 不校验**字符串**内容,而 `$label` 会带引号进来。
|
|
2668
2678
|
if (typeof name !== 'string' || !/^[A-Za-z_$\u0080-\uffff][A-Za-z0-9_$\u0080-\uffff-]*$/.test(name)) continue
|
|
2669
|
-
const span = declarationSpan(tokens, pairs, hit.startTok, hit.end - 1, lang, lineAt, rule.kind, lines, termScan)
|
|
2670
|
-
|
|
2679
|
+
const span = declarationSpan(tokens, pairs, hit.matchStart, hit.startTok, hit.end - 1, lang, lineAt, rule.kind, lines, termScan)
|
|
2680
|
+
/**
|
|
2681
|
+
* 规则在名字之前只有 `$mods*` / `$ident*` 这类“可选前缀段”、没有字面量时,匹配器可能
|
|
2682
|
+
* 一段都没吃到:C 的 `static int alpha`(`$ident*` 只吃非关键字,`int` 是关键字)、
|
|
2683
|
+
* Dart 的 `int alpha(...)`(规则是 `$mods* $name …`,返回类型不在模式里)。此时
|
|
2684
|
+
* `declStartTok` 落在名字上,类型若回到**上一行**,行区间会把上一行留成悬空前缀。
|
|
2685
|
+
*
|
|
2686
|
+
* 这里只做标记,判据放在定位层({@link sharedLineContent}):它能看到行文本与上一行
|
|
2687
|
+
* 是否真的像声明头。`matchStart !== startTok` 说明前缀已经被吃进跨度,没有这个盲区。
|
|
2688
|
+
*/
|
|
2689
|
+
const nameAt = rule.pattern.findIndex((part) => part === '$name' || part === '$nameNotKeyword')
|
|
2690
|
+
const prefixParts = nameAt > 0 ? rule.pattern.slice(0, nameAt) : []
|
|
2691
|
+
const declStartUnknown =
|
|
2692
|
+
hit.matchStart === hit.startTok && prefixParts.length > 0 && prefixParts.every((part) => part.startsWith('$'))
|
|
2693
|
+
symbols.push({ name, ...span, ...(declStartUnknown ? { declStartUnknown: true } : {}) })
|
|
2671
2694
|
break
|
|
2672
2695
|
}
|
|
2673
2696
|
}
|
|
@@ -2803,6 +2803,8 @@ export function planHunks(content, hunks, filePath = null, options = {}) {
|
|
|
2803
2803
|
if (!found.ok) {
|
|
2804
2804
|
throw new PatchError(found.message, found.code, {
|
|
2805
2805
|
...(found.candidates === undefined ? {} : { candidates: found.candidates }),
|
|
2806
|
+
/** `LOCATOR_LINE_SHARED` 的“被谁占了”证据(符号兄弟之外的 import/尾随语句)。 */
|
|
2807
|
+
...(found.shared === undefined ? {} : { shared: found.shared }),
|
|
2806
2808
|
})
|
|
2807
2809
|
}
|
|
2808
2810
|
hits = [{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aura-basetools",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "Byte-exact read, edit and write tools for coding agents. Standalone CLI and MCP server; no DSH required. 面向 coding agent 的逐字节读写工具;独立 CLI / MCP,可选 DSH 适配器。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|