@cookielab.io/oopst 0.2.0 → 0.3.1

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/dist/index.js CHANGED
@@ -21,9 +21,9 @@ var __commonJS = (cb, mod) => function __require2() {
21
21
  };
22
22
  var __copyProps = (to, from, except, desc) => {
23
23
  if (from && typeof from === "object" || typeof from === "function") {
24
- for (let key5 of __getOwnPropNames(from))
25
- if (!__hasOwnProp.call(to, key5) && key5 !== except)
26
- __defProp(to, key5, { get: () => from[key5], enumerable: !(desc = __getOwnPropDesc(from, key5)) || desc.enumerable });
24
+ for (let key6 of __getOwnPropNames(from))
25
+ if (!__hasOwnProp.call(to, key6) && key6 !== except)
26
+ __defProp(to, key6, { get: () => from[key6], enumerable: !(desc = __getOwnPropDesc(from, key6)) || desc.enumerable });
27
27
  }
28
28
  return to;
29
29
  };
@@ -1061,9 +1061,9 @@ var require_option = __commonJS({
1061
1061
  this.positiveOptions.set(option.attributeName(), option);
1062
1062
  }
1063
1063
  });
1064
- this.negativeOptions.forEach((value, key5) => {
1065
- if (this.positiveOptions.has(key5)) {
1066
- this.dualOptions.add(key5);
1064
+ this.negativeOptions.forEach((value, key6) => {
1065
+ if (this.positiveOptions.has(key6)) {
1066
+ this.dualOptions.add(key6);
1067
1067
  }
1068
1068
  });
1069
1069
  }
@@ -1998,11 +1998,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1998
1998
  * @param {string} key
1999
1999
  * @return {object} value
2000
2000
  */
2001
- getOptionValue(key5) {
2001
+ getOptionValue(key6) {
2002
2002
  if (this._storeOptionsAsProperties) {
2003
- return this[key5];
2003
+ return this[key6];
2004
2004
  }
2005
- return this._optionValues[key5];
2005
+ return this._optionValues[key6];
2006
2006
  }
2007
2007
  /**
2008
2008
  * Store option value.
@@ -2011,8 +2011,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2011
2011
  * @param {object} value
2012
2012
  * @return {Command} `this` command for chaining
2013
2013
  */
2014
- setOptionValue(key5, value) {
2015
- return this.setOptionValueWithSource(key5, value, void 0);
2014
+ setOptionValue(key6, value) {
2015
+ return this.setOptionValueWithSource(key6, value, void 0);
2016
2016
  }
2017
2017
  /**
2018
2018
  * Store option value and where the value came from.
@@ -2022,13 +2022,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
2022
2022
  * @param {string} source - expected values are default/config/env/cli/implied
2023
2023
  * @return {Command} `this` command for chaining
2024
2024
  */
2025
- setOptionValueWithSource(key5, value, source) {
2025
+ setOptionValueWithSource(key6, value, source) {
2026
2026
  if (this._storeOptionsAsProperties) {
2027
- this[key5] = value;
2027
+ this[key6] = value;
2028
2028
  } else {
2029
- this._optionValues[key5] = value;
2029
+ this._optionValues[key6] = value;
2030
2030
  }
2031
- this._optionValueSources[key5] = source;
2031
+ this._optionValueSources[key6] = source;
2032
2032
  return this;
2033
2033
  }
2034
2034
  /**
@@ -2038,8 +2038,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2038
2038
  * @param {string} key
2039
2039
  * @return {string}
2040
2040
  */
2041
- getOptionValueSource(key5) {
2042
- return this._optionValueSources[key5];
2041
+ getOptionValueSource(key6) {
2042
+ return this._optionValueSources[key6];
2043
2043
  }
2044
2044
  /**
2045
2045
  * Get source of option value. See also .optsWithGlobals().
@@ -2048,11 +2048,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2048
2048
  * @param {string} key
2049
2049
  * @return {string}
2050
2050
  */
2051
- getOptionValueSourceWithGlobals(key5) {
2051
+ getOptionValueSourceWithGlobals(key6) {
2052
2052
  let source;
2053
2053
  this._getCommandAndAncestors().forEach((cmd) => {
2054
- if (cmd.getOptionValueSource(key5) !== void 0) {
2055
- source = cmd.getOptionValueSource(key5);
2054
+ if (cmd.getOptionValueSource(key6) !== void 0) {
2055
+ source = cmd.getOptionValueSource(key6);
2056
2056
  }
2057
2057
  });
2058
2058
  return source;
@@ -2778,8 +2778,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2778
2778
  const result = {};
2779
2779
  const len = this.options.length;
2780
2780
  for (let i = 0; i < len; i++) {
2781
- const key5 = this.options[i].attributeName();
2782
- result[key5] = key5 === this._versionOptionName ? this._version : this[key5];
2781
+ const key6 = this.options[i].attributeName();
2782
+ result[key6] = key6 === this._versionOptionName ? this._version : this[key6];
2783
2783
  }
2784
2784
  return result;
2785
2785
  }
@@ -3524,7 +3524,9 @@ function readCliEnv() {
3524
3524
  piAgentDir: runtimeEnv["PI_CODING_AGENT_DIR"],
3525
3525
  piSessionDir: runtimeEnv["PI_CODING_AGENT_SESSION_DIR"],
3526
3526
  hermesAgentDir: runtimeEnv["HERMES_CODING_AGENT_DIR"],
3527
- hermesSessionDir: runtimeEnv["HERMES_CODING_AGENT_SESSION_DIR"]
3527
+ hermesSessionDir: runtimeEnv["HERMES_CODING_AGENT_SESSION_DIR"],
3528
+ opencodeDataDir: runtimeEnv["OPENCODE_DATA_DIR"],
3529
+ opencodeDbPath: runtimeEnv["OPENCODE_DB"]
3528
3530
  };
3529
3531
  }
3530
3532
 
@@ -3575,9 +3577,9 @@ function isNetworkError(err) {
3575
3577
 
3576
3578
  // src/sync/parser-factory.ts
3577
3579
  init_esm_shims();
3578
- import { existsSync } from "fs";
3579
- import { homedir as homedir4 } from "os";
3580
- import { join as join5 } from "path";
3580
+ import { existsSync, readdirSync, statSync } from "fs";
3581
+ import { homedir as homedir5 } from "os";
3582
+ import { isAbsolute as isAbsolute2, join as join6 } from "path";
3581
3583
 
3582
3584
  // ../../packages/parsers/src/index.ts
3583
3585
  init_esm_shims();
@@ -3612,6 +3614,14 @@ function readHermesSessionDir() {
3612
3614
  const value = runtimeEnv2["HERMES_CODING_AGENT_SESSION_DIR"]?.trim();
3613
3615
  return value === void 0 || value.length === 0 ? void 0 : value;
3614
3616
  }
3617
+ function readOpenCodeDataDir() {
3618
+ const value = runtimeEnv2["OPENCODE_DATA_DIR"]?.trim();
3619
+ return value === void 0 || value.length === 0 ? void 0 : value;
3620
+ }
3621
+ function readOpenCodeDbPath() {
3622
+ const value = runtimeEnv2["OPENCODE_DB"]?.trim();
3623
+ return value === void 0 || value.length === 0 ? void 0 : value;
3624
+ }
3615
3625
 
3616
3626
  // ../../packages/parsers/src/types.ts
3617
3627
  init_esm_shims();
@@ -4090,11 +4100,419 @@ async function* parseCodex(opts) {
4090
4100
  }
4091
4101
  }
4092
4102
 
4093
- // ../../packages/parsers/src/pi/index.ts
4103
+ // ../../packages/parsers/src/opencode/index.ts
4094
4104
  init_esm_shims();
4095
- import { stat as stat4 } from "fs/promises";
4105
+ import { readdir as readdir2, stat as stat4 } from "fs/promises";
4096
4106
  import { homedir as homedir3 } from "os";
4097
- import { join as join4 } from "path";
4107
+ import { basename, isAbsolute, join as join4 } from "path";
4108
+
4109
+ // ../../packages/parsers/src/opencode/bucketize.ts
4110
+ init_esm_shims();
4111
+ import { readFile } from "fs/promises";
4112
+
4113
+ // ../../packages/parsers/src/opencode/events.ts
4114
+ init_esm_shims();
4115
+ function isObject3(value) {
4116
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4117
+ }
4118
+ function isFiniteNumber3(value) {
4119
+ return typeof value === "number" && Number.isFinite(value);
4120
+ }
4121
+ function timestampMs(value) {
4122
+ if (isFiniteNumber3(value)) {
4123
+ return value;
4124
+ }
4125
+ if (typeof value !== "string") {
4126
+ return void 0;
4127
+ }
4128
+ const parsed = Date.parse(value);
4129
+ return Number.isFinite(parsed) ? parsed : void 0;
4130
+ }
4131
+ function isOpenCodeTokens(value) {
4132
+ if (!isObject3(value)) {
4133
+ return false;
4134
+ }
4135
+ const keys = ["input", "output", "reasoning"];
4136
+ for (const k of keys) {
4137
+ const v = value[k];
4138
+ if (v !== void 0 && !isFiniteNumber3(v)) {
4139
+ return false;
4140
+ }
4141
+ }
4142
+ const cache = value["cache"];
4143
+ if (cache !== void 0) {
4144
+ if (!isObject3(cache)) {
4145
+ return false;
4146
+ }
4147
+ for (const k of ["read", "write"]) {
4148
+ const v = cache[k];
4149
+ if (v !== void 0 && !isFiniteNumber3(v)) {
4150
+ return false;
4151
+ }
4152
+ }
4153
+ }
4154
+ return true;
4155
+ }
4156
+ function usageFromModernMessage(value, fallbackSessionId, fallbackTimestampMs) {
4157
+ const model = value["model"];
4158
+ if (!isObject3(model)) {
4159
+ return void 0;
4160
+ }
4161
+ const providerId = model["providerID"];
4162
+ const modelId3 = model["modelID"] ?? model["id"];
4163
+ if (typeof providerId !== "string" || typeof modelId3 !== "string") {
4164
+ return void 0;
4165
+ }
4166
+ const tokens = value["tokens"];
4167
+ if (!isOpenCodeTokens(tokens)) {
4168
+ return void 0;
4169
+ }
4170
+ const time = value["time"];
4171
+ const messageTimestampMs = isObject3(time) ? timestampMs(time["created"] ?? time["completed"]) : void 0;
4172
+ const sessionId = typeof value["sessionID"] === "string" ? value["sessionID"] : fallbackSessionId;
4173
+ const tsMs = messageTimestampMs ?? fallbackTimestampMs;
4174
+ if (sessionId === void 0 || tsMs === void 0) {
4175
+ return void 0;
4176
+ }
4177
+ return {
4178
+ sessionId,
4179
+ providerId,
4180
+ modelId: modelId3,
4181
+ timestampMs: tsMs,
4182
+ tokens
4183
+ };
4184
+ }
4185
+ function usageFromLegacyMessage(value, fallbackSessionId, fallbackTimestampMs) {
4186
+ if (value["role"] !== "assistant") {
4187
+ return void 0;
4188
+ }
4189
+ const providerId = value["providerID"];
4190
+ const modelId3 = value["modelID"];
4191
+ const tokens = value["tokens"];
4192
+ if (typeof providerId !== "string" || typeof modelId3 !== "string" || !isOpenCodeTokens(tokens)) {
4193
+ return void 0;
4194
+ }
4195
+ const time = value["time"];
4196
+ const messageTimestampMs = isObject3(time) ? timestampMs(time["created"] ?? time["completed"]) : void 0;
4197
+ const sessionId = typeof value["sessionID"] === "string" ? value["sessionID"] : fallbackSessionId;
4198
+ const tsMs = messageTimestampMs ?? fallbackTimestampMs;
4199
+ if (sessionId === void 0 || tsMs === void 0) {
4200
+ return void 0;
4201
+ }
4202
+ return {
4203
+ sessionId,
4204
+ providerId,
4205
+ modelId: modelId3,
4206
+ timestampMs: tsMs,
4207
+ tokens
4208
+ };
4209
+ }
4210
+ function usageFromMetadataMessage(value, fallbackSessionId, fallbackTimestampMs) {
4211
+ if (value["role"] !== "assistant") {
4212
+ return void 0;
4213
+ }
4214
+ const metadata = value["metadata"];
4215
+ if (!isObject3(metadata)) {
4216
+ return void 0;
4217
+ }
4218
+ const assistant = metadata["assistant"];
4219
+ if (!isObject3(assistant)) {
4220
+ return void 0;
4221
+ }
4222
+ const providerId = assistant["providerID"];
4223
+ const modelId3 = assistant["modelID"];
4224
+ const tokens = assistant["tokens"];
4225
+ if (typeof providerId !== "string" || typeof modelId3 !== "string" || !isOpenCodeTokens(tokens)) {
4226
+ return void 0;
4227
+ }
4228
+ const time = metadata["time"];
4229
+ const messageTimestampMs = isObject3(time) ? timestampMs(time["created"] ?? time["completed"]) : void 0;
4230
+ const sessionId = typeof metadata["sessionID"] === "string" ? metadata["sessionID"] : fallbackSessionId;
4231
+ const tsMs = messageTimestampMs ?? fallbackTimestampMs;
4232
+ if (sessionId === void 0 || tsMs === void 0) {
4233
+ return void 0;
4234
+ }
4235
+ return {
4236
+ sessionId,
4237
+ providerId,
4238
+ modelId: modelId3,
4239
+ timestampMs: tsMs,
4240
+ tokens
4241
+ };
4242
+ }
4243
+ function pickOpenCodeUsage(value, fallbackSessionId, fallbackTimestampMs) {
4244
+ if (!isObject3(value)) {
4245
+ return void 0;
4246
+ }
4247
+ return usageFromModernMessage(value, fallbackSessionId, fallbackTimestampMs) ?? usageFromLegacyMessage(value, fallbackSessionId, fallbackTimestampMs) ?? usageFromMetadataMessage(value, fallbackSessionId, fallbackTimestampMs);
4248
+ }
4249
+
4250
+ // ../../packages/parsers/src/opencode/bucketize.ts
4251
+ var NODE_SQLITE_SPECIFIER = ["node", "sqlite"].join(":");
4252
+ var MISSING_SESSION_MESSAGE_TABLE_PATTERN = /no such table: session_message/u;
4253
+ function key3(hourUtc, model, sessionId) {
4254
+ return `${hourUtc.toISOString()}|${model}|${sessionId}`;
4255
+ }
4256
+ function modelId(provider, model) {
4257
+ const trimmedProvider = provider.trim();
4258
+ const trimmedModel = model.trim();
4259
+ if (trimmedProvider.length === 0 || trimmedModel.length === 0) {
4260
+ return void 0;
4261
+ }
4262
+ return `${trimmedProvider}/${trimmedModel}`;
4263
+ }
4264
+ function finiteTimestampMs(value) {
4265
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4266
+ }
4267
+ function positive(value) {
4268
+ return Math.max(0, value ?? 0);
4269
+ }
4270
+ function addUsage(accumulators, usage) {
4271
+ const model = modelId(usage.providerId, usage.modelId);
4272
+ if (model === void 0) {
4273
+ return;
4274
+ }
4275
+ const inputTokens = positive(usage.tokens.input);
4276
+ const outputTokens = positive(usage.tokens.output) + positive(usage.tokens.reasoning);
4277
+ const cacheReadTokens = positive(usage.tokens.cache?.read);
4278
+ const cacheCreateTokens = positive(usage.tokens.cache?.write);
4279
+ if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
4280
+ return;
4281
+ }
4282
+ const eventAt = new Date(usage.timestampMs);
4283
+ const hourUtc = startOfUtcHour(eventAt);
4284
+ const sessionId = `opencode:${usage.sessionId}`;
4285
+ const accumulatorKey = key3(hourUtc, model, sessionId);
4286
+ const existing = accumulators.get(accumulatorKey);
4287
+ if (existing === void 0) {
4288
+ accumulators.set(accumulatorKey, {
4289
+ hourUtc,
4290
+ model,
4291
+ sessionId,
4292
+ inputTokens,
4293
+ outputTokens,
4294
+ cacheReadTokens,
4295
+ cacheCreateTokens,
4296
+ sessionStartedAt: eventAt,
4297
+ sessionLastSeenAt: eventAt
4298
+ });
4299
+ return;
4300
+ }
4301
+ existing.inputTokens += inputTokens;
4302
+ existing.outputTokens += outputTokens;
4303
+ existing.cacheReadTokens += cacheReadTokens;
4304
+ existing.cacheCreateTokens += cacheCreateTokens;
4305
+ if (eventAt.getTime() < existing.sessionStartedAt.getTime()) {
4306
+ existing.sessionStartedAt = eventAt;
4307
+ }
4308
+ if (eventAt.getTime() > existing.sessionLastSeenAt.getTime()) {
4309
+ existing.sessionLastSeenAt = eventAt;
4310
+ }
4311
+ }
4312
+ function* finalizedBuckets(accumulators) {
4313
+ for (const acc of accumulators.values()) {
4314
+ yield {
4315
+ harness: "opencode",
4316
+ model: acc.model,
4317
+ hourUtc: acc.hourUtc,
4318
+ inputTokens: acc.inputTokens,
4319
+ outputTokens: acc.outputTokens,
4320
+ cacheReadTokens: acc.cacheReadTokens,
4321
+ cacheCreateTokens: acc.cacheCreateTokens,
4322
+ sessionIds: /* @__PURE__ */ new Set([acc.sessionId]),
4323
+ sessionStartedAt: acc.sessionStartedAt,
4324
+ sessionLastSeenAt: acc.sessionLastSeenAt
4325
+ };
4326
+ }
4327
+ }
4328
+ function parseJson(value) {
4329
+ return typeof value === "string" ? JSON.parse(value) : value;
4330
+ }
4331
+ function isMissingTableError(err) {
4332
+ return err instanceof Error && MISSING_SESSION_MESSAGE_TABLE_PATTERN.test(err.message);
4333
+ }
4334
+ async function loadDatabaseSync() {
4335
+ const sqliteModule = await import(NODE_SQLITE_SPECIFIER);
4336
+ return sqliteModule.DatabaseSync;
4337
+ }
4338
+ function* readSessionMessageRows(db, cutoff) {
4339
+ const statement = db.prepare(`
4340
+ select session_id as "sessionId", time_created as "timeCreated", data
4341
+ from session_message
4342
+ where type = 'assistant' and time_created >= ?
4343
+ order by time_created asc, id asc
4344
+ `);
4345
+ yield* statement.all(cutoff.getTime());
4346
+ }
4347
+ async function* bucketizeOpenCodeDatabase(path2, cutoff, logger) {
4348
+ const accumulators = /* @__PURE__ */ new Map();
4349
+ const DatabaseSync = await loadDatabaseSync();
4350
+ let db;
4351
+ try {
4352
+ db = new DatabaseSync(path2, { readOnly: true, timeout: 1e3 });
4353
+ for (const row of readSessionMessageRows(db, cutoff)) {
4354
+ const sessionId = typeof row.sessionId === "string" ? row.sessionId : void 0;
4355
+ const fallbackTimestampMs = finiteTimestampMs(row.timeCreated);
4356
+ let parsed;
4357
+ try {
4358
+ parsed = parseJson(row.data);
4359
+ } catch {
4360
+ logger.warn("malformed opencode sqlite message", { path: path2 });
4361
+ continue;
4362
+ }
4363
+ const usage = pickOpenCodeUsage(parsed, sessionId, fallbackTimestampMs);
4364
+ if (usage !== void 0) {
4365
+ addUsage(accumulators, usage);
4366
+ }
4367
+ }
4368
+ } catch (err) {
4369
+ if (!isMissingTableError(err)) {
4370
+ logger.warn("failed to parse opencode sqlite database", { path: path2 });
4371
+ }
4372
+ } finally {
4373
+ db?.close();
4374
+ }
4375
+ yield* finalizedBuckets(accumulators);
4376
+ }
4377
+ async function* bucketizeOpenCodeJsonFile(path2, logger) {
4378
+ let parsed;
4379
+ try {
4380
+ parsed = JSON.parse(await readFile(path2, "utf8"));
4381
+ } catch {
4382
+ logger.warn("malformed opencode json file", { path: path2 });
4383
+ return;
4384
+ }
4385
+ const usage = pickOpenCodeUsage(parsed);
4386
+ if (usage === void 0) {
4387
+ return;
4388
+ }
4389
+ const accumulators = /* @__PURE__ */ new Map();
4390
+ addUsage(accumulators, usage);
4391
+ yield* finalizedBuckets(accumulators);
4392
+ }
4393
+
4394
+ // ../../packages/parsers/src/opencode/index.ts
4395
+ var OPENCODE_DB_FILE_PATTERN = /^opencode(?:-[^/]+)?\.db$/u;
4396
+ async function pathKind(path2) {
4397
+ try {
4398
+ const s = await stat4(path2);
4399
+ if (s.isFile()) {
4400
+ return "file";
4401
+ }
4402
+ if (s.isDirectory()) {
4403
+ return "directory";
4404
+ }
4405
+ return "missing";
4406
+ } catch {
4407
+ return "missing";
4408
+ }
4409
+ }
4410
+ function defaultDataRoot() {
4411
+ return readOpenCodeDataDir() ?? join4(homedir3(), ".local", "share", "opencode");
4412
+ }
4413
+ function resolveDefaultRoot() {
4414
+ const dbPath = readOpenCodeDbPath();
4415
+ if (dbPath === void 0) {
4416
+ return defaultDataRoot();
4417
+ }
4418
+ if (dbPath === ":memory:") {
4419
+ return dbPath;
4420
+ }
4421
+ if (isAbsolute(dbPath)) {
4422
+ return dbPath;
4423
+ }
4424
+ return join4(defaultDataRoot(), dbPath);
4425
+ }
4426
+ async function listOpenCodeDatabases(root) {
4427
+ let entries;
4428
+ try {
4429
+ entries = await readdir2(root, { withFileTypes: true });
4430
+ } catch {
4431
+ return [];
4432
+ }
4433
+ return entries.filter((entry) => entry.isFile() && OPENCODE_DB_FILE_PATTERN.test(String(entry.name))).map((entry) => join4(root, String(entry.name)));
4434
+ }
4435
+ async function listProjectLegacyRoots(root) {
4436
+ const projectRoot = join4(root, "project");
4437
+ let entries;
4438
+ try {
4439
+ entries = await readdir2(projectRoot, { withFileTypes: true });
4440
+ } catch {
4441
+ return [];
4442
+ }
4443
+ return entries.filter((entry) => entry.isDirectory()).flatMap((entry) => [
4444
+ join4(projectRoot, String(entry.name), "storage", "message"),
4445
+ join4(projectRoot, String(entry.name), "storage", "session", "message")
4446
+ ]);
4447
+ }
4448
+ async function legacyRoots(root) {
4449
+ return [
4450
+ join4(root, "storage", "message"),
4451
+ join4(root, "storage", "session", "message"),
4452
+ ...await listProjectLegacyRoots(root)
4453
+ ];
4454
+ }
4455
+ async function* walkJson(root, cutoff) {
4456
+ let entries;
4457
+ try {
4458
+ entries = await readdir2(root, { withFileTypes: true });
4459
+ } catch {
4460
+ return;
4461
+ }
4462
+ for (const entry of entries) {
4463
+ const full = join4(root, String(entry.name));
4464
+ if (entry.isDirectory()) {
4465
+ yield* walkJson(full, cutoff);
4466
+ continue;
4467
+ }
4468
+ if (!(entry.isFile() && entry.name.endsWith(".json"))) {
4469
+ continue;
4470
+ }
4471
+ let mtime;
4472
+ try {
4473
+ ({ mtime } = await stat4(full));
4474
+ } catch {
4475
+ continue;
4476
+ }
4477
+ if (mtime.getTime() >= cutoff.getTime()) {
4478
+ yield full;
4479
+ }
4480
+ }
4481
+ }
4482
+ async function* parseOpenCode(opts) {
4483
+ const logger = opts.logger ?? consoleLogger;
4484
+ const root = opts.root ?? resolveDefaultRoot();
4485
+ const kind = await pathKind(root);
4486
+ if (kind === "missing") {
4487
+ return;
4488
+ }
4489
+ if (kind === "file") {
4490
+ if (basename(root).endsWith(".json")) {
4491
+ yield* bucketizeOpenCodeJsonFile(root, logger);
4492
+ return;
4493
+ }
4494
+ yield* bucketizeOpenCodeDatabase(root, opts.cutoff, logger);
4495
+ return;
4496
+ }
4497
+ const databases = await listOpenCodeDatabases(root);
4498
+ if (databases.length > 0) {
4499
+ for (const dbPath of databases) {
4500
+ yield* bucketizeOpenCodeDatabase(dbPath, opts.cutoff, logger);
4501
+ }
4502
+ return;
4503
+ }
4504
+ for (const legacyRoot of await legacyRoots(root)) {
4505
+ for await (const file of walkJson(legacyRoot, opts.cutoff)) {
4506
+ yield* bucketizeOpenCodeJsonFile(file, logger);
4507
+ }
4508
+ }
4509
+ }
4510
+
4511
+ // ../../packages/parsers/src/pi/index.ts
4512
+ init_esm_shims();
4513
+ import { stat as stat5 } from "fs/promises";
4514
+ import { homedir as homedir4 } from "os";
4515
+ import { join as join5 } from "path";
4098
4516
 
4099
4517
  // ../../packages/parsers/src/pi/bucketize.ts
4100
4518
  init_esm_shims();
@@ -4103,27 +4521,27 @@ import { createInterface as createInterface3 } from "readline";
4103
4521
 
4104
4522
  // ../../packages/parsers/src/pi/events.ts
4105
4523
  init_esm_shims();
4106
- function isObject3(value) {
4524
+ function isObject4(value) {
4107
4525
  return typeof value === "object" && value !== null && !Array.isArray(value);
4108
4526
  }
4109
- function isFiniteNumber3(value) {
4527
+ function isFiniteNumber4(value) {
4110
4528
  return typeof value === "number" && Number.isFinite(value);
4111
4529
  }
4112
4530
  function isPiUsage(value) {
4113
- if (!isObject3(value)) {
4531
+ if (!isObject4(value)) {
4114
4532
  return false;
4115
4533
  }
4116
4534
  const keys = ["input", "output", "cacheRead", "cacheWrite"];
4117
4535
  for (const k of keys) {
4118
4536
  const v = value[k];
4119
- if (v !== void 0 && !isFiniteNumber3(v)) {
4537
+ if (v !== void 0 && !isFiniteNumber4(v)) {
4120
4538
  return false;
4121
4539
  }
4122
4540
  }
4123
4541
  return true;
4124
4542
  }
4125
4543
  function isPiAssistantMessageEntry(value) {
4126
- if (!isObject3(value)) {
4544
+ if (!isObject4(value)) {
4127
4545
  return false;
4128
4546
  }
4129
4547
  if (value["type"] !== "message") {
@@ -4133,7 +4551,7 @@ function isPiAssistantMessageEntry(value) {
4133
4551
  return false;
4134
4552
  }
4135
4553
  const message = value["message"];
4136
- if (!isObject3(message)) {
4554
+ if (!isObject4(message)) {
4137
4555
  return false;
4138
4556
  }
4139
4557
  if (message["role"] !== "assistant") {
@@ -4148,7 +4566,7 @@ function isPiAssistantMessageEntry(value) {
4148
4566
  if (message["stopReason"] !== void 0 && typeof message["stopReason"] !== "string") {
4149
4567
  return false;
4150
4568
  }
4151
- if (message["timestamp"] !== void 0 && !isFiniteNumber3(message["timestamp"])) {
4569
+ if (message["timestamp"] !== void 0 && !isFiniteNumber4(message["timestamp"])) {
4152
4570
  return false;
4153
4571
  }
4154
4572
  if (!isPiUsage(message["usage"])) {
@@ -4158,7 +4576,7 @@ function isPiAssistantMessageEntry(value) {
4158
4576
  }
4159
4577
 
4160
4578
  // ../../packages/parsers/src/pi/bucketize.ts
4161
- function key3(hourUtc, model) {
4579
+ function key4(hourUtc, model) {
4162
4580
  return `${hourUtc.toISOString()}|${model}`;
4163
4581
  }
4164
4582
  function eventTimestampMs(entry) {
@@ -4167,7 +4585,7 @@ function eventTimestampMs(entry) {
4167
4585
  }
4168
4586
  return Date.parse(entry.timestamp);
4169
4587
  }
4170
- function modelId(provider, model) {
4588
+ function modelId2(provider, model) {
4171
4589
  const trimmedProvider = provider.trim();
4172
4590
  const trimmedModel = model.trim();
4173
4591
  if (trimmedProvider.length === 0 || trimmedModel.length === 0) {
@@ -4206,7 +4624,7 @@ async function* bucketizePiStyleFile(path2, harness, logger) {
4206
4624
  if (!Number.isFinite(tsMs)) {
4207
4625
  continue;
4208
4626
  }
4209
- const model = modelId(parsed.message.provider, parsed.message.model);
4627
+ const model = modelId2(parsed.message.provider, parsed.message.model);
4210
4628
  if (model === void 0) {
4211
4629
  continue;
4212
4630
  }
@@ -4226,7 +4644,7 @@ async function* bucketizePiStyleFile(path2, harness, logger) {
4226
4644
  sessionLastSeenAt = eventAt;
4227
4645
  }
4228
4646
  const hourUtc = startOfUtcHour(eventAt);
4229
- const k = key3(hourUtc, model);
4647
+ const k = key4(hourUtc, model);
4230
4648
  const existing = buckets.get(k);
4231
4649
  if (existing === void 0) {
4232
4650
  buckets.set(k, {
@@ -4271,7 +4689,7 @@ async function* bucketizePiStyleFile(path2, harness, logger) {
4271
4689
  // ../../packages/parsers/src/pi/index.ts
4272
4690
  async function isFile3(path2) {
4273
4691
  try {
4274
- return (await stat4(path2)).isFile();
4692
+ return (await stat5(path2)).isFile();
4275
4693
  } catch {
4276
4694
  return false;
4277
4695
  }
@@ -4281,14 +4699,14 @@ function defaultPiRoot() {
4281
4699
  if (sessionDir !== void 0) {
4282
4700
  return sessionDir;
4283
4701
  }
4284
- return join4(readPiAgentDir() ?? join4(homedir3(), ".pi", "agent"), "sessions");
4702
+ return join5(readPiAgentDir() ?? join5(homedir4(), ".pi", "agent"), "sessions");
4285
4703
  }
4286
4704
  function defaultHermesRoot() {
4287
4705
  const sessionDir = readHermesSessionDir();
4288
4706
  if (sessionDir !== void 0) {
4289
4707
  return sessionDir;
4290
4708
  }
4291
- return join4(readHermesAgentDir() ?? join4(homedir3(), ".hermes", "agent"), "sessions");
4709
+ return join5(readHermesAgentDir() ?? join5(homedir4(), ".hermes", "agent"), "sessions");
4292
4710
  }
4293
4711
  async function* parsePiStyle(opts, harness, defaultRoot2) {
4294
4712
  const logger = opts.logger ?? consoleLogger;
@@ -4309,29 +4727,69 @@ async function* parseHermes(opts) {
4309
4727
  }
4310
4728
 
4311
4729
  // src/sync/parser-factory.ts
4730
+ var OPENCODE_DB_FILE_PATTERN2 = /^opencode(?:-[^/]+)?\.db$/u;
4312
4731
  function sessionsRoot(agentDir, sessionDir, fallbackAgentDir) {
4313
- return sessionDir ?? join5(agentDir ?? fallbackAgentDir, "sessions");
4732
+ return sessionDir ?? join6(agentDir ?? fallbackAgentDir, "sessions");
4733
+ }
4734
+ function isFile4(path2) {
4735
+ try {
4736
+ return statSync(path2).isFile();
4737
+ } catch {
4738
+ return false;
4739
+ }
4740
+ }
4741
+ function defaultOpenCodeDataRoot(home) {
4742
+ return join6(home, ".local", "share", "opencode");
4743
+ }
4744
+ function openCodeRoot(home, env) {
4745
+ const dataRoot = env.opencodeDataDir ?? defaultOpenCodeDataRoot(home);
4746
+ const dbPath = env.opencodeDbPath;
4747
+ if (dbPath === void 0 || dbPath === ":memory:") {
4748
+ return dbPath ?? dataRoot;
4749
+ }
4750
+ return isAbsolute2(dbPath) ? dbPath : join6(dataRoot, dbPath);
4751
+ }
4752
+ function hasOpenCodeDatabase(root) {
4753
+ let entries;
4754
+ try {
4755
+ entries = readdirSync(root);
4756
+ } catch {
4757
+ return false;
4758
+ }
4759
+ return entries.some((entry) => OPENCODE_DB_FILE_PATTERN2.test(entry) && isFile4(join6(root, entry)));
4760
+ }
4761
+ function openCodeSourceExists(root) {
4762
+ if (root === ":memory:") {
4763
+ return false;
4764
+ }
4765
+ if (isFile4(root)) {
4766
+ return true;
4767
+ }
4768
+ return hasOpenCodeDatabase(root) || existsSync(join6(root, "storage", "message")) || existsSync(join6(root, "storage", "session", "message")) || existsSync(join6(root, "project"));
4314
4769
  }
4315
4770
  function buildRealParserFactory() {
4316
4771
  return (ctx) => {
4317
- const home = homedir4();
4772
+ const home = homedir5();
4318
4773
  const env = readCliEnv();
4319
- const codexSessionsRoot = join5(home, ".codex", "sessions");
4320
- const codexArchivedRoot = join5(home, ".codex", "archived_sessions");
4774
+ const codexSessionsRoot = join6(home, ".codex", "sessions");
4775
+ const codexArchivedRoot = join6(home, ".codex", "archived_sessions");
4321
4776
  const codexExists = existsSync(codexSessionsRoot) || existsSync(codexArchivedRoot);
4322
4777
  const envDir = env.claudeConfigDir;
4323
- const claudeRoot = envDir == null ? join5(home, ".claude", "projects") : join5(envDir, "projects");
4778
+ const claudeRoot = envDir == null ? join6(home, ".claude", "projects") : join6(envDir, "projects");
4324
4779
  const claudeExists = existsSync(claudeRoot);
4325
- const piRoot = sessionsRoot(env.piAgentDir, env.piSessionDir, join5(home, ".pi", "agent"));
4780
+ const piRoot = sessionsRoot(env.piAgentDir, env.piSessionDir, join6(home, ".pi", "agent"));
4326
4781
  const piExists = existsSync(piRoot);
4327
- const hermesRoot = sessionsRoot(env.hermesAgentDir, env.hermesSessionDir, join5(home, ".hermes", "agent"));
4782
+ const hermesRoot = sessionsRoot(env.hermesAgentDir, env.hermesSessionDir, join6(home, ".hermes", "agent"));
4328
4783
  const hermesExists = existsSync(hermesRoot);
4784
+ const opencodeRoot = openCodeRoot(home, env);
4785
+ const opencodeExists = openCodeSourceExists(opencodeRoot);
4329
4786
  return {
4330
- anySourceExisted: codexExists || claudeExists || piExists || hermesExists,
4787
+ anySourceExisted: codexExists || claudeExists || piExists || hermesExists || opencodeExists,
4331
4788
  codex: parseCodex({ cutoff: ctx.cutoff }),
4332
4789
  claudeCode: parseClaude({ cutoff: ctx.cutoff, root: claudeRoot }),
4333
4790
  pi: parsePi({ cutoff: ctx.cutoff, root: piRoot }),
4334
- hermes: parseHermes({ cutoff: ctx.cutoff, root: hermesRoot })
4791
+ hermes: parseHermes({ cutoff: ctx.cutoff, root: hermesRoot }),
4792
+ opencode: parseOpenCode({ cutoff: ctx.cutoff, root: opencodeRoot })
4335
4793
  };
4336
4794
  };
4337
4795
  }
@@ -4345,7 +4803,7 @@ import { createHmac } from "crypto";
4345
4803
  function toIso(hourUtc) {
4346
4804
  return hourUtc instanceof Date ? hourUtc.toISOString() : hourUtc;
4347
4805
  }
4348
- function key4(b) {
4806
+ function key5(b) {
4349
4807
  return `${b.harness} ${b.model} ${toIso(b.hourUtc)}`;
4350
4808
  }
4351
4809
  function finalize(acc) {
@@ -4402,7 +4860,7 @@ async function* aggregateUsage(source, options) {
4402
4860
  const accumulators = /* @__PURE__ */ new Map();
4403
4861
  const sessionAccumulators = /* @__PURE__ */ new Map();
4404
4862
  for await (const bucket of source) {
4405
- const k = key4(bucket);
4863
+ const k = key5(bucket);
4406
4864
  let acc = accumulators.get(k);
4407
4865
  if (!acc) {
4408
4866
  acc = {
@@ -4461,11 +4919,11 @@ var __commonJS2 = (cb, mod) => function() {
4461
4919
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4462
4920
  };
4463
4921
  var __copyProps2 = (to, from, except, desc) => {
4464
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames2(from), i = 0, n = keys.length, key5; i < n; i++) {
4465
- key5 = keys[i];
4466
- if (!__hasOwnProp2.call(to, key5) && key5 !== except) __defProp2(to, key5, {
4467
- get: ((k) => from[k]).bind(null, key5),
4468
- enumerable: !(desc = __getOwnPropDesc2(from, key5)) || desc.enumerable
4922
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames2(from), i = 0, n = keys.length, key6; i < n; i++) {
4923
+ key6 = keys[i];
4924
+ if (!__hasOwnProp2.call(to, key6) && key6 !== except) __defProp2(to, key6, {
4925
+ get: ((k) => from[k]).bind(null, key6),
4926
+ enumerable: !(desc = __getOwnPropDesc2(from, key6)) || desc.enumerable
4469
4927
  });
4470
4928
  }
4471
4929
  return to;
@@ -4740,7 +5198,7 @@ init_esm_shims();
4740
5198
 
4741
5199
  // ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@6.0.3/node_modules/@trpc/server/dist/codes-DagpWZLc.mjs
4742
5200
  init_esm_shims();
4743
- function isObject4(value) {
5201
+ function isObject5(value) {
4744
5202
  return !!value && !Array.isArray(value) && typeof value === "object";
4745
5203
  }
4746
5204
  function emptyObject() {
@@ -4787,11 +5245,11 @@ var __commonJS3 = (cb, mod) => function() {
4787
5245
  return mod || (0, cb[__getOwnPropNames3(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4788
5246
  };
4789
5247
  var __copyProps3 = (to, from, except, desc) => {
4790
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames3(from), i = 0, n = keys.length, key5; i < n; i++) {
4791
- key5 = keys[i];
4792
- if (!__hasOwnProp3.call(to, key5) && key5 !== except) __defProp3(to, key5, {
4793
- get: ((k) => from[k]).bind(null, key5),
4794
- enumerable: !(desc = __getOwnPropDesc3(from, key5)) || desc.enumerable
5248
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames3(from), i = 0, n = keys.length, key6; i < n; i++) {
5249
+ key6 = keys[i];
5250
+ if (!__hasOwnProp3.call(to, key6) && key6 !== except) __defProp3(to, key6, {
5251
+ get: ((k) => from[k]).bind(null, key6),
5252
+ enumerable: !(desc = __getOwnPropDesc3(from, key6)) || desc.enumerable
4795
5253
  });
4796
5254
  }
4797
5255
  return to;
@@ -4809,9 +5267,9 @@ function createInnerProxy(callback, path2, memo) {
4809
5267
  var _memo$cacheKey;
4810
5268
  const cacheKey = path2.join(".");
4811
5269
  (_memo$cacheKey = memo[cacheKey]) !== null && _memo$cacheKey !== void 0 || (memo[cacheKey] = new Proxy(noop, {
4812
- get(_obj, key5) {
4813
- if (typeof key5 !== "string" || key5 === "then") return void 0;
4814
- return createInnerProxy(callback, [...path2, key5], memo);
5270
+ get(_obj, key6) {
5271
+ if (typeof key6 !== "string" || key6 === "then") return void 0;
5272
+ return createInnerProxy(callback, [...path2, key6], memo);
4815
5273
  },
4816
5274
  apply(_1, _2, args) {
4817
5275
  const lastOfPath = path2[path2.length - 1];
@@ -4951,8 +5409,8 @@ function transformResult(response, transformer) {
4951
5409
  } catch (_unused) {
4952
5410
  throw new TransformResultError();
4953
5411
  }
4954
- if (!result.ok && (!isObject4(result.error.error) || typeof result.error.error["code"] !== "number")) throw new TransformResultError();
4955
- if (result.ok && !isObject4(result.result)) throw new TransformResultError();
5412
+ if (!result.ok && (!isObject5(result.error.error) || typeof result.error.error["code"] !== "number")) throw new TransformResultError();
5413
+ if (result.ok && !isObject5(result.result)) throw new TransformResultError();
4956
5414
  return result;
4957
5415
  }
4958
5416
  var import_objectSpread22 = __toESM3(require_objectSpread22(), 1);
@@ -4964,11 +5422,11 @@ function isTRPCClientError(cause) {
4964
5422
  return cause instanceof TRPCClientError;
4965
5423
  }
4966
5424
  function isTRPCErrorResponse(obj) {
4967
- return isObject4(obj) && isObject4(obj["error"]) && typeof obj["error"]["code"] === "number" && typeof obj["error"]["message"] === "string";
5425
+ return isObject5(obj) && isObject5(obj["error"]) && typeof obj["error"]["code"] === "number" && typeof obj["error"]["message"] === "string";
4968
5426
  }
4969
5427
  function getMessageFromUnknownError(err, fallback) {
4970
5428
  if (typeof err === "string") return err;
4971
- if (isObject4(err) && typeof err["message"] === "string") return err["message"];
5429
+ if (isObject5(err) && typeof err["message"] === "string") return err["message"];
4972
5430
  return fallback;
4973
5431
  }
4974
5432
  var TRPCClientError = class TRPCClientError2 extends Error {
@@ -5213,11 +5671,11 @@ function dataLoader(batchLoader) {
5213
5671
  });
5214
5672
  }
5215
5673
  }
5216
- function load(key5) {
5674
+ function load(key6) {
5217
5675
  var _dispatchTimer;
5218
5676
  const item = {
5219
5677
  aborted: false,
5220
- key: key5,
5678
+ key: key6,
5221
5679
  batch: null,
5222
5680
  resolve: throwFatalError,
5223
5681
  reject: throwFatalError
@@ -5592,9 +6050,9 @@ function createTRPCClientProxy(client) {
5592
6050
  const fullPath = pathCopy.join(".");
5593
6051
  return client[procedureType](fullPath, ...args);
5594
6052
  });
5595
- return createFlatProxy((key5) => {
5596
- if (key5 === untypedClientSymbol) return client;
5597
- return proxy[key5];
6053
+ return createFlatProxy((key6) => {
6054
+ if (key6 === untypedClientSymbol) return client;
6055
+ return proxy[key6];
5598
6056
  });
5599
6057
  }
5600
6058
  function createTRPCClient(opts) {
@@ -5860,6 +6318,9 @@ async function runSync(options) {
5860
6318
  if (options.harness === void 0 || options.harness === "hermes") {
5861
6319
  sources.push({ label: "Hermes", source: parsers.hermes });
5862
6320
  }
6321
+ if (options.harness === void 0 || options.harness === "opencode") {
6322
+ sources.push({ label: "OpenCode", source: parsers.opencode });
6323
+ }
5863
6324
  let bucketsUploaded = 0;
5864
6325
  let sessionsUploaded = 0;
5865
6326
  let batchesUploaded = 0;
@@ -6034,7 +6495,7 @@ function readPackageVersion() {
6034
6495
  return "0.0.0";
6035
6496
  }
6036
6497
  var VERSION = readPackageVersion();
6037
- var SUPPORTED_HARNESSES = ["codex", "claude_code", "pi", "hermes"];
6498
+ var SUPPORTED_HARNESSES = ["codex", "claude_code", "pi", "hermes", "opencode"];
6038
6499
  function writeStdoutLine(msg) {
6039
6500
  process4.stdout.write(`${msg}
6040
6501
  `);
@@ -6068,7 +6529,7 @@ function buildCli() {
6068
6529
  const env = readCliEnv();
6069
6530
  const program2 = new Command();
6070
6531
  program2.name("oopst").description("OopsT \u2014 track AI coding token usage across users and devices").version(VERSION).exitOverride();
6071
- program2.command("sync").description("Scan local Codex and Claude Code logs and upload hourly usage records").option("--token <token>", "CLI API token (or OOPST_TOKEN env)").option("--device <name>", "Device name (or OOPST_DEVICE env)").option("--server <url>", "Server base URL", env.serverUrl).option("--lookback-days <days>", "Override watermark lookback", DEFAULT_LOOKBACK_DAYS).option("--harness <harness>", "Restrict to codex|claude_code|pi|hermes").option("--dry-run", "Parse and print without uploading", false).option("--verbose", "Verbose logs", false).action(
6532
+ program2.command("sync").description("Scan local AI coding harness logs and upload hourly usage records").option("--token <token>", "CLI API token (or OOPST_TOKEN env)").option("--device <name>", "Device name (or OOPST_DEVICE env)").option("--server <url>", "Server base URL", env.serverUrl).option("--lookback-days <days>", "Override watermark lookback", DEFAULT_LOOKBACK_DAYS).option("--harness <harness>", "Restrict to codex|claude_code|pi|hermes|opencode").option("--dry-run", "Parse and print without uploading", false).option("--verbose", "Verbose logs", false).action(
6072
6533
  async (raw) => {
6073
6534
  const token = raw.token ?? env.token;
6074
6535
  const device = raw.device ?? env.device;