promptcase 1.0.5 → 1.0.7
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 +1 -0
- package/dist/commands/init.js +27 -6
- package/dist/services/claude-capture.js +25 -2
- package/dist/services/daemon.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ The daemon auto-starts on login/boot and runs in the background.
|
|
|
40
40
|
| `promptcase show` | Show recent synced prompts |
|
|
41
41
|
| `promptcase stop` | Stop the running daemon (auto-start is preserved for next boot) |
|
|
42
42
|
| `promptcase logout` | Stop daemon, revoke token, clear credentials |
|
|
43
|
+
| `promptcase start` | **Internal** — run the daemon in the foreground. Invoked by the auto-start service (LaunchAgent / systemd / Startup folder). You don't normally need to run this directly. |
|
|
43
44
|
|
|
44
45
|
## How it works
|
|
45
46
|
|
package/dist/commands/init.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
import inquirer from 'inquirer';
|
|
9
9
|
import { Command } from 'commander';
|
|
10
10
|
import { exec, spawn } from 'child_process';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import path from 'path';
|
|
11
14
|
import { APIService } from '../services/api.js';
|
|
12
15
|
import { DaemonService } from '../services/daemon.js';
|
|
13
16
|
import { getConfig } from '../lib/config.js';
|
|
@@ -89,6 +92,15 @@ async function verifyAndShowUser(api) {
|
|
|
89
92
|
}
|
|
90
93
|
/**
|
|
91
94
|
* Start daemon in detached background process
|
|
95
|
+
*
|
|
96
|
+
* Polls for the lock dir to appear (the daemon writes the lock as soon as it
|
|
97
|
+
* finishes starting). This avoids the previous 1500ms fixed wait that would
|
|
98
|
+
* give false "Daemon may not have started" warnings on slow machines where
|
|
99
|
+
* Node + module load + lock acquisition took longer than 1500ms — the daemon
|
|
100
|
+
* would actually be running, but the parent had already given up and
|
|
101
|
+
* returned, leaving the user to think the spawn failed. The 1500ms was a
|
|
102
|
+
* too-tight deadline and the real signal is "lock dir exists" which means
|
|
103
|
+
* "daemon is fully initialized".
|
|
92
104
|
*/
|
|
93
105
|
async function startDaemonDetached(apiUrl) {
|
|
94
106
|
// Detach the daemon process so it runs independently
|
|
@@ -98,12 +110,21 @@ async function startDaemonDetached(apiUrl) {
|
|
|
98
110
|
windowsHide: true,
|
|
99
111
|
});
|
|
100
112
|
child.unref();
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
113
|
+
// Poll for the lock to appear. The daemon writes the lock dir as the
|
|
114
|
+
// first step of its lifecycle, so seeing it is a reliable "running" signal.
|
|
115
|
+
// Cap at 10 seconds — if the lock hasn't appeared by then, the daemon
|
|
116
|
+
// process must have died.
|
|
117
|
+
const lockPath = path.join(os.homedir(), '.promptcase', 'daemon.pid.lock');
|
|
118
|
+
const deadline = Date.now() + 10_000;
|
|
119
|
+
let acquired = false;
|
|
120
|
+
while (Date.now() < deadline) {
|
|
121
|
+
if (fs.existsSync(lockPath)) {
|
|
122
|
+
acquired = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
126
|
+
}
|
|
127
|
+
if (acquired) {
|
|
107
128
|
console.log(' ✅ Daemon started in background');
|
|
108
129
|
}
|
|
109
130
|
else {
|
|
@@ -287,7 +287,23 @@ export class ClaudeCaptureService {
|
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
289
289
|
* Get all prompts from all sources (project transcripts + history),
|
|
290
|
-
* deduplicated by SHA-256 content hash
|
|
290
|
+
* deduplicated by SHA-256 content hash.
|
|
291
|
+
*
|
|
292
|
+
* Sort order depends on whether this is a backfill or an incremental sync:
|
|
293
|
+
*
|
|
294
|
+
* - **Backfill** (`since` is undefined): the daemon's first sync, with no
|
|
295
|
+
* prior cursor. We sort **OLDEST FIRST** so that the server inserts
|
|
296
|
+
* prompts in chronological order. Since the server's `created_at`
|
|
297
|
+
* column defaults to insert time and the dashboard sorts `created_at
|
|
298
|
+
* DESC`, the *most recent* prompt in the batch ends up at the top of
|
|
299
|
+
* the dashboard — matching the user's intuition that "latest shows
|
|
300
|
+
* first."
|
|
301
|
+
*
|
|
302
|
+
* - **Incremental** (`since` is a date): the daemon's subsequent syncs
|
|
303
|
+
* pick up only prompts newer than the cursor. We sort **NEWEST FIRST**
|
|
304
|
+
* so that when the cursor advances we always send the freshest
|
|
305
|
+
* prompts first (and the new cursor lands on the actual latest
|
|
306
|
+
* prompt, not a few-batch-behind one).
|
|
291
307
|
*/
|
|
292
308
|
async getAllPrompts(since, limit = 100) {
|
|
293
309
|
const sessions = await this.getSessions();
|
|
@@ -326,7 +342,14 @@ export class ClaudeCaptureService {
|
|
|
326
342
|
seenHashes.add(prompt.hash);
|
|
327
343
|
allPrompts.push(prompt);
|
|
328
344
|
}
|
|
329
|
-
|
|
345
|
+
// Backfill: oldest first (so newest ends up at top of dashboard).
|
|
346
|
+
// Incremental: newest first (so the cursor advances to actual latest).
|
|
347
|
+
if (since) {
|
|
348
|
+
allPrompts.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
allPrompts.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
352
|
+
}
|
|
330
353
|
return allPrompts.slice(0, limit);
|
|
331
354
|
}
|
|
332
355
|
/**
|
package/dist/services/daemon.js
CHANGED
|
@@ -114,6 +114,14 @@ export class DaemonService {
|
|
|
114
114
|
async acquireExclusiveLock() {
|
|
115
115
|
try {
|
|
116
116
|
const lockDir = this.pidFile + '.lock';
|
|
117
|
+
// Ensure the parent dir exists before we try to mkdir the lock inside
|
|
118
|
+
// it. Without this, the very first run on a clean machine gets ENOENT
|
|
119
|
+
// because ~/.promptcase/ doesn't exist yet (writePidFile creates it
|
|
120
|
+
// later, but only if it gets to run).
|
|
121
|
+
const parentDir = path.dirname(lockDir);
|
|
122
|
+
if (!fs.existsSync(parentDir)) {
|
|
123
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
124
|
+
}
|
|
117
125
|
try {
|
|
118
126
|
fs.mkdirSync(lockDir);
|
|
119
127
|
// We won the race — write our PID into the lock dir for diagnostics
|