@scira/cli 0.1.1 → 0.1.3
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 +54 -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 +51 -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/ui/ink/SciraApp.js +11 -8
- package/dist/ui/ink/components/overlays.js +4 -4
- package/dist/ui/ink/constants.js +11 -3
- package/dist/ui/ink/hooks/use-agent-turn.js +24 -5
- package/dist/ui/ink/hooks/use-keyboard.js +3 -0
- package/dist/ui/ink/hooks/use-session.js +5 -3
- package/dist/ui/ink/hooks/use-settings.js +10 -8
- package/dist/ui/ink/hooks/use-submit.js +13 -2
- package/dist/ui/ink/hooks/use-theme.js +1 -1
- package/dist/ui/ink/lib/tool-result.js +72 -5
- package/dist/ui/ink/lib/utils.js +40 -3
- 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 +9 -6
|
@@ -8,7 +8,9 @@ export const DEFAULT_COLLAPSED_TOOLS = new Set([
|
|
|
8
8
|
"readSkill",
|
|
9
9
|
"bash",
|
|
10
10
|
"runWorkspaceCommand",
|
|
11
|
+
"todo",
|
|
11
12
|
"grepWorkspace",
|
|
13
|
+
"xSearch",
|
|
12
14
|
]);
|
|
13
15
|
export function feedToolItemId(feedIndex, toolCallId) {
|
|
14
16
|
return toolCallId ?? `feed-${feedIndex}`;
|
|
@@ -80,9 +82,15 @@ function searchHitToMarkdown(hit) {
|
|
|
80
82
|
}
|
|
81
83
|
function webSearchQueriesMarkdown(groups) {
|
|
82
84
|
const queries = groups.map((g) => g.query?.trim()).filter((q) => Boolean(q));
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
const errors = groups.map((g) => g.error?.trim()).filter((e) => Boolean(e));
|
|
86
|
+
const parts = [];
|
|
87
|
+
if (queries.length > 0) {
|
|
88
|
+
parts.push(`## Queries\n\n${queries.map((q, i) => `${i + 1}. ${q}`).join("\n")}`);
|
|
89
|
+
}
|
|
90
|
+
if (errors.length > 0) {
|
|
91
|
+
parts.push(`## Errors\n\n${errors.map((e, i) => `${i + 1}. ${e}`).join("\n")}`);
|
|
92
|
+
}
|
|
93
|
+
return parts.join("\n\n");
|
|
86
94
|
}
|
|
87
95
|
function webSearchSourcesMarkdown(hits) {
|
|
88
96
|
if (hits.length === 0)
|
|
@@ -199,10 +207,57 @@ function formatGrep(result, width, theme) {
|
|
|
199
207
|
return plainLines(row, width, { color: theme.textDim });
|
|
200
208
|
});
|
|
201
209
|
}
|
|
210
|
+
function xPostToMarkdown(p) {
|
|
211
|
+
const label = p.handle ? `@${p.handle}` : p.url;
|
|
212
|
+
let line = `- [${label}](${p.url})`;
|
|
213
|
+
if (p.text) {
|
|
214
|
+
const snippet = p.text.replace(/\s+/gu, " ").trim();
|
|
215
|
+
if (snippet)
|
|
216
|
+
line += `\n *${snippet}*`;
|
|
217
|
+
}
|
|
218
|
+
return line;
|
|
219
|
+
}
|
|
220
|
+
function xSearchPostsMarkdown(groups) {
|
|
221
|
+
const queries = groups.map((g) => g.query?.trim()).filter((q) => Boolean(q));
|
|
222
|
+
const errors = groups.map((g) => g.error?.trim()).filter((e) => Boolean(e));
|
|
223
|
+
const allPosts = groups.flatMap((g) => g.posts ?? []);
|
|
224
|
+
const dateRange = groups[0]?.dateRange;
|
|
225
|
+
const parts = [];
|
|
226
|
+
if (queries.length > 0) {
|
|
227
|
+
parts.push(`## Queries\n\n${queries.map((q, i) => `${i + 1}. ${q}`).join("\n")}`);
|
|
228
|
+
}
|
|
229
|
+
if (dateRange) {
|
|
230
|
+
parts.push(`*${dateRange}*`);
|
|
231
|
+
}
|
|
232
|
+
if (errors.length > 0) {
|
|
233
|
+
parts.push(`## Errors\n\n${errors.map((e, i) => `${i + 1}. ${e}`).join("\n")}`);
|
|
234
|
+
}
|
|
235
|
+
if (allPosts.length > 0) {
|
|
236
|
+
const postLines = allPosts.map(xPostToMarkdown).join("\n\n");
|
|
237
|
+
parts.push(`## Posts (${allPosts.length})\n\n${postLines}`);
|
|
238
|
+
}
|
|
239
|
+
return parts.join("\n\n");
|
|
240
|
+
}
|
|
241
|
+
function formatXSearch(result, width, theme) {
|
|
242
|
+
try {
|
|
243
|
+
const groups = JSON.parse(result);
|
|
244
|
+
if (!Array.isArray(groups))
|
|
245
|
+
return plainLines(result, width, { color: theme.textDim });
|
|
246
|
+
const md = xSearchPostsMarkdown(groups);
|
|
247
|
+
if (!md.trim())
|
|
248
|
+
return plainLines(result, width, { color: theme.textDim });
|
|
249
|
+
return markdownToSegLines(md, width, theme);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return plainLines(result, width, { color: theme.textDim });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
202
255
|
function formatBody(name, result, width, theme) {
|
|
203
256
|
switch (name) {
|
|
204
257
|
case "webSearch":
|
|
205
258
|
return formatWebSearch(result, width, theme);
|
|
259
|
+
case "xSearch":
|
|
260
|
+
return formatXSearch(result, width, theme);
|
|
206
261
|
case "readUrl":
|
|
207
262
|
return formatReadUrl(result, width, theme);
|
|
208
263
|
case "listSkills":
|
|
@@ -262,6 +317,18 @@ export function formatToolResultPreview(name, inputSummary, result, status) {
|
|
|
262
317
|
}
|
|
263
318
|
catch { /* fall through */ }
|
|
264
319
|
}
|
|
320
|
+
if (name === "xSearch") {
|
|
321
|
+
try {
|
|
322
|
+
const groups = JSON.parse(result);
|
|
323
|
+
if (Array.isArray(groups)) {
|
|
324
|
+
const queries = groups.map((g) => g.query?.trim()).filter(Boolean);
|
|
325
|
+
const total = groups.reduce((n, g) => n + (g.posts?.length ?? 0), 0);
|
|
326
|
+
const q = queries.length > 0 ? queries.slice(0, 2).join(" · ") + (queries.length > 2 ? ` +${queries.length - 2}` : "") : input;
|
|
327
|
+
return q ? `${q} · ${total} posts` : `${total} posts`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch { /* fall through */ }
|
|
331
|
+
}
|
|
265
332
|
if (name === "readFile" || name === "readWorkspaceFile") {
|
|
266
333
|
const lines = result.split("\n").length;
|
|
267
334
|
return input ? `${input} · ${lines} lines` : `${lines} lines`;
|
|
@@ -280,12 +347,12 @@ export function formatToolResultLines(name, inputSummary, result, status, conten
|
|
|
280
347
|
const width = Math.max(16, contentWidth);
|
|
281
348
|
const lines = [];
|
|
282
349
|
const input = inputSummary.replace(/\s+/gu, " ").trim();
|
|
283
|
-
const skipInput = name === "webSearch" && status === "done" && Boolean(result?.trim());
|
|
350
|
+
const skipInput = (name === "webSearch" || name === "xSearch") && status === "done" && Boolean(result?.trim());
|
|
284
351
|
if (input && !skipInput) {
|
|
285
352
|
if (name === "bash" || name === "runWorkspaceCommand") {
|
|
286
353
|
lines.push([seg("$ ", { color: theme.accent }), seg(input, { color: theme.text })]);
|
|
287
354
|
}
|
|
288
|
-
else if (name === "webSearch") {
|
|
355
|
+
else if (name === "webSearch" || name === "xSearch") {
|
|
289
356
|
lines.push(...markdownToSegLines(webSearchRunningMarkdown(input), width, theme));
|
|
290
357
|
}
|
|
291
358
|
else if (name === "readUrl") {
|
package/dist/ui/ink/lib/utils.js
CHANGED
|
@@ -219,9 +219,15 @@ function toolOutputText(output) {
|
|
|
219
219
|
}
|
|
220
220
|
export function summarizeToolInput(name, input) {
|
|
221
221
|
const obj = (input ?? {});
|
|
222
|
-
if (name === "bash" || name === "runWorkspaceCommand")
|
|
222
|
+
if (name === "bash" || name === "runBash" || name === "runWorkspaceCommand") {
|
|
223
|
+
const action = obj.action;
|
|
224
|
+
if (action && action !== "run")
|
|
225
|
+
return `${action}${obj.taskId ? ` ${obj.taskId}` : ""}`;
|
|
223
226
|
return String(obj.command ?? "");
|
|
224
|
-
|
|
227
|
+
}
|
|
228
|
+
if (name === "todo")
|
|
229
|
+
return `${String(obj.action ?? "list")}${obj.id ? ` ${obj.id}` : ""}`;
|
|
230
|
+
if (name === "webSearch" || name === "xSearch") {
|
|
225
231
|
const queries = Array.isArray(obj.queries) ? obj.queries : [];
|
|
226
232
|
return queries.length > 0 ? queries.slice(0, 2).join(" · ") + (queries.length > 2 ? ` +${queries.length - 2}` : "") : String(obj.query ?? "");
|
|
227
233
|
}
|
|
@@ -260,6 +266,25 @@ export function summarizeToolOutput(name, output) {
|
|
|
260
266
|
}
|
|
261
267
|
catch { /* fall through */ }
|
|
262
268
|
}
|
|
269
|
+
if (name === "xSearch") {
|
|
270
|
+
try {
|
|
271
|
+
const parsed = JSON.parse(text);
|
|
272
|
+
if (Array.isArray(parsed)) {
|
|
273
|
+
const posts = parsed.flatMap((s) => s.posts ?? []);
|
|
274
|
+
const total = posts.length;
|
|
275
|
+
if (total === 0)
|
|
276
|
+
return "no posts";
|
|
277
|
+
const handles = posts
|
|
278
|
+
.map((p) => p.handle)
|
|
279
|
+
.filter((h) => Boolean(h))
|
|
280
|
+
.slice(0, 3)
|
|
281
|
+
.map((h) => `@${h}`);
|
|
282
|
+
const head = `${total} post${total === 1 ? "" : "s"}`;
|
|
283
|
+
return handles.length > 0 ? `${head} · ${handles.join(", ")}` : head;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch { /* fall through */ }
|
|
287
|
+
}
|
|
263
288
|
if (name === "readUrl") {
|
|
264
289
|
const titleMatch = text.match(/^#\s+(.+)/m);
|
|
265
290
|
if (titleMatch?.[1])
|
|
@@ -284,12 +309,24 @@ export function summarizeToolOutput(name, output) {
|
|
|
284
309
|
if (name === "writeFile" || name === "writeWorkspaceFile" || name === "editFile" || name === "editWorkspaceFile") {
|
|
285
310
|
return oneLine(text, 120) || "ok";
|
|
286
311
|
}
|
|
287
|
-
if (name === "bash" || name === "runWorkspaceCommand") {
|
|
312
|
+
if (name === "bash" || name === "runBash" || name === "runWorkspaceCommand") {
|
|
313
|
+
if (text.startsWith("Started background task"))
|
|
314
|
+
return oneLine(text, 120);
|
|
315
|
+
if (text.startsWith("No background tasks") || text.includes("[running]") || text.includes("[exited]")) {
|
|
316
|
+
return oneLine(text.split("\n")[0] ?? text, 120);
|
|
317
|
+
}
|
|
288
318
|
const lines = text.split("\n").filter((l) => l.trim());
|
|
289
319
|
if (lines.length === 0)
|
|
290
320
|
return "done";
|
|
291
321
|
return lines.slice(-3).map((l) => oneLine(l, 80)).join(" · ").slice(0, 200);
|
|
292
322
|
}
|
|
323
|
+
if (name === "todo") {
|
|
324
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
325
|
+
if (lines.length === 0)
|
|
326
|
+
return "no todos";
|
|
327
|
+
const active = lines.filter((l) => l.includes("[ ]") || l.includes("[~]")).length;
|
|
328
|
+
return `${lines.length} todo${lines.length === 1 ? "" : "s"}${active > 0 ? ` · ${active} open` : ""}`;
|
|
329
|
+
}
|
|
293
330
|
if (name === "listWorkspaceDir") {
|
|
294
331
|
const lines = text.split("\n").filter((l) => l.trim());
|
|
295
332
|
const preview = lines.slice(0, 3).map((l) => oneLine(l, 40)).join(", ");
|
|
@@ -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.3",
|
|
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",
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
"build": "tsc -p tsconfig.json",
|
|
33
33
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
34
34
|
"prepublishOnly": "bun run build",
|
|
35
|
-
"dev": "
|
|
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
|
},
|
|
@@ -45,20 +47,21 @@
|
|
|
45
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
48
|
"@mozilla/readability": "^0.6.0",
|
|
47
49
|
"ai": "^6.0.202",
|
|
48
|
-
"commander": "^15.0.0",
|
|
49
50
|
"diff": "^9.0.0",
|
|
50
51
|
"exa-js": "^2.13.0",
|
|
51
52
|
"files-sdk": "^1.8.0",
|
|
52
53
|
"ink": "^7.0.5",
|
|
53
54
|
"jsdom": "^29.1.1",
|
|
54
|
-
"ora": "^9.4.0",
|
|
55
55
|
"parallel-web": "^1.1.0",
|
|
56
|
+
"picospinner": "^3.0.0",
|
|
56
57
|
"react": "^19.2.7",
|
|
57
|
-
"
|
|
58
|
+
"react-tweet": "^3.3.1",
|
|
59
|
+
"sade": "^1.8.1",
|
|
58
60
|
"workers-ai-provider": "^3.1.14",
|
|
59
61
|
"zod": "^4.4.3"
|
|
60
62
|
},
|
|
61
63
|
"devDependencies": {
|
|
64
|
+
"bun-types": "^1.3.14",
|
|
62
65
|
"@types/jsdom": "^28.0.3",
|
|
63
66
|
"@types/node": "^25.9.3",
|
|
64
67
|
"@types/react": "^19.2.17",
|
|
@@ -68,6 +71,6 @@
|
|
|
68
71
|
"vitest": "^4.1.8"
|
|
69
72
|
},
|
|
70
73
|
"engines": {
|
|
71
|
-
"
|
|
74
|
+
"bun": ">=1.2.0"
|
|
72
75
|
}
|
|
73
76
|
}
|