@meshxdata/fops 0.1.34 → 0.1.35

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/CHANGELOG.md CHANGED
@@ -1,3 +1,191 @@
1
+ # Changelog
2
+
3
+ All notable changes to @meshxdata/fops (Foundation Operator CLI) are documented here.
4
+
5
+ ## [0.1.35] - 2026-03-05
6
+
7
+ - status bar fix and new plugin for ttyd (27dde1e)
8
+ - file demo and tray (1a3e704)
9
+ - electron app (59ad0bb)
10
+ - compose and fops file plugin (1cf0e81)
11
+ - bump (346ffc1)
12
+ - localhost replaced by 127.0.0.1 (82b9f30)
13
+ - .29 (587b0e1)
14
+ - improve up down and bootstrap script (b79ebaf)
15
+ - checksum (22c8086)
16
+ - checksum (96b434f)
17
+ - checksum (15ed3c0)
18
+ - checksum (8a6543a)
19
+ - bump embed trino linksg (8440504)
20
+ - bump data (765ffd9)
21
+ - bump (cb8b232)
22
+ - broken tests (c532229)
23
+ - release 0.1.18, preflight checks (d902249)
24
+ - fix compute display bug (d10f5d9)
25
+ - cleanup packer files (6330f18)
26
+ - plan mode (cb36a8a)
27
+ - bump to 0.1.16 - agent ui (41ac1a2)
28
+ - bump to 0.1.15 - agent ui (4ebe2e1)
29
+ - bump to 0.1.14 (6c3a7fa)
30
+ - bump to 0.1.13 (8db570f)
31
+ - release 0.1.12 (c1c79e5)
32
+ - bump (11aa3b0)
33
+ - git keep and bump tui (be1678e)
34
+ - skills, index, rrf, compacted context (100k > 10k) (7b2fffd)
35
+ - cloudflare and token consumption, graphs indexing (0ad9eec)
36
+ - bump storage default (22c83ba)
37
+ - storage fix (68a22a0)
38
+ - skills update (7f56500)
39
+ - v9 bump (3864446)
40
+ - bump (c95eedc)
41
+ - rrf (dbf8c95)
42
+ - feat: warning when running predictions (95e8c52)
43
+ - feat: support for local predictions (45cf26b)
44
+ - feat: wip support for predictions + mlflow (3457052)
45
+ - add Reciprocal Rank Fusion (RRF) to knowledge and skill retrieval (61549bc)
46
+ - validate CSV headers in compute_run readiness check (a8c7a43)
47
+ - fix corrupted Iceberg metadata: probe tables + force cleanup on re-apply (50578af)
48
+ - enforce: never use foundation_apply to fix broken products (2e049bf)
49
+ - update SKILL.md with complete tool reference for knowledge retrieval (30b1924)
50
+ - add storage read, input DP table probe, and compute_run improvements (34e6c4c)
51
+ - skills update (1220385)
52
+ - skills update (bb66958)
53
+ - some tui improvement andd tools apply overwrite (e90c35c)
54
+ - skills update (e9227a1)
55
+ - skills update (669c4b3)
56
+ - fix plugin pre-flight checks (f741743)
57
+ - increase agent context (6479aaa)
58
+ - skills and init sql fixes (5fce35e)
59
+ - checksum (3518b56)
60
+ - penging job limit (a139861)
61
+ - checksum (575d28c)
62
+ - bump (92049ba)
63
+ - fix bug per tab status (0a33657)
64
+ - fix bug per tab status (50457c6)
65
+ - checksumming (0ad842e)
66
+ - shot af mardkwon overlapping (51f63b9)
67
+ - add spark dockerfile for multiarch builds (95abbd1)
68
+ - fix plugin initialization (16b9782)
69
+ - split index.js (50902a2)
70
+ - cloudflare cidr (cc4e021)
71
+ - cloduflare restrictions (2f6ba2d)
72
+ - sequential start (86b496e)
73
+ - sequential start (4930fe1)
74
+ - sequential start (353f014)
75
+ - qa tests (2dc6a1a)
76
+ - bump sha for .85 (dc2edfe)
77
+ - preserve env on sudo (7831227)
78
+ - bump sha for .84 (6c052f9)
79
+ - non interactive for azure vms (0aa8a2f)
80
+ - keep .env if present (d072450)
81
+ - bump (7a8e732)
82
+ - ensure opa is on compose if not set (f4a5228)
83
+ - checksum bump (a2ccc20)
84
+ - netrc defensive checks (a0b0ccc)
85
+ - netrc defensive checks (ae37403)
86
+ - checksum (ec45d11)
87
+ - update sync and fix up (7f9af72)
88
+ - expand test for azure and add new per app tag support (388a168)
89
+ - checksum on update (44005fc)
90
+ - cleanup for later (15e5313)
91
+ - cleanup for later (11c9597)
92
+ - switch branch feature (822fecc)
93
+ - add pull (d1c19ab)
94
+ - Bump hono from 4.11.9 to 4.12.0 in /operator-cli (ad25144)
95
+ - tests (f180a9a)
96
+ - cleanup (39c49a3)
97
+ - registry (7b7126a)
98
+ - reconcile kafka (832d0db)
99
+ - gh login bug (025886c)
100
+ - cleanup (bb96cab)
101
+ - strip envs from process (2421180)
102
+ - force use of gh creds not tokens in envs var (fff7787)
103
+ - resolve import between npm installs and npm link (79522e1)
104
+ - fix gh scope and azure states (afd846c)
105
+ - refactoring (da50352)
106
+ - split fops repo (d447638)
107
+ - aks (b791f8f)
108
+ - refactor azure (67d3bad)
109
+ - wildcard (391f023)
110
+ - azure plugin (c074074)
111
+ - zap (d7e6e7f)
112
+ - fix knock (cf89c05)
113
+ - azure (4adec98)
114
+ - Bump tar from 7.5.7 to 7.5.9 in /operator-cli (e41e98e)
115
+ - azure stack index.js split (de12272)
116
+ - Bump ajv from 8.17.1 to 8.18.0 in /operator-cli (76da21f)
117
+ - packer (9665fbc)
118
+ - remove stack api (db0fd4d)
119
+ - packer cleanup (fe1bf14)
120
+ - force refresh token (3a3d7e2)
121
+ - provision shell (2ad505f)
122
+ - azure vm management (91dcb31)
123
+ - azure specific (2b0cca8)
124
+ - azure packer (12175b8)
125
+ - init hashed pwd (db8523c)
126
+ - packer (5b5c7c4)
127
+ - doctor for azure vm (ed524fa)
128
+ - packer and 1pwd (c6d053e)
129
+ - split big index.js (dc85a1b)
130
+ - kafka volume update (21815ec)
131
+ - fix openai azure tools confirmation and flow (0118cd1)
132
+ - nighly fixx, test fix (5e0d04f)
133
+ - open ai training (cdc494a)
134
+ - openai integration in azure (1ca1475)
135
+ - ci (672cea9)
136
+ - refresh ghcr creds (4220c48)
137
+ - cleaned up version (1a0074f)
138
+ - traefik on ghcr and templates (8e31a05)
139
+ - apply fcl (e78911f)
140
+ - demo landscape (dd205fe)
141
+ - smarter login and schema (1af514f)
142
+ - no down before up unless something broke (56b1132)
143
+ - dai, reconcile failed containers (12907fa)
144
+ - reconcile dead container (7da75e4)
145
+ - defensive around storage buckets dir (b98871d)
146
+ - defensive around storage buckets dir (e86e132)
147
+ - gear in for multiarch (bf3fa3e)
148
+ - up autofix (99c7f89)
149
+ - autofix stale containers on up (43c7d0f)
150
+ - shared sessions fix (5de1359)
151
+ - share sessions between ui and tui (8321391)
152
+ - fix chat view display details (e263996)
153
+ - fix chat view display details (9babdda)
154
+ - tui up fixes (86e9f17)
155
+ - fix commands init (442538b)
156
+ - enable k3s profile (b2dcfc8)
157
+ - test up till job creation (656d388)
158
+ - tui fixes (0599779)
159
+ - cleanup (27731f0)
160
+ - train (90bf559)
161
+ - training (f809bf6)
162
+ - training (ba2b836)
163
+ - training (6fc5267)
164
+ - training (4af8ac9)
165
+ - fix build script (bd82836)
166
+ - infra test (5b79815)
167
+ - infra test (3a0ac05)
168
+ - infra test (e5c67b5)
169
+ - tests (ae7b621)
170
+ - tests (c09ae6a)
171
+ - update tui (4784153)
172
+ - training (0a5a330)
173
+ - tui (df4dd4a)
174
+ - pkg builds (4dc9993)
175
+ - also source env for creds (9a17d8f)
176
+ - fcl support (e8a5743)
177
+ - fcl support (8d6b6cd)
178
+ - fcl support (cb76a4a)
179
+ - bump package (df2ee85)
180
+ - add iam mgmt (2d3c294)
181
+ - fix k3s (976ae77)
182
+ - fix trino, add storage plugin (75cb1f4)
183
+ - add project root as config (a2863c6)
184
+ - failure learnings (637ef5c)
185
+ - Apple signed binaries (63a610e)
186
+ - send build info to apple for notary service (300c220)
187
+ - migration failure fixes (c7f0b2f)
188
+
1
189
  ## [0.1.34] - 2026-03-05
