codetime-cli 0.7.3 → 0.7.4
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/bin/codetime.mjs +52 -18
- package/package.json +1 -1
package/bin/codetime.mjs
CHANGED
|
@@ -208,7 +208,7 @@ var init_fs = __esm({
|
|
|
208
208
|
|
|
209
209
|
// src/cli.ts
|
|
210
210
|
import { spawn } from "node:child_process";
|
|
211
|
-
import { rm as rm2, stat as
|
|
211
|
+
import { rm as rm2, stat as stat7 } from "node:fs/promises";
|
|
212
212
|
import os3 from "node:os";
|
|
213
213
|
import path13 from "node:path";
|
|
214
214
|
import { fileURLToPath } from "node:url";
|
|
@@ -1030,7 +1030,7 @@ import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
|
1030
1030
|
import path2 from "node:path";
|
|
1031
1031
|
|
|
1032
1032
|
// src/lib/constants.ts
|
|
1033
|
-
var PACKAGE_VERSION = true ? "0.7.
|
|
1033
|
+
var PACKAGE_VERSION = true ? "0.7.4" : "0.1.1";
|
|
1034
1034
|
var DEFAULT_API_URL = "https://codetime.dev";
|
|
1035
1035
|
var DEFAULT_BACKFILL_BATCH_SIZE = 50;
|
|
1036
1036
|
var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
|
|
@@ -2334,7 +2334,7 @@ function createClaudeCodeAdapter() {
|
|
|
2334
2334
|
}
|
|
2335
2335
|
|
|
2336
2336
|
// src/adapters/codex.ts
|
|
2337
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
2337
|
+
import { readFile as readFile4, stat as stat4 } from "node:fs/promises";
|
|
2338
2338
|
import path7 from "node:path";
|
|
2339
2339
|
|
|
2340
2340
|
// src/lib/diff.ts
|
|
@@ -2381,6 +2381,7 @@ function fileActivitiesFromPatchChanges(changes, ts, cwd, displayFilePath3) {
|
|
|
2381
2381
|
}
|
|
2382
2382
|
|
|
2383
2383
|
// src/adapters/codex.ts
|
|
2384
|
+
init_fs();
|
|
2384
2385
|
async function parseCodexSessionFile(filePath, options) {
|
|
2385
2386
|
const text = await readFile4(filePath, "utf8");
|
|
2386
2387
|
const lines = text.split("\n").filter(Boolean);
|
|
@@ -3037,15 +3038,39 @@ function createCodexAdapter() {
|
|
|
3037
3038
|
const base = codexHome(home, env);
|
|
3038
3039
|
return [
|
|
3039
3040
|
path7.join(base, "sessions"),
|
|
3041
|
+
// Codex moves old rollouts here as it rotates; include so pre-existing
|
|
3042
|
+
// archived history is discovered, not silently dropped. Actual file
|
|
3043
|
+
// listing dedups active vs archived — see codexBackfillFiles.
|
|
3044
|
+
path7.join(base, "archived_sessions"),
|
|
3040
3045
|
path7.join(base, "history.jsonl")
|
|
3041
3046
|
];
|
|
3042
3047
|
},
|
|
3043
3048
|
parseSessionFile: parseCodexSessionFile
|
|
3044
3049
|
};
|
|
3045
3050
|
}
|
|
3051
|
+
async function codexBackfillFiles(sourceRoot, home, env) {
|
|
3052
|
+
const files = sourceRoot ? await listJsonlFiles(sourceRoot) : await codexDefaultFiles(home, env);
|
|
3053
|
+
return Promise.all(files.map(async (filePath) => {
|
|
3054
|
+
const info = await stat4(filePath);
|
|
3055
|
+
return { path: filePath, modifiedAt: info.mtime.toISOString() };
|
|
3056
|
+
}));
|
|
3057
|
+
}
|
|
3058
|
+
async function codexDefaultFiles(home, env) {
|
|
3059
|
+
const base = codexHome(home, env);
|
|
3060
|
+
const sessionsDir = path7.join(base, "sessions");
|
|
3061
|
+
const archivedDir = path7.join(base, "archived_sessions");
|
|
3062
|
+
const [activeFiles, archivedFiles, historyFiles] = await Promise.all([
|
|
3063
|
+
listJsonlFiles(sessionsDir),
|
|
3064
|
+
listJsonlFiles(archivedDir),
|
|
3065
|
+
listJsonlFiles(path7.join(base, "history.jsonl"))
|
|
3066
|
+
]);
|
|
3067
|
+
const activeRelative = new Set(activeFiles.map((f) => path7.relative(sessionsDir, f)));
|
|
3068
|
+
const dedupedArchived = archivedFiles.filter((f) => !activeRelative.has(path7.relative(archivedDir, f)));
|
|
3069
|
+
return [...activeFiles, ...dedupedArchived, ...historyFiles];
|
|
3070
|
+
}
|
|
3046
3071
|
|
|
3047
3072
|
// src/adapters/gemini.ts
|
|
3048
|
-
import { readFile as readFile5, stat as
|
|
3073
|
+
import { readFile as readFile5, stat as stat5 } from "node:fs/promises";
|
|
3049
3074
|
import path8 from "node:path";
|
|
3050
3075
|
init_fs();
|
|
3051
3076
|
function tokenFromKeys(record, keys) {
|
|
@@ -3221,7 +3246,7 @@ function parseJsonlRecords(lines, fileStem, fallbackTs) {
|
|
|
3221
3246
|
async function parseGeminiSessionFile(filePath, options) {
|
|
3222
3247
|
const text = await readFile5(filePath, "utf8");
|
|
3223
3248
|
const fileStem = path8.basename(filePath).replace(/\.jsonl?$/, "");
|
|
3224
|
-
const info = await
|
|
3249
|
+
const info = await stat5(filePath);
|
|
3225
3250
|
const fallbackTs = info.mtime.toISOString();
|
|
3226
3251
|
let parsed;
|
|
3227
3252
|
if (filePath.endsWith(".jsonl")) {
|
|
@@ -3312,7 +3337,7 @@ async function geminiBackfillFiles(sourceRoot, home, env) {
|
|
|
3312
3337
|
);
|
|
3313
3338
|
const files = fileLists.flat().sort();
|
|
3314
3339
|
return Promise.all(files.map(async (filePath) => {
|
|
3315
|
-
const info = await
|
|
3340
|
+
const info = await stat5(filePath);
|
|
3316
3341
|
return { path: filePath, modifiedAt: info.mtime.toISOString() };
|
|
3317
3342
|
}));
|
|
3318
3343
|
}
|
|
@@ -3731,19 +3756,19 @@ function opencodeDataCandidates(home, env) {
|
|
|
3731
3756
|
return [primary, path9.join(home, ".opencode", "opencode.db")];
|
|
3732
3757
|
}
|
|
3733
3758
|
async function opencodeBackfillFiles(sourceRoot, home = os2.homedir(), env) {
|
|
3734
|
-
const { stat:
|
|
3759
|
+
const { stat: stat8 } = await import("node:fs/promises");
|
|
3735
3760
|
if (sourceRoot) {
|
|
3736
3761
|
if (!sourceRoot.endsWith(".db")) {
|
|
3737
3762
|
return [];
|
|
3738
3763
|
}
|
|
3739
|
-
const info = await
|
|
3764
|
+
const info = await stat8(sourceRoot).catch(() => null);
|
|
3740
3765
|
if (!info) {
|
|
3741
3766
|
return [];
|
|
3742
3767
|
}
|
|
3743
3768
|
return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
|
|
3744
3769
|
}
|
|
3745
3770
|
for (const candidatePath of opencodeDataCandidates(home, env)) {
|
|
3746
|
-
const info = await
|
|
3771
|
+
const info = await stat8(candidatePath).catch(() => null);
|
|
3747
3772
|
if (info) {
|
|
3748
3773
|
return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
|
|
3749
3774
|
}
|
|
@@ -4912,7 +4937,7 @@ function defaultMachineName() {
|
|
|
4912
4937
|
init_fs();
|
|
4913
4938
|
|
|
4914
4939
|
// src/lib/logger.ts
|
|
4915
|
-
import { appendFile, mkdir as mkdir2, rename as rename2, stat as
|
|
4940
|
+
import { appendFile, mkdir as mkdir2, rename as rename2, stat as stat6 } from "node:fs/promises";
|
|
4916
4941
|
import { homedir as homedir2 } from "node:os";
|
|
4917
4942
|
import path12 from "node:path";
|
|
4918
4943
|
var MAX_BYTES = 1 * 1024 * 1024;
|
|
@@ -4930,7 +4955,7 @@ function serializeError(error) {
|
|
|
4930
4955
|
}
|
|
4931
4956
|
async function rotateIfNeeded(file) {
|
|
4932
4957
|
try {
|
|
4933
|
-
const info = await
|
|
4958
|
+
const info = await stat6(file);
|
|
4934
4959
|
if (info.size > MAX_BYTES) {
|
|
4935
4960
|
await rename2(file, `${file}.1`).catch(() => {
|
|
4936
4961
|
});
|
|
@@ -5217,7 +5242,7 @@ async function deleteMachine(remote, id) {
|
|
|
5217
5242
|
}
|
|
5218
5243
|
|
|
5219
5244
|
// src/lib/types.ts
|
|
5220
|
-
var BACKFILL_STATE_SCHEMA_VERSION =
|
|
5245
|
+
var BACKFILL_STATE_SCHEMA_VERSION = 5;
|
|
5221
5246
|
|
|
5222
5247
|
// src/cli.ts
|
|
5223
5248
|
function createRegistry() {
|
|
@@ -5288,8 +5313,8 @@ function createCli(ctx, registry) {
|
|
|
5288
5313
|
cli.command("hook", "Read agent hook JSON from stdin and report a throttled event").option("--agent <name>", "Agent name").option("--project <name>", "Project name").option("--min-interval <seconds>", "Minimum seconds between similar hook reports").action((options) => hookCommand(normalizeOptions(options), ctx));
|
|
5289
5314
|
cli.command("sync-local-trigger", "Trigger one background local sync with throttle and locking").option("--min-interval <seconds>", "Minimum seconds between sync triggers").action((options) => syncLocalTriggerCommand(normalizeOptions(options), ctx, registry));
|
|
5290
5315
|
cli.command("sync-local-runner", "Internal background local sync runner").option("--lock-file <path>", "Lock file for the active sync").option("--state-file <path>", "State file for trigger metadata").action((options) => syncLocalRunnerCommand(normalizeOptions(options), ctx, registry));
|
|
5291
|
-
cli.command("backfill [action]", "Inspect local history import candidates").option("--source <source>", "Backfill source").option("--since <time>", "Only include history after this time").option("--until <time>", "Only include history before this time").option("--project <name>", "Project filter").option("--source-root <path>", "Override source history root").option("--include-source-path", "Include local source paths in output").option("--import-run <id>", "Import run id for verify/resume workflows").option("--limit <count>", "Maximum session files to parse").option("--batch-size <count>", "Max rollups per request (also bounded by --batch-bytes)").option("--batch-bytes <bytes>", "Soft byte cap for the JSON body of a single ingest POST").option("--replace", "Replace conflicting records during import (default)").option("--skip-conflicts", "Skip conflicting records instead of replacing them").option("--force", "
|
|
5292
|
-
cli.command("sync", "Import and upload all local agent history (shorthand for `backfill import --source all`)").option("--source <source>", "Limit to one source (default: all)").option("--since <time>", "Only include history after this time").option("--until <time>", "Only include history before this time").option("--project <name>", "Project filter").option("--batch-size <count>", "Max rollups per request (also bounded by --batch-bytes)").option("--force", "
|
|
5316
|
+
cli.command("backfill [action]", "Inspect local history import candidates").option("--source <source>", "Backfill source").option("--since <time>", "Only include history after this time").option("--until <time>", "Only include history before this time").option("--project <name>", "Project filter").option("--source-root <path>", "Override source history root").option("--include-source-path", "Include local source paths in output").option("--import-run <id>", "Import run id for verify/resume workflows").option("--limit <count>", "Maximum session files to parse").option("--batch-size <count>", "Max rollups per request (also bounded by --batch-bytes)").option("--batch-bytes <bytes>", "Soft byte cap for the JSON body of a single ingest POST").option("--replace", "Replace conflicting records during import (default)").option("--skip-conflicts", "Skip conflicting records instead of replacing them").option("--force", "Full re-import: re-parse every file and overwrite matching rollups (non-destructive \u2014 nothing is deleted)").option("--purge", "Before re-importing, delete THIS machine's existing rollups for the source(s). Destructive: also removes rollups whose local files are gone. Implies --force").action((action, options) => backfillCommand({ ...normalizeOptions(options), action }, ctx, registry));
|
|
5317
|
+
cli.command("sync", "Import and upload all local agent history (shorthand for `backfill import --source all`)").option("--source <source>", "Limit to one source (default: all)").option("--since <time>", "Only include history after this time").option("--until <time>", "Only include history before this time").option("--project <name>", "Project filter").option("--batch-size <count>", "Max rollups per request (also bounded by --batch-bytes)").option("--force", "Full re-import: re-parse every file and overwrite matching rollups (non-destructive \u2014 nothing is deleted)").option("--purge", "Before re-importing, delete THIS machine's existing rollups for the source(s). Destructive: also removes rollups whose local files are gone. Implies --force").option("--dry-run", "Print the planned import without uploading").action((options) => {
|
|
5293
5318
|
const opts = normalizeOptions(options);
|
|
5294
5319
|
return backfillCommand({ ...opts, action: "import", source: stringOption(opts.source) || "all" }, ctx, registry);
|
|
5295
5320
|
});
|
|
@@ -5641,11 +5666,15 @@ async function listBackfillSourceFiles(source, options, ctx) {
|
|
|
5641
5666
|
if (source.id === "gemini") {
|
|
5642
5667
|
return geminiBackfillFiles(stringOption(options["source-root"]), resolveHome(options, ctx), ctx.env);
|
|
5643
5668
|
}
|
|
5669
|
+
if (source.id === "codex") {
|
|
5670
|
+
const files2 = await codexBackfillFiles(stringOption(options["source-root"]), resolveHome(options, ctx), ctx.env);
|
|
5671
|
+
return files2.sort((a, b) => a.path.localeCompare(b.path)).slice(0, numberOption(options.limit) || void 0);
|
|
5672
|
+
}
|
|
5644
5673
|
const roots = stringOption(options["source-root"]) ? [requiredOption(options, "source-root")] : source.paths;
|
|
5645
5674
|
const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
|
|
5646
5675
|
const files = fileLists.flat().sort().slice(0, numberOption(options.limit) || void 0);
|
|
5647
5676
|
return Promise.all(files.map(async (filePath) => {
|
|
5648
|
-
const info = await
|
|
5677
|
+
const info = await stat7(filePath);
|
|
5649
5678
|
return { path: filePath, modifiedAt: info.mtime.toISOString() };
|
|
5650
5679
|
}));
|
|
5651
5680
|
}
|
|
@@ -5729,8 +5758,11 @@ async function importBackfillPlan(plan, options, ctx, registry) {
|
|
|
5729
5758
|
return 1;
|
|
5730
5759
|
}
|
|
5731
5760
|
const sourceDefs = registry.all().filter((a) => supportedSources.has(a.id) && (source === "all" || a.id === source)).map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home, ctx.env) }));
|
|
5732
|
-
if (options.
|
|
5733
|
-
await
|
|
5761
|
+
if (options.purge) {
|
|
5762
|
+
await purgeSourceRollups(sourceDefs, options, ctx);
|
|
5763
|
+
}
|
|
5764
|
+
if (options.force || options.purge) {
|
|
5765
|
+
await clearBackfillWatermark(home, ctx);
|
|
5734
5766
|
}
|
|
5735
5767
|
const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home, ctx) : void 0;
|
|
5736
5768
|
if (!options.json) {
|
|
@@ -5764,13 +5796,15 @@ async function importBackfillPlan(plan, options, ctx, registry) {
|
|
|
5764
5796
|
}
|
|
5765
5797
|
return counts.failed > 0 || counts.conflicts > 0 && !options["skip-conflicts"] ? 1 : 0;
|
|
5766
5798
|
}
|
|
5767
|
-
async function
|
|
5799
|
+
async function clearBackfillWatermark(home, ctx) {
|
|
5768
5800
|
try {
|
|
5769
5801
|
await rm2(backfillIncrementalStatePath(home), { force: true });
|
|
5770
5802
|
} catch (error) {
|
|
5771
5803
|
debug(ctx, `Failed to clear backfill watermark: ${error.message}
|
|
5772
5804
|
`);
|
|
5773
5805
|
}
|
|
5806
|
+
}
|
|
5807
|
+
async function purgeSourceRollups(sourceDefs, options, ctx) {
|
|
5774
5808
|
for (const item of sourceDefs) {
|
|
5775
5809
|
try {
|
|
5776
5810
|
const deleted = await deleteSessionRollupsBySourceAPI(item.id, options, ctx);
|
package/package.json
CHANGED