fluxflow-cli 1.17.1 → 1.17.2

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.
Files changed (2) hide show
  1. package/dist/fluxflow.js +35 -17
  2. package/package.json +1 -1
package/dist/fluxflow.js CHANGED
@@ -304,7 +304,10 @@ var init_ChatLayout = __esm({
304
304
  const colPercentage = Math.floor(100 / header.length);
305
305
  const availableWidth = terminalWidth - 8;
306
306
  const colChars = Math.floor(availableWidth / header.length) - 2;
307
- return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "#333", paddingX: 1, marginY: 1, width: "100%", flexGrow: 1 }, /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "row", borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: "#444", marginBottom: 1, paddingBottom: 0, width: "100%" }, header.map((cell, i) => /* @__PURE__ */ React2.createElement(Box2, { key: i, flexBasis: `${colPercentage}%`, flexGrow: 1, flexShrink: 0, paddingRight: 2 }, /* @__PURE__ */ React2.createElement(InlineMarkdown, { text: wrapText(cell, colChars), color: "cyan" })))), data.map((row, ri) => /* @__PURE__ */ React2.createElement(Box2, { key: ri, flexDirection: "row", marginBottom: ri === data.length - 1 ? 0 : 1, width: "100%" }, row.map((cell, ci) => /* @__PURE__ */ React2.createElement(Box2, { key: ci, flexBasis: `${colPercentage}%`, flexGrow: 1, flexShrink: 0, paddingRight: 2, flexDirection: "column" }, /* @__PURE__ */ React2.createElement(InlineMarkdown, { text: wrapText(cell, colChars), color: "white" }))))));
307
+ return (
308
+ // Table MarginY here
309
+ /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "#454545ff", paddingX: 1, marginY: 0, width: "100%", flexGrow: 1 }, /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "row", borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: "#444", marginBottom: 1, paddingBottom: 0, width: "100%" }, header.map((cell, i) => /* @__PURE__ */ React2.createElement(Box2, { key: i, flexBasis: `${colPercentage}%`, flexGrow: 1, flexShrink: 0, paddingRight: 2 }, /* @__PURE__ */ React2.createElement(InlineMarkdown, { text: wrapText(cell, colChars), color: "cyan" })))), data.map((row, ri) => /* @__PURE__ */ React2.createElement(Box2, { key: ri, flexDirection: "row", marginBottom: ri === data.length - 1 ? 0 : 1, width: "100%" }, row.map((cell, ci) => /* @__PURE__ */ React2.createElement(Box2, { key: ci, flexBasis: `${colPercentage}%`, flexGrow: 1, flexShrink: 0, paddingRight: 2, flexDirection: "column" }, /* @__PURE__ */ React2.createElement(InlineMarkdown, { text: wrapText(cell, colChars), color: "white" }))))))
310
+ );
308
311
  });
309
312
  MarkdownText = React2.memo(({ text, color = "white", columns = 80 }) => {
310
313
  if (!text) return null;
@@ -391,7 +394,7 @@ var init_ChatLayout = __esm({
391
394
  const match = text.match(/\[DIFF_START\]([\s\S]*?)\[DIFF_END\]/);
392
395
  const diffBody = match ? match[1].trim() : "";
393
396
  const diffLines = diffBody.split("\n");
394
- return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", backgroundColor: "#1a1a1a", paddingY: 0, width: "100%" }, diffLines.map((line, i) => /* @__PURE__ */ React2.createElement(DiffLine, { key: i, line, columns }))));
397
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", width: "100%", marginBottom: 0 }, /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", backgroundColor: "#1a1a1a", paddingY: 0, width: "100%" }, diffLines.map((line, i) => /* @__PURE__ */ React2.createElement(DiffLine, { key: i, line, columns }))));
395
398
  });
