@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.
package/README.md CHANGED
@@ -36,7 +36,7 @@ Install the tarball into a clean repo or temp directory:
36
36
 
37
37
  ```bash
38
38
  npm init -y
39
- npm install /absolute/path/to/code/packages/btx-cli/secondcontext-btx-cli-0.0.1.tgz
39
+ npm install /absolute/path/to/code/packages/btx-cli/secondcontext-btx-cli-0.0.5.tgz
40
40
  ./node_modules/.bin/btx --help
41
41
  ./node_modules/.bin/btx repo status --json
42
42
  ```
@@ -45,9 +45,16 @@ npm install /absolute/path/to/code/packages/btx-cli/secondcontext-btx-cli-0.0.1.
45
45
 
46
46
  ```bash
47
47
  btx login
48
+ btx login status
49
+ btx logout
48
50
  btx setup
49
51
  btx setup codex
50
52
  btx projects
53
+ btx messages list
54
+ btx messages send "Shipped the fix"
55
+ btx messages send "Can you review this?" --peer faruk@secondcontext.com
56
+ btx messages conversations
57
+ btx messages members
51
58
  btx repo link <project-id>
52
59
  btx bootstrap all
53
60
  btx context get --json
@@ -60,6 +67,10 @@ btx session finish --summary "Recorded outcome and next steps"
60
67
  ## Auth and repo state
61
68
 
62
69
  - `btx login` imports an existing desktop auth session when available, or opens a browser flow that writes `~/.btx/auth-session.json`
70
+ - `btx login status` shows whether the CLI is authenticated and whether desktop fallback is enabled
71
+ - `btx logout` clears the standalone CLI session and disables desktop fallback so the CLI stays logged out. Use `btx logout --all` to also clear the desktop session file
72
+ - `btx messages list` reads BTX project chat, with `--peer` for direct messages and `--conversation` for named group chats
73
+ - `btx messages send` sends a BTX project chat message from inline text or piped stdin
63
74
  - `btx setup` is the first-run path for linking a repo and choosing `codex`, `claude`, or both bootstrap targets
64
75
  - Auth is read from `~/.btx/auth-session.json`, with a fallback to the desktop app auth session store
65
76
  - Repo linkage is stored in `.btx/btx.json`
@@ -80,16 +91,11 @@ Publish from the monorepo root:
80
91
  npm run publish:btx-cli --prefix code
81
92
  ```
82
93
 
83
- Then publish from `code/packages/btx-cli`:
84
-
85
- ```bash
86
- npm publish
87
- ```
88
-
89
94
  Expected manual npm steps:
90
95
 
91
96
  - Log in to npm with an account that can publish under `@secondcontext`
92
97
  - Set `NPM_TOKEN` in `code/packages/btx-cli/.env` or export it in your shell
93
- - The publish helper creates a temporary npm user config so workspace-local `.npmrc` files are not needed
98
+ - The publish helper in `code/scripts/publish-btx-cli.sh` creates a temporary npm user config so workspace-local `.npmrc` files are not needed. Do not run raw `npm publish` from `code/packages/btx-cli`
99
+ - If `npm run publish:btx-cli:dry-run --prefix code` succeeds but the real publish returns npm `404` for `@secondcontext/btx-cli`, the token is authenticated but does not have publish permission for the package. Fix the npm token or package access instead of retrying raw publish commands
94
100
  - Ensure the `@secondcontext` scope is configured for public packages
95
101
  - If you want an open-source package, add a real `LICENSE` file before publishing and replace the current `UNLICENSED` marker in `package.json`
package/dist/bin.js CHANGED
@@ -5,9 +5,22 @@ import { spawnSync } from 'node:child_process';
5
5
  import { createInterface } from 'node:readline/promises';
6
6
  import { apiGet, projectApiPatch, projectApiPost } from './api.js';
7
7
  import { bootstrapAgents } from './bootstrap.js';
