@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 +14 -8
- package/dist/bin.js +410 -13
- package/dist/bin.js.map +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +31 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -3
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +2 -1
- package/dist/login.js.map +1 -1
- package/dist/messages.d.ts +127 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +112 -0
- package/dist/messages.js.map +1 -0
- package/dist/runtime-cli.d.ts.map +1 -1
- package/dist/runtime-cli.js +182 -5
- package/dist/runtime-cli.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
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:
|
|
226
|
-
hasAuthToken:
|
|
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: ${
|
|
242
|
-
console.log(`Auth token available: ${
|
|
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
|
-
|
|
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(
|
|
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':
|