abelworkflow 1.1.2 → 1.2.0

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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -29
  3. package/extensions/gpt-responses-compat.ts +213 -0
  4. package/extensions/k2think-compat.ts +40 -0
  5. package/lib/cli/args.mjs +5 -19
  6. package/lib/cli/main.mjs +105 -72
  7. package/lib/cli/prompts.mjs +9 -2
  8. package/lib/config/dotenv.mjs +1 -1
  9. package/lib/config/jsonc.mjs +1 -1
  10. package/lib/config/store.mjs +197 -45
  11. package/lib/config/toml.mjs +147 -453
  12. package/lib/installer/assets.mjs +67 -45
  13. package/lib/installer/install.mjs +202 -45
  14. package/lib/installer/links.mjs +124 -101
  15. package/lib/installer/lock.mjs +82 -0
  16. package/lib/installer/state.mjs +14 -21
  17. package/lib/paths.mjs +9 -9
  18. package/lib/providers/claude.mjs +58 -114
  19. package/lib/providers/codex.mjs +101 -198
  20. package/lib/providers/pi.mjs +332 -439
  21. package/lib/providers/skills.mjs +88 -68
  22. package/lib/providers/url.mjs +35 -0
  23. package/lib/templates/workflow/commands/abel-design.md +23 -161
  24. package/lib/templates/workflow/commands/abel-diagnose.md +30 -56
  25. package/lib/templates/workflow/commands/abel-implement.md +30 -150
  26. package/lib/templates/workflow/commands/abel-init.md +1 -1
  27. package/lib/tools/cli-installer.mjs +29 -190
  28. package/package.json +7 -9
  29. package/skills/context7-auto-research/context7-api.cjs +75 -17
  30. package/skills/dev-browser/SKILL.md +15 -1
  31. package/skills/dev-browser/dist/scripts/start.d.ts +0 -1
  32. package/skills/dev-browser/dist/scripts/start.js +8 -36
  33. package/skills/dev-browser/dist/src/client.d.ts +3 -4
  34. package/skills/dev-browser/dist/src/client.js +117 -117
  35. package/skills/dev-browser/dist/src/entrypoint.d.ts +1 -1
  36. package/skills/dev-browser/dist/src/entrypoint.js +38 -31
  37. package/skills/dev-browser/dist/src/index.d.ts +0 -1
  38. package/skills/dev-browser/dist/src/index.js +0 -1
  39. package/skills/dev-browser/dist/src/page-api.d.ts +0 -1
  40. package/skills/dev-browser/dist/src/page-api.js +0 -1
  41. package/skills/dev-browser/dist/src/relay.d.ts +0 -1
  42. package/skills/dev-browser/dist/src/relay.js +53 -7
  43. package/skills/dev-browser/dist/src/runtime.d.ts +3 -14
  44. package/skills/dev-browser/dist/src/runtime.js +5 -15
  45. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +0 -1
  46. package/skills/dev-browser/dist/src/snapshot/browser-script.js +0 -1
  47. package/skills/dev-browser/dist/src/snapshot/index.d.ts +0 -1
  48. package/skills/dev-browser/dist/src/snapshot/index.js +0 -1
  49. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +0 -1
  50. package/skills/dev-browser/dist/src/snapshot/inject.js +0 -1
  51. package/skills/dev-browser/dist/src/standalone.d.ts +0 -1
  52. package/skills/dev-browser/dist/src/standalone.js +2 -3
  53. package/skills/dev-browser/dist/src/startup.d.ts +3 -12
  54. package/skills/dev-browser/dist/src/startup.js +6 -10
  55. package/skills/dev-browser/dist/src/target-registry.d.ts +0 -1
  56. package/skills/dev-browser/dist/src/target-registry.js +0 -1
  57. package/skills/dev-browser/dist/src/types.d.ts +0 -1
  58. package/skills/dev-browser/dist/src/types.js +0 -1
  59. package/skills/dev-browser/package-lock.json +1 -1
  60. package/skills/dev-browser/package.json +2 -2
  61. package/skills/grok-search/.env.example +1 -1
  62. package/skills/grok-search/SKILL.md +13 -16
  63. package/skills/grok-search/scripts/_dotenv.py +1 -1
  64. package/skills/grok-search/scripts/groksearch_cli.py +271 -195
  65. package/skills/time/SKILL.md +5 -2
  66. package/skills/time/scripts/time_cli.py +61 -22
  67. package/extensions/pi-gpt-responses-compat/index.ts +0 -25
  68. package/lib/templates/codex/config-base.toml +0 -103
  69. package/skills/dev-browser/dist/scripts/start.d.ts.map +0 -1
  70. package/skills/dev-browser/dist/scripts/start.js.map +0 -1
  71. package/skills/dev-browser/dist/src/client.d.ts.map +0 -1
  72. package/skills/dev-browser/dist/src/client.js.map +0 -1
  73. package/skills/dev-browser/dist/src/entrypoint.d.ts.map +0 -1
  74. package/skills/dev-browser/dist/src/entrypoint.js.map +0 -1
  75. package/skills/dev-browser/dist/src/index.d.ts.map +0 -1
  76. package/skills/dev-browser/dist/src/index.js.map +0 -1
  77. package/skills/dev-browser/dist/src/page-api.d.ts.map +0 -1
  78. package/skills/dev-browser/dist/src/page-api.js.map +0 -1
  79. package/skills/dev-browser/dist/src/relay.d.ts.map +0 -1
  80. package/skills/dev-browser/dist/src/relay.js.map +0 -1
  81. package/skills/dev-browser/dist/src/runtime.d.ts.map +0 -1
  82. package/skills/dev-browser/dist/src/runtime.js.map +0 -1
  83. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +0 -1
  84. package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +0 -1
  85. package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +0 -1
  86. package/skills/dev-browser/dist/src/snapshot/index.js.map +0 -1
  87. package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +0 -1
  88. package/skills/dev-browser/dist/src/snapshot/inject.js.map +0 -1
  89. package/skills/dev-browser/dist/src/standalone.d.ts.map +0 -1
  90. package/skills/dev-browser/dist/src/standalone.js.map +0 -1
  91. package/skills/dev-browser/dist/src/startup.d.ts.map +0 -1
  92. package/skills/dev-browser/dist/src/startup.js.map +0 -1
  93. package/skills/dev-browser/dist/src/target-registry.d.ts.map +0 -1
  94. package/skills/dev-browser/dist/src/target-registry.js.map +0 -1
  95. package/skills/dev-browser/dist/src/types.d.ts.map +0 -1
  96. package/skills/dev-browser/dist/src/types.js.map +0 -1
  97. package/skills/grok-search/gitignore.template +0 -4
  98. package/skills/grok-search/scripts/groksearch_entry.py +0 -131
