@cookielab.io/oopst 0.1.0 → 0.3.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/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,414 @@ 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
+ import { DatabaseSync } from "sqlite";
4113
+
4114
+ // ../../packages/parsers/src/opencode/events.ts
4115
+ init_esm_shims();
4116
+ function isObject3(value) {
4117
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4118
+ }
4119
+ function isFiniteNumber3(value) {
4120
+ return typeof value === "number" && Number.isFinite(value);
4121
+ }
4122
+ function timestampMs(value) {
4123
+ if (isFiniteNumber3(value)) {
4124
+ return value;
4125
+ }
4126
+ if (typeof value !== "string") {
4127
+ return void 0;
4128
+ }
4129
+ const parsed = Date.parse(value);
4130
+ return Number.isFinite(parsed) ? parsed : void 0;
4131
+ }
4132
+ function isOpenCodeTokens(value) {
4133
+ if (!isObject3(value)) {
4134
+ return false;
4135
+ }
4136
+ const keys = ["input", "output", "reasoning"];
4137
+ for (const k of keys) {
4138
+ const v = value[k];
4139
+ if (v !== void 0 && !isFiniteNumber3(v)) {
4140
+ return false;
4141
+ }
4142
+ }
4143
+ const cache = value["cache"];
4144
+ if (cache !== void 0) {
4145
+ if (!isObject3(cache)) {
4146
+ return false;
4147
+ }
4148
+ for (const k of ["read", "write"]) {
4149
+ const v = cache[k];
4150
+ if (v !== void 0 && !isFiniteNumber3(v)) {
4151
+ return false;
4152
+ }
4153
+ }
4154
+ }
4155
+ return true;
4156
+ }
4157
+ function usageFromModernMessage(value, fallbackSessionId, fallbackTimestampMs) {
4158
+ const model = value["model"];
4159
+ if (!isObject3(model)) {
4160
+ return void 0;
4161
+ }
4162
+ const providerId = model["providerID"];
4163
+ const modelId3 = model["modelID"] ?? model["id"];
4164
+ if (typeof providerId !== "string" || typeof modelId3 !== "string") {
4165
+ return void 0;
4166
+ }
4167
+ const tokens = value["tokens"];
4168
+ if (!isOpenCodeTokens(tokens)) {
4169
+ return void 0;
4170
+ }
4171
+ const time = value["time"];
4172
+ const messageTimestampMs = isObject3(time) ? timestampMs(time["created"] ?? time["completed"]) : void 0;
4173
+ const sessionId = typeof value["sessionID"] === "string" ? value["sessionID"] : fallbackSessionId;
4174
+ const tsMs = messageTimestampMs ?? fallbackTimestampMs;
4175
+ if (sessionId === void 0 || tsMs === void 0) {
4176
+ return void 0;
4177
+ }
4178
+ return {
4179
+ sessionId,
4180
+ providerId,
4181
+ modelId: modelId3,
4182
+ timestampMs: tsMs,
4183
+ tokens
4184
+ };
4185
+ }
4186
+ function usageFromLegacyMessage(value, fallbackSessionId, fallbackTimestampMs) {
4187
+ if (value["role"] !== "assistant") {
4188
+ return void 0;
4189
+ }
4190
+ const providerId = value["providerID"];
4191
+ const modelId3 = value["modelID"];
4192
+ const tokens = value["tokens"];
4193
+ if (typeof providerId !== "string" || typeof modelId3 !== "string" || !isOpenCodeTokens(tokens)) {
4194
+ return void 0;
4195
+ }
4196
+ const time = value["time"];
4197
+ const messageTimestampMs = isObject3(time) ? timestampMs(time["created"] ?? time["completed"]) : void 0;
4198
+ const sessionId = typeof value["sessionID"] === "string" ? value["sessionID"] : fallbackSessionId;
4199
+ const tsMs = messageTimestampMs ?? fallbackTimestampMs;
4200
+ if (sessionId === void 0 || tsMs === void 0) {
4201
+ return void 0;
4202
+ }
4203
+ return {
4204
+ sessionId,
4205
+ providerId,
4206
+ modelId: modelId3,
4207
+ timestampMs: tsMs,
4208
+ tokens
4209
+ };
4210
+ }
4211
+ function usageFromMetadataMessage(value, fallbackSessionId, fallbackTimestampMs) {
4212
+ if (value["role"] !== "assistant") {
4213
+ return void 0;
4214
+ }
4215
+ const metadata = value["metadata"];
4216
+ if (!isObject3(metadata)) {
4217
+ return void 0;
4218
+ }
4219
+ const assistant = metadata["assistant"];
4220
+ if (!isObject3(assistant)) {
4221
+ return void 0;
4222
+ }
4223
+ const providerId = assistant["providerID"];
4224
+ const modelId3 = assistant["modelID"];
4225
+ const tokens = assistant["tokens"];
4226
+ if (typeof providerId !== "string" || typeof modelId3 !== "string" || !isOpenCodeTokens(tokens)) {
4227
+ return void 0;
4228
+ }
4229
+ const time = metadata["time"];
4230
+ const messageTimestampMs = isObject3(time) ? timestampMs(time["created"] ?? time["completed"]) : void 0;
4231
+ const sessionId = typeof metadata["sessionID"] === "string" ? metadata["sessionID"] : fallbackSessionId;
4232
+ const tsMs = messageTimestampMs ?? fallbackTimestampMs;
4233
+ if (sessionId === void 0 || tsMs === void 0) {
4234
+ return void 0;
4235
+ }
4236
+ return {
4237
+ sessionId,
4238
+ providerId,
4239
+ modelId: modelId3,
4240
+ timestampMs: tsMs,
4241
+ tokens
4242
+ };
4243
+ }
4244
+ function pickOpenCodeUsage(value, fallbackSessionId, fallbackTimestampMs) {
4245
+ if (!isObject3(value)) {
4246
+ return void 0;
4247
+ }
4248
+ return usageFromModernMessage(value, fallbackSessionId, fallbackTimestampMs) ?? usageFromLegacyMessage(value, fallbackSessionId, fallbackTimestampMs) ?? usageFromMetadataMessage(value, fallbackSessionId, fallbackTimestampMs);
4249
+ }
4250
+
4251
+ // ../../packages/parsers/src/opencode/bucketize.ts
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
+ function* readSessionMessageRows(db, cutoff) {
4335
+ const statement = db.prepare(`
4336
+ select session_id as "sessionId", time_created as "timeCreated", data
4337
+ from session_message
4338
+ where type = 'assistant' and time_created >= ?
4339
+ order by time_created asc, id asc
4340
+ `);
4341
+ yield* statement.all(cutoff.getTime());
4342
+ }
4343
+ async function* bucketizeOpenCodeDatabase(path2, cutoff, logger) {
4344
+ const accumulators = /* @__PURE__ */ new Map();
4345
+ let db;
4346
+ try {
4347
+ db = new DatabaseSync(path2, { readOnly: true, timeout: 1e3 });
4348
+ for (const row of readSessionMessageRows(db, cutoff)) {
4349
+ const sessionId = typeof row.sessionId === "string" ? row.sessionId : void 0;
4350
+ const fallbackTimestampMs = finiteTimestampMs(row.timeCreated);
4351
+ let parsed;
4352
+ try {
4353
+ parsed = parseJson(row.data);
4354
+ } catch {
4355
+ logger.warn("malformed opencode sqlite message", { path: path2 });
4356
+ continue;
4357
+ }
4358
+ const usage = pickOpenCodeUsage(parsed, sessionId, fallbackTimestampMs);
4359
+ if (usage !== void 0) {
4360
+ addUsage(accumulators, usage);
4361
+ }
4362
+ }
4363
+ } catch (err) {
4364
+ if (!isMissingTableError(err)) {
4365
+ logger.warn("failed to parse opencode sqlite database", { path: path2 });
4366
+ }
4367
+ } finally {
4368
+ db?.close();
4369
+ }
4370
+ yield* finalizedBuckets(accumulators);
4371
+ }
4372
+ async function* bucketizeOpenCodeJsonFile(path2, logger) {
4373
+ let parsed;
4374
+ try {
4375
+ parsed = JSON.parse(await readFile(path2, "utf8"));
4376
+ } catch {
4377
+ logger.warn("malformed opencode json file", { path: path2 });
4378
+ return;
4379
+ }
4380
+ const usage = pickOpenCodeUsage(parsed);
4381
+ if (usage === void 0) {
4382
+ return;
4383
+ }
4384
+ const accumulators = /* @__PURE__ */ new Map();
4385
+ addUsage(accumulators, usage);
4386
+ yield* finalizedBuckets(accumulators);
4387
+ }
4388
+
4389
+ // ../../packages/parsers/src/opencode/index.ts
4390
+ var OPENCODE_DB_FILE_PATTERN = /^opencode(?:-[^/]+)?\.db$/u;
4391
+ async function pathKind(path2) {
4392
+ try {
4393
+ const s = await stat4(path2);
4394
+ if (s.isFile()) {
4395
+ return "file";
4396
+ }
4397
+ if (s.isDirectory()) {
4398
+ return "directory";
4399
+ }
4400
+ return "missing";
4401
+ } catch {
4402
+ return "missing";
4403
+ }
4404
+ }
4405
+ function defaultDataRoot() {
4406
+ return readOpenCodeDataDir() ?? join4(homedir3(), ".local", "share", "opencode");
4407
+ }
4408
+ function resolveDefaultRoot() {
4409
+ const dbPath = readOpenCodeDbPath();
4410
+ if (dbPath === void 0) {
4411
+ return defaultDataRoot();
4412
+ }
4413
+ if (dbPath === ":memory:") {
4414
+ return dbPath;
4415
+ }
4416
+ if (isAbsolute(dbPath)) {
4417
+ return dbPath;
4418
+ }
4419
+ return join4(defaultDataRoot(), dbPath);
4420
+ }
4421
+ async function listOpenCodeDatabases(root) {
4422
+ let entries;
4423
+ try {
4424
+ entries = await readdir2(root, { withFileTypes: true });
4425
+ } catch {
4426
+ return [];
4427
+ }
4428
+ return entries.filter((entry) => entry.isFile() && OPENCODE_DB_FILE_PATTERN.test(String(entry.name))).map((entry) => join4(root, String(entry.name)));
4429
+ }
4430
+ async function listProjectLegacyRoots(root) {
4431
+ const projectRoot = join4(root, "project");
4432
+ let entries;
4433
+ try {
4434
+ entries = await readdir2(projectRoot, { withFileTypes: true });
4435
+ } catch {
4436
+ return [];
4437
+ }
4438
+ return entries.filter((entry) => entry.isDirectory()).flatMap((entry) => [
4439
+ join4(projectRoot, String(entry.name), "storage", "message"),
4440
+ join4(projectRoot, String(entry.name), "storage", "session", "message")
4441
+ ]);
4442
+ }
4443
+ async function legacyRoots(root) {
4444
+ return [
4445
+ join4(root, "storage", "message"),
4446
+ join4(root, "storage", "session", "message"),
4447
+ ...await listProjectLegacyRoots(root)
4448
+ ];
4449
+ }
4450
+ async function* walkJson(root, cutoff) {
4451
+ let entries;
4452
+ try {
4453
+ entries = await readdir2(root, { withFileTypes: true });
4454
+ } catch {
4455
+ return;
4456
+ }
4457
+ for (const entry of entries) {
4458
+ const full = join4(root, String(entry.name));
4459
+ if (entry.isDirectory()) {
4460
+ yield* walkJson(full, cutoff);
4461
+ continue;
4462
+ }
4463
+ if (!(entry.isFile() && entry.name.endsWith(".json"))) {
4464
+ continue;
4465
+ }
4466
+ let mtime;
4467
+ try {
4468
+ ({ mtime } = await stat4(full));
4469
+ } catch {
4470
+ continue;
4471
+ }
4472
+ if (mtime.getTime() >= cutoff.getTime()) {
4473
+ yield full;
4474
+ }
4475
+ }
4476
+ }
4477
+ async function* parseOpenCode(opts) {
4478
+ const logger = opts.logger ?? consoleLogger;
4479
+ const root = opts.root ?? resolveDefaultRoot();
4480
+ const kind = await pathKind(root);
4481
+ if (kind === "missing") {
4482
+ return;
4483
+ }
4484
+ if (kind === "file") {
4485
+ if (basename(root).endsWith(".json")) {
4486
+ yield* bucketizeOpenCodeJsonFile(root, logger);
4487
+ return;
4488
+ }
4489
+ yield* bucketizeOpenCodeDatabase(root, opts.cutoff, logger);
4490
+ return;
4491
+ }
4492
+ const databases = await listOpenCodeDatabases(root);
4493
+ if (databases.length > 0) {
4494
+ for (const dbPath of databases) {
4495
+ yield* bucketizeOpenCodeDatabase(dbPath, opts.cutoff, logger);
4496
+ }
4497
+ return;
4498
+ }
4499
+ for (const legacyRoot of await legacyRoots(root)) {
4500
+ for await (const file of walkJson(legacyRoot, opts.cutoff)) {
4501
+ yield* bucketizeOpenCodeJsonFile(file, logger);
4502
+ }
4503
+ }
4504
+ }
4505
+
4506
+ // ../../packages/parsers/src/pi/index.ts
4507
+ init_esm_shims();
4508
+ import { stat as stat5 } from "fs/promises";
4509
+ import { homedir as homedir4 } from "os";
4510
+ import { join as join5 } from "path";
4098
4511
 
