osdy-pi 1.2.0 → 1.3.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.
Files changed (44) hide show
  1. package/README.md +160 -30
  2. package/bin/osdy-pi.mjs +29 -6
  3. package/bin/osdy.mjs +43 -0
  4. package/extensions/osdy-pi/account-profiles.test.ts +111 -97
  5. package/extensions/osdy-pi/account-profiles.ts +91 -162
  6. package/extensions/osdy-pi/animation.test.ts +84 -0
  7. package/extensions/osdy-pi/animation.ts +17 -8
  8. package/extensions/osdy-pi/codex-usage-ui.test.ts +1145 -0
  9. package/extensions/osdy-pi/codex-usage-ui.ts +728 -0
  10. package/extensions/osdy-pi/codex-usage.test.ts +276 -0
  11. package/extensions/osdy-pi/codex-usage.ts +276 -0
  12. package/extensions/osdy-pi/constants.ts +134 -34
  13. package/extensions/osdy-pi/context-usage-ui.test.ts +168 -0
  14. package/extensions/osdy-pi/context-usage-ui.ts +60 -0
  15. package/extensions/osdy-pi/editor-settings.test.ts +121 -23
  16. package/extensions/osdy-pi/editor-settings.ts +39 -3
  17. package/extensions/osdy-pi/metrics.test.ts +95 -0
  18. package/extensions/osdy-pi/metrics.ts +39 -1
  19. package/extensions/osdy-pi/profile-label.ts +2 -2
  20. package/extensions/osdy-pi/runtime-helpers.test.ts +220 -1
  21. package/extensions/osdy-pi/runtime-helpers.ts +42 -19
  22. package/extensions/osdy-pi/runtime.test.ts +355 -7
  23. package/extensions/osdy-pi/runtime.ts +344 -66
  24. package/extensions/osdy-pi/types.ts +26 -1
  25. package/extensions/osdy-pi/ui.test.ts +128 -0
  26. package/extensions/osdy-pi/ui.ts +57 -40
  27. package/package.json +5 -2
  28. package/scripts/osdy-pi-account-profiles.mjs +521 -76
  29. package/scripts/osdy-pi-gentle-coexistence.mjs +200 -0
  30. package/scripts/osdy-pi-profile-setup.mjs +175 -0
  31. package/themes/osdy-pi-catppuccin-frappe.json +6 -2
  32. package/themes/osdy-pi-catppuccin-latte.json +6 -2
  33. package/themes/osdy-pi-catppuccin-macchiato.json +6 -2
  34. package/themes/osdy-pi-catppuccin-mocha.json +6 -2
  35. package/themes/osdy-pi-dark.json +6 -2
  36. package/themes/osdy-pi-dracula.json +6 -2
  37. package/themes/osdy-pi-kanagawa-dragon.json +6 -2
  38. package/themes/osdy-pi-kanagawa-lotus.json +6 -2
  39. package/themes/osdy-pi-kanagawa-wave.json +6 -2
  40. package/themes/osdy-pi-lucent-orange.json +6 -2
  41. package/themes/osdy-pi-matrix.json +6 -2
  42. package/themes/osdy-pi-new.json +6 -2
  43. package/themes/osdy-pi-sexy.json +6 -2
  44. package/themes/osdy-pi-tokyo-night.json +6 -2
