osdy-pi 0.1.4 → 0.1.6
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 +105 -5
- package/extensions/osdy-pi/animation.ts +40 -0
- package/extensions/osdy-pi/audio-event-router.ts +44 -0
- package/extensions/osdy-pi/audio-notification-config.ts +194 -0
- package/extensions/osdy-pi/audio-notification-service.ts +58 -0
- package/extensions/osdy-pi/audio-notification-types.ts +76 -0
- package/extensions/osdy-pi/audio-playback-macos.ts +36 -0
- package/extensions/osdy-pi/audio-playback-unsupported.ts +8 -0
- package/extensions/osdy-pi/audio-playback-windows.ts +86 -0
- package/extensions/osdy-pi/audio-playback.ts +18 -0
- package/extensions/osdy-pi/audio-sound-settings.ts +108 -0
- package/extensions/osdy-pi/constants.ts +294 -31
- package/extensions/osdy-pi/runtime-helpers.ts +1 -1
- package/extensions/osdy-pi/runtime.ts +125 -21
- package/extensions/osdy-pi/sound-setup-wizard.ts +220 -0
- package/extensions/osdy-pi/types.ts +3 -0
- package/extensions/osdy-pi/ui.ts +69 -20
- package/extensions/osdy-pi/utils.ts +10 -10
- package/package.json +3 -2
- package/themes/osdy-pi-dark.json +6 -0
- package/themes/osdy-pi-light.json +6 -0
- package/themes/osdy-pi-simple.json +83 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import type { AudioPlaybackAdapter } from "./audio-playback.js";
|
|
3
|
+
|
|
4
|
+
const WINDOWS_AUDIO_TIMEOUT_MS = 15_000;
|
|
5
|
+
|
|
6
|
+
const WINDOWS_AUDIO_SCRIPT = [
|
|
7
|
+
"$path = $env:OSDY_PI_SOUND_PATH",
|
|
8
|
+
'if ([string]::IsNullOrWhiteSpace($path)) { throw "Missing sound path" }',
|
|
9
|
+
"$player = New-Object -ComObject WMPlayer.OCX",
|
|
10
|
+
"$player.settings.volume = 100",
|
|
11
|
+
"$player.URL = $path",
|
|
12
|
+
"$player.controls.play()",
|
|
13
|
+
"while ($true) {",
|
|
14
|
+
" $state = $player.playState",
|
|
15
|
+
" if ($state -eq 1 -or $state -eq 8 -or $state -eq 10) { break }",
|
|
16
|
+
" Start-Sleep -Milliseconds 100",
|
|
17
|
+
"}",
|
|
18
|
+
].join("\n");
|
|
19
|
+
|
|
20
|
+
function encodePowerShellScript(script: string): string {
|
|
21
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runPowerShell(script: string, soundPath: string): Promise<void> {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const child = spawn(
|
|
27
|
+
"powershell.exe",
|
|
28
|
+
[
|
|
29
|
+
"-NoProfile",
|
|
30
|
+
"-NonInteractive",
|
|
31
|
+
"-ExecutionPolicy",
|
|
32
|
+
"Bypass",
|
|
33
|
+
"-EncodedCommand",
|
|
34
|
+
encodePowerShellScript(script),
|
|
35
|
+
],
|
|
36
|
+
{
|
|
37
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
38
|
+
env: { ...process.env, OSDY_PI_SOUND_PATH: soundPath },
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
let stderr = "";
|
|
43
|
+
let settled = false;
|
|
44
|
+
const timeout = setTimeout(() => {
|
|
45
|
+
if (settled) return;
|
|
46
|
+
settled = true;
|
|
47
|
+
child.kill();
|
|
48
|
+
reject(new Error("powershell audio playback timed out"));
|
|
49
|
+
}, WINDOWS_AUDIO_TIMEOUT_MS);
|
|
50
|
+
const finish = (callback: () => void) => {
|
|
51
|
+
if (settled) return;
|
|
52
|
+
settled = true;
|
|
53
|
+
clearTimeout(timeout);
|
|
54
|
+
callback();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
child.stderr.on("data", (chunk: Buffer | string) => {
|
|
58
|
+
stderr += chunk.toString();
|
|
59
|
+
});
|
|
60
|
+
child.on("error", (error) => {
|
|
61
|
+
finish(() => reject(error));
|
|
62
|
+
});
|
|
63
|
+
child.on("close", (code) => {
|
|
64
|
+
finish(() => {
|
|
65
|
+
if (code === 0) {
|
|
66
|
+
resolve();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
reject(
|
|
70
|
+
new Error(
|
|
71
|
+
stderr.trim() || `powershell exited with code ${code ?? "unknown"}`,
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createWindowsAudioPlaybackAdapter(): AudioPlaybackAdapter {
|
|
80
|
+
return {
|
|
81
|
+
platform: "win32",
|
|
82
|
+
play(soundPath: string): Promise<void> {
|
|
83
|
+
return runPowerShell(WINDOWS_AUDIO_SCRIPT, soundPath);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createMacOsAudioPlaybackAdapter } from "./audio-playback-macos.js";
|
|
2
|
+
import { createUnsupportedAudioPlaybackAdapter } from "./audio-playback-unsupported.js";
|
|
3
|
+
import { createWindowsAudioPlaybackAdapter } from "./audio-playback-windows.js";
|
|
4
|
+
|
|
5
|
+
export type AudioPlaybackAdapter = {
|
|
6
|
+
readonly platform: "darwin" | "win32" | "unsupported";
|
|
7
|
+
play(path: string): Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function createAudioPlaybackAdapter(): AudioPlaybackAdapter {
|
|
11
|
+
if (process.platform === "darwin") {
|
|
12
|
+
return createMacOsAudioPlaybackAdapter();
|
|
13
|
+
}
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
return createWindowsAudioPlaybackAdapter();
|
|
16
|
+
}
|
|
17
|
+
return createUnsupportedAudioPlaybackAdapter();
|
|
18
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
AUDIO_NOTIFICATION_EVENTS,
|
|
6
|
+
type AudioNotificationConfig,
|
|
7
|
+
type AudioNotificationEvent,
|
|
8
|
+
type GlobalAudioNotificationSettings,
|
|
9
|
+
} from "./audio-notification-types.js";
|
|
10
|
+
|
|
11
|
+
const GLOBAL_SETTINGS_VERSION = 1;
|
|
12
|
+
const GLOBAL_SETTINGS_FILE_NAME = "audio-notifications.json";
|
|
13
|
+
const GLOBAL_SETTINGS_RELATIVE_DIR = path.join("extensions", "osdy-pi");
|
|
14
|
+
|
|
15
|
+
function getAgentDir(): string {
|
|
16
|
+
const configuredDir = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
17
|
+
if (configuredDir && configuredDir.length > 0) {
|
|
18
|
+
return configuredDir;
|
|
19
|
+
}
|
|
20
|
+
return path.join(os.homedir(), ".pi", "agent");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createDefaultSettings(): GlobalAudioNotificationSettings {
|
|
24
|
+
return { version: GLOBAL_SETTINGS_VERSION, sounds: {} };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isAudioEvent(value: string): value is AudioNotificationEvent {
|
|
28
|
+
return AUDIO_NOTIFICATION_EVENTS.includes(value as AudioNotificationEvent);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeSounds(value: unknown): AudioNotificationConfig {
|
|
32
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sounds: AudioNotificationConfig = {};
|
|
37
|
+
for (const [key, rawValue] of Object.entries(value)) {
|
|
38
|
+
if (!isAudioEvent(key) || typeof rawValue !== "string") continue;
|
|
39
|
+
const trimmedValue = rawValue.trim();
|
|
40
|
+
if (trimmedValue.length > 0) {
|
|
41
|
+
sounds[key] = trimmedValue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return sounds;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeLoadedSettings(
|
|
48
|
+
value: unknown,
|
|
49
|
+
): GlobalAudioNotificationSettings {
|
|
50
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
51
|
+
return createDefaultSettings();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const recordValue = value as Record<string, unknown>;
|
|
55
|
+
if (recordValue.version !== GLOBAL_SETTINGS_VERSION) {
|
|
56
|
+
return createDefaultSettings();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
version: GLOBAL_SETTINGS_VERSION,
|
|
61
|
+
sounds: normalizeSounds(recordValue.sounds),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type AudioSoundSettingsStore = {
|
|
66
|
+
readonly path: string;
|
|
67
|
+
load(): Promise<GlobalAudioNotificationSettings>;
|
|
68
|
+
save(settings: GlobalAudioNotificationSettings): Promise<void>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
class FileAudioSoundSettingsStore implements AudioSoundSettingsStore {
|
|
72
|
+
readonly path: string;
|
|
73
|
+
|
|
74
|
+
constructor(filePath: string) {
|
|
75
|
+
this.path = filePath;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async load(): Promise<GlobalAudioNotificationSettings> {
|
|
79
|
+
try {
|
|
80
|
+
const content = await readFile(this.path, "utf8");
|
|
81
|
+
return normalizeLoadedSettings(JSON.parse(content) as unknown);
|
|
82
|
+
} catch {
|
|
83
|
+
return createDefaultSettings();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async save(settings: GlobalAudioNotificationSettings): Promise<void> {
|
|
88
|
+
const normalizedSettings = normalizeLoadedSettings(settings);
|
|
89
|
+
const parentDir = path.dirname(this.path);
|
|
90
|
+
const tempPath = `${this.path}.tmp`;
|
|
91
|
+
await mkdir(parentDir, { recursive: true });
|
|
92
|
+
await writeFile(
|
|
93
|
+
tempPath,
|
|
94
|
+
`${JSON.stringify(normalizedSettings, null, 2)}\n`,
|
|
95
|
+
"utf8",
|
|
96
|
+
);
|
|
97
|
+
await rename(tempPath, this.path);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createAudioSoundSettingsStore(): AudioSoundSettingsStore {
|
|
102
|
+
const filePath = path.join(
|
|
103
|
+
getAgentDir(),
|
|
104
|
+
GLOBAL_SETTINGS_RELATIVE_DIR,
|
|
105
|
+
GLOBAL_SETTINGS_FILE_NAME,
|
|
106
|
+
);
|
|
107
|
+
return new FileAudioSoundSettingsStore(filePath);
|
|
108
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { HeaderVariant } from "./types.js";
|
|
2
3
|
|
|
3
4
|
export const THEME_NAME = "osdy-pi-dark";
|
|
4
5
|
export const ANIMATION_ENABLED = true;
|
|
@@ -20,37 +21,83 @@ export const WORKING_WIDGET_KEY = "osdy-pi-working";
|
|
|
20
21
|
export const MASCOT_MIN_ROWS = 34;
|
|
21
22
|
export const MASCOT_GAP = 0;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
24
|
+
const HTML_MASCOT = [
|
|
25
|
+
" ▓▓▓▓▒ ",
|
|
26
|
+
" ░ ▓▓▓ ▒▓ ",
|
|
27
|
+
" ░░░▓▓▓ ▓▓ ",
|
|
28
|
+
" ▒ ▒ ▒▒ ▒ ░░ ▓▓▓▓ ░▒▓▓ ",
|
|
29
|
+
"▓▓▓▓▓▓▓▓░░░░░ ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ ░░▒▓ ",
|
|
30
|
+
"▓▓ ▓▓▓▓▓░░░ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░ ░░▒▒ ",
|
|
31
|
+
"▓▓░ ▒▓▓▓▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒░░ ░▒▒ ",
|
|
32
|
+
" ▓▒░░ ▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓█▓▓█▓▓▓▓▓▒▒▒░▒▒░ ",
|
|
33
|
+
" ▓▒░░ ░▒▒▒▒▒▓▓▓▓▓▒█▒▒▒▒▒▓▓▓▓ ░░░ ▓▓▓▓▒▒▒░░ ",
|
|
34
|
+
" ▒▒░░ ░▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▓▓ ░ ▓ ▒▒░░ ",
|
|
35
|
+
" ▒▒▒▒░▒▒▓▓▓▓░ ░▓▒▒▒▒▒ ░▒▒█▓ ▒▓▓▓░▒ ",
|
|
36
|
+
" ░ ░▒▒▓▓▒ ▓░░ ░░ ░ ▓▓▓▒ ",
|
|
37
|
+
" ░░▒▓░ ░▒░▓▓ ░░ ░▓▓▒ ",
|
|
38
|
+
" ░▒ ▒ ░▒▒▒ ▒▒▒▒ ░ ▒▒▒ ",
|
|
39
|
+
" ░▒▓▓░ ▓▓░░░ █▓ ▒ ░▒▒▒ ",
|
|
40
|
+
" ▒▓▓▓░ ▓▓ ▓▓▓▓▓▓▓▓▓ ░░▒ ",
|
|
41
|
+
" ▓▓▓░ ░ ▓▓▓▓▓▓▒▓▓▓▓ ▓▓▓▓▓▓▓▒░░░░ ░ ",
|
|
42
|
+
" ▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░ ",
|
|
43
|
+
" ▓▒▒░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒░░ ▓ ",
|
|
44
|
+
" ░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒ ░░░ ",
|
|
45
|
+
" ░ ░░░░░░░░░░░▒▒░░ ░ ░ ░ ▓ ",
|
|
46
|
+
" ░░░░░▒▒▒▒░░▒░ ▒▒▒▒▒░░ ░░▒▒▒░░░░░░░░ ",
|
|
47
|
+
" ▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░ ▒░░▒▒▒▒▓▓▓▓░░░░ ",
|
|
48
|
+
" ░▒▒▒▒▒░ ░░▒▒▒▒▒▒▒▒░░░ ░░▒▓▓▓▓▒▒░░ ",
|
|
49
|
+
" ▓░░░░░░ ░ ░░░░░░░░░░░ ░░░░░░▓▒▒▒▒ ░",
|
|
50
|
+
" ░░░▒▒▓▓▓▓▓▒░░░░░░░░░░ ░░▒▒▒▒░░░░░░░▒▒░░ ",
|
|
51
|
+
" ░▒▒▒▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░░ ░▒▒▒▓▓▒░░░ ░░░▒ ",
|
|
52
|
+
" ▒░▒▒▒▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒░░░ ░▒▒▓▓▒░ ░ ",
|
|
53
|
+
" ░░▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒░░░░ ░▒▒▒▒▒ ░ ",
|
|
54
|
+
" ▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░ ▒▒░▒ ",
|
|
55
|
+
" ░░░░░░░░░░▒░░▒░▒▒░░░░ ░ ░░░░ ",
|
|
56
|
+
" ░░░░ ░░░░░░░░░░░ ░░░░ ",
|
|
57
|
+
" ░ ░░░░░░ ▓ ",
|
|
58
|
+
" ░░░░░ ░░░░░░ ",
|
|
59
|
+
" ░ ░ ",
|
|
46
60
|
] as const;
|
|
47
61
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
const HTML_MASCOT_MAP = [
|
|
63
|
+
" hhhhl ",
|
|
64
|
+
" dmmhhhmmlhm ",
|
|
65
|
+
" dmmmhhhdmmmmhh ",
|
|
66
|
+
" ldldllddmdldmmmmhhlhddmmmlhh ",
|
|
67
|
+
"hhhhhhhhmmmlm llllllllllllllllllmlldddmmmll ",
|
|
68
|
+
"hhmmmmlhhhhmmmdllllllllllllllllllllllllmddmmmll ",
|
|
69
|
+
"hhmmmddlhhhlmlllllllllllllllllllhlllllllllmmlld ",
|
|
70
|
+
" llmmmdddlmllllllllllllllllllhbhhbhhhhhllllllm ",
|
|
71
|
+
" dllmmmddllllllhhhhhlblllllhhhhmmmmmmhhhhlllmm ",
|
|
72
|
+
" dllmmmlllllhhhhhhhhhhllllhhmmmmmmmmmddddhdlllm ",
|
|
73
|
+
" lllllllhhhhmmmmmmmhllllldmdlllbhmmmmddddlhhhlldd ",
|
|
74
|
+
" mdlllhhlmmdhmmmddmmdmmddmmmmdldddddddddmmmhhhl ",
|
|
75
|
+
" lllhmddmlmhhmmmmmddmmmddddddddddddddddmmdmmmhhl ",
|
|
76
|
+
" mlldldddmlllddddddddlllldmddddddddddddmmmddmlll ",
|
|
77
|
+
" llhhmddddmmdddddddddhhmmmdbhdldddddddddmmddmlll ",
|
|
78
|
+
" llhhmmmdddddddddddddhhddddmhhhhhhhhhmmdddddlml ",
|
|
79
|
+
" hhhmmmmmddddddddldhhhhhhlhhhhmhhhhhhhlmmmmmm ",
|
|
80
|
+
" llllmddmddddddddhhhhhhhhhhlhhhhhhhlllmmmm ",
|
|
81
|
+
" hllllmddddddmhhhhhhhhhhhhhllllllmmmh ",
|
|
82
|
+
" dmmmmmmmmlllllllllllllmdmmm ",
|
|
83
|
+
" mmmmmmmmmmmmmmmlllmm ",
|
|
84
|
+
" dlmmmmllllmmlmmllllllm dmllllmmmmmmmm ",
|
|
85
|
+
" lllllmmlllllllllllllmmm lmllllllllhlmmmmm ",
|
|
86
|
+
" mlllllmdmlllllllllmmmd mddmmmmlhhhlllmmmmm ",
|
|
87
|
+
" hmmmmmmdmdmmmmmmmmmmm dddmmmmmmmmlllllmmdmm",
|
|
88
|
+
" mmmlllllhllllmmmmmmmm dmmllllmmmmmmmlllmdm ",
|
|
89
|
+
" mlllhhhhhhhhlllllllmmdddmlllhllmmmmmmlmml ",
|
|
90
|
+
" llllllhhhhhhhllllllllmmdddmmlllllmmmmdmmd ",
|
|
91
|
+
" mllllllllhhllllllllllmmmddmmmlllllmdddmm ",
|
|
92
|
+
" lmllmlllllllllllllllmmmmmddmmmllllmdddd ",
|
|
93
|
+
" lmmmmmmllllmllllllmmmdmdddddmmmmmddmd ",
|
|
94
|
+
" dmmmmmmd dmmmmmmmmmmmmddddmmmmmdd ",
|
|
95
|
+
" dmmmmmd dmmmmmmd h ",
|
|
96
|
+
" mmmmmmmm dmmmmmmm ",
|
|
97
|
+
" dmmmmmm ",
|
|
98
|
+
] as const;
|
|
52
99
|
|
|
53
|
-
|
|
100
|
+
const HEADER_CLASSIC = [
|
|
54
101
|
"░█████╗░░██████╗██████╗░██╗░░░██╗░░░░░░██████╗░██╗",
|
|
55
102
|
"██╔══██╗██╔════╝██╔══██╗╚██╗░██╔╝░░░░░░██╔══██╗ ║",
|
|
56
103
|
"██║░░██║╚█████╗░██║░░██║░╚████╔╝░█████╗██████╔╝██║",
|
|
@@ -64,11 +111,227 @@ export const HEADER = [
|
|
|
64
111
|
" ╱╱╱╱╱╱╱╰╯ </>",
|
|
65
112
|
] as const;
|
|
66
113
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
114
|
+
const HEADER_SIMPLE = [
|
|
115
|
+
" ███████ █████ ███████████ ███ ",
|
|
116
|
+
" ███░░░░░███ ░░███ ░░███░░░░░███ ░░░ ",
|
|
117
|
+
" ███ ░░███ █████ ███████ █████ ████ ░███ ░███ ████ ",
|
|
118
|
+
"░███ ░███ ███░░ ███░░███ ░░███ ░███ ██████████ ░██████████ ░░███ ",
|
|
119
|
+
"░███ ░███░░█████ ░███ ░███ ░███ ░███ ░░░░░░░░░░ ░███░░░░░░ ░███ ",
|
|
120
|
+
"░░███ ███ ░░░░███░███ ░███ ░███ ░███ ░███ ░███ ",
|
|
121
|
+
" ░░░███████░ ██████ ░░████████ ░░███████ █████ █████",
|
|
122
|
+
" ░░░░░░░ ░░░░░░ ░░░░░░░░ ░░░░░███ ░░░░░ ░░░░░ ",
|
|
123
|
+
" ███ ░███",
|
|
124
|
+
" ░░██████",
|
|
125
|
+
" ░░░░░░ </>",
|
|
126
|
+
] as const;
|
|
127
|
+
|
|
128
|
+
type HeaderPalette = {
|
|
129
|
+
baseColor: string;
|
|
130
|
+
highlightColor: string;
|
|
131
|
+
trailColor: string;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type MascotToneKey = "b" | "h" | "l" | "m" | "d" | "p" | "c" | "v";
|
|
135
|
+
|
|
136
|
+
export type MascotTonePalette = Record<MascotToneKey, string>;
|
|
137
|
+
|
|
138
|
+
type HeaderVariantConfig = {
|
|
139
|
+
label: string;
|
|
140
|
+
header: readonly string[];
|
|
141
|
+
headerMap?: readonly string[];
|
|
142
|
+
headerTonePalette?: MascotTonePalette;
|
|
143
|
+
minRowsForFull?: number;
|
|
144
|
+
fallbackHeader?: readonly string[];
|
|
145
|
+
fallbackLinePalette?: (lineIndex: number) => HeaderPalette;
|
|
146
|
+
mascot?: readonly string[];
|
|
147
|
+
mascotMap?: readonly string[];
|
|
148
|
+
linePalette: (lineIndex: number) => HeaderPalette;
|
|
149
|
+
mascotPalette: HeaderPalette;
|
|
150
|
+
mascotTonePalette?: MascotTonePalette;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const CLASSIC_PALETTE: HeaderPalette = {
|
|
154
|
+
baseColor: "accent",
|
|
155
|
+
highlightColor: "mdHeading",
|
|
156
|
+
trailColor: "mdLink",
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const CLASSIC_LINK_PALETTE: HeaderPalette = {
|
|
160
|
+
baseColor: "mdLink",
|
|
161
|
+
highlightColor: "mdHeading",
|
|
162
|
+
trailColor: "mdLink",
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const OSDY_THEME_PINK_PALETTE: HeaderPalette = {
|
|
166
|
+
baseColor: "accent",
|
|
167
|
+
highlightColor: "mdHeading",
|
|
168
|
+
trailColor: "mdLink",
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const OSDY_THEME_CYAN_PALETTE: HeaderPalette = {
|
|
172
|
+
baseColor: "mdLink",
|
|
173
|
+
highlightColor: "accent",
|
|
174
|
+
trailColor: "mdHeading",
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const OSDY_THEME_PURPLE_PALETTE: HeaderPalette = {
|
|
178
|
+
baseColor: "mdHeading",
|
|
179
|
+
highlightColor: "mdLink",
|
|
180
|
+
trailColor: "accent",
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const OSDY_THEME_MASCOT_PALETTE: HeaderPalette = {
|
|
184
|
+
baseColor: "mdLink",
|
|
185
|
+
highlightColor: "accent",
|
|
186
|
+
trailColor: "mdHeading",
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const HTML_MASCOT_TONES: MascotTonePalette = {
|
|
190
|
+
b: "mascotBg",
|
|
191
|
+
h: "mascotBright",
|
|
192
|
+
l: "mascotLight",
|
|
193
|
+
m: "mascotMid",
|
|
194
|
+
d: "mascotDark",
|
|
195
|
+
p: "htmlPink",
|
|
196
|
+
c: "mdLink",
|
|
197
|
+
v: "mdHeading",
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
type MascotArt = {
|
|
201
|
+
mascot: readonly string[];
|
|
202
|
+
toneMap: readonly string[];
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
function sampleIndex(
|
|
206
|
+
outputIndex: number,
|
|
207
|
+
outputCount: number,
|
|
208
|
+
inputCount: number,
|
|
209
|
+
): number {
|
|
210
|
+
if (outputCount <= 1) return 0;
|
|
211
|
+
return Math.min(
|
|
212
|
+
inputCount - 1,
|
|
213
|
+
Math.round((outputIndex * (inputCount - 1)) / (outputCount - 1)),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function sampleLine(
|
|
218
|
+
line: string,
|
|
219
|
+
outputWidth: number,
|
|
220
|
+
inputWidth: number,
|
|
221
|
+
): string {
|
|
222
|
+
const chars = Array.from(line.padEnd(inputWidth, " "));
|
|
223
|
+
return Array.from({ length: outputWidth }, (_value, outputIndex) => {
|
|
224
|
+
const inputIndex = sampleIndex(outputIndex, outputWidth, inputWidth);
|
|
225
|
+
return chars[inputIndex] ?? " ";
|
|
226
|
+
}).join("");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function scaleMascotArt(
|
|
230
|
+
mascot: readonly string[],
|
|
231
|
+
toneMap: readonly string[],
|
|
232
|
+
scale: number,
|
|
233
|
+
): MascotArt {
|
|
234
|
+
const inputRows = Math.min(mascot.length, toneMap.length);
|
|
235
|
+
const inputWidth = Math.max(
|
|
236
|
+
...mascot.slice(0, inputRows).map((line) => Array.from(line).length),
|
|
237
|
+
...toneMap.slice(0, inputRows).map((line) => Array.from(line).length),
|
|
238
|
+
);
|
|
239
|
+
const outputRows = Math.max(1, Math.round(inputRows * scale));
|
|
240
|
+
const outputWidth = Math.max(1, Math.round(inputWidth * scale));
|
|
241
|
+
|
|
242
|
+
return Array.from({ length: outputRows }, (_value, outputIndex) => {
|
|
243
|
+
const inputIndex = sampleIndex(outputIndex, outputRows, inputRows);
|
|
244
|
+
return {
|
|
245
|
+
mascot: sampleLine(mascot[inputIndex] ?? "", outputWidth, inputWidth),
|
|
246
|
+
toneMap: sampleLine(toneMap[inputIndex] ?? "", outputWidth, inputWidth),
|
|
247
|
+
};
|
|
248
|
+
}).reduce<MascotArt>(
|
|
249
|
+
(accumulator, line) => ({
|
|
250
|
+
mascot: [...accumulator.mascot, line.mascot],
|
|
251
|
+
toneMap: [...accumulator.toneMap, line.toneMap],
|
|
252
|
+
}),
|
|
253
|
+
{ mascot: [], toneMap: [] },
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function addRightEdgeGlow(
|
|
258
|
+
mascot: readonly string[],
|
|
259
|
+
toneMap: readonly string[],
|
|
260
|
+
): readonly string[] {
|
|
261
|
+
return toneMap.map((lineMap, lineIndex) => {
|
|
262
|
+
const chars = Array.from(lineMap);
|
|
263
|
+
const mascotLine = mascot[lineIndex] ?? "";
|
|
264
|
+
const rightEdgeIndex = Math.max(
|
|
265
|
+
...Array.from(mascotLine).map((char, index) =>
|
|
266
|
+
char === " " ? -1 : index,
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
if (rightEdgeIndex < 0) return lineMap;
|
|
270
|
+
const glowTones = ["p", "c", "v"] as const;
|
|
271
|
+
for (let offset = 0; offset < glowTones.length; offset += 1) {
|
|
272
|
+
const glowTone = glowTones[offset];
|
|
273
|
+
const index = rightEdgeIndex - offset;
|
|
274
|
+
if (
|
|
275
|
+
glowTone &&
|
|
276
|
+
index >= 0 &&
|
|
277
|
+
index < chars.length &&
|
|
278
|
+
chars[index] !== " "
|
|
279
|
+
) {
|
|
280
|
+
chars[index] = glowTone;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return chars.join("");
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const OSDY_THEME_MASCOT_ART = scaleMascotArt(HTML_MASCOT, HTML_MASCOT_MAP, 0.9);
|
|
288
|
+
const OSDY_THEME_MASCOT_MAP = addRightEdgeGlow(
|
|
289
|
+
OSDY_THEME_MASCOT_ART.mascot,
|
|
290
|
+
OSDY_THEME_MASCOT_ART.toneMap,
|
|
70
291
|
);
|
|
71
292
|
|
|
293
|
+
function osdyThemePalette(lineIndex: number): HeaderPalette {
|
|
294
|
+
if (lineIndex < 4) return OSDY_THEME_PINK_PALETTE;
|
|
295
|
+
if (lineIndex < 8) return OSDY_THEME_CYAN_PALETTE;
|
|
296
|
+
return OSDY_THEME_PURPLE_PALETTE;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const HEADER_VARIANTS: Record<HeaderVariant, HeaderVariantConfig> = {
|
|
300
|
+
"osdy-theme": {
|
|
301
|
+
label: "OsdyTheme",
|
|
302
|
+
header: HEADER_SIMPLE,
|
|
303
|
+
mascot: OSDY_THEME_MASCOT_ART.mascot,
|
|
304
|
+
mascotMap: OSDY_THEME_MASCOT_MAP,
|
|
305
|
+
linePalette: osdyThemePalette,
|
|
306
|
+
mascotPalette: OSDY_THEME_MASCOT_PALETTE,
|
|
307
|
+
mascotTonePalette: HTML_MASCOT_TONES,
|
|
308
|
+
},
|
|
309
|
+
classic: {
|
|
310
|
+
label: "Classic",
|
|
311
|
+
header: HEADER_CLASSIC,
|
|
312
|
+
mascot: HTML_MASCOT,
|
|
313
|
+
mascotMap: HTML_MASCOT_MAP,
|
|
314
|
+
linePalette: (lineIndex) =>
|
|
315
|
+
lineIndex >= 6 ? CLASSIC_LINK_PALETTE : CLASSIC_PALETTE,
|
|
316
|
+
mascotPalette: OSDY_THEME_MASCOT_PALETTE,
|
|
317
|
+
mascotTonePalette: HTML_MASCOT_TONES,
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export function headerWidth(variant: HeaderVariant): number {
|
|
322
|
+
return HEADER_VARIANTS[variant].header.reduce(
|
|
323
|
+
(maxWidth, line) => Math.max(maxWidth, visibleWidth(line)),
|
|
324
|
+
0,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function mascotWidth(variant: HeaderVariant): number {
|
|
329
|
+
return (HEADER_VARIANTS[variant].mascot ?? []).reduce(
|
|
330
|
+
(maxWidth, line) => Math.max(maxWidth, visibleWidth(line)),
|
|
331
|
+
0,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
72
335
|
export const HEADER_FALLBACK = [
|
|
73
336
|
"OSDY - PI",
|
|
74
337
|
"</> Compilador de ideas",
|
|
@@ -95,7 +95,7 @@ export function disableOsdyPi(ctx: ExtensionContext, state: OsdyState): void {
|
|
|
95
95
|
|
|
96
96
|
export function notifyStatus(ctx: ExtensionContext, state: OsdyState): void {
|
|
97
97
|
ctx.ui.notify(
|
|
98
|
-
`osdy-pi ${state.enabled ? "enabled" : "disabled"} · theme ${ctx.ui.theme.name ?? "unknown"} · animation ${asciiAnimationMode()} · ${modelLabel(ctx)} · ${usageLabel(ctx).trim()}`,
|
|
98
|
+
`osdy-pi ${state.enabled ? "enabled" : "disabled"} · theme ${ctx.ui.theme.name ?? "unknown"} · style ${state.headerVariant} · animation ${asciiAnimationMode()} · ${modelLabel(ctx)} · ${usageLabel(ctx).trim()}`,
|
|
99
99
|
"info",
|
|
100
100
|
);
|
|
101
101
|
}
|