@tuicomponents/core 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/index.cjs ADDED
@@ -0,0 +1,629 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BaseTuiComponent: () => BaseTuiComponent,
34
+ ComponentRegistry: () => ComponentRegistry,
35
+ DEFAULT_ANCHOR: () => DEFAULT_ANCHOR,
36
+ DEFAULT_TERMINAL_HEIGHT: () => DEFAULT_TERMINAL_HEIGHT,
37
+ DEFAULT_TERMINAL_WIDTH: () => DEFAULT_TERMINAL_WIDTH,
38
+ anchorLine: () => anchorLine,
39
+ applyMarkdownStyle: () => applyMarkdownStyle,
40
+ createRenderContext: () => createRenderContext,
41
+ createStyleFunctions: () => createStyleFunctions,
42
+ createThemeSync: () => createThemeSync,
43
+ defaultTheme: () => defaultTheme,
44
+ detectColorLevel: () => detectColorLevel,
45
+ detectEnvironment: () => detectEnvironment,
46
+ detectTheme: () => detectTheme,
47
+ getMarkdownRenderedWidth: () => getMarkdownRenderedWidth,
48
+ getProcessTree: () => getProcessTree,
49
+ getStringWidth: () => getStringWidth,
50
+ getTerminalSize: () => getTerminalSize,
51
+ getTerminalWidth: () => getTerminalWidth,
52
+ inlineCode: () => inlineCode,
53
+ isRunningInAIAssistant: () => isRunningInAIAssistant,
54
+ isTTY: () => isTTY,
55
+ joinAnchoredLines: () => joinAnchoredLines,
56
+ measureLines: () => measureLines,
57
+ padToWidth: () => padToWidth,
58
+ registry: () => registry,
59
+ stripMarkdownFormatting: () => stripMarkdownFormatting,
60
+ truncateToWidth: () => truncateToWidth,
61
+ wrapText: () => wrapText,
62
+ wrapTextWithInfo: () => wrapTextWithInfo
63
+ });
64
+ module.exports = __toCommonJS(index_exports);
65
+
66
+ // src/component.ts
67
+ var import_zod_to_json_schema = require("zod-to-json-schema");
68
+ var BaseTuiComponent = class {
69
+ getJsonSchema() {
70
+ return (0, import_zod_to_json_schema.zodToJsonSchema)(this.schema, {
71
+ name: this.metadata.name,
72
+ $refStrategy: "none"
73
+ });
74
+ }
75
+ };
76
+
77
+ // src/registry.ts
78
+ var ComponentRegistry = class {
79
+ components = /* @__PURE__ */ new Map();
80
+ /**
81
+ * Register a component by invoking its factory function.
82
+ * @param factory - Factory function that creates the component
83
+ * @returns The created component instance
84
+ */
85
+ register(factory) {
86
+ const component = factory();
87
+ const name = component.metadata.name;
88
+ if (this.components.has(name)) {
89
+ throw new Error(`Component "${name}" is already registered`);
90
+ }
91
+ this.components.set(name, component);
92
+ return component;
93
+ }
94
+ /**
95
+ * Get a component by name.
96
+ * @param name - Component name
97
+ * @returns The component or undefined if not found
98
+ */
99
+ get(name) {
100
+ return this.components.get(name);
101
+ }
102
+ /**
103
+ * Check if a component is registered.
104
+ * @param name - Component name
105
+ */
106
+ has(name) {
107
+ return this.components.has(name);
108
+ }
109
+ /**
110
+ * List all registered components.
111
+ * @returns Array of component info objects
112
+ */
113
+ list() {
114
+ return Array.from(this.components.values()).map((c) => ({
115
+ name: c.metadata.name,
116
+ description: c.metadata.description,
117
+ version: c.metadata.version
118
+ }));
119
+ }
120
+ /**
121
+ * Get all registered component names.
122
+ */
123
+ names() {
124
+ return Array.from(this.components.keys());
125
+ }
126
+ /**
127
+ * Clear all registered components.
128
+ * Primarily useful for testing.
129
+ */
130
+ clear() {
131
+ this.components.clear();
132
+ }
133
+ };
134
+ var registry = new ComponentRegistry();
135
+
136
+ // src/width.ts
137
+ var import_string_width = __toESM(require("string-width"), 1);
138
+ function getStringWidth(str) {
139
+ return (0, import_string_width.default)(str);
140
+ }
141
+ function padToWidth(str, targetWidth, options = {}) {
142
+ const { padChar = " ", align = "left" } = options;
143
+ const currentWidth = getStringWidth(str);
144
+ if (currentWidth >= targetWidth) {
145
+ return str;
146
+ }
147
+ const padCharWidth = getStringWidth(padChar);
148
+ if (padCharWidth === 0) {
149
+ return str;
150
+ }
151
+ const paddingNeeded = targetWidth - currentWidth;
152
+ const padCount = Math.floor(paddingNeeded / padCharWidth);
153
+ const padding = padChar.repeat(padCount);
154
+ switch (align) {
155
+ case "right":
156
+ return padding + str;
157
+ case "center": {
158
+ const leftPadCount = Math.floor(padCount / 2);
159
+ const rightPadCount = padCount - leftPadCount;
160
+ return padChar.repeat(leftPadCount) + str + padChar.repeat(rightPadCount);
161
+ }
162
+ case "left":
163
+ default:
164
+ return str + padding;
165
+ }
166
+ }
167
+ function truncateToWidth(str, targetWidth, options = {}) {
168
+ const { ellipsis = "\u2026", position = "end" } = options;
169
+ const currentWidth = getStringWidth(str);
170
+ if (currentWidth <= targetWidth) {
171
+ return str;
172
+ }
173
+ const ellipsisWidth = getStringWidth(ellipsis);
174
+ if (ellipsisWidth >= targetWidth) {
175
+ return truncateChars(str, targetWidth);
176
+ }
177
+ const availableWidth = targetWidth - ellipsisWidth;
178
+ if (position === "middle") {
179
+ const leftWidth = Math.ceil(availableWidth / 2);
180
+ const rightWidth = Math.floor(availableWidth / 2);
181
+ const leftPart = truncateChars(str, leftWidth);
182
+ const rightPart = truncateCharsFromEnd(str, rightWidth);
183
+ return leftPart + ellipsis + rightPart;
184
+ }
185
+ return truncateChars(str, availableWidth) + ellipsis;
186
+ }
187
+ function truncateChars(str, targetWidth) {
188
+ let result = "";
189
+ let width = 0;
190
+ for (const char of str) {
191
+ const charWidth = getStringWidth(char);
192
+ if (width + charWidth > targetWidth) {
193
+ break;
194
+ }
195
+ result += char;
196
+ width += charWidth;
197
+ }
198
+ return result;
199
+ }
200
+ function truncateCharsFromEnd(str, targetWidth) {
201
+ const collectedChars = [];
202
+ for (const char of str) {
203
+ collectedChars.push(char);
204
+ }
205
+ let result = "";
206
+ let width = 0;
207
+ for (let i = collectedChars.length - 1; i >= 0; i--) {
208
+ const char = collectedChars[i];
209
+ if (char === void 0) continue;
210
+ const charWidth = getStringWidth(char);
211
+ if (width + charWidth > targetWidth) {
212
+ break;
213
+ }
214
+ result = char + result;
215
+ width += charWidth;
216
+ }
217
+ return result;
218
+ }
219
+ function measureLines(str) {
220
+ const lines = str.split("\n");
221
+ let maxWidth = 0;
222
+ for (const line of lines) {
223
+ const lineWidth = getStringWidth(line);
224
+ if (lineWidth > maxWidth) {
225
+ maxWidth = lineWidth;
226
+ }
227
+ }
228
+ return {
229
+ lines,
230
+ maxWidth,
231
+ lineCount: lines.length
232
+ };
233
+ }
234
+
235
+ // src/terminal.ts
236
+ var import_terminal_size = __toESM(require("terminal-size"), 1);
237
+
238
+ // src/detection.ts
239
+ var import_node_child_process = require("child_process");
240
+ var AI_ASSISTANT_PATTERNS = [
241
+ { pattern: /claude-code/i, name: "claude-code" },
242
+ { pattern: /claude$/i, name: "claude" },
243
+ { pattern: /cursor/i, name: "cursor" },
244
+ { pattern: /copilot/i, name: "copilot" },
245
+ { pattern: /windsurf/i, name: "windsurf" },
246
+ { pattern: /cody/i, name: "cody" },
247
+ { pattern: /aider/i, name: "aider" },
248
+ { pattern: /continue/i, name: "continue" }
249
+ // continue.dev
250
+ ];
251
+ function getProcessTree() {
252
+ if (process.platform === "win32") {
253
+ return [];
254
+ }
255
+ const ancestors = [];
256
+ let pid = process.ppid;
257
+ for (let i = 0; i < 20 && pid > 1; i++) {
258
+ try {
259
+ const output = (0, import_node_child_process.execSync)(`ps -p ${String(pid)} -o ppid=,comm=`, {
260
+ encoding: "utf-8",
261
+ timeout: 1e3
262
+ }).trim();
263
+ const match = /^\s*(\d+)\s+(.+)$/.exec(output);
264
+ if (!match?.[1] || !match[2]) break;
265
+ const ppid = parseInt(match[1], 10);
266
+ const command = match[2];
267
+ ancestors.push({ pid, command });
268
+ pid = ppid;
269
+ } catch {
270
+ break;
271
+ }
272
+ }
273
+ return ancestors;
274
+ }
275
+ function findAIAssistantInProcessTree(processTree) {
276
+ for (const ancestor of processTree) {
277
+ for (const { pattern, name } of AI_ASSISTANT_PATTERNS) {
278
+ if (pattern.test(ancestor.command)) {
279
+ return name;
280
+ }
281
+ }
282
+ }
283
+ return void 0;
284
+ }
285
+ function detectEnvironment() {
286
+ const processTree = getProcessTree();
287
+ let aiScore = 0;
288
+ let terminalScore = 0;
289
+ let detectedAssistant;
290
+ detectedAssistant = findAIAssistantInProcessTree(processTree);
291
+ if (detectedAssistant) {
292
+ aiScore += 10;
293
+ }
294
+ const stdoutIsTTY = process.stdout.isTTY;
295
+ const stdinIsTTY = process.stdin.isTTY;
296
+ if (!stdoutIsTTY) {
297
+ aiScore += 2;
298
+ } else {
299
+ terminalScore += 2;
300
+ }
301
+ if (!stdinIsTTY) {
302
+ aiScore += 1;
303
+ }
304
+ if (process.env["CLAUDE_CODE"]) {
305
+ aiScore += 5;
306
+ detectedAssistant = detectedAssistant ?? "claude-code";
307
+ }
308
+ if (process.env["CURSOR_TRACE_ID"] || process.env["CURSOR_SESSION"]) {
309
+ aiScore += 3;
310
+ detectedAssistant = detectedAssistant ?? "cursor";
311
+ }
312
+ if (process.env["VSCODE_PID"] || process.env["VSCODE_IPC_HOOK"]) {
313
+ aiScore += 1;
314
+ }
315
+ const termProgram = process.env["TERM_PROGRAM"];
316
+ if (termProgram) {
317
+ const knownTerminals = [
318
+ "iTerm.app",
319
+ "Apple_Terminal",
320
+ "Hyper",
321
+ "Alacritty",
322
+ "kitty",
323
+ "WezTerm",
324
+ "Ghostty"
325
+ ];
326
+ if (knownTerminals.some((t) => termProgram.includes(t))) {
327
+ terminalScore += 2;
328
+ }
329
+ }
330
+ const term = process.env["TERM"];
331
+ if (term && term !== "dumb") {
332
+ terminalScore += 1;
333
+ if (term.includes("256color") || term.includes("truecolor")) {
334
+ terminalScore += 1;
335
+ }
336
+ } else if (term === "dumb" || !term) {
337
+ aiScore += 1;
338
+ }
339
+ if (process.env["ITERM_SESSION_ID"]) {
340
+ terminalScore += 2;
341
+ }
342
+ if (process.env["KITTY_WINDOW_ID"]) {
343
+ terminalScore += 2;
344
+ }
345
+ if (process.env["CI"] || process.env["GITHUB_ACTIONS"]) {
346
+ aiScore += 2;
347
+ }
348
+ const isAIAssistant = aiScore > terminalScore;
349
+ let confidence;
350
+ const scoreDiff = Math.abs(aiScore - terminalScore);
351
+ if (scoreDiff >= 4) {
352
+ confidence = "high";
353
+ } else if (scoreDiff >= 2) {
354
+ confidence = "medium";
355
+ } else {
356
+ confidence = "low";
357
+ }
358
+ const result = {
359
+ isAIAssistant,
360
+ confidence,
361
+ processTree
362
+ };
363
+ if (detectedAssistant !== void 0) {
364
+ result.detectedAssistant = detectedAssistant;
365
+ }
366
+ return result;
367
+ }
368
+ function isRunningInAIAssistant() {
369
+ return detectEnvironment().isAIAssistant;
370
+ }
371
+
372
+ // src/markdown.ts
373
+ var DEFAULT_ANCHOR = "\u2502";
374
+ function inlineCode(text) {
375
+ return ` \`${text}\``;
376
+ }
377
+ function anchorLine(content, anchor = DEFAULT_ANCHOR) {
378
+ return `${anchor}${content}`;
379
+ }
380
+ function applyMarkdownStyle(text, style) {
381
+ if (style === "secondary") {
382
+ return inlineCode(text);
383
+ }
384
+ return text;
385
+ }
386
+ function joinAnchoredLines(lines, anchor = DEFAULT_ANCHOR) {
387
+ return lines.map((line) => anchorLine(line, anchor)).join("\n");
388
+ }
389
+ function stripMarkdownFormatting(str) {
390
+ let result = str;
391
+ result = result.replace(/`([^`]+)`/g, "$1");
392
+ result = result.replace(/\*\*([^*]+)\*\*/g, "$1");
393
+ result = result.replace(/__([^_]+)__/g, "$1");
394
+ result = result.replace(
395
+ /(?<![*\w])\*([^*\s][^*]*[^*\s]|[^*\s])\*(?![*\w])/g,
396
+ "$1"
397
+ );
398
+ result = result.replace(
399
+ /(?<![_\w])_([^_\s][^_]*[^_\s]|[^_\s])_(?![_\w])/g,
400
+ "$1"
401
+ );
402
+ return result;
403
+ }
404
+ function getMarkdownRenderedWidth(str) {
405
+ return getStringWidth(stripMarkdownFormatting(str));
406
+ }
407
+
408
+ // src/styling.ts
409
+ var markdownStyleFunctions = {
410
+ primary: (text) => text,
411
+ secondary: (text) => text === "" ? "" : inlineCode(text),
412
+ header: (text) => text === "" ? "" : `**${text}**`,
413
+ border: (text) => text,
414
+ success: (text) => text === "" ? "" : inlineCode(text),
415
+ warning: (text) => text === "" ? "" : inlineCode(text),
416
+ error: (text) => text === "" ? "" : inlineCode(text),
417
+ info: (text) => text === "" ? "" : inlineCode(text)
418
+ };
419
+ function createPassthroughStyleFunctions() {
420
+ const passthrough = (text) => text;
421
+ return {
422
+ primary: passthrough,
423
+ secondary: passthrough,
424
+ header: passthrough,
425
+ border: passthrough,
426
+ success: passthrough,
427
+ warning: passthrough,
428
+ error: passthrough,
429
+ info: passthrough
430
+ };
431
+ }
432
+ function createThemedStyleFunctions(theme) {
433
+ return {
434
+ primary: (text) => theme.semantic.primary(text),
435
+ secondary: (text) => theme.semantic.secondary(text),
436
+ header: (text) => theme.semantic.header(text),
437
+ border: (text) => theme.semantic.border(text),
438
+ success: (text) => theme.semantic.success(text),
439
+ warning: (text) => theme.semantic.warning(text),
440
+ error: (text) => theme.semantic.error(text),
441
+ info: (text) => theme.semantic.info(text)
442
+ };
443
+ }
444
+ function createStyleFunctions(renderMode, theme) {
445
+ if (renderMode === "markdown") {
446
+ return markdownStyleFunctions;
447
+ }
448
+ if (theme) {
449
+ return createThemedStyleFunctions(theme);
450
+ }
451
+ return createPassthroughStyleFunctions();
452
+ }
453
+
454
+ // src/theme.ts
455
+ var import_chromaterm = require("chromaterm");
456
+ function createSemanticColors(theme) {
457
+ return {
458
+ primary: theme.foreground,
459
+ secondary: theme.muted,
460
+ border: theme.muted,
461
+ header: theme.brightWhite,
462
+ success: theme.success,
463
+ warning: theme.warning,
464
+ error: theme.error,
465
+ info: theme.info,
466
+ added: theme.green,
467
+ removed: theme.red,
468
+ modified: theme.yellow
469
+ };
470
+ }
471
+ function createThemeSync() {
472
+ const chromaterm = (0, import_chromaterm.createT1Theme)();
473
+ return {
474
+ chromaterm,
475
+ semantic: createSemanticColors(chromaterm)
476
+ };
477
+ }
478
+ async function detectTheme(options) {
479
+ const chromaterm = await (0, import_chromaterm.detectTheme)(options);
480
+ return {
481
+ chromaterm,
482
+ semantic: createSemanticColors(chromaterm)
483
+ };
484
+ }
485
+ var defaultTheme = createThemeSync();
486
+
487
+ // src/terminal.ts
488
+ var DEFAULT_TERMINAL_WIDTH = 80;
489
+ var DEFAULT_TERMINAL_HEIGHT = 24;
490
+ function getTerminalSize() {
491
+ const size = (0, import_terminal_size.default)();
492
+ return {
493
+ columns: size.columns || DEFAULT_TERMINAL_WIDTH,
494
+ rows: size.rows || DEFAULT_TERMINAL_HEIGHT
495
+ };
496
+ }
497
+ function getTerminalWidth() {
498
+ return getTerminalSize().columns;
499
+ }
500
+ function isTTY() {
501
+ const stdout = process.stdout;
502
+ return stdout.isTTY === true;
503
+ }
504
+ function detectColorLevel() {
505
+ if (process.env["NO_COLOR"] !== void 0) {
506
+ return 0;
507
+ }
508
+ const forceColor = process.env["FORCE_COLOR"];
509
+ if (forceColor !== void 0) {
510
+ if (forceColor === "0" || forceColor === "false") {
511
+ return 0;
512
+ }
513
+ if (forceColor === "1" || forceColor === "true" || forceColor === "") {
514
+ return 1;
515
+ }
516
+ if (forceColor === "2") {
517
+ return 2;
518
+ }
519
+ if (forceColor === "3") {
520
+ return 3;
521
+ }
522
+ }
523
+ if (!isTTY()) {
524
+ return 0;
525
+ }
526
+ const colorTerm = process.env["COLORTERM"];
527
+ if (colorTerm === "truecolor" || colorTerm === "24bit") {
528
+ return 3;
529
+ }
530
+ const term = process.env["TERM"] ?? "";
531
+ if (term.includes("256color") || term.includes("256")) {
532
+ return 2;
533
+ }
534
+ if (term.includes("color") || term.includes("ansi") || term === "xterm" || term === "linux") {
535
+ return 1;
536
+ }
537
+ return 1;
538
+ }
539
+ function determineRenderMode(options) {
540
+ if (options.renderMode !== void 0) {
541
+ return options.renderMode;
542
+ }
543
+ if (options.autoDetectMode === false) {
544
+ return "ansi";
545
+ }
546
+ if (isRunningInAIAssistant()) {
547
+ return "markdown";
548
+ }
549
+ return "ansi";
550
+ }
551
+ function createRenderContext(options = {}) {
552
+ const renderMode = determineRenderMode(options);
553
+ const colorLevel = options.noColor ? 0 : detectColorLevel();
554
+ const tty = isTTY();
555
+ const theme = colorLevel > 0 && renderMode === "ansi" ? options.theme ?? defaultTheme : void 0;
556
+ const context = {
557
+ width: options.width ?? getTerminalWidth(),
558
+ isTTY: tty,
559
+ colorLevel,
560
+ renderMode,
561
+ style: createStyleFunctions(renderMode, theme)
562
+ };
563
+ if (theme) {
564
+ context.theme = theme;
565
+ }
566
+ return context;
567
+ }
568
+
569
+ // src/wrap.ts
570
+ var import_wrap_ansi = __toESM(require("wrap-ansi"), 1);
571
+ function wrapText(text, width, options = {}) {
572
+ const { hard = false, trim = true, wordWrap = true } = options;
573
+ return (0, import_wrap_ansi.default)(text, width, {
574
+ hard,
575
+ trim,
576
+ wordWrap
577
+ });
578
+ }
579
+ function wrapTextWithInfo(text, width, options = {}) {
580
+ const wrapped = wrapText(text, width, options);
581
+ const lines = wrapped.split("\n");
582
+ let maxWidth = 0;
583
+ for (const line of lines) {
584
+ const lineWidth = getStringWidth(line);
585
+ if (lineWidth > maxWidth) {
586
+ maxWidth = lineWidth;
587
+ }
588
+ }
589
+ return {
590
+ text: wrapped,
591
+ lines,
592
+ lineCount: lines.length,
593
+ maxWidth
594
+ };
595
+ }
596
+ // Annotate the CommonJS export names for ESM import in node:
597
+ 0 && (module.exports = {
598
+ BaseTuiComponent,
599
+ ComponentRegistry,
600
+ DEFAULT_ANCHOR,
601
+ DEFAULT_TERMINAL_HEIGHT,
602
+ DEFAULT_TERMINAL_WIDTH,
603
+ anchorLine,
604
+ applyMarkdownStyle,
605
+ createRenderContext,
606
+ createStyleFunctions,
607
+ createThemeSync,
608
+ defaultTheme,
609
+ detectColorLevel,
610
+ detectEnvironment,
611
+ detectTheme,
612
+ getMarkdownRenderedWidth,
613
+ getProcessTree,
614
+ getStringWidth,
615
+ getTerminalSize,
616
+ getTerminalWidth,
617
+ inlineCode,
618
+ isRunningInAIAssistant,
619
+ isTTY,
620
+ joinAnchoredLines,
621
+ measureLines,
622
+ padToWidth,
623
+ registry,
624
+ stripMarkdownFormatting,
625
+ truncateToWidth,
626
+ wrapText,
627
+ wrapTextWithInfo
628
+ });
629
+ //# sourceMappingURL=index.cjs.map