2
190
 
3
191
  - electron app (59ad0bb)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshxdata/fops",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "CLI to install and manage data mesh platforms",
5
5
  "keywords": [
6
6
  "fops",
package/src/agent/llm.js CHANGED
@@ -559,11 +559,11 @@ function resolveProvider(opts, { anthropicKey, openaiKey, azureConfig, useClaude
559
559
  */
560
560
  function defaultModelForProvider(provider, opts, { anthropicKey, azureConfig }) {
561
561
  if (opts.model) return opts.model;
562
- if (provider === "anthropic" || provider === "claude_code") return process.env.ANTHROPIC_MODEL?.trim() || "claude-opus-4-20250514";
562
+ if (provider === "anthropic" || provider === "claude_code") return process.env.ANTHROPIC_MODEL?.trim() || "claude-sonnet-4-6";
563
563
  if (provider === "azure") return azureConfig?.deployment || "gpt-4o";
564
564
  if (provider === "openai") return process.env.OPENAI_MODEL?.trim() || "gpt-5";
565
565
  // Fallback
566
- return anthropicKey ? (process.env.ANTHROPIC_MODEL?.trim() || "claude-opus-4-20250514") : azureConfig ? azureConfig.deployment : "gpt-5";
566
+ return anthropicKey ? (process.env.ANTHROPIC_MODEL?.trim() || "claude-sonnet-4-6") : azureConfig ? azureConfig.deployment : "gpt-5";
567
567
  }
568
568
 
569
569
  export async function streamAssistantReply(root, messages, systemContent, opts) {
@@ -794,7 +794,7 @@ export async function callClaudeWithTools(messages, systemContent, tools, opts =
794
794
  const cached = getAnthropicModule();
795
795
  const { default: Anthropic } = cached || await import("@anthropic-ai/sdk");
796
796
  const client = new Anthropic({ apiKey: anthropicKey });
797
- const model = opts.model || process.env.ANTHROPIC_MODEL?.trim() || "claude-opus-4-20250514";
797
+ const model = opts.model || process.env.ANTHROPIC_MODEL?.trim() || "claude-sonnet-4-6";
798
798
  const claudeTools = toClaudeTools(tools);
799
799
 
800
800
  // Redact PII from context before sending
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "fops-plugin-dai-ttyd",
3
+ "name": "DAI Talk-to-your-Data",
4
+ "version": "0.1.0",
5
+ "description": "Natural language queries against the DAI TTYD (Talk-to-your-Data) service"
6
+ }
@@ -0,0 +1,182 @@
1
+ import chalk from "chalk";
2
+ import { DaiTTYDClient } from "./lib/client.js";
3
+
4
+ const DIM = chalk.dim;
5
+ const OK = chalk.green;
6
+ const ERR = chalk.red;
7
+ const ACCENT = chalk.cyan;
8
+ const BOLD = chalk.bold;
9
+ const WARN = chalk.yellow;
10
+
11
+ export function register(api) {
12
+ const client = new DaiTTYDClient(api.config);
13
+
14
+ function clientFor({ url, jwt } = {}) {
15
+ if (!url && !jwt) return client;
16
+ return new DaiTTYDClient({
17
+ ...api.config,
18
+ ...(url && { baseUrl: url }),
19
+ ...(jwt && { authToken: jwt }),
20
+ });
21
+ }
22
+
23
+ // ─── Doctor check ────────────────────────────────────────────────────────
24
+ api.registerDoctorCheck({
25
+ name: "dai-ttyd: auth token",
26
+ fn: async () => {
27
+ const token =
28
+ api.config?.authToken?.trim() || process.env.DAI_AUTH_TOKEN?.trim();
29
+ if (!token) {
30
+ return {
31
+ ok: false,
32
+ message: "DAI_AUTH_TOKEN not set — configure via env or ~/.fops.json plugins.entries.fops-plugin-dai-ttyd.config.authToken",
33
+ };
34
+ }
35
+ return { ok: true, message: "DAI_AUTH_TOKEN is set" };
36
+ },
37
+ });
38
+
39
+ // ─── Agent tools ─────────────────────────────────────────────────────────
40
+
41
+ api.registerTool({
42
+ name: "ttyd_thread_create",
43
+ description: "Create a new TTYD conversation thread. Returns a thread_id to use with ttyd_ask.",
44
+ inputSchema: {
45
+ type: "object",
46
+ properties: {
47
+ title: { type: "string", description: "Optional title for the thread" },
48
+ },
49
+ },
50
+ async execute({ title } = {}) {
51
+ const threadId = await client.createThread(title || "fops session");
52
+ return JSON.stringify({ thread_id: threadId });
53
+ },
54
+ });
55
+
56
+ api.registerTool({
57
+ name: "ttyd_ask",
58
+ description:
59
+ "Ask a natural-language question to the DAI Talk-to-your-Data service. " +
60
+ "Returns the generated SQL and a text summary/answer. " +
61
+ "Optionally scoped to a dashboard or tile for context. " +
62
+ "If thread_id is omitted, a new thread is created automatically.",
63
+ inputSchema: {
64
+ type: "object",
65
+ properties: {
66
+ question: { type: "string", description: "The natural-language question to ask" },
67
+ thread_id: { type: "number", description: "Existing thread id for multi-turn conversations" },
68
+ dashboard_id: { type: "number", description: "Optional dashboard id for context" },
69
+ tile_id: { type: "number", description: "Optional tile id for context" },
70
+ },
71
+ required: ["question"],
72
+ },
73
+ async execute({ question, thread_id, dashboard_id, tile_id }) {
74
+ const result = await client.ask(question, {
75
+ threadId: thread_id,
76
+ dashboardId: dashboard_id,
77
+ tileId: tile_id,
78
+ });
79
+ return JSON.stringify(result);
80
+ },
81
+ });
82
+
83
+ // ─── CLI commands ─────────────────────────────────────────────────────────
84
+
85
+ api.registerCommand((program) => {
86
+ const ttyd = program
87
+ .command("ttyd")
88
+ .description("Talk-to-your-Data — query data with natural language")
89
+ .option("--url <url>", "Override the TTYD base URL (e.g. https://api-live.dashboards.ai)")
90
+ .option("--jwt <token>", "Bearer token (overrides DAI_AUTH_TOKEN env)");
91
+
92
+ // fops ttyd ask "<question>"
93
+ ttyd
94
+ .command("ask <question>")
95
+ .description("Ask a natural-language question and stream the answer")
96
+ .option("--thread <id>", "Reuse an existing thread id (for follow-up questions)", (v) => Number(v))
97
+ .option("--dashboard <id>", "Scope to a dashboard id", (v) => Number(v))
98
+ .option("--tile <id>", "Scope to a tile id", (v) => Number(v))
99
+ .option("--json", "Output raw JSON instead of formatted text")
100
+ .option("--debug", "Print raw SSE events")
101
+ .action(async (question, opts) => {
102
+ const { url, jwt } = ttyd.opts();
103
+ const c = clientFor({ url, jwt });
104
+
105
+ let threadId = opts.thread ?? null;
106
+ if (threadId == null) {
107
+ process.stdout.write(DIM(" Creating thread... "));
108
+ try {
109
+ threadId = await c.createThread();
110
+ process.stdout.write(DIM(`thread ${threadId}\n`));
111
+ } catch (e) {
112
+ process.stdout.write(WARN(`skipped (${e.message})\n`));
113
+ // Proceed without a thread_id — server may create one implicitly
114
+ }
115
+ }
116
+
117
+ console.log(ACCENT(`\n ── Asking ${"─".repeat(44)}`));
118
+ console.log(DIM(` ${question}\n`));
119
+
120
+ let sql = "";
121
+ const summaryParts = [];
122
+
123
+ try {
124
+ for await (const event of c.askStream(question, threadId, {
125
+ dashboardId: opts.dashboard,
126
+ tileId: opts.tile,
127
+ })) {
128
+ if (opts.debug) console.error(DIM(`[sse] ${JSON.stringify(event)}`));
129
+ if (event.type === "sql") {
130
+ sql = event.content ?? "";
131
+ if (!opts.json) {
132
+ console.log(BOLD(" SQL:"));
133
+ for (const line of sql.split("\n")) {
134
+ console.log(DIM(` ${line}`));
135
+ }
136
+ console.log();
137
+ }
138
+ } else if (event.type === "answer") {
139
+ const chunk = event.content ?? "";
140
+ summaryParts.push(chunk);
141
+ if (!opts.json) process.stdout.write(chunk);
142
+ } else if (event.type === "error") {
143
+ throw new Error(event.message ?? "TTYD error");
144
+ } else if (event.type === "done") {
145
+ break;
146
+ }
147
+ }
148
+ } catch (e) {
149
+ console.error(ERR(`\n ✗ ${e.message}`));
150
+ return;
151
+ }
152
+
153
+ if (!opts.json) {
154
+ if (summaryParts.length > 0) process.stdout.write("\n");
155
+ if (threadId != null) {
156
+ console.log(DIM(`\n thread: ${threadId}`));
157
+ console.log(DIM(" Use --thread to continue this conversation.\n"));
158
+ } else {
159
+ console.log();
160
+ }
161
+ } else {
162
+ console.log(JSON.stringify({ thread_id: threadId, sql, summary: summaryParts.join("") }, null, 2));
163
+ }
164
+ });
165
+
166
+ // fops ttyd thread new [title]
167
+ const thread = ttyd
168
+ .command("thread")
169
+ .description("Manage TTYD threads");
170
+
171
+ thread
172
+ .command("new [title]")
173
+ .description("Create a new conversation thread")
174
+ .action(async (title = "fops session") => {
175
+ const { url, jwt } = ttyd.opts();
176
+ const c = clientFor({ url, jwt });
177
+ const threadId = await c.createThread(title);
178
+ console.log(OK(` ✓ Thread created: ${threadId}`));
179
+ console.log(DIM(` Use: fops ttyd ask --thread ${threadId} "<question>"\n`));
180
+ });
181
+ });
182
+ }
@@ -0,0 +1,164 @@
1
+ import http from "node:http";
2
+ import https from "node:https";
3
+
4
+ const DEFAULT_BASE_URL = "https://api.dashboards.ai";
5
+
6
+ function resolveBaseUrl(config) {
7
+ return (
8
+ config?.baseUrl?.trim() ||
9
+ process.env.DAI_TTYD_URL?.trim() ||
10
+ DEFAULT_BASE_URL
11
+ ).replace(/\/+$/, "");
12
+ }
13
+
14
+ function resolveAuthToken(config) {
15
+ const raw = config?.authToken || process.env.DAI_AUTH_TOKEN || "";
16
+ return raw.replace(/\s+/g, ""); // strip whitespace/newlines from env or shell wrapping
17
+ }
18
+
19
+ export class DaiTTYDClient {
20
+ constructor(config = {}) {
21
+ this.baseUrl = resolveBaseUrl(config);
22
+ this.authToken = resolveAuthToken(config);
23
+ }
24
+
25
+ _headers() {
26
+ return {
27
+ "Content-Type": "application/json",
28
+ Accept: "application/json",
29
+ Authorization: `Bearer ${this.authToken}`,
30
+ };
31
+ }
32
+
33
+ /** POST /ttyd/threads → thread id (number) */
34
+ async createThread(title = "fops session") {
35
+ const url = `${this.baseUrl}/ttyd/threads`;
36
+ let { status, data } = await request("POST", url, this._headers(), { title });
37
+ // Backend may return 400 "User with email already exists" on the first call
38
+ // when auto-provisioning the user — retry once, it should succeed
39
+ if (status === 400 && data.includes("already exists")) {
40
+ ({ status, data } = await request("POST", url, this._headers(), { title }));
41
+ }
42
+ if (status >= 400) throw new Error(`Failed to create thread (HTTP ${status}): ${data}`);
43
+ const body = JSON.parse(data);
44
+ const id = body.id ?? body.thread_id;
45
+ if (id == null) throw new Error(`No thread id in response: ${data}`);
46
+ return id;
47
+ }
48
+
49
+ /**
50
+ * POST /ttyd/answer/stream — async generator yielding SSE event objects.
51
+ * Each event: { type: "sql"|"answer"|"done"|"error", content?: string, message?: string }
52
+ */
53
+ async *askStream(question, threadId, { dashboardId, tileId } = {}) {
54
+ const url = `${this.baseUrl}/ttyd/answer/stream`;
55
+ const payload = { question, thread_id: threadId };
56
+ if (dashboardId != null) payload.dashboard_id = Number(dashboardId);
57
+ if (tileId != null) payload.tile_id = Number(tileId);
58
+ yield* streamSSE("POST", url, this._headers(), payload);
59
+ }
60
+
61
+ /** Convenience: create a thread, ask one question, collect full sql + summary. */
62
+ async ask(question, { threadId, dashboardId, tileId } = {}) {
63
+ const id = threadId ?? (await this.createThread());
64
+ let sql = "";
65
+ const summaryParts = [];
66
+ for await (const event of this.askStream(question, id, { dashboardId, tileId })) {
67
+ if (event.type === "sql") sql = event.content ?? "";
68
+ else if (event.type === "answer") summaryParts.push(event.content ?? "");
69
+ else if (event.type === "error") throw new Error(event.message ?? "TTYD error");
70
+ else if (event.type === "done") break;
71
+ }
72
+ return { threadId: id, sql, summary: summaryParts.join("") };
73
+ }
74
+
75
+ /** Verify connectivity — returns true/false. */
76
+ async ping() {
77
+ try {
78
+ const url = `${this.baseUrl}/ttyd/threads`;
79
+ const { status } = await request("POST", url, this._headers(), { title: "ping" });
80
+ return status < 500;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ }
86
+
87
+ // ─── Helpers ────────────────────────────────────────────────────────────────
88
+
89
+ function request(method, url, headers, body, timeoutMs = 15_000) {
90
+ return new Promise((resolve, reject) => {
91
+ const parsed = new URL(url);
92
+ const lib = parsed.protocol === "https:" ? https : http;
93
+ const encoded = JSON.stringify(body);
94
+ const options = {
95
+ hostname: parsed.hostname,
96
+ port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
97
+ path: parsed.pathname + parsed.search,
98
+ method,
99
+ headers: { ...headers, "Content-Length": Buffer.byteLength(encoded) },
100
+ timeout: timeoutMs,
101
+ };
102
+ const req = lib.request(options, (res) => {
103
+ let data = "";
104
+ res.on("data", (c) => { data += c; });
105
+ res.on("end", () => resolve({ status: res.statusCode, data }));
106
+ });
107
+ req.on("error", reject);
108
+ req.on("timeout", () => { req.destroy(); reject(new Error("Request timeout")); });
109
+ req.write(encoded);
110
+ req.end();
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Async generator that streams SSE from an HTTP endpoint.
116
+ * Yields parsed JSON objects from each "data: {...}" line.
117
+ */
118
+ async function* streamSSE(method, url, headers, body, timeoutMs = 300_000) {
119
+ const parsed = new URL(url);
120
+ const lib = parsed.protocol === "https:" ? https : http;
121
+ const encoded = JSON.stringify(body);
122
+ const options = {
123
+ hostname: parsed.hostname,
124
+ port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
125
+ path: parsed.pathname + parsed.search,
126
+ method,
127
+ headers: {
128
+ ...headers,
129
+ "Content-Length": Buffer.byteLength(encoded),
130
+ Accept: "text/event-stream",
131
+ },
132
+ timeout: timeoutMs,
133
+ };
134
+
135
+ const res = await new Promise((resolve, reject) => {
136
+ const req = lib.request(options, resolve);
137
+ req.on("error", reject);
138
+ req.on("timeout", () => { req.destroy(); reject(new Error("Stream timeout")); });
139
+ req.write(encoded);
140
+ req.end();
141
+ });
142
+
143
+ if (res.statusCode >= 400) {
144
+ let errData = "";
145
+ for await (const chunk of res) errData += chunk;
146
+ throw new Error(`HTTP ${res.statusCode}: ${errData}`);
147
+ }
148
+
149
+ let buffer = "";
150
+ for await (const chunk of res) {
151
+ buffer += chunk.toString();
152
+ const lines = buffer.split("\n");
153
+ buffer = lines.pop(); // keep any incomplete line
154
+ for (const line of lines) {
155
+ if (!line.startsWith("data:")) continue;
156
+ const raw = line.slice("data:".length).trim();
157
+ if (!raw) continue;
158
+ let data;
159
+ try { data = JSON.parse(raw); } catch { continue; }
160
+ yield data;
161
+ if (data?.type === "done" || data?.type === "error") return;
162
+ }
163
+ }
164
+ }
@@ -0,0 +1 @@
1
+ { "type": "module" }
package/src/ui/tui/App.js CHANGED
@@ -2790,7 +2790,7 @@ function TuiApp({ core, version, root }) {
2790
2790
  h(Box, { flexShrink: 0 },
2791
2791
  h(StatusBar, {
2792
2792
  agentName: activeSession?.agent,
2793
- model: core.opts.model || (providerLabel === "azure-openai" ? "azure-openai" : providerLabel === "openai" ? "openai" : "claude-sonnet"),
2793
+ model: core.opts.model || (providerLabel === "azure-openai" ? "azure-openai" : providerLabel === "openai" ? "openai" : process.env.ANTHROPIC_MODEL?.trim() || "claude-sonnet-4-6"),
2794
2794
  tokenCount: tokenCount || (activeSession ? (core.sessions.get(activeId)?.tokenCount || 0) : 0),
2795
2795
  cacheReadTokens,
2796
2796
  version,