@halyard/cli 0.1.0
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/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +17 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +171 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +83 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +83 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/lib/api-client.d.ts +9 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +31 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/auth.d.ts +25 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +84 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +11 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +16 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/discovery.d.ts +22 -0
- package/dist/lib/discovery.d.ts.map +1 -0
- package/dist/lib/discovery.js +85 -0
- package/dist/lib/discovery.js.map +1 -0
- package/dist/lib/sync-state.d.ts +31 -0
- package/dist/lib/sync-state.d.ts.map +1 -0
- package/dist/lib/sync-state.js +42 -0
- package/dist/lib/sync-state.js.map +1 -0
- package/package.json +33 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { DEFAULT_API_URL } from './lib/config.js';
|
|
4
|
+
import { loginCommand } from './commands/login.js';
|
|
5
|
+
import { syncCommand } from './commands/sync.js';
|
|
6
|
+
import { pushCommand } from './commands/push.js';
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name('halyard')
|
|
10
|
+
.description('Sync coding session logs to Halyard')
|
|
11
|
+
.version('0.1.0')
|
|
12
|
+
.option('--api-url <url>', 'Halyard API URL', DEFAULT_API_URL);
|
|
13
|
+
program.addCommand(loginCommand());
|
|
14
|
+
program.addCommand(syncCommand());
|
|
15
|
+
program.addCommand(pushCommand());
|
|
16
|
+
program.parse();
|
|
17
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,qCAAqC,CAAC;KAClD,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAAA;AAEhE,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC,CAAA;AAClC,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAA;AACjC,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAA;AAEjC,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAkBnC,wBAAgB,YAAY,IAAI,OAAO,CAmKtC"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import { saveCredentials } from '../lib/auth.js';
|
|
6
|
+
/**
|
|
7
|
+
* Generate a PKCE code verifier (RFC 7636)
|
|
8
|
+
*/
|
|
9
|
+
function generateCodeVerifier() {
|
|
10
|
+
return randomBytes(32).toString('base64url');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate a PKCE code challenge from a verifier
|
|
14
|
+
*/
|
|
15
|
+
function generateCodeChallenge(verifier) {
|
|
16
|
+
return createHash('sha256').update(verifier).digest('base64url');
|
|
17
|
+
}
|
|
18
|
+
export function loginCommand() {
|
|
19
|
+
return new Command('login')
|
|
20
|
+
.description('Authenticate with Halyard via browser login')
|
|
21
|
+
.action(async function () {
|
|
22
|
+
const apiUrl = this.optsWithGlobals().apiUrl;
|
|
23
|
+
console.log('Logging in to Halyard...');
|
|
24
|
+
// Step 1: Dynamic client registration
|
|
25
|
+
let clientId;
|
|
26
|
+
const callbackPort = await findOpenPort();
|
|
27
|
+
const redirectUri = `http://127.0.0.1:${callbackPort}/callback`;
|
|
28
|
+
try {
|
|
29
|
+
const regRes = await fetch(`${apiUrl}/oauth/register`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { 'Content-Type': 'application/json' },
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
client_name: 'Halyard CLI',
|
|
34
|
+
redirect_uris: [redirectUri],
|
|
35
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
36
|
+
response_types: ['code'],
|
|
37
|
+
token_endpoint_auth_method: 'none',
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
if (!regRes.ok) {
|
|
41
|
+
const body = await regRes.text();
|
|
42
|
+
throw new Error(`Client registration failed (${regRes.status}): ${body}`);
|
|
43
|
+
}
|
|
44
|
+
const regData = (await regRes.json());
|
|
45
|
+
clientId = regData.client_id;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error('Failed to register with Halyard API:', err instanceof Error ? err.message : err);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// Step 2: Generate PKCE values
|
|
52
|
+
const codeVerifier = generateCodeVerifier();
|
|
53
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
54
|
+
const state = randomBytes(16).toString('hex');
|
|
55
|
+
// Step 3: Start temp HTTP server to capture callback
|
|
56
|
+
const authCode = await new Promise((resolve, reject) => {
|
|
57
|
+
const timeout = setTimeout(() => {
|
|
58
|
+
server.close();
|
|
59
|
+
reject(new Error('Login timed out after 2 minutes'));
|
|
60
|
+
}, 120_000);
|
|
61
|
+
const server = createServer((req, res) => {
|
|
62
|
+
if (!req.url?.startsWith('/callback')) {
|
|
63
|
+
res.writeHead(404);
|
|
64
|
+
res.end('Not found');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const url = new URL(req.url, `http://127.0.0.1:${callbackPort}`);
|
|
68
|
+
const code = url.searchParams.get('code');
|
|
69
|
+
const returnedState = url.searchParams.get('state');
|
|
70
|
+
const error = url.searchParams.get('error');
|
|
71
|
+
if (error) {
|
|
72
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
73
|
+
res.end('<html><body><h1>Login failed</h1><p>You can close this tab.</p></body></html>');
|
|
74
|
+
clearTimeout(timeout);
|
|
75
|
+
server.close();
|
|
76
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (returnedState !== state) {
|
|
80
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
81
|
+
res.end('<html><body><h1>Login failed</h1><p>State mismatch. You can close this tab.</p></body></html>');
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
server.close();
|
|
84
|
+
reject(new Error('OAuth state mismatch'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!code) {
|
|
88
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
89
|
+
res.end('<html><body><h1>Login failed</h1><p>No auth code received. You can close this tab.</p></body></html>');
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
server.close();
|
|
92
|
+
reject(new Error('No authorization code received'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
96
|
+
res.end('<html><body><h1>Login successful!</h1><p>You can close this tab and return to the terminal.</p></body></html>');
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
server.close();
|
|
99
|
+
resolve(code);
|
|
100
|
+
});
|
|
101
|
+
server.listen(callbackPort, '127.0.0.1', () => {
|
|
102
|
+
// Step 4: Open browser to authorize
|
|
103
|
+
const authUrl = new URL(`${apiUrl}/oauth/authorize`);
|
|
104
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
105
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
106
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
107
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
108
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
109
|
+
authUrl.searchParams.set('state', state);
|
|
110
|
+
authUrl.searchParams.set('scope', 'openid profile');
|
|
111
|
+
console.log('Opening browser for authentication...');
|
|
112
|
+
console.log(`If the browser doesn't open, visit: ${authUrl.toString()}`);
|
|
113
|
+
open(authUrl.toString()).catch(() => {
|
|
114
|
+
// Browser open failed - user will use the URL printed above
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
// Step 5: Exchange auth code for tokens
|
|
119
|
+
console.log('Exchanging authorization code...');
|
|
120
|
+
try {
|
|
121
|
+
const tokenRes = await fetch(`${apiUrl}/oauth/token`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
124
|
+
body: new URLSearchParams({
|
|
125
|
+
grant_type: 'authorization_code',
|
|
126
|
+
client_id: clientId,
|
|
127
|
+
code: authCode,
|
|
128
|
+
code_verifier: codeVerifier,
|
|
129
|
+
redirect_uri: redirectUri,
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
if (!tokenRes.ok) {
|
|
133
|
+
const body = await tokenRes.text();
|
|
134
|
+
throw new Error(`Token exchange failed (${tokenRes.status}): ${body}`);
|
|
135
|
+
}
|
|
136
|
+
const tokenData = (await tokenRes.json());
|
|
137
|
+
saveCredentials({
|
|
138
|
+
accessToken: tokenData.access_token,
|
|
139
|
+
refreshToken: tokenData.refresh_token,
|
|
140
|
+
expiresAt: Date.now() + tokenData.expires_in * 1000,
|
|
141
|
+
apiUrl,
|
|
142
|
+
});
|
|
143
|
+
console.log('Login successful! Credentials saved to ~/.halyard/credentials.json');
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
console.error('Token exchange failed:', err instanceof Error ? err.message : err);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Find an available port for the OAuth callback server.
|
|
153
|
+
*/
|
|
154
|
+
function findOpenPort() {
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const server = createServer();
|
|
157
|
+
server.listen(0, '127.0.0.1', () => {
|
|
158
|
+
const addr = server.address();
|
|
159
|
+
if (addr && typeof addr === 'object') {
|
|
160
|
+
const port = addr.port;
|
|
161
|
+
server.close(() => resolve(port));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
server.close();
|
|
165
|
+
reject(new Error('Could not find open port'));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
server.on('error', reject);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAenC,wBAAgB,WAAW,IAAI,OAAO,CAmErC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { getAuthHeaders } from '../lib/auth.js';
|
|
5
|
+
import { uploadSession } from '../lib/api-client.js';
|
|
6
|
+
import { readSyncState, writeSyncState, markUploaded, hashFileContents } from '../lib/sync-state.js';
|
|
7
|
+
/**
|
|
8
|
+
* Detect provider from file path heuristics.
|
|
9
|
+
*/
|
|
10
|
+
function detectProvider(filePath) {
|
|
11
|
+
if (filePath.includes('.claude/') || filePath.includes('/claude/'))
|
|
12
|
+
return 'claude';
|
|
13
|
+
if (filePath.includes('.codex/') || filePath.includes('/codex/'))
|
|
14
|
+
return 'codex';
|
|
15
|
+
if (filePath.includes('.cursor/') || filePath.includes('/cursor/'))
|
|
16
|
+
return 'cursor';
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export function pushCommand() {
|
|
20
|
+
return new Command('push')
|
|
21
|
+
.description('Upload a single session log file to Halyard')
|
|
22
|
+
.argument('<path>', 'Path to the session log file (.jsonl)')
|
|
23
|
+
.option('--provider <provider>', 'Provider (claude, codex, cursor, other)')
|
|
24
|
+
.option('--session-id <id>', 'Session ID (defaults to filename without extension)')
|
|
25
|
+
.action(async function (filePath) {
|
|
26
|
+
const opts = this.opts();
|
|
27
|
+
const globalOpts = this.optsWithGlobals();
|
|
28
|
+
const apiUrl = globalOpts.apiUrl;
|
|
29
|
+
// Validate file exists
|
|
30
|
+
let stat;
|
|
31
|
+
try {
|
|
32
|
+
stat = statSync(filePath);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
console.error(`File not found: ${filePath}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
if (!stat.isFile()) {
|
|
39
|
+
console.error(`Not a file: ${filePath}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
// Determine provider
|
|
43
|
+
const provider = opts.provider ?? detectProvider(filePath) ?? 'other';
|
|
44
|
+
const sessionId = opts.sessionId ?? basename(filePath, '.jsonl');
|
|
45
|
+
// Authenticate
|
|
46
|
+
let authHeaders;
|
|
47
|
+
try {
|
|
48
|
+
authHeaders = await getAuthHeaders(apiUrl);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
// Upload
|
|
55
|
+
const sizeKb = (stat.size / 1024).toFixed(1);
|
|
56
|
+
console.log(`Uploading ${filePath} (${sizeKb} KB) as ${provider} session...`);
|
|
57
|
+
try {
|
|
58
|
+
const result = await uploadSession(filePath, provider, sessionId, authHeaders, apiUrl);
|
|
59
|
+
if (result.status === 'duplicate') {
|
|
60
|
+
console.log(`Session already exists (ID: ${result.sessionId})`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.log(`Uploaded successfully (ID: ${result.sessionId})`);
|
|
64
|
+
}
|
|
65
|
+
// Update sync state
|
|
66
|
+
const content = readFileSync(filePath);
|
|
67
|
+
const hash = hashFileContents(content);
|
|
68
|
+
const syncState = readSyncState();
|
|
69
|
+
markUploaded(syncState, filePath, {
|
|
70
|
+
sessionId: result.sessionId,
|
|
71
|
+
uploadedAt: new Date().toISOString(),
|
|
72
|
+
size: stat.size,
|
|
73
|
+
hash,
|
|
74
|
+
});
|
|
75
|
+
writeSyncState(syncState);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error(`Upload failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAYnC,wBAAgB,WAAW,IAAI,OAAO,CAgGrC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { getAuthHeaders } from '../lib/auth.js';
|
|
4
|
+
import { discoverAllSessions } from '../lib/discovery.js';
|
|
5
|
+
import { readSyncState, writeSyncState, isUploaded, markUploaded, hashFileContents, } from '../lib/sync-state.js';
|
|
6
|
+
import { uploadSession } from '../lib/api-client.js';
|
|
7
|
+
export function syncCommand() {
|
|
8
|
+
return new Command('sync')
|
|
9
|
+
.description('Discover and upload new session logs to Halyard')
|
|
10
|
+
.option('--provider <provider>', 'Filter by provider (claude, codex)')
|
|
11
|
+
.option('--dry-run', 'Show what would be uploaded without uploading')
|
|
12
|
+
.action(async function () {
|
|
13
|
+
const opts = this.opts();
|
|
14
|
+
const globalOpts = this.optsWithGlobals();
|
|
15
|
+
const apiUrl = globalOpts.apiUrl;
|
|
16
|
+
const providerFilter = opts.provider;
|
|
17
|
+
const dryRun = !!opts.dryRun;
|
|
18
|
+
// Authenticate
|
|
19
|
+
let authHeaders;
|
|
20
|
+
try {
|
|
21
|
+
authHeaders = await getAuthHeaders(apiUrl);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
// Discover sessions
|
|
28
|
+
console.log('Discovering session logs...');
|
|
29
|
+
const sessions = discoverAllSessions(providerFilter);
|
|
30
|
+
if (sessions.length === 0) {
|
|
31
|
+
console.log('No session logs found.');
|
|
32
|
+
if (!providerFilter) {
|
|
33
|
+
console.log('Looked in: ~/.claude/projects/ and ~/.codex/sessions/');
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log(`Found ${sessions.length} session log(s)`);
|
|
38
|
+
// Load sync state and filter out already-uploaded files
|
|
39
|
+
const syncState = readSyncState();
|
|
40
|
+
let uploaded = 0;
|
|
41
|
+
let skipped = 0;
|
|
42
|
+
let failed = 0;
|
|
43
|
+
for (const session of sessions) {
|
|
44
|
+
const content = readFileSync(session.filePath);
|
|
45
|
+
const hash = hashFileContents(content);
|
|
46
|
+
if (isUploaded(syncState, session.filePath, hash)) {
|
|
47
|
+
skipped++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (dryRun) {
|
|
51
|
+
const sizeKb = (session.sizeBytes / 1024).toFixed(1);
|
|
52
|
+
console.log(` [dry-run] Would upload: ${session.filePath} (${sizeKb} KB)`);
|
|
53
|
+
uploaded++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const result = await uploadSession(session.filePath, session.provider, session.sessionId, authHeaders, apiUrl);
|
|
58
|
+
markUploaded(syncState, session.filePath, {
|
|
59
|
+
sessionId: result.sessionId,
|
|
60
|
+
uploadedAt: new Date().toISOString(),
|
|
61
|
+
size: session.sizeBytes,
|
|
62
|
+
hash,
|
|
63
|
+
});
|
|
64
|
+
const sizeKb = (session.sizeBytes / 1024).toFixed(1);
|
|
65
|
+
const status = result.status === 'duplicate' ? 'already exists' : 'uploaded';
|
|
66
|
+
console.log(` ${status}: ${session.filePath} (${sizeKb} KB)`);
|
|
67
|
+
uploaded++;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
console.error(` failed: ${session.filePath} - ${err instanceof Error ? err.message : String(err)}`);
|
|
71
|
+
failed++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Save sync state
|
|
75
|
+
if (!dryRun) {
|
|
76
|
+
writeSyncState(syncState);
|
|
77
|
+
}
|
|
78
|
+
// Summary
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log(`Sync complete: ${uploaded} uploaded, ${skipped} already synced, ${failed} failed`);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface UploadResult {
|
|
2
|
+
status: 'created' | 'duplicate';
|
|
3
|
+
sessionId: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Upload a session log file to the Halyard ingest endpoint.
|
|
7
|
+
*/
|
|
8
|
+
export declare function uploadSession(filePath: string, provider: string, sessionId: string, authHeaders: Record<string, string>, apiUrl: string): Promise<UploadResult>;
|
|
9
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,GAAG,WAAW,CAAA;IAC/B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC,CA6BvB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Upload a session log file to the Halyard ingest endpoint.
|
|
5
|
+
*/
|
|
6
|
+
export async function uploadSession(filePath, provider, sessionId, authHeaders, apiUrl) {
|
|
7
|
+
const fileContent = readFileSync(filePath);
|
|
8
|
+
const fileName = basename(filePath);
|
|
9
|
+
// Build multipart form data
|
|
10
|
+
const formData = new FormData();
|
|
11
|
+
formData.append('provider', provider);
|
|
12
|
+
formData.append('sessionId', sessionId);
|
|
13
|
+
formData.append('file', new Blob([fileContent]), fileName);
|
|
14
|
+
const res = await fetch(`${apiUrl}/api/v1/sessions/ingest`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
...authHeaders,
|
|
18
|
+
// Do NOT set Content-Type — fetch sets it with the boundary for multipart
|
|
19
|
+
},
|
|
20
|
+
body: formData,
|
|
21
|
+
});
|
|
22
|
+
if (res.status === 401) {
|
|
23
|
+
throw new Error('Authentication failed. Run `halyard login` or check your HALYARD_TOKEN.');
|
|
24
|
+
}
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const body = await res.text();
|
|
27
|
+
throw new Error(`Upload failed (${res.status}): ${body}`);
|
|
28
|
+
}
|
|
29
|
+
return (await res.json());
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAOpC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,QAAgB,EAChB,SAAiB,EACjB,WAAmC,EACnC,MAAc;IAEd,MAAM,WAAW,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAEnC,4BAA4B;IAC5B,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAA;IAC/B,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IACrC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IACvC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;IAE1D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,yBAAyB,EAAE;QAC1D,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,GAAG,WAAW;YACd,0EAA0E;SAC3E;QACD,IAAI,EAAE,QAAQ;KACf,CAAC,CAAA;IAEF,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAA;IAC5F,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7B,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAA;IAC3D,CAAC;IAED,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAiB,CAAA;AAC3C,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface Credentials {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
expiresAt: number;
|
|
5
|
+
apiUrl: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Get stored credentials, or fall back to HALYARD_TOKEN env var.
|
|
9
|
+
* Returns null if no credentials are available.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getCredentials(): Credentials | null;
|
|
12
|
+
/**
|
|
13
|
+
* Get authorization headers for API requests.
|
|
14
|
+
* Auto-refreshes expired OAuth tokens.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAuthHeaders(apiUrl: string): Promise<Record<string, string>>;
|
|
17
|
+
/**
|
|
18
|
+
* Refresh an expired OAuth access token.
|
|
19
|
+
*/
|
|
20
|
+
export declare function refreshToken(refreshTokenValue: string, apiUrl: string): Promise<Credentials>;
|
|
21
|
+
/**
|
|
22
|
+
* Save credentials to disk.
|
|
23
|
+
*/
|
|
24
|
+
export declare function saveCredentials(creds: Credentials): void;
|
|
25
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAkBnD;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA0BpF;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,CAAC,CA8BtB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAGxD"}
|
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { CREDENTIALS_PATH, ensureHalyardDir } from './config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Get stored credentials, or fall back to HALYARD_TOKEN env var.
|
|
5
|
+
* Returns null if no credentials are available.
|
|
6
|
+
*/
|
|
7
|
+
export function getCredentials() {
|
|
8
|
+
// Check env var first (headless/CI use case)
|
|
9
|
+
const envToken = process.env['HALYARD_TOKEN'];
|
|
10
|
+
if (envToken) {
|
|
11
|
+
return {
|
|
12
|
+
accessToken: envToken,
|
|
13
|
+
refreshToken: '',
|
|
14
|
+
expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000, // Never expires for API keys
|
|
15
|
+
apiUrl: '', // Will use global --api-url option
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const raw = readFileSync(CREDENTIALS_PATH, 'utf-8');
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get authorization headers for API requests.
|
|
28
|
+
* Auto-refreshes expired OAuth tokens.
|
|
29
|
+
*/
|
|
30
|
+
export async function getAuthHeaders(apiUrl) {
|
|
31
|
+
const creds = getCredentials();
|
|
32
|
+
if (!creds) {
|
|
33
|
+
throw new Error('Not authenticated. Run `halyard login` or set the HALYARD_TOKEN environment variable.');
|
|
34
|
+
}
|
|
35
|
+
// API keys don't expire
|
|
36
|
+
if (creds.accessToken.startsWith('sk_halyard_')) {
|
|
37
|
+
return { Authorization: `Bearer ${creds.accessToken}` };
|
|
38
|
+
}
|
|
39
|
+
// Refresh if token is expired or about to expire (30s buffer)
|
|
40
|
+
if (creds.expiresAt < Date.now() + 30_000 && creds.refreshToken) {
|
|
41
|
+
try {
|
|
42
|
+
const refreshed = await refreshToken(creds.refreshToken, apiUrl);
|
|
43
|
+
return { Authorization: `Bearer ${refreshed.accessToken}` };
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new Error(`Token expired and refresh failed: ${err instanceof Error ? err.message : String(err)}. Run \`halyard login\` to re-authenticate.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { Authorization: `Bearer ${creds.accessToken}` };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Refresh an expired OAuth access token.
|
|
53
|
+
*/
|
|
54
|
+
export async function refreshToken(refreshTokenValue, apiUrl) {
|
|
55
|
+
const res = await fetch(`${apiUrl}/oauth/token`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
58
|
+
body: new URLSearchParams({
|
|
59
|
+
grant_type: 'refresh_token',
|
|
60
|
+
refresh_token: refreshTokenValue,
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
const body = await res.text();
|
|
65
|
+
throw new Error(`Token refresh failed (${res.status}): ${body}`);
|
|
66
|
+
}
|
|
67
|
+
const data = (await res.json());
|
|
68
|
+
const creds = {
|
|
69
|
+
accessToken: data.access_token,
|
|
70
|
+
refreshToken: data.refresh_token,
|
|
71
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
72
|
+
apiUrl,
|
|
73
|
+
};
|
|
74
|
+
saveCredentials(creds);
|
|
75
|
+
return creds;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Save credentials to disk.
|
|
79
|
+
*/
|
|
80
|
+
export function saveCredentials(creds) {
|
|
81
|
+
ensureHalyardDir();
|
|
82
|
+
writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Root directory for Halyard CLI config and state */
|
|
2
|
+
export declare const HALYARD_DIR: string;
|
|
3
|
+
/** Path to stored OAuth credentials */
|
|
4
|
+
export declare const CREDENTIALS_PATH: string;
|
|
5
|
+
/** Path to sync state (tracks uploaded files) */
|
|
6
|
+
export declare const SYNC_STATE_PATH: string;
|
|
7
|
+
/** Default API URL */
|
|
8
|
+
export declare const DEFAULT_API_URL = "https://api.usehalyard.ai";
|
|
9
|
+
/** Ensure the ~/.halyard directory exists */
|
|
10
|
+
export declare function ensureHalyardDir(): void;
|
|
11
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,sDAAsD;AACtD,eAAO,MAAM,WAAW,QAA8B,CAAA;AAEtD,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,QAAwC,CAAA;AAErE,iDAAiD;AACjD,eAAO,MAAM,eAAe,QAAuC,CAAA;AAEnE,sBAAsB;AACtB,eAAO,MAAM,eAAe,8BAA8B,CAAA;AAE1D,6CAA6C;AAC7C,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { mkdirSync } from 'node:fs';
|
|
4
|
+
/** Root directory for Halyard CLI config and state */
|
|
5
|
+
export const HALYARD_DIR = join(homedir(), '.halyard');
|
|
6
|
+
/** Path to stored OAuth credentials */
|
|
7
|
+
export const CREDENTIALS_PATH = join(HALYARD_DIR, 'credentials.json');
|
|
8
|
+
/** Path to sync state (tracks uploaded files) */
|
|
9
|
+
export const SYNC_STATE_PATH = join(HALYARD_DIR, 'sync-state.json');
|
|
10
|
+
/** Default API URL */
|
|
11
|
+
export const DEFAULT_API_URL = 'https://api.usehalyard.ai';
|
|
12
|
+
/** Ensure the ~/.halyard directory exists */
|
|
13
|
+
export function ensureHalyardDir() {
|
|
14
|
+
mkdirSync(HALYARD_DIR, { recursive: true, mode: 0o700 });
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC,sDAAsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAA;AAEtD,uCAAuC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAA;AAErE,iDAAiD;AACjD,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAA;AAEnE,sBAAsB;AACtB,MAAM,CAAC,MAAM,eAAe,GAAG,2BAA2B,CAAA;AAE1D,6CAA6C;AAC7C,MAAM,UAAU,gBAAgB;IAC9B,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AAC1D,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface DiscoveredSession {
|
|
2
|
+
provider: 'claude' | 'codex';
|
|
3
|
+
filePath: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
sizeBytes: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Discover Claude Code session log files.
|
|
9
|
+
* Claude Code stores sessions in ~/.claude/projects/<project-hash>/sessions/
|
|
10
|
+
* Session files are JSONL format.
|
|
11
|
+
*/
|
|
12
|
+
export declare function discoverClaudeSessions(): DiscoveredSession[];
|
|
13
|
+
/**
|
|
14
|
+
* Discover Codex session log files.
|
|
15
|
+
* Codex stores sessions in ~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-*.jsonl
|
|
16
|
+
*/
|
|
17
|
+
export declare function discoverCodexSessions(): DiscoveredSession[];
|
|
18
|
+
/**
|
|
19
|
+
* Auto-detect which providers have session files available.
|
|
20
|
+
*/
|
|
21
|
+
export declare function discoverAllSessions(providerFilter?: string): DiscoveredSession[];
|
|
22
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/lib/discovery.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAA;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,iBAAiB,EAAE,CAoB5D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,iBAAiB,EAAE,CAO3D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAYhF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join, basename } from 'node:path';
|
|
3
|
+
import { readdirSync, statSync, existsSync } from 'node:fs';
|
|
4
|
+
/**
|
|
5
|
+
* Discover Claude Code session log files.
|
|
6
|
+
* Claude Code stores sessions in ~/.claude/projects/<project-hash>/sessions/
|
|
7
|
+
* Session files are JSONL format.
|
|
8
|
+
*/
|
|
9
|
+
export function discoverClaudeSessions() {
|
|
10
|
+
const claudeDir = join(homedir(), '.claude', 'projects');
|
|
11
|
+
if (!existsSync(claudeDir))
|
|
12
|
+
return [];
|
|
13
|
+
const sessions = [];
|
|
14
|
+
try {
|
|
15
|
+
const projectDirs = readdirSync(claudeDir);
|
|
16
|
+
for (const project of projectDirs) {
|
|
17
|
+
const projectPath = join(claudeDir, project);
|
|
18
|
+
if (!statSync(projectPath).isDirectory())
|
|
19
|
+
continue;
|
|
20
|
+
// Look for JSONL files directly in project dir and in sessions subdirs
|
|
21
|
+
walkForJsonl(projectPath, 'claude', sessions, 0);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Permission errors or missing dirs - silently skip
|
|
26
|
+
}
|
|
27
|
+
return sessions;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Discover Codex session log files.
|
|
31
|
+
* Codex stores sessions in ~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-*.jsonl
|
|
32
|
+
*/
|
|
33
|
+
export function discoverCodexSessions() {
|
|
34
|
+
const codexDir = join(homedir(), '.codex', 'sessions');
|
|
35
|
+
if (!existsSync(codexDir))
|
|
36
|
+
return [];
|
|
37
|
+
const sessions = [];
|
|
38
|
+
walkForJsonl(codexDir, 'codex', sessions, 0);
|
|
39
|
+
return sessions;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Auto-detect which providers have session files available.
|
|
43
|
+
*/
|
|
44
|
+
export function discoverAllSessions(providerFilter) {
|
|
45
|
+
const sessions = [];
|
|
46
|
+
if (!providerFilter || providerFilter === 'claude') {
|
|
47
|
+
sessions.push(...discoverClaudeSessions());
|
|
48
|
+
}
|
|
49
|
+
if (!providerFilter || providerFilter === 'codex') {
|
|
50
|
+
sessions.push(...discoverCodexSessions());
|
|
51
|
+
}
|
|
52
|
+
return sessions;
|
|
53
|
+
}
|
|
54
|
+
/** Recursively walk a directory for .jsonl files, with a max depth. */
|
|
55
|
+
function walkForJsonl(dir, provider, results, depth) {
|
|
56
|
+
if (depth > 3)
|
|
57
|
+
return; // Don't recurse too deep
|
|
58
|
+
try {
|
|
59
|
+
const entries = readdirSync(dir);
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const fullPath = join(dir, entry);
|
|
62
|
+
try {
|
|
63
|
+
const stat = statSync(fullPath);
|
|
64
|
+
if (stat.isDirectory()) {
|
|
65
|
+
walkForJsonl(fullPath, provider, results, depth + 1);
|
|
66
|
+
}
|
|
67
|
+
else if (entry.endsWith('.jsonl') && stat.size > 0) {
|
|
68
|
+
results.push({
|
|
69
|
+
provider,
|
|
70
|
+
filePath: fullPath,
|
|
71
|
+
sessionId: basename(entry, '.jsonl'),
|
|
72
|
+
sizeBytes: stat.size,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Skip files we can't stat
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Skip directories we can't read
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/lib/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface SyncEntry {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
uploadedAt: string;
|
|
4
|
+
size: number;
|
|
5
|
+
hash: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SyncState {
|
|
8
|
+
/** Map of file path -> upload info */
|
|
9
|
+
files: Record<string, SyncEntry>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Read the current sync state from disk.
|
|
13
|
+
*/
|
|
14
|
+
export declare function readSyncState(): SyncState;
|
|
15
|
+
/**
|
|
16
|
+
* Write sync state to disk.
|
|
17
|
+
*/
|
|
18
|
+
export declare function writeSyncState(state: SyncState): void;
|
|
19
|
+
/**
|
|
20
|
+
* Check if a file has already been uploaded (by content hash).
|
|
21
|
+
*/
|
|
22
|
+
export declare function isUploaded(state: SyncState, filePath: string, hash: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Mark a file as uploaded in the sync state.
|
|
25
|
+
*/
|
|
26
|
+
export declare function markUploaded(state: SyncState, filePath: string, info: SyncEntry): void;
|
|
27
|
+
/**
|
|
28
|
+
* Compute a hash of a file's contents for change detection.
|
|
29
|
+
*/
|
|
30
|
+
export declare function hashFileContents(content: Buffer): string;
|
|
31
|
+
//# sourceMappingURL=sync-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-state.d.ts","sourceRoot":"","sources":["../../src/lib/sync-state.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,SAAS;IACxB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,SAAS,CAOzC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAGrD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGpF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAEtF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { SYNC_STATE_PATH, ensureHalyardDir } from './config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Read the current sync state from disk.
|
|
6
|
+
*/
|
|
7
|
+
export function readSyncState() {
|
|
8
|
+
try {
|
|
9
|
+
const raw = readFileSync(SYNC_STATE_PATH, 'utf-8');
|
|
10
|
+
return JSON.parse(raw);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return { files: {} };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Write sync state to disk.
|
|
18
|
+
*/
|
|
19
|
+
export function writeSyncState(state) {
|
|
20
|
+
ensureHalyardDir();
|
|
21
|
+
writeFileSync(SYNC_STATE_PATH, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if a file has already been uploaded (by content hash).
|
|
25
|
+
*/
|
|
26
|
+
export function isUploaded(state, filePath, hash) {
|
|
27
|
+
const entry = state.files[filePath];
|
|
28
|
+
return !!entry && entry.hash === hash;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Mark a file as uploaded in the sync state.
|
|
32
|
+
*/
|
|
33
|
+
export function markUploaded(state, filePath, info) {
|
|
34
|
+
state.files[filePath] = info;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compute a hash of a file's contents for change detection.
|
|
38
|
+
*/
|
|
39
|
+
export function hashFileContents(content) {
|
|
40
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=sync-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-state.js","sourceRoot":"","sources":["../../src/lib/sync-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAc/D;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAA;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IACtB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAgB;IAC7C,gBAAgB,EAAE,CAAA;IAClB,aAAa,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AACjF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,KAAgB,EAAE,QAAgB,EAAE,IAAY;IACzE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IACnC,OAAO,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAA;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgB,EAAE,QAAgB,EAAE,IAAe;IAC9E,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAA;AAC9B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AACxE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@halyard/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for syncing coding session logs to Halyard",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"halyard": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"commander": "^13.0.0",
|
|
14
|
+
"open": "^10.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.7.0",
|
|
18
|
+
"tsx": "^4.19.0",
|
|
19
|
+
"@halyard/tsconfig": "0.0.0",
|
|
20
|
+
"@halyard/prettier-config": "0.0.1"
|
|
21
|
+
},
|
|
22
|
+
"prettier": "@halyard/prettier-config",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"registry": "https://registry.npmjs.org/"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "tsx src/cli.ts",
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"build:binary": "bun build src/cli.ts --compile --outfile dist/halyard",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
}
|
|
33
|
+
}
|