osdy-pi 0.1.9 → 1.2.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 (39) hide show
  1. package/README.md +209 -161
  2. package/bin/osdy-pi.mjs +127 -0
  3. package/extensions/osdy-pi/account-profiles.test.ts +517 -0
  4. package/extensions/osdy-pi/account-profiles.ts +581 -0
  5. package/extensions/osdy-pi/animation.ts +47 -5
  6. package/extensions/osdy-pi/constants.ts +282 -103
  7. package/extensions/osdy-pi/diff-panel.ts +6 -14
  8. package/extensions/osdy-pi/editor-settings.test.ts +163 -0
  9. package/extensions/osdy-pi/editor-settings.ts +112 -0
  10. package/extensions/osdy-pi/metrics.ts +8 -135
  11. package/extensions/osdy-pi/plugin-events.ts +26 -0
  12. package/extensions/osdy-pi/profile-label.ts +29 -0
  13. package/extensions/osdy-pi/runtime-helpers.test.ts +26 -0
  14. package/extensions/osdy-pi/runtime-helpers.ts +87 -44
  15. package/extensions/osdy-pi/runtime.test.ts +62 -0
  16. package/extensions/osdy-pi/runtime.ts +256 -64
  17. package/extensions/osdy-pi/types.ts +33 -10
  18. package/extensions/osdy-pi/ui.test.ts +51 -0
  19. package/extensions/osdy-pi/ui.ts +298 -114
  20. package/extensions/osdy-pi/utils.ts +43 -1
  21. package/extensions/osdy-pi/working-tree.ts +25 -6
  22. package/package.json +22 -5
  23. package/scripts/osdy-pi-account-profiles.mjs +501 -0
  24. package/themes/osdy-pi-catppuccin-frappe.json +94 -0
  25. package/themes/osdy-pi-catppuccin-latte.json +94 -0
  26. package/themes/osdy-pi-catppuccin-macchiato.json +94 -0
  27. package/themes/osdy-pi-catppuccin-mocha.json +94 -0
  28. package/themes/osdy-pi-dark.json +0 -6
  29. package/themes/osdy-pi-dracula.json +83 -0
  30. package/themes/osdy-pi-kanagawa-dragon.json +91 -0
  31. package/themes/osdy-pi-kanagawa-lotus.json +92 -0
  32. package/themes/osdy-pi-kanagawa-wave.json +91 -0
  33. package/themes/osdy-pi-lucent-orange.json +80 -0
  34. package/themes/osdy-pi-matrix.json +83 -0
  35. package/themes/osdy-pi-new.json +91 -0
  36. package/themes/osdy-pi-sexy.json +84 -0
  37. package/themes/osdy-pi-tokyo-night.json +83 -0
  38. package/themes/osdy-pi-light.json +0 -84
  39. package/themes/osdy-pi-simple.json +0 -83
