@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 +28 -2
- package/package.json +1 -1
- package/src/daemon-service.js +272 -0
- package/src/index.js +14 -3
- package/src/init.js +4 -0
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
|
|
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.
|
|
124
|
+
Press Ctrl+C to stop.
|
|
99
125
|
|
|
100
126
|
## License
|
|
101
127
|
|
package/package.json
CHANGED
|
@@ -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
|
|
113
|
-
|
|
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
|
}
|