@kairyou/agent-tools 0.5.0 → 0.5.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 CHANGED
@@ -101,8 +101,8 @@ Here `5h` and `w` are Claude's rolling usage windows; `⟳` is the reset countdo
101
101
  When a compatible API relay is active, the statusline also appends provider usage.
102
102
 
103
103
  To choose what appears, edit `statusline.fields` in
104
- `~/.agent-tools/config.jsonc`. The installer may add new default keys on update;
105
- it preserves top-of-file comments and existing values.
104
+ `~/.agent-tools/config.jsonc`. Installer updates only add missing default keys
105
+ and never touch your edits or comments.
106
106
 
107
107
  ### Provider usage
108
108
 
package/README.zh-CN.md CHANGED
@@ -96,7 +96,7 @@ npx -y @kairyou/agent-tools@latest statusline -a claude
96
96
  使用兼容的 API 中转时, statusline 还会追加 provider usage.
97
97
 
98
98
  如需控制显示项, 修改 `~/.agent-tools/config.jsonc` 里的
99
- `statusline.fields`. 安装器更新时可能补充新的默认键; 文件顶部注释和已有值会保留.
99
+ `statusline.fields`. 安装器更新时只会补充缺失的默认键, 不会动你的修改和注释.
100
100
 
101
101
  ### Provider usage
102
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kairyou/agent-tools",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Reusable Agent Skills and installable integrations (statusline, provider usage, vision) for Codex, Claude Code, and opencode.",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -102,40 +102,6 @@ function nodeCmd(absScript) {
102
102
  return `node "${fwd(absScript)}"`;
103
103
  }
104
104
 
105
- function stripJsonComments(input) {
106
- let out = "";
107
- let inString = false;
108
- let escaped = false;
109
- for (let i = 0; i < input.length; i++) {
110
- const ch = input[i];
111
- const next = input[i + 1];
112
- if (inString) {
113
- out += ch;
114
- escaped = ch === "\\" ? !escaped : false;
115
- if (ch === "\"" && !escaped) inString = false;
116
- continue;
117
- }
118
- if (ch === "\"") {
119
- inString = true;
120
- out += ch;
121
- continue;
122
- }
123
- if (ch === "/" && next === "/") {
124
- while (i < input.length && input[i] !== "\n") i++;
125
- out += "\n";
126
- continue;
127
- }
128
- if (ch === "/" && next === "*") {
129
- i += 2;
130
- while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
131
- i++;
132
- continue;
133
- }
134
- out += ch;
135
- }
136
- return out;
137
- }
138
-
139
105
  function readJsonc(file) {
140
106
  if (!fs.existsSync(file)) return {};
141
107
  const raw = fs.readFileSync(file, "utf8").replace(/^\uFEFF/, "");
@@ -146,31 +112,6 @@ function readJsonc(file) {
146
112
  return parsed ?? {};
147
113
  }
148
114
 
149
- function headerComments(text) {
150
- const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
151
- const header = [];
152
- for (const line of lines) {
153
- if (/^\s*(?:\/\/.*)?$/.test(line)) {
154
- header.push(line);
155
- continue;
156
- }
157
- break;
158
- }
159
- return header.length ? header.join("\n").replace(/\s+$/, "") + "\n" : "";
160
- }
161
-
162
- function mergeDefaults(target, defaults) {
163
- if (Array.isArray(defaults)) return target === undefined ? defaults : target;
164
- if (!defaults || typeof defaults !== "object") {
165
- return target === undefined ? defaults : target;
166
- }
167
- const out = target && typeof target === "object" && !Array.isArray(target) ? { ...target } : {};
168
- for (const [key, value] of Object.entries(defaults)) {
169
- out[key] = mergeDefaults(out[key], value);
170
- }
171
- return out;
172
- }
173
-
174
115
  function writeText(file, text, dryRun) {
175
116
  if (dryRun) {
176
117
  console.log(` [dry-run] would write ${file}:`);
@@ -215,23 +156,46 @@ function updateOpenCodeTuiConfig(file, { remove, dryRun }) {
215
156
  writeText(file, updated, dryRun);
216
157
  }
217
158
 
159
+ // Keys present in defaults but absent in current, as jsonc-parser edit paths.
160
+ function missingDefaultPaths(current, defaults, basePath = []) {
161
+ const out = [];
162
+ for (const [key, value] of Object.entries(defaults)) {
163
+ const existing = current?.[key];
164
+ if (existing === undefined) {
165
+ out.push({ path: [...basePath, key], value });
166
+ } else if (
167
+ value && typeof value === "object" && !Array.isArray(value) &&
168
+ existing && typeof existing === "object" && !Array.isArray(existing)
169
+ ) {
170
+ out.push(...missingDefaultPaths(existing, value, [...basePath, key]));
171
+ }
172
+ }
173
+ return out;
174
+ }
175
+
176
+ // Updates only add missing default keys via surgical jsonc-parser edits, so
177
+ // the user's comments, formatting, and key order survive installer updates.
218
178
  function mergeJsoncFile(src, dest, dryRun) {
219
- const defaults = readJsonc(src);
220
- const defaultHeader = headerComments(fs.readFileSync(src, "utf8"));
221
- if (!fs.existsSync(dest)) {
179
+ if (!fs.existsSync(dest) || !fs.readFileSync(dest, "utf8").trim()) {
222
180
  writeText(dest, fs.readFileSync(src, "utf8"), dryRun);
223
181
  return;
224
182
  }
183
+ const defaults = readJsonc(src);
225
184
  const currentText = fs.readFileSync(dest, "utf8");
226
- const current = readJsonc(dest);
227
- const merged = mergeDefaults(current, defaults);
228
- const currentHeader = headerComments(currentText) || defaultHeader;
229
- const mergedText = currentHeader + JSON.stringify(merged, null, 2) + "\n";
230
- if (stripJsonComments(currentText).trim() === JSON.stringify(merged, null, 2)) {
185
+ const additions = missingDefaultPaths(readJsonc(dest), defaults);
186
+ if (additions.length === 0) {
231
187
  console.log(` kept existing ${dest}`);
232
188
  return;
233
189
  }
234
- writeText(dest, mergedText, dryRun);
190
+ const eol = currentText.includes("\r\n") ? "\r\n" : "\n";
191
+ let updated = currentText;
192
+ for (const { path: keyPath, value } of additions) {
193
+ const edits = modify(updated, keyPath, value, {
194
+ formattingOptions: { insertSpaces: true, tabSize: 2, eol },
195
+ });
196
+ updated = applyEdits(updated, edits);
197
+ }
198
+ writeText(dest, updated, dryRun);
235
199
  }
236
200
 
237
201
  function copyRuntimeFile(src, dest, dryRun, options = {}) {