@@ -0,0 +1,501 @@
1
+ import {
2
+ chmod,
3
+ lstat,
4
+ mkdir,
5
+ open,
6
+ readlink,
7
+ readdir,
8
+ rename,
9
+ rmdir,
10
+ symlink,
11
+ unlink,
12
+ writeFile,
13
+ } from "node:fs/promises";
14
+ import { Buffer } from "node:buffer";
15
+ import { randomUUID } from "node:crypto";
16
+ import { homedir } from "node:os";
17
+ import { basename, isAbsolute, join, resolve } from "node:path";
18
+
19
+ const PROFILE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62})$/;
20
+ const RESERVED_PROFILE_NAMES = new Set(["default", "profiles", "auth.json"]);
21
+ const DEFAULT_ACCOUNT_FILE = "default-account.json";
22
+ const MAX_DEFAULT_ACCOUNT_BYTES = 4096;
23
+ const SHARED_STATE = [
24
+ "settings.json",
25
+ "models.json",
26
+ "models-store.json",
27
+ "keybindings.json",
28
+ "trust.json",
29
+ "AGENTS.md",
30
+ "SYSTEM.md",
31
+ "APPEND_SYSTEM.md",
32
+ "sessions",
33
+ "extensions",
34
+ "skills",
35
+ "prompts",
36
+ "themes",
37
+ "npm",
38
+ "git",
39
+ "bin",
40
+ "tools",
41
+ // Legacy shared locations retained for existing installations.
42
+ "packages",
43
+ "resources",
44
+ ];
45
+
46
+ export function validateProfileName(value) {
47
+ if (
48
+ typeof value !== "string" ||
49
+ !PROFILE_NAME.test(value) ||
50
+ RESERVED_PROFILE_NAMES.has(value) ||
51
+ isAbsolute(value) ||
52
+ basename(value) !== value
53
+ ) {
54
+ throw new Error(
55
+ "Profile names must use lowercase letters, numbers, and hyphens (not reserved names).",
56
+ );
57
+ }
58
+ return value;
59
+ }
60
+
61
+ export function getSharedAgentDir(env = process.env) {
62
+ return resolve(
63
+ env.OSDY_PI_SHARED_AGENT_DIR ??
64
+ env.PI_CODING_AGENT_DIR ??
65
+ join(homedir(), ".pi", "agent"),
66
+ );
67
+ }
68
+
69
+ export function createProfileLayout(sharedAgentDir, name) {
70
+ const safeName = validateProfileName(name);
71
+ const profilesDir = join(resolve(sharedAgentDir), "osdy-pi", "profiles");
72
+ const profileDir = join(profilesDir, safeName);
73
+ return {
74
+ profileDir,
75
+ profilesDir,
76
+ authPath: join(profileDir, "auth.json"),
77
+ sharedState: SHARED_STATE,
78
+ };
79
+ }
80
+
81
+ export function parseAccountCommand(args) {
82
+ if (args[0] !== "account")
83
+ throw new Error(
84
+ "Usage: osdy-pi account list | create <name> | add <name> | rename <old> <new> | remove <name> --confirm <name> [--replacement <other>] | use <name> [-- <pi args...] | default [<name> | --clear]",
85
+ );
86
+ if (args[1] === "list" && args.length === 2) return { action: "list" };
87
+ if (args[1] === "create" && args.length === 3)
88
+ return { action: "create", name: validateProfileName(args[2]) };
89
+ if (args[1] === "add" && args.length === 3)
90
+ return { action: "add", name: validateProfileName(args[2]) };
91
+ if (args[1] === "rename" && args.length === 4)
92
+ return {
93
+ action: "rename",
94
+ oldName: validateProfileName(args[2]),
95
+ newName: validateProfileName(args[3]),
96
+ };
97
+ if (
98
+ args[1] === "remove" &&
99
+ args.length === 5 &&
100
+ args[3] === "--confirm" &&
101
+ args[4] === args[2]
102
+ )
103
+ return {
104
+ action: "remove",
105
+ name: validateProfileName(args[2]),
106
+ replacement: undefined,
107
+ };
108
+ if (
109
+ args[1] === "remove" &&
110
+ args.length === 7 &&
111
+ args[3] === "--confirm" &&
112
+ args[4] === args[2] &&
113
+ args[5] === "--replacement"
114
+ )
115
+ return {
116
+ action: "remove",
117
+ name: validateProfileName(args[2]),
118
+ replacement: validateProfileName(args[6]),
119
+ };
120
+ if (args[1] === "default" && args.length === 2)
121
+ return { action: "default", operation: "query" };
122
+ if (args[1] === "default" && args.length === 3 && args[2] === "--clear")
123
+ return { action: "default", operation: "clear" };
124
+ if (args[1] === "default" && args.length === 3)
125
+ return {
126
+ action: "default",
127
+ operation: "set",
128
+ name: validateProfileName(args[2]),
129
+ };
130
+ if (args[1] === "use" && args.length >= 3) {
131
+ const name = validateProfileName(args[2]);
132
+ const separator = args.indexOf("--", 3);
133
+ if (separator === -1 && args.length === 3)
134
+ return { action: "use", name, piArgs: [] };
135
+ if (separator === 3) return { action: "use", name, piArgs: args.slice(4) };
136
+ }
137
+ throw new Error(
138
+ "Usage: osdy-pi account list | create <name> | add <name> | rename <old> <new> | remove <name> --confirm <name> [--replacement <other>] | use <name> [-- <pi args...] | default [<name> | --clear]",
139
+ );
140
+ }
141
+
142
+ export function planPiLaunch(
143
+ sharedAgentDir,
144
+ name,
145
+ piArgs = [],
146
+ env = process.env,
147
+ ) {
148
+ const safeName = validateProfileName(name);
149
+ const layout = createProfileLayout(sharedAgentDir, safeName);
150
+ const devExtensionRoot = env.OSDY_PI_DEV_EXTENSION_ROOT;
151
+ if (
152
+ devExtensionRoot !== undefined &&
153
+ (typeof devExtensionRoot !== "string" || !isAbsolute(devExtensionRoot))
154
+ ) {
155
+ throw new Error("The development extension root must be absolute.");
156
+ }
157
+ return {
158
+ command: "pi",
159
+ args:
160
+ devExtensionRoot === undefined
161
+ ? [...piArgs]
162
+ : ["-e", devExtensionRoot, ...piArgs],
163
+ env: {
164
+ PI_CODING_AGENT_DIR: layout.profileDir,
165
+ OSDY_PI_PROFILE_NAME: safeName,
166
+ OSDY_PI_SHARED_AGENT_DIR: resolve(sharedAgentDir),
167
+ PI_CODING_AGENT_SESSION_DIR: join(resolve(sharedAgentDir), "sessions"),
168
+ },
169
+ };
170
+ }
171
+
172
+ async function ensurePrivateDirectory(path) {
173
+ try {
174
+ const stat = await lstat(path);
175
+ if (!stat.isDirectory())
176
+ throw new Error(`Managed profile path is not a directory: ${path}`);
177
+ } catch (error) {
178
+ if (!isErrorCode(error, "ENOENT")) throw error;
179
+ await mkdir(path, { mode: 0o700 });
180
+ const stat = await lstat(path);
181
+ if (!stat.isDirectory())
182
+ throw new Error(`Managed profile path is not a directory: ${path}`);
183
+ }
184
+ await chmod(path, 0o700);
185
+ }
186
+
187
+ function isErrorCode(error, code) {
188
+ return (
189
+ error && typeof error === "object" && "code" in error && error.code === code
190
+ );
191
+ }
192
+
193
+ async function ensureSharedLink(target, source) {
194
+ try {
195
+ const stat = await lstat(target);
196
+ if (!stat.isSymbolicLink())
197
+ throw new Error(
198
+ `Profile state already exists and is not a shared link: ${target}`,
199
+ );
200
+ if ((await readlink(target)) !== source) {
201
+ throw new Error(
202
+ `Profile shared link does not target the shared state: ${target}`,
203
+ );
204
+ }
205
+ return;
206
+ } catch (error) {
207
+ if (!isErrorCode(error, "ENOENT")) throw error;
208
+ }
209
+ await symlink(source, target);
210
+ }
211
+
212
+ export async function ensureProfileLayout(sharedAgentDir, name) {
213
+ const sharedDir = resolve(sharedAgentDir);
214
+ const layout = createProfileLayout(sharedDir, name);
215
+ await ensurePrivateDirectory(join(sharedDir, "osdy-pi"));
216
+ await ensurePrivateDirectory(layout.profilesDir);
217
+ await ensurePrivateDirectory(layout.profileDir);
218
+ for (const stateName of layout.sharedState) {
219
+ await ensureSharedLink(
220
+ join(layout.profileDir, stateName),
221
+ join(sharedDir, stateName),
222
+ );
223
+ }
224
+ return layout;
225
+ }
226
+
227
+ export function getDefaultAccountPath(sharedAgentDir) {
228
+ return join(resolve(sharedAgentDir), "osdy-pi", DEFAULT_ACCOUNT_FILE);
229
+ }
230
+
231
+ export async function validateExistingProfile(sharedAgentDir, name) {
232
+ const safeName = validateProfileName(name);
233
+ const layout = createProfileLayout(sharedAgentDir, safeName);
234
+ const confinedRoot = `${resolve(layout.profilesDir)}/`;
235
+ if (!resolve(layout.profileDir).startsWith(confinedRoot))
236
+ throw new Error(
237
+ "Profile directory is outside the managed profiles directory.",
238
+ );
239
+ try {
240
+ const osdyPiStat = await lstat(join(resolve(sharedAgentDir), "osdy-pi"));
241
+ if (!osdyPiStat.isDirectory())
242
+ throw new Error("Managed account path is not a directory.");
243
+ const profilesStat = await lstat(layout.profilesDir);
244
+ if (!profilesStat.isDirectory())
245
+ throw new Error("Managed profiles path is not a directory.");
246
+ const stat = await lstat(layout.profileDir);
247
+ if (!stat.isDirectory())
248
+ throw new Error("The selected profile is not an existing directory.");
249
+ } catch (error) {
250
+ if (isErrorCode(error, "ENOENT"))
251
+ throw new Error("The selected default must be an existing profile.");
252
+ throw error;
253
+ }
254
+ return { ...layout, name: safeName };
255
+ }
256
+
257
+ async function readBoundedRegularFile(path) {
258
+ const stat = await lstat(path);
259
+ if (!stat.isFile() || stat.size > MAX_DEFAULT_ACCOUNT_BYTES)
260
+ throw new Error("Default account metadata is not a bounded regular file.");
261
+ const handle = await open(path, "r");
262
+ try {
263
+ const openedStat = await handle.stat();
264
+ if (!openedStat.isFile() || openedStat.size > MAX_DEFAULT_ACCOUNT_BYTES)
265
+ throw new Error("Default account metadata is not a bounded regular file.");
266
+ const buffer = Buffer.alloc(MAX_DEFAULT_ACCOUNT_BYTES + 1);
267
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
268
+ if (bytesRead > MAX_DEFAULT_ACCOUNT_BYTES)
269
+ throw new Error("Default account metadata is too large.");
270
+ return buffer.toString("utf8", 0, bytesRead);
271
+ } finally {
272
+ await handle.close();
273
+ }
274
+ }
275
+
276
+ export async function readDefaultAccount(sharedAgentDir) {
277
+ const defaultPath = getDefaultAccountPath(sharedAgentDir);
278
+ try {
279
+ const defaultDirStat = await lstat(join(resolve(sharedAgentDir), "osdy-pi"));
280
+ if (!defaultDirStat.isDirectory()) return { status: "invalid" };
281
+ const parsed = JSON.parse(await readBoundedRegularFile(defaultPath));
282
+ if (
283
+ typeof parsed !== "object" ||
284
+ parsed === null ||
285
+ Object.keys(parsed).length !== 2 ||
286
+ parsed.version !== 1 ||
287
+ typeof parsed.profile !== "string"
288
+ )
289
+ return { status: "invalid" };
290
+ await validateExistingProfile(sharedAgentDir, parsed.profile);
291
+ return { status: "valid", profile: parsed.profile };
292
+ } catch (error) {
293
+ if (isErrorCode(error, "ENOENT")) return { status: "unset" };
294
+ return { status: "invalid" };
295
+ }
296
+ }
297
+
298
+ export async function setDefaultAccount(sharedAgentDir, name) {
299
+ const layout = await validateExistingProfile(sharedAgentDir, name);
300
+ const defaultDir = join(resolve(sharedAgentDir), "osdy-pi");
301
+ await ensurePrivateDirectory(defaultDir);
302
+ const defaultPath = getDefaultAccountPath(sharedAgentDir);
303
+ const tempPath = join(
304
+ defaultDir,
305
+ `.${DEFAULT_ACCOUNT_FILE}.${process.pid}.${randomUUID()}.tmp`,
306
+ );
307
+ try {
308
+ await writeFile(
309
+ tempPath,
310
+ `${JSON.stringify({ version: 1, profile: layout.name })}\n`,
311
+ { encoding: "utf8", mode: 0o600, flag: "wx" },
312
+ );
313
+ await chmod(tempPath, 0o600);
314
+ await rename(tempPath, defaultPath);
315
+ await chmod(defaultPath, 0o600);
316
+ } catch (error) {
317
+ await unlink(tempPath).catch(() => {});
318
+ throw error;
319
+ }
320
+ }
321
+
322
+ export async function clearDefaultAccount(sharedAgentDir) {
323
+ try {
324
+ await unlink(getDefaultAccountPath(sharedAgentDir));
325
+ } catch (error) {
326
+ if (!isErrorCode(error, "ENOENT")) throw error;
327
+ }
328
+ }
329
+
330
+ function refuseActiveProfile(name, activeProfile) {
331
+ if (activeProfile === name)
332
+ throw new Error(
333
+ "Cannot change the active profile. Close this Pi process first.",
334
+ );
335
+ }
336
+
337
+ async function assertMissingProfilePath(path) {
338
+ try {
339
+ await lstat(path);
340
+ throw new Error("The new profile path already exists.");
341
+ } catch (error) {
342
+ if (isErrorCode(error, "ENOENT")) return;
343
+ throw error;
344
+ }
345
+ }
346
+
347
+ export async function renameProfile(
348
+ sharedAgentDir,
349
+ oldName,
350
+ newName,
351
+ {
352
+ activeProfile = process.env.OSDY_PI_PROFILE_NAME,
353
+ setDefault = setDefaultAccount,
354
+ } = {},
355
+ ) {
356
+ const oldProfile = await validateExistingProfile(sharedAgentDir, oldName);
357
+ const safeNewName = validateProfileName(newName);
358
+ refuseActiveProfile(oldProfile.name, activeProfile);
359
+ const newProfile = createProfileLayout(sharedAgentDir, safeNewName);
360
+ await assertMissingProfilePath(newProfile.profileDir);
361
+ const defaultAccount = await readDefaultAccount(sharedAgentDir);
362
+ await rename(oldProfile.profileDir, newProfile.profileDir);
363
+ if (
364
+ defaultAccount.status !== "valid" ||
365
+ defaultAccount.profile !== oldProfile.name
366
+ )
367
+ return;
368
+ try {
369
+ await setDefault(sharedAgentDir, safeNewName);
370
+ } catch (error) {
371
+ try {
372
+ await rename(newProfile.profileDir, oldProfile.profileDir);
373
+ } catch (rollbackError) {
374
+ throw new Error(
375
+ `Default update failed and profile rollback failed: ${boundedError(error)}; ${boundedError(rollbackError)}`,
376
+ );
377
+ }
378
+ throw new Error(
379
+ `Default update failed; profile rename was rolled back: ${boundedError(error)}`,
380
+ );
381
+ }
382
+ }
383
+
384
+ function boundedError(error) {
385
+ return error instanceof Error ? error.message.slice(0, 180) : "unknown error";
386
+ }
387
+
388
+ export async function removeProfile(
389
+ sharedAgentDir,
390
+ name,
391
+ {
392
+ confirmation,
393
+ replacement,
394
+ activeProfile = process.env.OSDY_PI_PROFILE_NAME,
395
+ } = {},
396
+ ) {
397
+ const profile = await validateExistingProfile(sharedAgentDir, name);
398
+ if (confirmation !== profile.name)
399
+ throw new Error("Profile removal requires an exact matching confirmation.");
400
+ refuseActiveProfile(profile.name, activeProfile);
401
+ const defaultAccount = await readDefaultAccount(sharedAgentDir);
402
+ if (
403
+ defaultAccount.status === "valid" &&
404
+ defaultAccount.profile === profile.name
405
+ ) {
406
+ if (replacement === undefined)
407
+ throw new Error("Removing the default profile requires a replacement.");
408
+ const safeReplacement = validateProfileName(replacement);
409
+ if (safeReplacement === profile.name)
410
+ throw new Error(
411
+ "The default replacement must be different from the removed profile.",
412
+ );
413
+ await validateExistingProfile(sharedAgentDir, safeReplacement);
414
+ await setDefaultAccount(sharedAgentDir, safeReplacement);
415
+ } else if (replacement !== undefined) {
416
+ throw new Error(
417
+ "A replacement is only allowed when removing the default profile.",
418
+ );
419
+ }
420
+ const entries = await readdir(profile.profileDir, { withFileTypes: true });
421
+ for (const entry of entries) {
422
+ const entryPath = join(profile.profileDir, entry.name);
423
+ let stat;
424
+ try {
425
+ stat = await lstat(entryPath);
426
+ } catch (error) {
427
+ throw new Error(
428
+ `Profile entry changed during removal: ${entry.name}: ${boundedError(error)}`,
429
+ );
430
+ }
431
+ if (stat.isDirectory())
432
+ throw new Error(
433
+ `Refusing to remove unexpected directory in profile: ${entry.name}`,
434
+ );
435
+ if (!stat.isSymbolicLink() && !stat.isFile())
436
+ throw new Error(
437
+ `Refusing to remove unexpected profile entry: ${entry.name}`,
438
+ );
439
+ }
440
+ for (const entry of entries) {
441
+ try {
442
+ await unlink(join(profile.profileDir, entry.name));
443
+ } catch (error) {
444
+ throw new Error(
445
+ `Could not remove profile entry ${entry.name}: ${boundedError(error)}`,
446
+ );
447
+ }
448
+ }
449
+ try {
450
+ await rmdir(profile.profileDir);
451
+ } catch (error) {
452
+ throw new Error(
453
+ `Could not remove empty profile directory: ${boundedError(error)}`,
454
+ );
455
+ }
456
+ }
457
+
458
+ export async function planDefaultLaunch(
459
+ sharedAgentDir,
460
+ piArgs = [],
461
+ env = process.env,
462
+ ) {
463
+ const defaultAccount = await readDefaultAccount(sharedAgentDir);
464
+ if (defaultAccount.status === "valid") {
465
+ return {
466
+ plan: planPiLaunch(sharedAgentDir, defaultAccount.profile, piArgs, env),
467
+ defaultAccount,
468
+ };
469
+ }
470
+ return {
471
+ plan: { command: "pi", args: [...piArgs], env: {} },
472
+ defaultAccount,
473
+ };
474
+ }
475
+
476
+ export async function listProfiles(sharedAgentDir) {
477
+ const { profilesDir } = createProfileLayout(sharedAgentDir, "placeholder");
478
+ try {
479
+ const entries = await readdir(profilesDir, { withFileTypes: true });
480
+ const profiles = [];
481
+ for (const entry of entries) {
482
+ if (
483
+ entry.isDirectory() &&
484
+ PROFILE_NAME.test(entry.name) &&
485
+ !RESERVED_PROFILE_NAMES.has(entry.name)
486
+ ) {
487
+ profiles.push(entry.name);
488
+ }
489
+ }
490
+ return profiles.sort(compareStrings);
491
+ } catch (error) {
492
+ if (isErrorCode(error, "ENOENT")) return [];
493
+ throw error;
494
+ }
495
+ }
496
+
497
+ function compareStrings(left, right) {
498
+ if (left < right) return -1;
499
+ if (left > right) return 1;
500
+ return 0;
501
+ }
@@ -0,0 +1,94 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "osdy-pi-catppuccin-frappe",
4
+ "vars": {
5
+ "rosewater": "#f2d5cf",
6
+ "flamingo": "#eebebe",
7
+ "pink": "#f4b8e4",
8
+ "mauve": "#ca9ee6",
9
+ "red": "#e78284",
10
+ "maroon": "#ea999c",
11
+ "peach": "#ef9f76",
12
+ "yellow": "#e5c890",
13
+ "green": "#a6d189",
14
+ "teal": "#81c8be",
15
+ "sky": "#99d1db",
16
+ "sapphire": "#85c1dc",
17
+ "blue": "#8caaee",
18
+ "lavender": "#babbf1",
19
+ "text": "#c6d0f5",
20
+ "subtext1": "#b5bfe2",
21
+ "subtext0": "#a5adce",
22
+ "overlay2": "#949cbb",
23
+ "overlay1": "#838ba7",
24
+ "overlay0": "#737994",
25
+ "surface2": "#626880",
26
+ "surface1": "#51576d",
27
+ "surface0": "#414559",
28
+ "base": "#303446",
29
+ "mantle": "#292c3c",
30
+ "crust": "#232634"
31
+ },
32
+ "colors": {
33
+ "accent": "blue",
34
+ "border": "surface2",
35
+ "borderAccent": "lavender",
36
+ "borderMuted": "surface1",
37
+ "success": "green",
38
+ "error": "red",
39
+ "warning": "yellow",
40
+ "muted": "subtext1",
41
+ "dim": "subtext0",
42
+ "text": "text",
43
+ "thinkingText": "subtext1",
44
+ "selectedBg": "surface0",
45
+ "scrollbarThumb": "overlay1",
46
+ "searchMatchBg": "surface0",
47
+ "searchMatchText": "text",
48
+ "userMessageBg": "mantle",
49
+ "userMessageText": "text",
50
+ "customMessageBg": "surface0",
51
+ "customMessageText": "text",
52
+ "customMessageLabel": "blue",
53
+ "toolPendingBg": "mantle",
54
+ "toolSuccessBg": "surface0",
55
+ "toolErrorBg": "crust",
56
+ "toolTitle": "blue",
57
+ "toolOutput": "subtext1",
58
+ "mdHeading": "mauve",
59
+ "mdLink": "blue",
60
+ "mdLinkUrl": "sapphire",
61
+ "mdCode": "peach",
62
+ "mdCodeBlock": "subtext1",
63
+ "mdCodeBlockBorder": "surface2",
64
+ "mdQuote": "subtext0",
65
+ "mdQuoteBorder": "lavender",
66
+ "mdHr": "surface1",
67
+ "mdListBullet": "blue",
68
+ "toolDiffAdded": "green",
69
+ "toolDiffRemoved": "red",
70
+ "toolDiffContext": "yellow",
71
+ "syntaxComment": "overlay1",
72
+ "syntaxKeyword": "mauve",
73
+ "syntaxFunction": "blue",
74
+ "syntaxVariable": "text",
75
+ "syntaxString": "green",
76
+ "syntaxNumber": "peach",
77
+ "syntaxType": "lavender",
78
+ "syntaxOperator": "pink",
79
+ "syntaxPunctuation": "subtext1",
80
+ "thinkingOff": "overlay0",
81
+ "thinkingMinimal": "teal",
82
+ "thinkingLow": "green",
83
+ "thinkingMedium": "blue",
84
+ "thinkingHigh": "lavender",
85
+ "thinkingXhigh": "mauve",
86
+ "thinkingMax": "pink",
87
+ "bashMode": "yellow"
88
+ },
89
+ "export": {
90
+ "pageBg": "base",
91
+ "cardBg": "mantle",
92
+ "infoBg": "surface0"
93
+ }
94
+ }
@@ -0,0 +1,94 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "osdy-pi-catppuccin-latte",
4
+ "vars": {
5
+ "rosewater": "#dc8a78",
6
+ "flamingo": "#dd7878",
7
+ "pink": "#ea76cb",
8
+ "mauve": "#8839ef",
9
+ "red": "#d20f39",
10
+ "maroon": "#e64553",
11
+ "peach": "#fe640b",
12
+ "yellow": "#df8e1d",
13
+ "green": "#40a02b",
14
+ "teal": "#179299",
15
+ "sky": "#04a5e5",
16
+ "sapphire": "#209fb5",
17
+ "blue": "#1e66f5",
18
+ "lavender": "#7287fd",
19
+ "text": "#4c4f69",
20
+ "subtext1": "#5c5f77",
21
+ "subtext0": "#6c6f85",
22
+ "overlay2": "#7c7f93",
23
+ "overlay1": "#8c8fa1",
24
+ "overlay0": "#9ca0b0",
25
+ "surface2": "#acb0be",
26
+ "surface1": "#bcc0cc",
27
+ "surface0": "#ccd0da",
28
+ "base": "#eff1f5",
29
+ "mantle": "#e6e9ef",
30
+ "crust": "#dce0e8"
31
+ },
32
+ "colors": {
33
+ "accent": "blue",
34
+ "border": "surface2",
35
+ "borderAccent": "lavender",
36
+ "borderMuted": "surface1",
37
+ "success": "green",
38
+ "error": "red",
39
+ "warning": "yellow",
40
+ "muted": "subtext1",
41
+ "dim": "subtext0",
42
+ "text": "text",
43
+ "thinkingText": "subtext1",
44
+ "selectedBg": "surface0",
45
+ "scrollbarThumb": "overlay1",
46
+ "searchMatchBg": "surface0",
47
+ "searchMatchText": "text",
48
+ "userMessageBg": "mantle",
49
+ "userMessageText": "text",
50
+ "customMessageBg": "surface0",
51
+ "customMessageText": "text",
52
+ "customMessageLabel": "blue",
53
+ "toolPendingBg": "mantle",
54
+ "toolSuccessBg": "surface0",
55
+ "toolErrorBg": "crust",
56
+ "toolTitle": "blue",
57
+ "toolOutput": "text",
58
+ "mdHeading": "mauve",
59
+ "mdLink": "blue",
60
+ "mdLinkUrl": "sapphire",
61
+ "mdCode": "peach",
62
+ "mdCodeBlock": "subtext1",
63
+ "mdCodeBlockBorder": "surface2",
64
+ "mdQuote": "subtext0",
65
+ "mdQuoteBorder": "lavender",
66
+ "mdHr": "surface1",
67
+ "mdListBullet": "blue",
68
+ "toolDiffAdded": "green",
69
+ "toolDiffRemoved": "red",
70
+ "toolDiffContext": "yellow",
71
+ "syntaxComment": "overlay1",
72
+ "syntaxKeyword": "mauve",
73
+ "syntaxFunction": "blue",
74
+ "syntaxVariable": "text",
75
+ "syntaxString": "green",
76
+ "syntaxNumber": "peach",
77
+ "syntaxType": "lavender",
78
+ "syntaxOperator": "pink",
79
+ "syntaxPunctuation": "subtext1",
80
+ "thinkingOff": "overlay0",
81
+ "thinkingMinimal": "teal",
82
+ "thinkingLow": "green",
83
+ "thinkingMedium": "blue",
84
+ "thinkingHigh": "lavender",
85
+ "thinkingXhigh": "mauve",
86
+ "thinkingMax": "pink",
87
+ "bashMode": "yellow"
88
+ },
89
+ "export": {
90
+ "pageBg": "base",
91
+ "cardBg": "mantle",
92
+ "infoBg": "surface0"
93
+ }
94
+ }