@yhong91/vibetime 0.1.49 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/vibetime.mjs +336 -79
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -2047,7 +2047,7 @@ function claudeStyleFileMetrics(tool, input) {
2047
2047
  }
2048
2048
 
2049
2049
  // src/lib/constants.ts
2050
- var PACKAGE_VERSION = true ? "0.1.49" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.51" : "0.1.1";
2051
2051
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
2052
2052
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
2053
2053
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -8094,27 +8094,98 @@ import path17 from "node:path";
8094
8094
  import { access } from "node:fs/promises";
8095
8095
  import os7 from "node:os";
8096
8096
  import path16 from "node:path";
8097
+ function takeQoderDbModelCall(calls, requestId, blockStart) {
8098
+ if (requestId) {
8099
+ const call = calls.byRequestId.get(requestId)?.shift();
8100
+ if (call) {
8101
+ const index = calls.ordered.indexOf(call);
8102
+ if (index !== -1) {
8103
+ calls.ordered.splice(index, 1);
8104
+ }
8105
+ }
8106
+ return call;
8107
+ }
8108
+ if (!blockStart) {
8109
+ return void 0;
8110
+ }
8111
+ return calls.ordered.shift();
8112
+ }
8113
+ function appDataRoot(appDirName, home = os7.homedir()) {
8114
+ if (process.platform === "darwin") {
8115
+ return path16.join(home, "Library", "Application Support", appDirName);
8116
+ }
8117
+ if (process.platform === "win32") {
8118
+ return path16.join(process.env.APPDATA || path16.join(home, "AppData", "Roaming"), appDirName);
8119
+ }
8120
+ return path16.join(home, ".config", appDirName);
8121
+ }
8097
8122
  function qoderLocalDbCandidates(appDirName) {
8098
8123
  const candidates = [];
8099
8124
  const envPath = process.env.QODER_LOCAL_DB_PATH;
8100
8125
  if (envPath) {
8101
8126
  candidates.push(envPath);
8102
8127
  }
8103
- const home = os7.homedir();
8104
- let configRoot;
8105
- if (process.platform === "darwin") {
8106
- configRoot = path16.join(home, "Library", "Application Support", appDirName);
8107
- } else if (process.platform === "win32") {
8108
- configRoot = path16.join(process.env.APPDATA || path16.join(home, "AppData", "Roaming"), appDirName);
8109
- } else {
8110
- configRoot = path16.join(home, ".config", appDirName);
8111
- }
8128
+ const configRoot = appDataRoot(appDirName);
8112
8129
  candidates.push(
8113
8130
  path16.join(configRoot, "SharedClientCache", "cache", "db", "local.db"),
8114
8131
  path16.join(configRoot, "SharedClientCache", "db", "local.db")
8115
8132
  );
8116
8133
  return candidates;
8117
8134
  }
8135
+ async function loadQoderIdeModelCatalog(appDirName, home) {
8136
+ const map = {};
8137
+ try {
8138
+ const { DatabaseSync } = await import("node:sqlite");
8139
+ const db = new DatabaseSync(
8140
+ path16.join(appDataRoot(appDirName, home), "User", "globalStorage", "state.vscdb"),
8141
+ { readOnly: true }
8142
+ );
8143
+ try {
8144
+ const rows = db.prepare(
8145
+ `select key, value from ItemTable where key like 'aicoding.modelConfigs.cache.%' or key = 'aicoding.customModels'`
8146
+ ).all();
8147
+ const customIds = [];
8148
+ for (const row of rows) {
8149
+ const key = stringField(row, "key");
8150
+ const value = stringField(row, "value");
8151
+ if (!key || !value) {
8152
+ continue;
8153
+ }
8154
+ let entries;
8155
+ try {
8156
+ entries = JSON.parse(value);
8157
+ } catch {
8158
+ continue;
8159
+ }
8160
+ for (const entry of entries) {
8161
+ const displayName = stringField(entry, "displayName");
8162
+ if (key === "aicoding.customModels") {
8163
+ const id = stringField(entry, "id");
8164
+ if (id && displayName) {
8165
+ map[`custom:${id}`] = displayName;
8166
+ customIds.push(id);
8167
+ }
8168
+ } else {
8169
+ const name = stringField(entry, "name");
8170
+ if (name && displayName) {
8171
+ map[name] = displayName;
8172
+ }
8173
+ }
8174
+ }
8175
+ }
8176
+ if (customIds.length === 1) {
8177
+ const displayName = map[`custom:${customIds[0]}`];
8178
+ if (displayName) {
8179
+ map.custom_model = displayName;
8180
+ }
8181
+ }
8182
+ } finally {
8183
+ db.close();
8184
+ }
8185
+ } catch {
8186
+ }
8187
+ return map;
8188
+ }
8118
8189
  async function loadQoderDbModelCalls(appDirName, sessionId, modelMap) {
8119
8190
  const calls = { byRequestId: /* @__PURE__ */ new Map(), ordered: [] };
8120
8191
  if (!sessionId) {
@@ -8266,7 +8337,7 @@ async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
8266
8337
  return {};
8267
8338
  }
8268
8339
  }
