tokentracker-cli 0.22.3 → 0.23.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 +4 -3
- package/README.zh-CN.md +4 -3
- package/dashboard/dist/assets/{Card-Ce7Bq6YO.js → Card-BgXt23Ak.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-K_pYvsl8.js → DashboardPage-NVAsY99I.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-B0t441Pp.js → DevicePage-CWTFzGYj.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-rL5OMeU2.js → FadeIn-r11-zwBU.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CGS4pFqm.js → HeaderGithubStar-ZDYMF__6.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-Dg8ZSzBs.js → IpCheckPage-cCSBXnv2.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-BRTdgQhg.js → LandingPage-DDUROVcN.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-DMQ92NBo.js → LeaderboardPage-BaETRtBF.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-BYY0tPSp.js → LeaderboardProfilePage-BthYFghU.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-IguYTVRL.js → LimitsPage-BQpint0k.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-Ct_y3yvb.js → LoginPage-EUjSrjOv.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-BKxuCZoU.js → PopoverPopup-DyjmUoRY.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-BW8DsxOn.js → ProviderIcon-D7GhGvvD.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-BxDpsq6h.js → SettingsPage-9lfQ1c28.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-DGrfA2Ts.js → SkillsPage-Bt7d1kd0.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-DYLXbN7i.js → WidgetsPage-QfPgS0dO.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-C-NcEsy1.js → WrappedPage-0iiBrhBo.js} +1 -1
- package/dashboard/dist/assets/check-CtXuxG1H.js +1 -0
- package/dashboard/dist/assets/{chevron-down-sxrI4ACd.js → chevron-down-Br4ZSPLP.js} +1 -1
- package/dashboard/dist/assets/{download-tZXJTa2X.js → download-CnQNKpma.js} +1 -1
- package/dashboard/dist/assets/{leaderboard-columns-DjTC-eio.js → leaderboard-columns-Bdgb7vS7.js} +1 -1
- package/dashboard/dist/assets/main-0bSXJNLn.css +1 -0
- package/dashboard/dist/assets/{main-DsrMFwQm.js → main-D7f_DoMp.js} +2 -2
- package/dashboard/dist/assets/{use-limits-display-prefs-CzOLYgu5.js → use-limits-display-prefs-B5TQdyrf.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-D6YjGLZJ.js → use-native-settings-CSxKG_Na.js} +1 -1
- package/dashboard/dist/assets/{use-reduced-motion-0uADS5dP.js → use-reduced-motion-C9rLBEnX.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-xh-A-nDC.js → use-usage-limits-Bmok4oKO.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/lib/antigravity-paths.js +48 -0
- package/src/lib/cursor-config.js +20 -47
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +81 -157
- package/src/lib/skills-manager.js +76 -48
- package/src/lib/sqlite-reader.js +136 -0
- package/dashboard/dist/assets/check-fi-rlObU.js +0 -1
- package/dashboard/dist/assets/main-D55hG1yw.css +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -2,10 +2,10 @@ const fs = require("node:fs/promises");
|
|
|
2
2
|
const fssync = require("node:fs");
|
|
3
3
|
const path = require("node:path");
|
|
4
4
|
const readline = require("node:readline");
|
|
5
|
-
const cp = require("node:child_process");
|
|
6
5
|
|
|
7
6
|
const crypto = require("node:crypto");
|
|
8
7
|
const { ensureDir } = require("./fs");
|
|
8
|
+
const { readSqliteJsonRows } = require("./sqlite-reader");
|
|
9
9
|
|
|
10
10
|
const DEFAULT_SOURCE = "codex";
|
|
11
11
|
const DEFAULT_MODEL = "unknown";
|
|
@@ -2381,27 +2381,15 @@ async function walkOpencodeMessages(dir, out) {
|
|
|
2381
2381
|
// OpenCode SQLite DB reader (v1.2+ stores messages in opencode.db)
|
|
2382
2382
|
// ---------------------------------------------------------------------------
|
|
2383
2383
|
|
|
2384
|
-
function readOpencodeDbMessages(dbPath) {
|
|
2384
|
+
function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
2385
2385
|
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
2386
2386
|
const sql = `SELECT id, session_id, time_updated, data FROM message WHERE json_extract(data, '$.role') = 'assistant' ORDER BY time_created ASC`;
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
});
|
|
2394
|
-
} catch (_e) {
|
|
2395
|
-
return [];
|
|
2396
|
-
}
|
|
2397
|
-
if (!raw || !raw.trim()) return [];
|
|
2398
|
-
let rows;
|
|
2399
|
-
try {
|
|
2400
|
-
rows = JSON.parse(raw);
|
|
2401
|
-
} catch (_e) {
|
|
2402
|
-
return [];
|
|
2403
|
-
}
|
|
2404
|
-
if (!Array.isArray(rows)) return [];
|
|
2387
|
+
const rows = readSqliteJsonRows(dbPath, sql, {
|
|
2388
|
+
label: "OpenCode",
|
|
2389
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
2390
|
+
timeout: 30_000,
|
|
2391
|
+
...sqliteOptions,
|
|
2392
|
+
});
|
|
2405
2393
|
const out = [];
|
|
2406
2394
|
for (const row of rows) {
|
|
2407
2395
|
if (!row || typeof row.data !== "string") continue;
|
|
@@ -2717,28 +2705,16 @@ function resolveKiroJsonlPath() {
|
|
|
2717
2705
|
return path.join(resolveKiroBasePath(), "dev_data", "tokens_generated.jsonl");
|
|
2718
2706
|
}
|
|
2719
2707
|
|
|
2720
|
-
function readKiroDbTokens(dbPath, sinceId) {
|
|
2708
|
+
function readKiroDbTokens(dbPath, sinceId, sqliteOptions = {}) {
|
|
2721
2709
|
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
2722
2710
|
const minId = Number.isFinite(sinceId) && sinceId > 0 ? sinceId : 0;
|
|
2723
2711
|
const sql = `SELECT id, model, provider, tokens_prompt, tokens_generated, timestamp FROM tokens_generated WHERE id > ${minId} ORDER BY id ASC`;
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
});
|
|
2731
|
-
} catch (_e) {
|
|
2732
|
-
return [];
|
|
2733
|
-
}
|
|
2734
|
-
if (!raw || !raw.trim()) return [];
|
|
2735
|
-
let rows;
|
|
2736
|
-
try {
|
|
2737
|
-
rows = JSON.parse(raw);
|
|
2738
|
-
} catch (_e) {
|
|
2739
|
-
return [];
|
|
2740
|
-
}
|
|
2741
|
-
return Array.isArray(rows) ? rows : [];
|
|
2712
|
+
return readSqliteJsonRows(dbPath, sql, {
|
|
2713
|
+
label: "Kiro",
|
|
2714
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2715
|
+
timeout: 15_000,
|
|
2716
|
+
...sqliteOptions,
|
|
2717
|
+
});
|
|
2742
2718
|
}
|
|
2743
2719
|
|
|
2744
2720
|
// Read Kiro token data from JSONL fallback (tokens_generated.jsonl).
|
|
@@ -2880,7 +2856,7 @@ function normalizeKiroModelName(raw) {
|
|
|
2880
2856
|
return name || null;
|
|
2881
2857
|
}
|
|
2882
2858
|
|
|
2883
|
-
async function parseKiroIncremental({ dbPath, jsonlPath, cursors, queuePath, onProgress }) {
|
|
2859
|
+
async function parseKiroIncremental({ dbPath, jsonlPath, cursors, queuePath, onProgress, sqliteOptions } = {}) {
|
|
2884
2860
|
await ensureDir(path.dirname(queuePath));
|
|
2885
2861
|
const kiroState = cursors.kiro && typeof cursors.kiro === "object" ? cursors.kiro : {};
|
|
2886
2862
|
const lastDbId = typeof kiroState.lastDbId === "number"
|
|
@@ -2898,7 +2874,7 @@ async function parseKiroIncremental({ dbPath, jsonlPath, cursors, queuePath, onP
|
|
|
2898
2874
|
let nextJsonlLine = lastJsonlLine;
|
|
2899
2875
|
let usingDb = false;
|
|
2900
2876
|
if (fssync.existsSync(resolvedDbPath)) {
|
|
2901
|
-
rows = readKiroDbTokens(resolvedDbPath, lastDbId);
|
|
2877
|
+
rows = readKiroDbTokens(resolvedDbPath, lastDbId, sqliteOptions);
|
|
2902
2878
|
usingDb = true;
|
|
2903
2879
|
// DB and JSONL are siblings for the same usage events. If the DB ever
|
|
2904
2880
|
// disappears (corrupted / wiped) and we fall back to JSONL in a later
|
|
@@ -3088,7 +3064,7 @@ function snapshotSqliteDb(dbPath) {
|
|
|
3088
3064
|
};
|
|
3089
3065
|
}
|
|
3090
3066
|
|
|
3091
|
-
function readHermesSessions(dbPath, lastCompletedEpoch, unfinishedSessionIds = []) {
|
|
3067
|
+
function readHermesSessions(dbPath, lastCompletedEpoch, unfinishedSessionIds = [], sqliteOptions = {}) {
|
|
3092
3068
|
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
3093
3069
|
const since = Number.isFinite(lastCompletedEpoch) && lastCompletedEpoch > 0 ? lastCompletedEpoch : 0;
|
|
3094
3070
|
const forceIds = Array.isArray(unfinishedSessionIds)
|
|
@@ -3116,24 +3092,12 @@ function readHermesSessions(dbPath, lastCompletedEpoch, unfinishedSessionIds = [
|
|
|
3116
3092
|
}
|
|
3117
3093
|
|
|
3118
3094
|
try {
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
});
|
|
3126
|
-
} catch (_e) {
|
|
3127
|
-
return [];
|
|
3128
|
-
}
|
|
3129
|
-
if (!raw || !raw.trim()) return [];
|
|
3130
|
-
let rows;
|
|
3131
|
-
try {
|
|
3132
|
-
rows = JSON.parse(raw);
|
|
3133
|
-
} catch (_e) {
|
|
3134
|
-
return [];
|
|
3135
|
-
}
|
|
3136
|
-
return Array.isArray(rows) ? rows : [];
|
|
3095
|
+
return readSqliteJsonRows(effectiveDbPath, sql, {
|
|
3096
|
+
label: "Hermes",
|
|
3097
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
3098
|
+
timeout: 15_000,
|
|
3099
|
+
...sqliteOptions,
|
|
3100
|
+
});
|
|
3137
3101
|
} finally {
|
|
3138
3102
|
if (snapshot) snapshot.cleanup();
|
|
3139
3103
|
}
|
|
@@ -3147,7 +3111,7 @@ function hasLegacyHermesDefaultState(hermesState) {
|
|
|
3147
3111
|
);
|
|
3148
3112
|
}
|
|
3149
3113
|
|
|
3150
|
-
async function parseHermesIncremental({ hermesPath, dbPath, cursors, queuePath, onProgress }) {
|
|
3114
|
+
async function parseHermesIncremental({ hermesPath, dbPath, cursors, queuePath, onProgress, sqliteOptions } = {}) {
|
|
3151
3115
|
await ensureDir(path.dirname(queuePath));
|
|
3152
3116
|
const hermesState = cursors.hermes && typeof cursors.hermes === "object" ? cursors.hermes : {};
|
|
3153
3117
|
|
|
@@ -3168,7 +3132,12 @@ async function parseHermesIncremental({ hermesPath, dbPath, cursors, queuePath,
|
|
|
3168
3132
|
const trackedUnfinishedSessionIds = Array.isArray(dbState.unfinishedSessionIds)
|
|
3169
3133
|
? dbState.unfinishedSessionIds
|
|
3170
3134
|
: [];
|
|
3171
|
-
const rows = readHermesSessions(
|
|
3135
|
+
const rows = readHermesSessions(
|
|
3136
|
+
dbPath,
|
|
3137
|
+
dbState.lastCompletedStartedAt,
|
|
3138
|
+
trackedUnfinishedSessionIds,
|
|
3139
|
+
sqliteOptions,
|
|
3140
|
+
);
|
|
3172
3141
|
recordsProcessed += rows.length;
|
|
3173
3142
|
if (rows.length === 0) {
|
|
3174
3143
|
dbState.updatedAt = updatedAt;
|
|
@@ -3650,44 +3619,22 @@ function canonicalizeKiroCliModelId(raw) {
|
|
|
3650
3619
|
// `conversation_id` and the inner JSON `continuation_id` are different
|
|
3651
3620
|
// UUIDs on observed data; covering both means retraction fires whichever
|
|
3652
3621
|
// side matches the live session's `session_id`.
|
|
3653
|
-
function readKiroCliRequests(dbPath, env = process.env) {
|
|
3622
|
+
function readKiroCliRequests(dbPath, env = process.env, sqliteOptions = {}) {
|
|
3654
3623
|
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
{ encoding: "utf8", maxBuffer: 128 * 1024 * 1024, timeout: 120_000 },
|
|
3670
|
-
);
|
|
3671
|
-
} catch (err) {
|
|
3672
|
-
// TASK-012 / D-8: debug-gated stderr log so a missing sqlite3 binary
|
|
3673
|
-
// is distinguishable from an empty DB. Silent by default. env is
|
|
3674
|
-
// threaded so tests can toggle debug hermetically.
|
|
3675
|
-
const dbg = String((env && env.TOKENTRACKER_DEBUG) || "").toLowerCase();
|
|
3676
|
-
if (dbg === "1" || dbg === "true") {
|
|
3677
|
-
process.stderr.write(
|
|
3678
|
-
`[kiro-cli] sqlite3 read failed: ${err?.message || err}\n`,
|
|
3679
|
-
);
|
|
3680
|
-
}
|
|
3681
|
-
return [];
|
|
3682
|
-
}
|
|
3683
|
-
if (!raw || !raw.trim()) return [];
|
|
3684
|
-
let rows;
|
|
3685
|
-
try {
|
|
3686
|
-
rows = JSON.parse(raw);
|
|
3687
|
-
} catch {
|
|
3688
|
-
return [];
|
|
3689
|
-
}
|
|
3690
|
-
if (!Array.isArray(rows)) return [];
|
|
3624
|
+
const sql =
|
|
3625
|
+
"SELECT conversation_id, " +
|
|
3626
|
+
"json_extract(value, '$.model_info.model_id') AS session_model_id, " +
|
|
3627
|
+
"json_extract(value, '$.user_turn_metadata.continuation_id') AS continuation_id, " +
|
|
3628
|
+
"json_extract(value, '$.user_turn_metadata.requests') AS requests_json " +
|
|
3629
|
+
"FROM conversations_v2 " +
|
|
3630
|
+
"WHERE json_extract(value, '$.user_turn_metadata.requests') IS NOT NULL";
|
|
3631
|
+
const rows = readSqliteJsonRows(dbPath, sql, {
|
|
3632
|
+
label: "Kiro CLI",
|
|
3633
|
+
env,
|
|
3634
|
+
maxBuffer: 128 * 1024 * 1024,
|
|
3635
|
+
timeout: 120_000,
|
|
3636
|
+
...sqliteOptions,
|
|
3637
|
+
});
|
|
3691
3638
|
const flat = [];
|
|
3692
3639
|
for (const row of rows) {
|
|
3693
3640
|
let requests;
|
|
@@ -3715,7 +3662,7 @@ function readKiroCliRequests(dbPath, env = process.env) {
|
|
|
3715
3662
|
return flat;
|
|
3716
3663
|
}
|
|
3717
3664
|
|
|
3718
|
-
async function parseKiroCliIncremental({ sessionFiles, cursors, queuePath, onProgress, env } = {}) {
|
|
3665
|
+
async function parseKiroCliIncremental({ sessionFiles, cursors, queuePath, onProgress, env, sqliteOptions } = {}) {
|
|
3719
3666
|
await ensureDir(path.dirname(queuePath));
|
|
3720
3667
|
const kiroCliState =
|
|
3721
3668
|
cursors.kiroCli && typeof cursors.kiroCli === "object" ? cursors.kiroCli : {};
|
|
@@ -3750,7 +3697,7 @@ async function parseKiroCliIncremental({ sessionFiles, cursors, queuePath, onPro
|
|
|
3750
3697
|
// SQLite conversation_id OR continuation_id to subtract the orphan
|
|
3751
3698
|
// session-file cursor entry before the new SQLite row is processed.
|
|
3752
3699
|
const flatDb = fssync.existsSync(dbPath)
|
|
3753
|
-
? readKiroCliRequests(dbPath, resolvedEnv)
|
|
3700
|
+
? readKiroCliRequests(dbPath, resolvedEnv, sqliteOptions)
|
|
3754
3701
|
: [];
|
|
3755
3702
|
const sessionFilesList = resolveKiroCliSessionFiles(resolvedEnv);
|
|
3756
3703
|
let flatSessions = [];
|
|
@@ -5203,16 +5150,16 @@ function extractZedTotals(thread) {
|
|
|
5203
5150
|
// several `threads` schemas; older versions may omit created_at /
|
|
5204
5151
|
// folder_paths. We dynamically detect via PRAGMA so the query never fails on
|
|
5205
5152
|
// a missing column.
|
|
5206
|
-
function buildZedThreadsQuery(dbPath, cursorUpdatedAt) {
|
|
5207
|
-
const
|
|
5208
|
-
|
|
5153
|
+
function buildZedThreadsQuery(dbPath, cursorUpdatedAt, sqliteOptions = {}) {
|
|
5154
|
+
const pragmaRows = readSqliteJsonRows(dbPath, "PRAGMA table_info(threads)", {
|
|
5155
|
+
label: "Zed",
|
|
5209
5156
|
maxBuffer: 4 * 1024 * 1024,
|
|
5210
5157
|
timeout: 10_000,
|
|
5158
|
+
...sqliteOptions,
|
|
5211
5159
|
});
|
|
5212
5160
|
const columns = new Set(
|
|
5213
|
-
|
|
5214
|
-
.
|
|
5215
|
-
.map((line) => line.split("|")[1])
|
|
5161
|
+
pragmaRows
|
|
5162
|
+
.map((row) => row?.name)
|
|
5216
5163
|
.filter(Boolean),
|
|
5217
5164
|
);
|
|
5218
5165
|
const optional = (col) => (columns.has(col) ? col : `NULL AS ${col}`);
|
|
@@ -5228,27 +5175,14 @@ function buildZedThreadsQuery(dbPath, cursorUpdatedAt) {
|
|
|
5228
5175
|
return `SELECT id, updated_at, ${optional("created_at")}, data_type, hex(data) AS data_hex FROM threads${where}`;
|
|
5229
5176
|
}
|
|
5230
5177
|
|
|
5231
|
-
function readZedThreadRowsFromSqlite(dbPath, cursorUpdatedAt) {
|
|
5232
|
-
const query = buildZedThreadsQuery(dbPath, cursorUpdatedAt);
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
});
|
|
5240
|
-
} catch (_e) {
|
|
5241
|
-
return [];
|
|
5242
|
-
}
|
|
5243
|
-
if (!raw || !raw.trim()) return [];
|
|
5244
|
-
let rows;
|
|
5245
|
-
try {
|
|
5246
|
-
rows = JSON.parse(raw);
|
|
5247
|
-
} catch (_e) {
|
|
5248
|
-
return [];
|
|
5249
|
-
}
|
|
5250
|
-
if (!Array.isArray(rows)) return [];
|
|
5251
|
-
return rows;
|
|
5178
|
+
function readZedThreadRowsFromSqlite(dbPath, cursorUpdatedAt, sqliteOptions = {}) {
|
|
5179
|
+
const query = buildZedThreadsQuery(dbPath, cursorUpdatedAt, sqliteOptions);
|
|
5180
|
+
return readSqliteJsonRows(dbPath, query, {
|
|
5181
|
+
label: "Zed",
|
|
5182
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
5183
|
+
timeout: 60_000,
|
|
5184
|
+
...sqliteOptions,
|
|
5185
|
+
});
|
|
5252
5186
|
}
|
|
5253
5187
|
|
|
5254
5188
|
async function parseZedIncremental({
|
|
@@ -5257,6 +5191,7 @@ async function parseZedIncremental({
|
|
|
5257
5191
|
queuePath,
|
|
5258
5192
|
onProgress,
|
|
5259
5193
|
env,
|
|
5194
|
+
sqliteOptions,
|
|
5260
5195
|
} = {}) {
|
|
5261
5196
|
await ensureDir(path.dirname(queuePath));
|
|
5262
5197
|
const resolvedDb = dbPath || resolveZedDbPath(env || process.env);
|
|
@@ -5294,7 +5229,7 @@ async function parseZedIncremental({
|
|
|
5294
5229
|
const snap = snapshotSqliteDb(resolvedDb);
|
|
5295
5230
|
let rows = [];
|
|
5296
5231
|
try {
|
|
5297
|
-
rows = readZedThreadRowsFromSqlite(snap.path, cursorUpdatedAt);
|
|
5232
|
+
rows = readZedThreadRowsFromSqlite(snap.path, cursorUpdatedAt, sqliteOptions);
|
|
5298
5233
|
} finally {
|
|
5299
5234
|
snap.cleanup();
|
|
5300
5235
|
}
|
|
@@ -5528,21 +5463,18 @@ function parseGooseCreatedAt(s) {
|
|
|
5528
5463
|
return null;
|
|
5529
5464
|
}
|
|
5530
5465
|
|
|
5531
|
-
function readGooseSessionsFromSqlite(dbPath) {
|
|
5466
|
+
function readGooseSessionsFromSqlite(dbPath, sqliteOptions = {}) {
|
|
5532
5467
|
// Probe columns: the `accumulated_*` fields were added in a later Goose
|
|
5533
5468
|
// version; we keep the query forgiving so older installs still work.
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
});
|
|
5541
|
-
} catch (_e) { return []; }
|
|
5469
|
+
const pragmaRows = readSqliteJsonRows(dbPath, "PRAGMA table_info(sessions)", {
|
|
5470
|
+
label: "Goose",
|
|
5471
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
5472
|
+
timeout: 10_000,
|
|
5473
|
+
...sqliteOptions,
|
|
5474
|
+
});
|
|
5542
5475
|
const columns = new Set(
|
|
5543
|
-
|
|
5544
|
-
.
|
|
5545
|
-
.map((line) => line.split("|")[1])
|
|
5476
|
+
pragmaRows
|
|
5477
|
+
.map((row) => row?.name)
|
|
5546
5478
|
.filter(Boolean),
|
|
5547
5479
|
);
|
|
5548
5480
|
const optional = (col) => (columns.has(col) ? col : `NULL AS ${col}`);
|
|
@@ -5562,21 +5494,12 @@ function readGooseSessionsFromSqlite(dbPath) {
|
|
|
5562
5494
|
WHERE model_config_json IS NOT NULL
|
|
5563
5495
|
AND TRIM(model_config_json) != ''
|
|
5564
5496
|
`.trim();
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
});
|
|
5572
|
-
} catch (_e) { return []; }
|
|
5573
|
-
if (!raw || !raw.trim()) return [];
|
|
5574
|
-
try {
|
|
5575
|
-
const rows = JSON.parse(raw);
|
|
5576
|
-
return Array.isArray(rows) ? rows : [];
|
|
5577
|
-
} catch (_e) {
|
|
5578
|
-
return [];
|
|
5579
|
-
}
|
|
5497
|
+
return readSqliteJsonRows(dbPath, sql, {
|
|
5498
|
+
label: "Goose",
|
|
5499
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
5500
|
+
timeout: 60_000,
|
|
5501
|
+
...sqliteOptions,
|
|
5502
|
+
});
|
|
5580
5503
|
}
|
|
5581
5504
|
|
|
5582
5505
|
async function parseGooseIncremental({
|
|
@@ -5585,6 +5508,7 @@ async function parseGooseIncremental({
|
|
|
5585
5508
|
queuePath,
|
|
5586
5509
|
onProgress,
|
|
5587
5510
|
env,
|
|
5511
|
+
sqliteOptions,
|
|
5588
5512
|
} = {}) {
|
|
5589
5513
|
await ensureDir(path.dirname(queuePath));
|
|
5590
5514
|
const resolvedDb = dbPath || resolveGooseDbPath(env || process.env);
|
|
@@ -5618,7 +5542,7 @@ async function parseGooseIncremental({
|
|
|
5618
5542
|
const snap = snapshotSqliteDb(resolvedDb);
|
|
5619
5543
|
let rows = [];
|
|
5620
5544
|
try {
|
|
5621
|
-
rows = readGooseSessionsFromSqlite(snap.path);
|
|
5545
|
+
rows = readGooseSessionsFromSqlite(snap.path, sqliteOptions);
|
|
5622
5546
|
} finally {
|
|
5623
5547
|
snap.cleanup();
|
|
5624
5548
|
}
|
|
@@ -2,6 +2,7 @@ const fs = require("node:fs");
|
|
|
2
2
|
const os = require("node:os");
|
|
3
3
|
const path = require("node:path");
|
|
4
4
|
const { resolveGrokHome } = require("./grok-hook");
|
|
5
|
+
const { resolveAntigravitySkillDirs } = require("./antigravity-paths");
|
|
5
6
|
|
|
6
7
|
const DEFAULT_REPOS = [
|
|
7
8
|
{ owner: "anthropics", name: "skills", branch: "main", enabled: true },
|
|
@@ -14,12 +15,28 @@ const TARGETS = {
|
|
|
14
15
|
claude: { id: "claude", label: "Claude", dir: () => path.join(os.homedir(), ".claude", "skills") },
|
|
15
16
|
codex: { id: "codex", label: "Codex", dir: () => path.join(os.homedir(), ".codex", "skills") },
|
|
16
17
|
grok: { id: "grok", label: "Grok", dir: () => path.join(resolveGrokHome(process.env), "skills") },
|
|
18
|
+
antigravity: { id: "antigravity", label: "Antigravity", dirs: () => resolveAntigravitySkillDirs(process.env) },
|
|
17
19
|
gemini: { id: "gemini", label: "Gemini", dir: () => path.join(os.homedir(), ".gemini", "skills") },
|
|
18
20
|
opencode: { id: "opencode", label: "OpenCode", dir: () => path.join(os.homedir(), ".config", "opencode", "skills") },
|
|
19
21
|
hermes: { id: "hermes", label: "Hermes", dir: () => path.join(os.homedir(), ".hermes", "skills") },
|
|
20
22
|
agents: { id: "agents", label: "Agents", visible: false, dir: () => path.join(os.homedir(), ".agents", "skills") },
|
|
21
23
|
};
|
|
22
24
|
|
|
25
|
+
// Dual contract: a target exposes either dir() → string (single path) or
|
|
26
|
+
// dirs() → string[] (parallel-write to multiple paths, e.g. Antigravity which
|
|
27
|
+
// has separate user-skills dirs for the main app and the IDE). Consumers must
|
|
28
|
+
// route through these helpers so the single-path targets stay zero-overhead.
|
|
29
|
+
function targetDirs(target) {
|
|
30
|
+
if (typeof target.dirs === "function") return target.dirs();
|
|
31
|
+
return [target.dir()];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Used for surfacing one path in UI/local-api responses. For multi-dir targets
|
|
35
|
+
// this is the canonical "first" entry by convention (main app before IDE).
|
|
36
|
+
function targetPrimaryDir(target) {
|
|
37
|
+
return targetDirs(target)[0];
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
const FETCH_TIMEOUT_MS = 20_000;
|
|
24
41
|
const DISCOVER_CONCURRENCY = 4;
|
|
25
42
|
const DISCOVER_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
@@ -130,7 +147,7 @@ function targetList() {
|
|
|
130
147
|
.map((target) => ({
|
|
131
148
|
id: target.id,
|
|
132
149
|
label: target.label,
|
|
133
|
-
path: target
|
|
150
|
+
path: targetPrimaryDir(target),
|
|
134
151
|
}));
|
|
135
152
|
}
|
|
136
153
|
|
|
@@ -342,27 +359,35 @@ function syncSkillToTarget(directory, targetId) {
|
|
|
342
359
|
const target = TARGETS[targetId];
|
|
343
360
|
if (!target) throw new Error(`Unsupported target: ${targetId}`);
|
|
344
361
|
const source = path.join(ssotDir(), directory);
|
|
345
|
-
const dest = path.join(target.dir(), directory);
|
|
346
362
|
if (!fs.existsSync(source)) throw new Error(`Managed skill not found: ${directory}`);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
363
|
+
for (const baseDir of targetDirs(target)) {
|
|
364
|
+
const dest = path.join(baseDir, directory);
|
|
365
|
+
ensureDir(path.dirname(dest));
|
|
366
|
+
removePath(dest);
|
|
367
|
+
try {
|
|
368
|
+
fs.symlinkSync(source, dest, "dir");
|
|
369
|
+
} catch (_e) {
|
|
370
|
+
copyDir(source, dest);
|
|
371
|
+
}
|
|
353
372
|
}
|
|
354
373
|
}
|
|
355
374
|
|
|
356
375
|
function removeSkillFromTarget(directory, targetId) {
|
|
357
376
|
const target = TARGETS[targetId];
|
|
358
377
|
if (!target) return;
|
|
359
|
-
|
|
378
|
+
for (const baseDir of targetDirs(target)) {
|
|
379
|
+
removePath(path.join(baseDir, directory));
|
|
380
|
+
}
|
|
360
381
|
}
|
|
361
382
|
|
|
362
383
|
function scanTargetSkill(directory, targetId) {
|
|
363
384
|
const target = TARGETS[targetId];
|
|
364
385
|
if (!target) return false;
|
|
365
|
-
|
|
386
|
+
for (const baseDir of targetDirs(target)) {
|
|
387
|
+
const candidate = path.join(baseDir, directory);
|
|
388
|
+
if (fs.existsSync(candidate) || isSymlink(candidate)) return true;
|
|
389
|
+
}
|
|
390
|
+
return false;
|
|
366
391
|
}
|
|
367
392
|
|
|
368
393
|
function listInstalledSkills() {
|
|
@@ -378,41 +403,42 @@ function listInstalledSkills() {
|
|
|
378
403
|
const managedDirs = new Set(managed.map((skill) => skill.directory.toLowerCase()));
|
|
379
404
|
const unmanaged = new Map();
|
|
380
405
|
for (const target of Object.values(TARGETS)) {
|
|
381
|
-
const dir
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
406
|
+
for (const dir of targetDirs(target)) {
|
|
407
|
+
let entries = [];
|
|
408
|
+
try {
|
|
409
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
410
|
+
} catch (_e) {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
for (const entry of entries) {
|
|
414
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
415
|
+
const directory = entry.name;
|
|
416
|
+
if (!directory || directory.startsWith(".") || managedDirs.has(directory.toLowerCase())) continue;
|
|
417
|
+
const skillPath = path.join(dir, directory, "SKILL.md");
|
|
418
|
+
if (!fs.existsSync(skillPath)) continue;
|
|
419
|
+
const metadata = readSkillMetadata(fs.readFileSync(skillPath, "utf8"), directory);
|
|
420
|
+
const key = directory.toLowerCase();
|
|
421
|
+
if (!unmanaged.has(key)) {
|
|
422
|
+
unmanaged.set(key, {
|
|
423
|
+
id: `local:${directory}`,
|
|
424
|
+
key: `local:${directory}`,
|
|
425
|
+
name: metadata.name,
|
|
426
|
+
description: metadata.description,
|
|
427
|
+
directory,
|
|
428
|
+
readmeUrl: null,
|
|
429
|
+
repoOwner: null,
|
|
430
|
+
repoName: null,
|
|
431
|
+
repoBranch: null,
|
|
432
|
+
installedAt: null,
|
|
433
|
+
managed: false,
|
|
434
|
+
targets: [],
|
|
435
|
+
targetPaths: {},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
const skill = unmanaged.get(key);
|
|
439
|
+
if (!skill.targets.includes(target.id)) skill.targets.push(target.id);
|
|
440
|
+
if (!skill.targetPaths[target.id]) skill.targetPaths[target.id] = path.join(dir, directory);
|
|
412
441
|
}
|
|
413
|
-
const skill = unmanaged.get(key);
|
|
414
|
-
skill.targets.push(target.id);
|
|
415
|
-
skill.targetPaths[target.id] = path.join(dir, directory);
|
|
416
442
|
}
|
|
417
443
|
}
|
|
418
444
|
|
|
@@ -596,10 +622,12 @@ function findLocalSkillSource(directory) {
|
|
|
596
622
|
const installName = sanitizePathSegment(directory);
|
|
597
623
|
if (!installName) return null;
|
|
598
624
|
for (const target of Object.values(TARGETS)) {
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
625
|
+
for (const baseDir of targetDirs(target)) {
|
|
626
|
+
const skillPath = path.join(baseDir, installName);
|
|
627
|
+
const docPath = path.join(skillPath, "SKILL.md");
|
|
628
|
+
if (fs.existsSync(docPath)) {
|
|
629
|
+
return { path: skillPath, targetId: target.id };
|
|
630
|
+
}
|
|
603
631
|
}
|
|
604
632
|
}
|
|
605
633
|
return null;
|