Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ In the age of AI-assisted development, **tokens are the new energy**. They power
- [Date Filtering](#date-filtering)
- [Pricing Lookup](#pricing-lookup)
- [Social](#social)
- [Automatic Sync](#automatic-sync)
- [Cursor IDE Commands](#cursor-ide-commands)
- [Example Output](#example-output---light-version)
- [Configuration](#configuration)
Expand Down Expand Up @@ -337,6 +338,23 @@ tokscale logout

<img alt="CLI Submit" src="./.github/assets/cli-submit.png" />

### Automatic Sync

Set up automatic hourly submissions to keep your profile updated:

```bash
# Set up hourly sync
tokscale sync setup

# Check sync status
tokscale sync status

# Remove automatic sync
tokscale sync remove
```

Logs are saved to `~/.config/tokscale/sync.log`.

### Cursor IDE Commands

Cursor IDE requires separate authentication via session token (different from the social platform login):
Expand Down Expand Up @@ -457,6 +475,8 @@ Submitted data goes through Level 1 validation:
- Required fields present
- Duplicate detection

> **Cross-Machine Aggregation**: When you submit from multiple machines using the same GitHub account, your usage data is automatically aggregated (summed) rather than overwritten. Each device's contributions are tracked separately and combined into your total.

## Wrapped 2025

![Wrapped 2025](.github/assets/hero-wrapped-2025.png)
Expand Down
33 changes: 32 additions & 1 deletion packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
getCursorCredentialsPath,
syncCursorCache,
} from "./cursor.js";
import { setupSync, removeSync, syncStatus } from "./sync.js";
import {
createUsageTable,
formatUsageRow,
Expand Down Expand Up @@ -446,13 +447,43 @@ async function main() {
await cursorStatus();
});

// =========================================================================
// Automatic Sync Commands
// =========================================================================

const syncCommand = program
.command("sync")
.description("Automatic sync commands (crontab/Task Scheduler)");

syncCommand
.command("setup")
.description("Set up hourly automatic submission")
.option("--interval <minutes>", "Sync interval in minutes (default: 60)", "60")
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new --interval flag is accepted but ignored: setupSync does not use the interval and the cron entry is hard-coded to hourly. Either implement interval handling or remove the flag to avoid misleading users.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/cli.ts, line 461:

<comment>The new `--interval` flag is accepted but ignored: `setupSync` does not use the interval and the cron entry is hard-coded to hourly. Either implement interval handling or remove the flag to avoid misleading users.</comment>

<file context>
@@ -446,13 +447,43 @@ async function main() {
+  syncCommand
+    .command("setup")
+    .description("Set up hourly automatic submission")
+    .option("--interval <minutes>", "Sync interval in minutes (default: 60)", "60")
+    .action(async (options) => {
+      await setupSync({ interval: options.interval });
</file context>
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed the issue by removing the misleading --interval flag from the sync setup command.

The problem: The --interval flag was accepted by the CLI but completely ignored. The cron entry was hard-coded to 0 * * * * (hourly at minute 0), and the setupSync() function parameter was prefixed with underscore (_options) indicating it was unused.

The fix: Removed the flag entirely rather than implementing interval handling, because:

  1. The command description already says "Set up hourly automatic submission"
  2. Cron doesn't directly support arbitrary minute intervals (e.g., "every 45 minutes")
  3. Removing the flag avoids misleading users who expect it to work

Changes made:

  • packages/cli/src/cli.ts: Removed --interval option and simplified the action handler
  • packages/cli/src/sync.ts: Removed SyncSetupOptions interface and simplified setupSync() signature
diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts
index 88e9083..6f1e73c 100644
--- a/packages/cli/src/cli.ts
+++ b/packages/cli/src/cli.ts
@@ -458,9 +458,8 @@ async function main() {
   syncCommand
     .command("setup")
     .description("Set up hourly automatic submission")
-    .option("--interval <minutes>", "Sync interval in minutes (default: 60)", "60")
-    .action(async (options) => {
-      await setupSync({ interval: options.interval });
+    .action(async () => {
+      await setupSync();
     });
 
   syncCommand
diff --git a/packages/cli/src/sync.ts b/packages/cli/src/sync.ts
index cf2f967..1d134bd 100644
--- a/packages/cli/src/sync.ts
+++ b/packages/cli/src/sync.ts
@@ -237,11 +237,7 @@ function checkWindowsTask(): { exists: boolean; error?: string } {
 // Public API
 // =============================================================================
 
-export interface SyncSetupOptions {
-  interval?: string;
-}
-
-export async function setupSync(_options: SyncSetupOptions = {}): Promise<void> {
+export async function setupSync(): Promise<void> {
   const credentials = loadCredentials();
   if (!credentials) {
     console.log(pc.red("\n  Error: Not logged in to Tokscale."));

Files changed:

  • packages/cli/src/cli.ts — Removed --interval option from sync setup command

  • packages/cli/src/sync.ts — Removed SyncSetupOptions interface and simplified setupSync() function signature

.action(async (options) => {
await setupSync({ interval: options.interval });
});

syncCommand
.command("remove")
.description("Remove automatic sync")
.action(async () => {
await removeSync();
});

syncCommand
.command("status")
.description("Check sync status")
.action(async () => {
await syncStatus();
});

// Check if a subcommand was provided
const args = process.argv.slice(2);
const firstArg = args[0] || '';
// Global flags should go to main program
const isGlobalFlag = ['--help', '-h', '--version', '-V'].includes(firstArg);
const hasSubcommand = args.length > 0 && !firstArg.startsWith('-');
const knownCommands = ['monthly', 'models', 'graph', 'wrapped', 'login', 'logout', 'whoami', 'submit', 'cursor', 'tui', 'pricing', 'help'];
const knownCommands = ['monthly', 'models', 'graph', 'wrapped', 'login', 'logout', 'whoami', 'submit', 'cursor', 'sync', 'tui', 'pricing', 'help'];
const isKnownCommand = hasSubcommand && knownCommands.includes(firstArg);

if (isKnownCommand || isGlobalFlag) {
Expand Down
Loading