8269
- async function loadQoderCnModelNames(configDir2) {
8340
+ async function loadQoderCnModelNames(configDir2, home) {
8270
8341
  const map = await parseModelNamesFromDynamicTexts(path17.join(configDir2, ".auth", "dynamic-texts.json"));
8271
8342
  const siblingConfigDir = configDir2.replace(/\.qoder-cn$/, ".qoder");
8272
8343
  if (siblingConfigDir !== configDir2) {
@@ -8277,6 +8348,14 @@ async function loadQoderCnModelNames(configDir2) {
8277
8348
  }
8278
8349
  }
8279
8350
  }
8351
+ for (const appDirName of ["QoderCN", "Qoder"]) {
8352
+ const ideMap = await loadQoderIdeModelCatalog(appDirName, home);
8353
+ for (const [key, val] of Object.entries(ideMap)) {
8354
+ if (!(key in map)) {
8355
+ map[key] = val;
8356
+ }
8357
+ }
8358
+ }
8280
8359
  return map;
8281
8360
  }
8282
8361
  async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap) {
@@ -8310,7 +8389,8 @@ async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMa
8310
8389
  inputTokens: numberField(data, "input_tokens") || 0,
8311
8390
  outputTokens: numberField(data, "output_tokens") || 0,
8312
8391
  cacheCreationInputTokens: numberField(data, "cache_creation_input_tokens") || 0,
8313
- cacheReadInputTokens: numberField(data, "cache_read_input_tokens") || 0
8392
+ cacheReadInputTokens: numberField(data, "cache_read_input_tokens") || 0,
8393
+ stopReason: stringField(data, "stop_reason") || void 0
8314
8394
  });
8315
8395
  }
8316
8396
  }
@@ -8333,20 +8413,15 @@ async function parseQoderCnSessionFile(filePath, options) {
8333
8413
  let cwd;
8334
8414
  let project = projectContext.project;
8335
8415
  let model;
8336
- const modelMap = await loadQoderCnModelNames(configDir2);
8416
+ const home = path17.resolve(stringOption(options.home) || os8.homedir());
8417
+ const modelMap = await loadQoderCnModelNames(configDir2, home);
8337
8418
  const isSubagentSession = filePath.includes("subagents");
8338
8419
  const segmentModelCalls = await loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap);
8339
8420
  let modelCallIndex = 0;
8340
8421
  let dbModelCalls;
8341
8422
  const nextDbModelCall = async (requestId, blockStart) => {
8342
8423
  dbModelCalls ??= await loadQoderDbModelCalls("QoderCN", sessionId, modelMap);
8343
- if (requestId) {
8344
- return dbModelCalls.byRequestId.get(requestId)?.shift();
8345
- }
8346
- if (!blockStart) {
8347
- return void 0;
8348
- }
8349
- return dbModelCalls.ordered.shift();
8424
+ return takeQoderDbModelCall(dbModelCalls, requestId, blockStart);
8350
8425
  };
8351
8426
  const state = new SessionParserState(filePath, options, (event) => baseQoderCnEvent({ ...event, cwd, project, model }));
8352
8427
  state.sessionId = sessionId;
@@ -8458,6 +8533,41 @@ async function parseQoderCnSessionFile(filePath, options) {
8458
8533
  }
8459
8534
  pendingTools.delete(pending.id);
8460
8535
  }