396
399
  CodeRenderer = React2.memo(({ text, columns = 80 }) => {
397
400
  if (!text) return null;
@@ -419,9 +422,17 @@ var init_ChatLayout = __esm({
419
422
  const code = match ? match[2] : part.replace(/^```\w*\n?/, "").replace(/```$/, "");
420
423
  const codeLines = code.trimEnd().split("\n");
421
424
  const gutterWidth = String(codeLines.length).length;
422
- return /* @__PURE__ */ React2.createElement(Box2, { key: i, flexDirection: "column", marginY: 1, backgroundColor: "#111", borderStyle: "round", borderColor: "#333", paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Box2, { alignSelf: "flex-end", marginTop: -1, marginRight: 1 }, /* @__PURE__ */ React2.createElement(Text2, { backgroundColor: "#333", color: "white" }, " ", lang.toUpperCase(), " ")), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingY: 1, width: "100%" }, codeLines.map((line, idx) => /* @__PURE__ */ React2.createElement(Box2, { key: idx, width: "100%" }, /* @__PURE__ */ React2.createElement(Box2, { width: gutterWidth + 2, flexShrink: 0 }, /* @__PURE__ */ React2.createElement(Text2, { color: "gray", dimColor: true }, String(idx + 1).padStart(gutterWidth, " "), " ")), /* @__PURE__ */ React2.createElement(Box2, { flexGrow: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, line))))));
425
+ return /* @__PURE__ */ React2.createElement(Box2, { key: i, flexDirection: "column", marginY: 0, backgroundColor: "#111", borderStyle: "round", borderColor: "#333", paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Box2, { alignSelf: "flex-end", marginTop: -1, marginRight: 1 }, /* @__PURE__ */ React2.createElement(Text2, { backgroundColor: "#333", color: "white" }, " ", lang.toUpperCase(), " ")), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingY: 1, width: "100%" }, codeLines.map((line, idx) => /* @__PURE__ */ React2.createElement(Box2, { key: idx, width: "100%" }, /* @__PURE__ */ React2.createElement(Box2, { width: gutterWidth + 2, flexShrink: 0 }, /* @__PURE__ */ React2.createElement(Text2, { color: "gray", dimColor: true }, String(idx + 1).padStart(gutterWidth, " "), " ")), /* @__PURE__ */ React2.createElement(Box2, { flexGrow: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, line))))));
426
+ }
427
+ let cleanPart = part;
428
+ if (i > 0) {
429
+ cleanPart = cleanPart.replace(/^[\r\n]+/, "");
423
430
  }
424
- return /* @__PURE__ */ React2.createElement(MarkdownText, { key: i, text: part, columns });
431
+ if (i < parts.length - 1) {
432
+ cleanPart = cleanPart.replace(/[\r\n]+$/, "");
433
+ }
434
+ if (!cleanPart) return null;
435
+ return /* @__PURE__ */ React2.createElement(MarkdownText, { key: i, text: cleanPart, columns });
425
436
  }));
426
437
  }
427
438
  return /* @__PURE__ */ React2.createElement(MarkdownText, { text, columns });
@@ -522,7 +533,7 @@ var init_ChatLayout = __esm({
522
533
  }, [content, msg.role, showFullThinking, msg.isStreaming]);
