Skip to content
Closed
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")
.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