8536
+ const attachedPrompt = extractQoderAttachedPrompt(qoderCnToolResultItems(message)[0]?.content);
8537
+ if (attachedPrompt) {
8538
+ const prompt = qoderCnTextStats(attachedPrompt);
8539
+ state.closeTurn(ts, lineNumber, topType);
8540
+ state.currentTurnId = stringField(raw, "uuid") || stringField(raw, "promptId") || `turn_${createStableHash([sessionId, lineNumber]).slice(0, 24)}`;
8541
+ state.currentTurnStartedAt = ts;
8542
+ state.currentTurnLastEventAt = ts;
8543
+ push(baseQoderCnEvent({
8544
+ ts,
8545
+ type: "turn.started",
8546
+ sessionId,
8547
+ turnId: state.currentTurnId,
8548
+ cwd,
8549
+ project,
8550
+ model,
8551
+ confidence: "derived"
8552
+ }), lineNumber, topType, "attached_prompt");
8553
+ push(baseQoderCnEvent({
8554
+ ts,
8555
+ type: "prompt.submitted",
8556
+ sessionId,
8557
+ turnId: state.currentTurnId,
8558
+ cwd,
8559
+ project,
8560
+ model,
8561
+ confidence: "exact",
8562
+ metrics: {
8563
+ prompts: 1,
8564
+ promptChars: prompt.chars
8565
+ },
8566
+ refs: stringRefs({
8567
+ promptHash: prompt.hash
8568
+ })
8569
+ }), lineNumber, topType, "attached_prompt");
8570
+ }
8461
8571
  continue;
8462
8572
  }
8463
8573
  if (topType === "user") {
@@ -8532,6 +8642,20 @@ async function parseQoderCnSessionFile(filePath, options) {
8532
8642
  modelCalls: 1
8533
8643
  };
8534
8644
  model = call.model || model;
8645
+ if (!usage.tokensTotal && !usage.tokensCachedInput) {
8646
+ const dbCall = await nextDbModelCall(requestId, isBlockStart);
8647
+ if (dbCall && (dbCall.inputTokens || dbCall.outputTokens || dbCall.cachedTokens)) {
8648
+ usage = {
8649
+ tokensInput: dbCall.inputTokens || void 0,
8650
+ tokensCachedInput: dbCall.cachedTokens || void 0,
8651
+ tokensCacheReadInput: dbCall.cachedTokens || void 0,
8652
+ tokensOutput: dbCall.outputTokens || void 0,
8653
+ tokensTotal: dbCall.inputTokens + dbCall.outputTokens || void 0,
8654
+ modelCalls: 1
8655
+ };
8656
+ model = dbCall.model || model;
8657
+ }
8658
+ }
8535
8659
  } else if (shouldEmitUsage) {
8536
8660
  const dbCall = await nextDbModelCall(requestId, isBlockStart);
8537
8661
  if (dbCall) {
@@ -8785,6 +8909,30 @@ function isNoisePrompt(text) {
8785
8909
  }
8786
8910
  return false;
8787
8911
  }
8912
+ function extractQoderAttachedPrompt(content) {
8913
+ let text;
8914
+ if (typeof content === "string") {
8915
+ text = content;
8916
+ } else if (Array.isArray(content)) {
8917
+ text = content.filter((it) => isPlainObject(it) && it.type === "text").map((it) => stringField(it, "text")).join("\n");
8918
+ } else {
8919
+ return void 0;
8920
+ }
8921
+ if (!text.includes("aicoding-text-field") || !text.includes("text-field-content")) {
8922
+ return void 0;
8923
+ }
8924
+ const fenceStart = text.indexOf("```");
8925
+ if (fenceStart === -1) {
8926
+ return void 0;
8927
+ }
8928
+ const afterFence = text.slice(fenceStart + 3);
8929
+ const fenceEnd = afterFence.indexOf("```");
8930
+ if (fenceEnd === -1) {
8931
+ return void 0;
8932
+ }
8933
+ const inner = afterFence.slice(0, fenceEnd);
8934
+ return inner.split("\n").map((line) => line.replace(/^\s*\d+->/, "")).join("\n").trim() || void 0;
8935
+ }
8788
8936
  async function qoderCnProjectContextFromLines(filePath, lines, options, configDir2) {
8789
8937
  const { projectName: projectDir, sessionId } = parseQoderCnPaths(filePath);
8790
8938
  const isSubagent = filePath.includes(`${path17.sep}subagents${path17.sep}`);
@@ -8982,6 +9130,7 @@ function createQoderCnAdapter() {
8982
9130
  }
8983
9131
 
8984
9132
  // src/adapters/qoder.ts
9133
+ import { existsSync } from "node:fs";
8985
9134
  import { readdir as readdir8, readFile as readFile11, stat as stat9 } from "node:fs/promises";
8986
9135
  import os9 from "node:os";
8987
9136
  import path18 from "node:path";
@@ -9044,7 +9193,7 @@ async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
9044
9193
  return {};
9045
9194
  }
9046
9195
  }
