@tiagojct/subsub 0.20.0 → 0.22.0

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/src/web.ts CHANGED
@@ -14,20 +14,15 @@
14
14
  * stops by itself, so a shortcut can start it without a terminal.
15
15
  */
16
16
 
17
- import { spawn, type ChildProcess } from "node:child_process";
18
17
  import { randomBytes, timingSafeEqual } from "node:crypto";
19
18
  import { createReadStream, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, appendFileSync } from "node:fs";
20
19
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
21
20
  import { homedir } from "node:os";
22
21
  import { basename, dirname, extname, join, normalize, relative, resolve, sep } from "node:path";
23
- import { StringDecoder } from "node:string_decoder";
24
22
  import { agentDirFor, chooseCwd, PACKAGE_DIR, packageVersion } from "./cli.ts";
25
- import { configPath, expand, loadConfig, RECOMMENDED_MODELS, readConfigFile, saveConfig, type SubsubConfig, uiLanguage, withStarbuck } from "./config.ts";
23
+ import { configPath, loadConfig, RECOMMENDED_MODELS, readConfigFile, saveConfig, type SubsubConfig, uiLanguage, withStarbuck } from "./config.ts";
26
24
  import { TESTED_MODELS } from "./init.ts";
27
25
  import { KEY_PROVIDERS, saveApiKey } from "./keys.ts";
28
- import { NOTE_DIRS, obsidianRoot, SETTINGS_DIR } from "./layout.ts";
29
- import { field, frontMatter } from "./notes.ts";
30
- import { realish, within } from "./paths.ts";
31
26
 
32
27
  // ---------------------------------------------------------------- options
33
28
 
@@ -63,130 +58,15 @@ export { uiLanguage } from "./config.ts";
63
58
 
64
59
  export { KEY_PROVIDERS, saveApiKey } from "./keys.ts";
65
60
 
