@krtclcdy/tersio 2.20.1 → 2.22.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.
- package/README.md +8 -9
- package/cli/common.js +14 -1
- package/cli/dashboard.js +367 -35
- package/cli/doctor-fix.js +185 -0
- package/cli/doctor.js +120 -53
- package/cli/install.js +46 -60
- package/cli/rtk-wiring.js +1 -1
- package/cli/settings.js +128 -5
- package/cli/uninstall.js +4 -4
- package/cli/update.js +29 -20
- package/cli/usage.js +1 -1
- package/dashboard/{app.js → charts.js} +74 -361
- package/dashboard/core.js +357 -0
- package/dashboard/settings.js +166 -0
- package/dashboard/share.js +218 -0
- package/dashboard/styles.css +87 -12
- package/dashboard/template.html +162 -57
- package/extensions/ai-addons-updater/index.js +13 -30
- package/extensions/ai-addons-updater/index.ts +11 -29
- package/extensions/lib/utils.js +26 -1
- package/extensions/lib/utils.ts +28 -1
- package/extensions/shared/pricing.js +2 -2
- package/extensions/shared/pricing.ts +2 -1
- package/extensions/shared/usage-ledger.js +3 -2
- package/extensions/shared/usage-ledger.ts +3 -2
- package/extensions/shared/usage-store.js +7 -7
- package/extensions/shared/usage-store.ts +7 -6
- package/gain/dist/brand.webp +0 -0
- package/gain/dist/index.html +65 -0
- package/gain/dist/vite.svg +1 -0
- package/package.json +17 -34
- package/tersio.js +3 -2
|
@@ -12,7 +12,6 @@ import { CAVEMAN_REMOTE_RULE as CAVEMAN_REMOTE, RTK_RELEASE_API, fetchJson, find
|
|
|
12
12
|
const IS_WINDOWS = process.platform === 'win32';
|
|
13
13
|
const HOME = os.homedir();
|
|
14
14
|
const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
|
|
15
|
-
const PONYTAIL_NPM_SPEC = '@dietrichgebert/ponytail@latest';
|
|
16
15
|
const PONYTAIL_LOCAL = path.join(HOME, '.omp', 'plugins', 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
|
|
17
16
|
const RTK_BINARY = path.join(HOME, '.bun', 'bin', IS_WINDOWS ? 'rtk.exe' : 'rtk');
|
|
18
17
|
const CAVEMAN_LOCAL = path.join(HOME, '.omp', 'agent', 'extensions', 'caveman-session', 'rule.md');
|
|
@@ -119,38 +118,22 @@ async function checkAddons(ctx) {
|
|
|
119
118
|
return lines.join('\n');
|
|
120
119
|
}
|
|
121
120
|
async function updatePonytail(pi, ctx, dryRun = false) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return report(ctx, m, 'info');
|
|
126
|
-
}
|
|
127
|
-
notify(ctx, 'Ponytail: ensuring plugin directory exists…', 'info');
|
|
128
|
-
try {
|
|
129
|
-
await fs.mkdir(pluginsDir, { recursive: true });
|
|
130
|
-
}
|
|
131
|
-
catch (e) {
|
|
132
|
-
const m = `Ponytail update failed: failed to create ${pluginsDir}: ${e.message}`;
|
|
133
|
-
return report(ctx, m, 'warning');
|
|
134
|
-
}
|
|
135
|
-
if (!pi.exec) {
|
|
136
|
-
const m = 'Ponytail update failed: extension host does not provide exec';
|
|
137
|
-
return report(ctx, m, 'warning');
|
|
138
|
-
}
|
|
139
|
-
notify(ctx, 'Ponytail: running npm install…', 'info');
|
|
140
|
-
let out = '';
|
|
121
|
+
// Bundled with tersio: no separate package to refresh. `tersio update`
|
|
122
|
+
// pulls the bundled copy with the CLI.
|
|
123
|
+
let localVer = null;
|
|
141
124
|
try {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
throw new Error(r.stderr || `npm exited ${r.code}`);
|
|
125
|
+
const raw = await readTextIfExists(PONYTAIL_LOCAL);
|
|
126
|
+
if (raw)
|
|
127
|
+
localVer = JSON.parse(raw).version ?? null;
|
|
146
128
|
}
|
|
147
|
-
catch
|
|
148
|
-
|
|
149
|
-
return report(ctx, m, 'warning');
|
|
129
|
+
catch {
|
|
130
|
+
localVer = null;
|
|
150
131
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
132
|
+
void pi;
|
|
133
|
+
const m = dryRun
|
|
134
|
+
? `Ponytail dry-run: bundled with tersio (local=${localVer || '—'}); run \`tersio update\` to refresh it.`
|
|
135
|
+
: `Ponytail is bundled with tersio (local=${localVer || '—'}); run \`tersio update\` to refresh it.\n${RELOAD_MSG}`;
|
|
136
|
+
return report(ctx, m, 'info');
|
|
154
137
|
}
|
|
155
138
|
async function updateRtk(ctx, dryRun = false) {
|
|
156
139
|
let release;
|
|
@@ -29,7 +29,6 @@ const IS_WINDOWS = process.platform === 'win32';
|
|
|
29
29
|
const HOME = os.homedir();
|
|
30
30
|
|
|
31
31
|
const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
|
|
32
|
-
const PONYTAIL_NPM_SPEC = '@dietrichgebert/ponytail@latest';
|
|
33
32
|
const PONYTAIL_LOCAL = path.join(HOME, '.omp', 'plugins', 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
|
|
34
33
|
const RTK_BINARY = path.join(HOME, '.bun', 'bin', IS_WINDOWS ? 'rtk.exe' : 'rtk');
|
|
35
34
|
const CAVEMAN_LOCAL = path.join(HOME, '.omp', 'agent', 'extensions', 'caveman-session', 'rule.md');
|
|
@@ -165,35 +164,18 @@ async function checkAddons(ctx: AddonUpdaterCtx): Promise<string> {
|
|
|
165
164
|
}
|
|
166
165
|
|
|
167
166
|
async function updatePonytail(pi: AddonUpdaterPi, ctx: AddonUpdaterCtx, dryRun = false): Promise<string> {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return report(ctx, m, 'info');
|
|
172
|
-
}
|
|
173
|
-
notify(ctx, 'Ponytail: ensuring plugin directory exists…', 'info');
|
|
174
|
-
try {
|
|
175
|
-
await fs.mkdir(pluginsDir, { recursive: true });
|
|
176
|
-
} catch (e) {
|
|
177
|
-
const m = `Ponytail update failed: failed to create ${pluginsDir}: ${(e as Error).message}`;
|
|
178
|
-
return report(ctx, m, 'warning');
|
|
179
|
-
}
|
|
180
|
-
if (!pi.exec) {
|
|
181
|
-
const m = 'Ponytail update failed: extension host does not provide exec';
|
|
182
|
-
return report(ctx, m, 'warning');
|
|
183
|
-
}
|
|
184
|
-
notify(ctx, 'Ponytail: running npm install…', 'info');
|
|
185
|
-
let out = '';
|
|
167
|
+
// Bundled with tersio: no separate package to refresh. `tersio update`
|
|
168
|
+
// pulls the bundled copy with the CLI.
|
|
169
|
+
let localVer: string | null = null;
|
|
186
170
|
try {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
notify(ctx, 'Ponytail update finished. ' + RELOAD_MSG, 'info');
|
|
196
|
-
return m;
|
|
171
|
+
const raw = await readTextIfExists(PONYTAIL_LOCAL);
|
|
172
|
+
if (raw) localVer = (JSON.parse(raw) as { version?: string }).version ?? null;
|
|
173
|
+
} catch { localVer = null; }
|
|
174
|
+
void pi;
|
|
175
|
+
const m = dryRun
|
|
176
|
+
? `Ponytail dry-run: bundled with tersio (local=${localVer || '—'}); run \`tersio update\` to refresh it.`
|
|
177
|
+
: `Ponytail is bundled with tersio (local=${localVer || '—'}); run \`tersio update\` to refresh it.\n${RELOAD_MSG}`;
|
|
178
|
+
return report(ctx, m, 'info');
|
|
197
179
|
}
|
|
198
180
|
|
|
199
181
|
async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string> {
|
package/extensions/lib/utils.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Shared helpers for the installer and the AI add-ons updater.
|
|
2
2
|
// Node built-ins only — this module must stay dependency-free.
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { createWriteStream } from 'node:fs';
|
|
4
|
+
import { createWriteStream, existsSync, mkdirSync, renameSync } from 'node:fs';
|
|
5
5
|
import fs from 'node:fs/promises';
|
|
6
6
|
import https from 'node:https';
|
|
7
|
+
import os from 'node:os';
|
|
7
8
|
import path from 'node:path';
|
|
8
9
|
// Promise.withResolvers is Node 22+; the package supports Node 18+.
|
|
9
10
|
// ponytail: swap for the built-in when engines bumps to >=22.
|
|
@@ -149,6 +150,30 @@ export async function readTextIfExists(p) {
|
|
|
149
150
|
return null;
|
|
150
151
|
}
|
|
151
152
|
}
|
|
153
|
+
// Tersio-owned data home: ~/.tersio (Windows: %USERPROFILE%\.tersio).
|
|
154
|
+
// First use moves legacy ~/.omp/plugins/tersio-* files over, so existing
|
|
155
|
+
// installs keep their history.
|
|
156
|
+
const migratedTersioFiles = new Set();
|
|
157
|
+
export function tersioHome() {
|
|
158
|
+
return path.join(os.homedir(), '.tersio');
|
|
159
|
+
}
|
|
160
|
+
export function tersioDataPath(name, legacy) {
|
|
161
|
+
const dest = path.join(tersioHome(), name);
|
|
162
|
+
if (!migratedTersioFiles.has(name)) {
|
|
163
|
+
migratedTersioFiles.add(name);
|
|
164
|
+
try {
|
|
165
|
+
if (!existsSync(dest)) {
|
|
166
|
+
const src = path.join(os.homedir(), '.omp', 'plugins', legacy);
|
|
167
|
+
if (existsSync(src)) {
|
|
168
|
+
mkdirSync(tersioHome(), { recursive: true });
|
|
169
|
+
renameSync(src, dest);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch { /* best-effort; callers tolerate a missing file */ }
|
|
174
|
+
}
|
|
175
|
+
return dest;
|
|
176
|
+
}
|
|
152
177
|
export function normalizeRtkVersion(value) {
|
|
153
178
|
return String(value || '').replace(/^rtk\s+/i, '').replace(/^v/i, '').trim();
|
|
154
179
|
}
|
package/extensions/lib/utils.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// Node built-ins only — this module must stay dependency-free.
|
|
3
3
|
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
|
-
import { createWriteStream } from 'node:fs';
|
|
5
|
+
import { createWriteStream, existsSync, mkdirSync, renameSync } from 'node:fs';
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
7
|
import https from 'node:https';
|
|
8
|
+
import os from 'node:os';
|
|
8
9
|
import path from 'node:path';
|
|
9
10
|
|
|
10
11
|
export interface HttpOptions {
|
|
@@ -157,6 +158,32 @@ export async function readTextIfExists(p: string): Promise<string | null> {
|
|
|
157
158
|
try { return await fs.readFile(p, 'utf8'); } catch { return null; }
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
// Tersio-owned data home: ~/.tersio (Windows: %USERPROFILE%\.tersio).
|
|
162
|
+
// First use moves legacy ~/.omp/plugins/tersio-* files over, so existing
|
|
163
|
+
// installs keep their history.
|
|
164
|
+
const migratedTersioFiles = new Set<string>();
|
|
165
|
+
|
|
166
|
+
export function tersioHome(): string {
|
|
167
|
+
return path.join(os.homedir(), '.tersio');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function tersioDataPath(name: string, legacy: string): string {
|
|
171
|
+
const dest = path.join(tersioHome(), name);
|
|
172
|
+
if (!migratedTersioFiles.has(name)) {
|
|
173
|
+
migratedTersioFiles.add(name);
|
|
174
|
+
try {
|
|
175
|
+
if (!existsSync(dest)) {
|
|
176
|
+
const src = path.join(os.homedir(), '.omp', 'plugins', legacy);
|
|
177
|
+
if (existsSync(src)) {
|
|
178
|
+
mkdirSync(tersioHome(), { recursive: true });
|
|
179
|
+
renameSync(src, dest);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch { /* best-effort; callers tolerate a missing file */ }
|
|
183
|
+
}
|
|
184
|
+
return dest;
|
|
185
|
+
}
|
|
186
|
+
|
|
160
187
|
export function normalizeRtkVersion(value: string | undefined): string {
|
|
161
188
|
return String(value || '').replace(/^rtk\s+/i, '').replace(/^v/i, '').trim();
|
|
162
189
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// per-model table — list prices rot, the feed does not. Unknown models report
|
|
6
6
|
// the default with known:false so the dashboard labels them honestly.
|
|
7
7
|
import fs from 'node:fs';
|
|
8
|
-
import os from 'node:os';
|
|
9
8
|
import path from 'node:path';
|
|
9
|
+
import { tersioDataPath } from '../lib/utils.js';
|
|
10
10
|
// Sonnet-class default for unpriced models; always paired with known:false.
|
|
11
11
|
export const DEFAULT_PRICE = { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 };
|
|
12
12
|
const LITELLM_URL = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
|
|
@@ -18,7 +18,7 @@ export function pricesCachePath() {
|
|
|
18
18
|
const override = process.env.TERSIO_PRICES_FILE;
|
|
19
19
|
if (override)
|
|
20
20
|
return override;
|
|
21
|
-
return
|
|
21
|
+
return tersioDataPath('prices.json', 'tersio-prices.json');
|
|
22
22
|
}
|
|
23
23
|
export function pricesUrl() {
|
|
24
24
|
return process.env.TERSIO_PRICES_URL || LITELLM_URL;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import os from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import { tersioDataPath } from '../lib/utils.ts';
|
|
10
11
|
|
|
11
12
|
export interface ModelPrice {
|
|
12
13
|
input: number;
|
|
@@ -27,7 +28,7 @@ let refreshInflight: Promise<boolean> | null = null;
|
|
|
27
28
|
export function pricesCachePath(): string {
|
|
28
29
|
const override = process.env.TERSIO_PRICES_FILE;
|
|
29
30
|
if (override) return override;
|
|
30
|
-
return
|
|
31
|
+
return tersioDataPath('prices.json', 'tersio-prices.json');
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export function pricesUrl(): string {
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
+
import { tersioDataPath } from '../lib/utils.js';
|
|
8
9
|
export function ledgerPath() {
|
|
9
10
|
const override = process.env.TERSIO_USAGE_FILE;
|
|
10
11
|
if (override)
|
|
11
12
|
return override;
|
|
12
|
-
return
|
|
13
|
+
return tersioDataPath('usage.jsonl', 'tersio-usage.jsonl');
|
|
13
14
|
}
|
|
14
15
|
// Reset watermark: tersio-owned timestamp marking the last statistics reset.
|
|
15
16
|
// Session transcripts and the RTK database are host/tool-owned and never
|
|
@@ -20,7 +21,7 @@ export function resetMarkerPath() {
|
|
|
20
21
|
const override = process.env.TERSIO_RESET_FILE;
|
|
21
22
|
if (override)
|
|
22
23
|
return override;
|
|
23
|
-
return
|
|
24
|
+
return tersioDataPath('reset.json', 'tersio-reset.json');
|
|
24
25
|
}
|
|
25
26
|
export function readResetWatermark() {
|
|
26
27
|
try {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
+
import { tersioDataPath } from '../lib/utils.ts';
|
|
8
9
|
|
|
9
10
|
export type UsageKind = 'command' | 'toggle' | 'install' | 'update' | 'rtk-audit';
|
|
10
11
|
|
|
@@ -17,7 +18,7 @@ export interface UsageRow {
|
|
|
17
18
|
export function ledgerPath(): string {
|
|
18
19
|
const override = process.env.TERSIO_USAGE_FILE;
|
|
19
20
|
if (override) return override;
|
|
20
|
-
return
|
|
21
|
+
return tersioDataPath('usage.jsonl', 'tersio-usage.jsonl');
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
// Reset watermark: tersio-owned timestamp marking the last statistics reset.
|
|
@@ -28,7 +29,7 @@ export function ledgerPath(): string {
|
|
|
28
29
|
export function resetMarkerPath(): string {
|
|
29
30
|
const override = process.env.TERSIO_RESET_FILE;
|
|
30
31
|
if (override) return override;
|
|
31
|
-
return
|
|
32
|
+
return tersioDataPath('reset.json', 'tersio-reset.json');
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export function readResetWatermark(): number {
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
// back to the live path. Never touches RTK's history.db or host transcripts.
|
|
9
9
|
import { execFileSync } from 'node:child_process';
|
|
10
10
|
import fs from 'node:fs';
|
|
11
|
-
import os from 'node:os';
|
|
12
11
|
import path from 'node:path';
|
|
13
12
|
import { RECENT_LIMIT, canonicalModelId, classifySessionLine, codexSessionsDir, costOf, durOf, ingestSessionRow, newSessionAccum, sessionsDir, walkJsonl, } from './usage-ledger.js';
|
|
13
|
+
import { tersioDataPath } from '../lib/utils.js';
|
|
14
14
|
export function usageDbPath() {
|
|
15
15
|
const override = process.env.TERSIO_USAGE_DB;
|
|
16
16
|
if (override)
|
|
17
17
|
return override;
|
|
18
|
-
return
|
|
18
|
+
return tersioDataPath('usage.db', 'tersio-usage.db');
|
|
19
19
|
}
|
|
20
20
|
function esc(v) {
|
|
21
21
|
return `'${v.replace(/'/g, "''")}'`;
|
|
@@ -129,8 +129,9 @@ function insertSql(file, r) {
|
|
|
129
129
|
`${nullNum(r.d)},${r.cr},${r.cw},${nullNum(r.usd)},${esc(r.st)},` +
|
|
130
130
|
`${nullNum(r.code)},${nullStr(r.note)},${esc(JSON.stringify(r.tools))});`;
|
|
131
131
|
}
|
|
132
|
-
// Incremental sync: unchanged transcripts are skipped via mtime+size,
|
|
133
|
-
// ones are deleted and re-inserted
|
|
132
|
+
// Incremental sync: unchanged transcripts are skipped via mtime+size,
|
|
133
|
+
// changed ones are deleted and re-inserted. Rows for deleted transcripts
|
|
134
|
+
// are kept, so OMP session rotation never erases history. True on success
|
|
134
135
|
// (including nothing-to-do), false when sqlite3 or disk is unavailable.
|
|
135
136
|
export function syncUsageDb() {
|
|
136
137
|
let db;
|
|
@@ -168,8 +169,7 @@ export function syncUsageDb() {
|
|
|
168
169
|
if (!prev || prev.mtime !== entry.mtime || prev.size !== entry.size)
|
|
169
170
|
changed.push(file);
|
|
170
171
|
}
|
|
171
|
-
|
|
172
|
-
if (!changed.length && !removed.length)
|
|
172
|
+
if (!changed.length)
|
|
173
173
|
return true;
|
|
174
174
|
const chunks = [];
|
|
175
175
|
let chunkBytes = 0;
|
|
@@ -193,7 +193,7 @@ export function syncUsageDb() {
|
|
|
193
193
|
return true;
|
|
194
194
|
return flush();
|
|
195
195
|
};
|
|
196
|
-
for (const file of
|
|
196
|
+
for (const file of changed) {
|
|
197
197
|
if (!push(`DELETE FROM messages WHERE file=${esc(file)};DELETE FROM files WHERE path=${esc(file)};`))
|
|
198
198
|
return false;
|
|
199
199
|
}
|
|
@@ -22,12 +22,13 @@ import {
|
|
|
22
22
|
sessionsDir,
|
|
23
23
|
walkJsonl,
|
|
24
24
|
} from './usage-ledger.ts';
|
|
25
|
+
import { tersioDataPath } from '../lib/utils.ts';
|
|
25
26
|
import type { RunStatus, SessionTokens } from './usage-ledger.ts';
|
|
26
27
|
|
|
27
28
|
export function usageDbPath(): string {
|
|
28
29
|
const override = process.env.TERSIO_USAGE_DB;
|
|
29
30
|
if (override) return override;
|
|
30
|
-
return
|
|
31
|
+
return tersioDataPath('usage.db', 'tersio-usage.db');
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export interface StoredUsage {
|
|
@@ -172,8 +173,9 @@ function insertSql(file: string, r: StoredRow): string {
|
|
|
172
173
|
`${nullNum(r.code)},${nullStr(r.note)},${esc(JSON.stringify(r.tools))});`;
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
// Incremental sync: unchanged transcripts are skipped via mtime+size,
|
|
176
|
-
// ones are deleted and re-inserted
|
|
176
|
+
// Incremental sync: unchanged transcripts are skipped via mtime+size,
|
|
177
|
+
// changed ones are deleted and re-inserted. Rows for deleted transcripts
|
|
178
|
+
// are kept, so OMP session rotation never erases history. True on success
|
|
177
179
|
// (including nothing-to-do), false when sqlite3 or disk is unavailable.
|
|
178
180
|
export function syncUsageDb(): boolean {
|
|
179
181
|
let db: string;
|
|
@@ -207,8 +209,7 @@ export function syncUsageDb(): boolean {
|
|
|
207
209
|
const prev = known[file];
|
|
208
210
|
if (!prev || prev.mtime !== entry.mtime || prev.size !== entry.size) changed.push(file);
|
|
209
211
|
}
|
|
210
|
-
|
|
211
|
-
if (!changed.length && !removed.length) return true;
|
|
212
|
+
if (!changed.length) return true;
|
|
212
213
|
const chunks: string[] = [];
|
|
213
214
|
let chunkBytes = 0;
|
|
214
215
|
const flush = (): boolean => {
|
|
@@ -228,7 +229,7 @@ export function syncUsageDb(): boolean {
|
|
|
228
229
|
if (chunkBytes < 400_000) return true;
|
|
229
230
|
return flush();
|
|
230
231
|
};
|
|
231
|
-
for (const file of
|
|
232
|
+
for (const file of changed) {
|
|
232
233
|
if (!push(`DELETE FROM messages WHERE file=${esc(file)};DELETE FROM files WHERE path=${esc(file)};`)) return false;
|
|
233
234
|
}
|
|
234
235
|
try {
|
|
Binary file
|