9047
- async function loadQoderModelNames(configDir2) {
9196
+ async function loadQoderModelNames(configDir2, home) {
9048
9197
  const map = await parseModelNamesFromDynamicTexts2(path18.join(configDir2, ".auth", "dynamic-texts.json"));
9049
9198
  const siblingConfigDir = configDir2.replace(/\.qoder$/, ".qoder-cn");
9050
9199
  if (siblingConfigDir !== configDir2) {
@@ -9055,8 +9204,38 @@ async function loadQoderModelNames(configDir2) {
9055
9204
  }
9056
9205
  }
9057
9206
  }
9207
+ if (isQwenworkConfigRoot(configDir2)) {
9208
+ for (const dir of [path18.join(home, ".qoder"), path18.join(home, ".qoder-cn")]) {
9209
+ if (path18.resolve(dir) === path18.resolve(configDir2)) {
9210
+ continue;
9211
+ }
9212
+ const fallbackMap = await parseModelNamesFromDynamicTexts2(path18.join(dir, ".auth", "dynamic-texts.json"));
9213
+ for (const [key, val] of Object.entries(fallbackMap)) {
9214
+ if (!(key in map)) {
9215
+ map[key] = val;
9216
+ }
9217
+ }
9218
+ }
9219
+ }
9220
+ for (const appDirName of ["Qoder", "QoderCN"]) {
9221
+ const ideMap = await loadQoderIdeModelCatalog(appDirName, home);
9222
+ for (const [key, val] of Object.entries(ideMap)) {
9223
+ if (!(key in map)) {
9224
+ map[key] = val;
9225
+ }
9226
+ }
9227
+ }
9058
9228
  return map;
9059
9229
  }
9230
+ function isQwenworkConfigRoot(configDir2) {
9231
+ const name = path18.basename(path18.resolve(configDir2));
9232
+ if (name === ".qwenworkcn" || name === ".qwenwork") {
9233
+ return true;
9234
+ }
9235
+ const override = process.env.QWENWORK_CONFIG_DIR;
9236
+ return Boolean(override && override.trim() && path18.basename(path18.resolve(override)) === name);
9237
+ }
9238
+ var FAILED_SEGMENT_STOP_REASONS = /* @__PURE__ */ new Set(["cancelled", "canceled", "error", "failed", "refusal"]);
9060
9239
  async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap) {
9061
9240
  const { configDir: configDir2, projectName, sessionId } = parseQoderPaths(filePath);
9062
9241
  const segmentsPath = path18.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
@@ -9088,7 +9267,8 @@ async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap)
9088
9267
  inputTokens: numberField(data, "input_tokens") || 0,
9089
9268
  outputTokens: numberField(data, "output_tokens") || 0,
9090
9269
  cacheCreationInputTokens: numberField(data, "cache_creation_input_tokens") || 0,
9091
- cacheReadInputTokens: numberField(data, "cache_read_input_tokens") || 0
9270
+ cacheReadInputTokens: numberField(data, "cache_read_input_tokens") || 0,
9271
+ stopReason: stringField(data, "stop_reason") || void 0
9092
9272
  });
9093
9273
  }
9094
9274
  }
@@ -9111,20 +9291,16 @@ async function parseQoderSessionFile(filePath, options) {
9111
9291
  let cwd;
9112
9292
  let project = projectContext.project;
9113
9293
  let model;
9114
- const modelMap = await loadQoderModelNames(configDir2);
9294
+ const home = path18.resolve(stringOption(options.home) || os9.homedir());
9295
+ const modelMap = await loadQoderModelNames(configDir2, home);
9296
+ const qwenworkRoot = isQwenworkConfigRoot(configDir2);
9115
9297
  const isSubagentSession = filePath.includes("subagents");
9116
9298
  const segmentModelCalls = await loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap);
9117
9299
  let modelCallIndex = 0;
9118
9300
  let dbModelCalls;
