@chatbridge/cli 0.2.2 → 0.3.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.
@@ -13,6 +13,7 @@ const EXIT_CODES = {
13
13
  PROVIDER_LOAD: 5,
14
14
  INVALID_PROVIDER: 5,
15
15
  INVALID_STATE: 1,
16
+ BLOCKED: 6,
16
17
  };
17
18
  const DEFAULT_TIMEOUT_SEC = 120;
18
19
  /** Validates --timeout before any browser is launched. */
@@ -46,6 +47,7 @@ export function createCli(opts) {
46
47
  "",
47
48
  "Without -p, an interactive chat opens (needs a terminal and Bun >= 1.3 or Node >= 26.4).",
48
49
  "One-shot mode prints the AI response to stdout.",
50
+ "Exit codes: 1 usage/config, 2 not logged in, 3 auth expired, 4 timeout, 5 provider load, 6 blocked by the service (try --headful).",
49
51
  ...(opts.provider
50
52
  ? []
51
53
  : [
@@ -4,9 +4,13 @@ export declare const GUIDE = "Enter send \u00B7 Shift+Enter (or Ctrl+J) newline
4
4
  export interface ChatViewOptions {
5
5
  title: string;
6
6
  providerName: string;
7
+ /** Response timeout budget shown next to the elapsed time. */
8
+ timeoutMs: number;
7
9
  }
8
10
  /** Builds the OpenTUI tree for one ChatModel and mirrors its state.
9
- * Layout: header / scrolling history / 4-line textarea / status line. */
11
+ * Layout: header / scrolling history / 4-line textarea / status line.
12
+ * Status line while busy: activity indicator with elapsed time against the
13
+ * timeout budget. */
10
14
  export declare class ChatView {
11
15
  private readonly renderer;
12
16
  private readonly model;
@@ -16,6 +20,8 @@ export declare class ChatView {
16
20
  private rendered;
17
21
  private spinner;
18
22
  private frame;
23
+ private readonly budgetSec;
24
+ private startedAt;
19
25
  private destroyed;
20
26
  private statusPinned;
21
27
  constructor(renderer: CliRenderer, model: ChatModel, opts: ChatViewOptions);
@@ -1,14 +1,17 @@
1
1
  import { BoxRenderable, ScrollBoxRenderable, TextRenderable, TextareaRenderable, } from "@opentui/core";
2
2
  export const GUIDE = "Enter send · Shift+Enter (or Ctrl+J) newline · Ctrl+C quit";
3
- const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
4
- const SPINNER_INTERVAL_MS = 80;
3
+ /** Three fixed cells so legacy terminals keep the line aligned. */
4
+ const FRAMES = ["●○○", "○●○", "○○●", "○●○"];
5
+ const FRAME_INTERVAL_MS = 120;
5
6
  const LABELS = {
6
7
  user: "You",
7
8
  assistant: "Assistant",
8
9
  error: "Error",
9
10
  };
10
11
  /** Builds the OpenTUI tree for one ChatModel and mirrors its state.
11
- * Layout: header / scrolling history / 4-line textarea / status line. */
12
+ * Layout: header / scrolling history / 4-line textarea / status line.
13
+ * Status line while busy: activity indicator with elapsed time against the
14
+ * timeout budget. */
12
15
  export class ChatView {
13
16
  renderer;
14
17
  model;
@@ -18,11 +21,14 @@ export class ChatView {
18
21
  rendered = 0;
19
22
  spinner;
20
23
  frame = 0;
24
+ budgetSec;
25
+ startedAt = 0;
21
26
  destroyed = false;
22
27
  statusPinned = false;
23
28
  constructor(renderer, model, opts) {
24
29
  this.renderer = renderer;
25
30
  this.model = model;
31
+ this.budgetSec = Math.round(opts.timeoutMs / 1000);
26
32
  const root = new BoxRenderable(renderer, {
27
33
  id: "root",
28
34
  flexDirection: "column",
@@ -136,12 +142,14 @@ export class ChatView {
136
142
  startSpinner() {
137
143
  if (this.spinner)
138
144
  return;
145
+ this.startedAt = Date.now();
139
146
  const tick = () => {
140
- this.frame = (this.frame + 1) % SPINNER.length;
141
- this.status.content = `${SPINNER[this.frame]} Waiting for response...`;
147
+ this.frame = (this.frame + 1) % FRAMES.length;
148
+ const elapsed = Math.floor((Date.now() - this.startedAt) / 1000);
149
+ this.status.content = `${FRAMES[this.frame]} Thinking… ${elapsed}s / ${this.budgetSec}s`;
142
150
  };
143
151
  tick();
144
- this.spinner = setInterval(tick, SPINNER_INTERVAL_MS);
152
+ this.spinner = setInterval(tick, FRAME_INTERVAL_MS);
145
153
  }
146
154
  stopSpinner() {
147
155
  if (!this.spinner)
@@ -50,6 +50,7 @@ export async function runInteractive(opts) {
50
50
  view = new ChatView(renderer, model, {
51
51
  title: opts.title,
52
52
  providerName: opts.provider.name,
53
+ timeoutMs: opts.timeoutMs,
53
54
  });
54
55
  const quit = waitForQuit(renderer, model);
55
56
  renderer.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatbridge/cli",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Drive browser-only web chat AI services from the command line",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "access": "public"
29
29
  },
30
30
  "dependencies": {
31
- "@chatbridge/core": "0.2.2",
31
+ "@chatbridge/core": "0.3.0",
32
32
  "@opentui/core": "0.5.10"
33
33
  },
34
34
  "devDependencies": {