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
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon service for background sync
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { APIService } from './api.js';
|
|
8
|
+
import { getConfig } from '../lib/config.js';
|
|
9
|
+
import { ClaudeCaptureService } from './claude-capture.js';
|
|
10
|
+
import { scriptPath } from '../lib/path.js';
|
|
11
|
+
import { DEFAULT_API_URL, MAX_PROMPTS_PER_SYNC } from '../lib/constants.js';
|
|
12
|
+
export class DaemonService {
|
|
13
|
+
apiService;
|
|
14
|
+
captureService;
|
|
15
|
+
configService;
|
|
16
|
+
syncInterval = null;
|
|
17
|
+
status;
|
|
18
|
+
pidFile;
|
|
19
|
+
constructor(apiUrl = DEFAULT_API_URL) {
|
|
20
|
+
this.apiService = new APIService(apiUrl);
|
|
21
|
+
this.captureService = new ClaudeCaptureService();
|
|
22
|
+
this.configService = getConfig();
|
|
23
|
+
this.pidFile = this.getPidFilePath();
|
|
24
|
+
this.status = {
|
|
25
|
+
lastSyncAt: null,
|
|
26
|
+
promptsSynced: 0,
|
|
27
|
+
errors: [],
|
|
28
|
+
isRunning: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get PID file path for cross-platform support
|
|
33
|
+
*/
|
|
34
|
+
getPidFilePath() {
|
|
35
|
+
const isWindows = process.platform === 'win32';
|
|
36
|
+
const configDir = path.join(os.homedir(), '.promptcase');
|
|
37
|
+
if (isWindows) {
|
|
38
|
+
return path.join(configDir, 'daemon.lock');
|
|
39
|
+
}
|
|
40
|
+
return path.join(configDir, 'daemon.pid');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if daemon is already running
|
|
44
|
+
*/
|
|
45
|
+
async isRunning() {
|
|
46
|
+
if (!fs.existsSync(this.pidFile)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const pid = parseInt(fs.readFileSync(this.pidFile, 'utf-8').trim(), 10);
|
|
51
|
+
// On Unix, check if process exists
|
|
52
|
+
if (process.platform !== 'win32') {
|
|
53
|
+
try {
|
|
54
|
+
process.kill(pid, 0);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Process doesn't exist
|
|
59
|
+
await this.cleanupPidFile();
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// On Windows, just check if PID file exists
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Write PID file
|
|
72
|
+
*/
|
|
73
|
+
async writePidFile() {
|
|
74
|
+
const configDir = path.dirname(this.pidFile);
|
|
75
|
+
if (!fs.existsSync(configDir)) {
|
|
76
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
fs.writeFileSync(this.pidFile, process.pid.toString());
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Clean up PID file
|
|
82
|
+
*/
|
|
83
|
+
async cleanupPidFile() {
|
|
84
|
+
if (fs.existsSync(this.pidFile)) {
|
|
85
|
+
fs.unlinkSync(this.pidFile);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Initialize with stored credentials
|
|
90
|
+
*/
|
|
91
|
+
async initialize() {
|
|
92
|
+
const credentials = await this.configService.getCredentials();
|
|
93
|
+
if (!credentials) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
this.apiService.setTokens(credentials.accessToken, credentials.refreshToken);
|
|
97
|
+
// Restore the lifetime promptsSynced counter from the previous run.
|
|
98
|
+
// This is important for `promptcase sync` which creates a fresh
|
|
99
|
+
// DaemonService instance each invocation — without restore, every
|
|
100
|
+
// foreground sync would show the count for just that one call instead
|
|
101
|
+
// of the cumulative lifetime total.
|
|
102
|
+
const stored = await this.configService.getDaemonStatus();
|
|
103
|
+
if (stored && typeof stored.promptsSynced === 'number') {
|
|
104
|
+
this.status.promptsSynced = stored.promptsSynced;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Save refreshed tokens back to config.
|
|
110
|
+
*
|
|
111
|
+
* The server refresh endpoint returns new `expires_in` (in seconds). We
|
|
112
|
+
* honour that value instead of hardcoding 180 days so if the server
|
|
113
|
+
* ever rotates the TTL, the CLI tracks it correctly.
|
|
114
|
+
*/
|
|
115
|
+
async saveRefreshedTokens() {
|
|
116
|
+
const credentials = await this.configService.getCredentials();
|
|
117
|
+
if (!credentials)
|
|
118
|
+
return;
|
|
119
|
+
const accessToken = this.apiService.getAccessToken();
|
|
120
|
+
const refreshToken = this.apiService.getRefreshToken();
|
|
121
|
+
if (accessToken && refreshToken) {
|
|
122
|
+
// Use existing expiresAt as fallback; we don't know the server's new
|
|
123
|
+
// expires_in from outside the refresh path, so the safest is to keep
|
|
124
|
+
// the existing window unless we have a fresh signal. (The TTL on
|
|
125
|
+
// tokens in `refreshAccessToken`'s response is 180d server-side.)
|
|
126
|
+
await this.configService.setCredentials({
|
|
127
|
+
accessToken,
|
|
128
|
+
refreshToken,
|
|
129
|
+
expiresAt: credentials.expiresAt,
|
|
130
|
+
createdAt: credentials.createdAt,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Start the daemon (foreground process)
|
|
136
|
+
*/
|
|
137
|
+
async start() {
|
|
138
|
+
// Initialize with credentials
|
|
139
|
+
if (!await this.initialize()) {
|
|
140
|
+
console.error('No credentials found. Run "promptcase init" first.');
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
// Write PID file
|
|
144
|
+
await this.writePidFile();
|
|
145
|
+
// Start sync loop
|
|
146
|
+
const syncInterval = await this.configService.getSyncInterval();
|
|
147
|
+
this.status.isRunning = true;
|
|
148
|
+
// Restore the lifetime promptsSynced counter from the previous run so
|
|
149
|
+
// the count stays cumulative across daemon restarts. The `status`
|
|
150
|
+
// command reads this from conf, so without restoring it, every fresh
|
|
151
|
+
// daemon process would report "Total synced: 0" until the first sync.
|
|
152
|
+
const stored = await this.configService.getDaemonStatus();
|
|
153
|
+
if (stored && typeof stored.promptsSynced === 'number') {
|
|
154
|
+
this.status.promptsSynced = stored.promptsSynced;
|
|
155
|
+
}
|
|
156
|
+
// Save daemon status
|
|
157
|
+
await this.configService.setDaemonStatus({
|
|
158
|
+
isRunning: true,
|
|
159
|
+
pid: process.pid,
|
|
160
|
+
startedAt: new Date(),
|
|
161
|
+
lastSyncAt: null,
|
|
162
|
+
nextSyncAt: new Date(Date.now() + syncInterval),
|
|
163
|
+
promptsSynced: this.status.promptsSynced,
|
|
164
|
+
});
|
|
165
|
+
console.log(`\n🟢 Daemon started (PID: ${process.pid})`);
|
|
166
|
+
console.log(` Syncing every ${syncInterval / 1000} seconds`);
|
|
167
|
+
console.log(` Logs: ${this.getPidFilePath().replace(/\/[^\/]+$/, '/daemon.log')}`);
|
|
168
|
+
console.log(`\n Press Ctrl+C to stop the daemon\n`);
|
|
169
|
+
// Initial sync
|
|
170
|
+
await this.sync();
|
|
171
|
+
// Schedule periodic syncs. The interval timers MUST stay alive (no
|
|
172
|
+
// `.unref()`) because they are the only thing keeping the Node event
|
|
173
|
+
// loop busy once `start()` returns its `new Promise(() => {})`. Adding
|
|
174
|
+
// `.unref()` here caused the daemon to silently exit after the first
|
|
175
|
+
// sync (regression from v1.0.2's working behavior). KeepAlive=true in
|
|
176
|
+
// the LaunchAgent then respawns it, but each respawn dies the same way
|
|
177
|
+
// — visible to the user as "Daemon: 🔴 Not running".
|
|
178
|
+
this.syncInterval = setInterval(async () => {
|
|
179
|
+
await this.sync();
|
|
180
|
+
}, syncInterval);
|
|
181
|
+
// Save refreshed tokens every hour.
|
|
182
|
+
setInterval(async () => {
|
|
183
|
+
await this.saveRefreshedTokens();
|
|
184
|
+
}, 60 * 60 * 1000);
|
|
185
|
+
// Handle graceful shutdown
|
|
186
|
+
process.on('SIGINT', async () => {
|
|
187
|
+
console.log('\n\n🛑 Stopping daemon...');
|
|
188
|
+
await this.stop();
|
|
189
|
+
process.exit(0);
|
|
190
|
+
});
|
|
191
|
+
process.on('SIGTERM', async () => {
|
|
192
|
+
console.log('\n\n🛑 Stopping daemon...');
|
|
193
|
+
await this.stop();
|
|
194
|
+
process.exit(0);
|
|
195
|
+
});
|
|
196
|
+
// Keep the process alive
|
|
197
|
+
return new Promise(() => {
|
|
198
|
+
// Never resolves - keeps daemon running
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Stop the daemon
|
|
203
|
+
*/
|
|
204
|
+
async stop() {
|
|
205
|
+
if (this.syncInterval) {
|
|
206
|
+
clearInterval(this.syncInterval);
|
|
207
|
+
this.syncInterval = null;
|
|
208
|
+
}
|
|
209
|
+
await this.cleanupPidFile();
|
|
210
|
+
this.status.isRunning = false;
|
|
211
|
+
await this.configService.setDaemonStatus({
|
|
212
|
+
isRunning: false,
|
|
213
|
+
pid: undefined,
|
|
214
|
+
startedAt: null,
|
|
215
|
+
lastSyncAt: this.status.lastSyncAt,
|
|
216
|
+
nextSyncAt: null,
|
|
217
|
+
// Preserve the lifetime synced count so the next daemon start picks it up
|
|
218
|
+
promptsSynced: this.status.promptsSynced,
|
|
219
|
+
});
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Sync prompts from Claude to backend
|
|
224
|
+
*/
|
|
225
|
+
async sync() {
|
|
226
|
+
if (!this.apiService.isAuthenticated()) {
|
|
227
|
+
this.status.errors.push('Not authenticated');
|
|
228
|
+
return this.status;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
// Get prompts from Claude. Use a timestamp cursor to skip prompts older
|
|
232
|
+
// than the last successful sync (server dedups, but skipping locally
|
|
233
|
+
// saves bandwidth on subsequent syncs).
|
|
234
|
+
const cursorIso = await this.configService.getLastSyncCursor();
|
|
235
|
+
const since = cursorIso ? new Date(cursorIso) : undefined;
|
|
236
|
+
const prompts = await this.captureService.getAllPrompts(since, MAX_PROMPTS_PER_SYNC);
|
|
237
|
+
if (prompts.length === 0) {
|
|
238
|
+
this.status.lastSyncAt = new Date();
|
|
239
|
+
await this.configService.setDaemonStatus({
|
|
240
|
+
isRunning: true,
|
|
241
|
+
pid: process.pid,
|
|
242
|
+
startedAt: new Date(),
|
|
243
|
+
lastSyncAt: this.status.lastSyncAt,
|
|
244
|
+
nextSyncAt: new Date(Date.now() + (await this.configService.getSyncInterval())),
|
|
245
|
+
promptsSynced: this.status.promptsSynced,
|
|
246
|
+
});
|
|
247
|
+
return this.status;
|
|
248
|
+
}
|
|
249
|
+
// Prepare prompts for API
|
|
250
|
+
const apiPrompts = prompts.map((p) => ({
|
|
251
|
+
content: p.content,
|
|
252
|
+
title: p.title,
|
|
253
|
+
source_type: 'claude_code',
|
|
254
|
+
captured_at: p.timestamp.toISOString(),
|
|
255
|
+
content_hash: p.hash,
|
|
256
|
+
app_context: {
|
|
257
|
+
projectPath: p.projectPath,
|
|
258
|
+
sessionId: p.sessionId,
|
|
259
|
+
},
|
|
260
|
+
}));
|
|
261
|
+
// Sync to backend
|
|
262
|
+
const result = await this.apiService.syncPrompts(apiPrompts);
|
|
263
|
+
this.status.lastSyncAt = new Date();
|
|
264
|
+
this.status.promptsSynced += result.synced;
|
|
265
|
+
// Persist the cursor at the newest prompt we ATTEMPTED to sync (not
|
|
266
|
+
// just newly-synced ones). The server's content_hash dedup means we'll
|
|
267
|
+
// see `result.synced: 0` on subsequent runs even though the prompts
|
|
268
|
+
// are already saved — that's normal. We want the cursor to advance
|
|
269
|
+
// so the next run only fetches prompts newer than the newest one we've
|
|
270
|
+
// seen, regardless of whether they were already on the server.
|
|
271
|
+
if (prompts.length > 0) {
|
|
272
|
+
const newest = prompts[0].timestamp;
|
|
273
|
+
await this.configService.setLastSyncCursor(newest.toISOString());
|
|
274
|
+
}
|
|
275
|
+
// Update daemon status with new sync time. Include promptsSynced so the
|
|
276
|
+
// `status` command (which reads from conf) shows the cumulative count
|
|
277
|
+
// across daemon restarts.
|
|
278
|
+
const syncInterval = await this.configService.getSyncInterval();
|
|
279
|
+
await this.configService.setDaemonStatus({
|
|
280
|
+
isRunning: true,
|
|
281
|
+
pid: process.pid,
|
|
282
|
+
startedAt: new Date(),
|
|
283
|
+
lastSyncAt: this.status.lastSyncAt,
|
|
284
|
+
nextSyncAt: new Date(Date.now() + syncInterval),
|
|
285
|
+
promptsSynced: this.status.promptsSynced,
|
|
286
|
+
});
|
|
287
|
+
console.log(`[${new Date().toLocaleTimeString()}] ✅ Synced ${result.synced} prompts (total this session: ${this.status.promptsSynced})`);
|
|
288
|
+
return this.status;
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const errorMsg = `Sync error: ${error.message}`;
|
|
292
|
+
this.status.errors.push(errorMsg);
|
|
293
|
+
console.error(`[${new Date().toLocaleTimeString()}] ❌ ${errorMsg}`);
|
|
294
|
+
return this.status;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get current status
|
|
299
|
+
*/
|
|
300
|
+
getStatus() {
|
|
301
|
+
return { ...this.status };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Setup process manager for auto-start on boot
|
|
305
|
+
*/
|
|
306
|
+
async setupAutoStart() {
|
|
307
|
+
try {
|
|
308
|
+
if (process.platform === 'win32') {
|
|
309
|
+
return this.setupWindowsService();
|
|
310
|
+
}
|
|
311
|
+
else if (process.platform === 'darwin') {
|
|
312
|
+
return this.setupMacOSLaunchd();
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
return this.setupLinuxSystemd();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.error('Failed to setup auto-start:', error.message);
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get auto-start script command
|
|
325
|
+
* Uses 'start' subcommand since daemon command is removed
|
|
326
|
+
*/
|
|
327
|
+
getAutoStartCommand() {
|
|
328
|
+
return `${process.execPath} ${scriptPath()} start`;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Setup macOS LaunchAgent for auto-start
|
|
332
|
+
*/
|
|
333
|
+
async setupMacOSLaunchd() {
|
|
334
|
+
const launchAgentsDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
335
|
+
const plistPath = path.join(launchAgentsDir, 'app.promptcase.daemon.plist');
|
|
336
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
337
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
338
|
+
<plist version="1.0">
|
|
339
|
+
<dict>
|
|
340
|
+
<key>Label</key>
|
|
341
|
+
<string>app.promptcase.daemon</string>
|
|
342
|
+
<key>ProgramArguments</key>
|
|
343
|
+
<array>
|
|
344
|
+
<string>${process.execPath}</string>
|
|
345
|
+
<string>${scriptPath()}</string>
|
|
346
|
+
<string>start</string>
|
|
347
|
+
</array>
|
|
348
|
+
<key>RunAtLoad</key>
|
|
349
|
+
<true/>
|
|
350
|
+
<key>KeepAlive</key>
|
|
351
|
+
<true/>
|
|
352
|
+
<key>ProcessType</key>
|
|
353
|
+
<string>Background</string>
|
|
354
|
+
<key>StandardOutPath</key>
|
|
355
|
+
<string>${path.join(os.homedir(), '.promptcase', 'daemon.log')}</string>
|
|
356
|
+
<key>StandardErrorPath</key>
|
|
357
|
+
<string>${path.join(os.homedir(), '.promptcase', 'daemon.error.log')}</string>
|
|
358
|
+
</dict>
|
|
359
|
+
</plist>`;
|
|
360
|
+
try {
|
|
361
|
+
if (!fs.existsSync(launchAgentsDir)) {
|
|
362
|
+
fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
363
|
+
}
|
|
364
|
+
fs.writeFileSync(plistPath, plistContent);
|
|
365
|
+
console.log(`✅ LaunchAgent installed at ${plistPath}`);
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
console.error('Failed to setup LaunchAgent:', error.message);
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Setup Linux systemd service for auto-start
|
|
375
|
+
*/
|
|
376
|
+
async setupLinuxSystemd() {
|
|
377
|
+
// Use user-level systemd service instead of system-level (no sudo needed)
|
|
378
|
+
const userServiceDir = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
379
|
+
const servicePath = path.join(userServiceDir, 'promptcase.service');
|
|
380
|
+
const serviceContent = `[Unit]
|
|
381
|
+
Description=PromptCase CLI Daemon
|
|
382
|
+
After=network.target
|
|
383
|
+
|
|
384
|
+
[Service]
|
|
385
|
+
Type=simple
|
|
386
|
+
ExecStart=${this.getAutoStartCommand()}
|
|
387
|
+
Restart=on-failure
|
|
388
|
+
RestartSec=10
|
|
389
|
+
StandardOutput=append:${path.join(os.homedir(), '.promptcase', 'daemon.log')}
|
|
390
|
+
StandardError=append:${path.join(os.homedir(), '.promptcase', 'daemon.error.log')}
|
|
391
|
+
|
|
392
|
+
[Install]
|
|
393
|
+
WantedBy=default.target`;
|
|
394
|
+
try {
|
|
395
|
+
if (!fs.existsSync(userServiceDir)) {
|
|
396
|
+
fs.mkdirSync(userServiceDir, { recursive: true });
|
|
397
|
+
}
|
|
398
|
+
fs.writeFileSync(servicePath, serviceContent);
|
|
399
|
+
console.log(`✅ User systemd service installed at ${servicePath}`);
|
|
400
|
+
console.log(' To enable auto-start, run:');
|
|
401
|
+
console.log(' systemctl --user enable promptcase');
|
|
402
|
+
console.log(' systemctl --user start promptcase');
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
console.error('Failed to setup systemd service:', error.message);
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Setup Windows service for auto-start
|
|
412
|
+
*/
|
|
413
|
+
async setupWindowsService() {
|
|
414
|
+
const startupPath = path.join(os.homedir(), 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
415
|
+
const batchPath = path.join(startupPath, 'promptcase.bat');
|
|
416
|
+
const batchContent = `@echo off
|
|
417
|
+
"${this.getAutoStartCommand()}"`;
|
|
418
|
+
try {
|
|
419
|
+
if (!fs.existsSync(startupPath)) {
|
|
420
|
+
fs.mkdirSync(startupPath, { recursive: true });
|
|
421
|
+
}
|
|
422
|
+
fs.writeFileSync(batchPath, batchContent);
|
|
423
|
+
console.log(`✅ Startup shortcut installed at ${batchPath}`);
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
console.error('Failed to setup Windows startup:', error.message);
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for PromptCase CLI
|
|
3
|
+
*/
|
|
4
|
+
// Supported source types — kept in sync with the `prompts.source_type`
|
|
5
|
+
// check constraint in the database schema (migration 001).
|
|
6
|
+
export const SourceType = {
|
|
7
|
+
CLAUDE_CODE: 'claude_code',
|
|
8
|
+
CLAUDE_AI: 'claude_ai',
|
|
9
|
+
GEMINI: 'gemini',
|
|
10
|
+
VS_CODE: 'vs_code',
|
|
11
|
+
CLAUDE_DESKTOP: 'claude_desktop',
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "promptcase",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "CLI daemon to capture and sync AI prompts to PromptCase web app",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"promptcase": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"lint": "eslint src --ext .ts",
|
|
15
|
+
"lint:fix": "eslint src --ext .ts --fix"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"ai",
|
|
19
|
+
"prompts",
|
|
20
|
+
"capture",
|
|
21
|
+
"sync",
|
|
22
|
+
"daemon"
|
|
23
|
+
],
|
|
24
|
+
"author": "PromptCase",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"commander": "^11.1.0",
|
|
29
|
+
"conf": "^12.0.0",
|
|
30
|
+
"got": "^14.2.1",
|
|
31
|
+
"inquirer": "^9.2.16",
|
|
32
|
+
"promptcase": "file:"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/inquirer": "^9.0.10",
|
|
36
|
+
"@types/node": "^20.10.0",
|
|
37
|
+
"tsx": "^4.6.1",
|
|
38
|
+
"typescript": "^5.3.2"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20.0.0"
|
|
42
|
+
},
|
|
43
|
+
"types": "./dist/index.d.ts"
|
|
44
|
+
}
|