osborn 0.9.122 → 0.9.123
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/dist/index.js +54 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -815,6 +815,60 @@ function startApiServer(workingDir, port) {
|
|
|
815
815
|
res.destroy(); });
|
|
816
816
|
return;
|
|
817
817
|
}
|
|
818
|
+
// GET /sessions/export-one?sessionId=X — tar.gz of a SINGLE session's files
|
|
819
|
+
// (the .jsonl, its sidecar dir, and its osb/ workspace), for SESSION SHARING
|
|
820
|
+
// (0.9.123): the frontend fetches this from the owner's machine, uploads it
|
|
821
|
+
// to Supabase Storage, and the recipient's machine imports it via the
|
|
822
|
+
// existing POST /sessions/import (slug-remapped into the recipient's
|
|
823
|
+
// workspace). Note: gated only by knowing the machine URL + the session UUID
|
|
824
|
+
// for the MVP — a per-share access token is a follow-up (tracked in
|
|
825
|
+
// shared_sessions). sessionId is regex-validated to block path traversal.
|
|
826
|
+
if (req.method === 'GET' && url.pathname === '/sessions/export-one') {
|
|
827
|
+
const sessionId = (url.searchParams.get('sessionId') || '').trim();
|
|
828
|
+
if (!sessionId || !/^[a-zA-Z0-9._-]+$/.test(sessionId)) {
|
|
829
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
830
|
+
res.end(JSON.stringify({ error: 'bad or missing sessionId' }));
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const claudeDir = join(homedir(), '.claude');
|
|
834
|
+
const projectsDir = join(claudeDir, 'projects');
|
|
835
|
+
if (!existsSync(projectsDir)) {
|
|
836
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
837
|
+
res.end(JSON.stringify({ error: 'no projects dir' }));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
// Find the project slug whose folder contains {sessionId}.jsonl.
|
|
841
|
+
let foundSlug = null;
|
|
842
|
+
for (const slug of readdirSync(projectsDir)) {
|
|
843
|
+
if (existsSync(join(projectsDir, slug, `${sessionId}.jsonl`))) {
|
|
844
|
+
foundSlug = slug;
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (!foundSlug) {
|
|
849
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
850
|
+
res.end(JSON.stringify({ error: 'session not found' }));
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
// Only tar the paths that exist (tar errors on a missing member).
|
|
854
|
+
const members = [join('projects', foundSlug, `${sessionId}.jsonl`)];
|
|
855
|
+
if (existsSync(join(projectsDir, foundSlug, sessionId)))
|
|
856
|
+
members.push(join('projects', foundSlug, sessionId));
|
|
857
|
+
if (existsSync(join(projectsDir, foundSlug, 'osb', sessionId)))
|
|
858
|
+
members.push(join('projects', foundSlug, 'osb', sessionId));
|
|
859
|
+
console.log(`📤 export-one session ${sessionId} (slug ${foundSlug}, ${members.length} member(s))`);
|
|
860
|
+
res.writeHead(200, {
|
|
861
|
+
'Content-Type': 'application/gzip',
|
|
862
|
+
'Content-Disposition': `attachment; filename="session-${sessionId}.tar.gz"`,
|
|
863
|
+
'Access-Control-Allow-Origin': '*',
|
|
864
|
+
});
|
|
865
|
+
const tar = spawn('tar', ['-czf', '-', '-C', claudeDir, ...members]);
|
|
866
|
+
tar.stdout.pipe(res);
|
|
867
|
+
tar.stderr.on('data', (d) => console.error('[export-one]', d.toString()));
|
|
868
|
+
tar.on('close', (code) => { if (code !== 0)
|
|
869
|
+
res.destroy(); });
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
818
872
|
// GET /sessions/manifest — return mtime+size for all .jsonl files per slug (public, no auth)
|
|
819
873
|
// Helper: merge an extracted tar directory into ~/.claude/{projects,skills}/.
|
|
820
874
|
//
|