@@ -0,0 +1,200 @@
1
+ import {
2
+ access,
3
+ mkdir,
4
+ readFile,
5
+ rename,
6
+ stat,
7
+ writeFile,
8
+ } from "node:fs/promises";
9
+ import { constants } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { dirname, isAbsolute, join, resolve } from "node:path";
12
+
13
+ const GENTLE_PACKAGE_NAME = "gentle-pi";
14
+ const REQUIRED_EXTENSIONS = [
15
+ "gentle-todo.ts",
16
+ "ask-user-question.ts",
17
+ "gentle-agents.ts",
18
+ ];
19
+ const EXCLUDED_EXTENSIONS = REQUIRED_EXTENSIONS.map(
20
+ (extension) => `-extensions/${extension}`,
21
+ );
22
+ const OSDY_ONLY_PACKAGE_SOURCES = [
23
+ "npm:@juicesharp/rpiv-ask-user-question",
24
+ "npm:pi-subagents-j0k3r",
25
+ ];
26
+
27
+ const nodeFileSystem = { access, mkdir, readFile, rename, stat, writeFile };
28
+
29
+ function isRecord(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+
33
+ function isGentleNpmSource(value) {
34
+ return typeof value === "string" && /^npm:gentle-pi(?:@[^\s]+)?$/i.test(value);
35
+ }
36
+
37
+ function isOsdyOnlyPackageSource(value) {
38
+ return (
39
+ typeof value === "string" &&
40
+ OSDY_ONLY_PACKAGE_SOURCES.includes(value.trim().toLowerCase())
41
+ );
42
+ }
43
+
44
+ function isOsdyPackageSource(value) {
45
+ return (
46
+ typeof value === "string" &&
47
+ /^(?:git:github\.com\/osdyortiz\/osdy-pi|npm:osdy-pi(?:@[^\s]+)?)$/i.test(
48
+ value.trim(),
49
+ )
50
+ );
51
+ }
52
+
53
+ function packageSource(entry) {
54
+ if (typeof entry === "string") return entry;
55
+ if (isRecord(entry) && typeof entry.source === "string") return entry.source;
56
+ return undefined;
57
+ }
58
+
59
+ function canonicalPackage(sourcePath) {
60
+ return {
61
+ source: sourcePath,
62
+ extensions: [...EXCLUDED_EXTENSIONS],
63
+ themes: [],
64
+ };
65
+ }
66
+
67
+ function canonicalOsdyOnlyPackages() {
68
+ return [...OSDY_ONLY_PACKAGE_SOURCES];
69
+ }
70
+
71
+ export function getPiSettingsPath(env = process.env, home = homedir()) {
72
+ const configuredDir = env.PI_CODING_AGENT_DIR?.trim();
73
+ return join(configuredDir || join(home, ".pi", "agent"), "settings.json");
74
+ }
75
+
76
+ export function reconcileGentlePackages(settings, sourcePath) {
77
+ if (!isRecord(settings))
78
+ throw new Error("settings.json must contain an object.");
79
+ if (settings.packages !== undefined && !Array.isArray(settings.packages))
80
+ throw new Error("settings.json packages must be an array.");
81
+
82
+ const packages = settings.packages ?? [];
83
+ const retained = packages.filter((entry) => {
84
+ const source = packageSource(entry);
85
+ return (
86
+ source !== sourcePath &&
87
+ !isGentleNpmSource(source) &&
88
+ !isOsdyOnlyPackageSource(source)
89
+ );
90
+ });
91
+ const canonicalPackages = [
92
+ canonicalPackage(sourcePath),
93
+ ...canonicalOsdyOnlyPackages(),
94
+ ];
95
+ const firstOsdyIndex = retained.findIndex((entry) =>
96
+ isOsdyPackageSource(packageSource(entry)),
97
+ );
98
+ const nextPackages = [...retained];
99
+ if (firstOsdyIndex === -1) nextPackages.push(...canonicalPackages);
100
+ else nextPackages.splice(firstOsdyIndex, 0, ...canonicalPackages);
101
+ const nextSettings = {
102
+ ...settings,
103
+ packages: nextPackages,
104
+ };
105
+ const changed = JSON.stringify(nextSettings) !== JSON.stringify(settings);
106
+ return { changed, settings: changed ? nextSettings : settings };
107
+ }
108
+
109
+ async function validateGentleSource(sourcePath, fileSystem) {
110
+ if (typeof sourcePath !== "string" || !isAbsolute(sourcePath))
111
+ throw new Error("Gentle source path must be an absolute path.");
112
+
113
+ const resolvedSource = resolve(sourcePath);
114
+ let sourceStats;
115
+ try {
116
+ sourceStats = await fileSystem.stat(resolvedSource);
117
+ } catch {
118
+ throw new Error("Gentle source path must be an existing directory.");
119
+ }
120
+ if (!sourceStats.isDirectory())
121
+ throw new Error("Gentle source path must be an existing directory.");
122
+
123
+ const packagePath = join(resolvedSource, "package.json");
124
+ let packageContents;
125
+ try {
126
+ await fileSystem.access(packagePath, constants.R_OK);
127
+ packageContents = await fileSystem.readFile(packagePath, "utf8");
128
+ } catch {
129
+ throw new Error("Gentle source package.json must be readable.");
130
+ }
131
+ let packageJson;
132
+ try {
133
+ packageJson = JSON.parse(packageContents);
134
+ } catch {
135
+ throw new Error("Gentle source package.json is not valid JSON.");
136
+ }
137
+ if (!isRecord(packageJson) || packageJson.name !== GENTLE_PACKAGE_NAME)
138
+ throw new Error('Gentle source package name must be exactly "gentle-pi".');
139
+
140
+ for (const extension of REQUIRED_EXTENSIONS) {
141
+ const extensionPath = join(resolvedSource, "extensions", extension);
142
+ try {
143
+ await fileSystem.access(extensionPath, constants.R_OK);
144
+ const extensionStats = await fileSystem.stat(extensionPath);
145
+ if (!extensionStats.isFile()) throw new Error("not a file");
146
+ } catch {
147
+ throw new Error(`Gentle extension is not readable: extensions/${extension}`);
148
+ }
149
+ }
150
+ return resolvedSource;
151
+ }
152
+
153
+ async function loadSettings(settingsPath, fileSystem) {
154
+ try {
155
+ const contents = await fileSystem.readFile(settingsPath, "utf8");
156
+ try {
157
+ return { exists: true, settings: JSON.parse(contents) };
158
+ } catch {
159
+ throw new Error("settings.json is not valid JSON.");
160
+ }
161
+ } catch (error) {
162
+ if (error && typeof error === "object" && error.code === "ENOENT")
163
+ return { exists: false, settings: {} };
164
+ throw error;
165
+ }
166
+ }
167
+
168
+ async function writeSettingsAtomically(settingsPath, settings, fileSystem) {
169
+ const directory = dirname(settingsPath);
170
+ const temporaryPath = `${settingsPath}.${process.pid}.${Date.now()}.tmp`;
171
+ await fileSystem.mkdir(directory, { recursive: true });
172
+ await fileSystem.writeFile(
173
+ temporaryPath,
174
+ `${JSON.stringify(settings, null, 2)}\n`,
175
+ "utf8",
176
+ );
177
+ await fileSystem.rename(temporaryPath, settingsPath);
178
+ }
179
+
180
+ export async function configureGentleCoexistence(sourcePath, options = {}) {
181
+ const fileSystem = { ...nodeFileSystem, ...options.fileSystem };
182
+ const settingsPath =
183
+ options.settingsPath ?? getPiSettingsPath(options.env, options.home);
184
+ const selectedSource = await validateGentleSource(sourcePath, fileSystem);
185
+ const loaded = await loadSettings(settingsPath, fileSystem);
186
+ const reconciled = reconcileGentlePackages(loaded.settings, selectedSource);
187
+ if (!reconciled.changed)
188
+ return {
189
+ status: "already configured",
190
+ settingsPath,
191
+ sourcePath: selectedSource,
192
+ };
193
+
194
+ await writeSettingsAtomically(settingsPath, reconciled.settings, fileSystem);
195
+ return {
196
+ status: loaded.exists ? "updated" : "configured",
197
+ settingsPath,
198
+ sourcePath: selectedSource,
199
+ };
200
+ }
@@ -0,0 +1,175 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { access, lstat, mkdir, readFile, readdir, realpath, rename, stat, symlink, unlink, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
5
+
6
+ const GENTLE_EXTENSIONS = ["gentle-todo.ts", "ask-user-question.ts", "gentle-agents.ts"];
7
+ const ISOLATED_NAMES = new Set(["settings.json", "sessions", "pi-crash.log", "run-history.jsonl", ".DS_Store"]);
8
+ const ASK_PACKAGE = "npm:@juicesharp/rpiv-ask-user-question";
9
+ const AGENT_PACKAGE = "npm:pi-subagents-j0k3r";
10
+
11
+ function record(value) {
12
+ return value !== null && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+
15
+ function packageSource(entry) {
16
+ return typeof entry === "string" ? entry : record(entry) && typeof entry.source === "string" ? entry.source : "";
17
+ }
18
+
19
+ async function settingsFrom(path, label) {
20
+ let settings;
21
+ try {
22
+ settings = JSON.parse(await readFile(path, "utf8"));
23
+ } catch (error) {
24
+ throw new Error(`${label} settings.json must be readable valid JSON: ${error.message}`);
25
+ }
26
+ if (!record(settings) || (settings.packages !== undefined && !Array.isArray(settings.packages)))
27
+ throw new Error(`${label} settings.json must be an object with an array of packages.`);
28
+ return settings;
29
+ }
30
+
31
+ async function validatePackage(path, expected) {
32
+ if (typeof path !== "string" || !isAbsolute(path))
33
+ throw new Error(`${expected} source must be an absolute directory path.`);
34
+ const root = resolve(path);
35
+ try {
36
+ if (!(await stat(root)).isDirectory()) throw new Error("not a directory");
37
+ const manifest = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
38
+ if (!record(manifest) || manifest.name !== expected) throw new Error("wrong package name");
39
+ if (expected === "gentle-pi") {
40
+ for (const file of GENTLE_EXTENSIONS) {
41
+ const extension = join(root, "extensions", file);
42
+ if (!(await stat(extension)).isFile()) throw new Error(`${file} is not a file`);
43
+ await access(extension, constants.R_OK);
44
+ }
45
+ }
46
+ } catch (error) {
47
+ throw new Error(`${expected === "gentle-pi" ? "Gentle" : "Osdy"} source invalid at ${root}: ${error.message}`);
48
+ }
49
+ return root;
50
+ }
51
+
52
+ async function chooseGentleRoot(packages, explicitRoot) {
53
+ if (explicitRoot !== undefined) return validatePackage(explicitRoot, "gentle-pi");
54
+ const candidates = new Set();
55
+ for (const entry of packages) {
56
+ const source = packageSource(entry);
57
+ if (!isAbsolute(source)) continue;
58
+ try {
59
+ candidates.add(await validatePackage(source, "gentle-pi"));
60
+ } catch {
61
+ // Other absolute local packages are not Gentle.
62
+ }
63
+ }
64
+ if (candidates.size !== 1)
65
+ throw new Error("Gentle source must be unique in official settings; set GENTLE_PI_EXTENSION_ROOT to an absolute gentle-pi checkout.");
66
+ return [...candidates][0];
67
+ }
68
+
69
+ function reconciledSettings(official, existing, gentleRoot, osdyRoot) {
70
+ const packages = (official.packages ?? []).filter((entry) => {
71
+ const source = packageSource(entry);
72
+ const lower = source.toLowerCase();
73
+ return source !== gentleRoot && source !== osdyRoot &&
74
+ !/^npm:gentle-pi(?:@[^\s]+)?$/.test(lower) &&
75
+ !/^npm:osdy-pi(?:@[^\s]+)?$/.test(lower) &&
76
+ !/^git:github\.com\/osdyortiz\/osdy-pi(?:@[^\s]+)?$/.test(lower) &&
77
+ lower !== ASK_PACKAGE && lower !== AGENT_PACKAGE;
78
+ });
79
+ const theme = existing?.theme;
80
+ return {
81
+ ...official,
82
+ ...(typeof theme === "string" && theme ? { theme } : {}),
83
+ packages: [...packages,
84
+ { source: gentleRoot, extensions: GENTLE_EXTENSIONS.map((name) => `-extensions/${name}`), themes: [] },
85
+ ASK_PACKAGE, AGENT_PACKAGE, { source: osdyRoot },
86
+ ],
87
+ };
88
+ }
89
+
90
+ async function optionalEntry(path) {
91
+ try { return await lstat(path); }
92
+ catch (error) {
93
+ if (error.code === "ENOENT") return undefined;
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ export async function resourceLinkType(source, platform = process.platform) {
99
+ if (platform !== "win32") return undefined;
100
+ return (await stat(source)).isDirectory() ? "junction" : "file";
101
+ }
102
+
103
+ async function canonicalDestination(path) {
104
+ try {
105
+ return await realpath(path);
106
+ } catch (error) {
107
+ if (error.code !== "ENOENT") throw error;
108
+ const parent = dirname(path);
109
+ if (parent === path) throw error;
110
+ return join(await canonicalDestination(parent), basename(path));
111
+ }
112
+ }
113
+
114
+ /** Create or reconcile the Osdy-owned settings and links, without writing official Pi state. */
115
+ export async function setupOsdyProfile({ officialDir, profileDir, osdyRoot, gentleRoot }) {
116
+ for (const [name, value] of Object.entries({ officialDir, profileDir, osdyRoot })) {
117
+ if (typeof value !== "string" || !isAbsolute(value))
118
+ throw new Error(`${name} must be an absolute path.`);
119
+ }
120
+ const sourceDir = resolve(officialDir);
121
+ const targetDir = resolve(profileDir);
122
+ const canonicalSource = await canonicalDestination(sourceDir);
123
+ const canonicalTarget = await canonicalDestination(targetDir);
124
+ if (canonicalSource === canonicalTarget ||
125
+ canonicalTarget.startsWith(`${canonicalSource}${sep}`) ||
126
+ canonicalSource.startsWith(`${canonicalTarget}${sep}`))
127
+ throw new Error("Official and isolated profile directories must be separate, non-nested paths.");
128
+ const sourceSettings = join(sourceDir, "settings.json");
129
+ if (!(await optionalEntry(sourceSettings))?.isFile())
130
+ throw new Error("Official settings.json must be a regular file, not a symlink.");
131
+ const official = await settingsFrom(sourceSettings, "Official");
132
+ const gentle = await chooseGentleRoot(official.packages ?? [], gentleRoot);
133
+ const osdy = await validatePackage(osdyRoot, "osdy-pi");
134
+ const profileState = await optionalEntry(targetDir);
135
+ if (profileState && !profileState.isDirectory())
136
+ throw new Error("Isolated profile directory must not be a symlink or file.");
137
+ const targetSettings = join(targetDir, "settings.json");
138
+ const settingsState = await optionalEntry(targetSettings);
139
+ if (settingsState && !settingsState.isFile())
140
+ throw new Error("Isolated settings.json must be a regular file, not a symlink.");
141
+ const sessions = join(targetDir, "sessions");
142
+ const sessionsState = await optionalEntry(sessions);
143
+ if (sessionsState && !sessionsState.isDirectory())
144
+ throw new Error("Isolated sessions must be a directory, not a symlink.");
145
+ const existing = settingsState ? await settingsFrom(targetSettings, "Isolated") : undefined;
146
+ const next = `${JSON.stringify(reconciledSettings(official, existing, gentle, osdy), null, 2)}\n`;
147
+ const current = settingsState ? await readFile(targetSettings, "utf8") : undefined;
148
+ const names = await readdir(sourceDir);
149
+ // Validate first: an existing entry always belongs to the user; never replace it.
150
+ const links = [];
151
+ for (const name of names) {
152
+ if (ISOLATED_NAMES.has(name)) continue;
153
+ const destination = join(targetDir, name);
154
+ if (await optionalEntry(destination)) continue;
155
+ const source = join(sourceDir, name);
156
+ links.push({ destination, source, type: await resourceLinkType(source) });
157
+ }
158
+ if (!profileState) await mkdir(targetDir, { recursive: true, mode: 0o700 });
159
+ if (!sessionsState) await mkdir(sessions, { mode: 0o700 });
160
+ for (const { destination, source, type } of links) {
161
+ // Never overwrite a user entry created between validation and linking.
162
+ try { await symlink(source, destination, type); }
163
+ catch (error) { if (error.code !== "EEXIST") throw error; }
164
+ }
165
+ if (current !== next) {
166
+ const temp = join(targetDir, `.settings.${randomUUID()}.tmp`);
167
+ try {
168
+ await writeFile(temp, next, { flag: "wx", mode: 0o600 });
169
+ await rename(temp, targetSettings);
170
+ } finally {
171
+ await unlink(temp).catch(() => {});
172
+ }
173
+ }
174
+ return { status: current === next && links.length === 0 && sessionsState ? "already configured" : "configured", profileDir: targetDir };
175
+ }
@@ -45,10 +45,14 @@
45
45
  "scrollbarThumb": "overlay1",
46
46
  "searchMatchBg": "surface0",
47
47
  "searchMatchText": "text",
48
- "userMessageBg": "mantle",
49
- "userMessageText": "text",
50
48
  "customMessageBg": "surface0",
51
49
  "customMessageText": "text",
50
+ "assistantMessageBg": "surface0",
51
+ "assistantMessageAccentBg": "blue",
52
+ "assistantMessageText": "text",
53
+ "userMessageAccentBg": "#FFFFFF",
54
+ "userMessageBg": "mantle",
55
+ "userMessageText": "text",
52
56
  "customMessageLabel": "blue",
53
57
  "toolPendingBg": "mantle",
54
58
  "toolSuccessBg": "surface0",
@@ -45,10 +45,14 @@
45
45
  "scrollbarThumb": "overlay1",
46
46
  "searchMatchBg": "surface0",
47
47
  "searchMatchText": "text",
48
- "userMessageBg": "mantle",
49
- "userMessageText": "text",
50
48
  "customMessageBg": "surface0",
51
49
  "customMessageText": "text",
50
+ "assistantMessageBg": "surface0",
51
+ "assistantMessageAccentBg": "blue",
52
+ "assistantMessageText": "text",
53
+ "userMessageAccentBg": "#FFFFFF",
54
+ "userMessageBg": "mantle",
55
+ "userMessageText": "text",
52
56
  "customMessageLabel": "blue",
53
57
  "toolPendingBg": "mantle",
54
58
  "toolSuccessBg": "surface0",
@@ -45,10 +45,14 @@
45
45
  "scrollbarThumb": "overlay1",
46
46
  "searchMatchBg": "surface0",
47
47
  "searchMatchText": "text",
48
- "userMessageBg": "mantle",
49
- "userMessageText": "text",
50
48
  "customMessageBg": "surface0",
51
49
  "customMessageText": "text",
50
+ "assistantMessageBg": "surface0",
51
+ "assistantMessageAccentBg": "blue",
52
+ "assistantMessageText": "text",
53
+ "userMessageAccentBg": "#FFFFFF",
54
+ "userMessageBg": "mantle",
55
+ "userMessageText": "text",
52
56
  "customMessageLabel": "blue",
53
57
  "toolPendingBg": "mantle",
54
58
  "toolSuccessBg": "surface0",
@@ -45,10 +45,14 @@
45
45
  "scrollbarThumb": "overlay1",
46
46
  "searchMatchBg": "surface0",
47
47
  "searchMatchText": "text",
48
- "userMessageBg": "mantle",
49
- "userMessageText": "text",
50
48
  "customMessageBg": "surface0",
51
49
  "customMessageText": "text",
50
+ "assistantMessageBg": "surface0",
51
+ "assistantMessageAccentBg": "blue",
52
+ "assistantMessageText": "text",
53
+ "userMessageAccentBg": "#FFFFFF",
54
+ "userMessageBg": "mantle",
55
+ "userMessageText": "text",
52
56
  "customMessageLabel": "blue",
53
57
  "toolPendingBg": "mantle",
54
58
  "toolSuccessBg": "surface0",
@@ -30,10 +30,14 @@
30
30
  "text": "textSoft",
31
31
  "thinkingText": "lavenderGray",
32
32
  "selectedBg": "surface2",
33
- "userMessageBg": "surface",
34
- "userMessageText": "textSoft",
35
33
  "customMessageBg": "#1C1230",
36
34
  "customMessageText": "textSoft",
35
+ "assistantMessageBg": "#1C1230",
36
+ "assistantMessageAccentBg": "pink",
37
+ "assistantMessageText": "textSoft",
38
+ "userMessageAccentBg": "#FFFFFF",
39
+ "userMessageBg": "surface",
40
+ "userMessageText": "textSoft",
37
41
  "customMessageLabel": "pink",
38
42
  "toolPendingBg": "#17112B",
39
43
  "toolSuccessBg": "#10261F",
@@ -34,10 +34,14 @@
34
34
  "scrollbarThumb": "lighter",
35
35
  "searchMatchBg": "selection",
36
36
  "searchMatchText": "foreground",
37
- "userMessageBg": "dark",
38
- "userMessageText": "foreground",
39
37
  "customMessageBg": "light",
40
38
  "customMessageText": "foreground",
39
+ "assistantMessageBg": "light",
40
+ "assistantMessageAccentBg": "purple",
41
+ "assistantMessageText": "foreground",
42
+ "userMessageAccentBg": "#FFFFFF",
43
+ "userMessageBg": "dark",
44
+ "userMessageText": "foreground",
41
45
  "customMessageLabel": "cyan",
42
46
  "toolPendingBg": "dark",
43
47
  "toolSuccessBg": "light",
@@ -42,10 +42,14 @@
42
42
  "scrollbarThumb": "border",
43
43
  "searchMatchBg": "search",
44
44
  "searchMatchText": "text",
45
- "userMessageBg": "panel",
46
- "userMessageText": "text",
47
45
  "customMessageBg": "raised",
48
46
  "customMessageText": "text",
47
+ "assistantMessageBg": "raised",
48
+ "assistantMessageAccentBg": "blue",
49
+ "assistantMessageText": "text",
50
+ "userMessageAccentBg": "#FFFFFF",
51
+ "userMessageBg": "panel",
52
+ "userMessageText": "text",
49
53
  "customMessageLabel": "cyan",
50
54
  "toolPendingBg": "panel",
51
55
  "toolSuccessBg": "raised",
@@ -43,10 +43,14 @@
43
43
  "scrollbarThumb": "border",
44
44
  "searchMatchBg": "selection",
45
45
  "searchMatchText": "text",
46
- "userMessageBg": "panel",
47
- "userMessageText": "text",
48
46
  "customMessageBg": "raised",
49
47
  "customMessageText": "text",
48
+ "assistantMessageBg": "raised",
49
+ "assistantMessageAccentBg": "blue",
50
+ "assistantMessageText": "text",
51
+ "userMessageAccentBg": "#FFFFFF",
52
+ "userMessageBg": "panel",
53
+ "userMessageText": "text",
50
54
  "customMessageLabel": "blue",
51
55
  "toolPendingBg": "panel",
52
56
  "toolSuccessBg": "raised",
@@ -42,10 +42,14 @@
42
42
  "scrollbarThumb": "border",
43
43
  "searchMatchBg": "search",
44
44
  "searchMatchText": "text",
45
- "userMessageBg": "panel",
46
- "userMessageText": "text",
47
45
  "customMessageBg": "raised",
48
46
  "customMessageText": "text",
47
+ "assistantMessageBg": "raised",
48
+ "assistantMessageAccentBg": "blue",
49
+ "assistantMessageText": "text",
50
+ "userMessageAccentBg": "#FFFFFF",
51
+ "userMessageBg": "panel",
52
+ "userMessageText": "text",
49
53
  "customMessageLabel": "cyan",
50
54
  "toolPendingBg": "panel",
51
55
  "toolSuccessBg": "raised",
@@ -31,10 +31,14 @@
31
31
  "scrollbarThumb": "subtle",
32
32
  "searchMatchBg": "orange",
33
33
  "searchMatchText": "accent",
34
- "userMessageBg": "",
35
- "userMessageText": "text",
36
34
  "customMessageBg": "",
37
35
  "customMessageText": "text",
36
+ "assistantMessageBg": "subtle",
37
+ "assistantMessageAccentBg": "orange",
38
+ "assistantMessageText": "text",
39
+ "userMessageAccentBg": "#FFFFFF",
40
+ "userMessageBg": "",
41
+ "userMessageText": "text",
38
42
  "customMessageLabel": "secondary",
39
43
  "toolPendingBg": "subtle",
40
44
  "toolSuccessBg": "subtle",
@@ -34,10 +34,14 @@
34
34
  "scrollbarThumb": "matrixInk3",
35
35
  "searchMatchBg": "rainPurple",
36
36
  "searchMatchText": "matrixInk0",
37
- "userMessageBg": "matrixInk1",
38
- "userMessageText": "rainGreenHi",
39
37
  "customMessageBg": "matrixInk2",
40
38
  "customMessageText": "rainGreenHi",
39
+ "assistantMessageBg": "matrixInk2",
40
+ "assistantMessageAccentBg": "rainGreen",
41
+ "assistantMessageText": "rainGreenHi",
42
+ "userMessageAccentBg": "#FFFFFF",
43
+ "userMessageBg": "matrixInk1",
44
+ "userMessageText": "rainGreenHi",
41
45
  "customMessageLabel": "rainCyan",
42
46
  "toolPendingBg": "matrixInk1",
43
47
  "toolSuccessBg": "matrixInk2",
@@ -42,10 +42,14 @@
42
42
  "scrollbarThumb": "slate",
43
43
  "searchMatchBg": "violet",
44
44
  "searchMatchText": "pureWhite",
45
- "userMessageBg": "surface",
46
- "userMessageText": "cyanText",
47
45
  "customMessageBg": "plasmaSurface",
48
46
  "customMessageText": "violetText",
47
+ "assistantMessageBg": "plasmaSurface",
48
+ "assistantMessageAccentBg": "cyan",
49
+ "assistantMessageText": "violetText",
50
+ "userMessageAccentBg": "#FFFFFF",
51
+ "userMessageBg": "surface",
52
+ "userMessageText": "cyanText",
49
53
  "customMessageLabel": "cyanSoft",
50
54
  "toolPendingBg": "surface",
51
55
  "toolSuccessBg": "card",
@@ -35,10 +35,14 @@
35
35
  "scrollbarThumb": "muted",
36
36
  "searchMatchBg": "accentPink",
37
37
  "searchMatchText": "text",
38
- "userMessageBg": "surface",
39
- "userMessageText": "text",
40
38
  "customMessageBg": "elevated",
41
39
  "customMessageText": "text",
40
+ "assistantMessageBg": "elevated",
41
+ "assistantMessageAccentBg": "accentPink",
42
+ "assistantMessageText": "text",
43
+ "userMessageAccentBg": "#FFFFFF",
44
+ "userMessageBg": "surface",
45
+ "userMessageText": "text",
42
46
  "customMessageLabel": "brightPink",
43
47
  "toolPendingBg": "surface",
44
48
  "toolSuccessBg": "elevated",
@@ -34,10 +34,14 @@
34
34
  "scrollbarThumb": "border",
35
35
  "searchMatchBg": "purple",
36
36
  "searchMatchText": "background",
37
- "userMessageBg": "panel",
38
- "userMessageText": "text",
39
37
  "customMessageBg": "elevated",
40
38
  "customMessageText": "text",
39
+ "assistantMessageBg": "elevated",
40
+ "assistantMessageAccentBg": "blue",
41
+ "assistantMessageText": "text",
42
+ "userMessageAccentBg": "#FFFFFF",
43
+ "userMessageBg": "panel",
44
+ "userMessageText": "text",
41
45
  "customMessageLabel": "cyan",
42
46
  "toolPendingBg": "panel",
43
47
  "toolSuccessBg": "elevated",