promptarc 0.0.2 → 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 +886 -4
- package/dist/share-direct.js +360 -0
- package/dist/web/.next/BUILD_ID +1 -1
- package/dist/web/.next/app-build-manifest.json +318 -226
- package/dist/web/.next/app-path-routes-manifest.json +24 -13
- package/dist/web/.next/build-manifest.json +14 -14
- package/dist/web/.next/prerender-manifest.json +89 -1
- package/dist/web/.next/routes-manifest.json +36 -0
- package/dist/web/.next/server/app/_not-found/page.js +1 -1
- 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.js +8 -0
- package/dist/web/.next/server/app/api/admin/backfill-ai/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/api/admin/backfill-ai/route_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/api/admin/backfill-search/route.js +1 -1
- package/dist/web/.next/server/app/api/admin/backfill-search/route.js.nft.json +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.js +28 -0
- package/dist/web/.next/server/app/api/ai/pr-summary/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/api/ai/pr-summary/route_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/api/ai/pr-summary-local/route.js +28 -0
- package/dist/web/.next/server/app/api/ai/pr-summary-local/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/api/ai/pr-summary-local/route_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/api/ai/process/route.js +8 -0
- package/dist/web/.next/server/app/api/ai/process/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/api/ai/process/route_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/api/cli/download/route.js +1 -1
- package/dist/web/.next/server/app/api/cli/download/route.js.nft.json +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.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.js +1 -1
- package/dist/web/.next/server/app/api/local/project-sessions/route.js.nft.json +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.js +2 -2
- package/dist/web/.next/server/app/api/local/watch/route.js.nft.json +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.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.js +1 -1
- package/dist/web/.next/server/app/api/search-index/route.js.nft.json +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.js +1 -1
- package/dist/web/.next/server/app/api/share/download/[slug]/route.js.nft.json +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.js +1 -1
- package/dist/web/.next/server/app/api/share/route.js.nft.json +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.js +2 -2
- package/dist/web/.next/server/app/api/upload/route.js.nft.json +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.js +2 -0
- package/dist/web/.next/server/app/blog/ai-coding-agents-2026/page.js.nft.json +1 -0
- package/dist/web/.next/server/app/blog/ai-coding-agents-2026/page_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/blog/page.js +2 -0
- package/dist/web/.next/server/app/blog/page.js.nft.json +1 -0
- package/dist/web/.next/server/app/blog/page_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/cli/login/page.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.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.js +1 -71
- package/dist/web/.next/server/app/opengraph-image/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/opengraph-image/route_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/opengraph-image.body +0 -0
- package/dist/web/.next/server/app/opengraph-image.meta +1 -0
- package/dist/web/.next/server/app/page.js +2 -2
- package/dist/web/.next/server/app/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/privacy/page.js +2 -0
- package/dist/web/.next/server/app/privacy/page.js.nft.json +1 -0
- package/dist/web/.next/server/app/privacy/page_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/projects/[id]/page.js +1 -1
- package/dist/web/.next/server/app/projects/[id]/page.js.nft.json +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.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 +2 -2
- package/dist/web/.next/server/app/r/[slug]/page.js.nft.json +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 +16 -0
- package/dist/web/.next/server/app/robots.txt/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/robots.txt/route_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/robots.txt.body +9 -0
- package/dist/web/.next/server/app/robots.txt.meta +1 -0
- package/dist/web/.next/server/app/search/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/sessions/[projectId]/[sessionId]/page.js +1 -1
- package/dist/web/.next/server/app/sessions/[projectId]/[sessionId]/page.js.nft.json +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.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 +16 -0
- package/dist/web/.next/server/app/sitemap.xml/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/sitemap.xml/route_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app/sitemap.xml.body +51 -0
- package/dist/web/.next/server/app/sitemap.xml.meta +1 -0
- package/dist/web/.next/server/app/terms/page.js +2 -0
- package/dist/web/.next/server/app/terms/page.js.nft.json +1 -0
- package/dist/web/.next/server/app/terms/page_client-reference-manifest.js +1 -0
- package/dist/web/.next/server/app-paths-manifest.json +24 -13
- package/dist/web/.next/server/chunks/1017.js +2 -0
- package/dist/web/.next/server/chunks/1134.js +8 -0
- package/dist/web/.next/server/chunks/1825.js +1 -1
- package/dist/web/.next/server/chunks/8334.js +1 -1
- package/dist/web/.next/server/functions-config-manifest.json +17 -2
- package/dist/web/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/.next/server/middleware-manifest.json +1 -43
- 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/1029-12dc6d00537d43fd.js +1 -0
- package/dist/web/.next/static/chunks/4695-b52fc3d81451c3c3.js +1 -0
- package/dist/web/.next/static/chunks/552926ff-c5d04ae0c2d9f826.js +1 -0
- package/dist/web/.next/static/chunks/7200-4560b15b9cad37b3.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/blog/layout-9b51270472318488.js +1 -0
- package/dist/web/.next/static/chunks/app/blog/page-8400ea4ac5a7eb44.js +1 -0
- package/dist/web/.next/static/chunks/app/cli/login/page-26287cfd64c0fb49.js +1 -0
- package/dist/web/.next/static/chunks/app/digest/page-8400ea4ac5a7eb44.js +1 -0
- package/dist/web/.next/static/chunks/app/feedback/page-55ef0005807f1cef.js +1 -0
- package/dist/web/.next/static/chunks/app/layout-aa56caf2bf5af86f.js +1 -0
- package/dist/web/.next/static/chunks/app/my-projects/[projectKey]/page-7c5f2badf689d2e8.js +1 -0
- package/dist/web/.next/static/chunks/app/my-projects/page-c0390151fd6332a0.js +1 -0
- package/dist/web/.next/static/chunks/app/my-shares/{page-697d61e07a7546b2.js → page-49c0112973401c14.js} +1 -1
- package/dist/web/.next/static/chunks/app/not-found-8400ea4ac5a7eb44.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-95394e3c6370084f.js +1 -0
- package/dist/web/.next/static/chunks/app/privacy/page-9b51270472318488.js +1 -0
- package/dist/web/.next/static/chunks/app/projects/[id]/page-7dd46a825d03a7d9.js +1 -0
- package/dist/web/.next/static/chunks/app/r/[slug]/opengraph-image/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/r/[slug]/page-6a0983805e048ac3.js +1 -0
- package/dist/web/.next/static/chunks/app/robots.txt/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/search/page-c29f37dc2462b676.js +1 -0
- package/dist/web/.next/static/chunks/app/sessions/[projectId]/[sessionId]/page-87b4505dd32edb9d.js +1 -0
- package/dist/web/.next/static/chunks/app/sign-in/page-fea3971ff9237b72.js +1 -0
- package/dist/web/.next/static/chunks/app/sitemap.xml/route-0f1123e35eb8f046.js +1 -0
- package/dist/web/.next/static/chunks/app/terms/page-9b51270472318488.js +1 -0
- package/dist/web/.next/static/chunks/d99d8e6a-55da88efbe15e3e1.js +1 -0
- package/dist/web/.next/static/chunks/framework-c1efa271b5a96d9d.js +1 -0
- package/dist/web/.next/static/chunks/main-app-70de904c7caade50.js +1 -0
- package/dist/web/.next/static/chunks/main-d211cddc79aac1cb.js +1 -0
- package/dist/web/.next/static/chunks/pages/{_app-42b849402a54b9e6.js → _app-701b0752a0d4c3a9.js} +1 -1
- package/dist/web/.next/static/chunks/pages/_error-327385abc873a736.js +1 -0
- package/dist/web/.next/static/chunks/{webpack-3a8b65d12c8eb987.js → webpack-3a446128dfa39f85.js} +1 -1
- package/dist/web/.next/static/css/c76bb7513858c50c.css +3 -0
- package/dist/web/package.json +1 -0
- package/dist/web/public/apple-touch-icon.png +0 -0
- package/dist/web/public/favicon-16.png +0 -0
- package/dist/web/public/favicon-32.png +0 -0
- package/dist/web/public/icon-192.png +0 -0
- package/dist/web/public/icon-512.png +0 -0
- package/dist/web/public/logo.png +0 -0
- package/package.json +1 -1
- package/dist/web/.next/server/app/opengraph-image/route.js.map +0 -1
- package/dist/web/.next/server/chunks/6019.js +0 -2
- package/dist/web/.next/server/edge-chunks/asset_noto-sans-v27-latin-regular.5dda3fca77107598.ttf +0 -0
- package/dist/web/.next/server/edge-chunks/wasm_77d9faebf7af9e421806970ce10a58e9d83116d7.wasm +0 -0
- package/dist/web/.next/server/edge-chunks/wasm_ef4866ecae192fd87727067cf2c0c0cf9fb8b020.wasm +0 -0
- package/dist/web/.next/server/edge-runtime-webpack.js +0 -2
- package/dist/web/.next/server/interception-route-rewrite-manifest.js +0 -1
- package/dist/web/.next/static/ZxFskpyUoxefJOzSxSb6s/_buildManifest.js +0 -1
- package/dist/web/.next/static/chunks/200-619918ea0fdbd6ff.js +0 -1
- package/dist/web/.next/static/chunks/29-7904810aeffd75a0.js +0 -1
- package/dist/web/.next/static/chunks/552926ff-e8a371ec0133d0dc.js +0 -1
- package/dist/web/.next/static/chunks/695-00bca043953d4170.js +0 -1
- package/dist/web/.next/static/chunks/app/_not-found/page-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/admin/backfill-search/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/download/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/feedback/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/init/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/my-shares/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/poll/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/project-settings/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/share-mutate/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/cli/sync-list/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/local/project-sessions/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/local/watch/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/search/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/search-index/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/share/download/[slug]/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/share/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/api/upload/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/auth/callback/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/auth/signout/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/cli/login/page-647fd605d0fe2472.js +0 -1
- package/dist/web/.next/static/chunks/app/digest/page-41ec674edc998616.js +0 -1
- package/dist/web/.next/static/chunks/app/feedback/page-60f4d489fd598c9c.js +0 -1
- package/dist/web/.next/static/chunks/app/layout-f5666b9135264807.js +0 -1
- package/dist/web/.next/static/chunks/app/my-projects/[projectKey]/page-3a816489e5e8534b.js +0 -1
- package/dist/web/.next/static/chunks/app/my-projects/page-c88eba56cb5d0b8d.js +0 -1
- package/dist/web/.next/static/chunks/app/not-found-41ec674edc998616.js +0 -1
- package/dist/web/.next/static/chunks/app/opengraph-image/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/page-e6e80157848514e3.js +0 -1
- package/dist/web/.next/static/chunks/app/projects/[id]/page-faec0dc5b09bd35f.js +0 -1
- package/dist/web/.next/static/chunks/app/r/[slug]/opengraph-image/route-bd7ebde8ab75cb95.js +0 -1
- package/dist/web/.next/static/chunks/app/r/[slug]/page-65707eb198f673a1.js +0 -1
- package/dist/web/.next/static/chunks/app/search/page-fc0b629efa815c64.js +0 -1
- package/dist/web/.next/static/chunks/app/sessions/[projectId]/[sessionId]/page-5e70eaa0a429d94a.js +0 -1
- package/dist/web/.next/static/chunks/app/sign-in/page-792d0d79889186cf.js +0 -1
- package/dist/web/.next/static/chunks/d99d8e6a-80443bd3c9d31f50.js +0 -1
- package/dist/web/.next/static/chunks/framework-39000e34f38d10f6.js +0 -1
- package/dist/web/.next/static/chunks/main-app-21e9441fa7ac15ef.js +0 -1
- package/dist/web/.next/static/chunks/main-f824d48fd7ff2e3b.js +0 -1
- package/dist/web/.next/static/chunks/pages/_error-412ff705bc5b0636.js +0 -1
- package/dist/web/.next/static/css/3c4744a7426424ee.css +0 -3
- /package/dist/web/.next/static/{ZxFskpyUoxefJOzSxSb6s → 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,6 +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
|
+
}
|
|
64
|
+
if (cmd === 'pr') {
|
|
65
|
+
await runPR(args.slice(1));
|
|
66
|
+
return;
|
|
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
|
+
}
|
|
57
79
|
if (cmd === 'pull') {
|
|
58
80
|
const slug = args[1];
|
|
59
81
|
if (!slug) {
|
|
@@ -81,6 +103,10 @@ COMMANDS
|
|
|
81
103
|
login Sign in to your promptarc account via GitHub
|
|
82
104
|
logout Delete the saved auth token
|
|
83
105
|
whoami Print the currently signed-in account
|
|
106
|
+
context What do I need to know? Past sessions for this project
|
|
107
|
+
pr Generate a PR description from the current session
|
|
108
|
+
pr --share Generate + share session as private (includes link)
|
|
109
|
+
share <id> Share a local session to the cloud (private by default)
|
|
84
110
|
pull <slug> Download a shared session into ~/.claude/ for resume
|
|
85
111
|
--version Print the CLI version
|
|
86
112
|
--help Show this message
|
|
@@ -89,13 +115,869 @@ LEARN MORE
|
|
|
89
115
|
https://promptarc.dev
|
|
90
116
|
`);
|
|
91
117
|
}
|
|
92
|
-
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).
|
|
93
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();
|
|
464
|
+
if (!auth) {
|
|
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)}`);
|
|
550
|
+
process.exitCode = 1;
|
|
551
|
+
return;
|
|
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();
|
|
94
623
|
if (!auth) {
|
|
95
|
-
console.
|
|
624
|
+
console.log(' ▸ not signed in, starting login...');
|
|
625
|
+
auth = await runLogin();
|
|
626
|
+
}
|
|
627
|
+
const shouldShare = args.includes('--share');
|
|
628
|
+
const sessionIdArg = args.find((a) => !a.startsWith('--'));
|
|
629
|
+
const copyFlag = args.includes('--copy');
|
|
630
|
+
// 1. Find the session.
|
|
631
|
+
const cwd = process.cwd();
|
|
632
|
+
const sessionFile = sessionIdArg
|
|
633
|
+
? findSessionById(sessionIdArg)
|
|
634
|
+
: findLatestSession(cwd);
|
|
635
|
+
if (!sessionFile) {
|
|
636
|
+
console.error(sessionIdArg
|
|
637
|
+
? `Session ${sessionIdArg} not found.`
|
|
638
|
+
: `No sessions found for ${cwd}. Run this from a project directory with Claude Code/Codex/Cursor sessions.`);
|
|
639
|
+
process.exitCode = 1;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
console.log(` ▸ reading ${sessionFile.path}`);
|
|
643
|
+
// 2. Extract summary input from the local JSONL.
|
|
644
|
+
const input = extractLocalSummaryInput(sessionFile.path);
|
|
645
|
+
if (input.length < 20) {
|
|
646
|
+
console.error('Session is too short to generate a PR description.');
|
|
647
|
+
process.exitCode = 1;
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
// 3. If --share, share the session first via the local dashboard.
|
|
651
|
+
let shareUrl = '';
|
|
652
|
+
if (shouldShare) {
|
|
653
|
+
console.log(' ▸ sharing session as private...');
|
|
654
|
+
const localOrigin = 'http://localhost:3030';
|
|
655
|
+
try {
|
|
656
|
+
const shareRes = await fetch(`${localOrigin}/api/share`, {
|
|
657
|
+
method: 'POST',
|
|
658
|
+
headers: { 'content-type': 'application/json' },
|
|
659
|
+
body: JSON.stringify({
|
|
660
|
+
sessionId: sessionFile.sessionId,
|
|
661
|
+
projectId: sessionFile.projectId,
|
|
662
|
+
visibility: 'private',
|
|
663
|
+
_h: '',
|
|
664
|
+
}),
|
|
665
|
+
});
|
|
666
|
+
if (shareRes.ok) {
|
|
667
|
+
const json = (await shareRes.json());
|
|
668
|
+
shareUrl = json.url ?? '';
|
|
669
|
+
console.log(` ▸ shared: ${shareUrl}`);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
console.error(' ▸ share failed (dashboard may not be running). Continuing without link.');
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
console.error(' ▸ could not reach local dashboard. Run `promptarc` first, or skip --share.');
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// 4. Generate PR description via hosted API.
|
|
680
|
+
console.log(' ▸ generating PR description...');
|
|
681
|
+
const siteUrl = process.env.PROMPTARC_SITE_URL ?? 'https://promptarc.dev';
|
|
682
|
+
let markdown;
|
|
683
|
+
try {
|
|
684
|
+
const res = await fetch(`${siteUrl}/api/ai/pr-summary-local`, {
|
|
685
|
+
method: 'POST',
|
|
686
|
+
headers: {
|
|
687
|
+
'content-type': 'application/json',
|
|
688
|
+
authorization: `Bearer ${auth.token}`,
|
|
689
|
+
},
|
|
690
|
+
body: JSON.stringify({ input }),
|
|
691
|
+
});
|
|
692
|
+
if (!res.ok) {
|
|
693
|
+
const json = (await res.json());
|
|
694
|
+
console.error(` ✗ ${json.error ?? `HTTP ${res.status}`}`);
|
|
695
|
+
process.exitCode = 1;
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const json = (await res.json());
|
|
699
|
+
markdown = json.markdown;
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
console.error(` ✗ Failed to reach ${siteUrl}: ${String(err)}`);
|
|
96
703
|
process.exitCode = 1;
|
|
97
704
|
return;
|
|
98
705
|
}
|
|
706
|
+
// 5. Append session link if shared.
|
|
707
|
+
if (shareUrl) {
|
|
708
|
+
markdown += `\n\n---\n*Generated by [promptarc](https://promptarc.dev) from [session →](${shareUrl})*`;
|
|
709
|
+
}
|
|
710
|
+
// 6. Output.
|
|
711
|
+
if (copyFlag) {
|
|
712
|
+
try {
|
|
713
|
+
const cmd = process.platform === 'darwin'
|
|
714
|
+
? 'pbcopy'
|
|
715
|
+
: process.platform === 'win32'
|
|
716
|
+
? 'clip'
|
|
717
|
+
: 'xclip -selection clipboard';
|
|
718
|
+
const { execSync } = await import('node:child_process');
|
|
719
|
+
execSync(cmd, { input: markdown });
|
|
720
|
+
console.log(' ✓ Copied to clipboard.\n');
|
|
721
|
+
}
|
|
722
|
+
catch {
|
|
723
|
+
// Fall through to stdout.
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
console.log('');
|
|
727
|
+
console.log(markdown);
|
|
728
|
+
console.log('');
|
|
729
|
+
}
|
|
730
|
+
function findLatestSession(cwd) {
|
|
731
|
+
const candidates = [];
|
|
732
|
+
const cwdParts = cwd.split('/').filter(Boolean);
|
|
733
|
+
const lastPart = cwdParts[cwdParts.length - 1] ?? '';
|
|
734
|
+
const encoded = cwd.replace(/\//g, '-');
|
|
735
|
+
// 1. Claude Code: ~/.claude/projects/<encoded-cwd>/*.jsonl
|
|
736
|
+
const claudeRoot = join(homedir(), '.claude', 'projects');
|
|
737
|
+
try {
|
|
738
|
+
const dirs = readdirSync(claudeRoot);
|
|
739
|
+
for (const dir of dirs) {
|
|
740
|
+
if (dir === encoded || dir.endsWith(lastPart)) {
|
|
741
|
+
const projectDir = join(claudeRoot, dir);
|
|
742
|
+
if (!statSync(projectDir).isDirectory())
|
|
743
|
+
continue;
|
|
744
|
+
const files = readdirSync(projectDir).filter((f) => f.endsWith('.jsonl'));
|
|
745
|
+
for (const f of files) {
|
|
746
|
+
const fp = join(projectDir, f);
|
|
747
|
+
candidates.push({
|
|
748
|
+
path: fp,
|
|
749
|
+
sessionId: basename(f, '.jsonl'),
|
|
750
|
+
projectId: dir,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
catch { /* no Claude sessions */ }
|
|
757
|
+
// 2. Codex: ~/.codex/sessions/**/*.jsonl (scan all, match cwd from session_meta)
|
|
758
|
+
const codexRoot = join(homedir(), '.codex', 'sessions');
|
|
759
|
+
try {
|
|
760
|
+
const codexFiles = findJsonlRecursive(codexRoot);
|
|
761
|
+
for (const fp of codexFiles) {
|
|
762
|
+
// Quick check: read first line for cwd match.
|
|
763
|
+
try {
|
|
764
|
+
const firstLine = readFileSync(fp, 'utf8').split('\n')[0] ?? '';
|
|
765
|
+
if (firstLine.includes(cwd) || firstLine.includes(lastPart)) {
|
|
766
|
+
const name = basename(fp, '.jsonl');
|
|
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);
|
|
768
|
+
candidates.push({
|
|
769
|
+
path: fp,
|
|
770
|
+
sessionId: uuidMatch?.[1] ?? name,
|
|
771
|
+
projectId: `codex:${cwd.replace(/\//g, '-')}`,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
catch { /* skip */ }
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
catch { /* no Codex sessions */ }
|
|
779
|
+
// 3. Cursor: ~/.cursor/projects/<encoded>/agent-transcripts/<uuid>/<uuid>.jsonl
|
|
780
|
+
const cursorEncoded = cwd.replace(/\//g, '-').replace(/^-/, '');
|
|
781
|
+
const cursorRoot = join(homedir(), '.cursor', 'projects');
|
|
782
|
+
try {
|
|
783
|
+
const dirs = readdirSync(cursorRoot);
|
|
784
|
+
for (const dir of dirs) {
|
|
785
|
+
if (dir === cursorEncoded || dir.endsWith(lastPart)) {
|
|
786
|
+
const transcriptsDir = join(cursorRoot, dir, 'agent-transcripts');
|
|
787
|
+
try {
|
|
788
|
+
const sessionDirs = readdirSync(transcriptsDir);
|
|
789
|
+
for (const sd of sessionDirs) {
|
|
790
|
+
const fp = join(transcriptsDir, sd, `${sd}.jsonl`);
|
|
791
|
+
try {
|
|
792
|
+
statSync(fp);
|
|
793
|
+
candidates.push({
|
|
794
|
+
path: fp,
|
|
795
|
+
sessionId: sd,
|
|
796
|
+
projectId: `cursor:${dir}`,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
catch { /* skip */ }
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
catch { /* no transcripts */ }
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
catch { /* no Cursor sessions */ }
|
|
807
|
+
if (candidates.length === 0)
|
|
808
|
+
return null;
|
|
809
|
+
// Return the most recently modified file.
|
|
810
|
+
candidates.sort((a, b) => {
|
|
811
|
+
try {
|
|
812
|
+
return statSync(b.path).mtimeMs - statSync(a.path).mtimeMs;
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
return 0;
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
return candidates[0];
|
|
819
|
+
}
|
|
820
|
+
function findJsonlRecursive(dir) {
|
|
821
|
+
const results = [];
|
|
822
|
+
try {
|
|
823
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
824
|
+
for (const entry of entries) {
|
|
825
|
+
const full = join(dir, entry.name);
|
|
826
|
+
if (entry.isDirectory())
|
|
827
|
+
results.push(...findJsonlRecursive(full));
|
|
828
|
+
else if (entry.name.endsWith('.jsonl'))
|
|
829
|
+
results.push(full);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
catch { /* skip */ }
|
|
833
|
+
return results;
|
|
834
|
+
}
|
|
835
|
+
function findSessionById(id) {
|
|
836
|
+
// Search Claude Code.
|
|
837
|
+
const claudeRoot = join(homedir(), '.claude', 'projects');
|
|
838
|
+
try {
|
|
839
|
+
for (const dir of readdirSync(claudeRoot)) {
|
|
840
|
+
const fp = join(claudeRoot, dir, `${id}.jsonl`);
|
|
841
|
+
try {
|
|
842
|
+
statSync(fp);
|
|
843
|
+
return { path: fp, sessionId: id, projectId: dir };
|
|
844
|
+
}
|
|
845
|
+
catch { /* skip */ }
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
catch { /* no Claude */ }
|
|
849
|
+
// Search Cursor.
|
|
850
|
+
const cursorRoot = join(homedir(), '.cursor', 'projects');
|
|
851
|
+
try {
|
|
852
|
+
for (const dir of readdirSync(cursorRoot)) {
|
|
853
|
+
const fp = join(cursorRoot, dir, 'agent-transcripts', id, `${id}.jsonl`);
|
|
854
|
+
try {
|
|
855
|
+
statSync(fp);
|
|
856
|
+
return { path: fp, sessionId: id, projectId: `cursor:${dir}` };
|
|
857
|
+
}
|
|
858
|
+
catch { /* skip */ }
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
catch { /* no Cursor */ }
|
|
862
|
+
// Search Codex (scan all, match by UUID in filename).
|
|
863
|
+
const codexRoot = join(homedir(), '.codex', 'sessions');
|
|
864
|
+
const codexFiles = findJsonlRecursive(codexRoot);
|
|
865
|
+
for (const fp of codexFiles) {
|
|
866
|
+
if (fp.includes(id)) {
|
|
867
|
+
return { path: fp, sessionId: id, projectId: null };
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Lightweight summary input extraction for the CLI. Reads the local
|
|
874
|
+
* JSONL and pulls: first user prompt, last user prompt, tool call
|
|
875
|
+
* names + file paths, and error outputs. Capped at 4K chars.
|
|
876
|
+
*/
|
|
877
|
+
function extractLocalSummaryInput(filePath) {
|
|
878
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
879
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
880
|
+
const parts = [];
|
|
881
|
+
const userPrompts = [];
|
|
882
|
+
const toolCalls = [];
|
|
883
|
+
const errors = [];
|
|
884
|
+
const filePaths = new Set();
|
|
885
|
+
for (const line of lines) {
|
|
886
|
+
let parsed;
|
|
887
|
+
try {
|
|
888
|
+
parsed = JSON.parse(line);
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
// Claude Code format.
|
|
894
|
+
const type = parsed.type;
|
|
895
|
+
const msg = parsed.message;
|
|
896
|
+
if (type === 'user' && typeof msg === 'string') {
|
|
897
|
+
userPrompts.push(msg);
|
|
898
|
+
}
|
|
899
|
+
if (typeof msg === 'object' && msg && Array.isArray(msg.content)) {
|
|
900
|
+
for (const block of msg.content) {
|
|
901
|
+
if (block.type === 'text' && typeof block.text === 'string') {
|
|
902
|
+
if (type === 'user')
|
|
903
|
+
userPrompts.push(block.text);
|
|
904
|
+
}
|
|
905
|
+
if (block.type === 'tool_use') {
|
|
906
|
+
const name = block.name;
|
|
907
|
+
const input = block.input;
|
|
908
|
+
const fp = input?.file_path ?? input?.path ?? '';
|
|
909
|
+
if (fp)
|
|
910
|
+
filePaths.add(fp);
|
|
911
|
+
if (input?.command)
|
|
912
|
+
toolCalls.push(`${name}: ${String(input.command).slice(0, 100)}`);
|
|
913
|
+
else if (fp)
|
|
914
|
+
toolCalls.push(`${name}: ${fp}`);
|
|
915
|
+
else
|
|
916
|
+
toolCalls.push(name);
|
|
917
|
+
}
|
|
918
|
+
if (block.type === 'tool_result' && block.is_error) {
|
|
919
|
+
const output = typeof block.content === 'string' ? block.content : String(block.output ?? '');
|
|
920
|
+
if (output)
|
|
921
|
+
errors.push(output.slice(0, 200));
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Codex format: {timestamp, type, payload}.
|
|
926
|
+
if (parsed.payload && typeof parsed.payload === 'object') {
|
|
927
|
+
const payload = parsed.payload;
|
|
928
|
+
if (payload.type === 'message' && payload.role === 'user') {
|
|
929
|
+
const content = payload.content;
|
|
930
|
+
if (Array.isArray(content)) {
|
|
931
|
+
for (const block of content) {
|
|
932
|
+
if ((block.type === 'input_text' || block.type === 'text') && typeof block.text === 'string') {
|
|
933
|
+
userPrompts.push(block.text);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
// Cursor format: {role, message: {content}}.
|
|
940
|
+
if (parsed.role === 'user' && typeof parsed.message === 'object' && parsed.message) {
|
|
941
|
+
const content = parsed.message.content;
|
|
942
|
+
if (Array.isArray(content)) {
|
|
943
|
+
for (const block of content) {
|
|
944
|
+
if (block.type === 'text' && typeof block.text === 'string') {
|
|
945
|
+
const text = block.text.replace(/<\/?user_query>/g, '').trim();
|
|
946
|
+
if (text)
|
|
947
|
+
userPrompts.push(text);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (userPrompts.length > 0) {
|
|
954
|
+
parts.push('--- First prompt ---');
|
|
955
|
+
parts.push(userPrompts[0].slice(0, 500));
|
|
956
|
+
}
|
|
957
|
+
if (userPrompts.length > 1) {
|
|
958
|
+
parts.push('--- Last prompt ---');
|
|
959
|
+
parts.push(userPrompts[userPrompts.length - 1].slice(0, 300));
|
|
960
|
+
}
|
|
961
|
+
if (toolCalls.length > 0) {
|
|
962
|
+
parts.push('--- Tool calls ---');
|
|
963
|
+
parts.push([...new Set(toolCalls)].slice(0, 15).join('\n'));
|
|
964
|
+
}
|
|
965
|
+
if (filePaths.size > 0) {
|
|
966
|
+
parts.push(`--- Files (${filePaths.size}) ---`);
|
|
967
|
+
parts.push([...filePaths].slice(0, 20).join(', '));
|
|
968
|
+
}
|
|
969
|
+
if (errors.length > 0) {
|
|
970
|
+
parts.push('--- Errors ---');
|
|
971
|
+
parts.push(errors.slice(0, 3).join('\n'));
|
|
972
|
+
}
|
|
973
|
+
return parts.join('\n').slice(0, 4000);
|
|
974
|
+
}
|
|
975
|
+
async function runPull(slug) {
|
|
976
|
+
let auth = loadAuth();
|
|
977
|
+
if (!auth) {
|
|
978
|
+
console.log(' ▸ not signed in, starting login...');
|
|
979
|
+
auth = await runLogin();
|
|
980
|
+
}
|
|
99
981
|
console.log(`promptarc pull ${slug}`);
|
|
100
982
|
console.log(' ▸ downloading session...');
|
|
101
983
|
const siteUrl = process.env.PROMPTARC_SITE_URL ?? 'https://promptarc.dev';
|