9119
9301
  const nextDbModelCall = async (requestId, blockStart) => {
9120
9302
  dbModelCalls ??= await loadQoderDbModelCalls("Qoder", sessionId, modelMap);
9121
- if (requestId) {
9122
- return dbModelCalls.byRequestId.get(requestId)?.shift();
9123
- }
9124
- if (!blockStart) {
9125
- return void 0;
9126
- }
9127
- return dbModelCalls.ordered.shift();
9303
+ return takeQoderDbModelCall(dbModelCalls, requestId, blockStart);
9128
9304
  };
9129
9305
  const state = new SessionParserState(filePath, options, (event) => baseQoderEvent({ ...event, cwd, project, model }));
9130
9306
  state.sessionId = sessionId;
@@ -9236,6 +9412,41 @@ async function parseQoderSessionFile(filePath, options) {
9236
9412
  }
9237
9413
  pendingTools.delete(pending.id);
9238
9414
  }
9415
+ const attachedPrompt = extractQoderAttachedPrompt(qoderToolResultItems(message)[0]?.content);
9416
+ if (attachedPrompt) {
9417
+ const prompt = qoderTextStats(attachedPrompt);
9418
+ state.closeTurn(ts, lineNumber, topType);
9419
+ state.currentTurnId = stringField(raw, "uuid") || stringField(raw, "promptId") || `turn_${createStableHash([sessionId, lineNumber]).slice(0, 24)}`;
9420
+ state.currentTurnStartedAt = ts;
9421
+ state.currentTurnLastEventAt = ts;
9422
+ push(baseQoderEvent({
9423
+ ts,
9424
+ type: "turn.started",
9425
+ sessionId,
9426
+ turnId: state.currentTurnId,
9427
+ cwd,
9428
+ project,
9429
+ model,
9430
+ confidence: "derived"
9431
+ }), lineNumber, topType, "attached_prompt");
9432
+ push(baseQoderEvent({
9433
+ ts,
9434
+ type: "prompt.submitted",
9435
+ sessionId,
9436
+ turnId: state.currentTurnId,
9437
+ cwd,
9438
+ project,
9439
+ model,
9440
+ confidence: "exact",
9441
+ metrics: {
9442
+ prompts: 1,
9443
+ promptChars: prompt.chars
9444
+ },
9445
+ refs: stringRefs({
9446
+ promptHash: prompt.hash
9447
+ })
9448
+ }), lineNumber, topType, "attached_prompt");
9449
+ }
9239
9450
  continue;
9240
9451
  }