@@ -1,127 +1,31 @@
1
- import { parse as validateToml } from "smol-toml";
1
+ import { parse } from "smol-toml";
2
2
 
3
- function detectLineEnding(content) {
4
- return content.includes("\r\n") ? "\r\n" : "\n";
5
- }
6
-
7
- function updateTopLevelTomlField(content, field, value) {
8
- const lineEnding = detectLineEnding(content);
9
- if (value === null) {
10
- return removeTopLevelTomlField(content, field);
11
- }
12
-
13
- const document = parseTomlDocument(content);
14
- const entry = findTomlAssignment(document.assignments, field, null);
15
- const nextLine = `${field} = ${JSON.stringify(value)}`;
16
-
17
- if (entry) {
18
- return `${content.slice(0, entry.start)}${entry.indent}${nextLine}${content.slice(entry.end)}`;
19
- }
20
-
21
- const topLevel = content.slice(0, document.topLevelEnd);
22
- const rest = content.slice(document.topLevelEnd);
23
- let nextTopLevel = topLevel.trimEnd()
24
- ? `${topLevel.trimEnd()}${lineEnding}${nextLine}`
25
- : nextLine;
26
- if (rest && nextTopLevel) {
27
- nextTopLevel = `${nextTopLevel}${lineEnding}${lineEnding}`;
28
- }
29
- return `${nextTopLevel}${rest}`;
30
- }
31
-
32
- function removeTopLevelTomlField(content, field) {
33
- const document = parseTomlDocument(content);
34
- const entry = findTomlAssignment(document.assignments, field, null);
35
- if (!entry) {
36
- return content;
37
- }
38
- return `${content.slice(0, entry.start)}${content.slice(entry.lineEnd)}`;
39
- }
40
-
41
- function removeTomlSection(content, sectionName) {
42
- const section = findTomlSection(parseTomlDocument(content), sectionName);
43
- if (!section) {
44
- return content;
45
- }
46
- return `${content.slice(0, section.start)}${content.slice(section.end)}`;
47
- }
48
-
49
- function removeTomlSectionField(content, sectionName, field) {
50
- const document = parseTomlDocument(content);
51
- const section = findTomlSection(document, sectionName);
52
- const entry = section && findTomlAssignment(document.assignments, field, section);
53
- return entry
54
- ? `${content.slice(0, entry.start)}${content.slice(entry.lineEnd)}`
55
- : content;
56
- }
57
-
58
- function buildTomlSection(sectionName, values, lineEnding = "\n") {
59
- const lines = [`[${sectionName}]`];
60
- for (const [key, value] of Object.entries(values)) {
61
- if (value === undefined || value === null || value === "") {
62
- continue;
63
- }
64
- if (typeof value === "string") {
65
- lines.push(`${key} = ${JSON.stringify(value)}`);
66
- } else if (typeof value === "boolean") {
67
- lines.push(`${key} = ${value ? "true" : "false"}`);
68
- } else {
69
- lines.push(`${key} = ${String(value)}`);
70
- }
71
- }
72
- return `${lines.join(lineEnding)}${lineEnding}`;
73
- }
74
-
75
- function formatTomlValue(value) {
76
- if (typeof value === "string") {
77
- return JSON.stringify(value);
78
- }
79
- if (typeof value === "boolean") {
80
- return value ? "true" : "false";
81
- }
82
- return String(value);
3
+ function parseToml(content) {
4
+ return parse(content, { integersAsBigInt: true });
83
5
  }
84
6
 
85
- function formatTomlKeySegment(value) {
86
- const segment = String(value);
87
- return /^[A-Za-z0-9_-]+$/u.test(segment) ? segment : JSON.stringify(segment);
7
+ function detectLineEnding(content) {
8
+ return content.includes("\r\n") ? "\r\n" : "\n";
88
9
  }
89
10
 
90
- function getTomlLines(content) {
11
+ function getLines(content) {
91
12
  const lines = [];
92
13
  for (let start = 0; start < content.length;) {
93
14
  const newline = content.indexOf("\n", start);
94
- if (newline === -1) {
95
- lines.push({
96
- contentEnd: content.length,
97
- end: content.length,
98
- start,
99
- text: content.slice(start)
100
- });
101
- break;
102
- }
103
-
104
- const contentEnd = content[newline - 1] === "\r" ? newline - 1 : newline;
105
- lines.push({
106
- contentEnd,
107
- end: newline + 1,
108
- start,
109
- text: content.slice(start, contentEnd)
110
- });
111
- start = newline + 1;
15
+ const end = newline === -1 ? content.length : newline + 1;
16
+ const contentEnd = newline === -1 ? end : content[newline - 1] === "\r" ? newline - 1 : newline;
17
+ lines.push({ start, end, contentEnd, text: content.slice(start, contentEnd) });
18
+ start = end;
112
19
  }
113
20
  return lines;
114
21
  }
115
22
 
116
- function skipTomlWhitespace(line, index) {
117
- while (line[index] === " " || line[index] === "\t") {
118
- index += 1;
119
- }
23
+ function skipWhitespace(text, index) {
24
+ while (text[index] === " " || text[index] === "\t") index += 1;
120
25
  return index;
121
26
  }
122
27
 
123
- function readTomlBasicEscape(text, index) {
124
- const escaped = text[index + 1];
28
+ function readBasicEscape(text, index) {
125
29
  const simple = {
126
30
  '"': '"',
127
31
  "\\": "\\",
@@ -132,32 +36,24 @@ function readTomlBasicEscape(text, index) {
132
36
  r: "\r",
133
37
  t: "\t"
134
38
  };
135
- if (escaped in simple) {
136
- return { index: index + 2, value: simple[escaped] };
137
- }
138
-
39
+ const escaped = text[index + 1];
40
+ if (escaped in simple) return { index: index + 2, value: simple[escaped] };
139
41
  const length = { U: 8, u: 4, x: 2 }[escaped];
140
42
  const hex = length ? text.slice(index + 2, index + 2 + length) : "";
141
- if (!length || hex.length !== length || !/^[0-9A-Fa-f]+$/u.test(hex)) {
142
- return null;
143
- }
43
+ if (!length || hex.length !== length || !/^[0-9A-Fa-f]+$/u.test(hex)) return null;
144
44
  const codePoint = Number.parseInt(hex, 16);
145
- if (codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)) {
146
- return null;
147
- }
45
+ if (codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)) return null;
148
46
  return { index: index + 2 + length, value: String.fromCodePoint(codePoint) };
149
47
  }
150
48
 
151
- function parseTomlSimpleKey(text, index) {
49
+ function parseKeySegment(text, index) {
152
50
  const quote = text[index];
153
- if (quote === `"` || quote === `'`) {
51
+ if (quote === '"' || quote === "'") {
154
52
  let value = "";
155
53
  for (index += 1; index < text.length;) {
156
- if (text[index] === quote) {
157
- return { index: index + 1, value };
158
- }
159
- if (quote === `"` && text[index] === "\\") {
160
- const escape = readTomlBasicEscape(text, index);
54
+ if (text[index] === quote) return { index: index + 1, value };
55
+ if (quote === '"' && text[index] === "\\") {
56
+ const escape = readBasicEscape(text, index);
161
57
  if (!escape) return null;
162
58
  value += escape.value;
163
59
  index = escape.index;
@@ -170,108 +66,80 @@ function parseTomlSimpleKey(text, index) {
170
66
  }
171
67
 
172
68
  const start = index;
173
- while (index < text.length && /[A-Za-z0-9_-]/u.test(text[index])) {
174
- index += 1;
175
- }
69
+ while (/[A-Za-z0-9_-]/u.test(text[index] ?? "")) index += 1;
176
70
  return index === start ? null : { index, value: text.slice(start, index) };
177
71
  }
178
72
 
179
- function parseTomlKeyPath(text, index = 0) {
73
+ function parseKeyPath(text, index = 0) {
180
74
  const path = [];
181
75
  while (true) {
182
- index = skipTomlWhitespace(text, index);
183
- const key = parseTomlSimpleKey(text, index);
184
- if (!key) return null;
185
- path.push(key.value);
186
- index = skipTomlWhitespace(text, key.index);
187
- if (text[index] !== ".") {
188
- return { index, path };
189
- }
76
+ index = skipWhitespace(text, index);
77
+ const segment = parseKeySegment(text, index);
78
+ if (!segment) return null;
79
+ path.push(segment.value);
80
+ index = skipWhitespace(text, segment.index);
81
+ if (text[index] !== ".") return { index, path };
190
82
  index += 1;
191
83
  }
192
84
  }
193
85
 
194
- function parseTomlHeader(text) {
195
- let index = skipTomlWhitespace(text, 0);
86
+ function parseHeader(text) {
87
+ let index = skipWhitespace(text, 0);
196
88
  const arrayTable = text.startsWith("[[", index);
197
89
  if (!arrayTable && text[index] !== "[") return null;
198
90
  index += arrayTable ? 2 : 1;
199
-
200
- const key = parseTomlKeyPath(text, index);
91
+ const key = parseKeyPath(text, index);
201
92
  if (!key) return null;
202
- index = skipTomlWhitespace(text, key.index);
93
+ index = skipWhitespace(text, key.index);
203
94
  const close = arrayTable ? "]]" : "]";
204
95
  if (!text.startsWith(close, index)) return null;
205
- index = skipTomlWhitespace(text, index + close.length);
96
+ index = skipWhitespace(text, index + close.length);
206
97
  if (index !== text.length && text[index] !== "#") return null;
207
98
  return { arrayTable, path: key.path };
208
99
  }
209
100
 
210
- function parseTomlAssignmentHead(text) {
211
- const keyStart = skipTomlWhitespace(text, 0);
212
- if (text[keyStart] === "#") return null;
213
- const key = parseTomlKeyPath(text, keyStart);
101
+ function parseAssignmentHead(text) {
102
+ const start = skipWhitespace(text, 0);
103
+ if (text[start] === "#") return null;
104
+ const key = parseKeyPath(text, start);
214
105
  if (!key) return null;
215
- let index = skipTomlWhitespace(text, key.index);
106
+ let index = skipWhitespace(text, key.index);
216
107
  if (text[index] !== "=") return null;
217
- index = skipTomlWhitespace(text, index + 1);
218
- return {
219
- indent: text.slice(0, keyStart),
220
- path: key.path,
221
- valueIndex: index
222
- };
108
+ index = skipWhitespace(text, index + 1);
109
+ return { indent: text.slice(0, start), path: key.path, valueIndex: index };
223
110
  }
224
111
 
225
- function closeTomlMultilineString(text, state, index, quote) {
112
+ function closeMultilineString(text, state, index, quote) {
226
113
  let end = index + 3;
227
- while (text[end] === quote) {
228
- end += 1;
229
- }
230
- if (end - index <= 5) {
231
- state.mode = "normal";
232
- }
114
+ while (text[end] === quote) end += 1;
115
+ if (end - index <= 5) state.mode = "normal";
233
116
  return end;
234
117
  }
235
118
 
236
- function scanTomlValueLine(text, start, state) {
119
+ function scanValueLine(text, start, state) {
237
120
  let valueEnd = text.length;
238
121
  for (let index = start; index < text.length;) {
239
122
  if (state.mode === "multiline-basic") {
240
- if (text[index] === "\\") {
241
- index += 2;
242
- } else if (text.startsWith(`"""`, index)) {
243
- index = closeTomlMultilineString(text, state, index, `"`);
244
- } else {
245
- index += 1;
246
- }
123
+ if (text[index] === "\\") index += 2;
124
+ else if (text.startsWith('"""', index)) index = closeMultilineString(text, state, index, '"');
125
+ else index += 1;
247
126
  continue;
248
127
  }
249
-
250
128
  if (state.mode === "multiline-literal") {
251
- if (text.startsWith(`'''`, index)) {
252
- index = closeTomlMultilineString(text, state, index, `'`);
253
- } else {
254
- index += 1;
255
- }
129
+ if (text.startsWith("'''", index)) index = closeMultilineString(text, state, index, "'");
130
+ else index += 1;
256
131
  continue;
257
132
  }
258
-
259
133
  if (state.mode === "basic") {
260
- if (text[index] === "\\") {
261
- index += 2;
262
- } else if (text[index] === `"`) {
134
+ if (text[index] === "\\") index += 2;
135
+ else if (text[index] === '"') {
263
136
  state.mode = "normal";
264
137
  index += 1;
265
- } else {
266
- index += 1;
267
- }
138
+ } else index += 1;
268
139
  continue;
269
140
  }
270
-
271
141
  if (state.mode === "literal") {
272
- if (text[index] === `'`) {
273
- state.mode = "normal";
274
- }
142
+ if (text[index] === "'") state.mode = "normal";
275
143
  index += 1;
276
144
  continue;
277
145
  }
@@ -280,90 +148,62 @@ function scanTomlValueLine(text, start, state) {
280
148
  valueEnd = index;
281
149
  break;
282
150
  }
283
- if (text.startsWith(`"""`, index)) {
151
+ if (text.startsWith('"""', index)) {
284
152
  state.mode = "multiline-basic";
285
153
  index += 3;
286
- continue;
287
- }
288
- if (text.startsWith(`'''`, index)) {
154
+ } else if (text.startsWith("'''", index)) {
289
155
  state.mode = "multiline-literal";
290
156
  index += 3;
291
- continue;
292
- }
293
- if (text[index] === `"`) {
157
+ } else if (text[index] === '"') {
294
158
  state.mode = "basic";
295
- } else if (text[index] === `'`) {
159
+ index += 1;
160
+ } else if (text[index] === "'") {
296
161
  state.mode = "literal";
297
- } else if (text[index] === "[") {
298
- state.arrayDepth += 1;
299
- } else if (text[index] === "]" && state.arrayDepth > 0) {
300
- state.arrayDepth -= 1;
301
- } else if (text[index] === "{") {
302
- state.inlineTableDepth += 1;
303
- } else if (text[index] === "}" && state.inlineTableDepth > 0) {
304
- state.inlineTableDepth -= 1;
162
+ index += 1;
163
+ } else {
164
+ if (text[index] === "[") state.arrayDepth += 1;
165
+ else if (text[index] === "]" && state.arrayDepth) state.arrayDepth -= 1;
166
+ else if (text[index] === "{") state.inlineDepth += 1;
167
+ else if (text[index] === "}" && state.inlineDepth) state.inlineDepth -= 1;
168
+ index += 1;
305
169
  }
306
- index += 1;
307
- }
308
- while (valueEnd > start && (text[valueEnd - 1] === " " || text[valueEnd - 1] === "\t")) {
309
- valueEnd -= 1;
310
170
  }
171
+ while (valueEnd > start && /[ \t]/u.test(text[valueEnd - 1])) valueEnd -= 1;
311
172
  return {
312
- complete: state.mode === "normal" && state.arrayDepth === 0 && state.inlineTableDepth === 0,
173
+ complete: state.mode === "normal" && state.arrayDepth === 0 && state.inlineDepth === 0,
313
174
  invalid: state.mode === "basic" || state.mode === "literal",
314
175
  valueEnd
315
176
  };
316
177
  }
317
178
 
318
- function parseTomlDocument(content) {
319
- try {
320
- validateToml(content, { integersAsBigInt: true });
321
- } catch (error) {
322
- const message = error instanceof Error ? error.message.split(/\r?\n/u, 1)[0] : "Invalid TOML document";
323
- throw new SyntaxError(message, { cause: error });
324
- }
325
-
179
+ function parseDocument(content) {
180
+ parseToml(content);
326
181
  const assignments = [];
327
- const lines = getTomlLines(content);
328
182
  const sections = [];
183
+ const lines = getLines(content);
329
184
  let currentSection = null;
330
185
 
331
186
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
332
187
  const line = lines[lineIndex];
333
- const header = parseTomlHeader(line.text);
188
+ const header = parseHeader(line.text);
334
189
  if (header) {
335
190
  if (currentSection) currentSection.end = line.start;
336
- currentSection = {
337
- ...header,
338
- end: content.length,
339
- start: line.start
340
- };
191
+ currentSection = { ...header, start: line.start, end: content.length };
341
192
  sections.push(currentSection);
342
193
  continue;
343
194
  }
344
-
345
- const head = parseTomlAssignmentHead(line.text);
195
+ const head = parseAssignmentHead(line.text);
346
196
  if (!head) continue;
347
- const state = { arrayDepth: 0, inlineTableDepth: 0, mode: "normal" };
348
- let endLineIndex = lineIndex;
197
+ const state = { arrayDepth: 0, inlineDepth: 0, mode: "normal" };
349
198
  let scan;
199
+ let endLineIndex = lineIndex;
350
200
  for (; endLineIndex < lines.length; endLineIndex += 1) {
351
- const valueLine = lines[endLineIndex];
352
- scan = scanTomlValueLine(
353
- valueLine.text,
354
- endLineIndex === lineIndex ? head.valueIndex : 0,
355
- state
356
- );
357
- if (scan.invalid) {
358
- throw new SyntaxError(`Incomplete TOML string at offset ${valueLine.start}`);
359
- }
201
+ scan = scanValueLine(lines[endLineIndex].text, endLineIndex === lineIndex ? head.valueIndex : 0, state);
202
+ if (scan.invalid) throw new SyntaxError(`Incomplete TOML string at offset ${lines[endLineIndex].start}`);
360
203
  if (scan.complete) break;
361
204
  }
362
- if (!scan?.complete) {
363
- throw new SyntaxError(`Incomplete TOML assignment at offset ${line.start}`);
364
- }
365
- if (endLineIndex >= lines.length) endLineIndex = lines.length - 1;
366
- const endLine = lines[endLineIndex];
205
+ if (!scan?.complete) throw new SyntaxError(`Incomplete TOML assignment at offset ${line.start}`);
206
+ const endLine = lines[Math.min(endLineIndex, lines.length - 1)];
367
207
  assignments.push({
368
208
  end: endLine.contentEnd,
369
209
  indent: head.indent,
@@ -376,262 +216,116 @@ function parseTomlDocument(content) {
376
216
  });
377
217
  lineIndex = endLineIndex;
378
218
  }
379
-
380
- return {
381
- assignments,
382
- sections,
383
- topLevelEnd: sections[0]?.start ?? content.length
384
- };
219
+ return { assignments, sections, topLevelEnd: sections[0]?.start ?? content.length };
385
220
  }
386
221
 
387
- function sameTomlPath(left, right) {
222
+ function samePath(left, right) {
388
223
  return left.length === right.length && left.every((part, index) => part === right[index]);
389
224
  }
390
225
 
391
- function parseTomlRequestedPath(value) {
392
- const parsed = parseTomlKeyPath(value);
393
- return parsed && skipTomlWhitespace(value, parsed.index) === value.length ? parsed.path : null;
226
+ function requestedPath(value) {
227
+ const parsed = parseKeyPath(value);
228
+ return parsed && skipWhitespace(value, parsed.index) === value.length ? parsed.path : null;
394
229
  }
395
230
 
396
- function findTomlSection(document, sectionName) {
397
- const path = parseTomlRequestedPath(sectionName);
398
- return path
399
- ? document.sections.find((section) => !section.arrayTable && sameTomlPath(section.path, path))
400
- : null;
231
+ function findSection(document, sectionName) {
232
+ const path = requestedPath(sectionName);
233
+ return path ? document.sections.find((section) => !section.arrayTable && samePath(section.path, path)) : null;
401
234
  }
402
235
 
403
- function findTomlAssignment(assignments, field, section) {
404
- return assignments.find((entry) => (
405
- entry.section === section
406
- && entry.path.length === 1
407
- && entry.path[0] === field
408
- ));
236
+ function findAssignment(assignments, field, section) {
237
+ return assignments.find((entry) => entry.section === section && entry.path.length === 1 && entry.path[0] === field);
409
238
  }
410
239
 
411
- function extractTopLevelTomlEntries(content) {
412
- const document = parseTomlDocument(content);
413
- return document.assignments
414
- .filter(({ path, section }) => section === null && path.length === 1)
415
- .map((entry) => ({
416
- field: entry.path[0],
417
- raw: content.slice(entry.start, entry.end)
418
- }));
240
+ function formatValue(value) {
241
+ if (typeof value === "string") return JSON.stringify(value);
242
+ if (typeof value === "boolean") return value ? "true" : "false";
243
+ throw new TypeError("Managed TOML values must be strings or booleans");
419
244
  }
420
245
 
421
- function mergeMissingTopLevelTomlEntries(content, entries) {
422
- if (!entries.length) {
423
- return content;
246
+ function formatTomlKeySegment(value) {
247
+ const segment = String(value);
248
+ return /^[A-Za-z0-9_-]+$/u.test(segment) ? segment : JSON.stringify(segment);
249
+ }
250
+
251
+ function updateTopLevelTomlField(content, field, value) {
252
+ if (value === null) return removeTopLevelTomlField(content, field);
253
+ const document = parseDocument(content);
254
+ const entry = findAssignment(document.assignments, field, null);
255
+ const line = `${field} = ${formatValue(value)}`;
256
+ if (entry) {
257
+ return `${content.slice(0, entry.valueStart)}${formatValue(value)}${content.slice(entry.valueEnd)}`;
424
258
  }
425
259
 
426
260
  const lineEnding = detectLineEnding(content);
427
- const document = parseTomlDocument(content);
428
- const topLevel = content.slice(0, document.topLevelEnd);
261
+ const top = content.slice(0, document.topLevelEnd);
429
262
  const rest = content.slice(document.topLevelEnd);
430
- const existingFields = new Set(
431
- document.assignments
432
- .filter(({ path, section }) => section === null && path.length === 1)
433
- .map(({ path }) => path[0])
434
- );
435
- const missingEntries = entries.filter(({ field }) => !existingFields.has(field));
436
- if (!missingEntries.length) {
437
- return content;
438
- }
263
+ let nextTop = top.trimEnd() ? `${top.trimEnd()}${lineEnding}${line}` : line;
264
+ if (rest) nextTop += `${lineEnding}${lineEnding}`;
265
+ return `${nextTop}${rest}`;
266
+ }
439
267
 
440
- let nextTopLevel = topLevel.trimEnd();
441
- for (const entry of missingEntries) {
442
- nextTopLevel = nextTopLevel
443
- ? `${nextTopLevel}${lineEnding}${entry.raw}`
444
- : entry.raw;
445
- }
268
+ function removeTopLevelTomlField(content, field) {
269
+ const document = parseDocument(content);
270
+ const entry = findAssignment(document.assignments, field, null);
271
+ return entry ? `${content.slice(0, entry.start)}${content.slice(entry.lineEnd)}` : content;
272
+ }
446
273
 
447
- nextTopLevel = nextTopLevel.trimEnd();
448
- if (rest && nextTopLevel) {
449
- nextTopLevel = `${nextTopLevel}${lineEnding}${lineEnding}`;
450
- }
451
- return `${nextTopLevel}${rest}`;
274
+ function removeTomlSectionField(content, sectionName, field) {
275
+ const document = parseDocument(content);
276
+ const section = findSection(document, sectionName);
277
+ const entry = section && findAssignment(document.assignments, field, section);
278
+ return entry ? `${content.slice(0, entry.start)}${content.slice(entry.lineEnd)}` : content;
452
279
  }
453
280
 
454
- function applyTomlEdits(content, edits) {
455
- let nextContent = content;
456
- for (const { end, start, text } of edits.sort((left, right) => right.start - left.start)) {
457
- nextContent = `${nextContent.slice(0, start)}${text}${nextContent.slice(end)}`;
281
+ function applyEdits(content, edits) {
282
+ let next = content;
283
+ for (const edit of edits.sort((left, right) => right.start - left.start)) {
284
+ next = `${next.slice(0, edit.start)}${edit.text}${next.slice(edit.end)}`;
458
285
  }
459
- return nextContent;
286
+ return next;
460
287
  }
461
288
 
462
289
  function updateTomlSectionFields(content, sectionName, values) {
463
290
  const lineEnding = detectLineEnding(content);
464
- const document = parseTomlDocument(content);
465
- const section = findTomlSection(document, sectionName);
466
-
291
+ const document = parseDocument(content);
292
+ const section = findSection(document, sectionName);
293
+ const entries = Object.entries(values).filter(([, value]) => value !== undefined && value !== null && value !== "");
467
294
  if (!section) {
468
- const nextSection = buildTomlSection(sectionName, values, lineEnding).trimEnd();
295
+ const lines = [`[${sectionName}]`, ...entries.map(([field, value]) => `${field} = ${formatValue(value)}`)];
296
+ const block = lines.join(lineEnding);
469
297
  return content.trimEnd()
470
- ? `${content.trimEnd()}${lineEnding}${lineEnding}${nextSection}${lineEnding}`
471
- : `${nextSection}${lineEnding}`;
298
+ ? `${content.trimEnd()}${lineEnding}${lineEnding}${block}${lineEnding}`
299
+ : `${block}${lineEnding}`;
472
300
  }
473
301
 
474
- const remaining = new Map(
475
- Object.entries(values).filter(([, value]) => value !== undefined && value !== null && value !== "")
476
- );
477
- const managedFields = new Set(remaining.keys());
478
- const seenFields = new Set();
302
+ const remaining = new Map(entries);
479
303
  const edits = [];
480
-
481
304
  for (const entry of document.assignments) {
482
- if (
483
- entry.section !== section
484
- || entry.path.length !== 1
485
- || !managedFields.has(entry.path[0])
486
- ) continue;
305
+ if (entry.section !== section || entry.path.length !== 1 || !remaining.has(entry.path[0])) continue;
487
306
  const field = entry.path[0];
488
- if (seenFields.has(field)) {
489
- edits.push({ end: entry.lineEnd, start: entry.start, text: "" });
490
- } else {
491
- edits.push({
492
- end: entry.end,
493
- start: entry.start,
494
- text: `${entry.indent}${field} = ${formatTomlValue(remaining.get(field))}`
495
- });
496
- seenFields.add(field);
497
- remaining.delete(field);
498
- }
307
+ edits.push({
308
+ start: entry.valueStart,
309
+ end: entry.valueEnd,
310
+ text: formatValue(remaining.get(field))
311
+ });
312
+ remaining.delete(field);
499
313
  }
500
-
501
314
  if (remaining.size) {
502
- const leading = section.end > 0 && content[section.end - 1] !== "\n" ? lineEnding : "";
503
- const lines = [...remaining].map(([field, value]) => `${field} = ${formatTomlValue(value)}`);
315
+ const leading = section.end && content[section.end - 1] !== "\n" ? lineEnding : "";
504
316
  edits.push({
505
- end: section.end,
506
317
  start: section.end,
507
- text: `${leading}${lines.join(lineEnding)}${lineEnding}`
318
+ end: section.end,
319
+ text: `${leading}${[...remaining].map(([field, value]) => `${field} = ${formatValue(value)}`).join(lineEnding)}${lineEnding}`
508
320
  });
509
321
  }
510
- return applyTomlEdits(content, edits);
511
- }
512
-
513
- function parseTomlBasicString(value) {
514
- if (value[0] !== `"` || value.startsWith(`"""`)) return null;
515
- let parsed = "";
516
- for (let index = 1; index < value.length;) {
517
- if (value[index] === `"`) {
518
- return index === value.length - 1 ? parsed : null;
519
- }
520
- if (value[index] === "\\") {
521
- const escape = readTomlBasicEscape(value, index);
522
- if (!escape) return null;
523
- parsed += escape.value;
524
- index = escape.index;
525
- } else {
526
- parsed += value[index];
527
- index += 1;
528
- }
529
- }
530
- return null;
322
+ return applyEdits(content, edits);
531
323
  }
532
324
 
533
- function parseTomlMultilineString(value, quote) {
534
- const delimiter = quote.repeat(3);
535
- if (!value.startsWith(delimiter)) return null;
536
- let content = value.slice(3).replace(/\r\n/gu, "\n");
537
- if (content.startsWith("\n")) content = content.slice(1);
538
-
539
- let parsed = "";
540
- for (let index = 0; index < content.length;) {
541
- if (content.startsWith(delimiter, index)) {
542
- let end = index + 3;
543
- while (content[end] === quote) end += 1;
544
- const quoteCount = end - index;
545
- return quoteCount <= 5 && end === content.length
546
- ? `${parsed}${quote.repeat(quoteCount - 3)}`
547
- : null;
548
- }
549
-
550
- if (quote === `'` || content[index] !== "\\") {
551
- parsed += content[index];
552
- index += 1;
553
- continue;
554
- }
555
-
556
- let continuation = index + 1;
557
- while (content[continuation] === " " || content[continuation] === "\t") {
558
- continuation += 1;
559
- }
560
- if (content[continuation] === "\n") {
561
- index = continuation + 1;
562
- while (content[index] === " " || content[index] === "\t" || content[index] === "\n") {
563
- index += 1;
564
- }
565
- continue;
566
- }
567
-
568
- const escape = readTomlBasicEscape(content, index);
569
- if (!escape) return null;
570
- parsed += escape.value;
571
- index = escape.index;
572
- }
573
- return parsed;
574
- }
575
-
576
- function parseTomlScalar(rawValue) {
577
- const multilineBasic = parseTomlMultilineString(rawValue, `"`);
578
- if (multilineBasic !== null) return { parsed: true, value: multilineBasic };
579
- const multilineLiteral = parseTomlMultilineString(rawValue, `'`);
580
- if (multilineLiteral !== null) return { parsed: true, value: multilineLiteral };
581
- const value = rawValue.trim();
582
- const basicString = parseTomlBasicString(value);
583
- if (basicString !== null) return { parsed: true, value: basicString };
584
- if (/^'[^']*'$/u.test(value)) return { parsed: true, value: value.slice(1, -1) };
585
- if (value === "true" || value === "false") {
586
- return { parsed: true, value: value === "true" };
587
- }
588
- if (/^-?\d+(?:\.\d+)?$/u.test(value)) {
589
- return { parsed: true, value: Number(value) };
590
- }
591
- return { parsed: false, value: undefined };
592
- }
593
-
594
- function readTopLevelTomlString(content, field) {
595
- const document = parseTomlDocument(content);
596
- const entry = findTomlAssignment(document.assignments, field, null);
597
- if (!entry) return "";
598
- const value = parseTomlScalar(content.slice(entry.valueStart, entry.valueEnd));
599
- return value.parsed && typeof value.value === "string" ? value.value : "";
600
- }
601
-
602
- function parseTomlSection(content, sectionName) {
603
- const document = parseTomlDocument(content);
604
- const section = findTomlSection(document, sectionName);
605
- if (!section) return {};
606
- const values = {};
607
- for (const entry of document.assignments) {
608
- if (entry.section !== section || entry.path.length !== 1) continue;
609
- const value = parseTomlScalar(content.slice(entry.valueStart, entry.valueEnd));
610
- if (value.parsed) values[entry.path[0]] = value.value;
611
- }
612
- return values;
613
- }
614
-
615
- function getTomlSectionFieldNames(content, sectionName) {
616
- const document = parseTomlDocument(content);
617
- const section = findTomlSection(document, sectionName);
618
- if (!section) return [];
619
- return document.assignments
620
- .filter((entry) => entry.section === section && entry.path.length === 1)
621
- .map((entry) => entry.path[0]);
622
- }
623
-
624
-
625
325
  export {
626
- buildTomlSection,
627
326
  detectLineEnding,
628
- extractTopLevelTomlEntries,
629
327
  formatTomlKeySegment,
630
- getTomlSectionFieldNames,
631
- mergeMissingTopLevelTomlEntries,
632
- parseTomlSection,
633
- readTopLevelTomlString,
634
- removeTomlSection,
328
+ parseToml,
635
329
  removeTomlSectionField,
636
330
  removeTopLevelTomlField,
637
331
  updateTomlSectionFields,