tau-coding-agent 0.1.0 → 0.1.2

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 CHANGED
@@ -2,41 +2,61 @@
2
2
 
3
3
  Coding-focused Tau package for [Pi](https://pi.dev).
4
4
 
5
+ Tau turns Pi's minimal core into an opinionated, polished coding-agent setup with web search, code review workflows, sandboxing, local memory, usage reporting, and a curated skill set for day-to-day software work.
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
8
10
  pi install npm:tau-coding-agent
9
11
  ```
10
12
 
11
- ## Includes
12
-
13
- Extensions:
14
-
15
- - `answer`
16
- - `branch-term`
17
- - `btw`
18
- - `ghostty`
19
- - `git-diff-stats`
20
- - `git-pr-status`
21
- - `insights`
22
- - `interlude`
23
- - `loop`
24
- - `memory`
25
- - `notify`
26
- - `openai-fast`
27
- - `openai-verbosity`
28
- - `review`
29
- - `sandbox`
30
- - `usage`
31
- - `websearch`
32
- - `worktree`
33
-
34
- Skills:
35
-
36
- - `browser-tools`
37
- - `git-clean-history`
38
- - `git-commit`
39
- - `oracle`
40
- - `sentry`
41
- - `update-changelog`
42
- - `web-design`
13
+ Project-local install lets a repository pin Tau for everyone working on it:
14
+
15
+ ```bash
16
+ pi install -l npm:tau-coding-agent
17
+ ```
18
+
19
+ ## Extensions
20
+
21
+ | Extension | Command | Description |
22
+ | ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
23
+ | `answer` | `/answer` | Extract and interactively answer agent questions. |
24
+ | `branch-term` | `/branch` | Open a new terminal on the current session's git branch. |
25
+ | `btw` | `/btw` | Run a one-off side request with read-only tools and no context persistence. |
26
+ | `ghostty` | — | Ghostty tab title enhancements while the agent is working, waiting, or idle. |
27
+ | `git-diff-stats` | — | Status bar diff stats for local changes in the current repo. |
28
+ | `git-pr-status` | — | Status bar PR number and link for the current branch. |
29
+ | `insights` | `/insights` | Analyze Pi sessions and suggest reusable instructions, templates, skills, and extensions. |
30
+ | `interlude` | `alt+x` | Stash the current message draft, send one interlude message, then restore the draft. |
31
+ | `loop` | `/loop` | Repeat a prompt until the agent signals success. |
32
+ | `memory` | `/memory` | Opt-in project-local memory for learning and continuity across sessions. |
33
+ | `notify` | — | Terminal notification when the agent is waiting for input. |
34
+ | `openai-fast` | `/fast` | Toggle priority service tier for supported OpenAI models. |
35
+ | `openai-verbosity` | `/verbosity` | Set verbosity for supported OpenAI models. |
36
+ | `review` | `/review`, `/triage` | Multi-focus review and PR feedback triage for PRs, branches, commits, and local changes, with integrated follow-up fixes. |
37
+ | `sandbox` | `/sandbox` | OS-level sandboxing for bash commands with runtime overrides. |
38
+ | `usage` | `/usage` | Historical provider usage breakdown with all-provider history and live quota snapshots. |
39
+ | `websearch` | — | Web search via Gemini, OpenAI, or Claude, leveraging Pi or browser session credentials. |
40
+ | `worktree` | `/worktree` | Create, list, and archive git worktrees, optionally opening them in a new terminal or tmux pane. |
41
+
42
+ ## Skills
43
+
44
+ | Skill | Description |
45
+ | ------------------- | ---------------------------------------------------------------------------------- |
46
+ | `browser-tools` | Interactive browser automation via Chrome DevTools Protocol. |
47
+ | `git-clean-history` | Reimplement a branch on a fresh branch off `main` with a clean commit history. |
48
+ | `git-commit` | Tidy, focused commits with clear rationale in messages. |
49
+ | `oracle` | Second opinion from another LLM for debugging, refactors, design, or code reviews. |
50
+ | `sentry` | Fetch and analyze Sentry issues, events, transactions, and logs. |
51
+ | `update-changelog` | Update CHANGELOG.md following Keep a Changelog. |
52
+ | `web-design` | Distinctive, production-ready web interfaces. |
53
+
54
+ ## Themes
55
+
56
+ | Theme | Description |
57
+ | ---------- | ------------------------------------------------------------- |
58
+ | `tau-dark` | Pi's official dark theme with a calmer, more cohesive polish. |
59
+
60
+ ## Agent configuration
61
+
62
+ Tau does not include agent configuration; those files are highly personal. Configure Pi with your own `AGENTS.md`, `settings.json`, sandbox config, and model preferences.
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { complete, type Model, type Api, type UserMessage } from "@mariozechner/pi-ai";
14
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
14
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
15
15
  import { BorderedLoader } from "@mariozechner/pi-coding-agent";
16
16
  import {
17
17
  type Component,
@@ -464,138 +464,131 @@ class QnAComponent implements Component {
464
464
  }
465
465
 
466
466
  export default function (pi: ExtensionAPI) {
467
- const answerHandler = async (ctx: ExtensionContext) => {
468
- if (!ctx.hasUI) {
469
- ctx.ui.notify("answer requires interactive mode", "error");
470
- return;
471
- }
467
+ pi.registerCommand("answer", {
468
+ description: "Extract questions from last assistant message into interactive Q&A",
469
+ handler: async (_args, ctx) => {
470
+ if (!ctx.hasUI) {
471
+ ctx.ui.notify("answer requires interactive mode", "error");
472
+ return;
473
+ }
472
474
 
473
- if (!ctx.model) {
474
- ctx.ui.notify("No model selected", "error");
475
- return;
476
- }
475
+ if (!ctx.model) {
476
+ ctx.ui.notify("No model selected", "error");
477
+ return;
478
+ }
477
479
 
478
- // Find the last assistant message on the current branch
479
- const branch = ctx.sessionManager.getBranch();
480
- let lastAssistantText: string | undefined;
481
-
482
- for (let i = branch.length - 1; i >= 0; i--) {
483
- const entry = branch[i];
484
- if (entry.type === "message") {
485
- const msg = entry.message;
486
- if ("role" in msg && msg.role === "assistant") {
487
- if (msg.stopReason !== "stop") {
488
- ctx.ui.notify(`Last assistant message incomplete (${msg.stopReason})`, "error");
489
- return;
490
- }
491
- const textParts = msg.content
492
- .filter((c): c is { type: "text"; text: string } => c.type === "text")
493
- .map((c) => c.text);
494
- if (textParts.length > 0) {
495
- lastAssistantText = textParts.join("\n");
496
- break;
480
+ // Find the last assistant message on the current branch
481
+ const branch = ctx.sessionManager.getBranch();
482
+ let lastAssistantText: string | undefined;
483
+
484
+ for (let i = branch.length - 1; i >= 0; i--) {
485
+ const entry = branch[i];
486
+ if (entry.type === "message") {
487
+ const msg = entry.message;
488
+ if ("role" in msg && msg.role === "assistant") {
489
+ if (msg.stopReason !== "stop") {
490
+ ctx.ui.notify(`Last assistant message incomplete (${msg.stopReason})`, "error");
491
+ return;
492
+ }
493
+ const textParts = msg.content
494
+ .filter((c): c is { type: "text"; text: string } => c.type === "text")
495
+ .map((c) => c.text);
496
+ if (textParts.length > 0) {
497
+ lastAssistantText = textParts.join("\n");
498
+ break;
499
+ }
497
500
  }
498
501
  }
499
502
  }
500
- }
501
503
 
502
- if (!lastAssistantText) {
503
- ctx.ui.notify("No assistant messages found", "error");
504
- return;
505
- }
506
-
507
- // Select the best model for extraction (prefer a fast model in the current family).
508
- const extractionSelection = await selectExtractionModel(ctx.model, ctx.modelRegistry);
509
- if (!extractionSelection) {
510
- ctx.ui.notify("No configured Pi model is available for question extraction", "error");
511
- return;
512
- }
504
+ if (!lastAssistantText) {
505
+ ctx.ui.notify("No assistant messages found", "error");
506
+ return;
507
+ }
513
508
 
514
- // Run extraction with loader UI
515
- const extractionResult = await ctx.ui.custom<ExtractionResult | null>(
516
- (tui, theme, _kb, done) => {
517
- const loader = new BorderedLoader(
518
- tui,
519
- theme,
520
- `Extracting questions using ${extractionSelection.model.id}...`,
521
- );
522
- loader.onAbort = () => done(null);
523
-
524
- const doExtract = async () => {
525
- const userMessage: UserMessage = {
526
- role: "user",
527
- content: [{ type: "text", text: lastAssistantText! }],
528
- timestamp: Date.now(),
529
- };
509
+ // Select the best model for extraction (prefer a fast model in the current family).
510
+ const extractionSelection = await selectExtractionModel(ctx.model, ctx.modelRegistry);
511
+ if (!extractionSelection) {
512
+ ctx.ui.notify("No configured Pi model is available for question extraction", "error");
513
+ return;
514
+ }
530
515
 
531
- const response = await complete(
532
- extractionSelection.model,
533
- { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
534
- {
535
- apiKey: extractionSelection.apiKey,
536
- headers: extractionSelection.headers,
537
- signal: loader.signal,
538
- },
516
+ // Run extraction with loader UI
517
+ const extractionResult = await ctx.ui.custom<ExtractionResult | null>(
518
+ (tui, theme, _kb, done) => {
519
+ const loader = new BorderedLoader(
520
+ tui,
521
+ theme,
522
+ `Extracting questions using ${extractionSelection.model.id}...`,
539
523
  );
524
+ loader.onAbort = () => done(null);
525
+
526
+ const doExtract = async () => {
527
+ const userMessage: UserMessage = {
528
+ role: "user",
529
+ content: [{ type: "text", text: lastAssistantText! }],
530
+ timestamp: Date.now(),
531
+ };
532
+
533
+ const response = await complete(
534
+ extractionSelection.model,
535
+ { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
536
+ {
537
+ apiKey: extractionSelection.apiKey,
538
+ headers: extractionSelection.headers,
539
+ signal: loader.signal,
540
+ },
541
+ );
542
+
543
+ if (response.stopReason === "aborted") {
544
+ return null;
545
+ }
546
+
547
+ const responseText = response.content
548
+ .filter((c): c is { type: "text"; text: string } => c.type === "text")
549
+ .map((c) => c.text)
550
+ .join("\n");
551
+
552
+ return parseExtractionResult(responseText);
553
+ };
540
554
 
541
- if (response.stopReason === "aborted") {
542
- return null;
543
- }
544
-
545
- const responseText = response.content
546
- .filter((c): c is { type: "text"; text: string } => c.type === "text")
547
- .map((c) => c.text)
548
- .join("\n");
549
-
550
- return parseExtractionResult(responseText);
551
- };
552
-
553
- doExtract()
554
- .then(done)
555
- .catch(() => done(null));
556
-
557
- return loader;
558
- },
559
- );
560
-
561
- if (extractionResult === null) {
562
- ctx.ui.notify("Cancelled", "info");
563
- return;
564
- }
555
+ doExtract()
556
+ .then(done)
557
+ .catch(() => done(null));
565
558
 
566
- if (extractionResult.questions.length === 0) {
567
- ctx.ui.notify("No questions found in the last message", "info");
568
- return;
569
- }
559
+ return loader;
560
+ },
561
+ );
570
562
 
571
- // Show the Q&A component
572
- const answersResult = await ctx.ui.custom<string | null>((tui, _theme, _kb, done) => {
573
- return new QnAComponent(extractionResult.questions, tui, done);
574
- });
563
+ if (extractionResult === null) {
564
+ ctx.ui.notify("Cancelled", "info");
565
+ return;
566
+ }
575
567
 
576
- if (answersResult === null) {
577
- ctx.ui.notify("Cancelled", "info");
578
- return;
579
- }
568
+ if (extractionResult.questions.length === 0) {
569
+ ctx.ui.notify("No questions found in the last message", "info");
570
+ return;
571
+ }
580
572
 
581
- // Send the answers directly as a message and trigger a turn
582
- pi.sendMessage(
583
- {
584
- customType: "answers",
585
- content: "Here are my answers to your questions:\n\n" + answersResult,
586
- display: true,
587
- },
588
- { triggerTurn: true },
589
- );
590
- };
573
+ // Show the Q&A component
574
+ const answersResult = await ctx.ui.custom<string | null>((tui, _theme, _kb, done) => {
575
+ return new QnAComponent(extractionResult.questions, tui, done);
576
+ });
591
577
 
592
- pi.registerCommand("answer", {
593
- description: "Extract questions from last assistant message into interactive Q&A",
594
- handler: (_args, ctx) => answerHandler(ctx),
595
- });
578
+ if (answersResult === null) {
579
+ ctx.ui.notify("Cancelled", "info");
580
+ return;
581
+ }
596
582
 
597
- pi.registerShortcut("ctrl+.", {
598
- description: "Extract and answer questions",
599
- handler: answerHandler,
583
+ // Send the answers directly as a message and trigger a turn
584
+ pi.sendMessage(
585
+ {
586
+ customType: "answers",
587
+ content: "Here are my answers to your questions:\n\n" + answersResult,
588
+ display: true,
589
+ },
590
+ { triggerTurn: true },
591
+ );
592
+ },
600
593
  });
601
594
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tau-coding-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Coding-focused Tau distribution for Pi",
5
5
  "keywords": [
6
6
  "pi-extension",
@@ -61,11 +61,15 @@
61
61
  "skills/sentry",
62
62
  "skills/update-changelog",
63
63
  "skills/web-design"
64
+ ],
65
+ "themes": [
66
+ "themes/tau-dark.json"
64
67
  ]
65
68
  },
66
69
  "files": [
67
70
  "extensions",
68
71
  "skills",
72
+ "themes",
69
73
  "README.md",
70
74
  "LICENSE"
71
75
  ]
@@ -130,10 +130,31 @@ done
130
130
 
131
131
  include_patterns=()
132
132
  exclude_patterns=()
133
+ explicit_files=()
134
+
135
+ add_pattern() {
136
+ local is_exclude="$1"
137
+ local pattern="$2"
138
+ if [[ "$is_exclude" -eq 1 ]]; then
139
+ exclude_patterns+=("$pattern")
140
+ else
141
+ include_patterns+=("$pattern")
142
+ fi
143
+ }
144
+
145
+ add_exact_file() {
146
+ local is_exclude="$1"
147
+ local file="$2"
148
+ if [[ "$is_exclude" -eq 1 ]]; then
149
+ exclude_patterns+=("$file")
150
+ else
151
+ explicit_files+=("$file")
152
+ fi
153
+ }
133
154
 
134
155
  for sel in "${selectors[@]}"; do
135
156
  is_exclude=0
136
- if [[ "$sel" == !* ]]; then
157
+ if [[ "${sel:0:1}" == "!" ]]; then
137
158
  is_exclude=1
138
159
  sel="${sel:1}"
139
160
  fi
@@ -148,69 +169,96 @@ for sel in "${selectors[@]}"; do
148
169
  if [[ "$sel" == "${ROOT_DIR}/"* ]]; then
149
170
  sel="${sel#"${ROOT_DIR}/"}"
150
171
  else
151
- echo "error: absolute paths must be under repo root: ${sel}" >&2
152
- exit 2
172
+ if [[ "$is_exclude" -eq 1 ]]; then
173
+ add_pattern "$is_exclude" "$sel"
174
+ continue
175
+ fi
176
+ if [[ ! -f "$sel" ]]; then
177
+ echo "error: absolute paths outside repo must name an existing file: ${sel}" >&2
178
+ exit 2
179
+ fi
180
+ add_exact_file "$is_exclude" "$sel"
181
+ continue
153
182
  fi
154
183
  fi
155
184
 
156
- if [[ -e "$sel" && -d "$sel" ]] && ! has_glob_chars "$sel"; then
157
- sel="${sel}/**"
185
+ sel="${sel#./}"
186
+
187
+ if [[ -f "$sel" ]]; then
188
+ add_exact_file "$is_exclude" "$sel"
189
+ continue
158
190
  fi
159
191
 
160
- if [[ $is_exclude -eq 1 ]]; then
161
- exclude_patterns+=("$sel")
162
- else
163
- include_patterns+=("$sel")
192
+ if [[ -d "$sel" ]] && ! has_glob_chars "$sel"; then
193
+ sel="${sel}/**"
164
194
  fi
195
+
196
+ add_pattern "$is_exclude" "$sel"
165
197
  done
166
198
 
167
- if [[ "${#include_patterns[@]}" -eq 0 ]]; then
199
+ if [[ "${#include_patterns[@]}" -eq 0 && "${#explicit_files[@]}" -eq 0 ]]; then
168
200
  echo "error: no include patterns provided (need at least one non-! --file)" >&2
169
201
  exit 2
170
202
  fi
171
203
 
172
204
  all_files=()
173
- if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
174
- while IFS= read -r f; do
175
- [[ -n "$f" ]] && all_files+=("$f")
176
- done < <(git ls-files -co --exclude-standard)
177
- else
178
- while IFS= read -r f; do
179
- f="${f#./}"
180
- [[ -n "$f" ]] && all_files+=("$f")
181
- done < <(find . -type f -print)
205
+ if [[ "${#include_patterns[@]}" -gt 0 ]]; then
206
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
207
+ while IFS= read -r f; do
208
+ [[ -n "$f" ]] && all_files+=("$f")
209
+ done < <(git ls-files -co --exclude-standard)
210
+ else
211
+ while IFS= read -r f; do
212
+ f="${f#./}"
213
+ [[ -n "$f" ]] && all_files+=("$f")
214
+ done < <(find . -type f -print)
215
+ fi
182
216
  fi
183
217
 
184
218
  selected=()
185
219
  skipped=()
186
220
 
187
- for rel in "${all_files[@]}"; do
188
- [[ -z "$rel" ]] && continue
189
- path_has_ignored_dir "$rel" && continue
221
+ select_file() {
222
+ local file="$1"
223
+ local size=""
190
224
 
191
- if ! matches_any_pattern "$rel" "${include_patterns[@]}"; then
192
- continue
193
- fi
225
+ [[ -z "$file" ]] && return 0
194
226
 
195
- if [[ "${#exclude_patterns[@]}" -gt 0 ]] && matches_any_pattern "$rel" "${exclude_patterns[@]}"; then
196
- continue
227
+ if [[ "${#exclude_patterns[@]}" -gt 0 ]] && matches_any_pattern "$file" "${exclude_patterns[@]}"; then
228
+ return 0
197
229
  fi
198
230
 
199
- if [[ ! -f "$rel" ]]; then
200
- continue
231
+ if [[ ! -f "$file" ]]; then
232
+ return 0
201
233
  fi
202
234
 
203
- size="$(file_size_bytes "$rel")"
235
+ size="$(file_size_bytes "$file")"
204
236
  if [[ "$size" -gt "$MAX_BYTES" ]]; then
205
- skipped+=("${rel} (too large: ${size} bytes)")
237
+ skipped+=("${file} (too large: ${size} bytes)")
238
+ return 0
239
+ fi
240
+
241
+ selected+=("$file")
242
+ }
243
+
244
+ for file in "${explicit_files[@]}"; do
245
+ select_file "$file"
246
+ done
247
+
248
+ for rel in "${all_files[@]}"; do
249
+ [[ -z "$rel" ]] && continue
250
+
251
+ path_has_ignored_dir "$rel" && continue
252
+
253
+ if ! matches_any_pattern "$rel" "${include_patterns[@]}"; then
206
254
  continue
207
255
  fi
208
256
 
209
- selected+=("$rel")
257
+ select_file "$rel"
210
258
  done
211
259
 
212
260
  if [[ "$DRY_RUN" -eq 1 ]]; then
213
- printf '%s\n' "${selected[@]}" | LC_ALL=C sort
261
+ printf '%s\n' "${selected[@]}" | LC_ALL=C sort -u
214
262
  if [[ "${#skipped[@]}" -gt 0 ]]; then
215
263
  printf '\nSkipped:\n' >&2
216
264
  printf '%s\n' "${skipped[@]}" >&2
@@ -219,7 +267,7 @@ if [[ "$DRY_RUN" -eq 1 ]]; then
219
267
  fi
220
268
 
221
269
  printf '# Task\n\n%s\n\n# Files\n' "$(trim "$PROMPT")"
222
- printf '%s\n' "${selected[@]}" | LC_ALL=C sort | while IFS= read -r rel; do
270
+ printf '%s\n' "${selected[@]}" | LC_ALL=C sort -u | while IFS= read -r rel; do
223
271
  [[ -z "$rel" ]] && continue
224
272
  printf '\n## %s\n\n```\n' "$rel"
225
273
  cat "$rel"
@@ -0,0 +1,85 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "tau-dark",
4
+ "vars": {
5
+ "accent": "#8abeb7",
6
+ "blue": "#5f87af",
7
+ "green": "#b5bd68",
8
+ "red": "#cc6666",
9
+ "yellow": "#f0c674",
10
+ "gray": "#808080",
11
+ "dimGray": "#666666",
12
+ "darkGray": "#505050",
13
+ "heading": "#DCDCAA",
14
+ "selectedBg": "#3a3a4a",
15
+ "userMsgBg": "#343541",
16
+ "toolPendingBg": "#282832",
17
+ "toolSuccessBg": "#283228",
18
+ "toolErrorBg": "#3c2828",
19
+ "customMsgBg": "#2d2838"
20
+ },
21
+ "colors": {
22
+ "accent": "accent",
23
+ "border": "blue",
24
+ "borderAccent": "accent",
25
+ "borderMuted": "darkGray",
26
+ "success": "green",
27
+ "error": "red",
28
+ "warning": "yellow",
29
+ "muted": "gray",
30
+ "dim": "dimGray",
31
+ "text": "",
32
+ "thinkingText": "gray",
33
+
34
+ "selectedBg": "selectedBg",
35
+ "userMessageBg": "userMsgBg",
36
+ "userMessageText": "",
37
+ "customMessageBg": "customMsgBg",
38
+ "customMessageText": "",
39
+ "customMessageLabel": "#9575cd",
40
+ "toolPendingBg": "toolPendingBg",
41
+ "toolSuccessBg": "toolSuccessBg",
42
+ "toolErrorBg": "toolErrorBg",
43
+ "toolTitle": "",
44
+ "toolOutput": "gray",
45
+
46
+ "mdHeading": "heading",
47
+ "mdLink": "#81a2be",
48
+ "mdLinkUrl": "dimGray",
49
+ "mdCode": "accent",
50
+ "mdCodeBlock": "green",
51
+ "mdCodeBlockBorder": "gray",
52
+ "mdQuote": "gray",
53
+ "mdQuoteBorder": "gray",
54
+ "mdHr": "gray",
55
+ "mdListBullet": "accent",
56
+
57
+ "toolDiffAdded": "green",
58
+ "toolDiffRemoved": "red",
59
+ "toolDiffContext": "gray",
60
+
61
+ "syntaxComment": "#6A9955",
62
+ "syntaxKeyword": "#569CD6",
63
+ "syntaxFunction": "#DCDCAA",
64
+ "syntaxVariable": "#9CDCFE",
65
+ "syntaxString": "#CE9178",
66
+ "syntaxNumber": "#B5CEA8",
67
+ "syntaxType": "#4EC9B0",
68
+ "syntaxOperator": "#D4D4D4",
69
+ "syntaxPunctuation": "#D4D4D4",
70
+
71
+ "thinkingOff": "darkGray",
72
+ "thinkingMinimal": "dimGray",
73
+ "thinkingLow": "#657B83",
74
+ "thinkingMedium": "#6f9bb3",
75
+ "thinkingHigh": "#78a8a1",
76
+ "thinkingXhigh": "accent",
77
+
78
+ "bashMode": "green"
79
+ },
80
+ "export": {
81
+ "pageBg": "#18181e",
82
+ "cardBg": "#1e1e24",
83
+ "infoBg": "#3c3728"
84
+ }
85
+ }