9241
9452
  if (topType === "user") {
@@ -9292,39 +9503,28 @@ async function parseQoderSessionFile(filePath, options) {
9292
9503
  seenUsageKeys.add(usageKey);
9293
9504
  }
9294
9505
  let usage;
9506
+ let keepZeroTokenUsage = false;
9295
9507
  if (shouldEmitUsage && modelCallIndex < segmentModelCalls.length) {
9296
9508
  const call = segmentModelCalls[modelCallIndex++];
9297
- const inputTokens = call.inputTokens;
9298
- const cacheCreationInputTokens = call.cacheCreationInputTokens;
9299
- const cacheReadInputTokens = call.cacheReadInputTokens;
9300
- const outputTokens = call.outputTokens;
9301
- const cachedInputTokens = cacheCreationInputTokens + cacheReadInputTokens;
9302
- const totalInputTokens = inputTokens;
9303
- usage = {
9304
- tokensInput: totalInputTokens || void 0,
9305
- tokensCachedInput: cachedInputTokens || void 0,
9306
- tokensCacheCreationInput: cacheCreationInputTokens || void 0,
9307
- tokensCacheReadInput: cacheReadInputTokens || void 0,
9308
- tokensOutput: outputTokens || void 0,
9309
- tokensTotal: totalInputTokens + outputTokens || void 0,
9310
- modelCalls: 1
9311
- };
9509
+ usage = qoderSegmentUsage(call);
9312
9510
  model = call.model || model;
9511
+ if (!usage.tokensTotal && !usage.tokensCachedInput) {
9512
+ const dbCall = await nextDbModelCall(requestId, isBlockStart);
9513
+ if (dbCall && (dbCall.inputTokens || dbCall.outputTokens || dbCall.cachedTokens)) {
9514
+ usage = qoderDbUsage(dbCall);
9515
+ model = dbCall.model || model;
9516
+ } else if (qwenworkRoot && (!call.stopReason || !FAILED_SEGMENT_STOP_REASONS.has(call.stopReason))) {
9517
+ keepZeroTokenUsage = true;
9518
+ }
9519
+ }
9313
9520
  } else if (shouldEmitUsage) {
9314
9521
  const dbCall = await nextDbModelCall(requestId, isBlockStart);
9315
9522
  if (dbCall) {
9316
- usage = {
9317
- tokensInput: dbCall.inputTokens || void 0,
9318
- tokensCachedInput: dbCall.cachedTokens || void 0,
9319
- tokensCacheReadInput: dbCall.cachedTokens || void 0,
9320
- tokensOutput: dbCall.outputTokens || void 0,
9321
- tokensTotal: dbCall.inputTokens + dbCall.outputTokens || void 0,
9322
- modelCalls: 1
9323
- };
9523
+ usage = qoderDbUsage(dbCall);
9324
9524
  model = dbCall.model || model;
9325
9525
  }
9326
9526
  }
9327
- if (usage && (usage.tokensTotal || usage.tokensCachedInput)) {
9527
+ if (usage && (usage.tokensTotal || usage.tokensCachedInput || keepZeroTokenUsage)) {
9328
9528
  const speed = stringField(objectField(message, "usage"), "speed");
9329
9529
  const usageModel = speed === "fast" && model ? `${model}-fast` : model;
9330
9530
  push(baseQoderEvent({
@@ -9451,6 +9651,28 @@ async function parseQoderSessionFile(filePath, options) {
9451
9651
  }
9452
9652
  return validEvents;
9453
9653
  }
9654
+ function qoderSegmentUsage(call) {
9655
+ const cachedInputTokens = call.cacheCreationInputTokens + call.cacheReadInputTokens;
9656
+ return {
9657
+ tokensInput: call.inputTokens || void 0,
9658
+ tokensCachedInput: cachedInputTokens || void 0,
9659
+ tokensCacheCreationInput: call.cacheCreationInputTokens || void 0,
9660
+ tokensCacheReadInput: call.cacheReadInputTokens || void 0,
9661
+ tokensOutput: call.outputTokens || void 0,
9662
+ tokensTotal: call.inputTokens + call.outputTokens || void 0,
9663
+ modelCalls: 1
9664
+ };
9665
+ }
9666
+ function qoderDbUsage(dbCall) {
9667
+ return {
9668
+ tokensInput: dbCall.inputTokens || void 0,
9669
+ tokensCachedInput: dbCall.cachedTokens || void 0,
9670
+ tokensCacheReadInput: dbCall.cachedTokens || void 0,
9671
+ tokensOutput: dbCall.outputTokens || void 0,
9672
+ tokensTotal: dbCall.inputTokens + dbCall.outputTokens || void 0,
9673
+ modelCalls: 1
9674
+ };
9675
+ }
9454
9676
  function baseQoderEvent(event) {
9455
9677
  return {
9456
9678
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
@@ -9534,12 +9756,20 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
9534
9756
  const isSubagent = filePath.includes(`${path18.sep}subagents${path18.sep}`);
9535
9757
  const inherited = isSubagent ? await readPersistedSessionContextFromOptions(options, sessionId) : void 0;
9536
9758
  let cwds = [];
9759
+ const workspaceDirs = [];
9537
9760
  for (const line of lines) {
9538
9761
  const raw = parseJsonLine(line);
9539
9762
  const cwd = raw ? stringField(raw, "cwd") : void 0;
9540
9763
  if (cwd && path18.isAbsolute(cwd)) {
9541
9764
  cwds.push(cwd);
9542
9765
  }
9766
+ if (raw && stringField(raw, "type") === "workspace-directories") {
9767
+ for (const dir of arrayField5(raw, "directories")) {
9768
+ if (typeof dir === "string" && path18.isAbsolute(dir)) {
9769
+ workspaceDirs.push(dir);
9770
+ }
9771
+ }
9772
+ }
9543
9773
  }
9544
9774
  if (isSubagent) {
9545
9775
  if (inherited?.cwd && path18.isAbsolute(inherited.cwd)) {
@@ -9555,6 +9785,13 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
9555
9785
  if (cwd && path18.isAbsolute(cwd)) {
9556
9786
  parentCwds.push(cwd);
9557
9787
  }
9788
+ if (workspaceDirs.length === 0 && raw && stringField(raw, "type") === "workspace-directories") {
9789
+ for (const dir of arrayField5(raw, "directories")) {
9790
+ if (typeof dir === "string" && path18.isAbsolute(dir)) {
9791
+ workspaceDirs.push(dir);
9792
+ }
9793
+ }
9794
+ }
9558
9795
  }
9559
9796
  if (parentCwds.length > 0) {
9560
9797
  cwds = parentCwds;
@@ -9563,6 +9800,12 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
9563
9800
  }
9564
9801
  }
9565
9802
  }
9803
+ if (cwds.length > 0 && cwds.every((cwd) => pathInsideDir(cwd, configDir2))) {
9804
+ const external = workspaceDirs.filter((dir) => !pathInsideDir(dir, configDir2));
9805
+ if (external.length > 0) {
9806
+ cwds = external;
9807
+ }
9808
+ }
9566
9809
  const root = await gitRootFromCwds3(cwds) || qoderProjectRootFromCwds(projectDir, cwds);
9567
9810
  const project = inherited?.project || (cwds.length > 0 ? path18.basename(cwds[0]) : root ? path18.basename(root) : await qoderProjectFromFilePath(filePath, options));
9568
9811
  return {
@@ -9570,6 +9813,11 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
9570
9813
  workspaceId: createWorkspaceId({ projectName: project, repoRoot: root })
9571
9814
  };
9572
9815
  }
9816
+ function pathInsideDir(candidate, dir) {
9817
+ const resolvedDir = path18.resolve(dir);
9818
+ const resolved = path18.resolve(candidate);
9819
+ return resolved === resolvedDir || resolved.startsWith(`${resolvedDir}${path18.sep}`);
9820
+ }
9573
9821
  async function gitRootFromCwds3(cwds) {
9574
9822
  const seen = /* @__PURE__ */ new Set();
9575
9823
  for (const cwd of cwds) {
@@ -9594,7 +9842,7 @@ function qoderProjectRootFromCwds(projectDir, cwds) {
9594
9842
  for (const cwd of cwds) {
9595
9843
  let current = path18.resolve(cwd);
9596
9844
  while (true) {
9597
- if (encodeQoderProjectPath(current) === projectDir) {
9845
+ if (qoderEncodedVariants(current).includes(projectDir)) {
9598
9846
  return current;
9599
9847
  }
9600
9848
  const parent = path18.dirname(current);
@@ -9606,21 +9854,23 @@ function qoderProjectRootFromCwds(projectDir, cwds) {
9606
9854
  }
9607
9855
  return void 0;
9608
9856
  }
9609
- function encodeQoderProjectPath(value) {
9610
- return path18.resolve(value).split(path18.sep).join("-").replaceAll("_", "-");
9611
- }
9612
9857
  function rawQoderProjectPath(value) {
9613
9858
  return path18.resolve(value).split(path18.sep).join("-");
9614
9859
  }
9615
9860
  function qoderEncodedVariants(value) {
9616
9861
  const raw = rawQoderProjectPath(value);
9617
- const normalized = raw.replaceAll("_", "-");
9618
- return raw === normalized ? [raw] : [raw, normalized];
9862
+ const variants = /* @__PURE__ */ new Set([
9863
+ raw,
9864
+ raw.replaceAll("_", "-"),
9865
+ raw.replaceAll(".", "-"),
9866
+ raw.replaceAll("_", "-").replaceAll(".", "-")
9867
+ ]);
9868
+ return [...variants];
9619
9869
  }
9620
9870
  function qoderEncodedProjectSuffix(projectDir, home) {
9621
9871
  for (const prefix of qoderEncodedVariants(home).map((value) => `${value}-`)) {
9622
9872
  if (projectDir.startsWith(prefix)) {
9623
- return projectDir.slice(prefix.length) || void 0;
9873
+ return projectDir.slice(prefix.length).replace(/^-+/, "") || void 0;
9624
9874
  }
9625
9875
  }
9626
9876
  return void 0;
@@ -9705,6 +9955,16 @@ function qoderConfigDir(home, env) {
9705
9955
  }
9706
9956
  return path18.join(home, ".qoder");
9707
9957
  }
9958
+ function qwenworkConfigDir(home, env) {
9959
+ const override = env?.QWENWORK_CONFIG_DIR;
9960
+ if (override && override.trim()) {
9961
+ return path18.resolve(override);
9962
+ }
9963
+ return path18.join(home, ".qwenworkcn");
9964
+ }
9965
+ function qoderConfigDirs(home, env) {
9966
+ return [...new Set([qoderConfigDir(home, env), qwenworkConfigDir(home, env)].map((dir) => path18.resolve(dir)))];
9967
+ }
9708
9968
  function createQoderAdapter() {
9709
9969
  return {
9710
9970
  id: "qoder",
@@ -9724,19 +9984,22 @@ function createQoderAdapter() {
9724
9984
  );
9725
9985
  },
9726
9986
  installEntries(home, env) {
9727
- return [{
9987
+ const [primary, ...variants] = qoderConfigDirs(home, env);
9988
+ const targets = [primary, ...variants.filter((dir) => existsSync(dir))];
9989
+ return targets.map((base) => ({
9728
9990
  kind: "hooks-json",
9729
- path: path18.join(qoderConfigDir(home, env), "settings.json"),
9991
+ path: path18.join(base, "settings.json"),
9730
9992
  content: hookConfig7()
9731
- }];
9993
+ }));
9732
9994
  },
9733
9995
  sourcePaths(home, env) {
9996
+ const paths = qoderConfigDirs(home, env).map((base2) => path18.join(base2, "projects"));
9734
9997
  const base = qoderConfigDir(home, env);
9735
- return [
9736
- path18.join(base, "projects"),
9998
+ paths.push(
9737
9999
  path18.join(base, ".qoder.json"),
9738
10000
  path18.join(home, ".qoder.json")
9739
- ];
10001
+ );
10002
+ return paths;
9740
10003
  },
9741
10004
  parseSessionFile: parseQoderSessionFile
9742
10005
  };
@@ -11171,13 +11434,7 @@ function createZedAdapter() {
11171
11434
  }
11172
11435
 
11173
11436
  // src/lib/pricing.ts
11174
- function estimateEventCostUsd(event) {
11175
- if (event.type !== "model.usage") {
11176
- return 0;
11177
- }
11178
- if (typeof event.metrics?.costUsd === "number" && event.metrics.costUsd > 0) {
11179
- return event.metrics.costUsd;
11180
- }
11437
+ function estimateEventCostUsd(_event) {
11181
11438
  return 0;
11182
11439
  }
11183
11440
 
@@ -11640,7 +11897,7 @@ function hookCommandFromGroup(group) {
11640
11897
 
11641
11898
  // src/lib/config.ts
11642
11899
  import { randomUUID } from "node:crypto";
11643
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11900
+ import { existsSync as existsSync2, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11644
11901
  import { homedir, hostname } from "node:os";
11645
11902
  import path22 from "node:path";
11646
11903
  function configDir(home = homedir()) {
@@ -11654,7 +11911,7 @@ function machineIdPath(home = homedir()) {
11654
11911
  }
11655
11912
  function readConfig(home = homedir()) {
11656
11913
  const file = configPath(home);
11657
- if (!existsSync(file)) {
11914
+ if (!existsSync2(file)) {
11658
11915
  return {};
11659
11916
  }
11660
11917
  try {
@@ -11666,7 +11923,7 @@ function readConfig(home = homedir()) {
11666
11923
  }
11667
11924
  function writeConfig(config, home = homedir()) {
11668
11925
  const dir = configDir(home);
11669
- if (!existsSync(dir)) {
11926
+ if (!existsSync2(dir)) {
11670
11927
  mkdirSync(dir, { recursive: true });
11671
11928
  }
11672
11929
  writeFileSync(configPath(home), `${JSON.stringify(config, null, 2)}
@@ -11674,7 +11931,7 @@ function writeConfig(config, home = homedir()) {
11674
11931
  }
11675
11932
  function ensureLocalMachineId(home = homedir()) {
11676
11933
  const file = machineIdPath(home);
11677
- if (existsSync(file)) {
11934
+ if (existsSync2(file)) {
11678
11935
  const value = readFileSync(file, "utf8").trim();
11679
11936
  if (value.length > 0) {
11680
11937
  return value;
@@ -11682,7 +11939,7 @@ function ensureLocalMachineId(home = homedir()) {
11682
11939
  }
11683
11940
  const id = randomUUID();
11684
11941
  const dir = configDir(home);
11685
- if (!existsSync(dir)) {
11942
+ if (!existsSync2(dir)) {
11686
11943
  mkdirSync(dir, { recursive: true });
11687
11944
  }
11688
11945
  writeFileSync(file, `${id}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.49",
4
+ "version": "0.1.51",
5
5
  "description": "vibetime CLI — install AI-agent hooks (Claude Code, Codex, OpenCode, Pi) and report activity to vibetime.",
6
6
  "license": "MIT",
7
7
  "publishConfig": {