codetime-cli 0.4.0 → 0.5.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 +143 -3
- package/package.json +1 -1
package/bin/codetime.mjs
CHANGED
|
@@ -973,7 +973,7 @@ import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
|
973
973
|
import path2 from "node:path";
|
|
974
974
|
|
|
975
975
|
// src/lib/constants.ts
|
|
976
|
-
var PACKAGE_VERSION = true ? "0.
|
|
976
|
+
var PACKAGE_VERSION = true ? "0.5.0" : "0.1.1";
|
|
977
977
|
var DEFAULT_API_URL = "https://codetime.dev";
|
|
978
978
|
var DEFAULT_BACKFILL_BATCH_SIZE = 50;
|
|
979
979
|
var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
|
|
@@ -4578,6 +4578,40 @@ async function logError(scope, error, meta = {}, home = homedir2()) {
|
|
|
4578
4578
|
await writeLog({ scope, error, meta, level: "error" }, home);
|
|
4579
4579
|
}
|
|
4580
4580
|
|
|
4581
|
+
// src/lib/login.ts
|
|
4582
|
+
function sleep(ms) {
|
|
4583
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4584
|
+
}
|
|
4585
|
+
function openBrowser(ctx, url) {
|
|
4586
|
+
const { command, args } = browserCommand(ctx.env.CODETIME_OS ?? process.platform, url);
|
|
4587
|
+
try {
|
|
4588
|
+
const child = ctx.spawn(command, args, { detached: true, stdio: "ignore" });
|
|
4589
|
+
child.unref?.();
|
|
4590
|
+
} catch {
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
function browserCommand(platform, url) {
|
|
4594
|
+
if (platform === "darwin") {
|
|
4595
|
+
return { command: "open", args: [url] };
|
|
4596
|
+
}
|
|
4597
|
+
if (platform === "win32") {
|
|
4598
|
+
return { command: "cmd", args: ["/c", "start", "", url] };
|
|
4599
|
+
}
|
|
4600
|
+
return { command: "xdg-open", args: [url] };
|
|
4601
|
+
}
|
|
4602
|
+
function isHeadless(ctx) {
|
|
4603
|
+
if (ctx.env.CODETIME_NO_BROWSER) {
|
|
4604
|
+
return true;
|
|
4605
|
+
}
|
|
4606
|
+
if (ctx.env.SSH_CONNECTION || ctx.env.SSH_TTY) {
|
|
4607
|
+
return true;
|
|
4608
|
+
}
|
|
4609
|
+
if (process.platform === "linux") {
|
|
4610
|
+
return !ctx.env.DISPLAY && !ctx.env.WAYLAND_DISPLAY;
|
|
4611
|
+
}
|
|
4612
|
+
return false;
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4581
4615
|
// src/lib/progress.ts
|
|
4582
4616
|
var BAR_WIDTH = 24;
|
|
4583
4617
|
var ProgressBar = class {
|
|
@@ -4737,6 +4771,28 @@ async function deleteRollupsBySource(remote, source, machine) {
|
|
|
4737
4771
|
const body = await response.json();
|
|
4738
4772
|
return Number(body.deleted) || 0;
|
|
4739
4773
|
}
|
|
4774
|
+
async function startCliLink(remote) {
|
|
4775
|
+
const response = await remote.fetchImpl(joinUrl(remote.baseUrl, "/v3/agent/cli/link/start"), {
|
|
4776
|
+
method: "POST",
|
|
4777
|
+
headers: buildHeaders(),
|
|
4778
|
+
body: "{}"
|
|
4779
|
+
});
|
|
4780
|
+
if (!response.ok) {
|
|
4781
|
+
throw new Error(`Failed to start login: ${response.status}`);
|
|
4782
|
+
}
|
|
4783
|
+
return await response.json();
|
|
4784
|
+
}
|
|
4785
|
+
async function pollCliLink(remote, deviceCode) {
|
|
4786
|
+
const response = await remote.fetchImpl(joinUrl(remote.baseUrl, "/v3/agent/cli/link/poll"), {
|
|
4787
|
+
method: "POST",
|
|
4788
|
+
headers: buildHeaders(),
|
|
4789
|
+
body: JSON.stringify({ deviceCode })
|
|
4790
|
+
});
|
|
4791
|
+
if (!response.ok) {
|
|
4792
|
+
throw new Error(`Login poll failed: ${response.status}`);
|
|
4793
|
+
}
|
|
4794
|
+
return await response.json();
|
|
4795
|
+
}
|
|
4740
4796
|
async function listMachines(remote) {
|
|
4741
4797
|
const response = await remote.fetchImpl(joinUrl(remote.baseUrl, "/v3/machines"), {
|
|
4742
4798
|
headers: buildHeaders(remote.token)
|
|
@@ -4843,6 +4899,11 @@ function createCli(ctx, registry) {
|
|
|
4843
4899
|
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));
|
|
4844
4900
|
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));
|
|
4845
4901
|
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));
|
|
4902
|
+
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", "Force full re-import: clear watermark and re-process all files").option("--dry-run", "Print the planned import without uploading").action((options) => {
|
|
4903
|
+
const opts = normalizeOptions(options);
|
|
4904
|
+
return backfillCommand({ ...opts, action: "import", source: stringOption(opts.source) || "all" }, ctx, registry);
|
|
4905
|
+
});
|
|
4906
|
+
cli.command("login", "Authorize this machine by signing in through your browser").option("--remote <url>", "Override API base URL for this login").option("--no-browser", "Print the login URL instead of opening a browser").action((options) => loginCommand(normalizeOptions(options), ctx));
|
|
4846
4907
|
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));
|
|
4847
4908
|
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));
|
|
4848
4909
|
return cli;
|
|
@@ -5716,6 +5777,80 @@ function maskToken(token) {
|
|
|
5716
5777
|
}
|
|
5717
5778
|
return `${token.slice(0, 3)}\u2026${token.slice(-4)}`;
|
|
5718
5779
|
}
|
|
5780
|
+
var LOGIN_MAX_WAIT_MS = 15 * 60 * 1e3;
|
|
5781
|
+
async function loginCommand(options, ctx) {
|
|
5782
|
+
const home = resolveHome(options, ctx);
|
|
5783
|
+
const remoteOverride = stringOption(options.remote) || stringOption(options["api-url"]);
|
|
5784
|
+
const existing = readConfig(home);
|
|
5785
|
+
const baseUrl = (remoteOverride || ctx.env.CODETIME_API_URL || existing.remoteUrl || DEFAULT_API_URL).replace(/\/$/, "");
|
|
5786
|
+
const remote = resolveRemote({
|
|
5787
|
+
apiUrl: baseUrl,
|
|
5788
|
+
env: ctx.env,
|
|
5789
|
+
fetch: ctx.fetch,
|
|
5790
|
+
homeOverride: home
|
|
5791
|
+
});
|
|
5792
|
+
if (!remote) {
|
|
5793
|
+
write(ctx.stderr, "No fetch implementation available.\n");
|
|
5794
|
+
return 1;
|
|
5795
|
+
}
|
|
5796
|
+
let link;
|
|
5797
|
+
try {
|
|
5798
|
+
link = await startCliLink(remote);
|
|
5799
|
+
} catch (error) {
|
|
5800
|
+
write(ctx.stderr, `${error.message}
|
|
5801
|
+
`);
|
|
5802
|
+
return 1;
|
|
5803
|
+
}
|
|
5804
|
+
const authUrl = `${baseUrl}/cli/auth?code=${encodeURIComponent(link.userCode)}`;
|
|
5805
|
+
const noBrowser = options.browser === false;
|
|
5806
|
+
const headless = isHeadless(ctx);
|
|
5807
|
+
write(ctx.stdout, `
|
|
5808
|
+
To sign in, visit:
|
|
5809
|
+
|
|
5810
|
+
${authUrl}
|
|
5811
|
+
|
|
5812
|
+
and confirm this code: ${link.userCode}
|
|
5813
|
+
|
|
5814
|
+
`);
|
|
5815
|
+
if (!noBrowser && !headless) {
|
|
5816
|
+
openBrowser(ctx, authUrl);
|
|
5817
|
+
}
|
|
5818
|
+
write(ctx.stdout, "Waiting for authorization\u2026\n");
|
|
5819
|
+
const intervalMs = Math.max(1, link.interval || 4) * 1e3;
|
|
5820
|
+
const deadline = Date.now() + Math.min(LOGIN_MAX_WAIT_MS, Math.max(1, link.expiresIn || 600) * 1e3);
|
|
5821
|
+
while (Date.now() < deadline) {
|
|
5822
|
+
let poll;
|
|
5823
|
+
try {
|
|
5824
|
+
poll = await pollCliLink(remote, link.deviceCode);
|
|
5825
|
+
} catch (error) {
|
|
5826
|
+
void error;
|
|
5827
|
+
await sleep(intervalMs);
|
|
5828
|
+
continue;
|
|
5829
|
+
}
|
|
5830
|
+
if (poll.status === "pending") {
|
|
5831
|
+
await sleep(intervalMs);
|
|
5832
|
+
continue;
|
|
5833
|
+
}
|
|
5834
|
+
if (poll.status === "expired") {
|
|
5835
|
+
write(ctx.stderr, "Login code expired before it was approved. Re-run `codetime login`.\n");
|
|
5836
|
+
return 1;
|
|
5837
|
+
}
|
|
5838
|
+
writeConfig({
|
|
5839
|
+
...existing,
|
|
5840
|
+
token: poll.token,
|
|
5841
|
+
...poll.userId == null ? {} : { userId: String(poll.userId) },
|
|
5842
|
+
// Persist the host only when explicitly overridden, matching
|
|
5843
|
+
// `token set` so a default-host login never clobbers an earlier
|
|
5844
|
+
// --remote choice.
|
|
5845
|
+
...remoteOverride ? { remoteUrl: remoteOverride } : {}
|
|
5846
|
+
}, home);
|
|
5847
|
+
write(ctx.stdout, `Logged in. Token saved (${maskToken(poll.token)}).
|
|
5848
|
+
`);
|
|
5849
|
+
return 0;
|
|
5850
|
+
}
|
|
5851
|
+
write(ctx.stderr, "Timed out waiting for authorization. Re-run `codetime login`.\n");
|
|
5852
|
+
return 1;
|
|
5853
|
+
}
|
|
5719
5854
|
async function tokenCommand(action, value, options, ctx) {
|
|
5720
5855
|
const verb = action || "show";
|
|
5721
5856
|
const home = resolveHome(options, ctx);
|
|
@@ -5827,20 +5962,25 @@ Usage:
|
|
|
5827
5962
|
codetime detect [--json] [--home <path>]
|
|
5828
5963
|
codetime install [--target codex,claude,opencode,pi] [--all] [--dry-run] [--force] [--home <path>]
|
|
5829
5964
|
codetime hook --agent <name>
|
|
5965
|
+
codetime sync [--source <source>] [--force] [--dry-run]
|
|
5830
5966
|
codetime backfill discover|plan|import|verify --source codex|claude-code|opencode|pi|all --dry-run [--json] [--batch-size <count>]
|
|
5967
|
+
codetime login [--no-browser] [--remote <url>]
|
|
5831
5968
|
codetime token set <token>
|
|
5832
5969
|
codetime token show
|
|
5833
5970
|
codetime token clear
|
|
5834
5971
|
|
|
5835
5972
|
Setup:
|
|
5836
|
-
|
|
5837
|
-
|
|
5973
|
+
Run: codetime login (signs in through your browser)
|
|
5974
|
+
Or copy your upload token from https://codetime.dev/dashboard/settings
|
|
5975
|
+
and run: codetime token set <token>
|
|
5838
5976
|
|
|
5839
5977
|
Commands:
|
|
5840
5978
|
detect Show supported local targets and install status.
|
|
5841
5979
|
install Install integration files into detected or requested targets.
|
|
5842
5980
|
hook Read agent hook JSON from stdin and report a throttled event.
|
|
5981
|
+
sync Import and upload all local agent history (backfill import --source all).
|
|
5843
5982
|
backfill Discover local history and create metadata-only import plans.
|
|
5983
|
+
login Authorize this machine by signing in through your browser.
|
|
5844
5984
|
token Set, show, or clear the persisted API token.
|
|
5845
5985
|
machine List your machines (read-only).
|
|
5846
5986
|
version Print CLI version.
|
package/package.json
CHANGED