@recall_v3/mcp-server 0.1.0 → 3.0.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/README.md +66 -0
- package/dist/api/client.d.ts +102 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +65 -16
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +297 -0
- package/dist/config/index.d.ts +10 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +71 -4
- package/dist/crypto/index.d.ts +10 -0
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js +29 -0
- package/dist/index.js +12 -12
- package/dist/tools/getContext.d.ts +2 -2
- package/dist/tools/getContext.d.ts.map +1 -1
- package/dist/tools/getContext.js +57 -20
- package/dist/tools/getHistory.d.ts +1 -1
- package/dist/tools/getHistory.d.ts.map +1 -1
- package/dist/tools/getHistory.js +93 -30
- package/dist/tools/getTranscripts.js +1 -1
- package/dist/tools/saveSession.d.ts +8 -3
- package/dist/tools/saveSession.d.ts.map +1 -1
- package/dist/tools/saveSession.js +186 -45
- package/package.json +1 -1
- package/src/api/client.ts +154 -17
- package/src/cli.ts +334 -0
- package/src/config/index.ts +84 -4
- package/src/crypto/index.ts +33 -0
- package/src/index.ts +12 -12
- package/src/tools/getContext.ts +102 -20
- package/src/tools/getHistory.ts +157 -31
- package/src/tools/getTranscripts.ts +1 -1
- package/src/tools/saveSession.ts +282 -50
|
@@ -3,38 +3,107 @@
|
|
|
3
3
|
* saveSession Tool Implementation
|
|
4
4
|
*
|
|
5
5
|
* Saves a summary of what was accomplished in this coding session.
|
|
6
|
-
*
|
|
6
|
+
* Encrypts content with team key and sends to Recall v3 API.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.saveSession = saveSession;
|
|
10
10
|
const client_js_1 = require("../api/client.js");
|
|
11
11
|
const index_js_1 = require("../config/index.js");
|
|
12
|
+
const index_js_2 = require("../crypto/index.js");
|
|
12
13
|
const types_js_1 = require("./types.js");
|
|
13
14
|
const utils_js_1 = require("./utils.js");
|
|
15
|
+
/**
|
|
16
|
+
* Build the full session content to encrypt
|
|
17
|
+
*/
|
|
18
|
+
function buildSessionContent(args) {
|
|
19
|
+
const parts = [];
|
|
20
|
+
parts.push('## Summary');
|
|
21
|
+
parts.push((args.summary || 'Session saved').trim());
|
|
22
|
+
parts.push('');
|
|
23
|
+
if (args.decisions && args.decisions.length > 0) {
|
|
24
|
+
parts.push('## Decisions');
|
|
25
|
+
for (const decision of args.decisions) {
|
|
26
|
+
parts.push(`- **${decision.what}**: ${decision.why}`);
|
|
27
|
+
}
|
|
28
|
+
parts.push('');
|
|
29
|
+
}
|
|
30
|
+
if (args.mistakes && args.mistakes.length > 0) {
|
|
31
|
+
parts.push('## Mistakes / Gotchas');
|
|
32
|
+
for (const mistake of args.mistakes) {
|
|
33
|
+
parts.push(`- ${mistake}`);
|
|
34
|
+
}
|
|
35
|
+
parts.push('');
|
|
36
|
+
}
|
|
37
|
+
if (args.filesChanged && args.filesChanged.length > 0) {
|
|
38
|
+
parts.push('## Files Changed');
|
|
39
|
+
for (const file of args.filesChanged) {
|
|
40
|
+
parts.push(`- ${file}`);
|
|
41
|
+
}
|
|
42
|
+
parts.push('');
|
|
43
|
+
}
|
|
44
|
+
if (args.nextSteps) {
|
|
45
|
+
parts.push('## Next Steps');
|
|
46
|
+
parts.push(args.nextSteps.trim());
|
|
47
|
+
parts.push('');
|
|
48
|
+
}
|
|
49
|
+
if (args.blockers) {
|
|
50
|
+
parts.push('## Blockers');
|
|
51
|
+
parts.push(args.blockers.trim());
|
|
52
|
+
parts.push('');
|
|
53
|
+
}
|
|
54
|
+
return parts.join('\n');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build TLDR metadata for the session
|
|
58
|
+
*/
|
|
59
|
+
function buildTldr(args) {
|
|
60
|
+
const tldr = {
|
|
61
|
+
summary: (args.summary || 'Session saved').trim().substring(0, 200),
|
|
62
|
+
status: args.blockers ? 'blocked' : 'complete',
|
|
63
|
+
};
|
|
64
|
+
if (args.decisions && args.decisions.length > 0) {
|
|
65
|
+
tldr.decisions = args.decisions.map(d => d.what);
|
|
66
|
+
}
|
|
67
|
+
if (args.mistakes && args.mistakes.length > 0) {
|
|
68
|
+
tldr.mistakes = args.mistakes;
|
|
69
|
+
}
|
|
70
|
+
if (args.filesChanged && args.filesChanged.length > 0) {
|
|
71
|
+
tldr.files_changed = args.filesChanged;
|
|
72
|
+
}
|
|
73
|
+
if (args.nextSteps) {
|
|
74
|
+
tldr.next_steps = args.nextSteps.trim();
|
|
75
|
+
}
|
|
76
|
+
if (args.blockers) {
|
|
77
|
+
tldr.blockers = args.blockers.trim();
|
|
78
|
+
}
|
|
79
|
+
return tldr;
|
|
80
|
+
}
|
|
14
81
|
/**
|
|
15
82
|
* Execute the saveSession tool
|
|
16
83
|
*
|
|
17
|
-
* @param args - Tool arguments with session summary and metadata
|
|
84
|
+
* @param args - Tool arguments with session summary/transcript and metadata
|
|
18
85
|
* @returns MCP tool response with save confirmation
|
|
19
86
|
*/
|
|
20
87
|
async function saveSession(args) {
|
|
21
88
|
try {
|
|
22
|
-
// Validate required fields
|
|
23
|
-
if (!args.summary || args.summary.trim().length === 0)
|
|
24
|
-
|
|
89
|
+
// Validate required fields - need either summary or transcript
|
|
90
|
+
if ((!args.summary || args.summary.trim().length === 0) &&
|
|
91
|
+
(!args.transcript || args.transcript.trim().length === 0)) {
|
|
92
|
+
return (0, types_js_1.errorResponse)('Either summary or transcript is required. Please provide a description of what was accomplished or the session transcript.');
|
|
25
93
|
}
|
|
26
94
|
// Get API token
|
|
27
95
|
const token = (0, index_js_1.getApiToken)();
|
|
28
96
|
if (!token) {
|
|
29
97
|
return (0, types_js_1.errorResponse)('Not authenticated. Run `recall auth` to connect your account, or set RECALL_API_TOKEN environment variable.');
|
|
30
98
|
}
|
|
31
|
-
// Resolve project path (use
|
|
32
|
-
const projectPath = (0, utils_js_1.resolveProjectPath)(
|
|
99
|
+
// Resolve project path (use args.projectPath if provided, otherwise cwd)
|
|
100
|
+
const projectPath = (0, utils_js_1.resolveProjectPath)(args.projectPath);
|
|
33
101
|
// Get repo info from git
|
|
34
102
|
const repoInfo = await (0, utils_js_1.getRepoInfo)(projectPath);
|
|
35
103
|
if (!repoInfo) {
|
|
36
104
|
return (0, types_js_1.errorResponse)(`Could not determine repository info for: ${projectPath}\n` +
|
|
37
|
-
'Make sure this is a git repository with a remote origin
|
|
105
|
+
'Make sure this is a git repository with a remote origin.\n' +
|
|
106
|
+
'If running from Claude Code hooks, use --project-path to specify the repo path.');
|
|
38
107
|
}
|
|
39
108
|
// Create API client
|
|
40
109
|
const client = new client_js_1.RecallApiClient({
|
|
@@ -50,51 +119,123 @@ async function saveSession(args) {
|
|
|
50
119
|
teamKey = keyResponse.encryptionKey;
|
|
51
120
|
(0, index_js_1.setTeamKey)(teamId, teamKey);
|
|
52
121
|
}
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
122
|
+
// If transcript provided, call summarize API to generate structured summary
|
|
123
|
+
let summarizedArgs = { ...args };
|
|
124
|
+
let aiSummary = null;
|
|
125
|
+
if (args.transcript && args.transcript.trim().length >= 50) {
|
|
126
|
+
try {
|
|
127
|
+
// Build the API request
|
|
128
|
+
const now = new Date().toISOString();
|
|
129
|
+
const summarizeRequest = {
|
|
130
|
+
transcript: args.transcript,
|
|
131
|
+
repo_name: repoInfo.fullName,
|
|
132
|
+
tool: 'claude-code',
|
|
133
|
+
started_at: now,
|
|
134
|
+
ended_at: now,
|
|
135
|
+
};
|
|
136
|
+
// Call the summarize API using the raw method to avoid type mismatch
|
|
137
|
+
aiSummary = await client.summarizeRaw(summarizeRequest);
|
|
138
|
+
// Update args with AI-generated content
|
|
139
|
+
summarizedArgs = {
|
|
140
|
+
...args,
|
|
141
|
+
summary: aiSummary.tldr.summary || aiSummary.summary,
|
|
142
|
+
decisions: aiSummary.decisions.map(d => ({
|
|
143
|
+
what: d.what,
|
|
144
|
+
why: d.why,
|
|
145
|
+
})),
|
|
146
|
+
mistakes: aiSummary.tldr.mistakes,
|
|
147
|
+
filesChanged: aiSummary.tldr.files_changed,
|
|
148
|
+
nextSteps: undefined, // Could extract from lessons
|
|
149
|
+
blockers: aiSummary.tldr.status === 'blocked' ? 'Session blocked' : undefined,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
// If summarization fails, fall back to provided summary or placeholder
|
|
154
|
+
console.error('[Recall] AI summarization failed, using fallback:', error);
|
|
155
|
+
if (!args.summary || args.summary.trim().length === 0) {
|
|
156
|
+
summarizedArgs.summary = 'Session saved (AI summarization unavailable)';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
68
159
|
}
|
|
69
|
-
if (args.
|
|
70
|
-
|
|
160
|
+
else if (!args.summary || args.summary.trim().length === 0) {
|
|
161
|
+
// No transcript and no summary - this shouldn't happen due to validation
|
|
162
|
+
summarizedArgs.summary = 'Session saved';
|
|
71
163
|
}
|
|
164
|
+
// Build the session content to encrypt
|
|
165
|
+
const sessionContent = buildSessionContent(summarizedArgs);
|
|
166
|
+
// Encrypt the content with team key
|
|
167
|
+
const encryptedContent = (0, index_js_2.encryptForApi)(sessionContent, teamKey);
|
|
168
|
+
// Build TLDR metadata (use summarized args if available)
|
|
169
|
+
const tldr = buildTldr(summarizedArgs);
|
|
170
|
+
// Create timestamps (session is "now")
|
|
171
|
+
const now = new Date().toISOString();
|
|
172
|
+
// Build the API request body (matches CreateSessionRequestBody in API)
|
|
173
|
+
const requestBody = {
|
|
174
|
+
encrypted_content: encryptedContent,
|
|
175
|
+
tldr,
|
|
176
|
+
started_at: now,
|
|
177
|
+
ended_at: now,
|
|
178
|
+
tool: 'claude-code',
|
|
179
|
+
};
|
|
72
180
|
// Save session via API
|
|
73
|
-
const response = await client.
|
|
74
|
-
// Build success message
|
|
75
|
-
const parts = [
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`
|
|
79
|
-
`Repository: ${repoInfo.fullName}`,
|
|
80
|
-
];
|
|
81
|
-
if (args.decisions && args.decisions.length > 0) {
|
|
82
|
-
parts.push(`Decisions logged: ${args.decisions.length}`);
|
|
181
|
+
const response = await client.saveSessionV3(repoId, requestBody);
|
|
182
|
+
// Build a clean, informative success message
|
|
183
|
+
const parts = [];
|
|
184
|
+
parts.push(`Session saved to ${repoInfo.fullName}`);
|
|
185
|
+
if (aiSummary) {
|
|
186
|
+
parts.push(` (AI summarized)`);
|
|
83
187
|
}
|
|
84
|
-
|
|
85
|
-
|
|
188
|
+
parts.push(``);
|
|
189
|
+
parts.push(` Saved:`);
|
|
190
|
+
// Summary (truncate if too long)
|
|
191
|
+
const summaryText = summarizedArgs.summary || 'Session saved';
|
|
192
|
+
const summaryPreview = summaryText.trim().length > 80
|
|
193
|
+
? summaryText.trim().substring(0, 77) + '...'
|
|
194
|
+
: summaryText.trim();
|
|
195
|
+
parts.push(` - Summary: ${summaryPreview}`);
|
|
196
|
+
// Key decisions (show first one if any)
|
|
197
|
+
if (summarizedArgs.decisions && summarizedArgs.decisions.length > 0) {
|
|
198
|
+
const firstDecision = summarizedArgs.decisions[0].what;
|
|
199
|
+
if (summarizedArgs.decisions.length === 1) {
|
|
200
|
+
parts.push(` - Decision: ${firstDecision}`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
parts.push(` - Decisions: ${firstDecision} (+${summarizedArgs.decisions.length - 1} more)`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Blockers (if any)
|
|
207
|
+
if (summarizedArgs.blockers) {
|
|
208
|
+
const blockerPreview = summarizedArgs.blockers.trim().length > 60
|
|
209
|
+
? summarizedArgs.blockers.trim().substring(0, 57) + '...'
|
|
210
|
+
: summarizedArgs.blockers.trim();
|
|
211
|
+
parts.push(` - Blocker: ${blockerPreview}`);
|
|
86
212
|
}
|
|
87
|
-
|
|
88
|
-
|
|
213
|
+
// Next steps (if any)
|
|
214
|
+
if (summarizedArgs.nextSteps) {
|
|
215
|
+
const nextPreview = summarizedArgs.nextSteps.trim().length > 60
|
|
216
|
+
? summarizedArgs.nextSteps.trim().substring(0, 57) + '...'
|
|
217
|
+
: summarizedArgs.nextSteps.trim();
|
|
218
|
+
parts.push(` - Next: ${nextPreview}`);
|
|
89
219
|
}
|
|
90
|
-
if
|
|
91
|
-
|
|
220
|
+
// Files changed (count only if many)
|
|
221
|
+
if (summarizedArgs.filesChanged && summarizedArgs.filesChanged.length > 0) {
|
|
222
|
+
if (summarizedArgs.filesChanged.length <= 3) {
|
|
223
|
+
parts.push(` - Files: ${summarizedArgs.filesChanged.join(', ')}`);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
parts.push(` - Files: ${summarizedArgs.filesChanged.length} files changed`);
|
|
227
|
+
}
|
|
92
228
|
}
|
|
93
|
-
|
|
94
|
-
|
|
229
|
+
// Footer
|
|
230
|
+
parts.push(` - Updated team memory for next session`);
|
|
231
|
+
// Show warnings if any (non-critical issues that didn't prevent save)
|
|
232
|
+
if (response.warnings && response.warnings.length > 0) {
|
|
233
|
+
parts.push(``);
|
|
234
|
+
parts.push(` Warnings:`);
|
|
235
|
+
for (const warning of response.warnings) {
|
|
236
|
+
parts.push(` ⚠️ ${warning}`);
|
|
237
|
+
}
|
|
95
238
|
}
|
|
96
|
-
parts.push(``);
|
|
97
|
-
parts.push(`Team memory has been updated.`);
|
|
98
239
|
return (0, types_js_1.successResponse)(parts.join('\n'));
|
|
99
240
|
}
|
|
100
241
|
catch (error) {
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -94,16 +94,18 @@ export class RecallApiClient {
|
|
|
94
94
|
private async request<T>(
|
|
95
95
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
96
96
|
path: string,
|
|
97
|
-
body?: unknown
|
|
97
|
+
body?: unknown,
|
|
98
|
+
options?: { timeout?: number }
|
|
98
99
|
): Promise<T> {
|
|
99
100
|
const url = `${this.baseUrl}${path}`;
|
|
101
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
100
102
|
|
|
101
103
|
let lastError: Error | undefined;
|
|
102
104
|
|
|
103
105
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
104
106
|
try {
|
|
105
107
|
const controller = new AbortController();
|
|
106
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
108
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
107
109
|
|
|
108
110
|
try {
|
|
109
111
|
const response = await fetch(url, {
|
|
@@ -139,13 +141,20 @@ export class RecallApiClient {
|
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
// Parse successful response
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
// API may return { success: true, data: T } or T directly
|
|
145
|
+
const rawData = await response.json();
|
|
146
|
+
|
|
147
|
+
// Check if it's a wrapped response
|
|
148
|
+
if (rawData && typeof rawData === 'object' && 'success' in rawData) {
|
|
149
|
+
const wrapped = rawData as ApiResponse<T>;
|
|
150
|
+
if (!wrapped.success && wrapped.error) {
|
|
151
|
+
throw new RecallApiError(wrapped.error.message, wrapped.error.code, response.status, false);
|
|
152
|
+
}
|
|
153
|
+
return wrapped.data as T;
|
|
146
154
|
}
|
|
147
155
|
|
|
148
|
-
|
|
156
|
+
// Direct response (not wrapped)
|
|
157
|
+
return rawData as T;
|
|
149
158
|
} finally {
|
|
150
159
|
clearTimeout(timeoutId);
|
|
151
160
|
}
|
|
@@ -218,31 +227,141 @@ export class RecallApiClient {
|
|
|
218
227
|
* Called by saveSession tool after reading JSONL from disk
|
|
219
228
|
*/
|
|
220
229
|
async summarize(request: SummarizeRequest): Promise<SummarizeResponse> {
|
|
221
|
-
return this.request<SummarizeResponse>('POST', '/
|
|
230
|
+
return this.request<SummarizeResponse>('POST', '/v1/summarize', request);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Summarize a session transcript using the actual API format
|
|
235
|
+
* This bypasses the strict shared types which don't match the real API
|
|
236
|
+
* Uses 2 minute timeout since AI summarization can take time
|
|
237
|
+
*/
|
|
238
|
+
async summarizeRaw<TRequest, TResponse>(request: TRequest): Promise<TResponse> {
|
|
239
|
+
return this.request<TResponse>('POST', '/v1/summarize', request, { timeout: 120000 });
|
|
222
240
|
}
|
|
223
241
|
|
|
224
242
|
/**
|
|
225
243
|
* Save a session with manual input (not from transcript)
|
|
226
244
|
* Used when user provides summary directly via MCP tool
|
|
245
|
+
* @deprecated Use saveSessionV3 for proper encrypted format
|
|
227
246
|
*/
|
|
228
247
|
async saveSession(repoId: string, data: SaveSessionRequest): Promise<SaveSessionResponse> {
|
|
229
|
-
return this.request<SaveSessionResponse>('POST', `/
|
|
248
|
+
return this.request<SaveSessionResponse>('POST', `/v1/repos/${repoId}/sessions`, data);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Save a session with encrypted content (v3 format)
|
|
253
|
+
* This is the correct format for the v3 API
|
|
254
|
+
*/
|
|
255
|
+
async saveSessionV3(repoId: string, data: {
|
|
256
|
+
encrypted_content: string;
|
|
257
|
+
tldr: {
|
|
258
|
+
summary: string;
|
|
259
|
+
status: 'complete' | 'in-progress' | 'blocked';
|
|
260
|
+
decisions?: string[];
|
|
261
|
+
mistakes?: string[];
|
|
262
|
+
tags?: string[];
|
|
263
|
+
files_changed?: string[];
|
|
264
|
+
blockers?: string | null;
|
|
265
|
+
next_steps?: string | null;
|
|
266
|
+
};
|
|
267
|
+
started_at: string;
|
|
268
|
+
ended_at: string;
|
|
269
|
+
tool?: string;
|
|
270
|
+
}): Promise<{ id: string; created_at: string; warnings?: string[] }> {
|
|
271
|
+
return this.request<{ id: string; created_at: string; warnings?: string[] }>('POST', `/v1/repos/${repoId}/sessions`, data);
|
|
230
272
|
}
|
|
231
273
|
|
|
232
274
|
/**
|
|
233
275
|
* Get context for a repository (context.md)
|
|
234
276
|
* Returns the distilled team brain for this repo
|
|
277
|
+
* @deprecated Use getContextV3 for proper v3 API format
|
|
235
278
|
*/
|
|
236
279
|
async getContext(repoId: string): Promise<GetContextResponse> {
|
|
237
|
-
return this.request<GetContextResponse>('GET', `/
|
|
280
|
+
return this.request<GetContextResponse>('GET', `/v1/repos/${repoId}/context`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get context for a repository (v3 format)
|
|
285
|
+
* Returns sessions with encrypted content for client-side decryption
|
|
286
|
+
*/
|
|
287
|
+
async getContextV3(repoId: string): Promise<{
|
|
288
|
+
sessions: Array<{
|
|
289
|
+
id: string;
|
|
290
|
+
encrypted_content: string;
|
|
291
|
+
started_at: string;
|
|
292
|
+
ended_at: string;
|
|
293
|
+
status: string;
|
|
294
|
+
tldr_summary: string | null;
|
|
295
|
+
user: {
|
|
296
|
+
id: string;
|
|
297
|
+
name: string | null;
|
|
298
|
+
github_username: string | null;
|
|
299
|
+
avatar_url: string | null;
|
|
300
|
+
};
|
|
301
|
+
}>;
|
|
302
|
+
cached_context: string;
|
|
303
|
+
updated_at: string;
|
|
304
|
+
session_count: number;
|
|
305
|
+
tier: string;
|
|
306
|
+
}> {
|
|
307
|
+
return this.request('GET', `/v1/repos/${repoId}/context`);
|
|
238
308
|
}
|
|
239
309
|
|
|
240
310
|
/**
|
|
241
311
|
* Get history for a repository (context.md + history.md)
|
|
242
312
|
* Returns more detail than getContext
|
|
313
|
+
* @deprecated Use getHistoryV3 for proper v3 API format
|
|
243
314
|
*/
|
|
244
315
|
async getHistory(repoId: string): Promise<GetHistoryResponse> {
|
|
245
|
-
return this.request<GetHistoryResponse>('GET', `/
|
|
316
|
+
return this.request<GetHistoryResponse>('GET', `/v1/repos/${repoId}/history`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get history for a repository (v3 format)
|
|
321
|
+
* Returns sessions, decisions, and mistakes with encrypted content
|
|
322
|
+
*/
|
|
323
|
+
async getHistoryV3(repoId: string, days: number = 30): Promise<{
|
|
324
|
+
sessions: Array<{
|
|
325
|
+
id: string;
|
|
326
|
+
encrypted_content: string;
|
|
327
|
+
started_at: string;
|
|
328
|
+
ended_at: string;
|
|
329
|
+
status: string;
|
|
330
|
+
tldr_summary: string | null;
|
|
331
|
+
user: {
|
|
332
|
+
id: string;
|
|
333
|
+
name: string | null;
|
|
334
|
+
github_username: string | null;
|
|
335
|
+
avatar_url: string | null;
|
|
336
|
+
};
|
|
337
|
+
}>;
|
|
338
|
+
decisions: Array<{
|
|
339
|
+
id: string;
|
|
340
|
+
title: string;
|
|
341
|
+
encrypted_content: string;
|
|
342
|
+
created_at: string;
|
|
343
|
+
user: {
|
|
344
|
+
id: string;
|
|
345
|
+
name: string | null;
|
|
346
|
+
github_username: string | null;
|
|
347
|
+
avatar_url: string | null;
|
|
348
|
+
};
|
|
349
|
+
}>;
|
|
350
|
+
mistakes: Array<{
|
|
351
|
+
id: string;
|
|
352
|
+
title: string;
|
|
353
|
+
encrypted_content: string;
|
|
354
|
+
created_at: string;
|
|
355
|
+
user: {
|
|
356
|
+
id: string;
|
|
357
|
+
name: string | null;
|
|
358
|
+
github_username: string | null;
|
|
359
|
+
avatar_url: string | null;
|
|
360
|
+
};
|
|
361
|
+
}>;
|
|
362
|
+
token_warning?: string;
|
|
363
|
+
}> {
|
|
364
|
+
return this.request('GET', `/v1/repos/${repoId}/history?days=${days}`);
|
|
246
365
|
}
|
|
247
366
|
|
|
248
367
|
/**
|
|
@@ -250,14 +369,14 @@ export class RecallApiClient {
|
|
|
250
369
|
* WARNING: Can be very large, uses many tokens
|
|
251
370
|
*/
|
|
252
371
|
async getTranscripts(repoId: string): Promise<GetTranscriptsResponse> {
|
|
253
|
-
return this.request<GetTranscriptsResponse>('GET', `/
|
|
372
|
+
return this.request<GetTranscriptsResponse>('GET', `/v1/repos/${repoId}/transcripts`);
|
|
254
373
|
}
|
|
255
374
|
|
|
256
375
|
/**
|
|
257
376
|
* Log a decision for a repository
|
|
258
377
|
*/
|
|
259
378
|
async logDecision(repoId: string, data: LogDecisionRequest): Promise<LogDecisionResponse> {
|
|
260
|
-
return this.request<LogDecisionResponse>('POST', `/
|
|
379
|
+
return this.request<LogDecisionResponse>('POST', `/v1/repos/${repoId}/decisions`, data);
|
|
261
380
|
}
|
|
262
381
|
|
|
263
382
|
/**
|
|
@@ -265,21 +384,39 @@ export class RecallApiClient {
|
|
|
265
384
|
* Used to decrypt content locally
|
|
266
385
|
*/
|
|
267
386
|
async getTeamKey(teamId: string): Promise<TeamKeyResponse> {
|
|
268
|
-
|
|
387
|
+
// API returns { hasAccess, key, keyVersion, teamId, teamName, teamSlug, tier }
|
|
388
|
+
// Map to TeamKeyResponse type
|
|
389
|
+
const response = await this.request<{
|
|
390
|
+
hasAccess: boolean;
|
|
391
|
+
key: string;
|
|
392
|
+
keyVersion: number;
|
|
393
|
+
teamId: string;
|
|
394
|
+
teamName: string;
|
|
395
|
+
teamSlug: string;
|
|
396
|
+
tier: string;
|
|
397
|
+
}>('GET', '/v1/keys/team');
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
teamId: response.teamId,
|
|
401
|
+
encryptionKey: response.key,
|
|
402
|
+
keyVersion: response.keyVersion,
|
|
403
|
+
teamName: response.teamName,
|
|
404
|
+
tier: response.tier as 'team' | 'pro' | 'enterprise',
|
|
405
|
+
};
|
|
269
406
|
}
|
|
270
407
|
|
|
271
408
|
/**
|
|
272
409
|
* List teams the user belongs to
|
|
273
410
|
*/
|
|
274
411
|
async listTeams(): Promise<ListTeamsResponse> {
|
|
275
|
-
return this.request<ListTeamsResponse>('GET', '/
|
|
412
|
+
return this.request<ListTeamsResponse>('GET', '/v1/teams');
|
|
276
413
|
}
|
|
277
414
|
|
|
278
415
|
/**
|
|
279
416
|
* Get authentication status
|
|
280
417
|
*/
|
|
281
418
|
async getStatus(): Promise<RecallStatusResponse> {
|
|
282
|
-
return this.request<RecallStatusResponse>('GET', '/
|
|
419
|
+
return this.request<RecallStatusResponse>('GET', '/v1/status');
|
|
283
420
|
}
|
|
284
421
|
|
|
285
422
|
/**
|
|
@@ -287,7 +424,7 @@ export class RecallApiClient {
|
|
|
287
424
|
* Returns the repo ID for subsequent API calls
|
|
288
425
|
*/
|
|
289
426
|
async resolveRepo(fullName: string, defaultBranch?: string): Promise<{ repoId: string; teamId: string }> {
|
|
290
|
-
return this.request<{ repoId: string; teamId: string }>('POST', '/
|
|
427
|
+
return this.request<{ repoId: string; teamId: string }>('POST', '/v1/repos/resolve', {
|
|
291
428
|
fullName,
|
|
292
429
|
defaultBranch,
|
|
293
430
|
});
|