8
- import { DEFAULT_API_URL, clearActiveSession, findRepoRoot, getActiveSession, loadRepoConfig, resolveRuntimeEnv, resolveStoredAuthSessionSource, setActiveSession, writeRepoConfig, } from './config.js';
8
+ import { clearStoredAuthSession, DEFAULT_API_URL, clearActiveSession, findRepoRoot, getActiveSession, isDesktopFallbackEnabled, loadRepoConfig, resolveRuntimeEnv, resolveStoredAuthSessionSource, setActiveSession, writeRepoConfig, } from './config.js';
9
9
  import { loginToBtx } from './login.js';
10
+ import { listChatConversations, listChatMessages, listProjectMembers, resolveMessageScope, sendChatMessage, } from './messages.js';
10
11
  import { runRuntimeCli } from './runtime-cli.js';
12
+ const SESSION_CHECKPOINT_TYPES = [
13
+ 'note',
14
+ 'progress',
15
+ 'decision',
16
+ 'risk',
17
+ 'blocker',
18
+ 'assumption',
19
+ 'summary',
20
+ 'question',
21
+ 'next_step',
22
+ ];
23
+ const SESSION_CHECKPOINT_USAGE = SESSION_CHECKPOINT_TYPES.join('|');
11
24
  const RUNTIME_RESOURCES = new Set([
12
25
  'tasks',
13
26
  'sessions',
@@ -66,14 +79,17 @@ Usage:
66
79
 
67
80
  Standalone commands:
68
81
  btx login Sign in to BTX for standalone CLI use
82
+ btx logout Remove stored standalone BTX auth credentials
69
83
  btx setup [agent] Link this repo and bootstrap AGENTS.md and/or CLAUDE.md
70
84
  btx projects List BTX projects you can access
85
+ btx ask "<prompt>" Send a freeform prompt to the general cloud agent
86
+ btx messages ... Read and send BTX project chat messages
71
87
  btx repo link <project-id> Link the current repo to a BTX project
72
88
  btx repo status Show current repo BTX status
73
89
  btx bootstrap <agent> Write BTX instructions into AGENTS.md and/or CLAUDE.md
74
90
  btx session start Start or resume the active BTX coding session for this repo
75
91
  btx session status Show the active BTX coding session
76
- btx session checkpoint Record progress, risk, decision, blocker, or note
92
+ btx session checkpoint Record durable session memory: decisions, assumptions, learnings, risks, next steps
77
93
  btx session finish Close the active BTX coding session
78
94
 
79
95
  BTX resources:
@@ -82,20 +98,66 @@ BTX resources:
82
98
 
83
99
  Examples:
84
100
  btx login
101
+ btx login status
102
+ btx logout
85
103
  btx setup
86
104
  btx setup codex
87
105
  btx projects
106
+ btx ask "Show me a count of tasks by status"
107
+ btx ask "Who should I follow up with this week?" --trace-tools
108
+ btx messages list
109
+ btx messages send "Shipped the fix"
110
+ btx messages send "Can you review this?" --peer faruk@secondcontext.com
111
+ btx messages conversations
88
112
  btx repo link <project-id>
89
113
  btx bootstrap all
90
114
  btx context get
115
+ btx sessions list --limit 10
116
+ btx sessions get <session-id>
91
117
  btx tasks list --json
92
118
  btx tasks list
93
119
  btx session start --runtime codex --label "Implement project telemetry"
120
+ btx session checkpoint --type decision --content "Decision: keep desktop and standalone CLI on one contract"
121
+ btx session checkpoint --type assumption --content "Assumption: users will review saved session intelligence in BTX"
122
+ btx session checkpoint --type note --content "Learning: product session activities feed long-term project memory"
94
123
  btx session checkpoint --type risk --content "Task meaning is still ambiguous across sessions"
95
124
  btx session finish --summary "Finished CLI bootstrap and repo linking flow"
96
125
 
97
126
  All commands that return BTX data support --json for machine-readable output.`);
98
127
  }
128
+ function printAskHelp() {
129
+ console.log(`Usage:
130
+ btx ask "<prompt>" [--session-id <id>] [--label <label>] [--trace-tools] [--json]
131
+
132
+ Examples:
133
+ btx ask "Show me a count of tasks by status"
134
+ btx ask "What should I focus on next?" --trace-tools
135
+ btx ask "Continue refining that plan" --session-id <session-id>
136
+ btx ask --prompt "Summarize our open risks" --json`);
137
+ }
138
+ function printMessagesHelp() {
139
+ console.log(`Usage:
140
+ btx messages list [--peer <member>] [--conversation <conversation>] [--limit <n>] [--before <iso>] [--json]
141
+ btx messages read [--peer <member>] [--conversation <conversation>] [--limit <n>] [--before <iso>] [--json]
142
+ btx messages send "<body>" [--peer <member>] [--conversation <conversation>] [--reply-to <message-id>] [--json]
143
+ btx messages members [--json]
144
+ btx messages conversations [--json]
145
+
146
+ Scope:
147
+ no scope flags Team chat
148
+ --peer <member> Direct messages with a project member
149
+ --conversation <conversation> Existing group conversation
150
+
151
+ Examples:
152
+ btx messages list
153
+ btx messages list --peer faruk@secondcontext.com
154
+ btx messages send "Shipped the fix in main"
155
+ btx messages send "Can you take a look?" --peer Faruk
156
+ btx messages send --conversation "Launch planning" "Review the latest blockers"
157
+ printf "Daily update: validation passed" | btx messages send
158
+ btx messages members
159
+ btx messages conversations --json`);
160
+ }
99
161
  function bestEffortGitBranch(repoRoot) {
100
162
  const result = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
101
163
  cwd: repoRoot,
@@ -167,6 +229,18 @@ function printLoginResult(authSource, apiUrl, browserOpened) {
167
229
  }
168
230
  console.log(`API base: ${apiUrl}`);
169
231
  }
232
+ function authStatusPayload() {
233
+ const env = resolveRuntimeEnv();
234
+ const hasEnvToken = !!process.env.BTX_ACCESS_TOKEN;
235
+ const authSource = hasEnvToken ? 'env' : resolveStoredAuthSessionSource();
236
+ return {
237
+ loggedIn: !!env.BTX_ACCESS_TOKEN,
238
+ authSource,
239
+ apiBase: env.BTX_API_URL ?? null,
240
+ desktopFallbackEnabled: isDesktopFallbackEnabled(),
241
+ hasEnvToken,
242
+ };
243
+ }
170
244
  async function handleProjects(flags) {
171
245
  const env = resolveRuntimeEnv();
172
246
  const projects = await listProjects(env);
@@ -215,16 +289,16 @@ function handleRepoStatus(flags) {
215
289
  const repoRoot = findRepoRoot();
216
290
  const repoConfig = loadRepoConfig(repoRoot);
217
291
  const activeSession = getActiveSession(repoRoot);
218
- const env = resolveRuntimeEnv();
219
- const authSource = process.env.BTX_ACCESS_TOKEN ? 'env' : resolveStoredAuthSessionSource();
292
+ const auth = authStatusPayload();
220
293
  const status = {
221
294
  repoRoot,
222
295
  linked: !!repoConfig,
223
296
  projectId: repoConfig?.projectId ?? null,
224
297
  projectName: repoConfig?.projectName ?? null,
225
- apiBase: env.BTX_API_URL ?? null,
226
- hasAuthToken: !!env.BTX_ACCESS_TOKEN,
227
- authSource,
298
+ apiBase: auth.apiBase,
299
+ hasAuthToken: auth.loggedIn,
300
+ authSource: auth.authSource,
301
+ desktopFallbackEnabled: auth.desktopFallbackEnabled,
228
302
  activeSession,
229
303
  };
230
304
  if (isJson(flags)) {
@@ -238,9 +312,10 @@ function handleRepoStatus(flags) {
238
312
  else {
239
313
  console.log(`Linked project: ${repoConfig.projectName ?? 'BTX Project'} (${repoConfig.projectId})`);
240
314
  }
241
- console.log(`API base: ${env.BTX_API_URL}`);
242
- console.log(`Auth token available: ${env.BTX_ACCESS_TOKEN ? 'yes' : 'no'}`);
243
- console.log(`Auth source: ${authSource ?? 'none'}`);
315
+ console.log(`API base: ${auth.apiBase}`);
316
+ console.log(`Auth token available: ${auth.loggedIn ? 'yes' : 'no'}`);
317
+ console.log(`Auth source: ${auth.authSource ?? 'none'}`);
318
+ console.log(`Desktop fallback enabled: ${auth.desktopFallbackEnabled ? 'yes' : 'no'}`);
244
319
  if (activeSession) {
245
320
  console.log(`Active session: ${activeSession.label} (${activeSession.sessionId})`);
246
321
  console.log(`Runtime: ${activeSession.runtime}`);
@@ -249,7 +324,292 @@ function handleRepoStatus(flags) {
249
324
  console.log('Active session: none');
250
325
  }
251
326
  }
252
- async function handleLogin(flags) {
327
+ function formatAskToolTrace(toolCalls) {
328
+ return toolCalls.map((toolCall) => {
329
+ const status = toolCall.isError ? 'error' : toolCall.status;
330
+ const input = Object.keys(toolCall.input).length > 0
331
+ ? ` ${JSON.stringify(toolCall.input)}`
332
+ : '';
333
+ return `[${toolCall.kind}:${status}] ${toolCall.name}${input}`;
334
+ });
335
+ }
336
+ function parsePositiveIntFlag(rawValue, flagName) {
337
+ if (!rawValue)
338
+ return undefined;
339
+ const parsed = Number.parseInt(rawValue, 10);
340
+ if (!Number.isInteger(parsed) || parsed < 1) {
341
+ die(`\`${flagName}\` must be a positive integer.`);
342
+ }
343
+ return parsed;
344
+ }
345
+ function formatTimestamp(value) {
346
+ const date = new Date(value);
347
+ if (Number.isNaN(date.valueOf()))
348
+ return value;
349
+ return new Intl.DateTimeFormat(undefined, {
350
+ dateStyle: 'medium',
351
+ timeStyle: 'short',
352
+ }).format(date);
353
+ }
354
+ function summarizeText(value, maxLength = 120) {
355
+ const normalized = value.replace(/\s+/g, ' ').trim();
356
+ if (normalized.length <= maxLength)
357
+ return normalized;
358
+ return `${normalized.slice(0, maxLength - 1).trimEnd()}…`;
359
+ }
360
+ function printIndentedBlock(value, indent = ' ') {
361
+ for (const line of value.split(/\r?\n/)) {
362
+ console.log(`${indent}${line}`);
363
+ }
364
+ }
365
+ function describeMember(member) {
366
+ if (member.name && member.email)
367
+ return `${member.name} <${member.email}>`;
368
+ return member.name ?? member.email ?? member.userId;
369
+ }
370
+ function describeConversationMember(member) {
371
+ if (member.name && member.email)
372
+ return `${member.name} <${member.email}>`;
373
+ if (member.name)
374
+ return member.name;
375
+ if (member.email)
376
+ return member.email;
377
+ if (member.externalName && member.externalEmail)
378
+ return `${member.externalName} <${member.externalEmail}>`;
379
+ return member.externalName ?? member.externalEmail ?? member.userId ?? 'Unknown member';
380
+ }
381
+ function printMessage(message) {
382
+ const sender = message.senderName ?? message.senderEmail ?? message.userId;
383
+ console.log(`${formatTimestamp(message.createdAt)} ${sender}`);
384
+ console.log(` ID: ${message.id}`);
385
+ if (message.replyTo) {
386
+ console.log(` Replying to ${message.replyTo.senderName ?? 'Unknown sender'}: ${summarizeText(message.replyTo.body, 90)}`);
387
+ }
388
+ if (message.body.trim()) {
389
+ printIndentedBlock(message.body);
390
+ }
391
+ else if (message.attachments.length > 0) {
392
+ console.log(' Attachment-only message');
393
+ }
394
+ if (message.attachments.length > 0) {
395
+ console.log(` Attachments: ${message.attachments.length}`);
396
+ }
397
+ if (message.reactions.length > 0) {
398
+ console.log(` Reactions: ${message.reactions.map((reaction) => `${reaction.emoji} x${reaction.count}`).join(', ')}`);
399
+ }
400
+ }
401
+ async function readMessageBody(positionals, flags) {
402
+ const inlineBody = (flags.body ?? positionals.slice(2).join(' ')).trim();
403
+ if (inlineBody)
404
+ return inlineBody;
405
+ if (!process.stdin.isTTY) {
406
+ const chunks = [];
407
+ for await (const chunk of process.stdin) {
408
+ chunks.push(typeof chunk === 'string' ? chunk : chunk.toString('utf8'));
409
+ }
410
+ const pipedBody = chunks.join('').trim();
411
+ if (pipedBody)
412
+ return pipedBody;
413
+ }
414
+ return '';
415
+ }
416
+ function printScopeSummary(scope) {
417
+ console.log(`Scope: ${scope.label}`);
418
+ }
419
+ async function handleAsk(positionals, flags) {
420
+ if (flags.help === 'true') {
421
+ printAskHelp();
422
+ return;
423
+ }
424
+ const prompt = (flags.prompt ?? positionals.slice(1).join(' ')).trim();
425
+ if (!prompt) {
426
+ printAskHelp();
427
+ die('Prompt is required.');
428
+ }
429
+ const env = resolveRuntimeEnv(process.cwd(), {
430
+ ...(flags['api-url'] ? { BTX_API_URL: flags['api-url'] } : {}),
431
+ });
432
+ const result = await projectApiPost(env, '/ask', {
433
+ prompt,
434
+ ...(flags['session-id'] ? { sessionId: flags['session-id'] } : {}),
435
+ ...(flags.label ? { label: flags.label } : {}),
436
+ includeToolTrace: flags['trace-tools'] === 'true',
437
+ });
438
+ if (isJson(flags)) {
439
+ printJson(result);
440
+ return;
441
+ }
442
+ if (result.response) {
443
+ console.log(result.response);
444
+ }
445
+ if (result.question) {
446
+ if (result.response)
447
+ console.log();
448
+ console.log(`Question: ${result.question}`);
449
+ }
450
+ if (flags['trace-tools'] === 'true' && result.toolCalls && result.toolCalls.length > 0) {
451
+ if (result.response || result.question)
452
+ console.log();
453
+ console.log('Tools:');
454
+ for (const line of formatAskToolTrace(result.toolCalls)) {
455
+ console.log(` ${line}`);
456
+ }
457
+ }
458
+ }
459
+ async function handleMessagesMembers(flags) {
460
+ const env = resolveRuntimeEnv();
461
+ const members = await listProjectMembers(env);
462
+ if (isJson(flags)) {
463
+ printJson(members);
464
+ return;
465
+ }
466
+ if (members.length === 0) {
467
+ console.log('No BTX project members found.');
468
+ return;
469
+ }
470
+ for (const member of members) {
471
+ console.log(describeMember(member));
472
+ console.log(` User ID: ${member.userId}`);
473
+ console.log(` Member ID: ${member.id}`);
474
+ console.log(` Role: ${member.role}`);
475
+ console.log(` Joined: ${formatTimestamp(member.joinedAt)}`);
476
+ console.log();
477
+ }
478
+ }
479
+ async function handleMessagesConversations(flags) {
480
+ const env = resolveRuntimeEnv();
481
+ const result = await listChatConversations(env);
482
+ if (isJson(flags)) {
483
+ printJson(result);
484
+ return;
485
+ }
486
+ if (result.conversations.length === 0) {
487
+ console.log('No BTX chat conversations found.');
488
+ return;
489
+ }
490
+ for (const conversation of result.conversations) {
491
+ console.log(conversation.name);
492
+ console.log(` ID: ${conversation.id}`);
493
+ console.log(` Created: ${formatTimestamp(conversation.createdAt)}`);
494
+ if (conversation.members.length > 0) {
495
+ console.log(` Members: ${conversation.members.map(describeConversationMember).join(', ')}`);
496
+ }
497
+ if (conversation.lastMessage) {
498
+ const sender = conversation.lastMessage.senderName ?? 'Unknown sender';
499
+ console.log(` Last message: ${sender}: ${summarizeText(conversation.lastMessage.body)} (${formatTimestamp(conversation.lastMessage.createdAt)})`);
500
+ }
501
+ if (conversation.cursorAt) {
502
+ console.log(` Last read: ${formatTimestamp(conversation.cursorAt)}`);
503
+ }
504
+ console.log();
505
+ }
506
+ }
507
+ async function handleMessagesList(flags) {
508
+ const env = resolveRuntimeEnv();
509
+ const scope = await resolveMessageScope(env, {
510
+ peer: flags.peer,
511
+ conversation: flags.conversation,
512
+ });
513
+ const result = await listChatMessages(env, scope, {
514
+ before: flags.before,
515
+ limit: parsePositiveIntFlag(flags.limit, '--limit'),
516
+ });
517
+ if (isJson(flags)) {
518
+ printJson({
519
+ scope,
520
+ ...result,
521
+ });
522
+ return;
523
+ }
524
+ printScopeSummary(scope);
525
+ if (result.lastReadAt) {
526
+ console.log(`Last read: ${formatTimestamp(result.lastReadAt)}`);
527
+ }
528
+ if (result.messages.length === 0) {
529
+ console.log('No messages found.');
530
+ return;
531
+ }
532
+ console.log();
533
+ for (const [index, message] of result.messages.entries()) {
534
+ printMessage(message);
535
+ if (index < result.messages.length - 1)
536
+ console.log();
537
+ }
538
+ if (result.hasMore) {
539
+ console.log();
540
+ console.log('More messages are available. Use `--before <iso>` to page backward.');
541
+ }
542
+ }
543
+ async function handleMessagesSend(positionals, flags) {
544
+ const env = resolveRuntimeEnv();
545
+ const scope = await resolveMessageScope(env, {
546
+ peer: flags.peer,
547
+ conversation: flags.conversation,
548
+ });
549
+ const body = await readMessageBody(positionals, flags);
550
+ if (!body) {
551
+ die('Usage: btx messages send "<body>" [--peer <member>] [--conversation <conversation>] [--reply-to <message-id>]');
552
+ }
553
+ const result = await sendChatMessage(env, scope, {
554
+ body,
555
+ replyToId: flags['reply-to'] ?? null,
556
+ });
557
+ if (isJson(flags)) {
558
+ printJson({
559
+ ...result,
560
+ scope,
561
+ body,
562
+ replyToId: flags['reply-to'] ?? null,
563
+ });
564
+ return;
565
+ }
566
+ console.log(`Sent message ${result.id}`);
567
+ printScopeSummary(scope);
568
+ console.log(`Created: ${formatTimestamp(result.createdAt)}`);
569
+ }
570
+ async function handleMessages(positionals, flags) {
571
+ const subcommand = positionals[1];
572
+ if (flags.help === 'true' || !subcommand || subcommand === 'help') {
573
+ printMessagesHelp();
574
+ return;
575
+ }
576
+ switch (subcommand) {
577
+ case 'list':
578
+ case 'read':
579
+ return handleMessagesList(flags);
580
+ case 'send':
581
+ return handleMessagesSend(positionals, flags);
582
+ case 'members':
583
+ return handleMessagesMembers(flags);
584
+ case 'conversations':
585
+ return handleMessagesConversations(flags);
586
+ default:
587
+ die('Usage: btx messages <list|read|send|members|conversations> [...]');
588
+ }
589
+ }
590
+ function handleLoginStatus(flags) {
591
+ const status = authStatusPayload();
592
+ if (isJson(flags)) {
593
+ printJson(status);
594
+ return;
595
+ }
596
+ console.log(`Logged in: ${status.loggedIn ? 'yes' : 'no'}`);
597
+ console.log(`Auth source: ${status.authSource ?? 'none'}`);
598
+ console.log(`API base: ${status.apiBase}`);
599
+ console.log(`Desktop fallback enabled: ${status.desktopFallbackEnabled ? 'yes' : 'no'}`);
600
+ if (status.hasEnvToken) {
601
+ console.log('BTX_ACCESS_TOKEN is set in the current shell environment.');
602
+ }
603
+ }
604
+ async function handleLogin(positionals, flags) {
605
+ const subcommand = positionals[1];
606
+ if (subcommand === 'status') {
607
+ handleLoginStatus(flags);
608
+ return;
609
+ }
610
+ if (subcommand) {
611
+ die('Usage: btx login [status] [flags]');
612
+ }
253
613
  const env = resolveRuntimeEnv(process.cwd(), {
254
614
  ...(flags['api-url'] ? { BTX_API_URL: flags['api-url'] } : {}),
255
615
  });
@@ -280,6 +640,34 @@ async function handleLogin(flags) {
280
640
  }
281
641
  printLoginResult(result.authSource, result.apiUrl, result.browserOpened);
282
642
  }