523
534
  return (
524
535
  // [SPACE POINT]
525
- /* @__PURE__ */ React2.createElement(Box2, { marginBottom: msg.role === "think" ? 0 : msg.role === "user" ? 1 : msg.role === "agent" ? 0 : 0, marginTop: msg.role === "think" ? 0 : msg.role === "user" ? 1 : msg.role === "agent" ? 0 : 1, flexDirection: "column", flexShrink: 0, width: "100%", flexGrow: 1 }, msg.role === "user" ? /* @__PURE__ */ React2.createElement(
536
+ /* @__PURE__ */ React2.createElement(Box2, { marginBottom: msg.role === "think" ? 0 : msg.role === "user" ? 1 : msg.role === "agent" ? 0 : 1, marginTop: msg.role === "think" ? 0 : msg.role === "user" ? 0 : msg.role === "agent" ? 0 : 0, flexDirection: "column", flexShrink: 0, width: "100%", flexGrow: 1 }, msg.role === "user" ? /* @__PURE__ */ React2.createElement(
526
537
  Box2,
527
538
  {
528
539
  backgroundColor: "#262626",
@@ -535,7 +546,7 @@ var init_ChatLayout = __esm({
535
546
  finalContent.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\\\n/g, "\n").replace(/\\$/, ""),
536
547
  columns - 6
537
548
  ).split("\n").map((line, lineIdx) => /* @__PURE__ */ React2.createElement(Box2, { key: lineIdx, flexDirection: "row", width: "100%" }, /* @__PURE__ */ React2.createElement(Box2, { flexShrink: 0, width: 2 }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, lineIdx === 0 ? "\u276F" : " ")), /* @__PURE__ */ React2.createElement(Box2, { flexGrow: 1, marginLeft: 1 }, /* @__PURE__ */ React2.createElement(InlineMarkdown, { text: line, color: msg.color || "white" }))))
538
- ) : msg.role === "think" ? /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 0, marginBottom: 0, paddingX: 1, width: "100%" }, msg.isStreaming && !msg.duration ? /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "\u2727 Thinking...") : /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "\u2726 Thought", msg.duration ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true, color: "gray" }, " for ", /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, formatThinkingDuration(msg.duration))) : ""), /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, paddingLeft: 2, paddingTop: 1, paddingBottom: 1, flexDirection: "column", width: "100%" }, formatThinkText(finalContent, columns))) : /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginTop: 0, width: "100%" }, /* @__PURE__ */ React2.createElement(CodeRenderer, { text: finalContent.replace(/ \|\n\n/g, " |\n"), columns }), msg.memoryUpdated && /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow", italic: true }, "\u2728 [Memory Updated]")), msg.role === "agent" && msg.workedDuration ? /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true, color: "gray" }, "[\u26A1 Worked for ", /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, formatThinkingDuration(msg.workedDuration)), " ]")) : null))
549
+ ) : msg.role === "think" ? /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 0, marginBottom: 0, paddingX: 1, width: "100%" }, msg.isStreaming && !msg.duration ? /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "\u2727 Thinking...") : /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "\u2726 Thought", msg.duration ? /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, " for ", /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, formatThinkingDuration(msg.duration))) : ""), /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, paddingLeft: 2, paddingTop: 1, paddingBottom: 1, flexDirection: "column", width: "100%" }, formatThinkText(finalContent, columns))) : /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginTop: 0, width: "100%" }, /* @__PURE__ */ React2.createElement(CodeRenderer, { text: finalContent.replace(/ \|\n\n/g, " |\n"), columns }), msg.memoryUpdated && /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow", italic: true }, "\u2728 [Memory Updated]")), msg.role === "agent" && msg.workedDuration ? /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, marginBottom: 2, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, null, "["), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u26A1 Worked for ", /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, formatThinkingDuration(msg.workedDuration))), /* @__PURE__ */ React2.createElement(Text2, null, "]")) : null))
539
550
  );
540
551
  });
