@alisio/alisio-code 0.1.0-alpha.3

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.
@@ -0,0 +1,546 @@
1
+ const trimZero = (value) => value.replace(/\.0$/, "");
2
+ export function formatTokens(n) {
3
+ if (!Number.isFinite(n) || n < 0)
4
+ return "0";
5
+ if (n < 1000)
6
+ return String(Math.round(n));
7
+ if (n < 1_000_000) {
8
+ const k = n / 1000;
9
+ return `${k < 100 ? trimZero(k.toFixed(1)) : Math.round(k)}k`;
10
+ }
11
+ return `${trimZero((n / 1_000_000).toFixed(1))}M`;
12
+ }
13
+ /** Prefixes every model with its owning provider so identical model ids are never ambiguous. */
14
+ export function providerModelItems(provider, models, current) {
15
+ return models.map((model) => ({
16
+ value: model.id,
17
+ label: `${provider} · ${model.name ?? model.id}${model.id === current ? " (current)" : ""}`,
18
+ ...(model.contextWindow
19
+ ? { description: `${model.id} · ${formatTokens(model.contextWindow)} context` }
20
+ : model.name
21
+ ? { description: model.id }
22
+ : {}),
23
+ }));
24
+ }
25
+ /** Text markers remain meaningful without color: [x] active, [ ] inactive, [!] failed, [*] pending. */
26
+ export function pluginCatalogItems(entries) {
27
+ const marker = (entry) => entry.status === "active"
28
+ ? "[x]"
29
+ : entry.status === "inactive"
30
+ ? "[ ]"
31
+ : entry.status === "failed"
32
+ ? "[!]"
33
+ : "[*]";
34
+ return entries.map((entry) => ({
35
+ value: entry.id,
36
+ label: `${marker(entry)} ${entry.name} · ${entry.builtin ? "built-in" : entry.source}`,
37
+ description: `${entry.status}${entry.categories.length ? ` · ${entry.categories.join(", ")}` : ""} · ${entry.description}`,
38
+ }));
39
+ }
40
+ export const pluginToggleNeedsConfirmation = (entry) => !entry.builtin;
41
+ const mcpSourceTitle = (kind) => ({
42
+ global: "User",
43
+ project: "Project",
44
+ explicit: "Explicit",
45
+ builtin: "Built-in",
46
+ plugin: "Plugin",
47
+ })[kind];
48
+ /** Group headings are generated only for sources that actually registered servers. */
49
+ export function mcpServerItems(entries) {
50
+ const marker = (status) => ({
51
+ disabled: "[ ]",
52
+ disconnected: "[-]",
53
+ connecting: "[…]",
54
+ connected: "[x]",
55
+ failed: "[!]",
56
+ "needs-authentication": "[?]",
57
+ "restart-required": "[*]",
58
+ })[status];
59
+ const grouped = new Map();
60
+ for (const entry of entries) {
61
+ const title = mcpSourceTitle(entry.source.kind);
62
+ grouped.set(title, [...(grouped.get(title) ?? []), entry]);
63
+ }
64
+ return [...grouped].flatMap(([title, servers]) => servers.map((entry, index) => ({
65
+ value: entry.name,
66
+ label: `${index === 0 ? `${title} · ` : ""}${marker(entry.status)} ${entry.displayName}`,
67
+ description: `configured ${entry.enabled ? "enabled" : "disabled"} · permission ${entry.runtimePermission} · ${entry.status}${entry.status === "connected" ? ` · ${entry.counts.tools} tool${entry.counts.tools === 1 ? "" : "s"} loaded` : " · 0 tools loaded"}`,
68
+ })));
69
+ }
70
+ export function mcpToolItems(tools) {
71
+ return tools.map((tool) => {
72
+ const flags = [
73
+ tool.annotations?.readOnly === true ? "read-only" : undefined,
74
+ tool.annotations?.destructive === true ? "destructive" : undefined,
75
+ tool.annotations?.openWorld === true ? "open-world" : undefined,
76
+ ].filter(Boolean);
77
+ return {
78
+ value: tool.effectiveName ?? tool.name,
79
+ label: tool.title
80
+ ? `${tool.title} · ${tool.name}${tool.effectiveName && tool.effectiveName !== tool.name ? ` → ${tool.effectiveName}` : ""}`
81
+ : tool.effectiveName && tool.effectiveName !== tool.name
82
+ ? `${tool.name} → ${tool.effectiveName}`
83
+ : tool.name,
84
+ description: [...flags, tool.description].filter(Boolean).join(" · ") || "No description",
85
+ };
86
+ });
87
+ }
88
+ /** Builds one filterable list while retaining provider/profile ownership in each opaque value. */
89
+ export function configuredProviderModelItems(catalogs, current) {
90
+ const items = [];
91
+ for (const catalog of catalogs) {
92
+ if (catalog.unavailable) {
93
+ items.push({
94
+ value: JSON.stringify({ profile: catalog.profile }),
95
+ label: `${catalog.title} · unavailable`,
96
+ description: `${catalog.provider} · catalog refresh failed`,
97
+ unavailable: true,
98
+ });
99
+ continue;
100
+ }
101
+ const models = catalog.models.length
102
+ ? catalog.models
103
+ : catalog.configuredModel
104
+ ? [{ id: catalog.configuredModel }]
105
+ : [];
106
+ for (const model of models)
107
+ items.push({
108
+ value: JSON.stringify({
109
+ profile: catalog.profile,
110
+ provider: catalog.provider,
111
+ model: model.id,
112
+ }),
113
+ label: `${catalog.title} · ${model.name ?? model.id}${catalog.provider === current?.provider && model.id === current.model ? " (current)" : ""}`,
114
+ description: `${model.id.startsWith(`${catalog.provider}/`) ? model.id : `${catalog.provider}/${model.id}`}${model.contextWindow ? ` · ${formatTokens(model.contextWindow)} context` : ""}`,
115
+ unavailable: false,
116
+ });
117
+ }
118
+ return items;
119
+ }
120
+ export function contextLevel(pct) {
121
+ return pct < 60 ? "ok" : pct < 85 ? "warn" : "danger";
122
+ }
123
+ export function contextPercent(used, total) {
124
+ return total && total > 0 ? (used / total) * 100 : undefined;
125
+ }
126
+ export function formatContext(used, total, estimated) {
127
+ const prefix = `${estimated ? "~" : ""}${formatTokens(used)} / `;
128
+ const pct = contextPercent(used, total);
129
+ return pct === undefined || !total
130
+ ? `${prefix}unknown`
131
+ : `${prefix}${formatTokens(total)} (${Math.round(pct)}%)`;
132
+ }
133
+ export function formatDuration(ms) {
134
+ if (ms < 1000)
135
+ return `${Math.max(0, Math.round(ms))}ms`;
136
+ if (ms < 60_000)
137
+ return `${(ms / 1000).toFixed(1)}s`;
138
+ const total = Math.floor(ms / 1000);
139
+ return `${Math.floor(total / 60)}m${String(total % 60).padStart(2, "0")}s`;
140
+ }
141
+ export const textWidth = (text) => [...text].length;
142
+ export function truncatePlain(text, width) {
143
+ if (width <= 0)
144
+ return "";
145
+ const chars = [...text];
146
+ return chars.length <= width ? text : `${chars.slice(0, width - 1).join("")}…`;
147
+ }
148
+ export function shortenPath(path, home, max = 40) {
149
+ let display = home && (path === home || path.startsWith(`${home}/`)) ? `~${path.slice(home.length)}` : path;
150
+ if (textWidth(display) <= max)
151
+ return display;
152
+ const parts = display.split("/").filter(Boolean);
153
+ display = "";
154
+ for (let i = parts.length - 1; i >= 0; i--) {
155
+ const next = `${parts[i]}${display ? `/${display}` : ""}`;
156
+ if (textWidth(`…/${next}`) > max && display)
157
+ break;
158
+ display = next;
159
+ }
160
+ return truncatePlain(`…/${display}`, max);
161
+ }
162
+ /** Host (and port) only: never path, query, user info or credentials. */
163
+ export function hostOf(baseURL) {
164
+ try {
165
+ return new URL(baseURL).host || "unknown";
166
+ }
167
+ catch {
168
+ return "unknown";
169
+ }
170
+ }
171
+ export const shortId = (id) => id.slice(0, 8);
172
+ /** Keeps segments in order, dropping the lowest priority ones until the line fits. */
173
+ export function fitSegments(segments, width, separator) {
174
+ const kept = [...segments];
175
+ const total = () => kept.reduce((sum, s) => sum + textWidth(s.text), 0) +
176
+ Math.max(0, kept.length - 1) * textWidth(separator);
177
+ while (kept.length > 1 && total() > width) {
178
+ let lowest = 0;
179
+ for (let i = 1; i < kept.length; i++)
180
+ if ((kept[i]?.priority ?? 0) < (kept[lowest]?.priority ?? 0))
181
+ lowest = i;
182
+ kept.splice(lowest, 1);
183
+ }
184
+ const only = kept[0];
185
+ if (kept.length === 1 && only && textWidth(only.text) > width)
186
+ kept[0] = { ...only, text: truncatePlain(only.text, width) };
187
+ return kept;
188
+ }
189
+ export const COMMANDS = [
190
+ { name: "help", description: "Show commands and keys" },
191
+ { name: "connect", description: "Configure a provider and choose its active model" },
192
+ {
193
+ name: "model",
194
+ description: "Switch provider and model",
195
+ aliases: ["models"],
196
+ },
197
+ { name: "compact", description: "Summarize older history", argumentHint: "[focus]" },
198
+ { name: "stats", description: "Session statistics" },
199
+ { name: "clear", description: "Start a new session", aliases: ["new"] },
200
+ { name: "sessions", description: "List recent sessions" },
201
+ { name: "resume", description: "Resume a session by ID or prefix", argumentHint: "<id>" },
202
+ { name: "tools", description: "List tools and permission state" },
203
+ {
204
+ name: "plugins",
205
+ description: "Browse and manage project plugins",
206
+ aliases: ["plugin"],
207
+ },
208
+ {
209
+ name: "skills",
210
+ description: "Browse and manage effective skills",
211
+ aliases: ["skill"],
212
+ },
213
+ { name: "mcp", description: "Browse and manage MCP servers" },
214
+ { name: "copy", description: "Copy the last assistant response to the clipboard" },
215
+ {
216
+ name: "ask",
217
+ description: "Ask the agent to turn your question into a multiple-choice ask_user_question",
218
+ argumentHint: "<question>",
219
+ },
220
+ { name: "exit", description: "Exit Alisio", aliases: ["quit"] },
221
+ ];
222
+ /** Every TUI slash name (commands, aliases and routing prefixes); templates cannot take them. */
223
+ export function reservedCommandNames() {
224
+ return [...COMMANDS.flatMap((c) => [c.name, ...(c.aliases ?? [])]), "command"];
225
+ }
226
+ export function resolveCommand(name) {
227
+ const lower = name.toLowerCase();
228
+ return COMMANDS.find((c) => c.name === lower || c.aliases?.includes(lower))?.name;
229
+ }
230
+ export function parseCommand(input) {
231
+ const match = /^\/([A-Za-z][\w:.-]*)(?:\s+([\s\S]*))?$/.exec(input.trim());
232
+ if (!match?.[1])
233
+ return undefined;
234
+ return { name: match[1], args: (match[2] ?? "").trim() };
235
+ }
236
+ function parseArgs(args) {
237
+ try {
238
+ const value = JSON.parse(args);
239
+ return value && typeof value === "object" && !Array.isArray(value)
240
+ ? value
241
+ : undefined;
242
+ }
243
+ catch {
244
+ return undefined;
245
+ }
246
+ }
247
+ const oneLine = (text) => text.replace(/\s+/g, " ").trim();
248
+ export function summarizeToolArgs(name, args) {
249
+ const input = parseArgs(args);
250
+ if (!input)
251
+ return truncatePlain(oneLine(args), 120);
252
+ const str = (key) => (typeof input[key] === "string" ? input[key] : "");
253
+ let text;
254
+ if (name === "run_process") {
255
+ const rest = Array.isArray(input.args) ? input.args.map(String) : [];
256
+ text = [str("command"), ...rest].join(" ");
257
+ }
258
+ else if (name === "search_text")
259
+ text = `"${str("pattern")}"${str("path") ? ` in ${str("path")}` : ""}`;
260
+ else {
261
+ const key = ["path", "command", "name", "query", "pattern", "url", "title"].find((k) => str(k));
262
+ text = key ? str(key) : Object.keys(input).length ? JSON.stringify(input) : "";
263
+ }
264
+ return truncatePlain(oneLine(text), 120);
265
+ }
266
+ const splitLines = (text) => {
267
+ const lines = text.split(/\r?\n/);
268
+ if (lines.length > 1 && lines.at(-1) === "")
269
+ lines.pop();
270
+ return lines;
271
+ };
272
+ function diffLines(before, after) {
273
+ if (before.length * after.length > 250_000)
274
+ return [
275
+ ...before.map((text) => ({ sign: "-", text })),
276
+ ...after.map((text) => ({ sign: "+", text })),
277
+ ];
278
+ const n = before.length, m = after.length;
279
+ const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
280
+ for (let i = n - 1; i >= 0; i--)
281
+ for (let j = m - 1; j >= 0; j--)
282
+ lcs[i][j] =
283
+ before[i] === after[j]
284
+ ? (lcs[i + 1]?.[j + 1] ?? 0) + 1
285
+ : Math.max(lcs[i + 1]?.[j] ?? 0, lcs[i]?.[j + 1] ?? 0);
286
+ const out = [];
287
+ let i = 0, j = 0;
288
+ while (i < n || j < m) {
289
+ if (i < n && j < m && before[i] === after[j]) {
290
+ i++;
291
+ j++;
292
+ }
293
+ else if (j >= m || (i < n && (lcs[i + 1]?.[j] ?? 0) >= (lcs[i]?.[j + 1] ?? 0)))
294
+ out.push({ sign: "-", text: before[i++] ?? "" });
295
+ else
296
+ out.push({ sign: "+", text: after[j++] ?? "" });
297
+ }
298
+ return out;
299
+ }
300
+ export function editSummary(name, args) {
301
+ if (name !== "edit_file" && name !== "write_file")
302
+ return undefined;
303
+ const input = parseArgs(args);
304
+ if (!input)
305
+ return undefined;
306
+ const path = typeof input.path === "string" ? input.path : "";
307
+ const lines = name === "write_file"
308
+ ? splitLines(String(input.content ?? "")).map((text) => ({ sign: "+", text }))
309
+ : diffLines(splitLines(String(input.oldText ?? "")), splitLines(String(input.newText ?? "")));
310
+ return {
311
+ path,
312
+ added: lines.filter((l) => l.sign === "+").length,
313
+ removed: lines.filter((l) => l.sign === "-").length,
314
+ lines,
315
+ };
316
+ }
317
+ export function initialViewState(model, now = Date.now()) {
318
+ return {
319
+ items: [],
320
+ streaming: false,
321
+ compacting: false,
322
+ model,
323
+ stats: {
324
+ input: 0,
325
+ output: 0,
326
+ cached: 0,
327
+ turns: 0,
328
+ runs: 0,
329
+ tools: {},
330
+ models: [model],
331
+ startedAt: now,
332
+ },
333
+ };
334
+ }
335
+ export function addItem(state, item) {
336
+ return { ...state, items: [...state.items, item] };
337
+ }
338
+ const withModel = (stats, model) => typeof model === "string" && model && !stats.models.includes(model)
339
+ ? { ...stats, models: [...stats.models, model] }
340
+ : stats;
341
+ function appendAssistant(state, field, delta) {
342
+ const last = state.items.at(-1);
343
+ if (last?.kind === "assistant" && !last.done)
344
+ return {
345
+ ...state,
346
+ items: [...state.items.slice(0, -1), { ...last, [field]: last[field] + delta }],
347
+ };
348
+ return addItem(state, {
349
+ kind: "assistant",
350
+ text: field === "text" ? delta : "",
351
+ reasoning: field === "reasoning" ? delta : "",
352
+ done: false,
353
+ });
354
+ }
355
+ function updateTool(state, id, update) {
356
+ const index = state.items.findLastIndex((i) => i.kind === "tool" && i.id === id);
357
+ const item = state.items[index];
358
+ if (index < 0 || item?.kind !== "tool")
359
+ return state;
360
+ const items = [...state.items];
361
+ items[index] = { ...item, ...update };
362
+ return { ...state, items };
363
+ }
364
+ export function reduceEvent(state, event) {
365
+ const d = (event.data ?? {});
366
+ const at = Date.parse(event.timestamp);
367
+ switch (event.type) {
368
+ case "run_started":
369
+ return {
370
+ ...state,
371
+ streaming: true,
372
+ runStartedAt: at,
373
+ stats: withModel({ ...state.stats, runs: state.stats.runs + 1 }, d.model),
374
+ };
375
+ case "text_delta":
376
+ return appendAssistant(state, "text", String(d.delta ?? ""));
377
+ case "reasoning_delta":
378
+ return appendAssistant(state, "reasoning", String(d.delta ?? ""));
379
+ case "turn_completed": {
380
+ const usage = d.usage;
381
+ const last = state.items.at(-1);
382
+ const items = last?.kind === "assistant" && !last.done
383
+ ? [...state.items.slice(0, -1), { ...last, done: true }]
384
+ : state.items;
385
+ const stats = withModel({ ...state.stats, turns: state.stats.turns + 1 }, d.model);
386
+ if (!usage)
387
+ return { ...state, items, stats };
388
+ return {
389
+ ...state,
390
+ items,
391
+ context: { used: (usage.input ?? 0) + (usage.output ?? 0), estimated: false },
392
+ stats: {
393
+ ...stats,
394
+ input: stats.input + (usage.input ?? 0),
395
+ output: stats.output + (usage.output ?? 0),
396
+ cached: stats.cached + (usage.cachedInput ?? 0),
397
+ },
398
+ };
399
+ }
400
+ case "tool_started":
401
+ return addItem(state, {
402
+ kind: "tool",
403
+ id: String(d.id ?? ""),
404
+ name: String(d.name ?? "tool"),
405
+ args: typeof d.arguments === "string" ? d.arguments : "",
406
+ summary: summarizeToolArgs(String(d.name ?? ""), typeof d.arguments === "string" ? d.arguments : ""),
407
+ status: "running",
408
+ });
409
+ case "approval_requested":
410
+ return updateTool(state, d.id, { status: "approval" });
411
+ case "approval_resolved":
412
+ return updateTool(state, d.id, { status: "running" });
413
+ case "tool_completed": {
414
+ const name = String(d.name ?? "tool");
415
+ const current = state.stats.tools[name] ?? { calls: 0, errors: 0 };
416
+ const next = updateTool(state, d.id, {
417
+ status: d.isError ? "error" : "ok",
418
+ ...(typeof d.durationMs === "number" ? { durationMs: d.durationMs } : {}),
419
+ ...(typeof d.preview === "string" ? { preview: d.preview } : {}),
420
+ });
421
+ return {
422
+ ...next,
423
+ stats: {
424
+ ...next.stats,
425
+ tools: {
426
+ ...next.stats.tools,
427
+ [name]: { calls: current.calls + 1, errors: current.errors + (d.isError ? 1 : 0) },
428
+ },
429
+ },
430
+ };
431
+ }
432
+ case "run_completed":
433
+ case "run_failed":
434
+ case "run_cancelled": {
435
+ const ended = {
436
+ ...state,
437
+ streaming: false,
438
+ compacting: false,
439
+ stats: {
440
+ ...state.stats,
441
+ ...(state.runStartedAt !== undefined ? { lastRunMs: at - state.runStartedAt } : {}),
442
+ },
443
+ };
444
+ if (event.type === "run_failed")
445
+ return addItem(ended, { kind: "error", text: String(d.error ?? "Run failed") });
446
+ if (event.type === "run_cancelled") {
447
+ const reason = String(d.error ?? "cancelled");
448
+ return addItem(ended, {
449
+ kind: "notice",
450
+ text: reason.startsWith("Interrupted") ? reason : `Interrupted: ${reason}`,
451
+ });
452
+ }
453
+ return ended;
454
+ }
455
+ case "compaction_started":
456
+ return { ...state, compacting: true };
457
+ case "compaction_completed": {
458
+ const reports = Object.values((d.plugins ?? {}))
459
+ .map((r) => (typeof r?.summary === "string" ? r.summary : ""))
460
+ .filter(Boolean);
461
+ const checkpoint = typeof d.summarizedTokens === "number" && typeof d.checkpointTokens === "number"
462
+ ? ` · checkpoint ~${formatTokens(d.summarizedTokens)} → ~${formatTokens(d.checkpointTokens)} tokens`
463
+ : "";
464
+ return addItem({
465
+ ...state,
466
+ compacting: false,
467
+ context: { used: Number(d.after ?? 0), estimated: true },
468
+ }, {
469
+ kind: "notice",
470
+ text: [
471
+ `Context compacted (${String(d.reason ?? "manual")}): ${String(d.replaced ?? 0)} messages summarized, ~${formatTokens(Number(d.before ?? 0))} → ~${formatTokens(Number(d.after ?? 0))} tokens${checkpoint}`,
472
+ ...reports,
473
+ ].join("\n"),
474
+ });
475
+ }
476
+ case "plugin_hook_failed":
477
+ return addItem(state, {
478
+ kind: "notice",
479
+ text: `Plugin ${String(d.source ?? "?")} ${String(d.hook ?? "hook")} failed: ${String(d.error ?? "unknown error")} (continued without it)`,
480
+ });
481
+ case "session_context_injected":
482
+ return addItem(state, {
483
+ kind: "notice",
484
+ text: `Context injected by ${(d.sources ?? ["plugin"]).join(", ")} (~${formatTokens(Number(d.tokens ?? 0))} tokens)`,
485
+ });
486
+ case "compaction_skipped":
487
+ return addItem({ ...state, compacting: false }, { kind: "notice", text: `Compaction skipped: ${String(d.detail ?? "nothing to compact")}` });
488
+ case "compaction_failed":
489
+ return addItem({ ...state, compacting: false }, { kind: "error", text: `Compaction failed: ${String(d.error ?? "unknown error")}` });
490
+ case "model_changed":
491
+ return {
492
+ ...state,
493
+ model: String(d.model ?? state.model),
494
+ stats: withModel(state.stats, d.model),
495
+ items: [
496
+ ...state.items,
497
+ {
498
+ kind: "notice",
499
+ text: `Model: ${String(d.previous ?? "?")} → ${String(d.model ?? "?")}`,
500
+ },
501
+ ],
502
+ };
503
+ default:
504
+ return state;
505
+ }
506
+ }
507
+ export function lastAssistantText(items) {
508
+ for (let i = items.length - 1; i >= 0; i--) {
509
+ const item = items[i];
510
+ if (item?.kind === "assistant" && item.text.trim())
511
+ return item.text;
512
+ }
513
+ return undefined;
514
+ }
515
+ /** Rebuilds transcript items from persisted history (used by /resume). */
516
+ export function itemsFromHistory(messages) {
517
+ const items = [];
518
+ const results = new Map();
519
+ for (const m of messages)
520
+ if (m.role === "tool")
521
+ results.set(m.callId, m);
522
+ for (const m of messages) {
523
+ if (m.role === "user")
524
+ items.push(m.summary ? { kind: "info", text: m.text } : { kind: "user", text: m.display ?? m.text });
525
+ else if (m.role === "assistant") {
526
+ if (m.text)
527
+ items.push({ kind: "assistant", text: m.text, reasoning: "", done: true });
528
+ for (const c of m.calls) {
529
+ const r = results.get(c.id);
530
+ items.push({
531
+ kind: "tool",
532
+ id: c.id,
533
+ name: c.name,
534
+ args: c.arguments,
535
+ summary: summarizeToolArgs(c.name, c.arguments),
536
+ status: r?.result.isError ? "error" : "ok",
537
+ preview: r?.result.content
538
+ .map((x) => x.text)
539
+ .join("\n")
540
+ .slice(0, 2_000),
541
+ });
542
+ }
543
+ }
544
+ }
545
+ return items;
546
+ }
@@ -0,0 +1,23 @@
1
+ import type { EditorTheme, ImageTheme, MarkdownTheme, SelectListTheme } from "@earendil-works/pi-tui";
2
+ import type { Level } from "./state.ts";
3
+ export declare const style: {
4
+ bold: (text: string) => string;
5
+ dim: (text: string) => string;
6
+ italic: (text: string) => string;
7
+ underline: (text: string) => string;
8
+ strike: (text: string) => string;
9
+ red: (text: string) => string;
10
+ green: (text: string) => string;
11
+ yellow: (text: string) => string;
12
+ blue: (text: string) => string;
13
+ magenta: (text: string) => string;
14
+ cyan: (text: string) => string;
15
+ gray: (text: string) => string;
16
+ brightCyan: (text: string) => string;
17
+ userBg: (text: string) => string;
18
+ };
19
+ export declare const levelColor: (level: Level) => (text: string) => string;
20
+ export declare const markdownTheme: MarkdownTheme;
21
+ export declare const selectListTheme: SelectListTheme;
22
+ export declare const editorTheme: EditorTheme;
23
+ export declare const imageTheme: ImageTheme;
@@ -0,0 +1,49 @@
1
+ const enabled = !process.env.NO_COLOR;
2
+ const sgr = (open, close) => (text) => enabled ? `\x1b[${open}m${text}\x1b[${close}m` : text;
3
+ export const style = {
4
+ bold: sgr(1, 22),
5
+ dim: sgr(2, 22),
6
+ italic: sgr(3, 23),
7
+ underline: sgr(4, 24),
8
+ strike: sgr(9, 29),
9
+ red: sgr(31, 39),
10
+ green: sgr(32, 39),
11
+ yellow: sgr(33, 39),
12
+ blue: sgr(34, 39),
13
+ magenta: sgr(35, 39),
14
+ cyan: sgr(36, 39),
15
+ gray: sgr(90, 39),
16
+ brightCyan: sgr(96, 39),
17
+ userBg: sgr("48;5;236", 49),
18
+ };
19
+ export const levelColor = (level) => level === "ok" ? style.green : level === "warn" ? style.yellow : style.red;
20
+ export const markdownTheme = {
21
+ heading: (t) => style.bold(style.cyan(t)),
22
+ link: (t) => style.underline(style.blue(t)),
23
+ linkUrl: (t) => style.dim(t),
24
+ code: (t) => style.yellow(t),
25
+ codeBlock: (t) => style.green(t),
26
+ codeBlockBorder: (t) => style.dim(t),
27
+ quote: (t) => style.italic(style.gray(t)),
28
+ quoteBorder: (t) => style.dim(t),
29
+ hr: (t) => style.dim(t),
30
+ listBullet: (t) => style.cyan(t),
31
+ bold: (t) => style.bold(t),
32
+ italic: (t) => style.italic(t),
33
+ strikethrough: (t) => style.strike(t),
34
+ underline: (t) => style.underline(t),
35
+ };
36
+ export const selectListTheme = {
37
+ selectedPrefix: (t) => style.cyan(t),
38
+ selectedText: (t) => style.bold(style.cyan(t)),
39
+ description: (t) => style.gray(t),
40
+ scrollInfo: (t) => style.dim(t),
41
+ noMatch: (t) => style.yellow(t),
42
+ };
43
+ export const editorTheme = {
44
+ borderColor: (t) => style.gray(t),
45
+ selectList: selectListTheme,
46
+ };
47
+ export const imageTheme = {
48
+ fallbackColor: (t) => style.gray(t),
49
+ };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@alisio/alisio-code",
3
+ "version": "0.1.0-alpha.3",
4
+ "description": "Alisio: an extensible, provider-agnostic coding-agent harness for your terminal. TUI, OpenAI-compatible providers, permissioned local tools, context compaction, persistent memory and a typed plugin SDK.",
5
+ "author": "Gustavo Gutiérrez",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/GustavoGutierrez/alisio.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "homepage": "https://gustavogutierrez.github.io/alisio/",
13
+ "bugs": {
14
+ "url": "https://github.com/GustavoGutierrez/alisio/issues"
15
+ },
16
+ "keywords": [
17
+ "ai",
18
+ "coding-agent",
19
+ "llm",
20
+ "agent-harness",
21
+ "openai-compatible",
22
+ "tui",
23
+ "cli",
24
+ "plugins",
25
+ "terminal"
26
+ ],
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/main.d.ts",
31
+ "import": "./dist/main.js"
32
+ }
33
+ },
34
+ "types": "./dist/main.d.ts",
35
+ "files": [
36
+ "dist",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "sideEffects": true,
41
+ "engines": {
42
+ "node": ">=22.16"
43
+ },
44
+ "bin": {
45
+ "alisio": "./dist/main.js"
46
+ },
47
+ "dependencies": {
48
+ "@alisio/core": "0.1.0-alpha.3",
49
+ "@alisio/plugin-deepseek": "0.1.0-alpha.2",
50
+ "@alisio/plugin-memory": "0.1.0-alpha.3",
51
+ "@alisio/plugin-openai-compatible": "0.1.0-alpha.3",
52
+ "@alisio/plugin-opencode": "0.1.0-alpha.2",
53
+ "@alisio/plugin-opencode-go": "0.1.0-alpha.2",
54
+ "@alisio/plugin-subagents": "0.1.0-alpha.3",
55
+ "@alisio/sdk": "0.1.0-alpha.2",
56
+ "@earendil-works/pi-tui": "0.87.1",
57
+ "commander": "15.0.0"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public",
61
+ "registry": "https://registry.npmjs.com/"
62
+ },
63
+ "scripts": {
64
+ "build": "tsc -p tsconfig.build.json"
65
+ }
66
+ }