@ipv9/tokentracker-cli 0.39.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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +464 -0
  3. package/README.ko.md +464 -0
  4. package/README.md +476 -0
  5. package/README.zh-CN.md +469 -0
  6. package/bin/tracker.js +52 -0
  7. package/dashboard/dist/app-icon.png +0 -0
  8. package/dashboard/dist/apple-touch-icon.png +0 -0
  9. package/dashboard/dist/assets/ActivityHeatmap-BxBGHY1z.js +42 -0
  10. package/dashboard/dist/assets/Card-GROxUc_6.js +1 -0
  11. package/dashboard/dist/assets/DashboardPage-BZ5VFoBk.js +19 -0
  12. package/dashboard/dist/assets/DevicePage-CQts7ivB.js +1 -0
  13. package/dashboard/dist/assets/DialogTitle-DqFsk26U.js +1 -0
  14. package/dashboard/dist/assets/FadeIn-HBBXW_3q.js +1 -0
  15. package/dashboard/dist/assets/HeaderGithubStar-COD2HfaF.js +1 -0
  16. package/dashboard/dist/assets/IpCheckPage-Cn0l8-BQ.js +15 -0
  17. package/dashboard/dist/assets/LandingPage-CmV1BMU4.js +4356 -0
  18. package/dashboard/dist/assets/LeaderboardAvatar-CiIpWZwb.js +1 -0
  19. package/dashboard/dist/assets/LeaderboardPage-BU0mlR3g.js +6 -0
  20. package/dashboard/dist/assets/LeaderboardProfileModal-C49_3Xm9.js +72 -0
  21. package/dashboard/dist/assets/LeaderboardProfilePage-Bh1n0BAM.js +1 -0
  22. package/dashboard/dist/assets/LimitsPage-fZAmwLWT.js +2 -0
  23. package/dashboard/dist/assets/LocalOnlyNotice-D3rQdA-P.js +1 -0
  24. package/dashboard/dist/assets/LoginPage-enO4XP-c.js +1 -0
  25. package/dashboard/dist/assets/PopoverPopup-pitJEpN1.js +1 -0
  26. package/dashboard/dist/assets/Select-tT9x4ntD.js +1 -0
  27. package/dashboard/dist/assets/SelectItemText-C7F7eIm0.js +1 -0
  28. package/dashboard/dist/assets/SettingsPage-pDasZNU2.js +1 -0
  29. package/dashboard/dist/assets/SkillsPage-DEu7FY-2.js +1 -0
  30. package/dashboard/dist/assets/WidgetsPage-DGcO7rbp.js +1 -0
  31. package/dashboard/dist/assets/WrappedPage-D_1ID213.js +1 -0
  32. package/dashboard/dist/assets/agent-logos-DXQVg1xy.js +1 -0
  33. package/dashboard/dist/assets/arrow-up-right-DNLo92Ba.js +1 -0
  34. package/dashboard/dist/assets/download-DbBBBpJF.js +1 -0
  35. package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-BPBWmzPh.woff +0 -0
  36. package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-Ce5q_31Z.woff2 +0 -0
  37. package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-CJBLNVQT.woff2 +0 -0
  38. package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-mNhfPmgl.woff +0 -0
  39. package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-DH5Q319x.woff +0 -0
  40. package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-VCNRadI3.woff2 +0 -0
  41. package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-BKC3PZkM.woff +0 -0
  42. package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-DdrQ8KBw.woff2 +0 -0
  43. package/dashboard/dist/assets/geist-mono-latin-400-normal-CoULgQGM.woff +0 -0
  44. package/dashboard/dist/assets/geist-mono-latin-400-normal-LC9RFr9I.woff2 +0 -0
  45. package/dashboard/dist/assets/geist-mono-latin-500-normal-D3o2eNa9.woff2 +0 -0
  46. package/dashboard/dist/assets/geist-mono-latin-500-normal-DOxI7kZ4.woff +0 -0
  47. package/dashboard/dist/assets/geist-mono-latin-700-normal-D6izGJRP.woff2 +0 -0
  48. package/dashboard/dist/assets/geist-mono-latin-700-normal-QGw08Lff.woff +0 -0
  49. package/dashboard/dist/assets/geist-mono-latin-900-normal-CmoKXrdK.woff +0 -0
  50. package/dashboard/dist/assets/geist-mono-latin-900-normal-Cu5MFKsu.woff2 +0 -0
  51. package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-Cgks_Qgx.woff2 +0 -0
  52. package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-CxNRRMGd.woff +0 -0
  53. package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-CQcGuCNt.woff2 +0 -0
  54. package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-diTenJ8L.woff +0 -0
  55. package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-BX9f1BHp.woff +0 -0
  56. package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-YOllDaLV.woff2 +0 -0
  57. package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-Br6WNUGb.woff +0 -0
  58. package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-CrmBTzU2.woff2 +0 -0
  59. package/dashboard/dist/assets/index-BeoRn2gJ.js +2 -0
  60. package/dashboard/dist/assets/info-Bh5qTz6k.js +1 -0
  61. package/dashboard/dist/assets/main-BIY3Nj6I.js +969 -0
  62. package/dashboard/dist/assets/main-G_F9Iu_x.css +1 -0
  63. package/dashboard/dist/assets/use-limits-display-prefs-B4iJ-bBc.js +1 -0
  64. package/dashboard/dist/assets/use-native-settings-DHjPFDv8.js +1 -0
  65. package/dashboard/dist/assets/use-usage-limits-odqe9OY7.js +1 -0
  66. package/dashboard/dist/assets/useCurrency-BsSZiZMP.js +1 -0
  67. package/dashboard/dist/assets/useOpenInteractionType-BQhgq4rv.js +12 -0
  68. package/dashboard/dist/brand-logos/antigravity.svg +1 -0
  69. package/dashboard/dist/brand-logos/claude-code.svg +1 -0
  70. package/dashboard/dist/brand-logos/codebuddy.svg +1 -0
  71. package/dashboard/dist/brand-logos/codex.svg +15 -0
  72. package/dashboard/dist/brand-logos/copilot.svg +1 -0
  73. package/dashboard/dist/brand-logos/cursor.svg +1 -0
  74. package/dashboard/dist/brand-logos/gemini.svg +1 -0
  75. package/dashboard/dist/brand-logos/grok.svg +1 -0
  76. package/dashboard/dist/brand-logos/hermes.svg +1 -0
  77. package/dashboard/dist/brand-logos/kilo.svg +4 -0
  78. package/dashboard/dist/brand-logos/kimi.svg +1 -0
  79. package/dashboard/dist/brand-logos/kiro.svg +1 -0
  80. package/dashboard/dist/brand-logos/openclaw.svg +22 -0
  81. package/dashboard/dist/brand-logos/opencode.svg +3 -0
  82. package/dashboard/dist/clawd/happy.svg +161 -0
  83. package/dashboard/dist/clawd/idle/collapse.svg +101 -0
  84. package/dashboard/dist/clawd/idle/doze.svg +72 -0
  85. package/dashboard/dist/clawd/idle/follow.svg +64 -0
  86. package/dashboard/dist/clawd/idle/living.svg +196 -0
  87. package/dashboard/dist/clawd/idle/look.svg +115 -0
  88. package/dashboard/dist/clawd/idle/yawn.svg +158 -0
  89. package/dashboard/dist/clawd/mini/alert.svg +129 -0
  90. package/dashboard/dist/clawd/mini/crabwalk.svg +76 -0
  91. package/dashboard/dist/clawd/mini/enter-sleep.svg +65 -0
  92. package/dashboard/dist/clawd/mini/enter.svg +115 -0
  93. package/dashboard/dist/clawd/mini/happy.svg +124 -0
  94. package/dashboard/dist/clawd/mini/idle-tight.svg +15 -0
  95. package/dashboard/dist/clawd/mini/idle.svg +83 -0
  96. package/dashboard/dist/clawd/mini/peek.svg +82 -0
  97. package/dashboard/dist/clawd/mini/sleep.svg +112 -0
  98. package/dashboard/dist/clawd/react/double.svg +108 -0
  99. package/dashboard/dist/clawd/react/drag.svg +102 -0
  100. package/dashboard/dist/clawd/react/left.svg +102 -0
  101. package/dashboard/dist/clawd/react/right.svg +102 -0
  102. package/dashboard/dist/clawd/sleep/collapse-sleep.svg +247 -0
  103. package/dashboard/dist/clawd/sleep/sleeping.svg +118 -0
  104. package/dashboard/dist/clawd/sleep/wake.svg +277 -0
  105. package/dashboard/dist/clawd/static-base.svg +21 -0
  106. package/dashboard/dist/clawd/status/disconnected.svg +136 -0
  107. package/dashboard/dist/clawd/status/error.svg +94 -0
  108. package/dashboard/dist/clawd/status/notification.svg +134 -0
  109. package/dashboard/dist/clawd/working/building.svg +231 -0
  110. package/dashboard/dist/clawd/working/carrying.svg +107 -0
  111. package/dashboard/dist/clawd/working/conducting.svg +118 -0
  112. package/dashboard/dist/clawd/working/confused.svg +113 -0
  113. package/dashboard/dist/clawd/working/debugger.svg +91 -0
  114. package/dashboard/dist/clawd/working/juggling.svg +82 -0
  115. package/dashboard/dist/clawd/working/overheated.svg +75 -0
  116. package/dashboard/dist/clawd/working/pushing.svg +125 -0
  117. package/dashboard/dist/clawd/working/sweeping.svg +79 -0
  118. package/dashboard/dist/clawd/working/thinking.svg +119 -0
  119. package/dashboard/dist/clawd/working/typing.svg +153 -0
  120. package/dashboard/dist/clawd/working/ultrathink.svg +166 -0
  121. package/dashboard/dist/clawd/working/wizard.svg +98 -0
  122. package/dashboard/dist/dashboard-dark.png +0 -0
  123. package/dashboard/dist/favicon-16.png +0 -0
  124. package/dashboard/dist/favicon-32.png +0 -0
  125. package/dashboard/dist/favicon.ico +0 -0
  126. package/dashboard/dist/feed.xml +34 -0
  127. package/dashboard/dist/icon-192.png +0 -0
  128. package/dashboard/dist/icon-512.png +0 -0
  129. package/dashboard/dist/index.html +355 -0
  130. package/dashboard/dist/llms.txt +53 -0
  131. package/dashboard/dist/neon_bg.png +0 -0
  132. package/dashboard/dist/robots.txt +7 -0
  133. package/dashboard/dist/share.html +140 -0
  134. package/dashboard/dist/sitemap.xml +27 -0
  135. package/dashboard/dist/widgets-overview.png +0 -0
  136. package/package.json +78 -0
  137. package/scripts/install-local-service.sh +140 -0
  138. package/scripts/uninstall-local-service.sh +21 -0
  139. package/src/cli.js +95 -0
  140. package/src/commands/device-login.js +161 -0
  141. package/src/commands/diagnostics.js +38 -0
  142. package/src/commands/doctor.js +96 -0
  143. package/src/commands/init.js +1195 -0
  144. package/src/commands/serve.js +351 -0
  145. package/src/commands/status.js +609 -0
  146. package/src/commands/sync.js +2285 -0
  147. package/src/commands/uninstall.js +189 -0
  148. package/src/commands/wrapped.js +150 -0
  149. package/src/lib/antigravity-paths.js +48 -0
  150. package/src/lib/browser-auth.js +281 -0
  151. package/src/lib/categorizer-utils.js +232 -0
  152. package/src/lib/claude-categorizer.js +1319 -0
  153. package/src/lib/claude-config.js +204 -0
  154. package/src/lib/cli-ui.js +179 -0
  155. package/src/lib/codex-config.js +350 -0
  156. package/src/lib/codex-context-breakdown.js +400 -0
  157. package/src/lib/codex-rollout-parser.js +623 -0
  158. package/src/lib/codex-token-refresh.js +112 -0
  159. package/src/lib/cursor-config.js +425 -0
  160. package/src/lib/debug-flags.js +7 -0
  161. package/src/lib/diagnostics.js +272 -0
  162. package/src/lib/doctor.js +338 -0
  163. package/src/lib/fs.js +91 -0
  164. package/src/lib/gemini-config.js +288 -0
  165. package/src/lib/grok-hook.js +351 -0
  166. package/src/lib/init-flow.js +42 -0
  167. package/src/lib/local-api.js +1861 -0
  168. package/src/lib/openclaw-hook.js +420 -0
  169. package/src/lib/openclaw-session-plugin.js +520 -0
  170. package/src/lib/opencode-config.js +99 -0
  171. package/src/lib/passive-mode.js +185 -0
  172. package/src/lib/pricing/curated-overrides.json +79 -0
  173. package/src/lib/pricing/index.js +143 -0
  174. package/src/lib/pricing/litellm-fetcher.js +172 -0
  175. package/src/lib/pricing/matcher.js +255 -0
  176. package/src/lib/pricing/seed-snapshot.json +1 -0
  177. package/src/lib/progress.js +76 -0
  178. package/src/lib/project-usage-purge.js +106 -0
  179. package/src/lib/prompt.js +24 -0
  180. package/src/lib/proxy-env.js +131 -0
  181. package/src/lib/rollout.js +8442 -0
  182. package/src/lib/runtime-config.js +122 -0
  183. package/src/lib/skill-usage.js +267 -0
  184. package/src/lib/skills-manager.js +1127 -0
  185. package/src/lib/source-metadata.js +48 -0
  186. package/src/lib/sqlite-reader.js +136 -0
  187. package/src/lib/static-server.js +59 -0
  188. package/src/lib/subscriptions.js +453 -0
  189. package/src/lib/tracker-paths.js +16 -0
  190. package/src/lib/upload-throttle.js +148 -0
  191. package/src/lib/usage-limits.js +1806 -0
  192. package/src/lib/wrapped-aggregator.js +225 -0
