promptarc 0.0.3 → 0.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 +33 -25
- package/dist/bin.js +561 -45
- package/dist/share-direct.js +360 -0
- package/dist/web/.next/BUILD_ID +1 -1
- package/dist/web/.next/app-build-manifest.json +86 -79
- package/dist/web/.next/app-path-routes-manifest.json +17 -16
- package/dist/web/.next/build-manifest.json +2 -2
- package/dist/web/.next/prerender-manifest.json +14 -14
- package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/admin/backfill-ai/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/admin/backfill-search/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/ai/context/route.js +31 -0
- package/dist/web/.next/server/app/api/ai/context/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/api/ai/context/route_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/api/ai/pr-summary/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/ai/pr-summary-local/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/ai/process/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/cli/download/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/cli/feedback/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/cli/init/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/cli/my-shares/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/cli/poll/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/cli/project-settings/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/cli/share-mutate/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/cli/sync-list/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/local/project-sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/local/watch/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/search-index/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/share/download/[slug]/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/share/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/auth/callback/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/auth/signout/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/blog/ai-coding-agents-2026/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/blog/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/cli/login/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/feedback/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/my-projects/[projectKey]/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/my-projects/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/my-shares/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/opengraph-image/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/page.js +1 -1
- package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/r/[slug]/opengraph-image/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/r/[slug]/page.js +1 -1
- package/dist/web/.next/server/app/r/[slug]/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/robots.txt/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/robots.txt/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/search/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/sessions/[projectId]/[sessionId]/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/sign-in/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/sitemap.xml/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/sitemap.xml/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/sitemap.xml.body +8 -8
- package/dist/web/.next/server/app/terms/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app-paths-manifest.json +17 -16
- package/dist/web/.next/server/functions-config-manifest.json +10 -7
- package/dist/web/.next/server/pages/500.html +1 -1
- package/dist/web/.next/server/server-reference-manifest.js +1 -1
- package/dist/web/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/.next/static/LF69v3WA6LbEAkvRHNqfG/_buildManifest.js +1 -0
- package/dist/web/.next/static/chunks/app/_not-found/page-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/admin/backfill-ai/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/admin/backfill-search/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/ai/context/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/ai/pr-summary/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/ai/pr-summary-local/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/ai/process/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/cli/download/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/cli/feedback/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/cli/init/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/cli/my-shares/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/cli/poll/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/cli/project-settings/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/cli/share-mutate/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/cli/sync-list/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/local/project-sessions/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/local/watch/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/search/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/search-index/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/share/download/[slug]/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/share/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/api/upload/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/auth/callback/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/auth/signout/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/blog/ai-coding-agents-2026/page-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/opengraph-image/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/{page-f7b69fa944b46c20.js → page-95394e3c6370084f.js} +1 -1
- package/dist/web/.next/static/chunks/app/r/[slug]/opengraph-image/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/robots.txt/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/sitemap.xml/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/css/c76bb7513858c50c.css +3 -0
- package/package.json +1 -1
- package/dist/web/.next/static/ABHGCNY7MSdkh00Z4Y8wS/_buildManifest.js +0 -1
- package/dist/web/.next/static/chunks/app/_not-found/page-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/admin/backfill-ai/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/admin/backfill-search/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/ai/pr-summary/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/ai/pr-summary-local/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/ai/process/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/download/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/feedback/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/init/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/my-shares/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/poll/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/project-settings/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/share-mutate/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/sync-list/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/local/project-sessions/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/local/watch/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/search/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/search-index/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/share/download/[slug]/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/share/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/api/upload/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/auth/callback/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/auth/signout/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/blog/ai-coding-agents-2026/page-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/opengraph-image/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/r/[slug]/opengraph-image/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/robots.txt/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/chunks/app/sitemap.xml/route-fd6592d30c58fa5e.js +0 -1
- package/dist/web/.next/static/css/b3ccdd7835e6edda.css +0 -3
- /package/dist/web/.next/static/{ABHGCNY7MSdkh00Z4Y8wS → LF69v3WA6LbEAkvRHNqfG}/_ssgManifest.js +0 -0
package/dist/bin.js
CHANGED
|
@@ -11,16 +11,19 @@
|
|
|
11
11
|
* promptarc login Run the auth flow without booting the server
|
|
12
12
|
* promptarc logout Delete the saved auth token
|
|
13
13
|
* promptarc whoami Print the signed-in account
|
|
14
|
+
* promptarc pr Generate a PR description from the current session
|
|
15
|
+
* promptarc pull Download a shared session for resume
|
|
14
16
|
* promptarc --version Print the package version
|
|
15
17
|
*/
|
|
16
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
17
19
|
import { homedir } from 'node:os';
|
|
18
|
-
import { dirname, join, resolve } from 'node:path';
|
|
20
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
19
21
|
import { fileURLToPath } from 'node:url';
|
|
20
22
|
import { spawn } from 'node:child_process';
|
|
21
23
|
import { createServer } from 'node:net';
|
|
22
24
|
import { runLogin, loadAuth, deleteAuth } from './auth.js';
|
|
23
25
|
import { startSyncLoop } from './sync.js';
|
|
26
|
+
import { buildReplayFromJsonl } from './share-direct.js';
|
|
24
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
28
|
const __dirname = dirname(__filename);
|
|
26
29
|
const VERSION = readJson(join(__dirname, '..', 'package.json')).version ?? '0.0.0';
|
|
@@ -54,10 +57,25 @@ async function main() {
|
|
|
54
57
|
console.log(`Signed in as ${auth.email ?? auth.userId}${auth.login ? ` (@${auth.login})` : ''}`);
|
|
55
58
|
return;
|
|
56
59
|
}
|
|
60
|
+
if (cmd === 'context') {
|
|
61
|
+
await runContext(args.slice(1));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
57
64
|
if (cmd === 'pr') {
|
|
58
65
|
await runPR(args.slice(1));
|
|
59
66
|
return;
|
|
60
67
|
}
|
|
68
|
+
if (cmd === 'share') {
|
|
69
|
+
const sessionArg = args[1];
|
|
70
|
+
if (!sessionArg) {
|
|
71
|
+
console.error('Usage: promptarc share <session-id>');
|
|
72
|
+
console.error(' Share a local session to the cloud as private.');
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await runShare(sessionArg, args.slice(2));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
61
79
|
if (cmd === 'pull') {
|
|
62
80
|
const slug = args[1];
|
|
63
81
|
if (!slug) {
|
|
@@ -85,8 +103,10 @@ COMMANDS
|
|
|
85
103
|
login Sign in to your promptarc account via GitHub
|
|
86
104
|
logout Delete the saved auth token
|
|
87
105
|
whoami Print the currently signed-in account
|
|
106
|
+
context What do I need to know? Past sessions for this project
|
|
88
107
|
pr Generate a PR description from the current session
|
|
89
108
|
pr --share Generate + share session as private (includes link)
|
|
109
|
+
share <id> Share a local session to the cloud (private by default)
|
|
90
110
|
pull <slug> Download a shared session into ~/.claude/ for resume
|
|
91
111
|
--version Print the CLI version
|
|
92
112
|
--help Show this message
|
|
@@ -95,13 +115,515 @@ LEARN MORE
|
|
|
95
115
|
https://promptarc.dev
|
|
96
116
|
`);
|
|
97
117
|
}
|
|
98
|
-
async function
|
|
118
|
+
async function runContext(args) {
|
|
119
|
+
const cwd = process.cwd();
|
|
120
|
+
const query = args.filter((a) => !a.startsWith('--')).join(' ');
|
|
121
|
+
const filesFlag = args.includes('--files');
|
|
122
|
+
const cwdParts = cwd.split('/').filter(Boolean);
|
|
123
|
+
const projectName = cwdParts[cwdParts.length - 1] ?? '';
|
|
124
|
+
console.log(`promptarc context`);
|
|
125
|
+
console.log(` ▸ project: ${projectName}`);
|
|
126
|
+
// 1. Detect recently changed files (git + find fallback).
|
|
127
|
+
let files = [];
|
|
128
|
+
if (filesFlag) {
|
|
129
|
+
try {
|
|
130
|
+
const { execSync } = await import('node:child_process');
|
|
131
|
+
// Try git first: staged + last 3 commits.
|
|
132
|
+
const gitFiles = execSync('git diff --name-only HEAD~3 HEAD 2>/dev/null; git diff --name-only --cached 2>/dev/null; git diff --name-only 2>/dev/null', { cwd, encoding: 'utf8' });
|
|
133
|
+
files = [...new Set(gitFiles.split('\n').filter(Boolean))].slice(0, 15);
|
|
134
|
+
}
|
|
135
|
+
catch { /* not a git repo */ }
|
|
136
|
+
// Fallback: recently modified files.
|
|
137
|
+
if (files.length === 0) {
|
|
138
|
+
try {
|
|
139
|
+
const { execSync } = await import('node:child_process');
|
|
140
|
+
const found = execSync('find . -maxdepth 3 -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.py" -o -name "*.rs" -o -name "*.go" | head -15', { cwd, encoding: 'utf8' });
|
|
141
|
+
files = found.split('\n').filter(Boolean).map((f) => f.replace(/^\.\//, ''));
|
|
142
|
+
}
|
|
143
|
+
catch { /* no find */ }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (files.length > 0) {
|
|
147
|
+
console.log(` ▸ files: ${files.slice(0, 5).join(', ')}${files.length > 5 ? ` +${files.length - 5} more` : ''}`);
|
|
148
|
+
}
|
|
149
|
+
// 2. Local scan: find sessions across all three agents for this project.
|
|
150
|
+
console.log(` ▸ scanning local sessions...`);
|
|
151
|
+
const localSessions = scanLocalSessions(cwd, projectName);
|
|
152
|
+
if (localSessions.length > 0) {
|
|
153
|
+
const lastActive = fmtRelativeTime(localSessions[0].mtime);
|
|
154
|
+
console.log(`\n ${projectName} · ${localSessions.length} session${localSessions.length === 1 ? '' : 's'} · last active ${lastActive}`);
|
|
155
|
+
}
|
|
156
|
+
// 3. Cloud search + AI briefing (requires auth).
|
|
99
157
|
const auth = loadAuth();
|
|
158
|
+
let cloudSessionsFound = false;
|
|
159
|
+
if (auth) {
|
|
160
|
+
console.log(`\n ▸ searching cloud archive...`);
|
|
161
|
+
const siteUrl = process.env.PROMPTARC_SITE_URL ?? 'https://promptarc.dev';
|
|
162
|
+
// Build local context to send to the API for AI briefing.
|
|
163
|
+
const localContext = localSessions.slice(0, 3).map((s) => {
|
|
164
|
+
const prompt = s.firstPrompt
|
|
165
|
+
? s.firstPrompt.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 300)
|
|
166
|
+
: '';
|
|
167
|
+
return `[${s.agent}] ${prompt} (${s.messageCount} messages)`;
|
|
168
|
+
}).join('\n');
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetch(`${siteUrl}/api/ai/context`, {
|
|
171
|
+
method: 'POST',
|
|
172
|
+
headers: {
|
|
173
|
+
'content-type': 'application/json',
|
|
174
|
+
authorization: `Bearer ${auth.token}`,
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify({
|
|
177
|
+
cwd,
|
|
178
|
+
files,
|
|
179
|
+
query,
|
|
180
|
+
localContext,
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
if (res.ok) {
|
|
184
|
+
const json = (await res.json());
|
|
185
|
+
if (json.count > 0) {
|
|
186
|
+
cloudSessionsFound = true;
|
|
187
|
+
console.log(` ${json.count} cloud session${json.count === 1 ? '' : 's'} found.\n`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
console.log(` No cloud sessions found.`);
|
|
191
|
+
if (localSessions.length > 0) {
|
|
192
|
+
console.log(` Share sessions to unlock AI briefing from any machine.`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Resume command — pick up the exact session.
|
|
196
|
+
if (localSessions.length > 0) {
|
|
197
|
+
const latest = localSessions[0];
|
|
198
|
+
const agentCmd = latest.agent === 'codex'
|
|
199
|
+
? 'codex'
|
|
200
|
+
: latest.agent === 'cursor'
|
|
201
|
+
? 'cursor'
|
|
202
|
+
: 'claude';
|
|
203
|
+
const shortId = latest.sessionId ?? basename(latest.path, '.jsonl');
|
|
204
|
+
console.log(`\n Resume last session:`);
|
|
205
|
+
console.log(` ${agentCmd} --resume ${shortId}`);
|
|
206
|
+
}
|
|
207
|
+
// AI briefing — the actionable part.
|
|
208
|
+
if (json.aiBriefing) {
|
|
209
|
+
if (json.aiBriefing.unfinished) {
|
|
210
|
+
console.log(`\n What's unfinished:`);
|
|
211
|
+
for (const line of json.aiBriefing.unfinished.split('\n')) {
|
|
212
|
+
const trimmed = line.trim();
|
|
213
|
+
if (trimmed)
|
|
214
|
+
console.log(` ${trimmed}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (json.aiBriefing.resumePrompt) {
|
|
218
|
+
console.log(`\n Resume prompt (paste into your agent):`);
|
|
219
|
+
console.log(` ┌─`);
|
|
220
|
+
for (const line of json.aiBriefing.resumePrompt.split('\n')) {
|
|
221
|
+
console.log(` │ ${line}`);
|
|
222
|
+
}
|
|
223
|
+
console.log(` └─`);
|
|
224
|
+
}
|
|
225
|
+
if (json.aiBriefing.warnings) {
|
|
226
|
+
console.log(`\n Heads up:`);
|
|
227
|
+
for (const line of json.aiBriefing.warnings.split('\n')) {
|
|
228
|
+
const trimmed = line.trim();
|
|
229
|
+
if (trimmed)
|
|
230
|
+
console.log(` ${trimmed}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (localSessions.length > 0 && json.count === 0) {
|
|
235
|
+
console.log(`\n Share sessions to get AI briefings with resume prompts.`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
console.log(` Cloud search failed (HTTP ${res.status}).`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
console.log(` Could not reach promptarc.dev (offline?).`);
|
|
244
|
+
if (localSessions.length === 0) {
|
|
245
|
+
console.log(` No local sessions found either.`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else if (localSessions.length === 0) {
|
|
250
|
+
console.log(`\n No local sessions found. Run \`promptarc login\` to search cloud archive.`);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
console.log(`\n Run \`promptarc login\` to also search your cloud archive.`);
|
|
254
|
+
}
|
|
255
|
+
// Session list at the bottom (least important).
|
|
256
|
+
if (localSessions.length > 0) {
|
|
257
|
+
console.log('');
|
|
258
|
+
for (const s of localSessions.slice(0, 6)) {
|
|
259
|
+
const when = fmtRelativeTime(s.mtime);
|
|
260
|
+
const agent = s.agent === 'claude' ? 'Claude' : s.agent === 'codex' ? 'Codex' : 'Cursor';
|
|
261
|
+
const cleanPrompt = s.firstPrompt
|
|
262
|
+
? s.firstPrompt.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim()
|
|
263
|
+
: '';
|
|
264
|
+
let title = cleanPrompt || `Session ${basename(s.path, '.jsonl').slice(0, 8)}`;
|
|
265
|
+
if (title.length > 50) {
|
|
266
|
+
title = title.slice(0, 50).replace(/\s\S*$/, '') + '...';
|
|
267
|
+
}
|
|
268
|
+
console.log(` ${when.padEnd(13)} [${agent}] ${title} · ${fmtNumber(s.messageCount)} msgs`);
|
|
269
|
+
}
|
|
270
|
+
// Gentle share nudge — only when no cloud sessions exist.
|
|
271
|
+
if (!auth || !cloudSessionsFound) {
|
|
272
|
+
const latest = localSessions[0];
|
|
273
|
+
console.log(`\n ${localSessions.length} local session${localSessions.length === 1 ? '' : 's'} · 0 cloud sessions`);
|
|
274
|
+
console.log(` Share to search from any machine: promptarc share ${latest.sessionId.slice(0, 8)}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
console.log('');
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Scan ~/.claude/, ~/.codex/, ~/.cursor/ for sessions matching the
|
|
281
|
+
* current project. Returns session summaries sorted by recency.
|
|
282
|
+
*/
|
|
283
|
+
function scanLocalSessions(cwd, projectName) {
|
|
284
|
+
const results = [];
|
|
285
|
+
const cwdEncoded = cwd.replace(/\//g, '-');
|
|
286
|
+
// Claude Code: ~/.claude/projects/<encoded-cwd>/*.jsonl
|
|
287
|
+
const claudeRoot = join(homedir(), '.claude', 'projects');
|
|
288
|
+
try {
|
|
289
|
+
for (const dir of readdirSync(claudeRoot)) {
|
|
290
|
+
if (dir === cwdEncoded || dir.endsWith(projectName)) {
|
|
291
|
+
const projectDir = join(claudeRoot, dir);
|
|
292
|
+
if (!statSync(projectDir).isDirectory())
|
|
293
|
+
continue;
|
|
294
|
+
for (const file of readdirSync(projectDir)) {
|
|
295
|
+
if (!file.endsWith('.jsonl'))
|
|
296
|
+
continue;
|
|
297
|
+
const fp = join(projectDir, file);
|
|
298
|
+
const info = quickParseSession(fp, 'claude');
|
|
299
|
+
if (info)
|
|
300
|
+
results.push(info);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch { /* no Claude sessions */ }
|
|
306
|
+
// Codex: ~/.codex/sessions/**/*.jsonl (match cwd in first line)
|
|
307
|
+
const codexRoot = join(homedir(), '.codex', 'sessions');
|
|
308
|
+
try {
|
|
309
|
+
const codexFiles = findJsonlRecursive(codexRoot);
|
|
310
|
+
for (const fp of codexFiles) {
|
|
311
|
+
try {
|
|
312
|
+
const firstLine = readFileSync(fp, 'utf8').split('\n')[0] ?? '';
|
|
313
|
+
if (firstLine.includes(cwd) || firstLine.includes(projectName)) {
|
|
314
|
+
const info = quickParseSession(fp, 'codex');
|
|
315
|
+
if (info)
|
|
316
|
+
results.push(info);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch { /* skip */ }
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch { /* no Codex sessions */ }
|
|
323
|
+
// Cursor: ~/.cursor/projects/<encoded>/agent-transcripts/<uuid>/<uuid>.jsonl
|
|
324
|
+
const cursorEncoded = cwd.replace(/\//g, '-').replace(/^-/, '');
|
|
325
|
+
const cursorRoot = join(homedir(), '.cursor', 'projects');
|
|
326
|
+
try {
|
|
327
|
+
for (const dir of readdirSync(cursorRoot)) {
|
|
328
|
+
if (dir === cursorEncoded || dir.endsWith(projectName)) {
|
|
329
|
+
const transcriptsDir = join(cursorRoot, dir, 'agent-transcripts');
|
|
330
|
+
try {
|
|
331
|
+
for (const sd of readdirSync(transcriptsDir)) {
|
|
332
|
+
const fp = join(transcriptsDir, sd, `${sd}.jsonl`);
|
|
333
|
+
try {
|
|
334
|
+
statSync(fp);
|
|
335
|
+
const info = quickParseSession(fp, 'cursor');
|
|
336
|
+
if (info)
|
|
337
|
+
results.push(info);
|
|
338
|
+
}
|
|
339
|
+
catch { /* skip */ }
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch { /* no transcripts */ }
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch { /* no Cursor sessions */ }
|
|
347
|
+
// Sort by most recent first.
|
|
348
|
+
results.sort((a, b) => b.mtime - a.mtime);
|
|
349
|
+
return results;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Quick parse a JSONL session to extract: mtime, message count,
|
|
353
|
+
* and the first user prompt. Reads only enough to get the data
|
|
354
|
+
* without loading the entire file.
|
|
355
|
+
*/
|
|
356
|
+
function quickParseSession(filePath, agent) {
|
|
357
|
+
try {
|
|
358
|
+
const stat = statSync(filePath);
|
|
359
|
+
// For large files, read only the first 100KB to find the first prompt.
|
|
360
|
+
// Estimate total message count from line count of the full file.
|
|
361
|
+
const fullRaw = readFileSync(filePath, 'utf8');
|
|
362
|
+
const totalLines = fullRaw.split('\n').filter(Boolean).length;
|
|
363
|
+
const scanRaw = fullRaw.length > 100_000 ? fullRaw.slice(0, 100_000) : fullRaw;
|
|
364
|
+
const lines = scanRaw.split('\n').filter(Boolean);
|
|
365
|
+
if (totalLines === 0)
|
|
366
|
+
return null;
|
|
367
|
+
let messageCount = totalLines; // approximate: 1 line ≈ 1 entry
|
|
368
|
+
let firstPrompt = '';
|
|
369
|
+
for (const line of lines) {
|
|
370
|
+
let parsed;
|
|
371
|
+
try {
|
|
372
|
+
parsed = JSON.parse(line);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
// Claude Code: {type: "user", message: "text"}
|
|
378
|
+
if (agent === 'claude') {
|
|
379
|
+
if (!firstPrompt && parsed.type === 'user') {
|
|
380
|
+
let text = '';
|
|
381
|
+
if (typeof parsed.message === 'string')
|
|
382
|
+
text = parsed.message;
|
|
383
|
+
else if (typeof parsed.message === 'object' && parsed.message) {
|
|
384
|
+
const content = parsed.message.content;
|
|
385
|
+
// content can be a string or an array of blocks.
|
|
386
|
+
if (typeof content === 'string')
|
|
387
|
+
text = content;
|
|
388
|
+
else if (Array.isArray(content)) {
|
|
389
|
+
const textBlock = content.find((b) => b.type === 'text');
|
|
390
|
+
if (textBlock)
|
|
391
|
+
text = textBlock.text;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Skip system messages and interrupted markers.
|
|
395
|
+
if (text && !isSystemMessage(text))
|
|
396
|
+
firstPrompt = text;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Codex: {type: "event_msg", payload: {type: "user_message", message: "..."}}
|
|
400
|
+
if (agent === 'codex') {
|
|
401
|
+
if (!firstPrompt && parsed.type === 'event_msg') {
|
|
402
|
+
const payload = parsed.payload;
|
|
403
|
+
if (payload?.type === 'user_message' && typeof payload.message === 'string') {
|
|
404
|
+
firstPrompt = payload.message;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Cursor: {role: "user", message: {content: [{type: "text", text: "..."}]}}
|
|
409
|
+
if (agent === 'cursor') {
|
|
410
|
+
if (!firstPrompt && parsed.role === 'user' && typeof parsed.message === 'object' && parsed.message) {
|
|
411
|
+
const content = parsed.message.content;
|
|
412
|
+
if (Array.isArray(content)) {
|
|
413
|
+
const textBlock = content.find((b) => b.type === 'text');
|
|
414
|
+
if (textBlock) {
|
|
415
|
+
firstPrompt = textBlock.text
|
|
416
|
+
.replace(/<\/?user_query>/g, '')
|
|
417
|
+
.trim();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Stop as soon as we find a real user prompt.
|
|
423
|
+
if (firstPrompt)
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
if (totalLines < 2)
|
|
427
|
+
return null;
|
|
428
|
+
// Fallback title: use session ID from filename.
|
|
429
|
+
if (!firstPrompt) {
|
|
430
|
+
const name = basename(filePath, '.jsonl');
|
|
431
|
+
firstPrompt = `Session ${name.slice(0, 8)}... (${messageCount} messages)`;
|
|
432
|
+
}
|
|
433
|
+
// Extract session ID from filename or path.
|
|
434
|
+
const fileName = basename(filePath, '.jsonl');
|
|
435
|
+
const uuidMatch = fileName.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
436
|
+
const sessionId = uuidMatch?.[1] ?? fileName;
|
|
437
|
+
return {
|
|
438
|
+
path: filePath,
|
|
439
|
+
sessionId,
|
|
440
|
+
agent,
|
|
441
|
+
mtime: stat.mtimeMs,
|
|
442
|
+
messageCount,
|
|
443
|
+
firstPrompt,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/** Filter out system-generated messages that aren't real user prompts. */
|
|
451
|
+
function isSystemMessage(text) {
|
|
452
|
+
const t = text.trim().toLowerCase();
|
|
453
|
+
return (t.startsWith('[request interrupted') ||
|
|
454
|
+
t.startsWith('[tool use') ||
|
|
455
|
+
t.startsWith('<system') ||
|
|
456
|
+
t.startsWith('<environment') ||
|
|
457
|
+
t.startsWith('<permissions') ||
|
|
458
|
+
t.startsWith('<app-context') ||
|
|
459
|
+
t === '' ||
|
|
460
|
+
t.length < 5);
|
|
461
|
+
}
|
|
462
|
+
async function runShare(sessionArg, extraArgs) {
|
|
463
|
+
let auth = loadAuth();
|
|
100
464
|
if (!auth) {
|
|
101
|
-
console.
|
|
465
|
+
console.log(' ▸ not signed in, starting login...');
|
|
466
|
+
auth = await runLogin();
|
|
467
|
+
}
|
|
468
|
+
// Find the session file.
|
|
469
|
+
const session = sessionArg.length >= 8
|
|
470
|
+
? findSessionById(sessionArg)
|
|
471
|
+
: null;
|
|
472
|
+
if (!session) {
|
|
473
|
+
// Try partial match — the user might pass just the first 8 chars.
|
|
474
|
+
const partialMatch = findSessionByPrefix(sessionArg);
|
|
475
|
+
if (!partialMatch) {
|
|
476
|
+
console.error(`Session not found: ${sessionArg}`);
|
|
477
|
+
console.error('Run `promptarc context` to see available sessions.');
|
|
478
|
+
process.exitCode = 1;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
return doShare(partialMatch, auth);
|
|
482
|
+
}
|
|
483
|
+
return doShare(session, auth);
|
|
484
|
+
}
|
|
485
|
+
async function doShare(session, auth) {
|
|
486
|
+
console.log(`promptarc share`);
|
|
487
|
+
console.log(` ▸ session: ${session.sessionId.slice(0, 8)}...`);
|
|
488
|
+
console.log(` ▸ building artifact locally...`);
|
|
489
|
+
// Extract project name from projectId.
|
|
490
|
+
const projectId = session.projectId ?? '';
|
|
491
|
+
const parts = projectId.replace(/^(codex:|cursor:)/, '').split('-').filter(Boolean);
|
|
492
|
+
const projectName = parts[parts.length - 1] ?? 'unknown';
|
|
493
|
+
// Build the SharedReplay artifact locally (reads JSONL, redacts secrets).
|
|
494
|
+
let replay;
|
|
495
|
+
try {
|
|
496
|
+
replay = buildReplayFromJsonl(session.path, projectName);
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
console.error(` ✗ Failed to build artifact: ${String(err)}`);
|
|
500
|
+
process.exitCode = 1;
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
console.log(` ▸ ${replay.meta.messageCount} messages, ${replay.redactionStats.secretsRedacted} secrets redacted`);
|
|
504
|
+
console.log(` ▸ uploading to promptarc.dev...`);
|
|
505
|
+
// POST directly to hosted /api/upload with the bearer token.
|
|
506
|
+
const siteUrl = process.env.PROMPTARC_UPLOAD_ENDPOINT ?? 'https://promptarc.dev/api/upload';
|
|
507
|
+
const payload = JSON.stringify({
|
|
508
|
+
replay,
|
|
509
|
+
sourceSessionId: session.sessionId,
|
|
510
|
+
projectKey: projectId,
|
|
511
|
+
agentSource: projectId.startsWith('codex:') ? 'codex' : projectId.startsWith('cursor:') ? 'cursor' : 'claude-code',
|
|
512
|
+
visibility: 'private',
|
|
513
|
+
autoSync: false,
|
|
514
|
+
_h: '',
|
|
515
|
+
});
|
|
516
|
+
if (payload.length > 5 * 1024 * 1024) {
|
|
517
|
+
console.error(` ✗ Session too large (${(payload.length / 1024 / 1024).toFixed(1)} MB). Max 5 MB.`);
|
|
518
|
+
process.exitCode = 1;
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
const res = await fetch(siteUrl, {
|
|
523
|
+
method: 'POST',
|
|
524
|
+
headers: {
|
|
525
|
+
'content-type': 'application/json',
|
|
526
|
+
authorization: `Bearer ${auth.token}`,
|
|
527
|
+
},
|
|
528
|
+
body: payload,
|
|
529
|
+
});
|
|
530
|
+
if (!res.ok) {
|
|
531
|
+
const text = await res.text();
|
|
532
|
+
console.error(` ✗ Upload failed: HTTP ${res.status}`);
|
|
533
|
+
console.error(` ${text.slice(0, 200)}`);
|
|
534
|
+
process.exitCode = 1;
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const json = (await res.json());
|
|
538
|
+
if (json.status === 'unchanged') {
|
|
539
|
+
console.log(` ✓ Already shared (unchanged).`);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
console.log(` ✓ Shared.`);
|
|
543
|
+
}
|
|
544
|
+
if (json.url) {
|
|
545
|
+
console.log(` ${json.url}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch (err) {
|
|
549
|
+
console.error(` ✗ Upload failed: ${String(err)}`);
|
|
102
550
|
process.exitCode = 1;
|
|
103
551
|
return;
|
|
104
552
|
}
|
|
553
|
+
console.log('');
|
|
554
|
+
}
|
|
555
|
+
function findSessionByPrefix(prefix) {
|
|
556
|
+
const claudeRoot = join(homedir(), '.claude', 'projects');
|
|
557
|
+
try {
|
|
558
|
+
for (const dir of readdirSync(claudeRoot)) {
|
|
559
|
+
const projectDir = join(claudeRoot, dir);
|
|
560
|
+
if (!statSync(projectDir).isDirectory())
|
|
561
|
+
continue;
|
|
562
|
+
for (const file of readdirSync(projectDir)) {
|
|
563
|
+
if (file.startsWith(prefix) && file.endsWith('.jsonl')) {
|
|
564
|
+
return {
|
|
565
|
+
path: join(projectDir, file),
|
|
566
|
+
sessionId: basename(file, '.jsonl'),
|
|
567
|
+
projectId: dir,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
catch { /* no Claude projects */ }
|
|
574
|
+
// Cursor.
|
|
575
|
+
const cursorRoot = join(homedir(), '.cursor', 'projects');
|
|
576
|
+
try {
|
|
577
|
+
for (const dir of readdirSync(cursorRoot)) {
|
|
578
|
+
const transcriptsDir = join(cursorRoot, dir, 'agent-transcripts');
|
|
579
|
+
try {
|
|
580
|
+
for (const sd of readdirSync(transcriptsDir)) {
|
|
581
|
+
if (sd.startsWith(prefix)) {
|
|
582
|
+
const fp = join(transcriptsDir, sd, `${sd}.jsonl`);
|
|
583
|
+
try {
|
|
584
|
+
statSync(fp);
|
|
585
|
+
return { path: fp, sessionId: sd, projectId: `cursor:${dir}` };
|
|
586
|
+
}
|
|
587
|
+
catch { /* skip */ }
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch { /* no transcripts */ }
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch { /* no Cursor */ }
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
function fmtRelativeTime(ms) {
|
|
598
|
+
const diff = Date.now() - ms;
|
|
599
|
+
const mins = Math.floor(diff / 60_000);
|
|
600
|
+
if (mins < 1)
|
|
601
|
+
return 'just now';
|
|
602
|
+
if (mins < 60)
|
|
603
|
+
return `${mins}m ago`;
|
|
604
|
+
const hours = Math.floor(mins / 60);
|
|
605
|
+
if (hours < 24)
|
|
606
|
+
return `${hours}h ago`;
|
|
607
|
+
const days = Math.floor(hours / 24);
|
|
608
|
+
if (days === 1)
|
|
609
|
+
return 'yesterday';
|
|
610
|
+
if (days < 7)
|
|
611
|
+
return `${days}d ago`;
|
|
612
|
+
const weeks = Math.floor(days / 7);
|
|
613
|
+
if (weeks < 5)
|
|
614
|
+
return `${weeks}w ago`;
|
|
615
|
+
const months = Math.floor(days / 30);
|
|
616
|
+
return `${months}mo ago`;
|
|
617
|
+
}
|
|
618
|
+
function fmtNumber(n) {
|
|
619
|
+
return n.toLocaleString('en-US');
|
|
620
|
+
}
|
|
621
|
+
async function runPR(args) {
|
|
622
|
+
let auth = loadAuth();
|
|
623
|
+
if (!auth) {
|
|
624
|
+
console.log(' ▸ not signed in, starting login...');
|
|
625
|
+
auth = await runLogin();
|
|
626
|
+
}
|
|
105
627
|
const shouldShare = args.includes('--share');
|
|
106
628
|
const sessionIdArg = args.find((a) => !a.startsWith('--'));
|
|
107
629
|
const copyFlag = args.includes('--copy');
|
|
@@ -206,27 +728,25 @@ async function runPR(args) {
|
|
|
206
728
|
console.log('');
|
|
207
729
|
}
|
|
208
730
|
function findLatestSession(cwd) {
|
|
209
|
-
const fs = require('node:fs');
|
|
210
|
-
const path = require('node:path');
|
|
211
731
|
const candidates = [];
|
|
212
732
|
const cwdParts = cwd.split('/').filter(Boolean);
|
|
213
733
|
const lastPart = cwdParts[cwdParts.length - 1] ?? '';
|
|
214
734
|
const encoded = cwd.replace(/\//g, '-');
|
|
215
735
|
// 1. Claude Code: ~/.claude/projects/<encoded-cwd>/*.jsonl
|
|
216
|
-
const claudeRoot =
|
|
736
|
+
const claudeRoot = join(homedir(), '.claude', 'projects');
|
|
217
737
|
try {
|
|
218
|
-
const dirs =
|
|
738
|
+
const dirs = readdirSync(claudeRoot);
|
|
219
739
|
for (const dir of dirs) {
|
|
220
740
|
if (dir === encoded || dir.endsWith(lastPart)) {
|
|
221
|
-
const projectDir =
|
|
222
|
-
if (!
|
|
741
|
+
const projectDir = join(claudeRoot, dir);
|
|
742
|
+
if (!statSync(projectDir).isDirectory())
|
|
223
743
|
continue;
|
|
224
|
-
const files =
|
|
744
|
+
const files = readdirSync(projectDir).filter((f) => f.endsWith('.jsonl'));
|
|
225
745
|
for (const f of files) {
|
|
226
|
-
const fp =
|
|
746
|
+
const fp = join(projectDir, f);
|
|
227
747
|
candidates.push({
|
|
228
748
|
path: fp,
|
|
229
|
-
sessionId:
|
|
749
|
+
sessionId: basename(f, '.jsonl'),
|
|
230
750
|
projectId: dir,
|
|
231
751
|
});
|
|
232
752
|
}
|
|
@@ -235,15 +755,15 @@ function findLatestSession(cwd) {
|
|
|
235
755
|
}
|
|
236
756
|
catch { /* no Claude sessions */ }
|
|
237
757
|
// 2. Codex: ~/.codex/sessions/**/*.jsonl (scan all, match cwd from session_meta)
|
|
238
|
-
const codexRoot =
|
|
758
|
+
const codexRoot = join(homedir(), '.codex', 'sessions');
|
|
239
759
|
try {
|
|
240
|
-
const codexFiles = findJsonlRecursive(codexRoot
|
|
760
|
+
const codexFiles = findJsonlRecursive(codexRoot);
|
|
241
761
|
for (const fp of codexFiles) {
|
|
242
762
|
// Quick check: read first line for cwd match.
|
|
243
763
|
try {
|
|
244
|
-
const firstLine =
|
|
764
|
+
const firstLine = readFileSync(fp, 'utf8').split('\n')[0] ?? '';
|
|
245
765
|
if (firstLine.includes(cwd) || firstLine.includes(lastPart)) {
|
|
246
|
-
const name =
|
|
766
|
+
const name = basename(fp, '.jsonl');
|
|
247
767
|
const uuidMatch = name.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
248
768
|
candidates.push({
|
|
249
769
|
path: fp,
|
|
@@ -258,18 +778,18 @@ function findLatestSession(cwd) {
|
|
|
258
778
|
catch { /* no Codex sessions */ }
|
|
259
779
|
// 3. Cursor: ~/.cursor/projects/<encoded>/agent-transcripts/<uuid>/<uuid>.jsonl
|
|
260
780
|
const cursorEncoded = cwd.replace(/\//g, '-').replace(/^-/, '');
|
|
261
|
-
const cursorRoot =
|
|
781
|
+
const cursorRoot = join(homedir(), '.cursor', 'projects');
|
|
262
782
|
try {
|
|
263
|
-
const dirs =
|
|
783
|
+
const dirs = readdirSync(cursorRoot);
|
|
264
784
|
for (const dir of dirs) {
|
|
265
785
|
if (dir === cursorEncoded || dir.endsWith(lastPart)) {
|
|
266
|
-
const transcriptsDir =
|
|
786
|
+
const transcriptsDir = join(cursorRoot, dir, 'agent-transcripts');
|
|
267
787
|
try {
|
|
268
|
-
const sessionDirs =
|
|
788
|
+
const sessionDirs = readdirSync(transcriptsDir);
|
|
269
789
|
for (const sd of sessionDirs) {
|
|
270
|
-
const fp =
|
|
790
|
+
const fp = join(transcriptsDir, sd, `${sd}.jsonl`);
|
|
271
791
|
try {
|
|
272
|
-
|
|
792
|
+
statSync(fp);
|
|
273
793
|
candidates.push({
|
|
274
794
|
path: fp,
|
|
275
795
|
sessionId: sd,
|
|
@@ -289,7 +809,7 @@ function findLatestSession(cwd) {
|
|
|
289
809
|
// Return the most recently modified file.
|
|
290
810
|
candidates.sort((a, b) => {
|
|
291
811
|
try {
|
|
292
|
-
return
|
|
812
|
+
return statSync(b.path).mtimeMs - statSync(a.path).mtimeMs;
|
|
293
813
|
}
|
|
294
814
|
catch {
|
|
295
815
|
return 0;
|
|
@@ -297,14 +817,14 @@ function findLatestSession(cwd) {
|
|
|
297
817
|
});
|
|
298
818
|
return candidates[0];
|
|
299
819
|
}
|
|
300
|
-
function findJsonlRecursive(dir
|
|
820
|
+
function findJsonlRecursive(dir) {
|
|
301
821
|
const results = [];
|
|
302
822
|
try {
|
|
303
|
-
const entries =
|
|
823
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
304
824
|
for (const entry of entries) {
|
|
305
|
-
const full =
|
|
825
|
+
const full = join(dir, entry.name);
|
|
306
826
|
if (entry.isDirectory())
|
|
307
|
-
results.push(...findJsonlRecursive(full
|
|
827
|
+
results.push(...findJsonlRecursive(full));
|
|
308
828
|
else if (entry.name.endsWith('.jsonl'))
|
|
309
829
|
results.push(full);
|
|
310
830
|
}
|
|
@@ -313,15 +833,13 @@ function findJsonlRecursive(dir, fs, path) {
|
|
|
313
833
|
return results;
|
|
314
834
|
}
|
|
315
835
|
function findSessionById(id) {
|
|
316
|
-
const fs = require('node:fs');
|
|
317
|
-
const path = require('node:path');
|
|
318
836
|
// Search Claude Code.
|
|
319
|
-
const claudeRoot =
|
|
837
|
+
const claudeRoot = join(homedir(), '.claude', 'projects');
|
|
320
838
|
try {
|
|
321
|
-
for (const dir of
|
|
322
|
-
const fp =
|
|
839
|
+
for (const dir of readdirSync(claudeRoot)) {
|
|
840
|
+
const fp = join(claudeRoot, dir, `${id}.jsonl`);
|
|
323
841
|
try {
|
|
324
|
-
|
|
842
|
+
statSync(fp);
|
|
325
843
|
return { path: fp, sessionId: id, projectId: dir };
|
|
326
844
|
}
|
|
327
845
|
catch { /* skip */ }
|
|
@@ -329,12 +847,12 @@ function findSessionById(id) {
|
|
|
329
847
|
}
|
|
330
848
|
catch { /* no Claude */ }
|
|
331
849
|
// Search Cursor.
|
|
332
|
-
const cursorRoot =
|
|
850
|
+
const cursorRoot = join(homedir(), '.cursor', 'projects');
|
|
333
851
|
try {
|
|
334
|
-
for (const dir of
|
|
335
|
-
const fp =
|
|
852
|
+
for (const dir of readdirSync(cursorRoot)) {
|
|
853
|
+
const fp = join(cursorRoot, dir, 'agent-transcripts', id, `${id}.jsonl`);
|
|
336
854
|
try {
|
|
337
|
-
|
|
855
|
+
statSync(fp);
|
|
338
856
|
return { path: fp, sessionId: id, projectId: `cursor:${dir}` };
|
|
339
857
|
}
|
|
340
858
|
catch { /* skip */ }
|
|
@@ -342,8 +860,8 @@ function findSessionById(id) {
|
|
|
342
860
|
}
|
|
343
861
|
catch { /* no Cursor */ }
|
|
344
862
|
// Search Codex (scan all, match by UUID in filename).
|
|
345
|
-
const codexRoot =
|
|
346
|
-
const codexFiles = findJsonlRecursive(codexRoot
|
|
863
|
+
const codexRoot = join(homedir(), '.codex', 'sessions');
|
|
864
|
+
const codexFiles = findJsonlRecursive(codexRoot);
|
|
347
865
|
for (const fp of codexFiles) {
|
|
348
866
|
if (fp.includes(id)) {
|
|
349
867
|
return { path: fp, sessionId: id, projectId: null };
|
|
@@ -357,7 +875,6 @@ function findSessionById(id) {
|
|
|
357
875
|
* names + file paths, and error outputs. Capped at 4K chars.
|
|
358
876
|
*/
|
|
359
877
|
function extractLocalSummaryInput(filePath) {
|
|
360
|
-
const { readFileSync } = require('node:fs');
|
|
361
878
|
const raw = readFileSync(filePath, 'utf8');
|
|
362
879
|
const lines = raw.split('\n').filter(Boolean);
|
|
363
880
|
const parts = [];
|
|
@@ -456,11 +973,10 @@ function extractLocalSummaryInput(filePath) {
|
|
|
456
973
|
return parts.join('\n').slice(0, 4000);
|
|
457
974
|
}
|
|
458
975
|
async function runPull(slug) {
|
|
459
|
-
|
|
976
|
+
let auth = loadAuth();
|
|
460
977
|
if (!auth) {
|
|
461
|
-
console.
|
|
462
|
-
|
|
463
|
-
return;
|
|
978
|
+
console.log(' ▸ not signed in, starting login...');
|
|
979
|
+
auth = await runLogin();
|
|
464
980
|
}
|
|
465
981
|
console.log(`promptarc pull ${slug}`);
|
|
466
982
|
console.log(' ▸ downloading session...');
|