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 +67 -0
- package/dist/commands/init.js +319 -0
- package/dist/commands/logout.js +57 -0
- package/dist/commands/show.js +59 -0
- package/dist/commands/start.js +34 -0
- package/dist/commands/status.js +126 -0
- package/dist/commands/stop.js +43 -0
- package/dist/commands/sync.js +117 -0
- package/dist/index.js +94 -0
- package/dist/lib/config.js +132 -0
- package/dist/lib/constants.js +18 -0
- package/dist/lib/path.js +26 -0
- package/dist/services/api.js +211 -0
- package/dist/services/claude-capture.js +368 -0
- package/dist/services/daemon.js +432 -0
- package/dist/types/index.js +13 -0
- package/package.json +44 -0
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
|