4099
4512
  // ../../packages/parsers/src/pi/bucketize.ts
4100
4513
  init_esm_shims();
@@ -4103,27 +4516,27 @@ import { createInterface as createInterface3 } from "readline";
4103
4516
 
4104
4517
  // ../../packages/parsers/src/pi/events.ts
4105
4518
  init_esm_shims();
4106
- function isObject3(value) {
4519
+ function isObject4(value) {
4107
4520
  return typeof value === "object" && value !== null && !Array.isArray(value);
4108
4521
  }
4109
- function isFiniteNumber3(value) {
4522
+ function isFiniteNumber4(value) {
4110
4523
  return typeof value === "number" && Number.isFinite(value);
4111
4524
  }
4112
4525
  function isPiUsage(value) {
4113
- if (!isObject3(value)) {
4526
+ if (!isObject4(value)) {
4114
4527
  return false;
4115
4528
  }
4116
4529
  const keys = ["input", "output", "cacheRead", "cacheWrite"];
4117
4530
  for (const k of keys) {
4118
4531
  const v = value[k];
4119
- if (v !== void 0 && !isFiniteNumber3(v)) {
4532
+ if (v !== void 0 && !isFiniteNumber4(v)) {
4120
4533
  return false;
4121
4534
  }
4122
4535
  }
4123
4536
  return true;
4124
4537
  }
4125
4538
  function isPiAssistantMessageEntry(value) {
4126
- if (!isObject3(value)) {
4539
+ if (!isObject4(value)) {
4127
4540
  return false;
4128
4541
  }
4129
4542
  if (value["type"] !== "message") {
@@ -4133,7 +4546,7 @@ function isPiAssistantMessageEntry(value) {
4133
4546
  return false;
4134
4547
  }
4135
4548
  const message = value["message"];
4136
- if (!isObject3(message)) {
4549
+ if (!isObject4(message)) {
4137
4550
  return false;
4138
4551
  }
4139
4552
  if (message["role"] !== "assistant") {
@@ -4148,7 +4561,7 @@ function isPiAssistantMessageEntry(value) {
4148
4561
  if (message["stopReason"] !== void 0 && typeof message["stopReason"] !== "string") {
4149
4562
  return false;
4150
4563
  }
4151
- if (message["timestamp"] !== void 0 && !isFiniteNumber3(message["timestamp"])) {
4564
+ if (message["timestamp"] !== void 0 && !isFiniteNumber4(message["timestamp"])) {
4152
4565
  return false;
4153
4566
  }
4154
4567
  if (!isPiUsage(message["usage"])) {
@@ -4158,7 +4571,7 @@ function isPiAssistantMessageEntry(value) {
4158
4571
  }
4159
4572
 
4160
4573
  // ../../packages/parsers/src/pi/bucketize.ts
4161
- function key3(hourUtc, model) {
4574
+ function key4(hourUtc, model) {
4162
4575
  return `${hourUtc.toISOString()}|${model}`;
4163
4576
  }
4164
4577
  function eventTimestampMs(entry) {
@@ -4167,7 +4580,7 @@ function eventTimestampMs(entry) {
4167
4580
  }
4168
4581
  return Date.parse(entry.timestamp);
4169
4582
  }
4170
- function modelId(provider, model) {
4583
+ function modelId2(provider, model) {
4171
4584
  const trimmedProvider = provider.trim();
4172
4585
  const trimmedModel = model.trim();
4173
4586
  if (trimmedProvider.length === 0 || trimmedModel.length === 0) {
@@ -4206,7 +4619,7 @@ async function* bucketizePiStyleFile(path2, harness, logger) {
4206
4619
  if (!Number.isFinite(tsMs)) {
4207
4620
  continue;
4208
4621
  }
4209
- const model = modelId(parsed.message.provider, parsed.message.model);
4622
+ const model = modelId2(parsed.message.provider, parsed.message.model);
4210
4623
  if (model === void 0) {
4211
4624
  continue;
4212
4625
  }
@@ -4226,7 +4639,7 @@ async function* bucketizePiStyleFile(path2, harness, logger) {
4226
4639
  sessionLastSeenAt = eventAt;
4227
4640
  }
4228
4641
  const hourUtc = startOfUtcHour(eventAt);
4229
- const k = key3(hourUtc, model);
4642
+ const k = key4(hourUtc, model);
4230
4643
  const existing = buckets.get(k);
4231
4644
  if (existing === void 0) {
4232
4645
  buckets.set(k, {
@@ -4271,7 +4684,7 @@ async function* bucketizePiStyleFile(path2, harness, logger) {
4271
4684
  // ../../packages/parsers/src/pi/index.ts
4272
4685
  async function isFile3(path2) {
4273
4686
  try {
4274
- return (await stat4(path2)).isFile();
4687
+ return (await stat5(path2)).isFile();
4275
4688
  } catch {
4276
4689
  return false;
4277
4690
  }
@@ -4281,14 +4694,14 @@ function defaultPiRoot() {
4281
4694
  if (sessionDir !== void 0) {
4282
4695
  return sessionDir;
4283
4696
  }
4284
- return join4(readPiAgentDir() ?? join4(homedir3(), ".pi", "agent"), "sessions");
4697
+ return join5(readPiAgentDir() ?? join5(homedir4(), ".pi", "agent"), "sessions");
4285
4698
  }
4286
4699
  function defaultHermesRoot() {
4287
4700
  const sessionDir = readHermesSessionDir();
4288
4701
  if (sessionDir !== void 0) {
4289
4702
  return sessionDir;
4290
4703
  }
4291
- return join4(readHermesAgentDir() ?? join4(homedir3(), ".hermes", "agent"), "sessions");
4704
+ return join5(readHermesAgentDir() ?? join5(homedir4(), ".hermes", "agent"), "sessions");
4292
4705
  }
4293
4706
  async function* parsePiStyle(opts, harness, defaultRoot2) {
4294
4707
  const logger = opts.logger ?? consoleLogger;
@@ -4309,29 +4722,69 @@ async function* parseHermes(opts) {
4309
4722
  }
4310
4723
 
4311
4724
  // src/sync/parser-factory.ts
4725
+ var OPENCODE_DB_FILE_PATTERN2 = /^opencode(?:-[^/]+)?\.db$/u;
4312
4726
  function sessionsRoot(agentDir, sessionDir, fallbackAgentDir) {
4313
- return sessionDir ?? join5(agentDir ?? fallbackAgentDir, "sessions");
4727
+ return sessionDir ?? join6(agentDir ?? fallbackAgentDir, "sessions");
4728
+ }
4729
+ function isFile4(path2) {
4730
+ try {
4731
+ return statSync(path2).isFile();
4732
+ } catch {
4733
+ return false;
4734
+ }
4735
+ }
4736
+ function defaultOpenCodeDataRoot(home) {
4737
+ return join6(home, ".local", "share", "opencode");
4738
+ }
4739
+ function openCodeRoot(home, env) {
4740
+ const dataRoot = env.opencodeDataDir ?? defaultOpenCodeDataRoot(home);
4741
+ const dbPath = env.opencodeDbPath;
4742
+ if (dbPath === void 0 || dbPath === ":memory:") {
4743
+ return dbPath ?? dataRoot;
4744
+ }
4745
+ return isAbsolute2(dbPath) ? dbPath : join6(dataRoot, dbPath);
4746
+ }
4747
+ function hasOpenCodeDatabase(root) {
4748
+ let entries;
4749
+ try {
4750
+ entries = readdirSync(root);
4751
+ } catch {
4752
+ return false;
4753
+ }
4754
+ return entries.some((entry) => OPENCODE_DB_FILE_PATTERN2.test(entry) && isFile4(join6(root, entry)));
4755
+ }
4756
+ function openCodeSourceExists(root) {
4757
+ if (root === ":memory:") {
4758
+ return false;
4759
+ }
4760
+ if (isFile4(root)) {
4761
+ return true;
4762
+ }
4763
+ return hasOpenCodeDatabase(root) || existsSync(join6(root, "storage", "message")) || existsSync(join6(root, "storage", "session", "message")) || existsSync(join6(root, "project"));
4314
4764
  }
4315
4765
  function buildRealParserFactory() {
4316
4766
  return (ctx) => {
4317
- const home = homedir4();
4767
+ const home = homedir5();
4318
4768
  const env = readCliEnv();
4319
- const codexSessionsRoot = join5(home, ".codex", "sessions");
4320
- const codexArchivedRoot = join5(home, ".codex", "archived_sessions");
4769
+ const codexSessionsRoot = join6(home, ".codex", "sessions");
4770
+ const codexArchivedRoot = join6(home, ".codex", "archived_sessions");
4321
4771
  const codexExists = existsSync(codexSessionsRoot) || existsSync(codexArchivedRoot);
4322
4772
  const envDir = env.claudeConfigDir;
4323
- const claudeRoot = envDir == null ? join5(home, ".claude", "projects") : join5(envDir, "projects");
4773
+ const claudeRoot = envDir == null ? join6(home, ".claude", "projects") : join6(envDir, "projects");
4324
4774
  const claudeExists = existsSync(claudeRoot);
4325
- const piRoot = sessionsRoot(env.piAgentDir, env.piSessionDir, join5(home, ".pi", "agent"));
4775
+ const piRoot = sessionsRoot(env.piAgentDir, env.piSessionDir, join6(home, ".pi", "agent"));
4326
4776
  const piExists = existsSync(piRoot);
4327
- const hermesRoot = sessionsRoot(env.hermesAgentDir, env.hermesSessionDir, join5(home, ".hermes", "agent"));
4777
+ const hermesRoot = sessionsRoot(env.hermesAgentDir, env.hermesSessionDir, join6(home, ".hermes", "agent"));
4328
4778
  const hermesExists = existsSync(hermesRoot);
4779
+ const opencodeRoot = openCodeRoot(home, env);
4780
+ const opencodeExists = openCodeSourceExists(opencodeRoot);
4329
4781
  return {
4330
- anySourceExisted: codexExists || claudeExists || piExists || hermesExists,
4782
+ anySourceExisted: codexExists || claudeExists || piExists || hermesExists || opencodeExists,
4331
4783
  codex: parseCodex({ cutoff: ctx.cutoff }),
4332
4784
  claudeCode: parseClaude({ cutoff: ctx.cutoff, root: claudeRoot }),
4333
4785
  pi: parsePi({ cutoff: ctx.cutoff, root: piRoot }),
4334
- hermes: parseHermes({ cutoff: ctx.cutoff, root: hermesRoot })
4786
+ hermes: parseHermes({ cutoff: ctx.cutoff, root: hermesRoot }),
4787
+ opencode: parseOpenCode({ cutoff: ctx.cutoff, root: opencodeRoot })
4335
4788
  };
4336
4789
  };
4337
4790
  }
@@ -4341,11 +4794,11 @@ init_esm_shims();
4341
4794
 
4342
4795
  // src/sync/aggregate.ts
4343
4796
  init_esm_shims();
4344
- import { createHash } from "crypto";
4797
+ import { createHmac } from "crypto";
4345
4798
  function toIso(hourUtc) {
4346
4799
  return hourUtc instanceof Date ? hourUtc.toISOString() : hourUtc;
4347
4800
  }
4348
- function key4(b) {
4801
+ function key5(b) {
4349
4802
  return `${b.harness} ${b.model} ${toIso(b.hourUtc)}`;
4350
4803
  }
4351
4804
  function finalize(acc) {
@@ -4360,8 +4813,12 @@ function finalize(acc) {
4360
4813
  sessionCount: acc.sessionIds.size
4361
4814
  };
4362
4815
  }
4363
- function createSessionKey(harness, localSessionId) {
4364
- return createHash("sha256").update(harness).update("\0").update(localSessionId).digest("hex");
4816
+ var SESSION_KEY_SALT_PATTERN = /^[a-f0-9]{64}$/u;
4817
+ function createSessionKey(harness, localSessionId, sessionKeySalt) {
4818
+ if (!SESSION_KEY_SALT_PATTERN.test(sessionKeySalt)) {
4819
+ throw new Error("invalid session key salt");
4820
+ }
4821
+ return createHmac("sha256", Buffer.from(sessionKeySalt, "hex")).update("oopst/session-key/v1").update("\0").update(harness).update("\0").update(localSessionId).digest("hex");
4365
4822
  }
4366
4823
  function sessionMapKey(harness, sessionKey) {
4367
4824
  return `${harness} ${sessionKey}`;
@@ -4374,8 +4831,8 @@ function finalizeSession(acc) {
4374
4831
  lastSeenAt: acc.lastSeenAt.toISOString()
4375
4832
  };
4376
4833
  }
4377
- function addSessionAccumulator(sessionAccumulators, bucket, localSessionId) {
4378
- const sessionKey = createSessionKey(bucket.harness, localSessionId);
4834
+ function addSessionAccumulator(sessionAccumulators, bucket, localSessionId, sessionKeySalt) {
4835
+ const sessionKey = createSessionKey(bucket.harness, localSessionId, sessionKeySalt);
4379
4836
  const sessionAccumulatorKey = sessionMapKey(bucket.harness, sessionKey);
4380
4837
  const existingSession = sessionAccumulators.get(sessionAccumulatorKey);
4381
4838
  if (existingSession) {
@@ -4398,7 +4855,7 @@ async function* aggregateUsage(source, options) {
4398
4855
  const accumulators = /* @__PURE__ */ new Map();
4399
4856
  const sessionAccumulators = /* @__PURE__ */ new Map();
4400
4857
  for await (const bucket of source) {
4401
- const k = key4(bucket);
4858
+ const k = key5(bucket);
4402
4859
  let acc = accumulators.get(k);
4403
4860
  if (!acc) {
4404
4861
  acc = {
@@ -4419,7 +4876,7 @@ async function* aggregateUsage(source, options) {
4419
4876
  acc.cacheCreateTokens += bucket.cacheCreateTokens;
4420
4877
  for (const id of bucket.sessionIds) {
4421
4878
  acc.sessionIds.add(id);
4422
- addSessionAccumulator(sessionAccumulators, bucket, id);
4879
+ addSessionAccumulator(sessionAccumulators, bucket, id, options.sessionKeySalt);
4423
4880
  }
4424
4881
  }
4425
4882
  const finalized = [];
@@ -4457,11 +4914,11 @@ var __commonJS2 = (cb, mod) => function() {
4457
4914
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4458
4915
  };
4459
4916
  var __copyProps2 = (to, from, except, desc) => {
4460
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames2(from), i = 0, n = keys.length, key5; i < n; i++) {
4461
- key5 = keys[i];
4462
- if (!__hasOwnProp2.call(to, key5) && key5 !== except) __defProp2(to, key5, {
4463
- get: ((k) => from[k]).bind(null, key5),
4464
- enumerable: !(desc = __getOwnPropDesc2(from, key5)) || desc.enumerable
4917
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames2(from), i = 0, n = keys.length, key6; i < n; i++) {
4918
+ key6 = keys[i];
4919
+ if (!__hasOwnProp2.call(to, key6) && key6 !== except) __defProp2(to, key6, {
4920
+ get: ((k) => from[k]).bind(null, key6),
4921
+ enumerable: !(desc = __getOwnPropDesc2(from, key6)) || desc.enumerable
4465
4922
  });
4466
4923
  }
4467
4924
  return to;
@@ -4736,7 +5193,7 @@ init_esm_shims();
4736
5193
 
4737
5194
  // ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@6.0.3/node_modules/@trpc/server/dist/codes-DagpWZLc.mjs
4738
5195
  init_esm_shims();
4739
- function isObject4(value) {
5196
+ function isObject5(value) {
4740
5197
  return !!value && !Array.isArray(value) && typeof value === "object";
4741
5198
  }
4742
5199
  function emptyObject() {
@@ -4783,11 +5240,11 @@ var __commonJS3 = (cb, mod) => function() {
4783
5240
  return mod || (0, cb[__getOwnPropNames3(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4784
5241
  };
4785
5242
  var __copyProps3 = (to, from, except, desc) => {
4786
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames3(from), i = 0, n = keys.length, key5; i < n; i++) {
4787
- key5 = keys[i];
4788
- if (!__hasOwnProp3.call(to, key5) && key5 !== except) __defProp3(to, key5, {
4789
- get: ((k) => from[k]).bind(null, key5),
4790
- enumerable: !(desc = __getOwnPropDesc3(from, key5)) || desc.enumerable
5243
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames3(from), i = 0, n = keys.length, key6; i < n; i++) {
5244
+ key6 = keys[i];
5245
+ if (!__hasOwnProp3.call(to, key6) && key6 !== except) __defProp3(to, key6, {
5246
+ get: ((k) => from[k]).bind(null, key6),
5247
+ enumerable: !(desc = __getOwnPropDesc3(from, key6)) || desc.enumerable
4791
5248
  });
4792
5249
  }
4793
5250
  return to;
@@ -4805,9 +5262,9 @@ function createInnerProxy(callback, path2, memo) {
4805
5262
  var _memo$cacheKey;
4806
5263
  const cacheKey = path2.join(".");
4807
5264
  (_memo$cacheKey = memo[cacheKey]) !== null && _memo$cacheKey !== void 0 || (memo[cacheKey] = new Proxy(noop, {
4808
- get(_obj, key5) {
4809
- if (typeof key5 !== "string" || key5 === "then") return void 0;
4810
- return createInnerProxy(callback, [...path2, key5], memo);
5265
+ get(_obj, key6) {
5266
+ if (typeof key6 !== "string" || key6 === "then") return void 0;
5267
+ return createInnerProxy(callback, [...path2, key6], memo);
4811
5268
  },
4812
5269
  apply(_1, _2, args) {
4813
5270
  const lastOfPath = path2[path2.length - 1];
@@ -4947,8 +5404,8 @@ function transformResult(response, transformer) {
4947
5404
  } catch (_unused) {
4948
5405
  throw new TransformResultError();
4949
5406
  }
4950
- if (!result.ok && (!isObject4(result.error.error) || typeof result.error.error["code"] !== "number")) throw new TransformResultError();
4951
- if (result.ok && !isObject4(result.result)) throw new TransformResultError();
5407
+ if (!result.ok && (!isObject5(result.error.error) || typeof result.error.error["code"] !== "number")) throw new TransformResultError();
5408
+ if (result.ok && !isObject5(result.result)) throw new TransformResultError();
4952
5409
  return result;
4953
5410
  }
4954
5411
  var import_objectSpread22 = __toESM3(require_objectSpread22(), 1);
@@ -4960,11 +5417,11 @@ function isTRPCClientError(cause) {
4960
5417
  return cause instanceof TRPCClientError;
4961
5418
  }
4962
5419
  function isTRPCErrorResponse(obj) {
4963
- return isObject4(obj) && isObject4(obj["error"]) && typeof obj["error"]["code"] === "number" && typeof obj["error"]["message"] === "string";
5420
+ return isObject5(obj) && isObject5(obj["error"]) && typeof obj["error"]["code"] === "number" && typeof obj["error"]["message"] === "string";
4964
5421
  }
4965
5422
  function getMessageFromUnknownError(err, fallback) {
4966
5423
  if (typeof err === "string") return err;
4967
- if (isObject4(err) && typeof err["message"] === "string") return err["message"];
5424
+ if (isObject5(err) && typeof err["message"] === "string") return err["message"];
4968
5425
  return fallback;
4969
5426
  }
4970
5427
  var TRPCClientError = class TRPCClientError2 extends Error {
@@ -5209,11 +5666,11 @@ function dataLoader(batchLoader) {
5209
5666
  });
5210
5667
  }
5211
5668
  }
5212
- function load(key5) {
5669
+ function load(key6) {
5213
5670
  var _dispatchTimer;
5214
5671
  const item = {
5215
5672
  aborted: false,
5216
- key: key5,
5673
+ key: key6,
5217
5674
  batch: null,
5218
5675
  resolve: throwFatalError,
5219
5676
  reject: throwFatalError
@@ -5588,9 +6045,9 @@ function createTRPCClientProxy(client) {
5588
6045
  const fullPath = pathCopy.join(".");
5589
6046
  return client[procedureType](fullPath, ...args);
5590
6047
  });
5591
- return createFlatProxy((key5) => {
5592
- if (key5 === untypedClientSymbol) return client;
5593
- return proxy[key5];
6048
+ return createFlatProxy((key6) => {
6049
+ if (key6 === untypedClientSymbol) return client;
6050
+ return proxy[key6];
5594
6051
  });
5595
6052
  }
5596
6053
  function createTRPCClient(opts) {
@@ -5827,10 +6284,14 @@ async function runSync(options) {
5827
6284
  const client = options.client ?? createClient({ token: options.token, serverUrl: options.serverUrl, fetch: options.fetch });
5828
6285
  const color = options.color === true;
5829
6286
  let watermark = null;
6287
+ let sessionKeySalt;
5830
6288
  try {
5831
6289
  options.logger.info(phase("Checking server sync point...", color));
5832
- const wm = await client.sync.getWatermark.query({ device: options.device });
5833
- watermark = wm.watermarkHourUtc ? new Date(wm.watermarkHourUtc) : null;
6290
+ const { sessionKeySalt: returnedSessionKeySalt, watermarkHourUtc } = await client.sync.getWatermark.mutate({
6291
+ device: options.device
6292
+ });
6293
+ watermark = watermarkHourUtc ? new Date(watermarkHourUtc) : null;
6294
+ sessionKeySalt = returnedSessionKeySalt;
5834
6295
  } catch (err) {
5835
6296
  return failureFromTrpcError(err, options.logger);
5836
6297
  }
@@ -5852,13 +6313,19 @@ async function runSync(options) {
5852
6313
  if (options.harness === void 0 || options.harness === "hermes") {
5853
6314
  sources.push({ label: "Hermes", source: parsers.hermes });
5854
6315
  }
6316
+ if (options.harness === void 0 || options.harness === "opencode") {
6317
+ sources.push({ label: "OpenCode", source: parsers.opencode });
6318
+ }
5855
6319
  let bucketsUploaded = 0;
5856
6320
  let sessionsUploaded = 0;
5857
6321
  let batchesUploaded = 0;
5858
6322
  const batches = [];
5859
6323
  try {
5860
6324
  const progressSources = sources.map((source) => withScanProgress(source, options.logger, color));
5861
- for await (const batch of aggregateUsage(merge(progressSources), { batchSize: SYNC_BATCH_SIZE })) {
6325
+ for await (const batch of aggregateUsage(merge(progressSources), {
6326
+ batchSize: SYNC_BATCH_SIZE,
6327
+ sessionKeySalt
6328
+ })) {
5862
6329
  if (batch.buckets.length === 0 && batch.sessions.length === 0) {
5863
6330
  continue;
5864
6331
  }
@@ -5887,7 +6354,7 @@ async function runSync(options) {
5887
6354
  options.logger.info(
5888
6355
  `${dryRunPrefix(color)} ${usageRecordText(plannedBuckets)} from ${localSessionText(plannedSessions)} would be uploaded in ${uploadRequestText(batches.length)}.`
5889
6356
  );
5890
- options.logger.info(muted("No data was sent.", color));
6357
+ options.logger.info(muted("No usage data was uploaded.", color));
5891
6358
  return {
5892
6359
  exitCode: EXIT.success,
5893
6360
  batchesUploaded: batches.length,
@@ -6023,7 +6490,7 @@ function readPackageVersion() {
6023
6490
  return "0.0.0";
6024
6491
  }
6025
6492
  var VERSION = readPackageVersion();
6026
- var SUPPORTED_HARNESSES = ["codex", "claude_code", "pi", "hermes"];
6493
+ var SUPPORTED_HARNESSES = ["codex", "claude_code", "pi", "hermes", "opencode"];
6027
6494
  function writeStdoutLine(msg) {
6028
6495
  process4.stdout.write(`${msg}
6029
6496
  `);
@@ -6057,7 +6524,7 @@ function buildCli() {
6057
6524
  const env = readCliEnv();
6058
6525
  const program2 = new Command();
6059
6526
  program2.name("oopst").description("OopsT \u2014 track AI coding token usage across users and devices").version(VERSION).exitOverride();
6060
- 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(
6527
+ 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(
6061
6528
  async (raw) => {
6062
6529
  const token = raw.token ?? env.token;
6063
6530
  const device = raw.device ?? env.device;