clauddy 1.0.0 → 1.2.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/README.md CHANGED
@@ -46,7 +46,7 @@ The token is saved locally (see [Data & privacy](#data--privacy)) and refreshed
46
46
  </tr>
47
47
  </table>
48
48
 
49
- Plus a welcome **wave** on launch. You can preview any state from the terminal with [`./pet`](#simulate-states-pet).
49
+ Plus a welcome **wave** on launch. You can [poke the pet from the terminal](#play-with-the-pet) too.
50
50
 
51
51
  ## Install
52
52
 
@@ -100,22 +100,25 @@ Settings saved from the UI live in `~/.claude-usage-monitor/config.json`, so you
100
100
  }
101
101
  ```
102
102
 
103
- ## Simulate states (`./pet`)
103
+ ## Play with the pet
104
104
 
105
- While developing the animations, force any state from the terminal — the app watches `~/.claude-usage-monitor/debug.json` and reacts live (no rebuild needed). Run from the repo root:
105
+ With the widget running, poke it from the terminal — just for fun:
106
106
 
107
107
  ```bash
108
- ./pet fire # 🔥 on fire — flames, shivers, red tint
109
- ./pet sleeping # 😴 sleeping blue zzz, closed eyes, moonlight
110
- ./pet working # 🍴 working — eats token coins and hops
111
- ./pet idle # 🙂 idle — breathe + blink
112
-
113
- ./pet poke # 💕 one-shot squish + hearts
114
- ./pet celebrate # 🎉 one-shot jump + confetti burst
115
-
116
- ./pet auto # ↩️ release control, back to real usage data
108
+ bunx clauddy poke # 💕 squish + hearts
109
+ bunx clauddy celebrate # 🎉 jump + confetti
110
+ bunx clauddy fire # 🔥 on fire
111
+ bunx clauddy sleeping # 😴 blue zzz
112
+ bunx clauddy working # 🍴 eats token coins
113
+ bunx clauddy tired # 🥵 maxed out
114
+ bunx clauddy idle # 🙂 calm
115
+ bunx clauddy auto # ↩️ back to your real usage
117
116
  ```
118
117
 
118
+ Each state is written to the data dir the running widget watches, so it reacts
119
+ live. (Installed globally? Drop the `bunx`: `clauddy poke`. Working on the repo?
120
+ `./pet <state>` does the same.)
121
+
119
122
  ## How it works
120
123
 
121
124
  - **`main.js`** — Electron main process: frameless, transparent, always-on-top window; polls usage; fires macOS notifications; watches `config.json` and `debug.json`.
@@ -150,5 +153,3 @@ your PRs.
150
153
 
151
154
  - `feat:` → minor, `fix:` → patch, `feat!:`/`BREAKING CHANGE` → major.
152
155
  - `docs:`/`chore:`/`ci:` etc. don't trigger a release.
153
- - Needs an **`NPM_TOKEN`** repo secret (an npm automation token); `GITHUB_TOKEN`
154
- is automatic.
package/auth.js CHANGED
@@ -11,7 +11,12 @@ const TOKEN_URL = 'https://platform.claude.com/v1/oauth/token'
11
11
  const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage'
12
12
  const SCOPE = 'org:create_api_key user:profile user:inference'
13
13
  const UA = 'claude-cli/2.1.181 (external, cli)'
14
- const TOKEN_PATH = path.join(os.homedir(), '.claude-usage-monitor', 'auth.json')
14
+ // when CLAUDE_CONFIG_DIR is set (e.g. via direnv for multi-account setups),
15
+ // keep the widget's data alongside that account's Claude config
16
+ const DATA_DIR = process.env.CLAUDE_CONFIG_DIR
17
+ ? path.join(process.env.CLAUDE_CONFIG_DIR, 'usage-monitor')
18
+ : path.join(os.homedir(), '.claude-usage-monitor')
19
+ const TOKEN_PATH = path.join(DATA_DIR, 'auth.json')
15
20
 
16
21
  const b64url = (buf) =>
17
22
  buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
package/bin/clauddy.js CHANGED
@@ -1,12 +1,59 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Entry point when installed from npm / run via `bunx claude-usage-monitor`:
4
- // spawn the Electron runtime pointed at the app's main process.
5
- const { spawn } = require('node:child_process')
3
+ // `clauddy` → launch the widget (spawns Electron on main.js)
4
+ // `clauddy <state>` → poke the running widget for fun/demo (writes the state
5
+ // to the data dir the app watches; the app reacts live)
6
6
  const path = require('node:path')
7
- const electron = require('electron')
7
+ const fs = require('node:fs')
8
+ const os = require('node:os')
8
9
 
9
- const child = spawn(electron, [path.join(__dirname, '..', 'main.js')], {
10
- stdio: 'inherit',
11
- })
12
- child.on('close', (code) => process.exit(code ?? 0))
10
+ // same data dir the app uses (respects CLAUDE_CONFIG_DIR for multi-account)
11
+ const dataDir = process.env.CLAUDE_CONFIG_DIR
12
+ ? path.join(process.env.CLAUDE_CONFIG_DIR, 'usage-monitor')
13
+ : path.join(os.homedir(), '.claude-usage-monitor')
14
+
15
+ const STATES = [
16
+ 'fire',
17
+ 'sleeping',
18
+ 'working',
19
+ 'tired',
20
+ 'idle',
21
+ 'poke',
22
+ 'celebrate',
23
+ 'auto',
24
+ 'clear',
25
+ ]
26
+ const arg = process.argv[2]
27
+
28
+ if (!arg) {
29
+ // no argument → launch the app
30
+ const { spawn } = require('node:child_process')
31
+ const electron = require('electron')
32
+ const child = spawn(electron, [path.join(__dirname, '..', 'main.js')], { stdio: 'inherit' })
33
+ child.on('close', (code) => process.exit(code ?? 0))
34
+ } else if (arg === '--help' || arg === '-h') {
35
+ console.log(
36
+ [
37
+ 'clauddy — a cute desktop pet that tracks your Claude Code usage',
38
+ '',
39
+ 'Usage:',
40
+ ' clauddy launch the widget',
41
+ ' clauddy <state> poke the running widget (just for fun)',
42
+ '',
43
+ 'States: fire, sleeping, working, tired, idle, poke, celebrate, auto',
44
+ '(the widget must be running for a state to show)',
45
+ ].join('\n'),
46
+ )
47
+ } else if (STATES.includes(arg)) {
48
+ // write the state for the running app to pick up
49
+ fs.mkdirSync(dataDir, { recursive: true })
50
+ fs.writeFileSync(
51
+ path.join(dataDir, 'debug.json'),
52
+ `${JSON.stringify({ state: arg, t: Date.now() })}\n`,
53
+ )
54
+ console.log(`clauddy → ${arg} (the running widget will react)`)
55
+ } else {
56
+ console.error(`clauddy: unknown command "${arg}"`)
57
+ console.error('try: fire, sleeping, working, tired, idle, poke, celebrate, auto — or --help')
58
+ process.exit(1)
59
+ }
package/main.js CHANGED
@@ -5,8 +5,20 @@ const os = require('node:os')
5
5
  const { getUsage } = require('./usage')
6
6
  const auth = require('./auth')
7
7
 
8
- // data dir: kept outside the project folder so moving the repo doesn't break it
9
- const DATA_DIR = path.join(os.homedir(), '.claude-usage-monitor')
8
+ // data dir: kept outside the project folder so moving the repo doesn't break it.
9
+ // when CLAUDE_CONFIG_DIR is set (e.g. via direnv for multi-account setups), nest
10
+ // the widget's data under that account's Claude config dir so each account gets
11
+ // its own auth.json, config.json, and Electron userData.
12
+ const DATA_DIR = process.env.CLAUDE_CONFIG_DIR
13
+ ? path.join(process.env.CLAUDE_CONFIG_DIR, 'usage-monitor')
14
+ : path.join(os.homedir(), '.claude-usage-monitor')
15
+ // when isolating per-account, also pin Electron's userData under DATA_DIR so two
16
+ // widgets can run in parallel without fighting over cookies/cache. left untouched
17
+ // in the default case so existing installs keep their window position etc.
18
+ if (process.env.CLAUDE_CONFIG_DIR) {
19
+ fs.mkdirSync(DATA_DIR, { recursive: true })
20
+ app.setPath('userData', path.join(DATA_DIR, 'electron'))
21
+ }
10
22
  // external config: edit without rebuilding the .app
11
23
  const EXTERNAL_CONFIG = path.join(DATA_DIR, 'config.json')
12
24
  // debug channel: `./pet <state>` writes here to force a state (dev only)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clauddy",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "A cute desktop pet that tracks your Claude Code usage",
5
5
  "main": "main.js",
6
6
  "bin": {
package/usage.js CHANGED
@@ -2,7 +2,8 @@ const fs = require('node:fs')
2
2
  const path = require('node:path')
3
3
  const os = require('node:os')
4
4
 
5
- const PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects')
5
+ const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude')
6
+ const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects')
6
7
 
7
8
  function labelFor(model) {
8
9
  if (!model) return 'desconhecido'