@pi-unipi/core 2.6.0 → 2.9.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.
- package/constants.ts +1 -28
- package/events.ts +1 -83
- package/index.ts +1 -0
- package/model-cache.ts +18 -12
- package/package.json +5 -3
- package/tui-overlay.ts +89 -0
- package/tui-width.ts +18 -0
- package/utils.ts +43 -0
package/constants.ts
CHANGED
|
@@ -38,7 +38,6 @@ export const MODULES = {
|
|
|
38
38
|
FOOTER: "@pi-unipi/footer",
|
|
39
39
|
UPDATER: "@pi-unipi/updater",
|
|
40
40
|
INPUT_SHORTCUTS: "@pi-unipi/input-shortcuts",
|
|
41
|
-
COCOINDEX: "@pi-unipi/cocoindex",
|
|
42
41
|
IMAGE: "@pi-unipi/image",
|
|
43
42
|
} as const;
|
|
44
43
|
|
|
@@ -134,13 +133,6 @@ export const MEMORY_DEFAULTS = {
|
|
|
134
133
|
} as const;
|
|
135
134
|
|
|
136
135
|
/** Memory types */
|
|
137
|
-
export const MEMORY_TYPES = {
|
|
138
|
-
PREFERENCE: "preference",
|
|
139
|
-
DECISION: "decision",
|
|
140
|
-
PATTERN: "pattern",
|
|
141
|
-
SUMMARY: "summary",
|
|
142
|
-
} as const;
|
|
143
|
-
|
|
144
136
|
/** Default ralph loop settings */
|
|
145
137
|
export const RALPH_DEFAULTS = {
|
|
146
138
|
MAX_ITERATIONS: 50,
|
|
@@ -297,6 +289,7 @@ export const NOTIFY_COMMANDS = {
|
|
|
297
289
|
SET_NTFY: "notify-set-ntfy",
|
|
298
290
|
TEST: "notify-test",
|
|
299
291
|
RECAP_MODEL: "notify-recap-model",
|
|
292
|
+
NOTIFY_EVENT: "notify-event",
|
|
300
293
|
} as const;
|
|
301
294
|
|
|
302
295
|
/** Notify tool names */
|
|
@@ -369,23 +362,3 @@ export const COMPACTOR_DEFAULTS = {
|
|
|
369
362
|
CACHE_TTL_HOURS: 24,
|
|
370
363
|
} as const;
|
|
371
364
|
|
|
372
|
-
/** CocoIndex minimum supported CLI version. */
|
|
373
|
-
export const COCOINDEX_MIN_VERSION = "1.0" as const;
|
|
374
|
-
|
|
375
|
-
/** CocoIndex package spec installed by the interactive installer. */
|
|
376
|
-
export const COCOINDEX_PACKAGE_SPEC = `cocoindex[lancedb]>=${COCOINDEX_MIN_VERSION}` as const;
|
|
377
|
-
|
|
378
|
-
/** CocoIndex tool names */
|
|
379
|
-
export const COCOINDEX_TOOLS = {
|
|
380
|
-
SEARCH: "cocoindex_search",
|
|
381
|
-
STATUS: "cocoindex_status",
|
|
382
|
-
} as const;
|
|
383
|
-
|
|
384
|
-
/** CocoIndex command names */
|
|
385
|
-
export const COCOINDEX_COMMANDS = {
|
|
386
|
-
UPDATE: "cocoindex-update",
|
|
387
|
-
STATUS: "cocoindex-status",
|
|
388
|
-
INIT: "cocoindex-init",
|
|
389
|
-
SETTINGS: "cocoindex-settings",
|
|
390
|
-
SEARCH: "cocoindex-search",
|
|
391
|
-
} as const;
|
package/events.ts
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
export const UNIPI_EVENTS = {
|
|
10
10
|
/** Module loaded and ready */
|
|
11
11
|
MODULE_READY: "unipi:module:ready",
|
|
12
|
-
/** @deprecated No built-in emitter; retained for external compatibility. */
|
|
13
|
-
MODULE_GONE: "unipi:module:gone",
|
|
14
12
|
|
|
15
13
|
/** Workflow command started */
|
|
16
14
|
WORKFLOW_START: "unipi:workflow:start",
|
|
@@ -24,10 +22,6 @@ export const UNIPI_EVENTS = {
|
|
|
24
22
|
/** Ralph loop iteration completed */
|
|
25
23
|
RALPH_ITERATION_DONE: "unipi:ralph:iteration:done",
|
|
26
24
|
|
|
27
|
-
/** @deprecated No built-in responders; use the info registry and /unipi:info. */
|
|
28
|
-
MODULE_STATUS_REQUEST: "unipi:module:status:request",
|
|
29
|
-
/** @deprecated No built-in aggregator; use the info registry and /unipi:info. */
|
|
30
|
-
MODULE_STATUS_RESPONSE: "unipi:module:status:response",
|
|
31
25
|
|
|
32
26
|
/** Info screen group registered */
|
|
33
27
|
INFO_GROUP_REGISTERED: "unipi:info:group:registered",
|
|
@@ -38,8 +32,6 @@ export const UNIPI_EVENTS = {
|
|
|
38
32
|
MEMORY_STORED: "unipi:memory:stored",
|
|
39
33
|
/** Memory deleted */
|
|
40
34
|
MEMORY_DELETED: "unipi:memory:deleted",
|
|
41
|
-
/** @deprecated No built-in emitter; retained for external compatibility. */
|
|
42
|
-
MEMORY_SEARCHED: "unipi:memory:searched",
|
|
43
35
|
/** Memory consolidation completed */
|
|
44
36
|
MEMORY_CONSOLIDATED: "unipi:memory:consolidated",
|
|
45
37
|
|
|
@@ -60,18 +52,12 @@ export const UNIPI_EVENTS = {
|
|
|
60
52
|
COMPACTOR_COMPACTED: "unipi:compactor:compacted",
|
|
61
53
|
/** @deprecated Footer reads live Pi session data; retained for compatibility. */
|
|
62
54
|
COMPACTOR_STATS_UPDATED: "unipi:compactor:stats:updated",
|
|
63
|
-
/** @deprecated No built-in emitter; retained for external compatibility. */
|
|
64
|
-
UTILITY_CLEANUP_START: "unipi:utility:cleanup:start",
|
|
65
55
|
/** Utility module cleanup completed */
|
|
66
56
|
UTILITY_CLEANUP_DONE: "unipi:utility:cleanup:done",
|
|
67
57
|
/** Utility diagnostics started */
|
|
68
58
|
UTILITY_DIAGNOSTICS_START: "unipi:utility:diagnostics:start",
|
|
69
59
|
/** Utility diagnostics completed */
|
|
70
60
|
UTILITY_DIAGNOSTICS_DONE: "unipi:utility:diagnostics:done",
|
|
71
|
-
/** @deprecated No built-in emitter; retained for external compatibility. */
|
|
72
|
-
UTILITY_CACHE_INVALIDATED: "unipi:utility:cache:invalidated",
|
|
73
|
-
/** @deprecated No built-in emitter; retained for external compatibility. */
|
|
74
|
-
UTILITY_LIFECYCLE_STATE: "unipi:utility:lifecycle:state",
|
|
75
61
|
|
|
76
62
|
/** Notification sent */
|
|
77
63
|
NOTIFICATION_SENT: "unipi:notify:sent",
|
|
@@ -91,12 +77,6 @@ export const UNIPI_EVENTS = {
|
|
|
91
77
|
/** Update error */
|
|
92
78
|
UPDATE_ERROR: "unipi:update:error",
|
|
93
79
|
|
|
94
|
-
/** @deprecated Reserved external contract; no built-in emitter. */
|
|
95
|
-
COCOINDEX_UPDATE_STARTED: "unipi:cocoindex:update:started",
|
|
96
|
-
/** @deprecated Reserved external contract; no built-in emitter. */
|
|
97
|
-
COCOINDEX_UPDATE_COMPLETED: "unipi:cocoindex:update:completed",
|
|
98
|
-
/** @deprecated Reserved external contract; no built-in emitter. */
|
|
99
|
-
COCOINDEX_SEARCH_PERFORMED: "unipi:cocoindex:search:performed",
|
|
100
80
|
} as const;
|
|
101
81
|
|
|
102
82
|
/** Payload for MODULE_READY / MODULE_GONE */
|
|
@@ -151,21 +131,7 @@ export interface UnipiRalphIterationEvent {
|
|
|
151
131
|
nextIteration: number;
|
|
152
132
|
}
|
|
153
133
|
|
|
154
|
-
/** @deprecated No built-in responders; retained for compatibility. */
|
|
155
|
-
export interface UnipiStatusRequestEvent {
|
|
156
|
-
/** Request ID for correlation */
|
|
157
|
-
requestId: string;
|
|
158
|
-
}
|
|
159
134
|
|
|
160
|
-
/** @deprecated No built-in aggregator; retained for compatibility. */
|
|
161
|
-
export interface UnipiStatusResponseEvent {
|
|
162
|
-
/** Request ID this responds to */
|
|
163
|
-
requestId: string;
|
|
164
|
-
/** Module name */
|
|
165
|
-
name: string;
|
|
166
|
-
/** Module status data */
|
|
167
|
-
status: Record<string, unknown>;
|
|
168
|
-
}
|
|
169
135
|
|
|
170
136
|
/** Payload for MEMORY_STORED */
|
|
171
137
|
export interface UnipiMemoryStoredEvent {
|
|
@@ -191,15 +157,6 @@ export interface UnipiMemoryDeletedEvent {
|
|
|
191
157
|
project: string;
|
|
192
158
|
}
|
|
193
159
|
|
|
194
|
-
/** Payload for MEMORY_SEARCHED */
|
|
195
|
-
export interface UnipiMemorySearchedEvent {
|
|
196
|
-
/** Search query */
|
|
197
|
-
query: string;
|
|
198
|
-
/** Number of results */
|
|
199
|
-
resultCount: number;
|
|
200
|
-
/** Project scope */
|
|
201
|
-
project: string;
|
|
202
|
-
}
|
|
203
160
|
|
|
204
161
|
/** Payload for MEMORY_CONSOLIDATED */
|
|
205
162
|
export interface UnipiMemoryConsolidatedEvent {
|
|
@@ -307,15 +264,6 @@ export interface UnipiUtilityDiagnosticsEvent {
|
|
|
307
264
|
report?: unknown;
|
|
308
265
|
}
|
|
309
266
|
|
|
310
|
-
/** Payload for UTILITY_LIFECYCLE_STATE */
|
|
311
|
-
export interface UnipiUtilityLifecycleEvent {
|
|
312
|
-
/** New lifecycle state */
|
|
313
|
-
state: "running" | "shutting_down" | "orphaned" | "error";
|
|
314
|
-
/** Previous state */
|
|
315
|
-
previousState?: string;
|
|
316
|
-
/** Reason for state change */
|
|
317
|
-
reason?: string;
|
|
318
|
-
}
|
|
319
267
|
|
|
320
268
|
/** Payload for BADGE_GENERATE_REQUEST */
|
|
321
269
|
export interface UnipiBadgeGenerateRequestEvent {
|
|
@@ -375,31 +323,8 @@ export interface UnipiUpdateErrorEvent {
|
|
|
375
323
|
phase: "check" | "install";
|
|
376
324
|
}
|
|
377
325
|
|
|
378
|
-
/** Payload for COCOINDEX_UPDATE_STARTED */
|
|
379
|
-
export interface UnipiCocoindexUpdateStartedEvent {
|
|
380
|
-
/** Project directory being indexed */
|
|
381
|
-
projectDir: string;
|
|
382
|
-
}
|
|
383
326
|
|
|
384
|
-
/** Payload for COCOINDEX_UPDATE_COMPLETED */
|
|
385
|
-
export interface UnipiCocoindexUpdateCompletedEvent {
|
|
386
|
-
/** Whether the update succeeded */
|
|
387
|
-
success: boolean;
|
|
388
|
-
/** Number of chunks processed */
|
|
389
|
-
chunksProcessed: number;
|
|
390
|
-
/** Duration in ms */
|
|
391
|
-
durationMs: number;
|
|
392
|
-
/** Error message if failed */
|
|
393
|
-
error?: string;
|
|
394
|
-
}
|
|
395
327
|
|
|
396
|
-
/** Payload for COCOINDEX_SEARCH_PERFORMED */
|
|
397
|
-
export interface UnipiCocoindexSearchPerformedEvent {
|
|
398
|
-
/** Search query */
|
|
399
|
-
query: string;
|
|
400
|
-
/** Number of results */
|
|
401
|
-
resultCount: number;
|
|
402
|
-
}
|
|
403
328
|
|
|
404
329
|
/** Payload for NOTIFICATION_SENT */
|
|
405
330
|
export interface UnipiNotificationSentEvent {
|
|
@@ -421,11 +346,8 @@ export type UnipiEventPayload =
|
|
|
421
346
|
| UnipiWorkflowEvent
|
|
422
347
|
| UnipiRalphLoopEvent
|
|
423
348
|
| UnipiRalphIterationEvent
|
|
424
|
-
| UnipiStatusRequestEvent
|
|
425
|
-
| UnipiStatusResponseEvent
|
|
426
349
|
| UnipiMemoryStoredEvent
|
|
427
350
|
| UnipiMemoryDeletedEvent
|
|
428
|
-
| UnipiMemorySearchedEvent
|
|
429
351
|
| UnipiMemoryConsolidatedEvent
|
|
430
352
|
| UnipiInfoGroupEvent
|
|
431
353
|
| UnipiInfoDataEvent
|
|
@@ -436,14 +358,10 @@ export type UnipiEventPayload =
|
|
|
436
358
|
| UnipiCompactorStatsEvent
|
|
437
359
|
| UnipiUtilityCleanupEvent
|
|
438
360
|
| UnipiUtilityDiagnosticsEvent
|
|
439
|
-
| UnipiUtilityLifecycleEvent
|
|
440
361
|
| UnipiNotificationSentEvent
|
|
441
362
|
| UnipiBadgeGenerateRequestEvent
|
|
442
363
|
| UnipiAskUserPromptEvent
|
|
443
364
|
| UnipiUpdateCheckEvent
|
|
444
365
|
| UnipiUpdateAvailableEvent
|
|
445
366
|
| UnipiUpdateAppliedEvent
|
|
446
|
-
| UnipiUpdateErrorEvent
|
|
447
|
-
| UnipiCocoindexUpdateStartedEvent
|
|
448
|
-
| UnipiCocoindexUpdateCompletedEvent
|
|
449
|
-
| UnipiCocoindexSearchPerformedEvent;
|
|
367
|
+
| UnipiUpdateErrorEvent;
|
package/index.ts
CHANGED
package/model-cache.ts
CHANGED
|
@@ -9,14 +9,18 @@
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
/** Resolve the model cache directory at call time (respects HOME changes). */
|
|
13
|
+
function cacheDir(): string {
|
|
14
|
+
return path.join(
|
|
15
|
+
process.env.HOME ?? process.env.USERPROFILE ?? "~",
|
|
16
|
+
".unipi/config",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
17
19
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
+
/** Resolve the model cache file path at call time. */
|
|
21
|
+
function cacheFile(): string {
|
|
22
|
+
return path.join(cacheDir(), "models-cache.json");
|
|
23
|
+
}
|
|
20
24
|
|
|
21
25
|
/** A single cached model entry */
|
|
22
26
|
export interface CachedModel {
|
|
@@ -42,8 +46,9 @@ export interface ModelCache {
|
|
|
42
46
|
*/
|
|
43
47
|
export function readModelCache(): CachedModel[] {
|
|
44
48
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
const file = cacheFile();
|
|
50
|
+
if (!fs.existsSync(file)) return [];
|
|
51
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
47
52
|
return Array.isArray(parsed.models) ? parsed.models : [];
|
|
48
53
|
} catch {
|
|
49
54
|
return [];
|
|
@@ -56,14 +61,15 @@ export function readModelCache(): CachedModel[] {
|
|
|
56
61
|
*/
|
|
57
62
|
export function writeModelCache(models: CachedModel[]): void {
|
|
58
63
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
const dir = cacheDir();
|
|
65
|
+
if (!fs.existsSync(dir)) {
|
|
66
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
61
67
|
}
|
|
62
68
|
const cache: ModelCache = {
|
|
63
69
|
updatedAt: new Date().toISOString(),
|
|
64
70
|
models,
|
|
65
71
|
};
|
|
66
|
-
fs.writeFileSync(
|
|
72
|
+
fs.writeFileSync(cacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
67
73
|
} catch {
|
|
68
74
|
// Best effort — cache is optional
|
|
69
75
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Shared utilities, event types, and constants for Unipi extension suite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,11 +24,13 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
27
|
+
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
28
|
+
"@earendil-works/pi-tui": "^0.84.0",
|
|
28
29
|
"typebox": "^1.1.38"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
32
|
+
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
33
|
+
"@earendil-works/pi-tui": "^0.84.0",
|
|
32
34
|
"typebox": "^1.1.38",
|
|
33
35
|
"@types/node": "^22.0.0"
|
|
34
36
|
},
|
package/tui-overlay.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/core — Shared TUI overlay helpers
|
|
3
|
+
*
|
|
4
|
+
* Eliminates the frameLine/borderLine/ruleLine/fg/bold duplication across
|
|
5
|
+
* 9+ overlay files. Each overlay creates an OverlayTheme instance and
|
|
6
|
+
* delegates box-drawing and theming to it.
|
|
7
|
+
*
|
|
8
|
+
* The key-handling bug (raw \x1b comparisons vs matchesKey) would have been
|
|
9
|
+
* caught at one site instead of 10 if these helpers had existed from the start.
|
|
10
|
+
*/
|
|
11
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
13
|
+
import { safeRepeat } from "./tui-width.js";
|
|
14
|
+
|
|
15
|
+
/** ANSI codes used when no Theme is available (terminal default). */
|
|
16
|
+
const FALLBACK_COLORS: Record<string, string> = {
|
|
17
|
+
accent: "\x1b[36m",
|
|
18
|
+
success: "\x1b[32m",
|
|
19
|
+
warning: "\x1b[33m",
|
|
20
|
+
error: "\x1b[31m",
|
|
21
|
+
dim: "\x1b[2m",
|
|
22
|
+
borderMuted: "\x1b[90m",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Shared theme + box-drawing helper for TUI overlays.
|
|
27
|
+
*
|
|
28
|
+
* Usage:
|
|
29
|
+
* ```ts
|
|
30
|
+
* class MyOverlay implements Component {
|
|
31
|
+
* private overlay = new OverlayTheme();
|
|
32
|
+
*
|
|
33
|
+
* setTheme(theme: Theme) { this.overlay.setTheme(theme); }
|
|
34
|
+
*
|
|
35
|
+
* render(width: number): string[] {
|
|
36
|
+
* const inner = boxInnerWidth(width);
|
|
37
|
+
* return [
|
|
38
|
+
* this.overlay.borderLine(inner, "top"),
|
|
39
|
+
* this.overlay.frameLine("Title", inner),
|
|
40
|
+
* this.overlay.borderLine(inner, "bottom"),
|
|
41
|
+
* ];
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export class OverlayTheme {
|
|
47
|
+
private theme: Theme | null = null;
|
|
48
|
+
|
|
49
|
+
setTheme(theme: Theme | null): void {
|
|
50
|
+
this.theme = theme;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Color text using the active theme, or a fallback ANSI code. */
|
|
54
|
+
fg(color: string, text: string): string {
|
|
55
|
+
if (this.theme) return this.theme.fg(color as never, text);
|
|
56
|
+
return `${FALLBACK_COLORS[color] ?? ""}${text}\x1b[0m`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Bold text using the active theme, or a fallback ANSI code. */
|
|
60
|
+
bold(text: string): string {
|
|
61
|
+
if (this.theme) return this.theme.bold(text);
|
|
62
|
+
return `\x1b[1m${text}\x1b[0m`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Background color using the active theme, or text unchanged. */
|
|
66
|
+
bg(color: string, text: string): string {
|
|
67
|
+
if (this.theme) return this.theme.bg(color as never, text);
|
|
68
|
+
return text;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Frame a content line with `│` borders, padded and truncated to innerWidth. */
|
|
72
|
+
frameLine(content: string, innerWidth: number): string {
|
|
73
|
+
const truncated = truncateToWidth(content, innerWidth, "");
|
|
74
|
+
const padding = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
75
|
+
return `${this.fg("borderMuted", "│")}${truncated}${safeRepeat(" ", padding)}${this.fg("borderMuted", "│")}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Horizontal rule: `├───┤`. */
|
|
79
|
+
ruleLine(innerWidth: number): string {
|
|
80
|
+
return this.fg("borderMuted", `├${safeRepeat("─", innerWidth)}┤`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Top or bottom border: `┌───┐` / `└───┘`. */
|
|
84
|
+
borderLine(innerWidth: number, edge: "top" | "bottom"): string {
|
|
85
|
+
const left = edge === "top" ? "┌" : "└";
|
|
86
|
+
const right = edge === "top" ? "┐" : "┘";
|
|
87
|
+
return this.fg("borderMuted", `${left}${safeRepeat("─", innerWidth)}${right}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
package/tui-width.ts
CHANGED
|
@@ -125,3 +125,21 @@ export class WidthKeyedCache {
|
|
|
125
125
|
this.width = -1;
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
+
|
|
129
|
+
/** Shared ANSI escape codes for TUI overlays. */
|
|
130
|
+
export const ansi = {
|
|
131
|
+
reset: "\x1b[0m",
|
|
132
|
+
bold: "\x1b[1m",
|
|
133
|
+
dim: "\x1b[2m",
|
|
134
|
+
cyan: "\x1b[36m",
|
|
135
|
+
green: "\x1b[32m",
|
|
136
|
+
yellow: "\x1b[33m",
|
|
137
|
+
red: "\x1b[31m",
|
|
138
|
+
gray: "\x1b[90m",
|
|
139
|
+
white: "\x1b[37m",
|
|
140
|
+
blue: "\x1b[34m",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Toggle symbols for TUI settings overlays. */
|
|
144
|
+
export const TOGGLE_ON = `${ansi.green}●${ansi.reset}`;
|
|
145
|
+
export const TOGGLE_OFF = `${ansi.dim}○${ansi.reset}`;
|
package/utils.ts
CHANGED
|
@@ -303,3 +303,46 @@ export async function withHerdrBlocked<T>(
|
|
|
303
303
|
event(false);
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
|
+
|
|
307
|
+
/** Format a token count for display (e.g. 1234 → "1.2k", 1500000 → "1.5M"). */
|
|
308
|
+
export function formatTokens(n: number): string {
|
|
309
|
+
if (n < 1000) return String(n);
|
|
310
|
+
if (n < 10_000) return `${(n / 1000).toFixed(1)}k`;
|
|
311
|
+
if (n < 1_000_000) return `${Math.round(n / 1000)}k`;
|
|
312
|
+
if (n < 10_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
313
|
+
return `${Math.round(n / 1_000_000)}M`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Check if a snapshot is active by checking its details.active field or content. */
|
|
317
|
+
export function isActiveSnapshot(snapshot: { content: unknown; details?: { active?: boolean } }): boolean {
|
|
318
|
+
if (typeof snapshot.details?.active === "boolean") return snapshot.details.active;
|
|
319
|
+
return typeof snapshot.content === "string" && snapshot.content.includes("Status: active");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Compare two semver-ish version strings.
|
|
323
|
+
* Returns 1 when a > b, -1 when a < b, 0 when equal.
|
|
324
|
+
* Handles `v` prefix, splits on `.` or `-`, ignores non-numeric suffixes. */
|
|
325
|
+
export function compareVersions(a: string, b: string): number {
|
|
326
|
+
const parse = (version: string): number[] => version
|
|
327
|
+
.replace(/^v/, "")
|
|
328
|
+
.split(/[.-]/)
|
|
329
|
+
.slice(0, 3)
|
|
330
|
+
.map((part) => {
|
|
331
|
+
const parsed = Number.parseInt(part, 10);
|
|
332
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const left = parse(a);
|
|
336
|
+
const right = parse(b);
|
|
337
|
+
for (let i = 0; i < 3; i++) {
|
|
338
|
+
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
339
|
+
if (diff > 0) return 1;
|
|
340
|
+
if (diff < 0) return -1;
|
|
341
|
+
}
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Return true only when `latest` is newer than `current`. */
|
|
346
|
+
export function isNewerVersion(latest: string, current: string): boolean {
|
|
347
|
+
return compareVersions(latest, current) > 0;
|
|
348
|
+
}
|