awarts 0.1.0 → 0.2.1

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/dist/index.js +239 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4718,11 +4718,11 @@ import os3 from "node:os";
4718
4718
  var AWARTS_DIR = path2.join(os3.homedir(), ".awarts");
4719
4719
  var AUTH_FILE = path2.join(AWARTS_DIR, "auth.json");
4720
4720
  async function ensureDir() {
4721
- await fs6.mkdir(AWARTS_DIR, { recursive: true });
4721
+ await fs6.mkdir(AWARTS_DIR, { recursive: true, mode: 448 });
4722
4722
  }
4723
4723
  async function saveAuth(data) {
4724
4724
  await ensureDir();
4725
- await fs6.writeFile(AUTH_FILE, JSON.stringify(data, null, 2), "utf-8");
4725
+ await fs6.writeFile(AUTH_FILE, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
4726
4726
  }
4727
4727
  async function loadAuth() {
4728
4728
  try {
@@ -4747,7 +4747,7 @@ async function getToken() {
4747
4747
  }
4748
4748
 
4749
4749
  // src/lib/api.ts
4750
- var DEFAULT_API_URL = "http://localhost:3001";
4750
+ var DEFAULT_API_URL = "https://honorable-bee-242.convex.site";
4751
4751
  async function readConfigUrl() {
4752
4752
  try {
4753
4753
  const configPath = path3.join(os4.homedir(), ".awarts", "config.json");
@@ -5816,13 +5816,15 @@ async function startDeviceAuth() {
5816
5816
  dim("Is the backend running? Check AWARTS_API_URL or ~/.awarts/config.json");
5817
5817
  return;
5818
5818
  }
5819
+ const FRONTEND_URL = process.env.AWARTS_FRONTEND_URL ?? "https://awarts.vercel.app";
5820
+ const verifyUrl = `${FRONTEND_URL}/cli/verify?code=${initData.code}`;
5819
5821
  console.log();
5820
5822
  console.log(` ${source_default.bold("Your verification code:")} ${source_default.bgWhite.black.bold(` ${initData.code} `)}`);
5821
5823
  console.log();
5822
- info(`Open this URL to verify: ${source_default.cyan.underline(initData.verify_url)}`);
5824
+ info(`Open this URL to verify: ${source_default.cyan.underline(verifyUrl)}`);
5823
5825
  console.log();
5824
5826
  try {
5825
- await open_default(initData.verify_url);
5827
+ await open_default(verifyUrl);
5826
5828
  dim("Browser opened automatically. Waiting for verification...");
5827
5829
  } catch {
5828
5830
  dim("Could not open browser. Please visit the URL above manually.");
@@ -6492,6 +6494,195 @@ async function syncCommand() {
6492
6494
  }
6493
6495
  }
6494
6496
 
6497
+ // src/lib/daemon.ts
6498
+ import fs12 from "node:fs/promises";
6499
+ import { openSync } from "node:fs";
6500
+ import path8 from "node:path";
6501
+ import os9 from "node:os";
6502
+ import { spawn } from "node:child_process";
6503
+ var AWARTS_DIR2 = path8.join(os9.homedir(), ".awarts");
6504
+ var PID_FILE = path8.join(AWARTS_DIR2, "daemon.pid");
6505
+ var LOG_FILE = path8.join(AWARTS_DIR2, "daemon.log");
6506
+ var DEFAULT_INTERVAL_MS = 5 * 60 * 1000;
6507
+ async function ensureDir2() {
6508
+ await fs12.mkdir(AWARTS_DIR2, { recursive: true });
6509
+ }
6510
+ async function readPid() {
6511
+ try {
6512
+ const raw = await fs12.readFile(PID_FILE, "utf-8");
6513
+ const pid = parseInt(raw.trim(), 10);
6514
+ return isNaN(pid) ? null : pid;
6515
+ } catch {
6516
+ return null;
6517
+ }
6518
+ }
6519
+ async function writePid(pid) {
6520
+ await ensureDir2();
6521
+ await fs12.writeFile(PID_FILE, String(pid), "utf-8");
6522
+ }
6523
+ async function removePid() {
6524
+ try {
6525
+ await fs12.unlink(PID_FILE);
6526
+ } catch {}
6527
+ }
6528
+ function isProcessRunning(pid) {
6529
+ try {
6530
+ process.kill(pid, 0);
6531
+ return true;
6532
+ } catch {
6533
+ return false;
6534
+ }
6535
+ }
6536
+ function killProcess(pid) {
6537
+ try {
6538
+ process.kill(pid, "SIGTERM");
6539
+ return true;
6540
+ } catch {
6541
+ return false;
6542
+ }
6543
+ }
6544
+ async function appendLog(message) {
6545
+ await ensureDir2();
6546
+ const timestamp = new Date().toISOString();
6547
+ const line = `[${timestamp}] ${message}
6548
+ `;
6549
+ await fs12.appendFile(LOG_FILE, line, "utf-8");
6550
+ }
6551
+ async function readLogTail(lines = 50) {
6552
+ try {
6553
+ const content = await fs12.readFile(LOG_FILE, "utf-8");
6554
+ const allLines = content.split(`
6555
+ `).filter(Boolean);
6556
+ return allLines.slice(-lines).join(`
6557
+ `);
6558
+ } catch {
6559
+ return "(no log file found)";
6560
+ }
6561
+ }
6562
+ async function spawnDaemon(intervalMs) {
6563
+ await ensureDir2();
6564
+ const cliScript = process.argv[1];
6565
+ const logFd = openSync(LOG_FILE, "a");
6566
+ const child = spawn(process.execPath, [cliScript, "daemon", "__run", "--interval", String(intervalMs)], {
6567
+ detached: true,
6568
+ stdio: ["ignore", logFd, logFd],
6569
+ env: { ...process.env }
6570
+ });
6571
+ child.unref();
6572
+ const pid = child.pid;
6573
+ if (!pid) {
6574
+ throw new Error("Failed to spawn daemon process");
6575
+ }
6576
+ await writePid(pid);
6577
+ return pid;
6578
+ }
6579
+
6580
+ // src/commands/daemon.ts
6581
+ async function daemonStartCommand(intervalMs) {
6582
+ banner();
6583
+ const auth = await loadAuth();
6584
+ if (!auth) {
6585
+ error("Not logged in. Run awarts login first.");
6586
+ return;
6587
+ }
6588
+ const existingPid = await readPid();
6589
+ if (existingPid && isProcessRunning(existingPid)) {
6590
+ warn(`Daemon is already running (PID ${existingPid}).`);
6591
+ dim("Run awarts daemon stop first to restart.");
6592
+ console.log();
6593
+ return;
6594
+ }
6595
+ if (existingPid) {
6596
+ await removePid();
6597
+ }
6598
+ const spin = spinner("Starting daemon...");
6599
+ spin.start();
6600
+ try {
6601
+ const pid = await spawnDaemon(intervalMs);
6602
+ spin.succeed(source_default.green(`Daemon started (PID ${pid})`));
6603
+ console.log();
6604
+ kv("Sync interval", `${Math.round(intervalMs / 60000)} minutes`);
6605
+ kv("PID file", PID_FILE);
6606
+ kv("Log file", LOG_FILE);
6607
+ console.log();
6608
+ dim("Run awarts daemon status to check health.");
6609
+ dim("Run awarts daemon logs to view output.");
6610
+ dim("Run awarts daemon stop to stop.");
6611
+ console.log();
6612
+ } catch (err) {
6613
+ spin.fail("Failed to start daemon.");
6614
+ error(err instanceof Error ? err.message : String(err));
6615
+ }
6616
+ }
6617
+ async function daemonStopCommand() {
6618
+ banner();
6619
+ const pid = await readPid();
6620
+ if (!pid) {
6621
+ info("No daemon PID file found. Daemon is not running.");
6622
+ console.log();
6623
+ return;
6624
+ }
6625
+ if (!isProcessRunning(pid)) {
6626
+ info(`Daemon process ${pid} is not running (stale PID file).`);
6627
+ await removePid();
6628
+ success("Cleaned up stale PID file.");
6629
+ console.log();
6630
+ return;
6631
+ }
6632
+ const killed = killProcess(pid);
6633
+ if (killed) {
6634
+ success(`Daemon stopped (PID ${pid}).`);
6635
+ await removePid();
6636
+ } else {
6637
+ error(`Failed to stop daemon (PID ${pid}).`);
6638
+ }
6639
+ console.log();
6640
+ }
6641
+ async function daemonStatusCommand() {
6642
+ banner();
6643
+ const pid = await readPid();
6644
+ console.log(` ${source_default.bold.underline("Daemon Status")}`);
6645
+ console.log();
6646
+ if (!pid) {
6647
+ warn("Daemon is not running (no PID file).");
6648
+ dim("Start with: awarts daemon start");
6649
+ console.log();
6650
+ return;
6651
+ }
6652
+ if (isProcessRunning(pid)) {
6653
+ success(`Daemon is running (PID ${pid})`);
6654
+ kv("PID file", PID_FILE);
6655
+ kv("Log file", LOG_FILE);
6656
+ } else {
6657
+ warn(`Daemon is not running (stale PID ${pid}).`);
6658
+ await removePid();
6659
+ dim("Cleaned up stale PID file. Start with: awarts daemon start");
6660
+ }
6661
+ console.log();
6662
+ }
6663
+ async function daemonLogsCommand(lines) {
6664
+ const tail = await readLogTail(lines);
6665
+ console.log(tail);
6666
+ }
6667
+ async function daemonRunLoop(intervalMs) {
6668
+ await appendLog(`Daemon started. Interval: ${intervalMs}ms (${Math.round(intervalMs / 60000)} min)`);
6669
+ const runSync = async () => {
6670
+ await appendLog("Starting sync...");
6671
+ try {
6672
+ await syncCommand();
6673
+ await appendLog("Sync completed successfully.");
6674
+ } catch (err) {
6675
+ const msg = err instanceof Error ? err.message : String(err);
6676
+ await appendLog(`Sync failed: ${msg}`);
6677
+ }
6678
+ };
6679
+ await runSync();
6680
+ while (true) {
6681
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
6682
+ await runSync();
6683
+ }
6684
+ }
6685
+
6495
6686
  // src/index.ts
6496
6687
  var program2 = new Command;
6497
6688
  program2.name("awarts").description("Track your AI coding spend across Claude, Codex, Gemini & Antigravity").version("0.1.0");
@@ -6542,6 +6733,49 @@ program2.command("logout").description("Clear stored credentials").action(async
6542
6733
  process.exit(1);
6543
6734
  }
6544
6735
  });
6736
+ var daemon = program2.command("daemon").description("Manage the background auto-sync daemon");
6737
+ daemon.command("start").description("Start the background sync daemon").option("--interval <minutes>", "Sync interval in minutes", "5").action(async (opts) => {
6738
+ try {
6739
+ const minutes = Number(opts.interval);
6740
+ const intervalMs = isNaN(minutes) || minutes < 1 ? DEFAULT_INTERVAL_MS : minutes * 60 * 1000;
6741
+ await daemonStartCommand(intervalMs);
6742
+ } catch (err) {
6743
+ error(err instanceof Error ? err.message : String(err));
6744
+ process.exit(1);
6745
+ }
6746
+ });
6747
+ daemon.command("stop").description("Stop the running daemon").action(async () => {
6748
+ try {
6749
+ await daemonStopCommand();
6750
+ } catch (err) {
6751
+ error(err instanceof Error ? err.message : String(err));
6752
+ process.exit(1);
6753
+ }
6754
+ });
6755
+ daemon.command("status").description("Check if the daemon is running").action(async () => {
6756
+ try {
6757
+ await daemonStatusCommand();
6758
+ } catch (err) {
6759
+ error(err instanceof Error ? err.message : String(err));
6760
+ process.exit(1);
6761
+ }
6762
+ });
6763
+ daemon.command("logs").description("Show recent daemon log output").option("-n, --lines <count>", "Number of lines to show", "50").action(async (opts) => {
6764
+ try {
6765
+ await daemonLogsCommand(Number(opts.lines) || 50);
6766
+ } catch (err) {
6767
+ error(err instanceof Error ? err.message : String(err));
6768
+ process.exit(1);
6769
+ }
6770
+ });
6771
+ daemon.command("__run", { hidden: true }).option("--interval <ms>", "Interval in milliseconds").action(async (opts) => {
6772
+ try {
6773
+ await daemonRunLoop(Number(opts.interval) || DEFAULT_INTERVAL_MS);
6774
+ } catch (err) {
6775
+ console.error(`Daemon error: ${err instanceof Error ? err.message : String(err)}`);
6776
+ process.exit(1);
6777
+ }
6778
+ });
6545
6779
  program2.parseAsync(process.argv).catch((err) => {
6546
6780
  error(err instanceof Error ? err.message : String(err));
6547
6781
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awarts",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Track your AI coding across Claude, Codex, Gemini & Antigravity",
5
5
  "type": "module",
6
6
  "bin": {