@tiagojct/subsub 0.6.1 → 0.8.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/profiles.ts CHANGED
@@ -19,6 +19,8 @@ export interface ProfileSpec {
19
19
  summary: string;
20
20
  /** Tools this profile does not offer (full names). */
21
21
  without: string[];
22
+ /** Prompt commands this profile does not run (names without the slash). */
23
+ withoutPrompts: string[];
22
24
  /** Items per review note and per batch. */
23
25
  batch: number;
24
26
  /** Added to the system prompt. {{user}} is the user's name or "the user". */
@@ -36,6 +38,7 @@ export const PROFILE_SPECS: Record<Profile, ProfileSpec> = {
36
38
  label: "Reader",
37
39
  summary: "explains each step; reading notes are quotes and questions; no summaries; no bulk library changes",
38
40
  without: [...BULK_LIBRARY, ...MANUSCRIPT],
41
+ withoutPrompts: ["lit", "compare", "review", "digest"],
39
42
  batch: 10,
40
43
  rules: [
41
44
  "Profile: Reader. {{User}} is learning to work with the literature and wants to do the reading and the writing.",
@@ -51,6 +54,7 @@ export const PROFILE_SPECS: Record<Profile, ProfileSpec> = {
51
54
  label: "Scholar",
52
55
  summary: "literature notes, syntheses, searches and alerts; the full librarian",
53
56
  without: MANUSCRIPT,
57
+ withoutPrompts: ["review"],
54
58
  batch: 25,
55
59
  rules: [
56
60
  "Profile: Scholar.",
@@ -64,6 +68,7 @@ export const PROFILE_SPECS: Record<Profile, ProfileSpec> = {
64
68
  label: "Author",
65
69
  summary: "Scholar plus manuscripts: citation check, bibliography, comments on the argument",
66
70
  without: [],
71
+ withoutPrompts: [],
67
72
  batch: 25,
68
73
  rules: [
69
74
  "Profile: Author.",
@@ -78,6 +83,7 @@ export const PROFILE_SPECS: Record<Profile, ProfileSpec> = {
78
83
  label: "Editor",
79
84
  summary: "everything, including drafting manuscript text on request",
80
85
  without: [],
86
+ withoutPrompts: [],
81
87
  batch: 25,
82
88
  rules: [
83
89
  "Profile: Editor. All tools.",
@@ -92,6 +98,17 @@ export function profileSpec(p: Profile): ProfileSpec {
92
98
  return PROFILE_SPECS[p];
93
99
  }
94
100
 
101
+ /** Why a prompt command does not run in this profile, or undefined when it does. */
102
+ export function promptBlocked(text: string, profile: Profile): string | undefined {
103
+ const m = /^\/([\w-]+)(?:\s|$)/.exec(text.trim());
104
+ if (!m || !PROFILE_SPECS[profile].withoutPrompts.includes(m[1])) return undefined;
105
+ const allowed = Object.values(PROFILE_SPECS)
106
+ .filter((s) => !s.withoutPrompts.includes(m[1]))
107
+ .map((s) => s.label)
108
+ .join(", ");
109
+ return `/${m[1]} is not part of the ${PROFILE_SPECS[profile].label} profile (it is in: ${allowed}). Change the profile with /profile.`;
110
+ }
111
+
95
112
  export function profileList(current?: Profile): string {
96
113
  return Object.values(PROFILE_SPECS)
97
114
  .map((s) => `${s.name === current ? "*" : " "} ${s.label.padEnd(8)} ${s.summary}`)
package/src/prompt.ts CHANGED
@@ -74,6 +74,12 @@ export function systemAddition(mode: Mode, cfg: SubsubConfig, cwd: string): stri
74
74
  if (t) parts.push(`# Vault conventions (${f})`, t);
75
75
  }
76
76
  }
77
+ if (cfg.setup === "fmup") {
78
+ parts.push(
79
+ "# U.Porto",
80
+ `${capital(user)} works at the University of Porto. Papers that are not open access often open through the university's subscriptions when ${user} uses Sub-Sub on the U.Porto network: say so when a work is not open access.`,
81
+ );
82
+ }
77
83
  // The profile and the language rule come last, so they win over the shared rules and the vault conventions.
78
84
  parts.push("# Profile", fill(profileSpec(cfg.profile).rules, cfg));
79
85
  parts.push("# Language", languageRule(cfg.language, user));
package/src/roles.ts CHANGED
@@ -7,6 +7,9 @@ import { profileSpec } from "./profiles.ts";
7
7
 
8
8
  export const FILE_TOOLS = ["read", "edit", "write", "grep", "find", "ls"];
9
9
 
10
+ /** Prefixes of the tools that Sub-Sub's servers provide (the gate checks the mode for these). */
11
+ export const SERVER_PREFIXES = ["zotero_", "scholar_", "verify_"];
12
+
10
13
  export const ZOTERO_READ = [
11
14
  "status",
12
15
  "library_overview",
@@ -51,7 +54,11 @@ export function toolsFor(mode: Mode, registered: string[], profile: Profile = "e
51
54
  const wanted =
52
55
  mode === "librarian"
53
56
  ? [...registered.filter((n) => n.startsWith("zotero_")), ...FILE_TOOLS]
54
- : [...ZOTERO_READ, ...registered.filter((n) => n.startsWith("scholar_")), ...FILE_TOOLS];
57
+ : [
58
+ ...ZOTERO_READ,
59
+ ...registered.filter((n) => n.startsWith("scholar_") || n.startsWith("verify_")),
60
+ ...FILE_TOOLS,
61
+ ];
55
62
  return [...new Set(wanted)].filter((n) => has.has(n) && !without.has(n));
56
63
  }
57
64
 
package/src/subsub.ts CHANGED
@@ -8,24 +8,41 @@
8
8
  * - Profiles (reader, scholar, author, editor) set what Sub-Sub writes and which
9
9
  * tools it offers; /profile changes it.
10
10
  * - Commands: /librarian, /researcher, /profile, /subsub, /history, /undo.
11
+ * - Add-on: with "addons": ["starbuck"], Starbuck runs as a third server
12
+ * ("verify") for reference checks; its tools belong to researcher mode.
11
13
  *
12
14
  * The Python servers start in session_start (not in the factory) and stop in
13
15
  * session_shutdown. Tools are registered once and use the current bridge.
14
16
  */
15
17
 
16
18
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
17
- import { readFileSync } from "node:fs";
19
+ import { existsSync, readFileSync } from "node:fs";
18
20
  import { dirname, join } from "node:path";
19
21
  import { fileURLToPath } from "node:url";
20
22
  import { Bridge, type ServerSpec } from "./bridge.ts";
21
- import { expand, findUv, isProfile, loadConfig, type Mode, type Profile, saveConfig, serverCommand, type SubsubConfig, who } from "./config.ts";
22
- import { profileList, profileSpec } from "./profiles.ts";
23
+ import {
24
+ expand,
25
+ findUv,
26
+ isProfile,
27
+ loadConfig,
28
+ type Mode,
29
+ parseEnvFile,
30
+ type Profile,
31
+ saveConfig,
32
+ serverCommand,
33
+ starbuckCommand,
34
+ starbuckEnabled,
35
+ type SubsubConfig,
36
+ who,
37
+ } from "./config.ts";
38
+ import { profileList, profileSpec, promptBlocked } from "./profiles.ts";
23
39
  import { coerceArgs, loosenArrays } from "./args.ts";
40
+ import { applyEdits, checkLiteratureNote } from "./notes.ts";
24
41
  import { buildPolicy, judgePath, normalizeToolPath, realish, within } from "./paths.ts";
25
42
  import { describeArgs, formatPreview } from "./preview.ts";
26
43
  import { headerLines, type LibraryState, paintPreview, QUOTES, type Scheme, schemeFromAnsi, themeName } from "./look.ts";
27
44
  import { systemAddition } from "./prompt.ts";
28
- import { gateKind, toolsFor, unavailableReason } from "./roles.ts";
45
+ import { gateKind, SERVER_PREFIXES, toolsFor, unavailableReason } from "./roles.ts";
29
46
 
30
47
  export interface SubsubDeps {
31
48
  config?: SubsubConfig;
@@ -48,12 +65,30 @@ function ownVersion(): string {
48
65
 
49
66
  export { findUv };
50
67
 
51
- export function serverSpecs(cfg: SubsubConfig): ServerSpec[] {
68
+ export function serverSpecs(cfg: SubsubConfig, processEnv: NodeJS.ProcessEnv = process.env): ServerSpec[] {
52
69
  const env: Record<string, string> = cfg.envFile ? { ZOTERO_MCP_ENV: cfg.envFile } : {};
53
- return [
54
- { name: "zotero", ...serverCommand(cfg, "zotero-local-mcp"), env },
55
- { name: "scholar", ...serverCommand(cfg, "zotero-scholar-mcp"), env },
70
+ const specs: ServerSpec[] = [
71
+ { name: "zotero", ...serverCommand(cfg, "zotero-local-mcp", [], processEnv), env },
72
+ { name: "scholar", ...serverCommand(cfg, "zotero-scholar-mcp", [], processEnv), env },
56
73
  ];
74
+ if (starbuckEnabled(cfg)) specs.push({ name: "verify", ...starbuckCommand(cfg, processEnv), env: starbuckEnv(cfg, processEnv) });
75
+ return specs;
76
+ }
77
+
78
+ /**
79
+ * Starbuck reads its contact address and API keys from the environment. Sub-Sub keeps
80
+ * them in the zotero-local-mcp env file, so they are passed on (the environment wins).
81
+ */
82
+ export function starbuckEnv(cfg: Pick<SubsubConfig, "envFile">, processEnv: NodeJS.ProcessEnv = process.env): Record<string, string> {
83
+ const file = cfg.envFile ? parseEnvFile(cfg.envFile) : {};
84
+ const out: Record<string, string> = {};
85
+ const email = processEnv.STARBUCK_EMAIL || file.STARBUCK_EMAIL || processEnv.ZOTERO_CONTACT_EMAIL || file.ZOTERO_CONTACT_EMAIL;
86
+ if (email) out.STARBUCK_EMAIL = email;
87
+ for (const key of ["NCBI_API_KEY", "OPENALEX_API_KEY"]) {
88
+ const v = processEnv[key] || file[key];
89
+ if (v) out[key] = v;
90
+ }
91
+ return out;
57
92
  }
58
93
 
59
94
  export async function createSubsub(pi: ExtensionAPI, deps: SubsubDeps = {}): Promise<void> {
@@ -125,6 +160,7 @@ export async function createSubsub(pi: ExtensionAPI, deps: SubsubDeps = {}): Pro
125
160
  language: cfg.language,
126
161
  userName: cfg.userName ?? null,
127
162
  vault: cfg.vault ?? null,
163
+ starbuck: starbuckEnabled(cfg),
128
164
  };
129
165
  try {
130
166
  ctx.ui.setStatus("subsub.web", JSON.stringify(state));
@@ -262,6 +298,15 @@ export async function createSubsub(pi: ExtensionAPI, deps: SubsubDeps = {}): Pro
262
298
 
263
299
  pi.on("model_select", async (_event, ctx) => status(ctx));
264
300
 
301
+ // Prompt commands that the profile does not run (/lit for Reader, /review for Reader and Scholar).
302
+ // The input event comes before pi expands a prompt template.
303
+ pi.on("input", async (event, ctx) => {
304
+ const why = promptBlocked(event.text, profile);
305
+ if (!why) return { action: "continue" };
306
+ ctx.ui.notify(`Sub-Sub: ${why}`, "warning");
307
+ return { action: "handled" };
308
+ });
309
+
265
310
  pi.on("before_agent_start", async (event, ctx) => ({
266
311
  systemPrompt: `${event.systemPrompt}\n\n${systemAddition(mode, { ...cfg, profile }, ctx.cwd)}`,
267
312
  }));
@@ -276,18 +321,21 @@ export async function createSubsub(pi: ExtensionAPI, deps: SubsubDeps = {}): Pro
276
321
  // Previews and the real call must see the same, repaired arguments.
277
322
  const schema = schemas.get(name);
278
323
  if (schema) coerceArgs(schema, input);
279
- if (name.startsWith("zotero_") || name.startsWith("scholar_")) {
324
+ if (SERVER_PREFIXES.some((p) => name.startsWith(p))) {
280
325
  const why = unavailableReason(name, mode, registered(), profile);
281
326
  if (why) {
282
327
  if (benchOut) record({ kind: "wrong_mode", tool: name, input });
283
328
  return { block: true, reason: why };
284
329
  }
285
330
  }
331
+ if (name.startsWith("verify_")) return verifyGate(name, input, ctx);
286
332
  const kind = benchOut && name === "scholar_queue_imports" ? "confirm" : gateKind(name, input);
287
333
  if (!kind) return undefined;
288
334
  if (benchOut) return benchGate(name, input, kind, ctx.cwd, ctx.signal);
289
335
  if (kind === "path") {
290
336
  const target = normalizeToolPath(String(input.path ?? ""), ctx.cwd);
337
+ const noteProblem = literatureNoteProblem(name, target, input);
338
+ if (noteProblem) return { block: true, reason: noteProblem };
291
339
  const verdict = judgePath(target, buildPolicy({ vault: cfg.vault, cwd: ctx.cwd, serverDir: cfg.serverDir, packageDir: PACKAGE_DIR }));
292
340
  if (verdict.ok) return undefined;
293
341
  if (!ctx.hasUI) return { block: true, reason: `Writing ${target} needs ${user}'s approval: ${verdict.why}.` };
@@ -318,9 +366,52 @@ export async function createSubsub(pi: ExtensionAPI, deps: SubsubDeps = {}): Pro
318
366
  return ok ? undefined : { block: true, reason: `${user} did not approve this change. Ask what to change; do not retry the same call.` };
319
367
  });
320
368
 
369
+ /** A literature note (front matter with a citekey) must state its evidence; see notes.ts. */
370
+ function literatureNoteProblem(name: string, target: string, input: Record<string, unknown>): string | undefined {
371
+ if (!target.toLowerCase().endsWith(".md")) return undefined;
372
+ if (name === "write") return checkLiteratureNote(String(input.content ?? ""));
373
+ if (name === "edit" && Array.isArray(input.edits) && existsSync(target)) {
374
+ const current = readFileSync(target, "utf8");
375
+ return checkLiteratureNote(applyEdits(current, input.edits as Array<{ oldText?: unknown; newText?: unknown }>));
376
+ }
377
+ return undefined;
378
+ }
379
+
380
+ /**
381
+ * Starbuck writes its reports into a folder: report_dir, or _starbuck next to the
382
+ * manuscript. Inside the vault or the working folder that needs no approval; elsewhere
383
+ * Sub-Sub asks, as for any file write. record_claims gets the current model's name.
384
+ */
385
+ async function verifyGate(name: string, input: Record<string, unknown>, ctx: ExtensionContext) {
386
+ if (name === "verify_record_claims") {
387
+ input.judged_by = ctx.model ? [ctx.model.provider, ctx.model.id].filter(Boolean).join("/") : "the agent's model";
388
+ }
389
+ if (name === "verify_check_references") return undefined;
390
+ const dir = input.report_dir
391
+ ? normalizeToolPath(String(input.report_dir), ctx.cwd)
392
+ : input.path
393
+ ? join(dirname(normalizeToolPath(String(input.path), ctx.cwd)), "_starbuck")
394
+ : undefined;
395
+ if (!dir) return undefined;
396
+ const verdict = judgePath(dir, buildPolicy({ vault: cfg.vault, cwd: ctx.cwd, serverDir: cfg.serverDir, packageDir: PACKAGE_DIR }));
397
+ if (verdict.ok) return undefined;
398
+ if (benchOut) {
399
+ record({ kind: "path_blocked", tool: name, path: dir });
400
+ return { block: true, reason: `Model test: Starbuck reports only inside the vault or ${benchOut}.` };
401
+ }
402
+ if (!ctx.hasUI) return { block: true, reason: `Writing a Starbuck report to ${dir} needs ${user}'s approval: ${verdict.why}.` };
403
+ const ok = await ctx.ui.confirm(`Sub-Sub: write the Starbuck report to ${dir}?`, `Asking because ${verdict.why}.`);
404
+ return ok ? undefined : { block: true, reason: `${user} did not allow writing the report there. Ask where to put it (report_dir).` };
405
+ }
406
+
321
407
  async function benchGate(name: string, input: Record<string, unknown>, kind: string, cwd: string, signal?: AbortSignal) {
322
408
  if (kind === "path") {
323
409
  const target = normalizeToolPath(String(input.path ?? ""), cwd);
410
+ const noteProblem = literatureNoteProblem(name, target, input);
411
+ if (noteProblem) {
412
+ record({ kind: "note_blocked", tool: name, path: target, reason: noteProblem });
413
+ return { block: true, reason: noteProblem };
414
+ }
324
415
  if (within(target, realish(benchOut!))) return undefined;
325
416
  record({ kind: "path_blocked", tool: name, path: target });
326
417
  return { block: true, reason: `Model test: write only inside ${benchOut}.` };
package/src/web.ts CHANGED
@@ -22,9 +22,9 @@ import { homedir } from "node:os";
22
22
  import { extname, join, normalize, resolve, sep } from "node:path";
23
23
  import { StringDecoder } from "node:string_decoder";
24
24
  import { agentDirFor, chooseCwd, PACKAGE_DIR, packageVersion } from "./cli.ts";
25
- import { expand, loadConfig, saveConfig, type SubsubConfig } from "./config.ts";
25
+ import { expand, loadConfig, saveConfig, type SubsubConfig, withStarbuck } from "./config.ts";
26
26
  import { TESTED_MODELS } from "./init.ts";
27
- import { within } from "./paths.ts";
27
+ import { realish, within } from "./paths.ts";
28
28
 
29
29
  // ---------------------------------------------------------------- options
30
30
 
@@ -577,6 +577,15 @@ export async function startWeb(opts: WebOptions, env: NodeJS.ProcessEnv = proces
577
577
  await restartAgent();
578
578
  return send(res, 200, { ok: true });
579
579
  }
580
+ case "POST /api/addons": {
581
+ // Turn an add-on on or off, then restart pi so the extension starts or stops its server.
582
+ if (busy) return send(res, 409, { error: "busy" });
583
+ const body = await readJson(req);
584
+ if (typeof body.starbuck !== "boolean") return send(res, 400, { error: "starbuck must be true or false" });
585
+ saveConfig({ addons: withStarbuck(loadConfig(env).addons, body.starbuck) }, env);
586
+ await restartAgent();
587
+ return send(res, 200, { ok: true });
588
+ }
580
589
  case "POST /api/restart":
581
590
  if (agent.proc) return send(res, 200, { ok: true });
582
591
  agent.start();
@@ -595,7 +604,10 @@ export async function startWeb(opts: WebOptions, env: NodeJS.ProcessEnv = proces
595
604
  const body = await readJson(req);
596
605
  const target = resolve(cwd, expand(String(body.path ?? "")));
597
606
  const vault = cfg.vault;
598
- if (!(within(target, cwd) || (vault && within(target, vault))) || !existsSync(target)) return send(res, 404, { error: "not found in the notes folder" });
607
+ // Judge the real file, so a link inside the notes folder cannot open a document outside it.
608
+ const real = realish(target);
609
+ const inside = within(real, realish(cwd)) || Boolean(vault && within(real, realish(vault)));
610
+ if (!inside || !existsSync(target)) return send(res, 404, { error: "not found in the notes folder" });
599
611
  // Documents only: a file that the model wrote must never be run.
600
612
  if (!OPENABLE.has(extname(target).toLowerCase())) return send(res, 403, { error: "Sub-Sub opens only notes and documents (.md, .txt, .pdf, .bib, .csv, .qmd)." });
601
613
  if (vault && within(target, vault) && existsSync(join(vault, ".obsidian"))) openExternal(`obsidian://open?path=${encodeURIComponent(target)}`);
@@ -40,6 +40,7 @@ Shared rules for the Sub-Sub librarian and researcher. Sub-Sub reads this file a
40
40
  - Folder: Literature/. File name: the citekey (smith2026.md).
41
41
  - Create a note only when the user asks. Ask before creating more than 10 notes.
42
42
  - Write from the full text (get_fulltext) when it exists. Otherwise use the abstract and the Zotero notes, and write "(from abstract)" after the summary.
43
+ - Front matter field evidence: full text, abstract or metadata (what you read). Page numbers only with evidence: full text. Sub-Sub refuses a literature note without this field.
43
44
  - Do not copy the abstract in full. Give page numbers for quotes when the text shows them.
44
45
  - If a note for the citekey exists, do not overwrite it. Add a dated section or ask.
45
46
  - After the note is written, offer to link it in Zotero with attach_note.
@@ -58,6 +59,7 @@ item-type: journalArticle
58
59
  venue: Journal name
59
60
  doi: 10.xxxx/xxxxx
60
61
  zotero: zotero://select/library/items/ITEMKEY
62
+ evidence: full text
61
63
  status: to-read
62
64
  tags:
63
65
  - topic/example
@@ -2,28 +2,28 @@
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": "#10161c",
6
- "surface": "#171f26",
7
- "raised": "#1f2932",
8
- "text": "#e8eef2",
9
- "muted": "#8c8c8c",
10
- "line": "#2a3540",
11
- "mark": "#3d97ff",
12
- "markBright": "#6cb2ff",
13
- "markDeep": "#007aff",
14
- "toneDeep": "#142430",
15
- "tone": "#2b4356",
16
- "toneBright": "#4d7391",
17
- "tonePale": "#93b7c9",
18
- "green": "#62ba46",
19
- "red": "#b85f5f",
20
- "yellow": "#c79a3d",
21
- "violet": "#b184db",
22
- "link": "#4a9edb",
23
- "dim": "#616365",
24
- "select": "#2b4356",
25
- "okBg": "#22352a",
26
- "errBg": "#31292f"
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"
27
27
  },
28
28
  "colors": {
29
29
  "accent": "mark",
@@ -65,15 +65,15 @@
65
65
  "toolDiffAdded": "green",
66
66
  "toolDiffRemoved": "red",
67
67
  "toolDiffContext": "muted",
68
- "syntaxComment": "#8c8c8c",
69
- "syntaxKeyword": "#007aff",
70
- "syntaxFunction": "#4a9edb",
71
- "syntaxVariable": "#e8eef2",
72
- "syntaxString": "#62ba46",
73
- "syntaxNumber": "#b184db",
74
- "syntaxType": "#45a3ad",
75
- "syntaxOperator": "#a7b1b8",
76
- "syntaxPunctuation": "#86929a",
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
77
  "thinkingOff": "line",
78
78
  "thinkingMinimal": "dim",
79
79
  "thinkingLow": "toneBright",
@@ -84,8 +84,8 @@
84
84
  "bashMode": "mark"
85
85
  },
86
86
  "export": {
87
- "pageBg": "#10161c",
88
- "cardBg": "#171f26",
89
- "infoBg": "#1f2932"
87
+ "pageBg": "#121517",
88
+ "cardBg": "#181d20",
89
+ "infoBg": "#20272b"
90
90
  }
91
91
  }
@@ -2,28 +2,28 @@
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-light",
4
4
  "vars": {
5
- "bg": "#f0f4f6",
6
- "surface": "#f7fafb",
5
+ "bg": "#f8f6f0",
6
+ "surface": "#f2ede2",
7
7
  "raised": "#ffffff",
8
- "text": "#16222a",
9
- "muted": "#55646d",
10
- "line": "#cdd7dc",
11
- "mark": "#0b62cf",
12
- "markBright": "#007aff",
13
- "markDeep": "#084b96",
14
- "toneDeep": "#16303f",
15
- "tone": "#35576b",
16
- "toneBright": "#3e6d84",
17
- "tonePale": "#b7cdd6",
18
- "green": "#3c6e38",
19
- "red": "#a55859",
20
- "yellow": "#846c36",
21
- "violet": "#6b588b",
22
- "link": "#33668b",
23
- "dim": "#8b969d",
24
- "select": "#b7cdd6",
25
- "okBg": "#dde6e0",
26
- "errBg": "#eae0e1"
8
+ "text": "#211e1b",
9
+ "muted": "#5c554a",
10
+ "line": "#ded6c4",
11
+ "mark": "#234b6e",
12
+ "markBright": "#2f648e",
13
+ "markDeep": "#15344e",
14
+ "toneDeep": "#16222b",
15
+ "tone": "#2e4854",
16
+ "toneBright": "#436574",
17
+ "tonePale": "#b5cad6",
18
+ "green": "#205438",
19
+ "red": "#9c281b",
20
+ "yellow": "#8f6b28",
21
+ "violet": "#604e76",
22
+ "link": "#234b6e",
23
+ "dim": "#827768",
24
+ "select": "#ded6c4",
25
+ "okBg": "#dfebe3",
26
+ "errBg": "#f5e4e2"
27
27
  },
28
28
  "colors": {
29
29
  "accent": "mark",
@@ -65,15 +65,15 @@
65
65
  "toolDiffAdded": "green",
66
66
  "toolDiffRemoved": "red",
67
67
  "toolDiffContext": "muted",
68
- "syntaxComment": "#55646d",
69
- "syntaxKeyword": "#0b62cf",
70
- "syntaxFunction": "#33668b",
71
- "syntaxVariable": "#16222a",
72
- "syntaxString": "#3c6e38",
73
- "syntaxNumber": "#6b588b",
74
- "syntaxType": "#306972",
75
- "syntaxOperator": "#55646d",
76
- "syntaxPunctuation": "#55646d",
68
+ "syntaxComment": "#827768",
69
+ "syntaxKeyword": "#234b6e",
70
+ "syntaxFunction": "#2e4854",
71
+ "syntaxVariable": "#211e1b",
72
+ "syntaxString": "#205438",
73
+ "syntaxNumber": "#604e76",
74
+ "syntaxType": "#436574",
75
+ "syntaxOperator": "#5c554a",
76
+ "syntaxPunctuation": "#5c554a",
77
77
  "thinkingOff": "line",
78
78
  "thinkingMinimal": "dim",
79
79
  "thinkingLow": "toneBright",
@@ -84,8 +84,8 @@
84
84
  "bashMode": "mark"
85
85
  },
86
86
  "export": {
87
- "pageBg": "#f0f4f6",
88
- "cardBg": "#f7fafb",
87
+ "pageBg": "#f8f6f0",
88
+ "cardBg": "#f2ede2",
89
89
  "infoBg": "#ffffff"
90
90
  }
91
91
  }
@@ -2,28 +2,28 @@
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-try-works-dark",
4
4
  "vars": {
5
- "bg": "#12161b",
6
- "surface": "#1b2127",
7
- "raised": "#232b32",
8
- "text": "#f1efe9",
9
- "muted": "#97a0a4",
10
- "line": "#2c3640",
11
- "mark": "#c9651d",
12
- "markBright": "#e0832a",
13
- "markDeep": "#9a4a16",
14
- "toneDeep": "#14242c",
15
- "tone": "#2c4953",
16
- "toneBright": "#4d7680",
17
- "tonePale": "#8fb6bd",
18
- "green": "#86a87f",
19
- "red": "#c05a3a",
20
- "yellow": "#c9651d",
21
- "violet": "#a487ba",
22
- "link": "#5f93b0",
23
- "dim": "#687074",
24
- "select": "#2c4953",
25
- "okBg": "#2a3433",
26
- "errBg": "#352a2a"
5
+ "bg": "#141210",
6
+ "surface": "#1c1916",
7
+ "raised": "#24201c",
8
+ "text": "#eae4d5",
9
+ "muted": "#9e9585",
10
+ "line": "#2e2924",
11
+ "mark": "#cf663d",
12
+ "markBright": "#eb7c52",
13
+ "markDeep": "#ad4c24",
14
+ "toneDeep": "#0e0d0b",
15
+ "tone": "#5c4a3e",
16
+ "toneBright": "#8c7362",
17
+ "tonePale": "#b5a08d",
18
+ "green": "#68aa80",
19
+ "red": "#cf663d",
20
+ "yellow": "#cca047",
21
+ "violet": "#a88bb5",
22
+ "link": "#6ea3cc",
23
+ "dim": "#736b5e",
24
+ "select": "#332c25",
25
+ "okBg": "#1b2920",
26
+ "errBg": "#331c19"
27
27
  },
28
28
  "colors": {
29
29
  "accent": "mark",
@@ -65,15 +65,15 @@
65
65
  "toolDiffAdded": "green",
66
66
  "toolDiffRemoved": "red",
67
67
  "toolDiffContext": "muted",
68
- "syntaxComment": "#97a0a4",
69
- "syntaxKeyword": "#c9651d",
70
- "syntaxFunction": "#5f93b0",
71
- "syntaxVariable": "#f1efe9",
72
- "syntaxString": "#86a87f",
73
- "syntaxNumber": "#a487ba",
74
- "syntaxType": "#5f97a0",
75
- "syntaxOperator": "#aeb6b8",
76
- "syntaxPunctuation": "#8a9296",
68
+ "syntaxComment": "#736b5e",
69
+ "syntaxKeyword": "#cf663d",
70
+ "syntaxFunction": "#6ea3cc",
71
+ "syntaxVariable": "#eae4d5",
72
+ "syntaxString": "#68aa80",
73
+ "syntaxNumber": "#a88bb5",
74
+ "syntaxType": "#8c7362",
75
+ "syntaxOperator": "#9e9585",
76
+ "syntaxPunctuation": "#9e9585",
77
77
  "thinkingOff": "line",
78
78
  "thinkingMinimal": "dim",
79
79
  "thinkingLow": "toneBright",
@@ -84,8 +84,8 @@
84
84
  "bashMode": "mark"
85
85
  },
86
86
  "export": {
87
- "pageBg": "#12161b",
88
- "cardBg": "#1b2127",
89
- "infoBg": "#232b32"
87
+ "pageBg": "#141210",
88
+ "cardBg": "#1c1916",
89
+ "infoBg": "#24201c"
90
90
  }
91
91
  }