@secondcontext/btx-cli 0.0.2 → 0.0.5

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.
@@ -0,0 +1,112 @@
1
+ import { projectApiGet, projectApiPost } from './api.js';
2
+ function normalizeMatchValue(value) {
3
+ return value?.trim().toLowerCase() ?? '';
4
+ }
5
+ function formatMember(member) {
6
+ const name = member.name?.trim();
7
+ const email = member.email?.trim();
8
+ if (name && email)
9
+ return `${name} <${email}> [${member.userId}]`;
10
+ if (name)
11
+ return `${name} [${member.userId}]`;
12
+ if (email)
13
+ return `${email} [${member.userId}]`;
14
+ return member.userId;
15
+ }
16
+ function formatConversation(conversation) {
17
+ return `${conversation.name} [${conversation.id}]`;
18
+ }
19
+ function resolveUniqueMatch(query, items, selectors, render, itemLabel, listCommand) {
20
+ const normalizedQuery = normalizeMatchValue(query);
21
+ if (!normalizedQuery) {
22
+ throw new Error(`Provide a ${itemLabel} value.`);
23
+ }
24
+ const exactMatches = items.filter((item) => selectors.some((selector) => normalizeMatchValue(selector(item)) === normalizedQuery));
25
+ if (exactMatches.length === 1)
26
+ return exactMatches[0];
27
+ if (exactMatches.length > 1) {
28
+ throw new Error(`Multiple ${itemLabel}s match "${query}". Narrow the query or inspect ${listCommand}:\n${exactMatches
29
+ .map((item) => ` ${render(item)}`)
30
+ .join('\n')}`);
31
+ }
32
+ const partialMatches = items.filter((item) => selectors.some((selector) => normalizeMatchValue(selector(item)).includes(normalizedQuery)));
33
+ if (partialMatches.length === 1)
34
+ return partialMatches[0];
35
+ if (partialMatches.length > 1) {
36
+ throw new Error(`Multiple ${itemLabel}s match "${query}". Narrow the query or inspect ${listCommand}:\n${partialMatches
37
+ .map((item) => ` ${render(item)}`)
38
+ .join('\n')}`);
39
+ }
40
+ throw new Error(`No ${itemLabel} matched "${query}". Run \`${listCommand}\` to inspect valid targets.`);
41
+ }
42
+ function formatScopeLabel(kind, label) {
43
+ switch (kind) {
44
+ case 'team':
45
+ return 'Team chat';
46
+ case 'dm':
47
+ return `Direct message with ${label}`;
48
+ case 'conversation':
49
+ return `Conversation: ${label}`;
50
+ }
51
+ }
52
+ export async function listProjectMembers(env) {
53
+ return projectApiGet(env, '/members');
54
+ }
55
+ export async function listChatConversations(env) {
56
+ return projectApiGet(env, '/chat/conversations');
57
+ }
58
+ export async function resolveMessageScope(env, flags) {
59
+ if (flags.peer && flags.conversation) {
60
+ throw new Error('Use either `--peer` or `--conversation`, not both.');
61
+ }
62
+ if (flags.peer) {
63
+ const members = await listProjectMembers(env);
64
+ const peer = resolveUniqueMatch(flags.peer, members, [
65
+ (member) => member.userId,
66
+ (member) => member.id,
67
+ (member) => member.email,
68
+ (member) => member.name,
69
+ ], formatMember, 'member', 'btx messages members');
70
+ return {
71
+ kind: 'dm',
72
+ label: formatScopeLabel('dm', formatMember(peer)),
73
+ peer,
74
+ requestQuery: { peer: peer.userId },
75
+ sendPayload: { recipientId: peer.userId },
76
+ };
77
+ }
78
+ if (flags.conversation) {
79
+ const { conversations } = await listChatConversations(env);
80
+ const conversation = resolveUniqueMatch(flags.conversation, conversations, [(item) => item.id, (item) => item.name], formatConversation, 'conversation', 'btx messages conversations');
81
+ return {
82
+ kind: 'conversation',
83
+ label: formatScopeLabel('conversation', formatConversation(conversation)),
84
+ conversation,
85
+ requestQuery: { conversation: conversation.id },
86
+ sendPayload: { conversationId: conversation.id },
87
+ };
88
+ }
89
+ return {
90
+ kind: 'team',
91
+ label: formatScopeLabel('team', 'Team chat'),
92
+ requestQuery: {},
93
+ sendPayload: {},
94
+ };
95
+ }
96
+ export async function listChatMessages(env, scope, options) {
97
+ const params = new URLSearchParams(scope.requestQuery);
98
+ if (options.before)
99
+ params.set('before', options.before);
100
+ if (typeof options.limit === 'number')
101
+ params.set('limit', String(options.limit));
102
+ const suffix = params.toString();
103
+ return projectApiGet(env, `/chat${suffix ? `?${suffix}` : ''}`);
104
+ }
105
+ export async function sendChatMessage(env, scope, input) {
106
+ return projectApiPost(env, '/chat', {
107
+ body: input.body,
108
+ replyToId: input.replyToId ?? null,
109
+ ...scope.sendPayload,
110
+ });
111
+ }
112
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-cli.d.ts","sourceRoot":"","sources":["../src/runtime-cli.ts"],"names":[],"mappings":"AAoLA,MAAM,WAAW,aAAa;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AA6hED,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,GAAE,aAA2B,GAC/B,OAAO,CAAC,IAAI,CAAC,CA0Qf"}
1
+ {"version":3,"file":"runtime-cli.d.ts","sourceRoot":"","sources":["../src/runtime-cli.ts"],"names":[],"mappings":"AAmNA,MAAM,WAAW,aAAa;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAgvED,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,GAAE,aAA2B,GAC/B,OAAO,CAAC,IAAI,CAAC,CAiRf"}
@@ -138,6 +138,15 @@ function isJson(flags) {
138
138
  function printJson(value) {
139
139
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
140
140
  }
141
+ function readIntFlag(flags, key, defaultValue, min = 1, max = 200) {
142
+ const raw = flags[key];
143
+ if (!raw)
144
+ return defaultValue;
145
+ const parsed = Number.parseInt(raw, 10);
146
+ if (!Number.isFinite(parsed))
147
+ return defaultValue;
148
+ return Math.max(min, Math.min(max, parsed));
149
+ }
141
150
  // ── Formatting ──────────────────────────────────────────────────────────────
142
151
  function truncate(str, max) {
143
152
  if (!str)
@@ -356,6 +365,94 @@ function formatMeetingTranscript(m) {
356
365
  console.log(`[${time}] ${speaker}: ${seg.text}`);
357
366
  }
358
367
  }
368
+ function sessionPayload(s) {
369
+ return JSON.stringify({
370
+ sessionId: s.sessionId,
371
+ label: s.label,
372
+ sessionType: s.sessionType,
373
+ runtime: s.runtime || null,
374
+ status: s.status,
375
+ messageCount: s.messageCount ?? 0,
376
+ activityCount: s.activityCount ?? 0,
377
+ summary: s.summary ?? null,
378
+ lastActivityType: s.lastActivityType ?? null,
379
+ lastActivityContent: s.lastActivityContent ?? null,
380
+ createdAt: typeof s.createdAt === 'number'
381
+ ? new Date(s.createdAt).toISOString()
382
+ : s.createdAt,
383
+ });
384
+ }
385
+ function sessionPreview(session) {
386
+ return session.summary || session.lastActivityContent || '';
387
+ }
388
+ function formatSessionTable(sessions) {
389
+ if (sessions.length === 0) {
390
+ console.log('No sessions found.');
391
+ return;
392
+ }
393
+ const idW = 12;
394
+ const dateW = 10;
395
+ const statusW = 8;
396
+ const typeW = 14;
397
+ const runtimeW = 12;
398
+ const labelW = 28;
399
+ console.log(`${padRight('ID', idW)} ${padRight('Date', dateW)} ${padRight('Status', statusW)} ${padRight('Type', typeW)} ${padRight('Runtime', runtimeW)} ${padRight('Label', labelW)} Preview`);
400
+ console.log(`${'\u2500'.repeat(idW)} ${'\u2500'.repeat(dateW)} ${'\u2500'.repeat(statusW)} ${'\u2500'.repeat(typeW)} ${'\u2500'.repeat(runtimeW)} ${'\u2500'.repeat(labelW)} ${'\u2500'.repeat(36)}`);
401
+ for (const session of sessions) {
402
+ const created = new Date(session.createdAt).toISOString().slice(0, 10);
403
+ console.log(`${padRight(truncate(session.sessionId, idW), idW)} ${padRight(created, dateW)} ${padRight(session.status, statusW)} ${padRight(truncate(session.sessionType, typeW), typeW)} ${padRight(truncate(session.runtime || '', runtimeW), runtimeW)} ${padRight(truncate(session.label, labelW), labelW)} ${truncate(sessionPreview(session), 44)}`);
404
+ }
405
+ console.log(`\n${sessions.length} session${sessions.length !== 1 ? 's' : ''} found`);
406
+ console.log('Use `btx sessions get <session-id>` for full details and recent checkpoints.');
407
+ for (const session of sessions) {
408
+ console.log(`BTX_SESSION_JSON: ${sessionPayload(session)}`);
409
+ }
410
+ }
411
+ function formatSessionDetail(session, activities) {
412
+ console.log(`Session: ${session.label}`);
413
+ console.log(`ID: ${session.sessionId}`);
414
+ console.log(`Status: ${session.status}`);
415
+ console.log(`Type: ${session.sessionType}`);
416
+ console.log(`Runtime: ${session.runtime || 'n/a'}`);
417
+ console.log(`Execution: ${session.executionMode}`);
418
+ console.log(`Started: ${new Date(session.createdAt).toISOString()}`);
419
+ if (session.updatedAt)
420
+ console.log(`Updated: ${new Date(session.updatedAt).toISOString()}`);
421
+ if (session.closedAt)
422
+ console.log(`Closed: ${session.closedAt}`);
423
+ if (session.userName || session.userEmail) {
424
+ console.log(`Owner: ${session.userName || session.userEmail}`);
425
+ }
426
+ console.log(`Messages: ${session.messageCount ?? 0}`);
427
+ console.log(`Activities: ${session.activityCount ?? 0}`);
428
+ if (session.totalCostUsd != null)
429
+ console.log(`Cost USD: ${session.totalCostUsd}`);
430
+ if (session.lastActivityType)
431
+ console.log(`Last activity: ${session.lastActivityType}`);
432
+ if (session.lastActivityAt)
433
+ console.log(`Last updated: ${session.lastActivityAt}`);
434
+ if (session.summary) {
435
+ console.log(`\nSummary:\n ${session.summary.replace(/\n/g, '\n ')}`);
436
+ }
437
+ if (session.lastActivityContent && session.lastActivityContent !== session.summary) {
438
+ console.log(`\nLatest checkpoint:\n ${session.lastActivityContent.replace(/\n/g, '\n ')}`);
439
+ }
440
+ if (activities.length > 0) {
441
+ console.log('\nRecent activity:');
442
+ for (const activity of activities) {
443
+ const date = new Date(activity.createdAt ?? Date.now()).toISOString();
444
+ const actor = activity.actorName || activity.actorEmail || 'Unknown';
445
+ const content = activity.content || '';
446
+ console.log(` [${date}] ${activity.type} (${actor})`);
447
+ if (content)
448
+ console.log(` ${content.replace(/\n/g, '\n ')}`);
449
+ }
450
+ }
451
+ else {
452
+ console.log('\nNo session activity found.');
453
+ }
454
+ console.log(`\nBTX_SESSION_JSON: ${sessionPayload(session)}`);
455
+ }
359
456
  // ── Help ─────────────────────────────────────────────────────────────────────
360
457
  const HELP = {
361
458
  top: `BTX CLI - manage tasks, contacts, meetings, and leads from a session.
@@ -366,7 +463,7 @@ Usage:
366
463
 
367
464
  Resources:
368
465
  tasks Create and manage project tasks
369
- sessions Add and list notes for a session
466
+ sessions Browse recent sessions, inspect summaries, add notes
370
467
  contacts View and update contacts, add notes
371
468
  orgs View organizations, add notes
372
469
  meetings View meeting recordings and transcripts
@@ -417,16 +514,24 @@ Examples:
417
514
  node "$BTX_CLI_PATH" tasks complete abc123
418
515
  node "$BTX_CLI_PATH" tasks notes list abc123
419
516
  node "$BTX_CLI_PATH" tasks notes add abc123 --content "Insight: ICP may be wrong on stage" --source "https://..."`,
420
- sessions: `BTX sessions - add and list notes for a session.
517
+ sessions: `BTX sessions - browse recent sessions and inspect a session's checkpoints.
421
518
 
422
519
  Commands:
520
+ list List recent sessions (metadata only)
521
+ get <session-id> Get one session with recent checkpoints
423
522
  notes list <session-id> List notes for a session
424
523
  notes add <session-id> --content "..." Add a note to a session
425
524
 
426
525
  Flags:
526
+ sessions list [--status open|closed] [--type <session-type>] [--runtime <runtime>] [--query "text"] [--limit <n>]
527
+ sessions get <session-id> [--activity-limit <n>]
427
528
  sessions notes add <session-id> --content "Label: value" [--source "https://..."]
428
529
 
429
530
  Examples:
531
+ node "$BTX_CLI_PATH" sessions list --limit 10
532
+ node "$BTX_CLI_PATH" sessions list --status closed --query "auth"
533
+ node "$BTX_CLI_PATH" sessions get abc-session-id
534
+ node "$BTX_CLI_PATH" sessions get abc-session-id --activity-limit 10
430
535
  node "$BTX_CLI_PATH" sessions notes list abc-session-id
431
536
  node "$BTX_CLI_PATH" sessions notes add abc-session-id --content "Insight: market timing is critical"
432
537
  node "$BTX_CLI_PATH" sessions notes add abc-session-id --content "Decision: pivot to enterprise" --source "https://..."`,
@@ -1144,6 +1249,69 @@ async function taskNotesList(taskId, flags) {
1144
1249
  }
1145
1250
  }
1146
1251
  // ── Session notes commands ────────────────────────────────────────────────────
1252
+ async function fetchSessions(flags) {
1253
+ const params = new URLSearchParams();
1254
+ if (flags.status)
1255
+ params.set('status', flags.status);
1256
+ if (flags.type)
1257
+ params.set('session_type', flags.type);
1258
+ params.set('limit', String(readIntFlag(flags, 'limit', 20)));
1259
+ const suffix = params.toString();
1260
+ const result = await api('GET', `/sessions${suffix ? `?${suffix}` : ''}`);
1261
+ let sessions = result.sessions ?? [];
1262
+ if (flags.runtime) {
1263
+ const runtime = flags.runtime.toLowerCase();
1264
+ sessions = sessions.filter((session) => (session.runtime || '').toLowerCase().includes(runtime));
1265
+ }
1266
+ if (flags.query) {
1267
+ const query = flags.query.toLowerCase();
1268
+ sessions = sessions.filter((session) => {
1269
+ const haystack = [
1270
+ session.label,
1271
+ session.summary,
1272
+ session.lastActivityContent,
1273
+ session.sessionType,
1274
+ session.runtime,
1275
+ session.userName,
1276
+ session.userEmail,
1277
+ ]
1278
+ .filter(Boolean)
1279
+ .join('\n')
1280
+ .toLowerCase();
1281
+ return haystack.includes(query);
1282
+ });
1283
+ }
1284
+ return sessions;
1285
+ }
1286
+ async function fetchSession(sessionId) {
1287
+ const result = await api('GET', `/sessions?sessionId=${encodeURIComponent(sessionId)}`);
1288
+ return result.session ?? null;
1289
+ }
1290
+ async function fetchSessionActivities(sessionId, limit) {
1291
+ const result = await api('GET', `/session-activities?sessionId=${encodeURIComponent(sessionId)}&limit=${limit}`);
1292
+ return Array.isArray(result) ? result : [];
1293
+ }
1294
+ async function sessionsList(flags) {
1295
+ const sessions = await fetchSessions(flags);
1296
+ if (isJson(flags)) {
1297
+ printJson(sessions);
1298
+ return;
1299
+ }
1300
+ formatSessionTable(sessions);
1301
+ }
1302
+ async function sessionsGet(sessionId, flags) {
1303
+ if (!sessionId)
1304
+ die('Usage: btx sessions get <session-id> [--activity-limit <n>]');
1305
+ const session = await fetchSession(sessionId);
1306
+ if (!session)
1307
+ die(`Session not found: ${sessionId}`);
1308
+ const activities = await fetchSessionActivities(sessionId, readIntFlag(flags, 'activity-limit', 10, 1, 50));
1309
+ if (isJson(flags)) {
1310
+ printJson({ session, activities });
1311
+ return;
1312
+ }
1313
+ formatSessionDetail(session, activities);
1314
+ }
1147
1315
  async function sessionNotesAdd(sessionId, flags) {
1148
1316
  if (!sessionId)
1149
1317
  die('Usage: btx sessions notes add <session-id> --content "..."');
@@ -1328,11 +1496,13 @@ async function globalSearch(flags) {
1328
1496
  }
1329
1497
  if (matches.length === 0) {
1330
1498
  console.log(`No tasks found matching "${query}".`);
1331
- console.log('\nTo search coding sessions, use Cmd+Shift+F in the BTX app.');
1499
+ console.log('\nFor recent coding context, use `node "$BTX_CLI_PATH" sessions list --limit 10`.');
1500
+ console.log('For full-text transcript search, use Cmd+Shift+F in the BTX app.');
1332
1501
  return;
1333
1502
  }
1334
1503
  formatTaskTable(matches);
1335
- console.log('\nTo search coding sessions, use Cmd+Shift+F in the BTX app.');
1504
+ console.log('\nFor recent coding context, use `node "$BTX_CLI_PATH" sessions list --limit 10`.');
1505
+ console.log('For full-text transcript search, use Cmd+Shift+F in the BTX app.');
1336
1506
  }
1337
1507
  // ── User profile notes commands ───────────────────────────────────────────────
1338
1508
  async function userNotesAdd(flags) {
@@ -1923,7 +2093,14 @@ export async function runRuntimeCli(args, env = process.env) {
1923
2093
  die(`Unknown sessions notes command: "${subCommand || '(none)'}". Run: node "$BTX_CLI_PATH" sessions --help`);
1924
2094
  }
1925
2095
  }
1926
- die(`Unknown sessions command: "${command || '(none)'}". Run: node "$BTX_CLI_PATH" sessions --help`);
2096
+ switch (command) {
2097
+ case 'list':
2098
+ return sessionsList(flags);
2099
+ case 'get':
2100
+ return sessionsGet(positional[2], flags);
2101
+ default:
2102
+ die(`Unknown sessions command: "${command || '(none)'}". Run: node "$BTX_CLI_PATH" sessions --help`);
2103
+ }
1927
2104
  }
1928
2105
  else if (resource === 'contacts') {
1929
2106
  if (command === 'notes') {