66
- // ---------------------------------------------------------------- pi in RPC mode
67
-
68
- type Listener = (ev: any) => void;
69
-
70
- /** pi as a child process in RPC mode. Strict JSONL: records end at LF only. */
71
- export class Agent {
72
- proc?: ChildProcess;
73
- private decoder = new StringDecoder("utf8");
74
- private buf = "";
75
- private n = 0;
76
- private pending = new Map<string, { resolve: (v: any) => void; reject: (e: Error) => void; timer: NodeJS.Timeout }>();
77
- private listeners = new Set<Listener>();
78
- stderr = "";
79
- onExit?: (code: number | null) => void;
80
-
81
- private readonly command: string;
82
- private readonly args: string[];
83
- private readonly cwd: string;
84
- private readonly env: NodeJS.ProcessEnv;
85
-
86
- constructor(command: string, args: string[], cwd: string, env: NodeJS.ProcessEnv) {
87
- this.command = command;
88
- this.args = args;
89
- this.cwd = cwd;
90
- this.env = env;
91
- }
92
-
93
- start(extra: string[] = []): void {
94
- this.buf = "";
95
- this.decoder = new StringDecoder("utf8");
96
- const proc = spawn(this.command, [...this.args, ...extra], { cwd: this.cwd, env: this.env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
97
- this.proc = proc;
98
- proc.stdout!.on("data", (chunk: Buffer) => this.feed(this.decoder.write(chunk)));
99
- proc.stderr!.on("data", (d: Buffer) => {
100
- this.stderr = (this.stderr + d.toString()).slice(-20_000);
101
- });
102
- proc.once("exit", (code) => {
103
- if (this.proc !== proc) return;
104
- this.proc = undefined;
105
- for (const p of this.pending.values()) {
106
- clearTimeout(p.timer);
107
- p.reject(new Error(`pi stopped (code ${code}). ${this.stderr.slice(-500)}`));
108
- }
109
- this.pending.clear();
110
- this.onExit?.(code);
111
- });
112
- }
113
-
114
- private feed(text: string): void {
115
- this.buf += text;
116
- let i: number;
117
- while ((i = this.buf.indexOf("\n")) >= 0) {
118
- const line = this.buf.slice(0, i).replace(/\r$/, "");
119
- this.buf = this.buf.slice(i + 1);
120
- if (!line.trim()) continue;
121
- let rec: any;
122
- try {
123
- rec = JSON.parse(line);
124
- } catch {
125
- continue;
126
- }
127
- if (rec.type === "response" && typeof rec.id === "string" && this.pending.has(rec.id)) {
128
- const p = this.pending.get(rec.id)!;
129
- this.pending.delete(rec.id);
130
- clearTimeout(p.timer);
131
- if (rec.success === false) p.reject(new Error(rec.error ?? `${rec.command} failed`));
132
- else p.resolve(rec.data);
133
- continue;
134
- }
135
- for (const l of this.listeners) l(rec);
136
- }
137
- }
61
+ // ---------------------------------------------------------------- files (shared with the terminal screens)
138
62
 
139
- on(l: Listener): () => void {
140
- this.listeners.add(l);
141
- return () => this.listeners.delete(l);
142
- }
143
-
144
- write(rec: unknown): void {
145
- const stdin = this.proc?.stdin;
146
- if (!stdin || !stdin.writable) throw new Error("pi is not running");
147
- stdin.write(`${JSON.stringify(rec)}\n`);
148
- }
63
+ import { allowedFile, listNotes, openExternal, openFile, OPENABLE, wikilinkTarget } from "./files.ts";
64
+ export { allowedFile, listNotes, openExternal, OPENABLE, wikilinkTarget };
149
65
 
150
- request<T = any>(cmd: Record<string, unknown>, timeoutMs = 30_000): Promise<T> {
151
- const id = `w${++this.n}`;
152
- return new Promise<T>((resolvePromise, reject) => {
153
- const timer = setTimeout(() => {
154
- this.pending.delete(id);
155
- reject(new Error(`pi did not answer ${String(cmd.type)} in time`));
156
- }, timeoutMs);
157
- this.pending.set(id, { resolve: resolvePromise, reject, timer });
158
- try {
159
- this.write({ ...cmd, id });
160
- } catch (err) {
161
- clearTimeout(timer);
162
- this.pending.delete(id);
163
- reject(err as Error);
164
- }
165
- });
166
- }
66
+ // ---------------------------------------------------------------- pi in RPC mode (src/rpc.ts)
167
67
 
168
- async stop(): Promise<void> {
169
- const proc = this.proc;
170
- if (!proc) return;
171
- this.proc = undefined;
172
- for (const p of this.pending.values()) {
173
- clearTimeout(p.timer);
174
- p.reject(new Error("pi was stopped"));
175
- }
176
- this.pending.clear();
177
- await new Promise<void>((done) => {
178
- const t = setTimeout(() => {
179
- proc.kill("SIGKILL");
180
- done();
181
- }, 3000);
182
- proc.once("exit", () => {
183
- clearTimeout(t);
184
- done();
185
- });
186
- proc.kill();
187
- });
188
- }
189
- }
68
+ import { Agent, findLibrary, isDialog, Side, withHints } from "./rpc.ts";
69
+ export { Agent };
190
70
 
191
71
  // ---------------------------------------------------------------- helpers
192
72
 
@@ -206,43 +86,6 @@ const NOTE_TYPES = new Set([".md", ".markdown"]);
206
86
  /** Images that a note shows (a figure cut out of a PDF, for example). */
207
87
  const IMAGE_TYPES: Record<string, string> = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
208
88
 
209
- /** Files that the Open button may hand to the system (never programs or scripts). */
210
- export const OPENABLE = new Set([".md", ".markdown", ".txt", ".pdf", ".bib", ".csv", ".qmd"]);
211
-
212
- /** A file inside the working folder or the Sub-Sub folder, judged by its real path, so a link cannot point outside. */
213
- export function allowedFile(raw: string, cwd: string, vault?: string): string | undefined {
214
- // expand() resolves against the process folder: use it for "~" only.
215
- const target = resolve(cwd, raw.startsWith("~") ? expand(raw) : raw);
216
- const real = realish(target);
217
- const inside = within(real, realish(cwd)) || Boolean(vault && within(real, realish(vault)));
218
- return inside && existsSync(target) && statSync(target).isFile() ? target : undefined;
219
- }
220
-
221
- /** The note a [[wikilink]] names: the first <folder>/<name>.md in the Sub-Sub folder, as Obsidian would find it. */
222
- export function wikilinkTarget(name: string, root: string): string | undefined {
223
- const file = name.endsWith(".md") ? name : `${name}.md`;
224
- return ["", ...NOTE_DIRS, SETTINGS_DIR].map((d) => join(root, d, file)).find((f) => existsSync(f));
225
- }
226
-
227
- /** The notes in the Sub-Sub folders, newest first (dot folders such as Research/.plans left out). */
228
- export function listNotes(root: string, max = 300): Array<{ path: string; rel: string; modified: number }> {
229
- const out: Array<{ path: string; rel: string; modified: number }> = [];
230
- for (const d of [...NOTE_DIRS, SETTINGS_DIR]) {
231
- let names: string[] = [];
232
- try {
233
- names = readdirSync(join(root, d), { recursive: true }) as string[];
234
- } catch {
235
- continue;
236
- }
237
- for (const n of names) {
238
- if (!n.endsWith(".md") || n.split(/[\\/]/).some((x) => x.startsWith("."))) continue;
239
- const path = join(root, d, n);
240
- out.push({ path, rel: join(d, n), modified: statSync(path).mtimeMs });
241
- }
242
- }
243
- return out.sort((a, b) => b.modified - a.modified).slice(0, max);
244
- }
245
-
246
89
  const CSP = [
247
90
  "default-src 'none'",
248
91
  "script-src 'self'",
@@ -255,14 +98,6 @@ const CSP = [
255
98
  "frame-ancestors 'none'",
256
99
  ].join("; ");
257
100
 
258
- function readText(path: string): string {
259
- try {
260
- return readFileSync(path, "utf8");
261
- } catch {
262
- return "";
263
- }
264
- }
265
-
266
101
  function same(a: string, b: string): boolean {
267
102
  const x = Buffer.from(a);
268
103
  const y = Buffer.from(b);
@@ -310,18 +145,6 @@ function alive(pid: number): boolean {
310
145
  }
311
146
  }
312
147
 
313
- /** Open a URL or a file with the system's default program. */
314
- export function openExternal(target: string): void {
315
- const opts = { detached: true, stdio: "ignore" as const, windowsHide: true };
316
- let child: ChildProcess;
317
- if (process.platform === "darwin") child = spawn("open", [target], opts);
318
- // rundll32 hands the target to the default program without cmd.exe parsing it.
319
- else if (process.platform === "win32") child = spawn("rundll32", ["url.dll,FileProtocolHandler", target], opts);
320
- else child = spawn("xdg-open", [target], opts);
321
- child.on("error", () => {});
322
- child.unref();
323
- }
324
-
325
148
  // ---------------------------------------------------------------- server
326
149
 
327
150
  interface RunningFile {
@@ -363,7 +186,7 @@ export async function startWeb(opts: WebOptions, env: NodeJS.ProcessEnv = proces
363
186
  const clients = new Set<ServerResponse>();
364
187
  const dialogs = new Map<string, any>();
365
188
  let subsubState: unknown = null;
366
- let lastFind: { q?: string } | null = null;
189
+ const side = new Side();
367
190
  let busy = false;
368
191
  let lastSeen = Date.now();
369
192
  let restarting = false;
@@ -388,22 +211,12 @@ export async function startWeb(opts: WebOptions, env: NodeJS.ProcessEnv = proces
388
211
 
389
212
  agent.on((ev) => {
390
213
  if (ev.type === "extension_ui_request") {
391
- if (["select", "confirm", "input", "editor"].includes(ev.method)) {
214
+ if (isDialog(ev)) {
392
215
  dialogs.set(ev.id, ev);
393
216
  broadcast(ev);
394
- } else if (ev.method === "setStatus" && ev.statusKey === "subsub.web") {
395
- try {
396
- subsubState = ev.statusText ? JSON.parse(ev.statusText) : null;
397
- } catch {
398
- subsubState = null;
399
- }
217
+ } else if (side.take(ev) === "state") {
218
+ subsubState = side.state;
400
219
  broadcast({ type: "subsub_state", state: subsubState });
401
- } else if (ev.method === "setStatus" && ev.statusKey === "subsub.find") {
402
- try {
403
- lastFind = ev.statusText ? JSON.parse(ev.statusText) : null;
404
- } catch {
405
- lastFind = null;
406
- }
407
220
  } else if (ev.method === "notify") {
408
221
  broadcast(ev);
409
222
  }
@@ -445,13 +258,7 @@ export async function startWeb(opts: WebOptions, env: NodeJS.ProcessEnv = proces
445
258
  out.session = await agent.request({ type: "get_state" });
446
259
  lastSession = (out.session as { sessionFile?: string }).sessionFile ?? lastSession;
447
260
  out.messages = ((await agent.request({ type: "get_messages" })) as { messages: unknown[] }).messages;
448
- const commands = ((await agent.request({ type: "get_commands" })) as { commands: Array<Record<string, any>> }).commands;
449
- // RPC has no argument hints: read them from the prompt files.
450
- out.commands = commands.map((c) => {
451
- const front = c.source === "prompt" && c.sourceInfo?.path ? frontMatter(readText(c.sourceInfo.path)) : undefined;
452
- const hint = front && field(front, "argument-hint");
453
- return hint ? { ...c, hint } : c;
454
- });
261
+ out.commands = withHints(((await agent.request({ type: "get_commands" })) as { commands: Array<Record<string, any>> }).commands);
455
262
  } catch (err) {
456
263
  out.error = (err as Error).message;
457
264
  }
@@ -702,8 +509,7 @@ export async function startWeb(opts: WebOptions, env: NodeJS.ProcessEnv = proces
702
509
  case "GET /api/library": {
703
510
  const q = (url.searchParams.get("q") ?? "").replace(/\s+/g, " ").trim().slice(0, 200);
704
511
  if (!q) return send(res, 200, { q, items: [] });
705
- await agent.request({ type: "prompt", message: `/subsub-find ${q}` });
706
- return send(res, 200, lastFind?.q === q ? lastFind : { q, items: [] });
512
+ return send(res, 200, await findLibrary(agent, side, q));
707
513
  }
708
514
  case "POST /api/session/name": {
709
515
  const body = await readJson(req);
@@ -739,8 +545,7 @@ export async function startWeb(opts: WebOptions, env: NodeJS.ProcessEnv = proces
739
545
  if (!target) return send(res, 404, { error: "not found in the Sub-Sub folder" });
740
546
  // Documents only: a file that the model wrote must never be run.
741
547
  if (!OPENABLE.has(extname(target).toLowerCase())) return send(res, 403, { error: "Sub-Sub opens only notes and documents (.md, .txt, .pdf, .bib, .csv, .qmd)." });
742
- if (vault && within(target, vault) && obsidianRoot(vault)) openExternal(`obsidian://open?path=${encodeURIComponent(target)}`);
743
- else openExternal(target);
548
+ openFile(target, vault);
744
549
  return send(res, 200, { ok: true });
745
550
  }
746
551
  case "POST /api/quit":
package/src/words.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The plain words of the web view (actions, suggestions, names of tools, in English and Portuguese), for the terminal screens.
3
+ * web/i18n.js is a browser module with no imports; it is loaded here as it is, so the two views never differ.
4
+ */
5
+
6
+ import { join } from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+ import { PACKAGE_DIR } from "./cli.ts";
9
+
10
+ export interface Action {
11
+ cmd: string;
12
+ en: string;
13
+ pt: string;
14
+ /** Send the command at once instead of filling the box. */
15
+ send?: boolean;
16
+ /** The command takes a library item. */
17
+ item?: boolean;
18
+ /** Needs an add-on, for example "starbuck". */
19
+ needs?: string;
20
+ }
21
+
22
+ export interface Words {
23
+ ACTIONS: { research: Action[]; library: Action[] };
24
+ SUGGESTIONS: Record<"researcher" | "librarian", Array<{ en: string; pt: string }>>;
25
+ TOOLS: { en: Record<string, string>; pt: Record<string, string> };
26
+ }
27
+
28
+ let words: Promise<Words> | undefined;
29
+
30
+ /** A dynamic import: a static one would pull a file outside src/ into the TypeScript build. */
31
+ export function loadWords(): Promise<Words> {
32
+ words ??= import(pathToFileURL(join(PACKAGE_DIR, "web", "i18n.js")).href) as Promise<Words>;
33
+ return words;
34
+ }
35
+
36
+ /** What a tool is called while it runs ("zotero_find_items" is "search the library"); unknown tools keep their name. */
37
+ export function toolWord(w: Words, lang: "en" | "pt", name: string): string {
38
+ const short = name.replace(/^(zotero|scholar)_/, "");
39
+ return w.TOOLS[lang][short] ?? w.TOOLS.en[short] ?? name;
40
+ }
@@ -0,0 +1,80 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "subsub-glauca-dark-256",
4
+ "vars": {
5
+ "ink": 254,
6
+ "muted": 145,
7
+ "faint": 246,
8
+ "prussian": 110,
9
+ "madder": 173,
10
+ "sap": 144,
11
+ "gamboge": 179,
12
+ "mark": 242,
13
+ "rule": 236,
14
+ "accent": 110,
15
+ "wash": 236
16
+ },
17
+ "colors": {
18
+ "text": "ink",
19
+ "userMessageText": "ink",
20
+ "customMessageText": "ink",
21
+ "toolTitle": "ink",
22
+ "mdHeading": "ink",
23
+ "mdCode": "ink",
24
+ "mdCodeBlock": "ink",
25
+ "error": "ink",
26
+ "muted": "muted",
27
+ "toolOutput": "muted",
28
+ "mdQuote": "muted",
29
+ "toolDiffContext": "muted",
30
+ "thinkingText": "muted",
31
+ "toolDiffRemoved": "muted",
32
+ "dim": "faint",
33
+ "mdLinkUrl": "faint",
34
+ "syntaxComment": "faint",
35
+ "border": "mark",
36
+ "mdQuoteBorder": "mark",
37
+ "mdListBullet": "mark",
38
+ "borderMuted": "rule",
39
+ "mdHr": "rule",
40
+ "mdCodeBlockBorder": "rule",
41
+ "scrollbarTrack": "rule",
42
+ "thinkingOff": "rule",
43
+ "scrollbarThumb": "faint",
44
+ "accent": "accent",
45
+ "borderAccent": "accent",
46
+ "bashMode": "accent",
47
+ "customMessageLabel": "accent",
48
+ "thinkingXhigh": "accent",
49
+ "thinkingMax": "accent",
50
+ "mdLink": "prussian",
51
+ "thinkingHigh": "prussian",
52
+ "success": "sap",
53
+ "toolDiffAdded": "sap",
54
+ "warning": "gamboge",
55
+ "selectedBg": "wash",
56
+ "searchMatchBg": "",
57
+ "searchMatchText": "ink",
58
+ "userMessageBg": "",
59
+ "toolSuccessBg": "",
60
+ "customMessageBg": "",
61
+ "toolErrorBg": "",
62
+ "toolPendingBg": "",
63
+ "syntaxKeyword": "prussian",
64
+ "syntaxNumber": "prussian",
65
+ "syntaxFunction": "ink",
66
+ "syntaxVariable": "ink",
67
+ "syntaxType": "ink",
68
+ "syntaxString": "muted",
69
+ "syntaxOperator": "muted",
70
+ "syntaxPunctuation": "faint",
71
+ "thinkingMinimal": "faint",
72
+ "thinkingLow": "mark",
73
+ "thinkingMedium": "muted"
74
+ },
75
+ "export": {
76
+ "pageBg": "#12171c",
77
+ "cardBg": "#192128",
78
+ "infoBg": "#0d1115"
79
+ }
80
+ }
@@ -2,90 +2,86 @@
2
2
  "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
3
  "name": "subsub-glauca-dark",
4
4
  "vars": {
5
- "bg": "#121517",
6
- "surface": "#181d20",
7
- "raised": "#20272b",
8
- "text": "#e5ebec",
9
- "muted": "#8a9699",
10
- "line": "#283236",
11
- "mark": "#5d99be",
12
- "markBright": "#78b4da",
13
- "markDeep": "#41789c",
14
- "toneDeep": "#0e181c",
15
- "tone": "#263e48",
16
- "toneBright": "#486e7e",
17
- "tonePale": "#8cb6cb",
18
- "green": "#68aa80",
19
- "red": "#cf5c5c",
20
- "yellow": "#c49a46",
21
- "violet": "#9d82b3",
22
- "link": "#78b4da",
23
- "dim": "#5d6a6d",
24
- "select": "#263e48",
25
- "okBg": "#1b2920",
26
- "errBg": "#331c19"
5
+ "sheet": "#12171c",
6
+ "sunk": "#0d1115",
7
+ "field": "#192128",
8
+ "ink": "#dce1de",
9
+ "muted": "#a6b0b3",
10
+ "faint": "#8d989d",
11
+ "rule": "#2c363f",
12
+ "mark": "#66727c",
13
+ "prussian": "#86b4da",
14
+ "prussianWash": "#1e3042",
15
+ "madder": "#e6866d",
16
+ "madderWash": "#3d2620",
17
+ "sap": "#a3c078",
18
+ "sapWash": "#253220",
19
+ "gamboge": "#e2b45a",
20
+ "gambogeWash": "#3a3019",
21
+ "accent": "#86b4da",
22
+ "wash": "#1e3042"
27
23
  },
28
24
  "colors": {
29
- "accent": "mark",
30
- "border": "toneBright",
31
- "borderAccent": "mark",
32
- "borderMuted": "line",
33
- "success": "green",
34
- "error": "red",
35
- "warning": "yellow",
25
+ "text": "ink",
26
+ "userMessageText": "ink",
27
+ "customMessageText": "ink",
28
+ "toolTitle": "ink",
29
+ "mdHeading": "ink",
30
+ "mdCode": "ink",
31
+ "mdCodeBlock": "ink",
32
+ "error": "ink",
36
33
  "muted": "muted",
37
- "dim": "dim",
38
- "text": "text",
39
- "thinkingText": "muted",
40
- "selectedBg": "select",
41
- "scrollbarTrack": "line",
42
- "scrollbarThumb": "muted",
43
- "searchMatchBg": "select",
44
- "searchMatchText": "text",
45
- "userMessageBg": "raised",
46
- "userMessageText": "text",
47
- "customMessageBg": "surface",
48
- "customMessageText": "text",
49
- "customMessageLabel": "tonePale",
50
- "toolPendingBg": "surface",
51
- "toolSuccessBg": "okBg",
52
- "toolErrorBg": "errBg",
53
- "toolTitle": "text",
54
34
  "toolOutput": "muted",
55
- "mdHeading": "tonePale",
56
- "mdLink": "link",
57
- "mdLinkUrl": "dim",
58
- "mdCode": "green",
59
- "mdCodeBlock": "text",
60
- "mdCodeBlockBorder": "line",
61
35
  "mdQuote": "muted",
62
- "mdQuoteBorder": "toneBright",
63
- "mdHr": "line",
64
- "mdListBullet": "toneBright",
65
- "toolDiffAdded": "green",
66
- "toolDiffRemoved": "red",
67
36
  "toolDiffContext": "muted",
68
- "syntaxComment": "#5d6a6d",
69
- "syntaxKeyword": "#78b4da",
70
- "syntaxFunction": "#5d99be",
71
- "syntaxVariable": "#e5ebec",
72
- "syntaxString": "#68aa80",
73
- "syntaxNumber": "#9d82b3",
74
- "syntaxType": "#8cb6cb",
75
- "syntaxOperator": "#8a9699",
76
- "syntaxPunctuation": "#8a9699",
77
- "thinkingOff": "line",
78
- "thinkingMinimal": "dim",
79
- "thinkingLow": "toneBright",
80
- "thinkingMedium": "tonePale",
81
- "thinkingHigh": "violet",
82
- "thinkingXhigh": "markBright",
83
- "thinkingMax": "mark",
84
- "bashMode": "mark"
37
+ "thinkingText": "muted",
38
+ "toolDiffRemoved": "muted",
39
+ "dim": "faint",
40
+ "mdLinkUrl": "faint",
41
+ "syntaxComment": "faint",
42
+ "border": "mark",
43
+ "mdQuoteBorder": "mark",
44
+ "mdListBullet": "mark",
45
+ "borderMuted": "rule",
46
+ "mdHr": "rule",
47
+ "mdCodeBlockBorder": "rule",
48
+ "scrollbarTrack": "rule",
49
+ "thinkingOff": "rule",
50
+ "scrollbarThumb": "faint",
51
+ "accent": "accent",
52
+ "borderAccent": "accent",
53
+ "bashMode": "accent",
54
+ "customMessageLabel": "accent",
55
+ "thinkingXhigh": "accent",
56
+ "thinkingMax": "accent",
57
+ "mdLink": "prussian",
58
+ "thinkingHigh": "prussian",
59
+ "success": "sap",
60
+ "toolDiffAdded": "sap",
61
+ "warning": "gamboge",
62
+ "selectedBg": "wash",
63
+ "searchMatchBg": "gambogeWash",
64
+ "searchMatchText": "ink",
65
+ "userMessageBg": "field",
66
+ "toolSuccessBg": "field",
67
+ "customMessageBg": "sunk",
68
+ "toolErrorBg": "sunk",
69
+ "toolPendingBg": "gambogeWash",
70
+ "syntaxKeyword": "prussian",
71
+ "syntaxNumber": "prussian",
72
+ "syntaxFunction": "ink",
73
+ "syntaxVariable": "ink",
74
+ "syntaxType": "ink",
75
+ "syntaxString": "muted",
76
+ "syntaxOperator": "muted",
77
+ "syntaxPunctuation": "faint",
78
+ "thinkingMinimal": "faint",
79
+ "thinkingLow": "mark",
80
+ "thinkingMedium": "muted"
85
81
  },
86
82
  "export": {
87
- "pageBg": "#121517",
88
- "cardBg": "#181d20",
89
- "infoBg": "#20272b"
83
+ "pageBg": "#12171c",
84
+ "cardBg": "#192128",
85
+ "infoBg": "#0d1115"
90
86
  }
91
87
  }
@@ -0,0 +1,80 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "subsub-glauca-light-256",
4
+ "vars": {
5
+ "ink": 234,
6
+ "muted": 239,
7
+ "faint": 241,
8
+ "prussian": 24,
9
+ "madder": 94,
10
+ "sap": 58,
11
+ "gamboge": 94,
12
+ "mark": 102,
13
+ "rule": 251,
14
+ "accent": 24,
15
+ "wash": 254
16
+ },
17
+ "colors": {
18
+ "text": "ink",
19
+ "userMessageText": "ink",
20
+ "customMessageText": "ink",
21
+ "toolTitle": "ink",
22
+ "mdHeading": "ink",
23
+ "mdCode": "ink",
24
+ "mdCodeBlock": "ink",
25
+ "error": "ink",
26
+ "muted": "muted",
27
+ "toolOutput": "muted",
28
+ "mdQuote": "muted",
29
+ "toolDiffContext": "muted",
30
+ "thinkingText": "muted",
31
+ "toolDiffRemoved": "muted",
32
+ "dim": "faint",
33
+ "mdLinkUrl": "faint",
34
+ "syntaxComment": "faint",
35
+ "border": "mark",
36
+ "mdQuoteBorder": "mark",
37
+ "mdListBullet": "mark",
38
+ "borderMuted": "rule",
39
+ "mdHr": "rule",
40
+ "mdCodeBlockBorder": "rule",
41
+ "scrollbarTrack": "rule",
42
+ "thinkingOff": "rule",
43
+ "scrollbarThumb": "faint",
44
+ "accent": "accent",
45
+ "borderAccent": "accent",
46
+ "bashMode": "accent",
47
+ "customMessageLabel": "accent",
48
+ "thinkingXhigh": "accent",
49
+ "thinkingMax": "accent",
50
+ "mdLink": "prussian",
51
+ "thinkingHigh": "prussian",
52
+ "success": "sap",
53
+ "toolDiffAdded": "sap",
54
+ "warning": "gamboge",
55
+ "selectedBg": "wash",
56
+ "searchMatchBg": "",
57
+ "searchMatchText": "ink",
58
+ "userMessageBg": "",
59
+ "toolSuccessBg": "",
60
+ "customMessageBg": "",
61
+ "toolErrorBg": "",
62
+ "toolPendingBg": "",
63
+ "syntaxKeyword": "prussian",
64
+ "syntaxNumber": "prussian",
65
+ "syntaxFunction": "ink",
66
+ "syntaxVariable": "ink",
67
+ "syntaxType": "ink",
68
+ "syntaxString": "muted",
69
+ "syntaxOperator": "muted",
70
+ "syntaxPunctuation": "faint",
71
+ "thinkingMinimal": "faint",
72
+ "thinkingLow": "mark",
73
+ "thinkingMedium": "muted"
74
+ },
75
+ "export": {
76
+ "pageBg": "#e9ece6",
77
+ "cardBg": "#f4f5f1",
78
+ "infoBg": "#dfe3dc"
79
+ }
80
+ }