codetime-cli 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/bin/codetime.mjs +260 -173
- package/package.json +5 -5
package/bin/codetime.mjs
CHANGED
|
@@ -143,6 +143,9 @@ var init_fs = __esm({
|
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
+
// src/lib/types.ts
|
|
147
|
+
var BACKFILL_STATE_SCHEMA_VERSION = 2;
|
|
148
|
+
|
|
146
149
|
// src/cli.ts
|
|
147
150
|
import { spawn } from "node:child_process";
|
|
148
151
|
import { mkdir as mkdir2, rm, stat as stat4, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -1329,9 +1332,10 @@ function msToIso(ms) {
|
|
|
1329
1332
|
}
|
|
1330
1333
|
|
|
1331
1334
|
// src/lib/constants.ts
|
|
1332
|
-
var PACKAGE_VERSION = true ? "0.
|
|
1335
|
+
var PACKAGE_VERSION = true ? "0.3.0" : "0.1.1";
|
|
1333
1336
|
var DEFAULT_API_URL = "https://codetime.dev";
|
|
1334
|
-
var DEFAULT_BACKFILL_BATCH_SIZE =
|
|
1337
|
+
var DEFAULT_BACKFILL_BATCH_SIZE = 50;
|
|
1338
|
+
var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
|
|
1335
1339
|
var ROLLUP_BUCKET_MS = 15 * 60 * 1e3;
|
|
1336
1340
|
var DEFAULT_HOOK_SYNC_MIN_INTERVAL_SECONDS = 60;
|
|
1337
1341
|
|
|
@@ -1471,6 +1475,7 @@ async function parseClaudeCodeSessionFile(filePath, options) {
|
|
|
1471
1475
|
const lines = text.split("\n").filter(Boolean);
|
|
1472
1476
|
const projectContext = await claudeProjectContextFromLines(filePath, lines, options);
|
|
1473
1477
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
1478
|
+
const seenUsageKeys = /* @__PURE__ */ new Set();
|
|
1474
1479
|
let sessionId = sessionIdFromFilePath(filePath, "claude");
|
|
1475
1480
|
let cwd;
|
|
1476
1481
|
let project = projectContext.project;
|
|
@@ -1622,8 +1627,19 @@ async function parseClaudeCodeSessionFile(filePath, options) {
|
|
|
1622
1627
|
continue;
|
|
1623
1628
|
}
|
|
1624
1629
|
model = stringField(message, "model") || model;
|
|
1630
|
+
const messageId = stringField(message, "id");
|
|
1631
|
+
const requestId = stringField(raw, "requestId");
|
|
1632
|
+
const usageKey = messageId ? `${messageId}:${requestId}` : null;
|
|
1633
|
+
if (usageKey != null && seenUsageKeys.has(usageKey)) {
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
if (usageKey != null) {
|
|
1637
|
+
seenUsageKeys.add(usageKey);
|
|
1638
|
+
}
|
|
1625
1639
|
const usage = claudeUsageFromMessage(message);
|
|
1626
1640
|
if (usage) {
|
|
1641
|
+
const speed = stringField(objectField(message, "usage"), "speed");
|
|
1642
|
+
const usageModel = speed === "fast" && model ? `${model}-fast` : model;
|
|
1627
1643
|
push(baseClaudeEvent({
|
|
1628
1644
|
ts,
|
|
1629
1645
|
type: "model.usage",
|
|
@@ -1631,7 +1647,7 @@ async function parseClaudeCodeSessionFile(filePath, options) {
|
|
|
1631
1647
|
turnId: state.currentTurnId,
|
|
1632
1648
|
cwd,
|
|
1633
1649
|
project,
|
|
1634
|
-
model,
|
|
1650
|
+
model: usageModel,
|
|
1635
1651
|
confidence: "partial",
|
|
1636
1652
|
metrics: usage
|
|
1637
1653
|
}), lineNumber, topType, "usage");
|
|
@@ -1939,35 +1955,43 @@ function hookConfig() {
|
|
|
1939
1955
|
}
|
|
1940
1956
|
};
|
|
1941
1957
|
}
|
|
1958
|
+
function claudeConfigDir(home, env) {
|
|
1959
|
+
const override = env?.CLAUDE_CONFIG_DIR;
|
|
1960
|
+
if (override && override.trim()) {
|
|
1961
|
+
return path5.resolve(override);
|
|
1962
|
+
}
|
|
1963
|
+
return path5.join(home, ".claude");
|
|
1964
|
+
}
|
|
1942
1965
|
function createClaudeCodeAdapter() {
|
|
1943
|
-
const CLAUDE_PATH = ".claude";
|
|
1944
1966
|
return {
|
|
1945
1967
|
id: "claude-code",
|
|
1946
1968
|
label: "Claude Code",
|
|
1947
1969
|
agentName: "claude",
|
|
1948
1970
|
kind: "agent",
|
|
1949
|
-
detectPath(home) {
|
|
1950
|
-
return
|
|
1971
|
+
detectPath(home, env) {
|
|
1972
|
+
return claudeConfigDir(home, env);
|
|
1951
1973
|
},
|
|
1952
|
-
installedPath(home) {
|
|
1953
|
-
return path5.join(home,
|
|
1974
|
+
installedPath(home, env) {
|
|
1975
|
+
return path5.join(claudeConfigDir(home, env), "settings.json");
|
|
1954
1976
|
},
|
|
1955
|
-
async isInstalled(home) {
|
|
1977
|
+
async isInstalled(home, env) {
|
|
1956
1978
|
return isHooksJsonInstalled(
|
|
1957
|
-
path5.join(home,
|
|
1979
|
+
path5.join(claudeConfigDir(home, env), "settings.json"),
|
|
1958
1980
|
"codetime hook --agent claude"
|
|
1959
1981
|
);
|
|
1960
1982
|
},
|
|
1961
|
-
installEntries(home) {
|
|
1983
|
+
installEntries(home, env) {
|
|
1962
1984
|
return [{
|
|
1963
1985
|
kind: "hooks-json",
|
|
1964
|
-
path: path5.join(home,
|
|
1986
|
+
path: path5.join(claudeConfigDir(home, env), "settings.json"),
|
|
1965
1987
|
content: hookConfig()
|
|
1966
1988
|
}];
|
|
1967
1989
|
},
|
|
1968
|
-
sourcePaths(home) {
|
|
1990
|
+
sourcePaths(home, env) {
|
|
1991
|
+
const base = claudeConfigDir(home, env);
|
|
1969
1992
|
return [
|
|
1970
|
-
path5.join(
|
|
1993
|
+
path5.join(base, "projects"),
|
|
1994
|
+
path5.join(base, ".claude.json"),
|
|
1971
1995
|
path5.join(home, ".claude.json")
|
|
1972
1996
|
];
|
|
1973
1997
|
},
|
|
@@ -2472,36 +2496,43 @@ function hookConfig2() {
|
|
|
2472
2496
|
}
|
|
2473
2497
|
};
|
|
2474
2498
|
}
|
|
2499
|
+
function codexHome(home, env) {
|
|
2500
|
+
const override = env?.CODEX_HOME;
|
|
2501
|
+
if (override && override.trim()) {
|
|
2502
|
+
return path6.resolve(override);
|
|
2503
|
+
}
|
|
2504
|
+
return path6.join(home, ".codex");
|
|
2505
|
+
}
|
|
2475
2506
|
function createCodexAdapter() {
|
|
2476
|
-
const CODE_PATH = ".codex";
|
|
2477
2507
|
return {
|
|
2478
2508
|
id: "codex",
|
|
2479
2509
|
label: "Codex",
|
|
2480
2510
|
agentName: "codex",
|
|
2481
2511
|
kind: "agent",
|
|
2482
|
-
detectPath(home) {
|
|
2483
|
-
return
|
|
2512
|
+
detectPath(home, env) {
|
|
2513
|
+
return codexHome(home, env);
|
|
2484
2514
|
},
|
|
2485
|
-
installedPath(home) {
|
|
2486
|
-
return path6.join(home,
|
|
2515
|
+
installedPath(home, env) {
|
|
2516
|
+
return path6.join(codexHome(home, env), "hooks.json");
|
|
2487
2517
|
},
|
|
2488
|
-
async isInstalled(home) {
|
|
2518
|
+
async isInstalled(home, env) {
|
|
2489
2519
|
return isHooksJsonInstalled(
|
|
2490
|
-
path6.join(home,
|
|
2520
|
+
path6.join(codexHome(home, env), "hooks.json"),
|
|
2491
2521
|
"codetime hook --agent codex"
|
|
2492
2522
|
);
|
|
2493
2523
|
},
|
|
2494
|
-
installEntries(home) {
|
|
2524
|
+
installEntries(home, env) {
|
|
2495
2525
|
return [{
|
|
2496
2526
|
kind: "hooks-json",
|
|
2497
|
-
path: path6.join(home,
|
|
2527
|
+
path: path6.join(codexHome(home, env), "hooks.json"),
|
|
2498
2528
|
content: hookConfig2()
|
|
2499
2529
|
}];
|
|
2500
2530
|
},
|
|
2501
|
-
sourcePaths(home) {
|
|
2531
|
+
sourcePaths(home, env) {
|
|
2532
|
+
const base = codexHome(home, env);
|
|
2502
2533
|
return [
|
|
2503
|
-
path6.join(
|
|
2504
|
-
path6.join(
|
|
2534
|
+
path6.join(base, "sessions"),
|
|
2535
|
+
path6.join(base, "history.jsonl")
|
|
2505
2536
|
];
|
|
2506
2537
|
},
|
|
2507
2538
|
parseSessionFile: parseCodexSessionFile
|
|
@@ -2854,7 +2885,23 @@ function opencodeUsageFromInfo(info) {
|
|
|
2854
2885
|
tokensTotal: total
|
|
2855
2886
|
};
|
|
2856
2887
|
}
|
|
2857
|
-
|
|
2888
|
+
function opencodeConfigDir(home, env) {
|
|
2889
|
+
const override = env?.OPENCODE_CONFIG_DIR;
|
|
2890
|
+
if (override && override.trim()) {
|
|
2891
|
+
return path7.resolve(override);
|
|
2892
|
+
}
|
|
2893
|
+
const xdgConfig = env?.XDG_CONFIG_HOME;
|
|
2894
|
+
if (xdgConfig && xdgConfig.trim()) {
|
|
2895
|
+
return path7.join(path7.resolve(xdgConfig), "opencode");
|
|
2896
|
+
}
|
|
2897
|
+
return path7.join(home, ".config", "opencode");
|
|
2898
|
+
}
|
|
2899
|
+
function opencodeDataCandidates(home, env) {
|
|
2900
|
+
const xdgData = env?.XDG_DATA_HOME;
|
|
2901
|
+
const primary = xdgData && xdgData.trim() ? path7.join(path7.resolve(xdgData), "opencode", "opencode.db") : path7.join(home, ".local", "share", "opencode", "opencode.db");
|
|
2902
|
+
return [primary, path7.join(home, ".opencode", "opencode.db")];
|
|
2903
|
+
}
|
|
2904
|
+
async function opencodeBackfillFiles(sourceRoot, home = os2.homedir(), env) {
|
|
2858
2905
|
const { stat: stat5 } = await import("node:fs/promises");
|
|
2859
2906
|
if (sourceRoot) {
|
|
2860
2907
|
if (!sourceRoot.endsWith(".db")) {
|
|
@@ -2866,11 +2913,7 @@ async function opencodeBackfillFiles(sourceRoot) {
|
|
|
2866
2913
|
}
|
|
2867
2914
|
return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
|
|
2868
2915
|
}
|
|
2869
|
-
const
|
|
2870
|
-
path7.join(os2.homedir(), ".local", "share", "opencode", "opencode.db"),
|
|
2871
|
-
path7.join(os2.homedir(), ".opencode", "opencode.db")
|
|
2872
|
-
];
|
|
2873
|
-
for (const candidatePath of candidates) {
|
|
2916
|
+
for (const candidatePath of opencodeDataCandidates(home, env)) {
|
|
2874
2917
|
const info = await stat5(candidatePath).catch(() => null);
|
|
2875
2918
|
if (info) {
|
|
2876
2919
|
return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
|
|
@@ -2924,39 +2967,35 @@ export const AgentTime = async ({ $, directory }) => {
|
|
|
2924
2967
|
`;
|
|
2925
2968
|
}
|
|
2926
2969
|
function createOpenCodeAdapter() {
|
|
2927
|
-
const OPENCODE_CONFIG = ".config/opencode";
|
|
2928
2970
|
const PLUGIN_PATH = "plugins/codetime.mjs";
|
|
2929
2971
|
return {
|
|
2930
2972
|
id: "opencode",
|
|
2931
2973
|
label: "OpenCode",
|
|
2932
2974
|
agentName: "opencode",
|
|
2933
2975
|
kind: "agent",
|
|
2934
|
-
detectPath(home) {
|
|
2935
|
-
return
|
|
2976
|
+
detectPath(home, env) {
|
|
2977
|
+
return opencodeConfigDir(home, env);
|
|
2936
2978
|
},
|
|
2937
|
-
installedPath(home) {
|
|
2938
|
-
return path7.join(home,
|
|
2979
|
+
installedPath(home, env) {
|
|
2980
|
+
return path7.join(opencodeConfigDir(home, env), PLUGIN_PATH);
|
|
2939
2981
|
},
|
|
2940
|
-
async isInstalled(home) {
|
|
2982
|
+
async isInstalled(home, env) {
|
|
2941
2983
|
try {
|
|
2942
2984
|
const { pathExists: pathExists2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
2943
|
-
return await pathExists2(path7.join(home,
|
|
2985
|
+
return await pathExists2(path7.join(opencodeConfigDir(home, env), PLUGIN_PATH)) || await pathExists2(path7.join(".opencode", PLUGIN_PATH));
|
|
2944
2986
|
} catch {
|
|
2945
2987
|
return false;
|
|
2946
2988
|
}
|
|
2947
2989
|
},
|
|
2948
|
-
installEntries(home) {
|
|
2990
|
+
installEntries(home, env) {
|
|
2949
2991
|
return [{
|
|
2950
2992
|
kind: "file",
|
|
2951
|
-
path: path7.join(home,
|
|
2993
|
+
path: path7.join(opencodeConfigDir(home, env), PLUGIN_PATH),
|
|
2952
2994
|
content: opencodePluginContent()
|
|
2953
2995
|
}];
|
|
2954
2996
|
},
|
|
2955
|
-
sourcePaths(home) {
|
|
2956
|
-
return
|
|
2957
|
-
path7.join(home, ".local", "share", "opencode", "opencode.db"),
|
|
2958
|
-
path7.join(home, ".opencode", "opencode.db")
|
|
2959
|
-
];
|
|
2997
|
+
sourcePaths(home, env) {
|
|
2998
|
+
return opencodeDataCandidates(home, env);
|
|
2960
2999
|
},
|
|
2961
3000
|
parseSessionFile: parseOpenCodeSessionFile
|
|
2962
3001
|
};
|
|
@@ -3390,36 +3429,49 @@ export default function (pi: ExtensionAPI) {
|
|
|
3390
3429
|
}
|
|
3391
3430
|
`;
|
|
3392
3431
|
}
|
|
3432
|
+
function piAgentDir(home, env) {
|
|
3433
|
+
const override = env?.PI_CODING_AGENT_DIR;
|
|
3434
|
+
if (override && override.trim()) {
|
|
3435
|
+
return path8.resolve(override);
|
|
3436
|
+
}
|
|
3437
|
+
return path8.join(home, ".pi", "agent");
|
|
3438
|
+
}
|
|
3439
|
+
function piSessionDir(home, env) {
|
|
3440
|
+
const override = env?.PI_CODING_AGENT_SESSION_DIR;
|
|
3441
|
+
if (override && override.trim()) {
|
|
3442
|
+
return path8.resolve(override);
|
|
3443
|
+
}
|
|
3444
|
+
return path8.join(piAgentDir(home, env), "sessions");
|
|
3445
|
+
}
|
|
3393
3446
|
function createPiAdapter() {
|
|
3394
|
-
const PI_EXTENSIONS_DIR = ".pi/agent/extensions";
|
|
3395
3447
|
return {
|
|
3396
3448
|
id: "pi",
|
|
3397
3449
|
label: "Pi",
|
|
3398
3450
|
agentName: "pi",
|
|
3399
3451
|
kind: "agent",
|
|
3400
|
-
detectPath(home) {
|
|
3401
|
-
return
|
|
3452
|
+
detectPath(home, env) {
|
|
3453
|
+
return piAgentDir(home, env);
|
|
3402
3454
|
},
|
|
3403
|
-
installedPath(home) {
|
|
3404
|
-
return path8.join(home,
|
|
3455
|
+
installedPath(home, env) {
|
|
3456
|
+
return path8.join(piAgentDir(home, env), "extensions", "codetime.ts");
|
|
3405
3457
|
},
|
|
3406
|
-
async isInstalled(home) {
|
|
3458
|
+
async isInstalled(home, env) {
|
|
3407
3459
|
try {
|
|
3408
3460
|
const { pathExists: pathExists2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
3409
|
-
return await pathExists2(path8.join(home,
|
|
3461
|
+
return await pathExists2(path8.join(piAgentDir(home, env), "extensions", "codetime.ts"));
|
|
3410
3462
|
} catch {
|
|
3411
3463
|
return false;
|
|
3412
3464
|
}
|
|
3413
3465
|
},
|
|
3414
|
-
installEntries(home) {
|
|
3466
|
+
installEntries(home, env) {
|
|
3415
3467
|
return [{
|
|
3416
3468
|
kind: "file",
|
|
3417
|
-
path: path8.join(home,
|
|
3469
|
+
path: path8.join(piAgentDir(home, env), "extensions", "codetime.ts"),
|
|
3418
3470
|
content: piExtensionContent()
|
|
3419
3471
|
}];
|
|
3420
3472
|
},
|
|
3421
|
-
sourcePaths(home) {
|
|
3422
|
-
return [
|
|
3473
|
+
sourcePaths(home, env) {
|
|
3474
|
+
return [piSessionDir(home, env)];
|
|
3423
3475
|
},
|
|
3424
3476
|
parseSessionFile: parsePiSessionFile
|
|
3425
3477
|
};
|
|
@@ -4538,7 +4590,7 @@ function createCli(ctx, registry) {
|
|
|
4538
4590
|
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));
|
|
4539
4591
|
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));
|
|
4540
4592
|
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));
|
|
4541
|
-
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>", "
|
|
4593
|
+
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", "Force full re-import: clear watermark and re-process all files").action((action, options) => backfillCommand({ ...normalizeOptions(options), action }, ctx, registry));
|
|
4542
4594
|
cli.command("token [action] [value]", "Set, show, or clear the persisted API token").option("--remote <url>", "Override API base URL when setting a token").action((action, value, options) => tokenCommand(action, value, normalizeOptions(options), ctx));
|
|
4543
4595
|
cli.command("machine [action]", "List or rename machines (requires login)").option("--name <name>", "New display name (used by `machine rename`)").option("--id <id>", "Machine id (defaults to current machine)").action((action, options) => machineCommand(action, normalizeOptions(options), ctx));
|
|
4544
4596
|
return cli;
|
|
@@ -4556,6 +4608,7 @@ function normalizeOptions(options) {
|
|
|
4556
4608
|
sourceRoot: "source-root",
|
|
4557
4609
|
importRun: "import-run",
|
|
4558
4610
|
batchSize: "batch-size",
|
|
4611
|
+
batchBytes: "batch-bytes",
|
|
4559
4612
|
skipConflicts: "skip-conflicts"
|
|
4560
4613
|
};
|
|
4561
4614
|
for (const [camel, dashed] of Object.entries(aliases)) {
|
|
@@ -4567,18 +4620,19 @@ function normalizeOptions(options) {
|
|
|
4567
4620
|
}
|
|
4568
4621
|
async function detectCommand(options, ctx, registry) {
|
|
4569
4622
|
const home = resolveHome(options, ctx);
|
|
4623
|
+
const env = ctx.env;
|
|
4570
4624
|
const adapters = registry.all();
|
|
4571
4625
|
const targets = await Promise.all(adapters.map(async (adapter) => {
|
|
4572
|
-
const detected = await pathExists(adapter.detectPath(home));
|
|
4573
|
-
const installed = await adapter.isInstalled(home);
|
|
4626
|
+
const detected = await pathExists(adapter.detectPath(home, env));
|
|
4627
|
+
const installed = await adapter.isInstalled(home, env);
|
|
4574
4628
|
return {
|
|
4575
4629
|
id: adapter.id,
|
|
4576
4630
|
label: adapter.label,
|
|
4577
4631
|
kind: adapter.kind,
|
|
4578
4632
|
detected,
|
|
4579
4633
|
installed,
|
|
4580
|
-
detectPath: adapter.detectPath(home),
|
|
4581
|
-
installedPath: adapter.installedPath(home)
|
|
4634
|
+
detectPath: adapter.detectPath(home, env),
|
|
4635
|
+
installedPath: adapter.installedPath(home, env)
|
|
4582
4636
|
};
|
|
4583
4637
|
}));
|
|
4584
4638
|
if (options.json) {
|
|
@@ -4595,6 +4649,7 @@ async function detectCommand(options, ctx, registry) {
|
|
|
4595
4649
|
}
|
|
4596
4650
|
async function installCommand(options, ctx, registry) {
|
|
4597
4651
|
const home = resolveHome(options, ctx);
|
|
4652
|
+
const env = ctx.env;
|
|
4598
4653
|
const dryRun = Boolean(options["dry-run"]);
|
|
4599
4654
|
const force = Boolean(options.force);
|
|
4600
4655
|
const allAdapters = registry.all();
|
|
@@ -4605,7 +4660,7 @@ async function installCommand(options, ctx, registry) {
|
|
|
4605
4660
|
}
|
|
4606
4661
|
const detected = [];
|
|
4607
4662
|
for (const adapter of allAdapters) {
|
|
4608
|
-
if (await pathExists(adapter.detectPath(home))) {
|
|
4663
|
+
if (await pathExists(adapter.detectPath(home, env))) {
|
|
4609
4664
|
detected.push(adapter.id);
|
|
4610
4665
|
}
|
|
4611
4666
|
}
|
|
@@ -4615,7 +4670,7 @@ async function installCommand(options, ctx, registry) {
|
|
|
4615
4670
|
return 1;
|
|
4616
4671
|
}
|
|
4617
4672
|
for (const adapter of allAdapters.filter((a) => selectedIds.includes(a.id))) {
|
|
4618
|
-
for (const entry of adapter.installEntries(home)) {
|
|
4673
|
+
for (const entry of adapter.installEntries(home, env)) {
|
|
4619
4674
|
await installEntry(entry, {
|
|
4620
4675
|
dryRun,
|
|
4621
4676
|
force,
|
|
@@ -4744,13 +4799,14 @@ async function backfillCommand(options, ctx, registry) {
|
|
|
4744
4799
|
}
|
|
4745
4800
|
async function createBackfillPlanFromOptions(options, ctx, action, registry) {
|
|
4746
4801
|
const home = resolveHome(options, ctx);
|
|
4802
|
+
const env = ctx.env;
|
|
4747
4803
|
const source = normalizeBackfillSource(stringOption(options.source) || "all");
|
|
4748
|
-
const sourceDefs = source === "all" ? registry.all().map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home) })) : (() => {
|
|
4804
|
+
const sourceDefs = source === "all" ? registry.all().map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home, env) })) : (() => {
|
|
4749
4805
|
const adapter = registry.get(source);
|
|
4750
4806
|
if (!adapter) {
|
|
4751
4807
|
return [];
|
|
4752
4808
|
}
|
|
4753
|
-
return [{ id: adapter.id, label: adapter.label, paths: adapter.sourcePaths(home) }];
|
|
4809
|
+
return [{ id: adapter.id, label: adapter.label, paths: adapter.sourcePaths(home, env) }];
|
|
4754
4810
|
})();
|
|
4755
4811
|
if (sourceDefs.length === 0) {
|
|
4756
4812
|
throw new Error(`Unknown backfill source: ${source}`);
|
|
@@ -4759,7 +4815,7 @@ async function createBackfillPlanFromOptions(options, ctx, action, registry) {
|
|
|
4759
4815
|
const candidates = candidateList.flat();
|
|
4760
4816
|
let events = [];
|
|
4761
4817
|
if (action !== "discover") {
|
|
4762
|
-
const eventList = await createBackfillEventsFromDefs(sourceDefs, options, registry);
|
|
4818
|
+
const eventList = await createBackfillEventsFromDefs(sourceDefs, options, registry, ctx);
|
|
4763
4819
|
events = eventList.flat();
|
|
4764
4820
|
}
|
|
4765
4821
|
const plannedEvents = events.map((event) => ({
|
|
@@ -4809,14 +4865,14 @@ async function createBackfillPlanFromOptions(options, ctx, action, registry) {
|
|
|
4809
4865
|
privacy: "metadata only; prompt text, command text, source code, and diffs are not imported"
|
|
4810
4866
|
};
|
|
4811
4867
|
}
|
|
4812
|
-
async function createBackfillEventsFromDefs(sourceDefs, options, registry, overrideFiles) {
|
|
4868
|
+
async function createBackfillEventsFromDefs(sourceDefs, options, registry, ctx, overrideFiles) {
|
|
4813
4869
|
const events = [];
|
|
4814
4870
|
for (const item of sourceDefs) {
|
|
4815
4871
|
const parser = registry.getParser(item.id);
|
|
4816
4872
|
if (!parser) {
|
|
4817
4873
|
continue;
|
|
4818
4874
|
}
|
|
4819
|
-
const sourceFiles = await listBackfillSourceFiles(item, options);
|
|
4875
|
+
const sourceFiles = await listBackfillSourceFiles(item, options, ctx);
|
|
4820
4876
|
const files = overrideFiles ?? sourceFiles.map((f) => f.path);
|
|
4821
4877
|
for (const filePath of files) {
|
|
4822
4878
|
const parsed = await parser(filePath, options);
|
|
@@ -4844,9 +4900,9 @@ async function createBackfillCandidates(source, options) {
|
|
|
4844
4900
|
};
|
|
4845
4901
|
}));
|
|
4846
4902
|
}
|
|
4847
|
-
async function listBackfillSourceFiles(source, options) {
|
|
4903
|
+
async function listBackfillSourceFiles(source, options, ctx) {
|
|
4848
4904
|
if (source.id === "opencode") {
|
|
4849
|
-
return opencodeBackfillFiles(stringOption(options["source-root"]));
|
|
4905
|
+
return opencodeBackfillFiles(stringOption(options["source-root"]), resolveHome(options, ctx), ctx.env);
|
|
4850
4906
|
}
|
|
4851
4907
|
const roots = stringOption(options["source-root"]) ? [requiredOption(options, "source-root")] : source.paths;
|
|
4852
4908
|
const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
|
|
@@ -4934,117 +4990,122 @@ async function importBackfillPlan(plan, options, ctx, registry) {
|
|
|
4934
4990
|
write(ctx.stderr, "Only Codex, Claude Code, OpenCode, and Pi backfill import are implemented.\n");
|
|
4935
4991
|
return 1;
|
|
4936
4992
|
}
|
|
4937
|
-
const
|
|
4938
|
-
const sourceDefs = allAdapters.filter((a) => supportedSources.has(a.id) && (source === "all" || a.id === source)).map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home) }));
|
|
4993
|
+
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) }));
|
|
4939
4994
|
if (options.force) {
|
|
4940
|
-
|
|
4941
|
-
await rm(backfillIncrementalStatePath(home), { force: true });
|
|
4942
|
-
} catch {
|
|
4943
|
-
}
|
|
4944
|
-
for (const item of sourceDefs) {
|
|
4945
|
-
try {
|
|
4946
|
-
const deleted = await deleteSessionRollupsBySourceAPI(item.id, options, ctx);
|
|
4947
|
-
if (!options.json) {
|
|
4948
|
-
write(ctx.stdout, `purged ${item.id}: ${deleted} old rollups
|
|
4949
|
-
`);
|
|
4950
|
-
}
|
|
4951
|
-
} catch (error) {
|
|
4952
|
-
debug(ctx, `Failed to purge ${item.id} rollups: ${error.message}
|
|
4953
|
-
`);
|
|
4954
|
-
}
|
|
4955
|
-
}
|
|
4995
|
+
await purgeForcedSources(sourceDefs, home, options, ctx);
|
|
4956
4996
|
}
|
|
4957
|
-
const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home) : void 0;
|
|
4958
|
-
const selectedFilesBySource = /* @__PURE__ */ new Map();
|
|
4959
|
-
const canonicalEvents = [];
|
|
4997
|
+
const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home, ctx) : void 0;
|
|
4960
4998
|
if (!options.json) {
|
|
4961
4999
|
write(ctx.stdout, `importRun ${plan.importRun.importRunId}
|
|
4962
5000
|
`);
|
|
4963
5001
|
write(ctx.stdout, `sources ${sourceDefs.map((s) => s.id).join(", ") || "none"}
|
|
5002
|
+
`);
|
|
5003
|
+
}
|
|
5004
|
+
const { canonicalEvents, selectedFilesBySource } = await collectCanonicalEvents(
|
|
5005
|
+
sourceDefs,
|
|
5006
|
+
registry,
|
|
5007
|
+
incrementalState,
|
|
5008
|
+
options,
|
|
5009
|
+
ctx
|
|
5010
|
+
);
|
|
5011
|
+
const rollups = buildSessionRollups(canonicalEvents);
|
|
5012
|
+
const counts = await uploadSessionRollups(rollups, canonicalEvents.length, options, ctx);
|
|
5013
|
+
const result = {
|
|
5014
|
+
importRunId: plan.importRun.importRunId,
|
|
5015
|
+
source,
|
|
5016
|
+
planned: rollups.length,
|
|
5017
|
+
sourceEvents: canonicalEvents.length,
|
|
5018
|
+
...counts
|
|
5019
|
+
};
|
|
5020
|
+
if (options.json) {
|
|
5021
|
+
write(ctx.stdout, `${JSON.stringify(result, null, 2)}
|
|
5022
|
+
`);
|
|
5023
|
+
}
|
|
5024
|
+
if (counts.failed === 0 && counts.conflicts === 0 && incrementalState) {
|
|
5025
|
+
await updateBackfillIncrementalState(home, incrementalState, selectedFilesBySource);
|
|
5026
|
+
}
|
|
5027
|
+
return counts.failed > 0 || counts.conflicts > 0 && !options["skip-conflicts"] ? 1 : 0;
|
|
5028
|
+
}
|
|
5029
|
+
async function purgeForcedSources(sourceDefs, home, options, ctx) {
|
|
5030
|
+
try {
|
|
5031
|
+
await rm(backfillIncrementalStatePath(home), { force: true });
|
|
5032
|
+
} catch (error) {
|
|
5033
|
+
debug(ctx, `Failed to clear backfill watermark: ${error.message}
|
|
4964
5034
|
`);
|
|
4965
5035
|
}
|
|
5036
|
+
for (const item of sourceDefs) {
|
|
5037
|
+
try {
|
|
5038
|
+
const deleted = await deleteSessionRollupsBySourceAPI(item.id, options, ctx);
|
|
5039
|
+
if (!options.json) {
|
|
5040
|
+
write(ctx.stdout, `purged ${item.id}: ${deleted} old rollups
|
|
5041
|
+
`);
|
|
5042
|
+
}
|
|
5043
|
+
} catch (error) {
|
|
5044
|
+
debug(ctx, `Failed to purge ${item.id} rollups: ${error.message}
|
|
5045
|
+
`);
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
async function collectCanonicalEvents(sourceDefs, registry, incrementalState, options, ctx) {
|
|
5050
|
+
const selectedFilesBySource = /* @__PURE__ */ new Map();
|
|
5051
|
+
const canonicalEvents = [];
|
|
4966
5052
|
for (const item of sourceDefs) {
|
|
4967
5053
|
const parser = registry.getParser(item.id);
|
|
4968
5054
|
if (!parser) {
|
|
4969
5055
|
continue;
|
|
4970
5056
|
}
|
|
4971
|
-
const sourceFiles = await listBackfillSourceFiles(item, options);
|
|
5057
|
+
const sourceFiles = await listBackfillSourceFiles(item, options, ctx);
|
|
4972
5058
|
const selectedFiles = selectBackfillFilesForImport(sourceFiles, incrementalState?.sources[item.id]?.watermarkTs);
|
|
4973
5059
|
selectedFilesBySource.set(item.id, selectedFiles);
|
|
4974
5060
|
const filePaths = selectedFiles.map((f) => f.path);
|
|
4975
5061
|
const sourceEvents = [];
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
}
|
|
4984
|
-
}
|
|
4985
|
-
} else {
|
|
4986
|
-
const bar = new ProgressBar(ctx.stdout, `${item.id.padEnd(12)}`);
|
|
4987
|
-
bar.init(filePaths.length, `0 events`);
|
|
4988
|
-
for (let fi = 0; fi < filePaths.length; fi += 1) {
|
|
4989
|
-
const parsed = await parser(filePaths[fi], options);
|
|
4990
|
-
for (const event of parsed) {
|
|
4991
|
-
if (matchesBackfillFilters(event, options)) {
|
|
4992
|
-
sourceEvents.push(event);
|
|
4993
|
-
}
|
|
5062
|
+
const bar = options.json ? void 0 : new ProgressBar(ctx.stdout, `${item.id.padEnd(12)}`);
|
|
5063
|
+
bar?.init(filePaths.length, `0 events`);
|
|
5064
|
+
for (let fi = 0; fi < filePaths.length; fi += 1) {
|
|
5065
|
+
const parsed = await parser(filePaths[fi], options);
|
|
5066
|
+
for (const event of parsed) {
|
|
5067
|
+
if (matchesBackfillFilters(event, options)) {
|
|
5068
|
+
sourceEvents.push(event);
|
|
4994
5069
|
}
|
|
4995
|
-
bar.tick(`${fi + 1}/${filePaths.length} files, ${sourceEvents.length} events`);
|
|
4996
5070
|
}
|
|
4997
|
-
bar
|
|
5071
|
+
bar?.tick(`${fi + 1}/${filePaths.length} files, ${sourceEvents.length} events`);
|
|
4998
5072
|
}
|
|
5073
|
+
bar?.finalize(`${sourceEvents.length} events`);
|
|
4999
5074
|
for (const event of sourceEvents) canonicalEvents.push(event);
|
|
5000
5075
|
}
|
|
5076
|
+
return { canonicalEvents, selectedFilesBySource };
|
|
5077
|
+
}
|
|
5078
|
+
async function uploadSessionRollups(rollups, eventCount, options, ctx) {
|
|
5001
5079
|
const counts = { inserted: 0, skipped: 0, conflicts: 0, failed: 0 };
|
|
5002
|
-
const rollups = buildSessionRollups(canonicalEvents);
|
|
5003
5080
|
const batchSize = Math.max(1, Math.floor(numberOption(options["batch-size"]) || DEFAULT_BACKFILL_BATCH_SIZE));
|
|
5004
|
-
const
|
|
5081
|
+
const batchBytes = Math.max(64 * 1024, Math.floor(numberOption(options["batch-bytes"]) || DEFAULT_BACKFILL_BATCH_BYTES));
|
|
5082
|
+
const batches = packRollupBatches(rollups, batchSize, batchBytes);
|
|
5083
|
+
const totalBatches = batches.length;
|
|
5005
5084
|
let uploadBar;
|
|
5006
5085
|
if (!options.json) {
|
|
5007
|
-
write(ctx.stdout, `rollup ${rollups.length} from ${
|
|
5086
|
+
write(ctx.stdout, `rollup ${rollups.length} from ${eventCount} events
|
|
5008
5087
|
`);
|
|
5009
5088
|
uploadBar = new ProgressBar(ctx.stdout, `upload`.padEnd(12));
|
|
5010
5089
|
uploadBar.init(totalBatches, `0/${totalBatches} batches, 0 inserted`);
|
|
5011
5090
|
}
|
|
5012
|
-
for (
|
|
5013
|
-
const batch =
|
|
5014
|
-
const batchNumber =
|
|
5091
|
+
for (const [i, batch_] of batches.entries()) {
|
|
5092
|
+
const batch = batch_;
|
|
5093
|
+
const batchNumber = i + 1;
|
|
5015
5094
|
try {
|
|
5016
|
-
const
|
|
5017
|
-
counts.inserted +=
|
|
5018
|
-
counts.skipped +=
|
|
5019
|
-
counts.conflicts +=
|
|
5020
|
-
counts.failed +=
|
|
5095
|
+
const result = await sendSessionRollupBatch(batch, options, ctx);
|
|
5096
|
+
counts.inserted += result.inserted;
|
|
5097
|
+
counts.skipped += result.skipped;
|
|
5098
|
+
counts.conflicts += result.conflicts;
|
|
5099
|
+
counts.failed += result.failed;
|
|
5021
5100
|
} catch (error) {
|
|
5022
|
-
debug(ctx, `backfill rollup batch ${
|
|
5101
|
+
debug(ctx, `backfill rollup batch ${batchNumber}/${totalBatches} (${batch.length} rollups) failed: ${error.message}
|
|
5023
5102
|
`);
|
|
5024
5103
|
counts.failed += batch.length;
|
|
5025
5104
|
}
|
|
5026
|
-
|
|
5027
|
-
uploadBar.update(batchNumber, `${batchNumber}/${totalBatches} batches, inserted ${counts.inserted}`);
|
|
5028
|
-
}
|
|
5029
|
-
}
|
|
5030
|
-
if (uploadBar) {
|
|
5031
|
-
uploadBar.finalize(`inserted ${counts.inserted} \xB7 skipped ${counts.skipped}${counts.conflicts ? ` \xB7 conflicts ${counts.conflicts}` : ""}${counts.failed ? ` \xB7 failed ${counts.failed}` : ""}`);
|
|
5032
|
-
}
|
|
5033
|
-
const result = {
|
|
5034
|
-
importRunId: plan.importRun.importRunId,
|
|
5035
|
-
source,
|
|
5036
|
-
planned: rollups.length,
|
|
5037
|
-
sourceEvents: canonicalEvents.length,
|
|
5038
|
-
...counts
|
|
5039
|
-
};
|
|
5040
|
-
if (options.json) {
|
|
5041
|
-
write(ctx.stdout, `${JSON.stringify(result, null, 2)}
|
|
5042
|
-
`);
|
|
5043
|
-
}
|
|
5044
|
-
if (counts.failed === 0 && counts.conflicts === 0 && incrementalState) {
|
|
5045
|
-
await updateBackfillIncrementalState(home, incrementalState, selectedFilesBySource);
|
|
5105
|
+
uploadBar?.update(batchNumber, `${batchNumber}/${totalBatches} batches, inserted ${counts.inserted}`);
|
|
5046
5106
|
}
|
|
5047
|
-
|
|
5107
|
+
uploadBar?.finalize(`inserted ${counts.inserted} \xB7 skipped ${counts.skipped}${counts.conflicts ? ` \xB7 conflicts ${counts.conflicts}` : ""}${counts.failed ? ` \xB7 failed ${counts.failed}` : ""}`);
|
|
5108
|
+
return counts;
|
|
5048
5109
|
}
|
|
5049
5110
|
function backfillVerifyCommand(options, ctx) {
|
|
5050
5111
|
const importRunId = stringOption(options["import-run"]);
|
|
@@ -5068,12 +5129,7 @@ ${result.message}
|
|
|
5068
5129
|
return 0;
|
|
5069
5130
|
}
|
|
5070
5131
|
async function sendSessionRollupBatch(rollups, options, ctx) {
|
|
5071
|
-
const remote =
|
|
5072
|
-
apiUrl: stringOption(options["api-url"]),
|
|
5073
|
-
token: stringOption(options.token),
|
|
5074
|
-
env: ctx.env,
|
|
5075
|
-
fetch: ctx.fetch
|
|
5076
|
-
});
|
|
5132
|
+
const remote = resolveRemoteFromOptions(options, ctx);
|
|
5077
5133
|
if (!remote) {
|
|
5078
5134
|
throw new Error("No fetch available for HTTP upload");
|
|
5079
5135
|
}
|
|
@@ -5092,12 +5148,7 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
|
|
|
5092
5148
|
return result;
|
|
5093
5149
|
}
|
|
5094
5150
|
async function deleteSessionRollupsBySourceAPI(source, options, ctx) {
|
|
5095
|
-
const remote =
|
|
5096
|
-
apiUrl: stringOption(options["api-url"]),
|
|
5097
|
-
token: stringOption(options.token),
|
|
5098
|
-
env: ctx.env,
|
|
5099
|
-
fetch: ctx.fetch
|
|
5100
|
-
});
|
|
5151
|
+
const remote = resolveRemoteFromOptions(options, ctx);
|
|
5101
5152
|
if (!remote) {
|
|
5102
5153
|
throw new Error("No fetch available for HTTP delete");
|
|
5103
5154
|
}
|
|
@@ -5111,6 +5162,26 @@ async function deleteSessionRollupsBySourceAPI(source, options, ctx) {
|
|
|
5111
5162
|
function shouldUseIncrementalBackfill(options) {
|
|
5112
5163
|
return !stringOption(options.since) && !stringOption(options.until) && !stringOption(options["source-root"]) && numberOption(options.limit) === void 0;
|
|
5113
5164
|
}
|
|
5165
|
+
function packRollupBatches(rollups, maxCount, maxBytes) {
|
|
5166
|
+
const batches = [];
|
|
5167
|
+
let current = [];
|
|
5168
|
+
let currentBytes = 0;
|
|
5169
|
+
for (const rollup of rollups) {
|
|
5170
|
+
const size = JSON.stringify(rollup).length;
|
|
5171
|
+
const wouldExceed = current.length > 0 && (current.length >= maxCount || currentBytes + size > maxBytes);
|
|
5172
|
+
if (wouldExceed) {
|
|
5173
|
+
batches.push(current);
|
|
5174
|
+
current = [];
|
|
5175
|
+
currentBytes = 0;
|
|
5176
|
+
}
|
|
5177
|
+
current.push(rollup);
|
|
5178
|
+
currentBytes += size;
|
|
5179
|
+
}
|
|
5180
|
+
if (current.length > 0) {
|
|
5181
|
+
batches.push(current);
|
|
5182
|
+
}
|
|
5183
|
+
return batches;
|
|
5184
|
+
}
|
|
5114
5185
|
function selectBackfillFilesForImport(files, watermarkTs) {
|
|
5115
5186
|
if (!watermarkTs) {
|
|
5116
5187
|
return files;
|
|
@@ -5133,10 +5204,25 @@ function syncLocalTriggerStatePath(home) {
|
|
|
5133
5204
|
function syncLocalTriggerLockPath(home) {
|
|
5134
5205
|
return path11.join(home, ".codetime", "sync-local-trigger.lock");
|
|
5135
5206
|
}
|
|
5136
|
-
async function readBackfillIncrementalState(home) {
|
|
5137
|
-
const
|
|
5207
|
+
async function readBackfillIncrementalState(home, ctx) {
|
|
5208
|
+
const statePath = backfillIncrementalStatePath(home);
|
|
5209
|
+
const state = await readJsonIfExists(statePath);
|
|
5210
|
+
if (state === null) {
|
|
5211
|
+
return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: {} };
|
|
5212
|
+
}
|
|
5138
5213
|
if (!isPlainObject(state) || !isPlainObject(state.sources)) {
|
|
5139
|
-
|
|
5214
|
+
if (ctx) {
|
|
5215
|
+
debug(ctx, `backfill-state malformed at ${statePath}; ignoring watermarks
|
|
5216
|
+
`);
|
|
5217
|
+
}
|
|
5218
|
+
return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: {} };
|
|
5219
|
+
}
|
|
5220
|
+
if (state.version !== BACKFILL_STATE_SCHEMA_VERSION) {
|
|
5221
|
+
if (ctx) {
|
|
5222
|
+
debug(ctx, `backfill-state version ${String(state.version)} at ${statePath} differs from current v${BACKFILL_STATE_SCHEMA_VERSION}; dropping watermarks so the next sync re-imports under the new parser
|
|
5223
|
+
`);
|
|
5224
|
+
}
|
|
5225
|
+
return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: {} };
|
|
5140
5226
|
}
|
|
5141
5227
|
const sources = {};
|
|
5142
5228
|
for (const source of ["codex", "claude-code", "opencode", "pi"]) {
|
|
@@ -5145,7 +5231,7 @@ async function readBackfillIncrementalState(home) {
|
|
|
5145
5231
|
sources[source] = { watermarkTs: item.watermarkTs };
|
|
5146
5232
|
}
|
|
5147
5233
|
}
|
|
5148
|
-
return { version:
|
|
5234
|
+
return { version: BACKFILL_STATE_SCHEMA_VERSION, sources };
|
|
5149
5235
|
}
|
|
5150
5236
|
async function updateBackfillIncrementalState(home, state, selectedFilesBySource) {
|
|
5151
5237
|
for (const [source, files] of selectedFilesBySource.entries()) {
|
|
@@ -5317,6 +5403,15 @@ function debug(ctx, message) {
|
|
|
5317
5403
|
write(ctx.stderr, message);
|
|
5318
5404
|
}
|
|
5319
5405
|
}
|
|
5406
|
+
function resolveRemoteFromOptions(options, ctx) {
|
|
5407
|
+
return resolveRemote({
|
|
5408
|
+
apiUrl: stringOption(options["api-url"]),
|
|
5409
|
+
token: stringOption(options.token),
|
|
5410
|
+
env: ctx.env,
|
|
5411
|
+
fetch: ctx.fetch,
|
|
5412
|
+
homeOverride: stringOption(options.home)
|
|
5413
|
+
});
|
|
5414
|
+
}
|
|
5320
5415
|
function maskToken(token) {
|
|
5321
5416
|
if (token.length <= 8) {
|
|
5322
5417
|
return "***";
|
|
@@ -5377,12 +5472,7 @@ Usage: codetime token [set <token>|show|clear] [--remote <url>]
|
|
|
5377
5472
|
return 1;
|
|
5378
5473
|
}
|
|
5379
5474
|
async function machineCommand(action, options, ctx) {
|
|
5380
|
-
const remote =
|
|
5381
|
-
apiUrl: stringOption(options["api-url"]),
|
|
5382
|
-
token: stringOption(options.token),
|
|
5383
|
-
env: ctx.env,
|
|
5384
|
-
fetch: ctx.fetch
|
|
5385
|
-
});
|
|
5475
|
+
const remote = resolveRemoteFromOptions(options, ctx);
|
|
5386
5476
|
if (!remote || !remote.token) {
|
|
5387
5477
|
write(ctx.stderr, "machine command requires login (run `codetime login`).\n");
|
|
5388
5478
|
return 1;
|
|
@@ -5463,14 +5553,11 @@ Global options:
|
|
|
5463
5553
|
--token <token> Bearer token for this invocation (overrides config).
|
|
5464
5554
|
|
|
5465
5555
|
Token precedence (highest first):
|
|
5466
|
-
--token flag >
|
|
5467
|
-
(legacy alias) > saved config (~/.codetime/config.json).
|
|
5556
|
+
--token flag > CODETIME_TOKEN env > saved config (~/.codetime/config.json).
|
|
5468
5557
|
|
|
5469
5558
|
Environment:
|
|
5470
5559
|
CODETIME_API_URL Defaults to ${DEFAULT_API_URL}
|
|
5471
|
-
|
|
5472
|
-
AGENT_TIME_API_URL Legacy alias for CODETIME_API_URL
|
|
5473
|
-
AGENT_TIME_API_TOKEN Legacy alias for CODETIME_AGENT_TOKEN
|
|
5560
|
+
CODETIME_TOKEN Bearer token for the API
|
|
5474
5561
|
`;
|
|
5475
5562
|
}
|
|
5476
5563
|
export {
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codetime-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "codetime CLI — install AI-agent hooks (Claude Code, Codex, OpenCode, Pi) and report activity to codetime.dev.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
|
-
"homepage": "https://github.com/
|
|
10
|
+
"homepage": "https://github.com/codetime-dev/codetime-cli#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/
|
|
13
|
+
"url": "git+https://github.com/codetime-dev/codetime-cli.git"
|
|
14
14
|
},
|
|
15
15
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/
|
|
16
|
+
"url": "https://github.com/codetime-dev/codetime-cli/issues"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"codetime",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"cac": "^7.0.0",
|
|
37
37
|
"esbuild": "^0.28.0",
|
|
38
|
-
"@codetime/shared": "0.
|
|
38
|
+
"@codetime/shared": "0.3.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc -p tsconfig.json",
|