@vibe-cafe/vibe-usage 0.6.8 → 0.7.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
@@ -19,7 +19,12 @@ This will:
19
19
  npx @vibe-cafe/vibe-usage # Init (first run) or sync (subsequent runs)
20
20
  npx @vibe-cafe/vibe-usage init # Re-run setup
21
21
  npx @vibe-cafe/vibe-usage sync # Manual sync
22
- npx @vibe-cafe/vibe-usage daemon # Continuous sync (every 5 minutes)
22
+ npx @vibe-cafe/vibe-usage daemon # Continuous sync (every 5m, foreground)
23
+ npx @vibe-cafe/vibe-usage daemon install # Install background service (systemd/launchd)
24
+ npx @vibe-cafe/vibe-usage daemon uninstall # Remove background service
25
+ npx @vibe-cafe/vibe-usage daemon status # Show background service status
26
+ npx @vibe-cafe/vibe-usage daemon stop # Stop background service
27
+ npx @vibe-cafe/vibe-usage daemon restart # Restart background service
23
28
  npx @vibe-cafe/vibe-usage reset # Delete all data and re-upload from local logs
24
29
  npx @vibe-cafe/vibe-usage reset --local # Delete this host's data only and re-upload
25
30
  npx @vibe-cafe/vibe-usage skill # Install skill for AI coding assistants
@@ -89,13 +94,34 @@ Config stored at `~/.vibe-usage/config.json` (dev: `config.dev.json`). Contains
89
94
 
90
95
  ## Daemon Mode
91
96
 
97
+ ### Background service (recommended)
98
+
99
+ Install as a system service for automatic background syncing:
100
+
101
+ ```bash
102
+ npx @vibe-cafe/vibe-usage daemon install
103
+ ```
104
+
105
+ This creates a user-level service (systemd on Linux, launchd on macOS) that syncs every 5 minutes and starts automatically on login. Manage with:
106
+
107
+ ```bash
108
+ npx @vibe-cafe/vibe-usage daemon status
109
+ npx @vibe-cafe/vibe-usage daemon stop
110
+ npx @vibe-cafe/vibe-usage daemon restart
111
+ npx @vibe-cafe/vibe-usage daemon uninstall
112
+ ```
113
+
114
+ For reliable operation, install globally first: `npm install -g @vibe-cafe/vibe-usage`
115
+
116
+ ### Foreground mode
117
+
92
118
  Run continuous syncing in the foreground (every 5 minutes):
93
119
 
94
120
  ```bash
95
121
  npx @vibe-cafe/vibe-usage daemon
96
122
  ```
97
123
 
98
- Press Ctrl+C to stop. For background use: `nohup npx @vibe-cafe/vibe-usage daemon &`
124
+ Press Ctrl+C to stop.
99
125
 
100
126
  ## License
