promptcase 1.0.4

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 ADDED
@@ -0,0 +1,67 @@
1
+ # PromptCase CLI
2
+
3
+ A background daemon that captures AI prompts you send to Claude Code and syncs them to your [PromptCase](https://promptcase.app) account for later search, tagging, and organisation.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g promptcase
9
+ ```
10
+
11
+ Requires **Node.js 20 or newer**.
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ # 1. Authenticate (opens browser, then auto-starts the daemon)
17
+ promptcase init
18
+
19
+ # 2. Check status
20
+ promptcase status
21
+
22
+ # 3. Force a sync
23
+ promptcase sync
24
+
25
+ # 4. View recent prompts
26
+ promptcase show
27
+ ```
28
+
29
+ The daemon runs in the background and auto-starts on system boot (LaunchAgent on macOS, user-level systemd on Linux, Startup folder on Windows).
30
+
31
+ ## Commands
32
+
33
+ | Command | Description |
34
+ | --- | --- |
35
+ | `promptcase init` | Authenticate and start the daemon |
36
+ | `promptcase status` | Show auth, daemon, and auto-start status |
37
+ | `promptcase sync` | Force a sync and run diagnostics |
38
+ | `promptcase show` | Show recent synced prompts |
39
+ | `promptcase stop` | Stop the running daemon (auto-start preserved) |
40
+ | `promptcase logout` | Stop daemon, revoke token, clear credentials |
41
+
42
+ ## Non-interactive authentication
43
+
44
+ Pipe a token JSON to `init`:
45
+
46
+ ```bash
47
+ echo '{"access_token":"...","refresh_token":"...","expires_in":15552000}' | promptcase init
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ Config and credentials are stored in `~/.promptcase/`. Logs:
53
+
54
+ - `~/.promptcase/daemon.log` (stdout)
55
+ - `~/.promptcase/daemon.error.log` (stderr)
56
+
57
+ ## Troubleshooting
58
+
59
+ ```bash
60
+ promptcase status # overall health
61
+ promptcase sync # diagnose and force a refresh
62
+ promptcase logout && promptcase init # reset credentials
63
+ ```
64
+
65
+ ## License
66
+
67
+ MIT
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Init command - Authenticate with PromptCase
3
+ *
4
+ * Behavior:
5
+ * - If authenticated: show status, verify token, ensure daemon is running, setup auto-start
6
+ * - If not authenticated: start auth flow, then auto-start daemon and setup auto-start
7
+ */
8
+ import inquirer from 'inquirer';
9
+ import { Command } from 'commander';
10
+ import { exec, spawn } from 'child_process';
11
+ import { APIService } from '../services/api.js';
12
+ import { DaemonService } from '../services/daemon.js';
13
+ import { getConfig } from '../lib/config.js';
14
+ import { scriptPath } from '../lib/path.js';
15
+ import { DEFAULT_API_URL, CLI_TOKEN_URL } from '../lib/constants.js';
16
+ /**
17
+ * Open URL in default browser (cross-platform)
18
+ * Returns true if the browser was launched, false on failure.
19
+ */
20
+ function openBrowser(url) {
21
+ const cmd = process.platform === 'darwin' ? 'open' :
22
+ process.platform === 'win32' ? 'start' : 'xdg-open';
23
+ try {
24
+ exec(`${cmd} "${url}"`, (error) => {
25
+ if (error) {
26
+ console.log(` āš ļø Could not open browser automatically. Open this URL manually:\n ${url}\n`);
27
+ }
28
+ });
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /**
36
+ * Check if already authenticated and show status
37
+ */
38
+ async function checkAuthStatus() {
39
+ const config = getConfig();
40
+ const isAuthenticated = await config.isAuthenticated();
41
+ if (isAuthenticated) {
42
+ const credentials = await config.getCredentials();
43
+ return { isAuthenticated: true, credentials: credentials || undefined };
44
+ }
45
+ return { isAuthenticated: false };
46
+ }
47
+ /**
48
+ * Save token after validation
49
+ */
50
+ async function saveToken(tokenJson, api, config) {
51
+ try {
52
+ const tokens = JSON.parse(tokenJson);
53
+ if (!tokens.access_token || !tokens.refresh_token) {
54
+ console.error('āŒ Invalid token format. Both access_token and refresh_token are required.');
55
+ return false;
56
+ }
57
+ if (!tokens.access_token.startsWith('pc_tok_') || !tokens.refresh_token.startsWith('pc_ref_')) {
58
+ console.error('āŒ Invalid token format. Tokens should start with pc_tok_ and pc_ref_');
59
+ return false;
60
+ }
61
+ const expiresAt = new Date(Date.now() + (tokens.expires_in || 15552000) * 1000);
62
+ const credentials = {
63
+ accessToken: tokens.access_token,
64
+ refreshToken: tokens.refresh_token,
65
+ expiresAt: expiresAt.toISOString(),
66
+ createdAt: new Date().toISOString(),
67
+ };
68
+ await config.setCredentials(credentials);
69
+ api.setTokens(credentials.accessToken, credentials.refreshToken);
70
+ return true;
71
+ }
72
+ catch (error) {
73
+ console.error('āŒ Invalid JSON format:', error.message);
74
+ return false;
75
+ }
76
+ }
77
+ /**
78
+ * Verify token and fetch user info
79
+ */
80
+ async function verifyAndShowUser(api) {
81
+ const userInfo = await api.verifyToken();
82
+ if (userInfo) {
83
+ console.log(` User-ID: ${userInfo.userId}`);
84
+ console.log(` Device: ${userInfo.deviceName} (${userInfo.deviceType})`);
85
+ }
86
+ else {
87
+ console.log(' āš ļø Token validation failed (but tokens stored)');
88
+ }
89
+ }
90
+ /**
91
+ * Start daemon in detached background process
92
+ */
93
+ async function startDaemonDetached(apiUrl) {
94
+ // Detach the daemon process so it runs independently
95
+ const child = spawn(process.execPath, [scriptPath(), 'start'], {
96
+ detached: true,
97
+ stdio: 'ignore',
98
+ windowsHide: true,
99
+ });
100
+ child.unref();
101
+ // Give it a moment to start
102
+ await new Promise(resolve => setTimeout(resolve, 1500));
103
+ // Verify it started
104
+ const daemon = new DaemonService(apiUrl);
105
+ const isRunning = await daemon.isRunning();
106
+ if (isRunning) {
107
+ console.log(' āœ… Daemon started in background');
108
+ }
109
+ else {
110
+ console.log(' āš ļø Daemon may not have started. Check with "promptcase status"');
111
+ }
112
+ }
113
+ /**
114
+ * Main init flow - handles authentication and daemon startup
115
+ */
116
+ export async function initFlow(apiUrl = DEFAULT_API_URL) {
117
+ const config = getConfig();
118
+ const api = new APIService(apiUrl);
119
+ console.log('\nšŸ” PromptCase CLI\n');
120
+ // Check if already authenticated
121
+ const { isAuthenticated, credentials } = await checkAuthStatus();
122
+ if (isAuthenticated && credentials) {
123
+ const expiresAt = new Date(credentials.expiresAt);
124
+ const daysLeft = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
125
+ console.log(' Status: āœ… Authenticated');
126
+ console.log(` Access Token: ${credentials.accessToken.slice(0, 20)}...`);
127
+ console.log(` Refresh Token: ${credentials.refreshToken.slice(0, 20)}...`);
128
+ console.log(` Expires: ${expiresAt.toLocaleDateString()} (${daysLeft} days left)\n`);
129
+ // Verify token and get user info
130
+ api.setTokens(credentials.accessToken, credentials.refreshToken);
131
+ await verifyAndShowUser(api);
132
+ // Check if daemon is running
133
+ const daemon = new DaemonService(apiUrl);
134
+ const daemonRunning = await daemon.isRunning();
135
+ if (!daemonRunning) {
136
+ console.log('\n Starting daemon in background...\n');
137
+ // Start daemon detached (in background)
138
+ await startDaemonDetached(apiUrl);
139
+ }
140
+ else {
141
+ console.log('\n Daemon is already running');
142
+ }
143
+ // Setup auto-start on boot if not already set up
144
+ console.log('\n Setting up auto-start on system boot...');
145
+ const autoStartSuccess = await daemon.setupAutoStart();
146
+ if (autoStartSuccess) {
147
+ console.log(' āœ… Auto-start configured');
148
+ }
149
+ else {
150
+ console.log(' āš ļø Auto-start setup failed (daemon will not auto-restart on reboot)');
151
+ }
152
+ console.log('\n Run "promptcase status" to check daemon status');
153
+ console.log(' Run "promptcase sync" to manually trigger a sync');
154
+ console.log(' Run "promptcase stop" to stop the daemon');
155
+ console.log(' Run "promptcase logout" to clear credentials\n');
156
+ return;
157
+ }
158
+ // Not authenticated - start auth flow
159
+ console.log(' Status: šŸ”“ Not authenticated\n');
160
+ try {
161
+ const { choice } = await inquirer.prompt([
162
+ {
163
+ type: 'list',
164
+ name: 'choice',
165
+ message: 'How would you like to authenticate?',
166
+ choices: [
167
+ { name: '🌐 Open browser to generate token (Recommended)', value: 'browser' },
168
+ { name: 'šŸ“‹ Paste token JSON manually', value: 'manual' },
169
+ ],
170
+ },
171
+ ]);
172
+ let tokenJson;
173
+ if (choice === 'browser') {
174
+ console.log('\n Opening browser to generate CLI token...\n');
175
+ console.log(` ${CLI_TOKEN_URL}\n`);
176
+ openBrowser(CLI_TOKEN_URL);
177
+ console.log(' (If the browser did not open automatically, copy the URL above.)\n');
178
+ const { proceed } = await inquirer.prompt([
179
+ {
180
+ type: 'confirm',
181
+ name: 'proceed',
182
+ message: 'Press Enter after you have copied the token (or "n" to cancel)',
183
+ default: true,
184
+ },
185
+ ]);
186
+ if (!proceed) {
187
+ console.log('\n Cancelled. You can run "promptcase init" again later.\n');
188
+ return;
189
+ }
190
+ const answer = await inquirer.prompt([
191
+ {
192
+ type: 'input',
193
+ name: 'tokenJson',
194
+ message: 'Paste the copied token JSON:',
195
+ validate: (input) => {
196
+ try {
197
+ const parsed = JSON.parse(input);
198
+ if (!parsed.access_token || !parsed.refresh_token) {
199
+ return 'Invalid token format. Both access_token and refresh_token are required.';
200
+ }
201
+ return true;
202
+ }
203
+ catch {
204
+ return 'Invalid JSON format';
205
+ }
206
+ },
207
+ },
208
+ ]);
209
+ tokenJson = answer.tokenJson;
210
+ }
211
+ else {
212
+ const answer = await inquirer.prompt([
213
+ {
214
+ type: 'input',
215
+ name: 'tokenJson',
216
+ message: 'Paste token JSON:',
217
+ validate: (input) => {
218
+ try {
219
+ const parsed = JSON.parse(input);
220
+ if (!parsed.access_token || !parsed.refresh_token) {
221
+ return 'Invalid token format. Both access_token and refresh_token are required.';
222
+ }
223
+ return true;
224
+ }
225
+ catch {
226
+ return 'Invalid JSON format';
227
+ }
228
+ },
229
+ },
230
+ ]);
231
+ tokenJson = answer.tokenJson;
232
+ }
233
+ // Save tokens
234
+ const saved = await saveToken(tokenJson, api, config);
235
+ if (!saved) {
236
+ process.exit(1);
237
+ }
238
+ console.log('\n āœ… Authentication successful!\n');
239
+ // Verify and show user info
240
+ const tokens = JSON.parse(tokenJson);
241
+ api.setTokens(tokens.access_token, tokens.refresh_token);
242
+ await verifyAndShowUser(api);
243
+ // Auto-start daemon
244
+ console.log('\n Starting daemon in background...\n');
245
+ await startDaemonDetached(apiUrl);
246
+ // Setup auto-start on boot
247
+ console.log(' Setting up auto-start on system boot...');
248
+ const daemon = new DaemonService(apiUrl);
249
+ const autoStartSuccess = await daemon.setupAutoStart();
250
+ if (autoStartSuccess) {
251
+ console.log(' āœ… Auto-start configured');
252
+ }
253
+ else {
254
+ console.log(' āš ļø Auto-start setup failed');
255
+ }
256
+ console.log('\n āœ… Setup complete!');
257
+ console.log('\n Commands:');
258
+ console.log(' promptcase status Check daemon status');
259
+ console.log(' promptcase sync Manually trigger a sync');
260
+ console.log(' promptcase stop Stop the daemon');
261
+ console.log(' promptcase logout Clear credentials\n');
262
+ }
263
+ catch (error) {
264
+ if (error.isTtyError) {
265
+ console.log('Opening browser for token generation...\n');
266
+ openBrowser(CLI_TOKEN_URL);
267
+ console.log('Please copy the token and run: promptcase init --token <json>\n');
268
+ }
269
+ else {
270
+ console.error('āŒ Authentication failed:', error.message);
271
+ }
272
+ process.exit(1);
273
+ }
274
+ }
275
+ /**
276
+ * Non-interactive auth handler for piped input
277
+ */
278
+ export function handleNonInteractiveToken(tokenJson, apiUrl = DEFAULT_API_URL) {
279
+ const config = getConfig();
280
+ const api = new APIService(apiUrl);
281
+ saveToken(tokenJson, api, config).then(async (saved) => {
282
+ if (!saved) {
283
+ process.exit(1);
284
+ }
285
+ console.log('\nāœ… Authentication successful!\n');
286
+ try {
287
+ const tokens = JSON.parse(tokenJson);
288
+ api.setTokens(tokens.access_token, tokens.refresh_token);
289
+ await verifyAndShowUser(api);
290
+ // Auto-start daemon
291
+ console.log('\n Starting daemon in background...');
292
+ await startDaemonDetached(apiUrl);
293
+ // Setup auto-start
294
+ console.log(' Setting up auto-start on system boot...');
295
+ const daemon = new DaemonService(apiUrl);
296
+ const autoStartSuccess = await daemon.setupAutoStart();
297
+ if (autoStartSuccess) {
298
+ console.log(' āœ… Auto-start configured');
299
+ }
300
+ else {
301
+ console.log(' āš ļø Auto-start setup failed');
302
+ }
303
+ console.log('\n āœ… Setup complete!\n');
304
+ }
305
+ catch (error) {
306
+ console.error('Setup error:', error.message);
307
+ }
308
+ });
309
+ }
310
+ export function createInitCommand() {
311
+ const command = new Command('init');
312
+ command
313
+ .description('Authenticate with PromptCase and start the daemon')
314
+ .action(async () => {
315
+ await initFlow();
316
+ });
317
+ return command;
318
+ }
319
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Logout command - Clear local credentials and invalidate token on server
3
+ */
4
+ import { Command } from 'commander';
5
+ import { APIService } from '../services/api.js';
6
+ import { getConfig } from '../lib/config.js';
7
+ import { DaemonService } from '../services/daemon.js';
8
+ import { DEFAULT_API_URL } from '../lib/constants.js';
9
+ export function createLogoutCommand(apiUrl = DEFAULT_API_URL) {
10
+ const command = new Command('logout');
11
+ command
12
+ .description('Clear credentials and invalidate CLI token on server')
13
+ .action(async () => {
14
+ const config = getConfig();
15
+ const api = new APIService(apiUrl);
16
+ const daemon = new DaemonService(apiUrl);
17
+ console.log('\nšŸ”’ Logging out of PromptCase\n');
18
+ // Check if there's anything to logout
19
+ const hasCreds = await config.hasCredentials();
20
+ if (!hasCreds) {
21
+ console.log(' No active authentication found.\n');
22
+ return;
23
+ }
24
+ const credentials = await config.getCredentials();
25
+ if (!credentials) {
26
+ await config.clearAll();
27
+ console.log(' āœ… Cleared local credentials.\n');
28
+ return;
29
+ }
30
+ // Stop daemon if running
31
+ const isRunning = await daemon.isRunning();
32
+ if (isRunning) {
33
+ console.log(' Stopping daemon...');
34
+ await daemon.stop();
35
+ console.log(' āœ… Daemon stopped');
36
+ }
37
+ // Try to invalidate token on server
38
+ console.log(' Invalidating token on server...');
39
+ api.setTokens(credentials.accessToken, credentials.refreshToken);
40
+ const revoked = await api.revokeDevice();
41
+ if (revoked) {
42
+ console.log(' āœ… Token revoked on server');
43
+ }
44
+ else {
45
+ console.log(' āš ļø Could not revoke token on server (will expire naturally)');
46
+ }
47
+ // Clear all local data
48
+ await config.clearAll();
49
+ console.log(' āœ… Local credentials cleared');
50
+ // Note: Auto-start is removed automatically since the daemon is stopped and we cleared data
51
+ // But we should leave the auto-start config so re-running init re-enables it
52
+ console.log('\n āœ… Logged out successfully!');
53
+ console.log(' Run "promptcase init" to authenticate again.\n');
54
+ });
55
+ return command;
56
+ }
57
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Show command - Display recent synced prompts
3
+ */
4
+ import { Command } from 'commander';
5
+ import { APIService } from '../services/api.js';
6
+ import { getConfig } from '../lib/config.js';
7
+ import { DEFAULT_API_URL } from '../lib/constants.js';
8
+ export function createShowCommand(apiUrl = DEFAULT_API_URL) {
9
+ const command = new Command('show');
10
+ command
11
+ .description('Show recent prompts synced to PromptCase')
12
+ .option('-n, --limit <number>', 'Number of prompts to show', '15')
13
+ .option('-q, --quiet', 'Only show prompt content, no metadata')
14
+ .action(async (options) => {
15
+ const config = getConfig();
16
+ const api = new APIService(apiUrl);
17
+ console.log('\nšŸ“ Recent Prompts\n');
18
+ // Check authentication
19
+ if (!(await config.isAuthenticated())) {
20
+ console.log('āŒ Not authenticated. Run "promptcase init" first.');
21
+ return;
22
+ }
23
+ // Get credentials
24
+ const credentials = await config.getCredentials();
25
+ if (!credentials) {
26
+ console.log('āŒ No credentials found. Run "promptcase init" first.');
27
+ return;
28
+ }
29
+ api.setTokens(credentials.accessToken, credentials.refreshToken);
30
+ try {
31
+ const limit = parseInt(options.limit, 10) || 15;
32
+ const prompts = await api.getRecentPrompts(limit);
33
+ if (prompts.length === 0) {
34
+ console.log('No prompts synced yet.\n');
35
+ return;
36
+ }
37
+ prompts.forEach((prompt, index) => {
38
+ const date = new Date(prompt.created_at);
39
+ const source = prompt.source || 'unknown';
40
+ if (options.quiet) {
41
+ console.log(`[${index + 1}] ${prompt.content.slice(0, 200)}${prompt.content.length > 200 ? '...' : ''}`);
42
+ }
43
+ else {
44
+ console.log(`ā”Œā”€ ${index + 1}. ${prompt.title || 'Untitled'}`);
45
+ console.log(`│ Source: ${source}`);
46
+ console.log(`│ Date: ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`);
47
+ console.log(`│ ${prompt.content.slice(0, 150)}${prompt.content.length > 150 ? '...' : ''}`);
48
+ console.log(`└────────────────────────────────────────`);
49
+ }
50
+ });
51
+ console.log(`\nShowing ${prompts.length} of ${prompts.length} prompts\n`);
52
+ }
53
+ catch (error) {
54
+ console.log(`āŒ Failed to fetch prompts: ${error.message}`);
55
+ }
56
+ });
57
+ return command;
58
+ }
59
+ //# sourceMappingURL=show.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Start command - Run daemon in foreground (used by auto-start services)
3
+ *
4
+ * This is the entry point for auto-start services on all platforms.
5
+ * It runs the daemon in foreground and only exits when killed.
6
+ */
7
+ import { Command } from 'commander';
8
+ import { DaemonService } from '../services/daemon.js';
9
+ import { DEFAULT_API_URL, DAEMON_PROCESS_TITLE } from '../lib/constants.js';
10
+ export function createStartCommand(apiUrl = DEFAULT_API_URL) {
11
+ const command = new Command('start');
12
+ command
13
+ .description('Run daemon in foreground (used by auto-start services)')
14
+ .action(async () => {
15
+ // Rename this process for `ps`/Activity Monitor so users see
16
+ // "PromptCase Daemon" instead of "node". Note: the macOS Settings
17
+ // → Privacy & Security panel still shows the original terminal
18
+ // session that spawned this process for a brief window — the only
19
+ // way to override that is to ship a signed .app bundle, which
20
+ // requires an Apple Developer ID ($99/year).
21
+ process.title = DAEMON_PROCESS_TITLE;
22
+ const daemon = new DaemonService(apiUrl);
23
+ // Check auth and ensure we have credentials
24
+ const initialized = await daemon.initialize();
25
+ if (!initialized) {
26
+ // Silently exit if not authenticated - service manager will retry on next boot
27
+ process.exit(0);
28
+ }
29
+ // Start daemon in foreground (never returns unless killed)
30
+ await daemon.start();
31
+ });
32
+ return command;
33
+ }
34
+ //# sourceMappingURL=start.js.map
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Status command - Show authentication, daemon, and sync status
3
+ */
4
+ import { Command } from 'commander';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { APIService } from '../services/api.js';
9
+ import { DaemonService } from '../services/daemon.js';
10
+ import { getConfig } from '../lib/config.js';
11
+ import { DEFAULT_API_URL } from '../lib/constants.js';
12
+ export function createStatusCommand(apiUrl = DEFAULT_API_URL) {
13
+ const command = new Command('status');
14
+ command
15
+ .description('Show authentication, daemon, and sync status')
16
+ .action(async () => {
17
+ const config = getConfig();
18
+ const api = new APIService(apiUrl);
19
+ const daemon = new DaemonService(apiUrl);
20
+ console.log('\nšŸ“Š PromptCase Status\n');
21
+ // --- Authentication Status ---
22
+ const isAuthenticated = await config.isAuthenticated();
23
+ const credentials = await config.getCredentials();
24
+ console.log('šŸ” Authentication:');
25
+ if (isAuthenticated && credentials) {
26
+ const expiresAt = new Date(credentials.expiresAt);
27
+ const daysLeft = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
28
+ api.setTokens(credentials.accessToken, credentials.refreshToken);
29
+ console.log(' Status: āœ… Authenticated');
30
+ console.log(` Access Token: ${credentials.accessToken.slice(0, 24)}...`);
31
+ console.log(` Refresh Token: ${credentials.refreshToken.slice(0, 24)}...`);
32
+ console.log(` Expires: ${expiresAt.toLocaleDateString()} (${daysLeft} days left)`);
33
+ // Get user info from API
34
+ const userInfo = await api.verifyToken();
35
+ if (userInfo) {
36
+ console.log(` User-ID: ${userInfo.userId}`);
37
+ console.log(` Device: ${userInfo.deviceName}`);
38
+ }
39
+ else {
40
+ console.log(` User-ID: āš ļø Could not verify token`);
41
+ }
42
+ }
43
+ else {
44
+ const hasCreds = await config.hasCredentials();
45
+ if (hasCreds) {
46
+ console.log(' Status: āš ļø Token expired - run "promptcase init" to refresh');
47
+ }
48
+ else {
49
+ console.log(' Status: šŸ”“ Not authenticated');
50
+ console.log(' Run "promptcase init" to authenticate');
51
+ }
52
+ }
53
+ // --- Daemon Status ---
54
+ const isRunning = await daemon.isRunning();
55
+ const daemonStatus = await config.getDaemonStatus();
56
+ const lifetimeSynced = daemonStatus.promptsSynced ?? 0;
57
+ console.log('\nšŸ”„ Daemon:');
58
+ if (isRunning) {
59
+ console.log(' Status: 🟢 Running');
60
+ console.log(` PID: ${daemonStatus.pid || process.pid}`);
61
+ console.log(` Started: ${daemonStatus.startedAt ? new Date(daemonStatus.startedAt).toLocaleString() : 'Unknown'}`);
62
+ const lastSync = daemonStatus.lastSyncAt;
63
+ if (lastSync) {
64
+ const lastSyncDate = new Date(lastSync);
65
+ const agoMinutes = Math.floor((Date.now() - lastSyncDate.getTime()) / 60000);
66
+ const agoText = agoMinutes < 1 ? 'just now' :
67
+ agoMinutes < 60 ? `${agoMinutes} min ago` :
68
+ agoMinutes < 1440 ? `${Math.floor(agoMinutes / 60)} hr ago` :
69
+ `${Math.floor(agoMinutes / 1440)} days ago`;
70
+ console.log(` Last sync: ${lastSyncDate.toLocaleString()} (${agoText})`);
71
+ console.log(` Total synced: ${lifetimeSynced} prompts (lifetime)`);
72
+ }
73
+ else {
74
+ console.log(' Last sync: Never');
75
+ console.log(` Total synced: ${lifetimeSynced} prompts (lifetime)`);
76
+ }
77
+ if (daemonStatus.nextSyncAt) {
78
+ const nextSyncDate = new Date(daemonStatus.nextSyncAt);
79
+ console.log(` Next sync: ${nextSyncDate.toLocaleString()}`);
80
+ }
81
+ }
82
+ else {
83
+ console.log(' Status: šŸ”“ Not running');
84
+ if (isAuthenticated) {
85
+ console.log(' Run "promptcase init" to start the daemon');
86
+ }
87
+ }
88
+ // --- Auto-Start Status ---
89
+ console.log('\nšŸš€ Auto-start:');
90
+ const platform = process.platform === 'darwin' ? 'macOS' :
91
+ process.platform === 'win32' ? 'Windows' : 'Linux';
92
+ console.log(` Platform: ${platform}`);
93
+ // Check if auto-start is configured
94
+ if (process.platform === 'darwin') {
95
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'app.promptcase.daemon.plist');
96
+ if (fs.existsSync(plistPath)) {
97
+ console.log(' Status: āœ… Configured');
98
+ }
99
+ else {
100
+ console.log(' Status: šŸ”“ Not configured');
101
+ }
102
+ }
103
+ else if (process.platform === 'win32') {
104
+ const batchPath = path.join(os.homedir(), 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', 'promptcase.bat');
105
+ if (fs.existsSync(batchPath)) {
106
+ console.log(' Status: āœ… Configured');
107
+ }
108
+ else {
109
+ console.log(' Status: šŸ”“ Not configured');
110
+ }
111
+ }
112
+ else {
113
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'promptcase.service');
114
+ if (fs.existsSync(servicePath)) {
115
+ console.log(' Status: āœ… Configured');
116
+ }
117
+ else {
118
+ console.log(' Status: šŸ”“ Not configured');
119
+ console.log(' Enable with: systemctl --user enable promptcase');
120
+ }
121
+ }
122
+ console.log('');
123
+ });
124
+ return command;
125
+ }
126
+ //# sourceMappingURL=status.js.map