@@ -0,0 +1,350 @@
1
+ const fs = require("node:fs/promises");
2
+ const path = require("node:path");
3
+
4
+ const { ensureDir, readJson, writeJson } = require("./fs");
5
+
6
+ async function upsertNotify({ configPath, notifyCmd, notifyOriginalPath, configLabel }) {
7
+ const originalText = await fs.readFile(configPath, "utf8").catch(() => null);
8
+ if (originalText == null) {
9
+ const label =
10
+ typeof configLabel === "string" && configLabel.length > 0 ? configLabel : "Config";
11
+ throw new Error(`${label} not found: ${configPath}`);
12
+ }
13
+
14
+ const existingNotify = extractNotify(originalText);
15
+ const already = arraysEqual(existingNotify, notifyCmd);
16
+
17
+ if (!already) {
18
+ // Persist original notify once (for uninstall + chaining).
19
+ if (existingNotify && existingNotify.length > 0) {
20
+ await ensureDir(path.dirname(notifyOriginalPath));
21
+ const existing = await readJson(notifyOriginalPath);
22
+ if (!existing) {
23
+ await writeJson(notifyOriginalPath, {
24
+ notify: existingNotify,
25
+ capturedAt: new Date().toISOString(),
26
+ });
27
+ }
28
+ }
29
+
30
+ const updated = setNotify(originalText, notifyCmd);
31
+ const backupPath = `${configPath}.bak.${new Date().toISOString().replace(/[:.]/g, "-")}`;
32
+ await fs.copyFile(configPath, backupPath);
33
+ await fs.writeFile(configPath, updated, "utf8");
34
+ return { changed: true, backupPath };
35
+ }
36
+
37
+ return { changed: false, backupPath: null };
38
+ }
39
+
40
+ async function restoreNotify({ configPath, notifyOriginalPath, expectedNotify }) {
41
+ const text = await fs.readFile(configPath, "utf8").catch(() => null);
42
+ if (text == null) return { restored: false, skippedReason: "config-missing" };
43
+
44
+ const original = await readJson(notifyOriginalPath);
45
+ const originalNotify = Array.isArray(original?.notify) ? original.notify : null;
46
+ const currentNotify = extractNotify(text);
47
+
48
+ if (!originalNotify && expectedNotify && !arraysEqual(currentNotify, expectedNotify)) {
49
+ return { restored: false, skippedReason: "no-backup-not-installed" };
50
+ }
51
+
52
+ const updated = originalNotify ? setNotify(text, originalNotify) : removeNotify(text);
53
+ if (updated === text) return { restored: false, skippedReason: "no-change" };
54
+
55
+ const backupPath = `${configPath}.bak.${new Date().toISOString().replace(/[:.]/g, "-")}`;
56
+ await fs.copyFile(configPath, backupPath).catch(() => {});
57
+ await fs.writeFile(configPath, updated, "utf8");
58
+ return { restored: true, skippedReason: null };
59
+ }
60
+
61
+ async function loadNotifyOriginal(notifyOriginalPath) {
62
+ const original = await readJson(notifyOriginalPath);
63
+ return Array.isArray(original?.notify) ? original.notify : null;
64
+ }
65
+
66
+ async function readNotify(configPath) {
67
+ const text = await fs.readFile(configPath, "utf8").catch(() => null);
68
+ if (text == null) return null;
69
+ return extractNotify(text);
70
+ }
71
+
72
+ async function upsertCodexNotify({ codexConfigPath, notifyCmd, notifyOriginalPath }) {
73
+ return upsertNotify({
74
+ configPath: codexConfigPath,
75
+ notifyCmd,
76
+ notifyOriginalPath,
77
+ configLabel: "Codex config",
78
+ });
79
+ }
80
+
81
+ async function restoreCodexNotify({ codexConfigPath, notifyOriginalPath, notifyCmd }) {
82
+ return restoreNotify({
83
+ configPath: codexConfigPath,
84
+ notifyOriginalPath,
85
+ expectedNotify: notifyCmd,
86
+ });
87
+ }
88
+
89
+ async function loadCodexNotifyOriginal(notifyOriginalPath) {
90
+ return loadNotifyOriginal(notifyOriginalPath);
91
+ }
92
+
93
+ async function readCodexNotify(codexConfigPath) {
94
+ return readNotify(codexConfigPath);
95
+ }
96
+
97
+ async function upsertEveryCodeNotify({ codeConfigPath, notifyCmd, notifyOriginalPath }) {
98
+ return upsertNotify({
99
+ configPath: codeConfigPath,
100
+ notifyCmd,
101
+ notifyOriginalPath,
102
+ configLabel: "Every Code config",
103
+ });
104
+ }
105
+
106
+ async function restoreEveryCodeNotify({ codeConfigPath, notifyOriginalPath, notifyCmd }) {
107
+ return restoreNotify({
108
+ configPath: codeConfigPath,
109
+ notifyOriginalPath,
110
+ expectedNotify: notifyCmd,
111
+ });
112
+ }
113
+
114
+ async function loadEveryCodeNotifyOriginal(notifyOriginalPath) {
115
+ return loadNotifyOriginal(notifyOriginalPath);
116
+ }
117
+
118
+ async function readEveryCodeNotify(codeConfigPath) {
119
+ return readNotify(codeConfigPath);
120
+ }
121
+
122
+ function extractNotify(text) {
123
+ // Heuristic parse: find a line that starts with "notify =".
124
+ // Supports single-line arrays:
125
+ // - notify = ["a", "b"]
126
+ // And multi-line arrays:
127
+ // - notify = [
128
+ // "a",
129
+ // "b"
130
+ // ]
131
+ const lines = text.split(/\r?\n/);
132
+ for (let i = 0; i < lines.length; i++) {
133
+ const line = lines[i];
134
+ const m = line.match(/^\s*notify\s*=\s*(.*)\s*$/);
135
+ if (!m) continue;
136
+
137
+ const rhs = (m[1] || "").trim();
138
+ const literal = readTomlArrayLiteral(lines, i, rhs);
139
+ if (!literal) continue;
140
+
141
+ const parsed = parseTomlStringArray(literal);
142
+ if (parsed) return parsed;
143
+ }
144
+ return null;
145
+ }
146
+
147
+ function setNotify(text, notifyCmd) {
148
+ const lines = text.split(/\r?\n/);
149
+ const notifyLine = `notify = ${formatTomlStringArray(notifyCmd)}`;
150
+
151
+ const out = [];
152
+ let replaced = false;
153
+ for (let i = 0; i < lines.length; i++) {
154
+ const line = lines[i];
155
+ const m = line.match(/^\s*notify\s*=\s*(.*)\s*$/);
156
+ if (m) {
157
+ if (!replaced) {
158
+ out.push(notifyLine);
159
+ replaced = true;
160
+ }
161
+
162
+ const rhs = (m[1] || "").trim();
163
+ i = findTomlArrayBlockEnd(lines, i, rhs);
164
+ continue;
165
+ }
166
+ out.push(line);
167
+ }
168
+
169
+ if (!replaced) {
170
+ // Insert at top-level, before the first table header.
171
+ const firstTableIdx = out.findIndex((l) => /^\s*\[/.test(l));
172
+ const headerIdx = firstTableIdx === -1 ? out.length : firstTableIdx;
173
+ out.splice(headerIdx, 0, notifyLine);
174
+ }
175
+
176
+ return out.join("\n").replace(/\n+$/, "\n");
177
+ }
178
+
179
+ function removeNotify(text) {
180
+ const lines = text.split(/\r?\n/);
181
+ const out = [];
182
+ for (let i = 0; i < lines.length; i++) {
183
+ const line = lines[i];
184
+ const m = line.match(/^\s*notify\s*=\s*(.*)\s*$/);
185
+ if (m) {
186
+ const rhs = (m[1] || "").trim();
187
+ i = findTomlArrayBlockEnd(lines, i, rhs);
188
+ continue;
189
+ }
190
+ out.push(line);
191
+ }
192
+ return out.join("\n").replace(/\n+$/, "\n");
193
+ }
194
+
195
+ function parseTomlStringArray(rhs) {
196
+ // Minimal parser for ["a", "b"] string arrays.
197
+ // Assumes there are no escapes in strings (good enough for our usage).
198
+ if (!rhs.startsWith("[") || !rhs.endsWith("]")) return null;
199
+ const inner = rhs.slice(1, -1).trim();
200
+ if (!inner) return [];
201
+
202
+ const parts = [];
203
+ let current = "";
204
+ let inString = false;
205
+ let quote = null;
206
+ for (let i = 0; i < inner.length; i++) {
207
+ const ch = inner[i];
208
+ if (!inString) {
209
+ if (ch === '"' || ch === "'") {
210
+ inString = true;
211
+ quote = ch;
212
+ current = "";
213
+ }
214
+ continue;
215
+ }
216
+ if (ch === quote) {
217
+ parts.push(current);
218
+ inString = false;
219
+ quote = null;
220
+ continue;
221
+ }
222
+ current += ch;
223
+ }
224
+
225
+ return parts.length > 0 ? parts : null;
226
+ }
227
+
228
+ function formatTomlStringArray(arr) {
229
+ return `[${arr.map((s) => JSON.stringify(String(s))).join(", ")}]`;
230
+ }
231
+
232
+ function readTomlArrayLiteral(lines, startIndex, rhs) {
233
+ const first = rhs.trim();
234
+ if (!first.startsWith("[")) return null;
235
+
236
+ let inString = false;
237
+ let quote = null;
238
+ let depth = 0;
239
+ let sawOpen = false;
240
+
241
+ function scanChunk(chunk) {
242
+ for (let i = 0; i < chunk.length; i++) {
243
+ const ch = chunk[i];
244
+ if (!inString) {
245
+ if (ch === '"' || ch === "'") {
246
+ inString = true;
247
+ quote = ch;
248
+ continue;
249
+ }
250
+ if (ch === "[") {
251
+ depth += 1;
252
+ sawOpen = true;
253
+ continue;
254
+ }
255
+ if (ch === "]") {
256
+ depth -= 1;
257
+ if (sawOpen && depth === 0) return i;
258
+ }
259
+ continue;
260
+ }
261
+ if (ch === quote) {
262
+ inString = false;
263
+ quote = null;
264
+ }
265
+ }
266
+ return -1;
267
+ }
268
+
269
+ const parts = [first];
270
+ let endPos = scanChunk(first);
271
+ if (endPos !== -1) return first.slice(0, endPos + 1).trim();
272
+
273
+ for (let j = startIndex + 1; j < lines.length; j++) {
274
+ const line = lines[j];
275
+ endPos = scanChunk(line);
276
+ if (endPos !== -1) {
277
+ parts.push(line.slice(0, endPos + 1));
278
+ return parts.join("\n").trim();
279
+ }
280
+ parts.push(line);
281
+ }
282
+
283
+ return null;
284
+ }
285
+
286
+ function findTomlArrayBlockEnd(lines, startIndex, rhs) {
287
+ const first = rhs.trim();
288
+ if (!first.startsWith("[")) return startIndex;
289
+
290
+ let inString = false;
291
+ let quote = null;
292
+ let depth = 0;
293
+ let sawOpen = false;
294
+
295
+ function scanChunk(chunk) {
296
+ for (let i = 0; i < chunk.length; i++) {
297
+ const ch = chunk[i];
298
+ if (!inString) {
299
+ if (ch === '"' || ch === "'") {
300
+ inString = true;
301
+ quote = ch;
302
+ continue;
303
+ }
304
+ if (ch === "[") {
305
+ depth += 1;
306
+ sawOpen = true;
307
+ continue;
308
+ }
309
+ if (ch === "]") {
310
+ depth -= 1;
311
+ if (sawOpen && depth === 0) return true;
312
+ }
313
+ continue;
314
+ }
315
+ if (ch === quote) {
316
+ inString = false;
317
+ quote = null;
318
+ }
319
+ }
320
+ return false;
321
+ }
322
+
323
+ if (scanChunk(first)) return startIndex;
324
+ for (let j = startIndex + 1; j < lines.length; j++) {
325
+ if (scanChunk(lines[j])) return j;
326
+ }
327
+ return startIndex;
328
+ }
329
+
330
+ function arraysEqual(a, b) {
331
+ if (!Array.isArray(a) || !Array.isArray(b)) return false;
332
+ if (a.length !== b.length) return false;
333
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
334
+ return true;
335
+ }
336
+
337
+ module.exports = {
338
+ upsertNotify,
339
+ restoreNotify,
340
+ loadNotifyOriginal,
341
+ readNotify,
342
+ upsertCodexNotify,
343
+ restoreCodexNotify,
344
+ loadCodexNotifyOriginal,
345
+ readCodexNotify,
346
+ upsertEveryCodeNotify,
347
+ restoreEveryCodeNotify,
348
+ loadEveryCodeNotifyOriginal,
349
+ readEveryCodeNotify,
350
+ };