101
127
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-cafe/vibe-usage",
3
- "version": "0.6.8",
3
+ "version": "0.7.0",
4
4
  "description": "Track your AI coding tool token usage and sync to vibecafe.ai",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,272 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { writeFileSync, unlinkSync, mkdirSync, existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { homedir, platform } from 'node:os';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const SERVICE_NAME = 'vibe-usage';
8
+ const LAUNCHD_LABEL = 'ai.vibecafe.vibe-usage';
9
+
10
+ function detectPlatform() {
11
+ const os = platform();
12
+ if (os === 'linux') {
13
+ if (existsSync('/run/systemd/system')) return 'systemd';
14
+ return null;
15
+ }
16
+ if (os === 'darwin') {
17
+ return 'launchd';
18
+ }
19
+ return null;
20
+ }
21
+
22
+ function resolvePaths() {
23
+ const nodePath = process.execPath;
24
+ const thisFile = fileURLToPath(import.meta.url);
25
+ const binPath = join(thisFile, '..', '..', 'bin', 'vibe-usage.js');
26
+
27
+ // npx cache paths are unstable — service will break when cache is cleared
28
+ const isNpxCache = binPath.includes('.npm/_npx');
29
+
30
+ return { nodePath, binPath, isNpxCache };
31
+ }
32
+
33
+ function getServicePaths(plat) {
34
+ if (plat === 'systemd') {
35
+ const dir = join(homedir(), '.config', 'systemd', 'user');
36
+ return { dir, file: join(dir, `${SERVICE_NAME}.service`) };
37
+ }
38
+ if (plat === 'launchd') {
39
+ const dir = join(homedir(), 'Library', 'LaunchAgents');
40
+ return { dir, file: join(dir, `${LAUNCHD_LABEL}.plist`) };
41
+ }
42
+ return null;
43
+ }
44
+
45
+ function generateSystemdUnit(nodePath, binPath) {
46
+ return `[Unit]
47
+ Description=VibeCafe Usage Tracker
48
+ After=network.target
49
+
50
+ [Service]
51
+ Type=simple
52
+ ExecStart=${nodePath} ${binPath} daemon
53
+ Restart=on-failure
54
+ RestartSec=10
55
+ Environment=NODE_ENV=production
56
+ WorkingDirectory=${homedir()}
57
+
58
+ [Install]
59
+ WantedBy=default.target
60
+ `;
61
+ }
62
+
63
+ function generateLaunchdPlist(nodePath, binPath) {
64
+ const logDir = join(homedir(), '.vibe-usage');
65
+ return `<?xml version="1.0" encoding="UTF-8"?>
66
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
67
+ <plist version="1.0">
68
+ <dict>
69
+ <key>Label</key>
70
+ <string>${LAUNCHD_LABEL}</string>
71
+ <key>ProgramArguments</key>
72
+ <array>
73
+ <string>${nodePath}</string>
74
+ <string>${binPath}</string>
75
+ <string>daemon</string>
76
+ </array>
77
+ <key>RunAtLoad</key>
78
+ <true/>
79
+ <key>KeepAlive</key>
80
+ <true/>
81
+ <key>WorkingDirectory</key>
82
+ <string>${homedir()}</string>
83
+ <key>StandardOutPath</key>
84
+ <string>${join(logDir, 'daemon.log')}</string>
85
+ <key>StandardErrorPath</key>
86
+ <string>${join(logDir, 'daemon.err')}</string>
87
+ <key>EnvironmentVariables</key>
88
+ <dict>
89
+ <key>NODE_ENV</key>
90
+ <string>production</string>
91
+ </dict>
92
+ </dict>
93
+ </plist>
94
+ `;
95
+ }
96
+
97
+ function run(cmd, args) {
98
+ try {
99
+ const output = execFileSync(cmd, args, {
100
+ encoding: 'utf-8',
101
+ stdio: ['pipe', 'pipe', 'pipe'],
102
+ });
103
+ return { ok: true, output: output.trim() };
104
+ } catch (err) {
105
+ return { ok: false, output: (err.stderr || err.stdout || err.message || '').trim() };
106
+ }
107
+ }
108
+
109
+ function install() {
110
+ const plat = detectPlatform();
111
+ if (!plat) {
112
+ console.log('Daemon install is not supported on this platform.');
113
+ console.log('Supported: Linux (systemd), macOS (launchd).');
114
+ return;
115
+ }
116
+
117
+ const { nodePath, binPath, isNpxCache } = resolvePaths();
118
+
119
+ if (isNpxCache) {
120
+ console.log('Warning: vibe-usage appears to be running from the npx cache.');
121
+ console.log('The daemon may break when the cache is cleared.');
122
+ console.log('For reliable operation, install globally first:');
123
+ console.log(' npm install -g @vibe-cafe/vibe-usage\n');
124
+ }
125
+
126
+ const paths = getServicePaths(plat);
127
+
128
+ if (existsSync(paths.file)) {
129
+ console.log('Service is already installed. Run `vibe-usage daemon restart` or `daemon uninstall` first.');
130
+ return;
131
+ }
132
+
133
+ mkdirSync(paths.dir, { recursive: true });
134
+
135
+ if (plat === 'systemd') {
136
+ writeFileSync(paths.file, generateSystemdUnit(nodePath, binPath), 'utf-8');
137
+ console.log(`Created ${paths.file}`);
138
+
139
+ run('systemctl', ['--user', 'daemon-reload']);
140
+ const result = run('systemctl', ['--user', 'enable', '--now', `${SERVICE_NAME}.service`]);
141
+ if (!result.ok) {
142
+ console.error(`Failed to start service: ${result.output}`);
143
+ return;
144
+ }
145
+ console.log('Service enabled and started.');
146
+ }
147
+
148
+ if (plat === 'launchd') {
149
+ mkdirSync(join(homedir(), '.vibe-usage'), { recursive: true });
150
+ writeFileSync(paths.file, generateLaunchdPlist(nodePath, binPath), 'utf-8');
151
+ console.log(`Created ${paths.file}`);
152
+
153
+ const result = run('launchctl', ['load', paths.file]);
154
+ if (!result.ok) {
155
+ console.error(`Failed to load service: ${result.output}`);
156
+ return;
157
+ }
158
+ console.log('Service loaded and started.');
159
+ }
160
+
161
+ console.log('\nDaemon installed. Usage data will sync automatically every 5 minutes.');
162
+ console.log('Run `vibe-usage daemon status` to check.');
163
+ }
164
+
165
+ function uninstall() {
166
+ const plat = detectPlatform();
167
+ if (!plat) {
168
+ console.log('No supported service platform detected.');
169
+ return;
170
+ }
171
+
172
+ const paths = getServicePaths(plat);
173
+
174
+ if (!existsSync(paths.file)) {
175
+ console.log('No daemon service is installed.');
176
+ return;
177
+ }
178
+
179
+ if (plat === 'systemd') {
180
+ run('systemctl', ['--user', 'stop', `${SERVICE_NAME}.service`]);
181
+ run('systemctl', ['--user', 'disable', `${SERVICE_NAME}.service`]);
182
+ unlinkSync(paths.file);
183
+ run('systemctl', ['--user', 'daemon-reload']);
184
+ console.log('Service stopped, disabled, and removed.');
185
+ }
186
+
187
+ if (plat === 'launchd') {
188
+ run('launchctl', ['unload', paths.file]);
189
+ unlinkSync(paths.file);
190
+ console.log('Service unloaded and removed.');
191
+ }
192
+ }
193
+
194
+ function status() {
195
+ const plat = detectPlatform();
196
+ if (!plat) {
197
+ console.log('No supported service platform detected.');
198
+ return;
199
+ }
200
+
201
+ const paths = getServicePaths(plat);
202
+
203
+ if (!existsSync(paths.file)) {
204
+ console.log('No daemon service is installed.');
205
+ console.log('Run `vibe-usage daemon install` to set up.');
206
+ return;
207
+ }
208
+
209
+ if (plat === 'systemd') {
210
+ const result = run('systemctl', ['--user', 'status', `${SERVICE_NAME}.service`]);
211
+ console.log(result.output);
212
+ }
213
+
214
+ if (plat === 'launchd') {
215
+ const result = run('launchctl', ['list', LAUNCHD_LABEL]);
216
+ if (result.ok) {
217
+ console.log(`Service: ${LAUNCHD_LABEL}`);
218
+ console.log(result.output);
219
+ } else {
220
+ console.log('Service is installed but not currently running.');
221
+ }
222
+ }
223
+ }
224
+
225
+ function stop() {
226
+ const plat = detectPlatform();
227
+ if (!plat) {
228
+ console.log('No supported service platform detected.');
229
+ return;
230
+ }
231
+
232
+ if (plat === 'systemd') {
233
+ const result = run('systemctl', ['--user', 'stop', `${SERVICE_NAME}.service`]);
234
+ console.log(result.ok ? 'Service stopped.' : `Failed: ${result.output}`);
235
+ }
236
+
237
+ if (plat === 'launchd') {
238
+ const result = run('launchctl', ['stop', LAUNCHD_LABEL]);
239
+ console.log(result.ok ? 'Service stopped.' : `Failed: ${result.output}`);
240
+ }
241
+ }
242
+
243
+ function restart() {
244
+ const plat = detectPlatform();
245
+ if (!plat) {
246
+ console.log('No supported service platform detected.');
247
+ return;
248
+ }
249
+
250
+ if (plat === 'systemd') {
251
+ const result = run('systemctl', ['--user', 'restart', `${SERVICE_NAME}.service`]);
252
+ console.log(result.ok ? 'Service restarted.' : `Failed: ${result.output}`);
253
+ }
254
+
255
+ if (plat === 'launchd') {
256
+ run('launchctl', ['stop', LAUNCHD_LABEL]);
257
+ const result = run('launchctl', ['start', LAUNCHD_LABEL]);
258
+ console.log(result.ok ? 'Service restarted.' : `Failed: ${result.output}`);
259
+ }
260
+ }
261
+
262
+ const SUBCOMMANDS = { install, uninstall, status, stop, restart };
263
+
264
+ export async function manageDaemon(subcommand) {
265
+ const fn = SUBCOMMANDS[subcommand];
266
+ if (!fn) {
267
+ console.error(`Unknown daemon subcommand: ${subcommand}`);
268
+ console.error('Usage: vibe-usage daemon <install|uninstall|status|stop|restart>');
269
+ process.exit(1);
270
+ }
271
+ fn();
272
+ }
package/src/index.js CHANGED
@@ -109,8 +109,14 @@ export async function run(args) {
109
109
  }
110
110
  case 'daemon':
111
111
  case '--daemon': {
112
- const { runDaemon } = await import('./daemon.js');
113
- await runDaemon();
112
+ const sub = args[1];
113
+ if (sub && ['install', 'uninstall', 'status', 'stop', 'restart'].includes(sub)) {
114
+ const { manageDaemon } = await import('./daemon-service.js');
115
+ await manageDaemon(sub);
116
+ } else {
117
+ const { runDaemon } = await import('./daemon.js');
118
+ await runDaemon();
119
+ }
114
120
  break;
115
121
  }
116
122
  case 'skill': {
@@ -136,7 +142,12 @@ export async function run(args) {
136
142
  npx @vibe-cafe/vibe-usage Init (first run) or sync
137
143
  npx @vibe-cafe/vibe-usage init Set up API key
138
144
  npx @vibe-cafe/vibe-usage sync Manually sync usage data
139
- npx @vibe-cafe/vibe-usage daemon Continuous sync (every 5m)
145
+ npx @vibe-cafe/vibe-usage daemon Continuous sync (every 5m, foreground)
146
+ npx @vibe-cafe/vibe-usage daemon install Install background service (systemd/launchd)
147
+ npx @vibe-cafe/vibe-usage daemon uninstall Remove background service
148
+ npx @vibe-cafe/vibe-usage daemon status Show background service status
149
+ npx @vibe-cafe/vibe-usage daemon stop Stop background service
150
+ npx @vibe-cafe/vibe-usage daemon restart Restart background service
140
151
  npx @vibe-cafe/vibe-usage reset Delete all data and re-upload
141
152
  npx @vibe-cafe/vibe-usage reset --local Delete data for this host only and re-upload
142
153
  npx @vibe-cafe/vibe-usage skill Install skill for AI coding tools
package/src/init.js CHANGED
@@ -75,4 +75,8 @@ export async function runInit() {
75
75
  await runSync();
76
76
 
77
77
  console.log(`\nSetup complete! View your dashboard at: ${apiUrl}/usage`);
78
+
79
+ if (process.platform === 'linux' || process.platform === 'darwin') {
80
+ console.log('\nTip: Run `npx @vibe-cafe/vibe-usage daemon install` to sync automatically in the background.');
81
+ }
78
82
  }