@scira/cli 0.1.2 → 0.1.4
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 +56 -10
- package/dist/agent/background-tasks.js +173 -0
- package/dist/agent/research-agent.js +95 -38
- package/dist/agent/todos.js +140 -0
- package/dist/agent/tools.js +146 -143
- package/dist/agent/tools.test.js +33 -0
- package/dist/agent/workspace.js +85 -0
- package/dist/cli/commands/init.js +53 -39
- package/dist/cli/index.js +30 -14
- package/dist/config/env-guide.js +151 -0
- package/dist/config/env-guide.test.js +18 -0
- package/dist/config/env-store.js +53 -0
- package/dist/config/env-store.test.js +60 -0
- package/dist/tools/agent-tools.js +621 -0
- package/dist/tools/background-tasks.js +261 -0
- package/dist/tools/bash-policy.test.js +38 -0
- package/dist/tools/file-tools.js +6 -1
- package/dist/tools/search-web.js +24 -6
- package/dist/tools/search-web.test.js +24 -0
- package/dist/tools/todos.js +140 -0
- package/dist/tools/workspace.js +91 -0
- package/dist/tools/workspace.test.js +75 -0
- package/dist/tools/x-search.js +142 -0
- package/dist/types/index.js +1 -0
- package/dist/types/schema.test.js +1 -0
- package/dist/ui/ink/SciraApp.js +74 -21
- package/dist/ui/ink/components/overlays.js +15 -9
- package/dist/ui/ink/constants.js +13 -4
- package/dist/ui/ink/hooks/use-agent-turn.js +26 -7
- package/dist/ui/ink/hooks/use-feed-lines.js +33 -6
- package/dist/ui/ink/hooks/use-keyboard.js +16 -1
- package/dist/ui/ink/hooks/use-session.js +15 -14
- package/dist/ui/ink/hooks/use-settings.js +30 -8
- package/dist/ui/ink/hooks/use-submit.js +14 -3
- package/dist/ui/ink/hooks/use-theme.js +1 -1
- package/dist/ui/ink/lib/tool-result.js +73 -5
- package/dist/ui/ink/lib/tool-result.test.js +3 -3
- package/dist/ui/ink/lib/utils.js +104 -5
- package/dist/ui/ink/lib/utils.test.js +18 -1
- package/dist/ui/ink/theme-context.js +29 -26
- package/dist/ui/ink/theme.js +36 -9
- package/dist/ui/ink/theme.test.js +32 -5
- package/package.json +6 -2
package/dist/ui/ink/lib/utils.js
CHANGED
|
@@ -142,11 +142,73 @@ export function relativeTime(ms) {
|
|
|
142
142
|
return `${weeks}w ago`;
|
|
143
143
|
return new Date(ms).toLocaleDateString();
|
|
144
144
|
}
|
|
145
|
-
|
|
145
|
+
function colorToAnsi(color) {
|
|
146
|
+
if (!color)
|
|
147
|
+
return [];
|
|
148
|
+
const hex = /^#([0-9a-f]{6})$/i.exec(color);
|
|
149
|
+
if (hex) {
|
|
150
|
+
const n = hex[1];
|
|
151
|
+
return [38, 2, parseInt(n.slice(0, 2), 16), parseInt(n.slice(2, 4), 16), parseInt(n.slice(4, 6), 16)];
|
|
152
|
+
}
|
|
153
|
+
const ansi256 = /^ansi256\((\d+)\)$/i.exec(color);
|
|
154
|
+
if (ansi256)
|
|
155
|
+
return [38, 5, parseInt(ansi256[1], 10)];
|
|
156
|
+
const named = {
|
|
157
|
+
red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36,
|
|
158
|
+
gray: 90, white: 97, black: 30,
|
|
159
|
+
};
|
|
160
|
+
const code = named[color.toLowerCase()];
|
|
161
|
+
return code ? [code] : [];
|
|
162
|
+
}
|
|
163
|
+
/** OSC 8 link with inline ANSI styling — avoids Ink Text props breaking the escape sequence. */
|
|
164
|
+
export function ansiHyperlink(text, url, style) {
|
|
165
|
+
const params = [];
|
|
166
|
+
if (style?.bold)
|
|
167
|
+
params.push(1);
|
|
168
|
+
if (style?.dim)
|
|
169
|
+
params.push(2);
|
|
170
|
+
if (style?.italic)
|
|
171
|
+
params.push(3);
|
|
172
|
+
if (style?.underline !== false)
|
|
173
|
+
params.push(4);
|
|
174
|
+
params.push(...colorToAnsi(style?.color));
|
|
175
|
+
const styled = params.length > 0 ? `\x1b[${params.join(";")}m${text}\x1b[0m` : text;
|
|
176
|
+
return `\x1b]8;;${url}\x1b\\${styled}\x1b]8;;\x1b\\`;
|
|
177
|
+
}
|
|
146
178
|
export function hyperlink(text, url) {
|
|
147
179
|
if (!url)
|
|
148
180
|
return text;
|
|
149
|
-
return
|
|
181
|
+
return ansiHyperlink(text, url, { underline: true });
|
|
182
|
+
}
|
|
183
|
+
export function computeLineLinks(segs, prefixCols = 0) {
|
|
184
|
+
const links = [];
|
|
185
|
+
let col = prefixCols;
|
|
186
|
+
for (const s of segs) {
|
|
187
|
+
const w = displayWidth(s.text);
|
|
188
|
+
if (s.url && w > 0)
|
|
189
|
+
links.push({ start: col, end: col + w - 1, url: s.url });
|
|
190
|
+
col += w;
|
|
191
|
+
}
|
|
192
|
+
return links;
|
|
193
|
+
}
|
|
194
|
+
/** Match an SGR mouse column (1-based) against link regions from computeLineLinks. */
|
|
195
|
+
export function linkAtMouseColumn(links, x) {
|
|
196
|
+
for (const l of links) {
|
|
197
|
+
if (x >= l.start + 1 && x <= l.end + 1)
|
|
198
|
+
return l.url;
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
/** Open a URL in the system browser. */
|
|
203
|
+
export function openExternalUrl(url) {
|
|
204
|
+
return new Promise((res) => {
|
|
205
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
206
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
207
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
208
|
+
child.on("error", () => res(false));
|
|
209
|
+
child.on("close", (code) => res(code === 0));
|
|
210
|
+
child.unref();
|
|
211
|
+
});
|
|
150
212
|
}
|
|
151
213
|
/** True if the prompt clearly asks for full, report-grade research. */
|
|
152
214
|
export function wantsFullResearch(prompt) {
|
|
@@ -219,9 +281,15 @@ function toolOutputText(output) {
|
|
|
219
281
|
}
|
|
220
282
|
export function summarizeToolInput(name, input) {
|
|
221
283
|
const obj = (input ?? {});
|
|
222
|
-
if (name === "bash" || name === "runWorkspaceCommand")
|
|
284
|
+
if (name === "bash" || name === "runBash" || name === "runWorkspaceCommand") {
|
|
285
|
+
const action = obj.action;
|
|
286
|
+
if (action && action !== "run")
|
|
287
|
+
return `${action}${obj.taskId ? ` ${obj.taskId}` : ""}`;
|
|
223
288
|
return String(obj.command ?? "");
|
|
224
|
-
|
|
289
|
+
}
|
|
290
|
+
if (name === "todo")
|
|
291
|
+
return `${String(obj.action ?? "list")}${obj.id ? ` ${obj.id}` : ""}`;
|
|
292
|
+
if (name === "webSearch" || name === "xSearch") {
|
|
225
293
|
const queries = Array.isArray(obj.queries) ? obj.queries : [];
|
|
226
294
|
return queries.length > 0 ? queries.slice(0, 2).join(" · ") + (queries.length > 2 ? ` +${queries.length - 2}` : "") : String(obj.query ?? "");
|
|
227
295
|
}
|
|
@@ -260,6 +328,25 @@ export function summarizeToolOutput(name, output) {
|
|
|
260
328
|
}
|
|
261
329
|
catch { /* fall through */ }
|
|
262
330
|
}
|
|
331
|
+
if (name === "xSearch") {
|
|
332
|
+
try {
|
|
333
|
+
const parsed = JSON.parse(text);
|
|
334
|
+
if (Array.isArray(parsed)) {
|
|
335
|
+
const posts = parsed.flatMap((s) => s.posts ?? []);
|
|
336
|
+
const total = posts.length;
|
|
337
|
+
if (total === 0)
|
|
338
|
+
return "no posts";
|
|
339
|
+
const handles = posts
|
|
340
|
+
.map((p) => p.handle)
|
|
341
|
+
.filter((h) => Boolean(h))
|
|
342
|
+
.slice(0, 3)
|
|
343
|
+
.map((h) => `@${h}`);
|
|
344
|
+
const head = `${total} post${total === 1 ? "" : "s"}`;
|
|
345
|
+
return handles.length > 0 ? `${head} · ${handles.join(", ")}` : head;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch { /* fall through */ }
|
|
349
|
+
}
|
|
263
350
|
if (name === "readUrl") {
|
|
264
351
|
const titleMatch = text.match(/^#\s+(.+)/m);
|
|
265
352
|
if (titleMatch?.[1])
|
|
@@ -284,12 +371,24 @@ export function summarizeToolOutput(name, output) {
|
|
|
284
371
|
if (name === "writeFile" || name === "writeWorkspaceFile" || name === "editFile" || name === "editWorkspaceFile") {
|
|
285
372
|
return oneLine(text, 120) || "ok";
|
|
286
373
|
}
|
|
287
|
-
if (name === "bash" || name === "runWorkspaceCommand") {
|
|
374
|
+
if (name === "bash" || name === "runBash" || name === "runWorkspaceCommand") {
|
|
375
|
+
if (text.startsWith("Started background task"))
|
|
376
|
+
return oneLine(text, 120);
|
|
377
|
+
if (text.startsWith("No background tasks") || text.includes("[running]") || text.includes("[exited]")) {
|
|
378
|
+
return oneLine(text.split("\n")[0] ?? text, 120);
|
|
379
|
+
}
|
|
288
380
|
const lines = text.split("\n").filter((l) => l.trim());
|
|
289
381
|
if (lines.length === 0)
|
|
290
382
|
return "done";
|
|
291
383
|
return lines.slice(-3).map((l) => oneLine(l, 80)).join(" · ").slice(0, 200);
|
|
292
384
|
}
|
|
385
|
+
if (name === "todo") {
|
|
386
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
387
|
+
if (lines.length === 0)
|
|
388
|
+
return "no todos";
|
|
389
|
+
const active = lines.filter((l) => l.includes("[ ]") || l.includes("[~]")).length;
|
|
390
|
+
return `${lines.length} todo${lines.length === 1 ? "" : "s"}${active > 0 ? ` · ${active} open` : ""}`;
|
|
391
|
+
}
|
|
293
392
|
if (name === "listWorkspaceDir") {
|
|
294
393
|
const lines = text.split("\n").filter((l) => l.trim());
|
|
295
394
|
const preview = lines.slice(0, 3).map((l) => oneLine(l, 40)).join(", ");
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { summarizeToolInput, summarizeToolOutput } from "./utils.js";
|
|
2
|
+
import { summarizeToolInput, summarizeToolOutput, ansiHyperlink, computeLineLinks, linkAtMouseColumn } from "./utils.js";
|
|
3
|
+
describe("hyperlink helpers", () => {
|
|
4
|
+
it("wraps OSC 8 around styled link text", () => {
|
|
5
|
+
const out = ansiHyperlink("docs", "https://example.com", { color: "#FFE0C2", underline: true });
|
|
6
|
+
expect(out).toContain("\x1b]8;;https://example.com\x1b\\");
|
|
7
|
+
expect(out).toContain("docs");
|
|
8
|
+
expect(out).toContain("\x1b]8;;\x1b\\");
|
|
9
|
+
});
|
|
10
|
+
it("maps mouse column to link url", () => {
|
|
11
|
+
const links = computeLineLinks([
|
|
12
|
+
{ text: "see " },
|
|
13
|
+
{ text: "docs", url: "https://example.com" },
|
|
14
|
+
], 2);
|
|
15
|
+
expect(links).toEqual([{ start: 6, end: 9, url: "https://example.com" }]);
|
|
16
|
+
expect(linkAtMouseColumn(links, 7)).toBe("https://example.com");
|
|
17
|
+
expect(linkAtMouseColumn(links, 3)).toBeUndefined();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
3
20
|
describe("summarizeToolInput", () => {
|
|
4
21
|
it("formats webSearch queries", () => {
|
|
5
22
|
expect(summarizeToolInput("webSearch", { queries: ["a", "b", "c"] })).toBe("a · b +1");
|
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React, { createContext, useContext, useMemo, useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const ThemeCtx = createContext(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
import { detectTerminalTheme, getThemeFromResolved, resolveRenderingAppearance, watchAutoThemeChanges, } from "./theme.js";
|
|
4
|
+
const initialTerminal = detectTerminalTheme();
|
|
5
|
+
const ThemeCtx = createContext({
|
|
6
|
+
colors: getThemeFromResolved(initialTerminal),
|
|
7
|
+
terminalAppearance: initialTerminal,
|
|
8
|
+
renderingAppearance: initialTerminal,
|
|
9
|
+
});
|
|
10
|
+
export function ThemeProvider({ config, children }) {
|
|
11
|
+
const [terminalAppearance, setTerminalAppearance] = useState(detectTerminalTheme);
|
|
9
12
|
React.useEffect(() => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
return watchAutoThemeChanges(() => {
|
|
14
|
+
const next = detectTerminalTheme();
|
|
15
|
+
setTerminalAppearance((prev) => (prev === next ? prev : next));
|
|
16
|
+
});
|
|
17
|
+
}, []);
|
|
18
|
+
const value = useMemo(() => {
|
|
19
|
+
const renderingAppearance = resolveRenderingAppearance(config.theme, terminalAppearance);
|
|
20
|
+
return {
|
|
21
|
+
colors: getThemeFromResolved(renderingAppearance),
|
|
22
|
+
terminalAppearance,
|
|
23
|
+
renderingAppearance,
|
|
21
24
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const colors = useMemo(() => {
|
|
25
|
-
if (config.theme !== "auto")
|
|
26
|
-
return getTheme(config.theme);
|
|
27
|
-
return getThemeFromResolved(probed ?? autoResolved);
|
|
28
|
-
}, [config.theme, autoResolved, probed]);
|
|
29
|
-
return _jsx(ThemeCtx.Provider, { value: colors, children: children });
|
|
25
|
+
}, [config.theme, terminalAppearance]);
|
|
26
|
+
return _jsx(ThemeCtx.Provider, { value: value, children: children });
|
|
30
27
|
}
|
|
31
28
|
export function useTheme() {
|
|
32
|
-
return useContext(ThemeCtx);
|
|
29
|
+
return useContext(ThemeCtx).colors;
|
|
30
|
+
}
|
|
31
|
+
export function useTerminalAppearance() {
|
|
32
|
+
return useContext(ThemeCtx).terminalAppearance;
|
|
33
|
+
}
|
|
34
|
+
export function useRenderingAppearance() {
|
|
35
|
+
return useContext(ThemeCtx).renderingAppearance;
|
|
33
36
|
}
|
package/dist/ui/ink/theme.js
CHANGED
|
@@ -7,10 +7,10 @@ export const DARK_THEME = {
|
|
|
7
7
|
accentDim: "#CFB59D",
|
|
8
8
|
background: "",
|
|
9
9
|
border: "#FFE0C2",
|
|
10
|
-
text: "
|
|
10
|
+
text: "ansi256(15)",
|
|
11
11
|
textDim: "ansi256(245)",
|
|
12
|
-
textInverse: "
|
|
13
|
-
inputText: "
|
|
12
|
+
textInverse: "ansi256(0)",
|
|
13
|
+
inputText: "ansi256(15)",
|
|
14
14
|
cursorBackground: "#FFE0C2",
|
|
15
15
|
cursorForeground: "#000000",
|
|
16
16
|
success: "green",
|
|
@@ -24,10 +24,10 @@ export const LIGHT_THEME = {
|
|
|
24
24
|
accentDim: "#CFB59D",
|
|
25
25
|
background: "",
|
|
26
26
|
border: "#FFE0C2",
|
|
27
|
-
text: "
|
|
28
|
-
textDim: "
|
|
29
|
-
textInverse: "
|
|
30
|
-
inputText: "
|
|
27
|
+
text: "ansi256(0)",
|
|
28
|
+
textDim: "ansi256(242)",
|
|
29
|
+
textInverse: "ansi256(15)",
|
|
30
|
+
inputText: "ansi256(0)",
|
|
31
31
|
cursorBackground: "#CFB59D",
|
|
32
32
|
cursorForeground: "#000000",
|
|
33
33
|
success: "green",
|
|
@@ -62,7 +62,7 @@ function readColorFgbg() {
|
|
|
62
62
|
return undefined;
|
|
63
63
|
}
|
|
64
64
|
function readTerminalProfile() {
|
|
65
|
-
const profile = process.env.TERM_PROFILE || process.env.ITERM_PROFILE || "";
|
|
65
|
+
const profile = process.env.TERM_PROFILE || process.env.ITERM_PROFILE || process.env.WARP_BOOTSTRAPPED || "";
|
|
66
66
|
if (!profile)
|
|
67
67
|
return undefined;
|
|
68
68
|
if (/light|day|solar/i.test(profile))
|
|
@@ -71,6 +71,17 @@ function readTerminalProfile() {
|
|
|
71
71
|
return "dark";
|
|
72
72
|
return undefined;
|
|
73
73
|
}
|
|
74
|
+
/** Common standalone terminals that default to dark profiles when unset. */
|
|
75
|
+
function readTermProgram() {
|
|
76
|
+
const program = (process.env.TERM_PROGRAM ?? "").toLowerCase();
|
|
77
|
+
if (!program)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (/warp|ghostty|alacritty|kitty|hyper|wezterm|tabby/.test(program))
|
|
80
|
+
return "dark";
|
|
81
|
+
if (program === "apple_terminal")
|
|
82
|
+
return "dark";
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
74
85
|
function editorSettingsPaths() {
|
|
75
86
|
const home = homedir();
|
|
76
87
|
if (process.platform === "darwin") {
|
|
@@ -146,9 +157,25 @@ function readSystemAppearance() {
|
|
|
146
157
|
export function detectTerminalTheme() {
|
|
147
158
|
return readColorFgbg()
|
|
148
159
|
?? readTerminalProfile()
|
|
160
|
+
?? readTermProgram()
|
|
149
161
|
?? readEditorColorTheme()
|
|
150
162
|
?? readSystemAppearance()
|
|
151
|
-
?? "
|
|
163
|
+
?? "dark";
|
|
164
|
+
}
|
|
165
|
+
/** Input foreground matched to terminal appearance. */
|
|
166
|
+
export function inputForegroundForAppearance(appearance) {
|
|
167
|
+
return appearance === "dark" ? "ansi256(15)" : "ansi256(0)";
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Pick colors for rendering. When a locked theme disagrees with the terminal
|
|
171
|
+
* background, follow the terminal so text stays readable.
|
|
172
|
+
*/
|
|
173
|
+
export function resolveRenderingAppearance(configTheme, terminalAppearance) {
|
|
174
|
+
if (configTheme === "auto")
|
|
175
|
+
return terminalAppearance;
|
|
176
|
+
if (configTheme !== terminalAppearance)
|
|
177
|
+
return terminalAppearance;
|
|
178
|
+
return configTheme;
|
|
152
179
|
}
|
|
153
180
|
export function getThemeFromResolved(resolved) {
|
|
154
181
|
return resolved === "light" ? LIGHT_THEME : DARK_THEME;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { detectTerminalTheme, getTheme, watchAutoThemeChanges } from "./theme.js";
|
|
2
|
+
import { detectTerminalTheme, getTheme, inputForegroundForAppearance, resolveRenderingAppearance, watchAutoThemeChanges, } from "./theme.js";
|
|
3
3
|
const env = process.env;
|
|
4
4
|
afterEach(() => {
|
|
5
5
|
process.env = { ...env };
|
|
@@ -18,14 +18,41 @@ describe("detectTerminalTheme", () => {
|
|
|
18
18
|
});
|
|
19
19
|
it("resolves auto theme colors from detected appearance", () => {
|
|
20
20
|
process.env.COLORFGBG = "0;15";
|
|
21
|
-
expect(getTheme("auto").text).toBe("
|
|
22
|
-
expect(getTheme("auto").inputText).toBe("
|
|
21
|
+
expect(getTheme("auto").text).toBe("ansi256(0)");
|
|
22
|
+
expect(getTheme("auto").inputText).toBe("ansi256(0)");
|
|
23
23
|
expect(getTheme("auto").userBandBackground).toBe("#f0f0f0");
|
|
24
24
|
process.env.COLORFGBG = "15;0";
|
|
25
|
-
expect(getTheme("auto").text).toBe("
|
|
26
|
-
expect(getTheme("auto").inputText).toBe("
|
|
25
|
+
expect(getTheme("auto").text).toBe("ansi256(15)");
|
|
26
|
+
expect(getTheme("auto").inputText).toBe("ansi256(15)");
|
|
27
27
|
expect(getTheme("auto").userBandBackground).toBe("ansi256(238)");
|
|
28
28
|
});
|
|
29
|
+
it("detects Warp and Apple Terminal as dark when unset", () => {
|
|
30
|
+
delete process.env.COLORFGBG;
|
|
31
|
+
delete process.env.TERM_PROFILE;
|
|
32
|
+
delete process.env.ITERM_PROFILE;
|
|
33
|
+
process.env.TERM_PROGRAM = "WarpTerminal";
|
|
34
|
+
expect(detectTerminalTheme()).toBe("dark");
|
|
35
|
+
process.env.TERM_PROGRAM = "Apple_Terminal";
|
|
36
|
+
expect(detectTerminalTheme()).toBe("dark");
|
|
37
|
+
});
|
|
38
|
+
it("defaults to dark when no signals are present", () => {
|
|
39
|
+
delete process.env.COLORFGBG;
|
|
40
|
+
delete process.env.TERM_PROFILE;
|
|
41
|
+
delete process.env.ITERM_PROFILE;
|
|
42
|
+
delete process.env.TERM_PROGRAM;
|
|
43
|
+
delete process.env.TERM_PROGRAM;
|
|
44
|
+
expect(detectTerminalTheme()).toBe("dark");
|
|
45
|
+
});
|
|
46
|
+
it("maps terminal appearance to ansi256 input foreground", () => {
|
|
47
|
+
expect(inputForegroundForAppearance("dark")).toBe("ansi256(15)");
|
|
48
|
+
expect(inputForegroundForAppearance("light")).toBe("ansi256(0)");
|
|
49
|
+
});
|
|
50
|
+
it("overrides a mismatched locked theme to match the terminal", () => {
|
|
51
|
+
expect(resolveRenderingAppearance("light", "dark")).toBe("dark");
|
|
52
|
+
expect(resolveRenderingAppearance("dark", "light")).toBe("light");
|
|
53
|
+
expect(resolveRenderingAppearance("dark", "dark")).toBe("dark");
|
|
54
|
+
expect(resolveRenderingAppearance("auto", "dark")).toBe("dark");
|
|
55
|
+
});
|
|
29
56
|
it("watchAutoThemeChanges fires immediately and on interval", () => {
|
|
30
57
|
vi.useFakeTimers();
|
|
31
58
|
const onChange = vi.fn();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scira/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
34
34
|
"prepublishOnly": "bun run build",
|
|
35
35
|
"dev": "bun src/cli/index.ts",
|
|
36
|
+
"docs:dev": "bun run --cwd docs dev",
|
|
37
|
+
"docs:build": "NODE_ENV=production bun run --cwd docs build",
|
|
36
38
|
"test": "vitest run",
|
|
37
39
|
"test:watch": "vitest"
|
|
38
40
|
},
|
|
@@ -49,16 +51,18 @@
|
|
|
49
51
|
"exa-js": "^2.13.0",
|
|
50
52
|
"files-sdk": "^1.8.0",
|
|
51
53
|
"ink": "^7.0.5",
|
|
54
|
+
"ink-link": "^5.0.0",
|
|
52
55
|
"jsdom": "^29.1.1",
|
|
53
56
|
"parallel-web": "^1.1.0",
|
|
54
57
|
"picospinner": "^3.0.0",
|
|
55
58
|
"react": "^19.2.7",
|
|
59
|
+
"react-tweet": "^3.3.1",
|
|
56
60
|
"sade": "^1.8.1",
|
|
57
61
|
"workers-ai-provider": "^3.1.14",
|
|
58
62
|
"zod": "^4.4.3"
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|
|
61
|
-
"bun-types": "^1.
|
|
65
|
+
"bun-types": "^1.3.14",
|
|
62
66
|
"@types/jsdom": "^28.0.3",
|
|
63
67
|
"@types/node": "^25.9.3",
|
|
64
68
|
"@types/react": "^19.2.17",
|