643
+ function handleLogout(flags) {
644
+ const hadEnvToken = !!process.env.BTX_ACCESS_TOKEN;
645
+ const result = clearStoredAuthSession({
646
+ clearDesktop: flags.all === 'true',
647
+ });
648
+ const status = authStatusPayload();
649
+ if (isJson(flags)) {
650
+ printJson({
651
+ loggedIn: status.loggedIn,
652
+ authSource: status.authSource,
653
+ desktopFallbackEnabled: status.desktopFallbackEnabled,
654
+ clearedLocal: result.clearedLocal,
655
+ clearedDesktop: result.clearedDesktop,
656
+ hadEnvToken,
657
+ });
658
+ return;
659
+ }
660
+ console.log('Removed stored BTX CLI authentication.');
661
+ console.log(`Cleared local session: ${result.clearedLocal ? 'yes' : 'no'}`);
662
+ console.log(`Cleared desktop session: ${result.clearedDesktop ? 'yes' : 'no'}`);
663
+ console.log(`Desktop fallback enabled: ${status.desktopFallbackEnabled ? 'yes' : 'no'}`);
664
+ if (!flags.all || flags.all !== 'true') {
665
+ console.log('The standalone CLI will stay logged out until you run `btx login` again.');
666
+ }
667
+ if (hadEnvToken) {
668
+ console.log('BTX_ACCESS_TOKEN is still set in the current shell environment and cannot be cleared by `btx logout`.');
669
+ }
670
+ }
283
671
  async function selectProject(env, projectId) {
284
672
  const projects = await listProjects(env);
285
673
  if (projects.length === 0) {
@@ -480,7 +868,10 @@ async function handleSessionCheckpoint(flags) {
480
868
  const type = flags.type || 'note';
481
869
  const content = flags.content;
482
870
  if (!content) {
483
- die('Usage: btx session checkpoint --type note|risk|decision|blocker|progress|assumption --content "..."');
871
+ die(`Usage: btx session checkpoint --type ${SESSION_CHECKPOINT_USAGE} --content "..."`);
872
+ }
873
+ if (!SESSION_CHECKPOINT_TYPES.includes(type)) {
874
+ die(`Unsupported checkpoint type: ${type}. Use one of ${SESSION_CHECKPOINT_USAGE}.`);
484
875
  }
485
876
  const env = resolveRuntimeEnv(repoRoot, {
486
877
  BTX_SESSION_ID: active.sessionId,
@@ -563,11 +954,17 @@ async function main() {
563
954
  }
564
955
  switch (command) {
565
956
  case 'login':
566
- return handleLogin(flags);
957
+ return handleLogin(positional, flags);
958
+ case 'logout':
959
+ return handleLogout(flags);
567
960
  case 'setup':
568
961
  return handleSetup(positional[1], flags);
569
962
  case 'projects':
570
963
  return handleProjects(flags);
964
+ case 'ask':
965
+ return handleAsk(positional, flags);
966
+ case 'messages':
967
+ return handleMessages(positional, flags);
571
968
  case 'repo':
572
969
  switch (positional[1]) {
573
970
  case 'link':