541
552
  ChatLayout = React2.memo(({ messages, showFullThinking, columns = 80 }) => {
@@ -1280,7 +1291,8 @@ var init_main_tools = __esm({
1280
1291
  TOOL_PROTOCOL = (mode, osDetected) => `
1281
1292
  -- TOOL DEFINITIONS --
1282
1293
  Access to internal tools. To call a tool, MUST use the exact syntax on a new line: [tool:functions.ToolName(args)]
1283
- - **STRICT POLICY: MAX 4 TOOL CALLS PER TURN. Next Turn, verify results, plan next**
1294
+ STRICT POLICY
1295
+ - **MAX 3 TOOL CALLS PER TURN. Next Turn, verify results, plan next**${mode === "Flux" ? "\n- **File Tools >> Code in chat**" : ""}
1284
1296
 
1285
1297
  - COMMUNICATION TOOLS -
1286
1298
  1. [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Ambiguity Resolution. Mandatory Triggers: Path Divergence, Security, Risk Mitigation. ask >> finish
@@ -1302,7 +1314,6 @@ ${mode === "Flux" ? `- PROJECT TOOLS (path = relative to CWD) -
1302
1314
  9. [tool:functions.WriteDoc(path="...", content="...")]. A4 Word document
1303
1315
 
1304
1316
  - VERIFY TOOL RESULT CONTENTS. Fix errors. No hallucinations
1305
- - File tools >>> Long chat
1306
1317
 
1307
1318
  - Escape quotes: \\" for code strings
1308
1319
  - Literal escapes: Double-escape sequences (e.g., \\\\n, \\\\t)
@@ -1424,7 +1435,7 @@ Check these first; These Files > Training Data. Safety rules apply
1424
1435
  return `${nameStr}${nicknameStr}${userInstrStr}[SYSTEM]
1425
1436
  Identity: Flux Flow (by Kushal Roy Chowdhury). Sassy${mode === "Flux" ? ", No Flirting, Respectful" : ", Friendly, Humorous, Sarcastic"}, CLI Agent
1426
1437
  Mode: ${mode}${thinkingLevel !== "Fast" ? " (Thinking Mode)" : ""}. ${mode === "Flux" ? "Logical, Highly Detailed, Task-Driven. Prioritizes scalable file/folder structures, modular architecture, clean code abstractions, step-by-step execution. Industry standard latest coding practices/libraries, clean code, Double Check Imports, Client-Server Sync" : "Conversational, Concise"}
1427
- ${isSystemDir ? "[PROTECTED DIRECTORY: ASK BEFORE MODIFYING]\n" : ""}
1438
+
1428
1439
  -- THINKING RULES --
1429
1440
  ${thinkingConfig}
1430
1441
  ${thinkingLevel !== "Fast" ? `
@@ -1439,7 +1450,7 @@ ${projectContextBlock}
1439
1450
  - Temporal Awareness: RELATIVE TIME REFERENCE eg. few mins ago
1440
1451
 
1441
1452
  -- SECURITY RULES --${systemSettings.allowExternalAccess ? "" : "\n- ACCESS CONTROL: CWD only"}
1442
- - Sensitive files? Ask before Read
1453
+ - Sensitive files? Ask before Read${isSystemDir ? "\nPROTECTED DIRECTORY: ASK BEFORE MODIFYING" : ""}
1443
1454
 
1444
1455
  -- FORMATTING --
1445
1456
  - GFM Supported
@@ -1450,6 +1461,7 @@ ${projectContextBlock}
1450
1461
  - End with [turn: continue] to continue or [turn: finish] when task done
1451
1462
  - Tool Called? No post tool response until [turn: continue]
1452
1463
  - Task Complete? End loop with [turn: finish]
1464
+ - NEVER USE [turn: continue] [turn:finish] together
1453
1465
  [/SYSTEM]`.trim();
1454
1466
  };
1455
1467
  getJanitorInstruction = (userMemories = "", isMemoryEnabled = true, needTitle = true) => {
@@ -2957,7 +2969,7 @@ var init_update_file = __esm({
2957
2969
  const lineEndPos = currentContent.indexOf("\n", affectedEndPos);
2958
2970
  const actualEndPos = lineEndPos === -1 ? currentContent.length : lineEndPos;
2959
2971
  const fullOldLines = currentContent.substring(lineStartPos, actualEndPos).split("\n");
2960
- const newAffectedEndPos = startPos + content_to_add.length;
2972
+ const newAffectedEndPos = startPos + finalContentToAdd.length;
2961
2973
  const newLineEndPos = newFileContent.indexOf("\n", newAffectedEndPos);
2962
2974
  const actualNewEndPos = newLineEndPos === -1 ? newFileContent.length : newLineEndPos;
2963
2975
  const fullNewLines = newFileContent.substring(lineStartPos, actualNewEndPos).split("\n");
@@ -4699,13 +4711,14 @@ ${newMemoryListStr}
4699
4711
  return "";
4700
4712
  }
4701
4713
  };
4714
+ yield { type: "status", content: "Gathering Context..." };
4702
4715
  let dirStructure = process.cwd() + "\n" + getDirTree(process.cwd());
4703
4716
  const firstUserMsg = `[METADATA (PRIORITY: DYNAMIC)] Time: ${dateTimeStr} | v${versionFluxflow2}
4704
4717
  CWD: ${process.cwd()}
4705
4718
  **DIRECTORY STRUCTURE**
4706
4719
  ${dirStructure}
4707
4720
  ${memoryPrompt}
4708
- ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS CORE PRIORITY. DO NOT START A RESPONSE WITHOUT <think> ... </think>**\n" : ""}[USER] ${agentText.replace(/\s*\[Prompted on:.*?\]/g, "").trim()}`.trim();
4721
+ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS CRITICAL PRIORITY. DO NOT START A RESPONSE WITHOUT <think> ... </think>**\n" : ""}[USER] ${agentText.replace(/\s*\[Prompted on:.*?\]/g, "").trim()}`.trim();
4709
4722
  modifiedHistory.push({ role: "user", text: firstUserMsg });
4710
4723
  let lastUsage = null;
4711
4724
  const MAX_LOOPS = mode === "Flux" ? 70 : 7;
@@ -4736,7 +4749,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS CORE
4736
4749
 
4737
4750
  [STEERING HINT]: ${hint}`;
4738
4751
  } else {
4739
- modifiedHistory.push({ role: "user", text: `${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRICT PRIORITY. DO NOT START A RESPONSE WITHOUT <think> ... </think>**\n" : ""}[STEERING HINT]: ${hint}` });
4752
+ modifiedHistory.push({ role: "user", text: `${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS CRITICAL PRIORITY. DO NOT START A RESPONSE WITHOUT <think> ... </think>**\n" : ""}[STEERING HINT]: ${hint}` });
4740
4753
  }
4741
4754
  yield { type: "status", content: "Steering Hint Injected." };
4742
4755
  }
@@ -4803,7 +4816,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS CORE
4803
4816
  const currentSystemInstruction = getSystemInstruction(profile, thinkingLevel, mode, systemSettings, isMemoryEnabled, MAX_LOOPS, loop + 1);
4804
4817
  const jitInstruction = `
4805
4818
 
4806
- [SYSTEM] Tool result received. Analyze output and proceed with your turn.${thinkingLevel != "Fast" ? "**STRICTLY MAINTAIN THINKING PROTOCOL. DO NOT START A RESPONSE WITHOUT <think> ... </think>**" : ""}`;
4819
+ [SYSTEM] Tool result received. Analyze output and proceed with your turn.${thinkingLevel != "Fast" ? "**STRICTLY MAINTAIN THINKING POLICY. DO NOT START A RESPONSE WITHOUT <think> ... </think>**" : ""}`;
4807
4820
  const lastUserMsg = contents[contents.length - 1];
4808
4821
  let addedMarker = false;
4809
4822
  if (lastUserMsg && lastUserMsg.role === "user" && lastUserMsg.parts?.[0]?.text?.startsWith("[TOOL RESULT]")) {
@@ -5836,8 +5849,8 @@ function formatPromptPreview(prompt) {
5836
5849
  if (!prompt) return "";
5837
5850
  const firstLine = prompt.split("\n")[0] || "";
5838
5851
  const words = firstLine.split(/\s+/).filter(Boolean);
5839
- if (words.length > 25) {
5840
- return words.slice(0, 25).join(" ") + "...";
5852
+ if (words.length > 15) {
5853
+ return words.slice(0, 15).join(" ") + "...";
5841
5854
  }
5842
5855
  if (prompt.includes("\n")) {
5843
5856
  return firstLine + "...";
@@ -7130,7 +7143,8 @@ ${timestamp}` };
7130
7143
  let hasFiredJanitor = false;
7131
7144
  setIsProcessing(true);
7132
7145
  setIsExpanded(false);
7133
- const apiStart = Date.now();
7146
+ let apiStart = Date.now();
7147
+ let isFirstPacket = true;
7134
7148
  try {
7135
7149
  const cleanHistoryForAI = [...messages, userMessage].filter(
7136
7150
  (m) => m.role !== "think" && !m.isVisualFeedback && !String(m.id).startsWith("welcome")
@@ -7267,6 +7281,10 @@ Selection: ${val}`,
7267
7281
  let inToolCallString = null;
7268
7282
  const signalRegex = /\[?\s*turn\s*:\s*.*?\s*\]?/gi;
7269
7283
  for await (const packet of stream) {
7284
+ if (isFirstPacket && packet.type === "text") {
7285
+ apiStart = Date.now();
7286
+ isFirstPacket = false;
7287
+ }
7270
7288
  if (packet.type === "status") {
7271
7289
  setStatusText(packet.content);
7272
7290
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.17.1",
3
+ "version": "1.17.2",
4
4
  "date": "2026-05-29",
5
5
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
6
6
  "keywords": [