@sheepbun/yips 0.1.1
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/dist/agent/commands/command-catalog.js +243 -0
- package/dist/agent/commands/commands.js +418 -0
- package/dist/agent/conductor.js +118 -0
- package/dist/agent/context/code-context.js +68 -0
- package/dist/agent/context/memory-store.js +159 -0
- package/dist/agent/context/session-store.js +211 -0
- package/dist/agent/protocol/tool-protocol.js +160 -0
- package/dist/agent/skills/skills.js +327 -0
- package/dist/agent/tools/tool-executor.js +415 -0
- package/dist/agent/tools/tool-safety.js +52 -0
- package/dist/app/index.js +35 -0
- package/dist/app/repl.js +105 -0
- package/dist/app/update-check.js +132 -0
- package/dist/app/version.js +51 -0
- package/dist/code-context.js +68 -0
- package/dist/colors.js +204 -0
- package/dist/command-catalog.js +242 -0
- package/dist/commands.js +350 -0
- package/dist/conductor.js +94 -0
- package/dist/config/config.js +335 -0
- package/dist/config/hooks.js +187 -0
- package/dist/config.js +335 -0
- package/dist/downloader-state.js +302 -0
- package/dist/downloader-ui.js +289 -0
- package/dist/gateway/adapters/discord.js +108 -0
- package/dist/gateway/adapters/formatting.js +96 -0
- package/dist/gateway/adapters/telegram.js +106 -0
- package/dist/gateway/adapters/types.js +2 -0
- package/dist/gateway/adapters/whatsapp.js +124 -0
- package/dist/gateway/auth-policy.js +66 -0
- package/dist/gateway/core.js +87 -0
- package/dist/gateway/headless-conductor.js +328 -0
- package/dist/gateway/message-router.js +23 -0
- package/dist/gateway/rate-limiter.js +48 -0
- package/dist/gateway/runtime/backend-policy.js +18 -0
- package/dist/gateway/runtime/discord-bot.js +104 -0
- package/dist/gateway/runtime/discord-main.js +69 -0
- package/dist/gateway/session-manager.js +77 -0
- package/dist/gateway/types.js +2 -0
- package/dist/hardware.js +92 -0
- package/dist/hooks.js +187 -0
- package/dist/index.js +34 -0
- package/dist/input-engine.js +250 -0
- package/dist/llama-client.js +227 -0
- package/dist/llama-server.js +620 -0
- package/dist/llm/llama-client.js +227 -0
- package/dist/llm/llama-server.js +620 -0
- package/dist/llm/token-counter.js +47 -0
- package/dist/memory-store.js +159 -0
- package/dist/messages.js +59 -0
- package/dist/model-downloader.js +382 -0
- package/dist/model-manager-state.js +118 -0
- package/dist/model-manager-ui.js +194 -0
- package/dist/model-manager.js +190 -0
- package/dist/models/hardware.js +92 -0
- package/dist/models/model-downloader.js +382 -0
- package/dist/models/model-manager.js +190 -0
- package/dist/prompt-box.js +78 -0
- package/dist/prompt-composer.js +498 -0
- package/dist/repl.js +105 -0
- package/dist/session-store.js +211 -0
- package/dist/spinner.js +76 -0
- package/dist/title-box.js +388 -0
- package/dist/token-counter.js +47 -0
- package/dist/tool-executor.js +415 -0
- package/dist/tool-protocol.js +121 -0
- package/dist/tool-safety.js +52 -0
- package/dist/tui/app.js +2553 -0
- package/dist/tui/startup.js +56 -0
- package/dist/tui-input-routing.js +53 -0
- package/dist/tui.js +51 -0
- package/dist/types/app-types.js +2 -0
- package/dist/types.js +2 -0
- package/dist/ui/colors.js +204 -0
- package/dist/ui/downloader/downloader-state.js +302 -0
- package/dist/ui/downloader/downloader-ui.js +289 -0
- package/dist/ui/input/input-engine.js +250 -0
- package/dist/ui/input/tui-input-routing.js +53 -0
- package/dist/ui/input/vt-session.js +168 -0
- package/dist/ui/messages.js +59 -0
- package/dist/ui/model-manager/model-manager-state.js +118 -0
- package/dist/ui/model-manager/model-manager-ui.js +194 -0
- package/dist/ui/prompt/prompt-box.js +78 -0
- package/dist/ui/prompt/prompt-composer.js +498 -0
- package/dist/ui/spinner.js +76 -0
- package/dist/ui/title-box.js +388 -0
- package/dist/ui/tui/app.js +6 -0
- package/dist/ui/tui/autocomplete.js +85 -0
- package/dist/ui/tui/constants.js +18 -0
- package/dist/ui/tui/history.js +29 -0
- package/dist/ui/tui/layout.js +341 -0
- package/dist/ui/tui/runtime-core.js +2584 -0
- package/dist/ui/tui/runtime-utils.js +53 -0
- package/dist/ui/tui/start-tui.js +54 -0
- package/dist/ui/tui/startup.js +56 -0
- package/dist/version.js +51 -0
- package/dist/vt-session.js +168 -0
- package/install.sh +457 -0
- package/package.json +128 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.slugifySessionNameFromMessage = slugifySessionNameFromMessage;
|
|
4
|
+
exports.getFirstUserMessage = getFirstUserMessage;
|
|
5
|
+
exports.createSessionFileFromHistory = createSessionFileFromHistory;
|
|
6
|
+
exports.writeSessionFile = writeSessionFile;
|
|
7
|
+
exports.listSessions = listSessions;
|
|
8
|
+
exports.loadSession = loadSession;
|
|
9
|
+
const promises_1 = require("node:fs/promises");
|
|
10
|
+
const node_os_1 = require("node:os");
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const SESSION_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".yips", "memory");
|
|
13
|
+
function pad2(value) {
|
|
14
|
+
return value.toString().padStart(2, "0");
|
|
15
|
+
}
|
|
16
|
+
function toSessionTimestamp(now) {
|
|
17
|
+
return [
|
|
18
|
+
now.getFullYear(),
|
|
19
|
+
"-",
|
|
20
|
+
pad2(now.getMonth() + 1),
|
|
21
|
+
"-",
|
|
22
|
+
pad2(now.getDate()),
|
|
23
|
+
"_",
|
|
24
|
+
pad2(now.getHours()),
|
|
25
|
+
"-",
|
|
26
|
+
pad2(now.getMinutes()),
|
|
27
|
+
"-",
|
|
28
|
+
pad2(now.getSeconds())
|
|
29
|
+
].join("");
|
|
30
|
+
}
|
|
31
|
+
function toDisplayTitle(sessionName) {
|
|
32
|
+
const words = sessionName
|
|
33
|
+
.replace(/_/g, " ")
|
|
34
|
+
.trim()
|
|
35
|
+
.split(/\s+/u)
|
|
36
|
+
.filter((word) => word.length > 0);
|
|
37
|
+
if (words.length === 0) {
|
|
38
|
+
return "Session";
|
|
39
|
+
}
|
|
40
|
+
return words
|
|
41
|
+
.map((word) => `${word[0]?.toUpperCase() ?? ""}${word.slice(1).toLowerCase()}`)
|
|
42
|
+
.join(" ");
|
|
43
|
+
}
|
|
44
|
+
function parseFileStem(path) {
|
|
45
|
+
const stem = (0, node_path_1.basename)(path, ".md");
|
|
46
|
+
const parts = stem.split("_", 3);
|
|
47
|
+
if (parts.length >= 3) {
|
|
48
|
+
const datePart = parts[0] ?? "";
|
|
49
|
+
const timePart = parts[1] ?? "";
|
|
50
|
+
const sessionName = stem.slice(`${datePart}_${timePart}_`.length);
|
|
51
|
+
if (datePart.includes("-")) {
|
|
52
|
+
const dt = new Date(`${datePart}T${timePart.replace(/-/g, ":")}`);
|
|
53
|
+
if (!Number.isNaN(dt.valueOf())) {
|
|
54
|
+
return { timestamp: dt, sessionName };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (datePart.length === 8 && timePart.length >= 6) {
|
|
58
|
+
const year = Number.parseInt(datePart.slice(0, 4), 10);
|
|
59
|
+
const month = Number.parseInt(datePart.slice(4, 6), 10);
|
|
60
|
+
const day = Number.parseInt(datePart.slice(6, 8), 10);
|
|
61
|
+
const hour = Number.parseInt(timePart.slice(0, 2), 10);
|
|
62
|
+
const minute = Number.parseInt(timePart.slice(2, 4), 10);
|
|
63
|
+
const second = Number.parseInt(timePart.slice(4, 6), 10);
|
|
64
|
+
const dt = new Date(year, month - 1, day, hour, minute, second);
|
|
65
|
+
if (!Number.isNaN(dt.valueOf())) {
|
|
66
|
+
return { timestamp: dt, sessionName };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
timestamp: new Date(0),
|
|
72
|
+
sessionName: stem
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function formatDisplayTimestamp(timestamp) {
|
|
76
|
+
const year = timestamp.getFullYear();
|
|
77
|
+
const month = timestamp.getMonth() + 1;
|
|
78
|
+
const day = timestamp.getDate();
|
|
79
|
+
const hours = timestamp.getHours();
|
|
80
|
+
const minutes = pad2(timestamp.getMinutes());
|
|
81
|
+
const amPm = hours < 12 ? "AM" : "PM";
|
|
82
|
+
const displayHour = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
|
83
|
+
return `${year}-${month}-${day} @ ${displayHour.toString().padStart(2, " ")}:${minutes} ${amPm}`;
|
|
84
|
+
}
|
|
85
|
+
function toDisplay(sessionName, timestamp) {
|
|
86
|
+
return `${formatDisplayTimestamp(timestamp)}: ${toDisplayTitle(sessionName)}`;
|
|
87
|
+
}
|
|
88
|
+
function parseConversation(content) {
|
|
89
|
+
const marker = "## Conversation";
|
|
90
|
+
const markerIndex = content.indexOf(marker);
|
|
91
|
+
const section = markerIndex >= 0
|
|
92
|
+
? content.slice(markerIndex + marker.length).split("\n---")[0] ?? ""
|
|
93
|
+
: content;
|
|
94
|
+
const history = [];
|
|
95
|
+
const lines = section.split(/\r?\n/u);
|
|
96
|
+
for (const rawLine of lines) {
|
|
97
|
+
const line = rawLine.trimEnd();
|
|
98
|
+
const roleMatch = line.match(/^\*\*([^*]+)\*\*:\s*(.*)$/u);
|
|
99
|
+
if (roleMatch) {
|
|
100
|
+
const roleName = (roleMatch[1] ?? "").trim();
|
|
101
|
+
const text = roleMatch[2] ?? "";
|
|
102
|
+
if (roleName.toLowerCase() === "yips") {
|
|
103
|
+
history.push({ role: "assistant", content: text });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
history.push({ role: "user", content: text });
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const systemMatch = line.match(/^\*\[System:\s*(.*)\]\*$/u);
|
|
111
|
+
if (systemMatch) {
|
|
112
|
+
history.push({ role: "system", content: systemMatch[1] ?? "" });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (line.trim().length === 0) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const last = history[history.length - 1];
|
|
119
|
+
if (last) {
|
|
120
|
+
last.content = `${last.content}\n${line}`.trimEnd();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return history;
|
|
124
|
+
}
|
|
125
|
+
function slugifySessionNameFromMessage(message) {
|
|
126
|
+
const base = message.toLowerCase().trim().replace(/[^a-z0-9\s]/g, "");
|
|
127
|
+
if (base.length === 0) {
|
|
128
|
+
return "session";
|
|
129
|
+
}
|
|
130
|
+
const slug = base.replace(/\s+/g, "_").slice(0, 50).replace(/_+$/g, "");
|
|
131
|
+
return slug.length > 0 ? slug : "session";
|
|
132
|
+
}
|
|
133
|
+
function getFirstUserMessage(history) {
|
|
134
|
+
for (const message of history) {
|
|
135
|
+
if (message.role === "user") {
|
|
136
|
+
const trimmed = message.content.trim();
|
|
137
|
+
if (trimmed.length > 0) {
|
|
138
|
+
return trimmed;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
async function createSessionFileFromHistory(history, now = new Date()) {
|
|
145
|
+
await (0, promises_1.mkdir)(SESSION_DIR, { recursive: true });
|
|
146
|
+
const firstUserMessage = getFirstUserMessage(history) ?? "";
|
|
147
|
+
const sessionName = slugifySessionNameFromMessage(firstUserMessage);
|
|
148
|
+
const path = (0, node_path_1.join)(SESSION_DIR, `${toSessionTimestamp(now)}_${sessionName}.md`);
|
|
149
|
+
return { path, sessionName };
|
|
150
|
+
}
|
|
151
|
+
async function writeSessionFile(options) {
|
|
152
|
+
const now = options.now ?? new Date();
|
|
153
|
+
const lines = [];
|
|
154
|
+
for (const message of options.history) {
|
|
155
|
+
if (message.role === "user") {
|
|
156
|
+
lines.push(`**${options.username}**: ${message.content}`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (message.role === "assistant") {
|
|
160
|
+
lines.push(`**Yips**: ${message.content}`);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
lines.push(`*[System: ${message.content}]*`);
|
|
164
|
+
}
|
|
165
|
+
const body = [
|
|
166
|
+
"# Session Memory",
|
|
167
|
+
"",
|
|
168
|
+
`**Created**: ${now.toISOString()}`,
|
|
169
|
+
"**Type**: Ongoing Session",
|
|
170
|
+
"",
|
|
171
|
+
"## Conversation",
|
|
172
|
+
"",
|
|
173
|
+
...lines,
|
|
174
|
+
"",
|
|
175
|
+
"---",
|
|
176
|
+
`*Last updated: ${now.toISOString()}*`,
|
|
177
|
+
""
|
|
178
|
+
].join("\n");
|
|
179
|
+
await (0, promises_1.writeFile)(options.path, body, "utf8");
|
|
180
|
+
}
|
|
181
|
+
async function listSessions(limit) {
|
|
182
|
+
try {
|
|
183
|
+
const entries = await (0, promises_1.readdir)(SESSION_DIR, { withFileTypes: true });
|
|
184
|
+
const sessionItems = entries
|
|
185
|
+
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md"))
|
|
186
|
+
.map((entry) => {
|
|
187
|
+
const path = (0, node_path_1.join)(SESSION_DIR, entry.name);
|
|
188
|
+
const parsed = parseFileStem(path);
|
|
189
|
+
return {
|
|
190
|
+
path,
|
|
191
|
+
sessionName: parsed.sessionName,
|
|
192
|
+
timestamp: parsed.timestamp,
|
|
193
|
+
display: toDisplay(parsed.sessionName, parsed.timestamp)
|
|
194
|
+
};
|
|
195
|
+
})
|
|
196
|
+
.sort((a, b) => b.timestamp.valueOf() - a.timestamp.valueOf());
|
|
197
|
+
return typeof limit === "number" && limit > 0 ? sessionItems.slice(0, limit) : sessionItems;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function loadSession(path) {
|
|
204
|
+
const content = await (0, promises_1.readFile)(path, "utf8");
|
|
205
|
+
const parsed = parseFileStem(path);
|
|
206
|
+
return {
|
|
207
|
+
history: parseConversation(content),
|
|
208
|
+
sessionName: parsed.sessionName,
|
|
209
|
+
path
|
|
210
|
+
};
|
|
211
|
+
}
|
package/dist/spinner.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Pulsing braille spinner with color oscillation. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.PulsingSpinner = void 0;
|
|
5
|
+
const colors_1 = require("./colors");
|
|
6
|
+
const SPINNER_FRAMES = ["⠹", "⢸", "⣰", "⣤", "⣆", "⡇", "⠏", "⠛"];
|
|
7
|
+
const FRAME_COUNT = SPINNER_FRAMES.length;
|
|
8
|
+
const OSCILLATION_RATE = 2.0;
|
|
9
|
+
const FRAME_INTERVAL_MS = 80;
|
|
10
|
+
class PulsingSpinner {
|
|
11
|
+
label;
|
|
12
|
+
frameIndex;
|
|
13
|
+
startTime;
|
|
14
|
+
lastFrameTime;
|
|
15
|
+
active;
|
|
16
|
+
constructor(label = "Thinking...") {
|
|
17
|
+
this.label = label;
|
|
18
|
+
this.frameIndex = 0;
|
|
19
|
+
this.startTime = Date.now();
|
|
20
|
+
this.lastFrameTime = this.startTime;
|
|
21
|
+
this.active = false;
|
|
22
|
+
}
|
|
23
|
+
start(label) {
|
|
24
|
+
if (label !== undefined) {
|
|
25
|
+
this.label = label;
|
|
26
|
+
}
|
|
27
|
+
this.frameIndex = 0;
|
|
28
|
+
this.startTime = Date.now();
|
|
29
|
+
this.lastFrameTime = this.startTime;
|
|
30
|
+
this.active = true;
|
|
31
|
+
}
|
|
32
|
+
stop() {
|
|
33
|
+
this.active = false;
|
|
34
|
+
}
|
|
35
|
+
update(label) {
|
|
36
|
+
this.label = label;
|
|
37
|
+
}
|
|
38
|
+
isActive() {
|
|
39
|
+
return this.active;
|
|
40
|
+
}
|
|
41
|
+
getElapsed() {
|
|
42
|
+
return Math.floor((Date.now() - this.startTime) / 1000);
|
|
43
|
+
}
|
|
44
|
+
formatElapsed(seconds) {
|
|
45
|
+
const minutes = Math.floor(seconds / 60);
|
|
46
|
+
const remainder = seconds % 60;
|
|
47
|
+
if (minutes === 0) {
|
|
48
|
+
return `${remainder}s`;
|
|
49
|
+
}
|
|
50
|
+
return `${minutes}m ${remainder}s`;
|
|
51
|
+
}
|
|
52
|
+
render() {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
const elapsedSinceFrame = now - this.lastFrameTime;
|
|
55
|
+
if (elapsedSinceFrame >= FRAME_INTERVAL_MS) {
|
|
56
|
+
const frameSteps = Math.floor(elapsedSinceFrame / FRAME_INTERVAL_MS);
|
|
57
|
+
this.frameIndex = (this.frameIndex + frameSteps) % FRAME_COUNT;
|
|
58
|
+
this.lastFrameTime += frameSteps * FRAME_INTERVAL_MS;
|
|
59
|
+
}
|
|
60
|
+
const frame = SPINNER_FRAMES[this.frameIndex % FRAME_COUNT];
|
|
61
|
+
const elapsedSeconds = Math.max(0, (now - this.startTime) / 1000);
|
|
62
|
+
const elapsed = Math.floor(elapsedSeconds);
|
|
63
|
+
const t = (Math.sin(elapsedSeconds * OSCILLATION_RATE) + 1) / 2;
|
|
64
|
+
const color = (0, colors_1.interpolateColor)(colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, t);
|
|
65
|
+
const timeText = this.formatElapsed(elapsed);
|
|
66
|
+
const spinnerChar = (0, colors_1.colorText)(frame, color);
|
|
67
|
+
const labelText = (0, colors_1.colorText)(this.label, color);
|
|
68
|
+
const suffix = (0, colors_1.colorText)(`(${timeText})`, color);
|
|
69
|
+
return `${spinnerChar} ${labelText} ${suffix}`;
|
|
70
|
+
}
|
|
71
|
+
static computeOscillationColor(elapsedSeconds) {
|
|
72
|
+
const t = (Math.sin(elapsedSeconds * OSCILLATION_RATE) + 1) / 2;
|
|
73
|
+
return (0, colors_1.interpolateColor)(colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, t);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.PulsingSpinner = PulsingSpinner;
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Responsive title box using the yips-cli visual contract. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.stripMarkup = stripMarkup;
|
|
5
|
+
exports.renderTitleBox = renderTitleBox;
|
|
6
|
+
exports.getLayoutMode = getLayoutMode;
|
|
7
|
+
const colors_1 = require("./colors");
|
|
8
|
+
const YIPS_LOGO = [
|
|
9
|
+
"██╗ ██╗██╗██████╗ ███████╗",
|
|
10
|
+
"╚██╗ ██╔╝██║██╔══██╗██╔════╝",
|
|
11
|
+
" ╚████╔╝ ██║██████╔╝███████╗",
|
|
12
|
+
" ╚██╔╝ ██║██╔═══╝ ╚════██║",
|
|
13
|
+
" ██║ ██║██║ ███████║",
|
|
14
|
+
" ╚═╝ ╚═╝╚═╝ ╚══════╝"
|
|
15
|
+
];
|
|
16
|
+
const LOGO_WIDTH = 28;
|
|
17
|
+
const TOP_TITLE = "Yips";
|
|
18
|
+
const TOP_TITLE_FALLBACK = "Yips";
|
|
19
|
+
const TOP_BORDER_MIN_WIDTH = 25;
|
|
20
|
+
function getLayoutMode(width) {
|
|
21
|
+
if (width >= 80)
|
|
22
|
+
return "full";
|
|
23
|
+
if (width >= 60)
|
|
24
|
+
return "single";
|
|
25
|
+
if (width >= 45)
|
|
26
|
+
return "compact";
|
|
27
|
+
return "minimal";
|
|
28
|
+
}
|
|
29
|
+
function centerText(text, width) {
|
|
30
|
+
if (width <= 0)
|
|
31
|
+
return "";
|
|
32
|
+
if (text.length >= width)
|
|
33
|
+
return text.slice(0, width);
|
|
34
|
+
const leftPadding = Math.floor((width - text.length) / 2);
|
|
35
|
+
const rightPadding = width - text.length - leftPadding;
|
|
36
|
+
return `${" ".repeat(leftPadding)}${text}${" ".repeat(rightPadding)}`;
|
|
37
|
+
}
|
|
38
|
+
function fitText(text, width) {
|
|
39
|
+
if (width <= 0)
|
|
40
|
+
return "";
|
|
41
|
+
if (text.length >= width)
|
|
42
|
+
return text.slice(0, width);
|
|
43
|
+
return text.padEnd(width, " ");
|
|
44
|
+
}
|
|
45
|
+
function toSingleColor(char, progress) {
|
|
46
|
+
return (0, colors_1.colorChar)(char, (0, colors_1.interpolateColor)(colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, progress));
|
|
47
|
+
}
|
|
48
|
+
function makeTopBorder(version, width) {
|
|
49
|
+
if (width <= 0)
|
|
50
|
+
return "";
|
|
51
|
+
const fallback = `╭${"─".repeat(Math.max(0, width - 2))}╮`;
|
|
52
|
+
if (width < TOP_BORDER_MIN_WIDTH) {
|
|
53
|
+
return (0, colors_1.horizontalGradient)(fallback, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW);
|
|
54
|
+
}
|
|
55
|
+
let title = TOP_TITLE;
|
|
56
|
+
let titleLength = title.length + 1 + version.length;
|
|
57
|
+
let borderAvailable = width - titleLength - 7;
|
|
58
|
+
if (borderAvailable < 0) {
|
|
59
|
+
title = TOP_TITLE_FALLBACK;
|
|
60
|
+
titleLength = title.length + 1 + version.length;
|
|
61
|
+
borderAvailable = width - titleLength - 7;
|
|
62
|
+
if (borderAvailable < 0) {
|
|
63
|
+
return (0, colors_1.horizontalGradient)(fallback, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const pieces = [];
|
|
67
|
+
let position = 0;
|
|
68
|
+
const appendBorder = (segment) => {
|
|
69
|
+
for (const char of segment) {
|
|
70
|
+
const progress = position / Math.max(width - 1, 1);
|
|
71
|
+
pieces.push(toSingleColor(char, progress));
|
|
72
|
+
position += 1;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
appendBorder("╭─── ");
|
|
76
|
+
pieces.push("\u001b[1m");
|
|
77
|
+
for (let i = 0; i < title.length; i++) {
|
|
78
|
+
const progress = i / Math.max(title.length - 1, 1);
|
|
79
|
+
pieces.push(toSingleColor(title[i], progress));
|
|
80
|
+
position += 1;
|
|
81
|
+
}
|
|
82
|
+
pieces.push(" ");
|
|
83
|
+
position += 1;
|
|
84
|
+
pieces.push((0, colors_1.colorText)(version, colors_1.GRADIENT_BLUE));
|
|
85
|
+
position += version.length;
|
|
86
|
+
pieces.push("\u001b[22m");
|
|
87
|
+
const closing = ` ${"─".repeat(Math.max(0, borderAvailable))}╮`;
|
|
88
|
+
appendBorder(closing);
|
|
89
|
+
return pieces.join("");
|
|
90
|
+
}
|
|
91
|
+
/** Strip ANSI and legacy terminal-kit markup sequences for test/plain rendering. */
|
|
92
|
+
function stripMarkup(text) {
|
|
93
|
+
return (0, colors_1.stripAnsi)(text).replace(/\^\[#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\]|\^#[0-9a-fA-F]{6}|\^:/g, "");
|
|
94
|
+
}
|
|
95
|
+
function makeBottomBorder(sessionName, width) {
|
|
96
|
+
if (width <= 0)
|
|
97
|
+
return "";
|
|
98
|
+
const borderChars = Array.from({ length: Math.max(0, width - 2) }, () => "─");
|
|
99
|
+
const trimmedSessionName = sessionName.trim();
|
|
100
|
+
if (trimmedSessionName.length === 0) {
|
|
101
|
+
return (0, colors_1.horizontalGradient)(`╰${borderChars.join("")}╯`, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW);
|
|
102
|
+
}
|
|
103
|
+
const displayName = ` ${trimmedSessionName.replace(/_/g, " ")} `;
|
|
104
|
+
if (displayName.length <= borderChars.length) {
|
|
105
|
+
const start = Math.floor((borderChars.length - displayName.length) / 2);
|
|
106
|
+
for (let i = 0; i < displayName.length; i++) {
|
|
107
|
+
borderChars[start + i] = displayName[i];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return (0, colors_1.horizontalGradient)(`╰${borderChars.join("")}╯`, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW);
|
|
111
|
+
}
|
|
112
|
+
function buildModelInfo(backend, model, tokenUsage) {
|
|
113
|
+
const trimmedModel = model.trim();
|
|
114
|
+
if (trimmedModel.length === 0) {
|
|
115
|
+
return backend;
|
|
116
|
+
}
|
|
117
|
+
const parts = [backend, trimmedModel];
|
|
118
|
+
const usage = tokenUsage.trim();
|
|
119
|
+
if (usage.length > 0) {
|
|
120
|
+
parts.push(usage);
|
|
121
|
+
}
|
|
122
|
+
return parts.join(" · ");
|
|
123
|
+
}
|
|
124
|
+
function padLine(markup, plain, width) {
|
|
125
|
+
const clippedPlain = fitText(plain, width);
|
|
126
|
+
if (plain.length > width) {
|
|
127
|
+
return clippedPlain;
|
|
128
|
+
}
|
|
129
|
+
const padding = " ".repeat(Math.max(0, width - plain.length));
|
|
130
|
+
return `${markup}${padding}`;
|
|
131
|
+
}
|
|
132
|
+
function makeSingleRow(contentMarkup, contentPlain, width) {
|
|
133
|
+
const innerWidth = Math.max(0, width - 2);
|
|
134
|
+
const leftBorder = (0, colors_1.horizontalGradient)("│", colors_1.GRADIENT_PINK, colors_1.GRADIENT_PINK);
|
|
135
|
+
const rightBorder = (0, colors_1.horizontalGradient)("│", colors_1.GRADIENT_YELLOW, colors_1.GRADIENT_YELLOW);
|
|
136
|
+
return `${leftBorder}${padLine(contentMarkup, contentPlain, innerWidth)}${rightBorder}`;
|
|
137
|
+
}
|
|
138
|
+
function styleCenteredText(text, width, style) {
|
|
139
|
+
const centered = centerText(text, width);
|
|
140
|
+
switch (style) {
|
|
141
|
+
case "gradient":
|
|
142
|
+
return {
|
|
143
|
+
markup: (0, colors_1.horizontalGradient)(centered, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW),
|
|
144
|
+
plain: centered
|
|
145
|
+
};
|
|
146
|
+
case "blue":
|
|
147
|
+
return { markup: (0, colors_1.colorText)(centered, colors_1.GRADIENT_BLUE), plain: centered };
|
|
148
|
+
case "pink":
|
|
149
|
+
return { markup: (0, colors_1.colorText)(centered, colors_1.INPUT_PINK), plain: centered };
|
|
150
|
+
case "dim":
|
|
151
|
+
return { markup: (0, colors_1.colorText)(centered, colors_1.DIM_GRAY), plain: centered };
|
|
152
|
+
case "plain":
|
|
153
|
+
return { markup: centered, plain: centered };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function styleCenteredTextWithGradientSpan(text, width) {
|
|
157
|
+
if (width <= 0)
|
|
158
|
+
return { markup: "", plain: "" };
|
|
159
|
+
const clipped = text.slice(0, width);
|
|
160
|
+
if (clipped.length >= width) {
|
|
161
|
+
return {
|
|
162
|
+
markup: (0, colors_1.horizontalGradient)(clipped, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW),
|
|
163
|
+
plain: clipped
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const leftPadding = Math.floor((width - clipped.length) / 2);
|
|
167
|
+
const rightPadding = width - clipped.length - leftPadding;
|
|
168
|
+
return {
|
|
169
|
+
markup: `${" ".repeat(leftPadding)}${(0, colors_1.horizontalGradient)(clipped, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}${" ".repeat(rightPadding)}`,
|
|
170
|
+
plain: `${" ".repeat(leftPadding)}${clipped}${" ".repeat(rightPadding)}`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function withBold(row) {
|
|
174
|
+
if (row.markup.length === 0)
|
|
175
|
+
return row;
|
|
176
|
+
return { markup: `\u001b[1m${row.markup}\u001b[22m`, plain: row.plain };
|
|
177
|
+
}
|
|
178
|
+
function styleLeftText(text, width, style) {
|
|
179
|
+
const clipped = text.slice(0, Math.max(0, width));
|
|
180
|
+
const padded = fitText(clipped, width);
|
|
181
|
+
switch (style) {
|
|
182
|
+
case "gradient":
|
|
183
|
+
if (clipped.length === 0)
|
|
184
|
+
return { markup: padded, plain: padded };
|
|
185
|
+
return {
|
|
186
|
+
markup: `${(0, colors_1.horizontalGradient)(clipped, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}${" ".repeat(Math.max(0, width - clipped.length))}`,
|
|
187
|
+
plain: padded
|
|
188
|
+
};
|
|
189
|
+
case "blue":
|
|
190
|
+
if (clipped.length === 0)
|
|
191
|
+
return { markup: padded, plain: padded };
|
|
192
|
+
return {
|
|
193
|
+
markup: `${(0, colors_1.colorText)(clipped, colors_1.GRADIENT_BLUE)}${" ".repeat(Math.max(0, width - clipped.length))}`,
|
|
194
|
+
plain: padded
|
|
195
|
+
};
|
|
196
|
+
case "white":
|
|
197
|
+
if (clipped.length === 0)
|
|
198
|
+
return { markup: padded, plain: padded };
|
|
199
|
+
return {
|
|
200
|
+
markup: `${(0, colors_1.colorText)(clipped, { r: 255, g: 255, b: 255 })}${" ".repeat(Math.max(0, width - clipped.length))}`,
|
|
201
|
+
plain: padded
|
|
202
|
+
};
|
|
203
|
+
case "dim":
|
|
204
|
+
if (clipped.length === 0)
|
|
205
|
+
return { markup: padded, plain: padded };
|
|
206
|
+
return {
|
|
207
|
+
markup: `${(0, colors_1.colorText)(clipped, colors_1.DIM_GRAY)}${" ".repeat(Math.max(0, width - clipped.length))}`,
|
|
208
|
+
plain: padded
|
|
209
|
+
};
|
|
210
|
+
case "plain":
|
|
211
|
+
return { markup: padded, plain: padded };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function styleHighlightedText(text, width) {
|
|
215
|
+
const clipped = text.slice(0, Math.max(0, width));
|
|
216
|
+
const padded = fitText(clipped, width);
|
|
217
|
+
if (clipped.length === 0)
|
|
218
|
+
return { markup: padded, plain: padded };
|
|
219
|
+
return {
|
|
220
|
+
markup: `\u001b[1m${(0, colors_1.colorText)(clipped, colors_1.INPUT_PINK)}\u001b[22m${" ".repeat(Math.max(0, width - clipped.length))}`,
|
|
221
|
+
plain: padded
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function styleLeftTextGlobalGradient(text, width, totalWidth, startColumn) {
|
|
225
|
+
const clipped = text.slice(0, Math.max(0, width));
|
|
226
|
+
const padded = fitText(clipped, width);
|
|
227
|
+
if (clipped.length === 0)
|
|
228
|
+
return { markup: padded, plain: padded };
|
|
229
|
+
const maxColumn = Math.max(totalWidth - 1, 1);
|
|
230
|
+
const startColor = (0, colors_1.interpolateColor)(colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, Math.max(0, Math.min(1, startColumn / maxColumn)));
|
|
231
|
+
const endColumn = startColumn + Math.max(clipped.length - 1, 0);
|
|
232
|
+
const endColor = (0, colors_1.interpolateColor)(colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, Math.max(0, Math.min(1, endColumn / maxColumn)));
|
|
233
|
+
return {
|
|
234
|
+
markup: `${(0, colors_1.horizontalGradient)(clipped, startColor, endColor)}${" ".repeat(Math.max(0, width - clipped.length))}`,
|
|
235
|
+
plain: padded
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function centerLogoLine(logoMarkup, logoPlain, width) {
|
|
239
|
+
const paddingLeft = Math.max(0, Math.floor((width - logoPlain.length) / 2));
|
|
240
|
+
const paddingRight = Math.max(0, width - logoPlain.length - paddingLeft);
|
|
241
|
+
return {
|
|
242
|
+
markup: `${" ".repeat(paddingLeft)}${logoMarkup}${" ".repeat(paddingRight)}`,
|
|
243
|
+
plain: `${" ".repeat(paddingLeft)}${logoPlain}${" ".repeat(paddingRight)}`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function renderSingleColumn(options, mode) {
|
|
247
|
+
const { width, version, username, backend, model, tokenUsage, cwd, sessionName } = options;
|
|
248
|
+
const innerWidth = Math.max(0, width - 2);
|
|
249
|
+
const lines = [];
|
|
250
|
+
const modelInfo = buildModelInfo(backend, model, tokenUsage);
|
|
251
|
+
const gradientLogo = (0, colors_1.rowMajorGradient)(YIPS_LOGO, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW);
|
|
252
|
+
const showLogo = innerWidth >= LOGO_WIDTH;
|
|
253
|
+
lines.push(makeTopBorder(version, width));
|
|
254
|
+
if (mode === "minimal") {
|
|
255
|
+
const minimalRows = [];
|
|
256
|
+
minimalRows.push(styleCenteredText("", innerWidth, "plain"));
|
|
257
|
+
if (showLogo) {
|
|
258
|
+
for (let i = 0; i < YIPS_LOGO.length; i++) {
|
|
259
|
+
minimalRows.push(centerLogoLine(gradientLogo[i] ?? "", YIPS_LOGO[i] ?? "", innerWidth));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
minimalRows.push(styleCenteredText("YIPS", innerWidth, "gradient"));
|
|
264
|
+
}
|
|
265
|
+
minimalRows.push(styleCenteredText(modelInfo, innerWidth, "blue"));
|
|
266
|
+
minimalRows.push(styleCenteredText("", innerWidth, "plain"));
|
|
267
|
+
for (const row of minimalRows) {
|
|
268
|
+
lines.push(makeSingleRow(row.markup, row.plain, width));
|
|
269
|
+
}
|
|
270
|
+
lines.push(makeBottomBorder(sessionName, width));
|
|
271
|
+
return lines;
|
|
272
|
+
}
|
|
273
|
+
const rows = [];
|
|
274
|
+
rows.push(styleCenteredText("", innerWidth, "plain"));
|
|
275
|
+
rows.push(mode === "single"
|
|
276
|
+
? withBold(styleCenteredTextWithGradientSpan(`Welcome back ${username}!`, innerWidth))
|
|
277
|
+
: styleCenteredTextWithGradientSpan(`Hi ${username}!`, innerWidth));
|
|
278
|
+
rows.push(styleCenteredText("", innerWidth, "plain"));
|
|
279
|
+
if (showLogo) {
|
|
280
|
+
for (let i = 0; i < YIPS_LOGO.length; i++) {
|
|
281
|
+
rows.push(centerLogoLine(gradientLogo[i] ?? "", YIPS_LOGO[i] ?? "", innerWidth));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
rows.push(styleCenteredText("YIPS", innerWidth, "gradient"));
|
|
286
|
+
}
|
|
287
|
+
rows.push(styleCenteredText(modelInfo, innerWidth, "blue"));
|
|
288
|
+
if (mode === "single") {
|
|
289
|
+
rows.push(styleCenteredTextWithGradientSpan(cwd, innerWidth));
|
|
290
|
+
}
|
|
291
|
+
rows.push(styleCenteredText("", innerWidth, "plain"));
|
|
292
|
+
for (const row of rows) {
|
|
293
|
+
lines.push(makeSingleRow(row.markup, row.plain, width));
|
|
294
|
+
}
|
|
295
|
+
lines.push(makeBottomBorder(sessionName, width));
|
|
296
|
+
return lines;
|
|
297
|
+
}
|
|
298
|
+
function renderFull(options) {
|
|
299
|
+
const { width, version, username, backend, model, tokenUsage, cwd, sessionName, recentActivity = [], sessionSelection } = options;
|
|
300
|
+
const lines = [];
|
|
301
|
+
const modelInfo = buildModelInfo(backend, model, tokenUsage);
|
|
302
|
+
const availableWidth = Math.max(0, width - 3);
|
|
303
|
+
const leftWidth = Math.max(Math.floor(availableWidth * 0.45), 30);
|
|
304
|
+
const rightWidth = Math.max(0, availableWidth - leftWidth);
|
|
305
|
+
const middleProgress = (leftWidth + 1) / Math.max(width - 1, 1);
|
|
306
|
+
const middleBorderColor = (0, colors_1.interpolateColor)(colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, middleProgress);
|
|
307
|
+
const leftBorder = (0, colors_1.horizontalGradient)("│", colors_1.GRADIENT_PINK, colors_1.GRADIENT_PINK);
|
|
308
|
+
const middleBorder = (0, colors_1.colorText)("│", middleBorderColor);
|
|
309
|
+
const rightBorder = (0, colors_1.horizontalGradient)("│", colors_1.GRADIENT_YELLOW, colors_1.GRADIENT_YELLOW);
|
|
310
|
+
const gradientLogo = (0, colors_1.rowMajorGradient)(YIPS_LOGO, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW);
|
|
311
|
+
const rightStartColumn = leftWidth + 2;
|
|
312
|
+
const leftRows = [
|
|
313
|
+
styleCenteredText("", leftWidth, "plain"),
|
|
314
|
+
withBold(styleCenteredTextWithGradientSpan(`Welcome back ${username}!`, leftWidth)),
|
|
315
|
+
styleCenteredText("", leftWidth, "plain")
|
|
316
|
+
];
|
|
317
|
+
for (let i = 0; i < YIPS_LOGO.length; i++) {
|
|
318
|
+
leftRows.push(centerLogoLine(gradientLogo[i] ?? "", YIPS_LOGO[i] ?? "", leftWidth));
|
|
319
|
+
}
|
|
320
|
+
leftRows.push(styleCenteredText(modelInfo, leftWidth, "blue"));
|
|
321
|
+
leftRows.push(styleCenteredTextWithGradientSpan(cwd, leftWidth));
|
|
322
|
+
leftRows.push(styleCenteredText("", leftWidth, "plain"));
|
|
323
|
+
const rightRows = [
|
|
324
|
+
withBold(styleLeftTextGlobalGradient("Tips for getting started:", rightWidth, width, rightStartColumn)),
|
|
325
|
+
styleLeftTextGlobalGradient("- Ask questions, edit files, or run commands.", rightWidth, width, rightStartColumn),
|
|
326
|
+
styleLeftTextGlobalGradient("- Be specific for the best results.", rightWidth, width, rightStartColumn),
|
|
327
|
+
styleLeftTextGlobalGradient("- /help for more information.", rightWidth, width, rightStartColumn),
|
|
328
|
+
styleLeftText("", rightWidth, "plain"),
|
|
329
|
+
styleLeftTextGlobalGradient("─".repeat(Math.max(0, rightWidth)), rightWidth, width, rightStartColumn),
|
|
330
|
+
withBold(styleLeftText("Recent activity", rightWidth, "white"))
|
|
331
|
+
];
|
|
332
|
+
const activityItems = recentActivity.length > 0 ? recentActivity : ["No recent activity yet."];
|
|
333
|
+
if (sessionSelection?.active) {
|
|
334
|
+
const maxSlots = 5;
|
|
335
|
+
const safeSelected = Math.max(0, Math.min(sessionSelection.selectedIndex, activityItems.length - 1));
|
|
336
|
+
const start = Math.max(0, Math.min(safeSelected - Math.floor(maxSlots / 2), Math.max(0, activityItems.length - maxSlots)));
|
|
337
|
+
const visible = activityItems.slice(start, start + maxSlots);
|
|
338
|
+
for (let i = 0; i < visible.length; i++) {
|
|
339
|
+
const actualIndex = start + i;
|
|
340
|
+
const item = visible[i] ?? "";
|
|
341
|
+
if (actualIndex === safeSelected) {
|
|
342
|
+
rightRows.push(styleHighlightedText(`> ${item}`, rightWidth));
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
rightRows.push(styleLeftText(` ${item}`, rightWidth, "dim"));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
for (const item of activityItems.slice(0, 5)) {
|
|
351
|
+
rightRows.push(styleLeftText(item, rightWidth, "dim"));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
while (rightRows.length < leftRows.length) {
|
|
355
|
+
rightRows.push(styleLeftText("", rightWidth, "plain"));
|
|
356
|
+
}
|
|
357
|
+
lines.push(makeTopBorder(version, width));
|
|
358
|
+
const maxRows = Math.max(leftRows.length, rightRows.length);
|
|
359
|
+
for (let row = 0; row < maxRows; row++) {
|
|
360
|
+
const left = leftRows[row] ?? styleLeftText("", leftWidth, "plain");
|
|
361
|
+
const right = rightRows[row] ?? styleLeftText("", rightWidth, "plain");
|
|
362
|
+
lines.push(`${leftBorder}${padLine(left.markup, left.plain, leftWidth)}${middleBorder}${padLine(right.markup, right.plain, rightWidth)}${rightBorder}`);
|
|
363
|
+
}
|
|
364
|
+
lines.push(makeBottomBorder(sessionName, width));
|
|
365
|
+
return lines;
|
|
366
|
+
}
|
|
367
|
+
function renderCompact(options) {
|
|
368
|
+
return renderSingleColumn(options, "compact");
|
|
369
|
+
}
|
|
370
|
+
function renderMinimal(options) {
|
|
371
|
+
return renderSingleColumn(options, "minimal");
|
|
372
|
+
}
|
|
373
|
+
function renderSingle(options) {
|
|
374
|
+
return renderSingleColumn(options, "single");
|
|
375
|
+
}
|
|
376
|
+
function renderTitleBox(options) {
|
|
377
|
+
const mode = getLayoutMode(options.width);
|
|
378
|
+
switch (mode) {
|
|
379
|
+
case "full":
|
|
380
|
+
return renderFull(options);
|
|
381
|
+
case "single":
|
|
382
|
+
return renderSingle(options);
|
|
383
|
+
case "compact":
|
|
384
|
+
return renderCompact(options);
|
|
385
|
+
case "minimal":
|
|
386
|
+
return renderMinimal(options);
|
|
387
|
+
}
|
|
388
|
+
}
|