@scira/cli 0.1.4 → 0.1.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/dist/ui/ink/SciraApp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { Box, useApp, useStdout, useStdin } from "ink";
|
|
4
|
-
import { CHAT_COMMANDS, MENU_VISIBLE } from "./constants.js";
|
|
3
|
+
import { Box, Text, useApp, useStdout, useStdin } from "ink";
|
|
4
|
+
import { CHAT_COMMANDS, MENU_VISIBLE, SPINNER_FRAMES, LOADING_PHRASES } from "./constants.js";
|
|
5
5
|
import { CWD_DISPLAY, wrapText, wrapInputWithCursor, loadInputHistory, saveInputHistory, linkAtMouseColumn, openExternalUrl } from "./lib/utils.js";
|
|
6
6
|
import { deleteRun } from "../../storage/run-store.js";
|
|
7
7
|
import { saveGlobalConfig } from "../../config/load-config.js";
|
|
@@ -346,12 +346,25 @@ export function SciraApp({ runPath: initialRunPath, config: initialConfig }) {
|
|
|
346
346
|
const menuHeight = commandMenuHeight + helpHeight + approvalHeight + linkHeight;
|
|
347
347
|
const feedRows = Math.max(3, rows - 6 - inputLines.length - menuHeight);
|
|
348
348
|
const hasRunningTool = feed.some((it) => it.kind === "tool" && it.status === "running");
|
|
349
|
-
const { lines: feedLines, toggleAtLine, groupToggleAtLine, linkAtLine } = useFeedLines(feed, innerWidth, reasoningTick, hasRunningTool ? frame : 0, collapsedGroups, focusedGroupKey, itemExpandState, hoveredIdx, config);
|
|
349
|
+
const { lines: feedLines, toggleAtLine, groupToggleAtLine, linkAtLine, lastUserLineStart } = useFeedLines(feed, innerWidth, reasoningTick, hasRunningTool ? frame : 0, collapsedGroups, focusedGroupKey, itemExpandState, hoveredIdx, config);
|
|
350
350
|
const contentRows = Math.max(1, feedRows);
|
|
351
351
|
const maxScrollOffset = Math.max(0, feedLines.length - contentRows);
|
|
352
352
|
wheelStateRef.current = { screen, maxScrollOffset };
|
|
353
|
-
|
|
354
|
-
|
|
353
|
+
// scrollOffset < 0 is a sentinel meaning "pin the most recent user message to
|
|
354
|
+
// the top of the viewport" (with empty space below for the incoming reply).
|
|
355
|
+
// Once the reply grows past the viewport we fall back to bottom-anchoring so
|
|
356
|
+
// the streaming output stays visible. Any manual scroll clears the sentinel.
|
|
357
|
+
const pinUserToTop = scrollOffset < 0 && lastUserLineStart >= 0;
|
|
358
|
+
let startIdx;
|
|
359
|
+
if (pinUserToTop) {
|
|
360
|
+
const fitsBelow = feedLines.length - lastUserLineStart <= contentRows;
|
|
361
|
+
startIdx = fitsBelow ? lastUserLineStart : Math.max(0, feedLines.length - contentRows);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
const off = Math.min(Math.max(0, scrollOffset), maxScrollOffset);
|
|
365
|
+
startIdx = Math.max(0, feedLines.length - contentRows - off);
|
|
366
|
+
}
|
|
367
|
+
const clampedOffset = Math.max(0, feedLines.length - contentRows - startIdx);
|
|
355
368
|
const hasLinkHover = hoveredIdx !== null && (linkAtLine.get(hoveredIdx)?.length ?? 0) > 0;
|
|
356
369
|
const feedStartRow = 3;
|
|
357
370
|
if (screen === "chat") {
|
|
@@ -384,7 +397,31 @@ export function SciraApp({ runPath: initialRunPath, config: initialConfig }) {
|
|
|
384
397
|
clickMapRef.current = clickMap;
|
|
385
398
|
hoverMapRef.current = hoverMap;
|
|
386
399
|
}
|
|
387
|
-
const
|
|
400
|
+
const slicedLines = feedLines.slice(startIdx, startIdx + contentRows);
|
|
401
|
+
// The feed box is bottom-aligned (justifyContent="flex-end"). When pinning the
|
|
402
|
+
// user message to the top, pad blank lines below it so short content is pushed
|
|
403
|
+
// up — the user message sits at the top with empty room below for the reply.
|
|
404
|
+
const blankLine = (k) => _jsx(Text, { children: " " }, k);
|
|
405
|
+
// Show an animated loading line whenever the agent is still doing non-text
|
|
406
|
+
// work (reasoning, tool calls, status) — i.e. the latest feed item isn't the
|
|
407
|
+
// streamed answer text. It stays pinned below the latest content for the whole
|
|
408
|
+
// turn, and the phrase rotates slowly so it doesn't feel frozen. The feed box
|
|
409
|
+
// is bottom-aligned with overflow hidden, so when the timeline is long the
|
|
410
|
+
// loader stays visible and the oldest lines scroll off the top instead.
|
|
411
|
+
const lastItem = feed[feed.length - 1];
|
|
412
|
+
const showLoader = busy && lastItem !== undefined && lastItem.kind !== "text";
|
|
413
|
+
const phrase = LOADING_PHRASES[Math.floor(frame / 24) % LOADING_PHRASES.length];
|
|
414
|
+
const loadingLine = (_jsx(Text, { dimColor: true, children: `${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ${phrase}` }, "loading"));
|
|
415
|
+
const contentWithLoader = showLoader
|
|
416
|
+
? (slicedLines.length > 0 ? [...slicedLines, blankLine("loading-gap"), loadingLine] : [loadingLine])
|
|
417
|
+
: slicedLines;
|
|
418
|
+
const visibleLines = pinUserToTop && contentWithLoader.length < contentRows
|
|
419
|
+
? [
|
|
420
|
+
blankLine("pad-top"),
|
|
421
|
+
...contentWithLoader,
|
|
422
|
+
...Array.from({ length: Math.max(0, contentRows - contentWithLoader.length - 1) }, (_, i) => blankLine(`pad-${i}`)),
|
|
423
|
+
]
|
|
424
|
+
: contentWithLoader;
|
|
388
425
|
const scrollLabel = clampedOffset > 0
|
|
389
426
|
? (startIdx > 0 ? `↑ ${startIdx} · ↓ ${clampedOffset} · wheel/⇞⇟` : `top · ↓ ${clampedOffset} · wheel/⇞⇟`)
|
|
390
427
|
: "";
|
package/dist/ui/ink/constants.js
CHANGED
|
@@ -51,6 +51,33 @@ export const TOOL_ICONS = {
|
|
|
51
51
|
grepWorkspace: "⌕"
|
|
52
52
|
};
|
|
53
53
|
export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
54
|
+
export const LOADING_PHRASES = [
|
|
55
|
+
"Thinking it through…",
|
|
56
|
+
"Digging into it…",
|
|
57
|
+
"Connecting the dots…",
|
|
58
|
+
"Gathering context…",
|
|
59
|
+
"Working through it…",
|
|
60
|
+
"Sifting the details…",
|
|
61
|
+
"Putting it together…",
|
|
62
|
+
"Chasing down answers…",
|
|
63
|
+
"Mulling it over…",
|
|
64
|
+
"Lining things up…",
|
|
65
|
+
"Reading the room…",
|
|
66
|
+
"Scanning the sources…",
|
|
67
|
+
"Cross-checking facts…",
|
|
68
|
+
"Tracing the threads…",
|
|
69
|
+
"Weighing the options…",
|
|
70
|
+
"Following the trail…",
|
|
71
|
+
"Piecing it together…",
|
|
72
|
+
"Untangling the details…",
|
|
73
|
+
"Skimming the fine print…",
|
|
74
|
+
"Joining the dots…",
|
|
75
|
+
"Hunting for specifics…",
|
|
76
|
+
"Sorting signal from noise…",
|
|
77
|
+
"Drafting the answer…",
|
|
78
|
+
"Double-checking the work…",
|
|
79
|
+
"Wrapping my head around it…",
|
|
80
|
+
];
|
|
54
81
|
export const HOME_TIPS = [
|
|
55
82
|
"Type a question and press ⏎ to start a new research run.",
|
|
56
83
|
"Say \"deep research …\" or \"compare …\" to trigger the full research harness.",
|
|
@@ -26,7 +26,9 @@ export function useAgentTurn({ config, currentRunPath, queuedPromptRef, fullMode
|
|
|
26
26
|
const controller = new AbortController();
|
|
27
27
|
session.abort = controller;
|
|
28
28
|
setBusy(true);
|
|
29
|
-
|
|
29
|
+
// Pin the just-sent user message to the top of the viewport, leaving room
|
|
30
|
+
// below for the incoming assistant reply (-1 sentinel; see SciraApp).
|
|
31
|
+
setScrollOffset(-1);
|
|
30
32
|
sessionSetBusy(runPath, true);
|
|
31
33
|
const modelId = config.model;
|
|
32
34
|
const turnStartedAt = Date.now();
|
|
@@ -63,6 +63,7 @@ reasoningTick, spinnerFrame, collapsedGroups, focusedGroupKey, itemExpandState,
|
|
|
63
63
|
const toggleAtLine = new Map();
|
|
64
64
|
const groupToggleAtLine = new Map();
|
|
65
65
|
const linkAtLine = new Map();
|
|
66
|
+
let lastUserLineStart = -1;
|
|
66
67
|
let key = 0;
|
|
67
68
|
const { groupOf, groups } = computeGroups(feed);
|
|
68
69
|
const eff = [];
|
|
@@ -188,6 +189,7 @@ reasoningTick, spinnerFrame, collapsedGroups, focusedGroupKey, itemExpandState,
|
|
|
188
189
|
const rightPad = time ? time.length + 1 : 0;
|
|
189
190
|
const wrapped = wrapText(fi.text, Math.max(10, bandW - 4 - rightPad));
|
|
190
191
|
const blank = " ".repeat(bandW);
|
|
192
|
+
lastUserLineStart = lines.length;
|
|
191
193
|
lines.push(_jsx(Text, { ...bandBg, children: blank }, key++));
|
|
192
194
|
wrapped.forEach((l, idx) => {
|
|
193
195
|
const isFirst = idx === 0;
|
|
@@ -233,7 +235,7 @@ reasoningTick, spinnerFrame, collapsedGroups, focusedGroupKey, itemExpandState,
|
|
|
233
235
|
}
|
|
234
236
|
}
|
|
235
237
|
});
|
|
236
|
-
return { lines, toggleAtLine, groupToggleAtLine, linkAtLine };
|
|
238
|
+
return { lines, toggleAtLine, groupToggleAtLine, linkAtLine, lastUserLineStart };
|
|
237
239
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
238
240
|
}, [feed, innerWidth, reasoningTick, spinnerFrame, collapsedGroups, focusedGroupKey, itemExpandState, hoveredLineIdx, config, theme]);
|
|
239
241
|
}
|
|
@@ -20,6 +20,10 @@ export function isCollapsibleToolName(name) {
|
|
|
20
20
|
return name.length > 0;
|
|
21
21
|
}
|
|
22
22
|
export function defaultCollapsedToolName(name) {
|
|
23
|
+
// Chrome DevTools MCP tools (prefixed `devtools_`) produce long browser
|
|
24
|
+
// snapshots/output, so collapse them by default like the built-in tools.
|
|
25
|
+
if (name.startsWith("devtools_"))
|
|
26
|
+
return true;
|
|
23
27
|
return DEFAULT_COLLAPSED_TOOLS.has(name);
|
|
24
28
|
}
|
|
25
29
|
export function isToolItemCollapsed(id, name, status, expandState) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scira/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Scira — terminal-native AI research agent with grounded sources, verified claims, and local run storage.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"test:watch": "vitest"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@ai-sdk/mcp": "^1.0.
|
|
43
|
-
"@ai-sdk/openai-compatible": "^2.0.
|
|
44
|
-
"@ai-sdk/xai": "^3.0.
|
|
42
|
+
"@ai-sdk/mcp": "^1.0.49",
|
|
43
|
+
"@ai-sdk/openai-compatible": "^2.0.50",
|
|
44
|
+
"@ai-sdk/xai": "^3.0.95",
|
|
45
45
|
"@clack/prompts": "^1.5.1",
|
|
46
46
|
"@mendable/firecrawl-js": "^4.25.3",
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
48
48
|
"@mozilla/readability": "^0.6.0",
|
|
49
|
-
"ai": "^6.0.
|
|
49
|
+
"ai": "^6.0.203",
|
|
50
50
|
"diff": "^9.0.0",
|
|
51
51
|
"exa-js": "^2.13.0",
|
|
52
52
|
"files-sdk": "^1.8.0",
|