@taui-standard/ink-adapter 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,20 @@
2
2
 
3
3
  The official [Ink](https://github.com/vadimdemedes/ink) adapter for **Terminal Agent UI (TAUI)**.
4
4
 
5
- **Author: Tariq Shams**
5
+ ### Ecosystem
6
+ Built on the **[TAUI-0001 Specification](https://github.com/TAUI-Standards/taui-standards)** and powered by:
7
+ - **[@taui-standard/core](https://github.com/TAUI-Standards/taui-core)**: Core runtime and state management.
8
+ - **[@taui-standard/validator](https://github.com/TAUI-Standards/taui-validator)**: Schema and primitive validation.
6
9
 
7
- ![TAUI HTOP Dashboard](examples/basic-htop/Screenshot.png)
10
+ *All packages and the TAUI specification created by **Tariq Shams***.
11
+
12
+ ## Demos
13
+
14
+ ### High-Fidelity HTOP
15
+ ![TAUI HTOP Dashboard](examples/basic-htop/Screenshot_Example_Htop.png)
16
+
17
+ ### Interactive Dashboard
18
+ ![TAUI Interactive Dashboard](examples/basic-htop/Screenshot_Example_Dashboard.png)
8
19
 
9
20
  ## Features
10
21
  - **Spec-Compliant**: Renders all primitives from TAUI-0001.
@@ -9,6 +9,7 @@ export declare const TAUIText: React.FC<RendererProps>;
9
9
  export declare const TAUIBox: React.FC<RendererProps>;
10
10
  export declare const TAUIButton: React.FC<RendererProps>;
11
11
  export declare const TAUIInput: React.FC<RendererProps>;
12
+ export declare const TAUISelect: React.FC<RendererProps>;
12
13
  export declare const TAUIProgress: React.FC<RendererProps>;
13
14
  export declare const TAUIKeyBinding: React.FC<RendererProps>;
14
15
  export declare const TAUITable: React.FC<RendererProps>;
package/dist/renderer.js CHANGED
@@ -21,7 +21,7 @@ export const TAUIBox = ({ node, runtime }) => {
21
21
  if (borderStyle && !validStyles.includes(borderStyle)) {
22
22
  borderStyle = 'single';
23
23
  }
24
- return (_jsxs(Box, { flexDirection: flexDirection, padding: typeof node.padding === 'number' ? node.padding : undefined, gap: node.gap, borderStyle: borderStyle, children: [node.type === 'Panel' && node.title && (_jsx(Box, { position: "absolute", marginTop: -1, marginLeft: 1, paddingX: 1, children: _jsx(Text, { bold: true, children: node.title }) })), node.children?.map((child, idx) => (_jsx(TAUIRenderer, { node: child, runtime: runtime }, child.id || `${node.type}-${idx}`)))] }));
24
+ return (_jsxs(Box, { flexDirection: flexDirection, padding: typeof node.padding === 'number' ? node.padding : undefined, gap: node.gap, borderStyle: borderStyle, children: [node.type === 'Panel' && node.title && (_jsx(Box, { position: "absolute", marginTop: -1, marginLeft: 1, paddingX: 1, children: _jsx(Text, { bold: true, color: node.style?.color, children: node.title }) })), node.children?.map((child, idx) => (_jsx(TAUIRenderer, { node: child, runtime: runtime }, child.id || `${node.type}-${idx}`)))] }));
25
25
  };
26
26
  export const TAUIButton = ({ node, runtime }) => {
27
27
  const { isFocused } = useFocus({ id: node.id });
@@ -34,7 +34,7 @@ export const TAUIButton = ({ node, runtime }) => {
34
34
  });
35
35
  }
36
36
  });
37
- return (_jsx(Box, { borderStyle: "round", borderColor: isFocused ? 'cyan' : 'gray', paddingX: 1, children: _jsx(Text, { color: isFocused ? 'cyan' : undefined, children: node.label }) }));
37
+ return (_jsx(Box, { borderStyle: "round", borderColor: isFocused ? (node.style?.color || 'white') : (node.style?.color || 'gray'), paddingX: 1, children: _jsx(Text, { color: node.style?.color, bold: isFocused, children: node.label }) }));
38
38
  };
39
39
  export const TAUIInput = ({ node, runtime }) => {
40
40
  const { isFocused } = useFocus({ id: node.id });
@@ -46,14 +46,46 @@ export const TAUIInput = ({ node, runtime }) => {
46
46
  timestamp: new Date().toISOString()
47
47
  });
48
48
  };
49
- return (_jsxs(Box, { flexDirection: "row", gap: 1, children: [node.placeholder && _jsxs(Text, { dimColor: true, children: [node.placeholder, ": "] }), _jsx(Box, { borderStyle: "single", borderColor: isFocused ? 'yellow' : 'gray', paddingX: 1, minWidth: 10, children: isFocused ? (_jsx(TextInput, { value: node.value || '', onChange: onChange, mask: node.mask ? '*' : undefined })) : (_jsx(Text, { children: node.mask ? '*'.repeat((node.value || '').length) : (node.value || ' ') })) })] }));
49
+ return (_jsxs(Box, { flexDirection: "row", gap: 1, children: [node.placeholder && _jsxs(Text, { dimColor: true, children: [node.placeholder, ": "] }), _jsx(Box, { borderStyle: "single", borderColor: isFocused ? (node.style?.color || 'white') : (node.style?.color || 'gray'), paddingX: 1, minWidth: 10, children: isFocused ? (_jsx(TextInput, { value: node.value || '', onChange: onChange, mask: node.mask ? '*' : undefined })) : (_jsx(Text, { color: node.style?.color, children: node.mask ? '*'.repeat((node.value || '').length) : (node.value || ' ') })) })] }));
50
+ };
51
+ export const TAUISelect = ({ node, runtime }) => {
52
+ const { isFocused } = useFocus({ id: node.id });
53
+ const options = node.options || [];
54
+ const currentValue = node.value;
55
+ const currentIndex = options.findIndex((opt) => opt.value === currentValue);
56
+ useInput((input, key) => {
57
+ if (isFocused) {
58
+ if (key.upArrow || key.leftArrow) {
59
+ const nextIndex = (currentIndex - 1 + options.length) % options.length;
60
+ runtime.dispatchRawEvent({
61
+ type: 'change',
62
+ targetId: node.id,
63
+ payload: { value: options[nextIndex].value },
64
+ timestamp: new Date().toISOString()
65
+ });
66
+ }
67
+ else if (key.downArrow || key.rightArrow) {
68
+ const nextIndex = (currentIndex + 1) % options.length;
69
+ runtime.dispatchRawEvent({
70
+ type: 'change',
71
+ targetId: node.id,
72
+ payload: { value: options[nextIndex].value },
73
+ timestamp: new Date().toISOString()
74
+ });
75
+ }
76
+ }
77
+ });
78
+ return (_jsx(Box, { flexDirection: "row", gap: 1, children: options.map((opt, i) => {
79
+ const isSelected = opt.value === currentValue;
80
+ return (_jsx(Box, { borderStyle: "single", borderColor: isFocused && isSelected ? (node.style?.color || 'white') : 'gray', paddingX: 1, children: _jsxs(Text, { color: node.style?.color, bold: isSelected, children: [isSelected ? '> ' : '', opt.label] }) }, i));
81
+ }) }));
50
82
  };
51
83
  export const TAUIProgress = ({ node }) => {
52
84
  const width = 20;
53
85
  const val = node.value || 0;
54
86
  const completed = Math.round(val * width);
55
87
  const bar = '\u2588'.repeat(completed) + '\u2591'.repeat(width - completed);
56
- return (_jsxs(Box, { flexDirection: "row", gap: 1, children: [node.label && _jsx(Text, { children: node.label }), _jsx(Text, { color: "cyan", children: bar }), _jsxs(Text, { children: [Math.round(val * 100), "%"] })] }));
88
+ return (_jsxs(Box, { flexDirection: "row", gap: 1, children: [node.label && _jsx(Text, { color: node.style?.color, children: node.label }), _jsx(Text, { color: node.style?.color, children: bar }), _jsxs(Text, { color: node.style?.color, children: [Math.round(val * 100), "%"] })] }));
57
89
  };
58
90
  export const TAUIKeyBinding = ({ node, runtime }) => {
59
91
  if (node.type !== 'KeyBinding')
@@ -87,13 +119,13 @@ export const TAUITable = ({ node }) => {
87
119
  return null;
88
120
  const headers = node.headers || [];
89
121
  const rows = node.rows || [];
90
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: [_jsx(Box, { flexDirection: "row", borderStyle: "single", borderBottom: true, children: headers.map((h, i) => (_jsx(Box, { flexGrow: 1, minWidth: 10, children: _jsx(Text, { bold: true, color: "cyan", children: h }) }, i))) }), rows.map((row, i) => (_jsx(Box, { flexDirection: "row", children: row.map((cell, j) => (_jsx(Box, { flexGrow: 1, minWidth: 10, children: _jsx(Text, { children: cell }) }, j))) }, i)))] }));
122
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: [_jsx(Box, { flexDirection: "row", borderStyle: "single", borderBottom: true, children: headers.map((h, i) => (_jsx(Box, { flexGrow: 1, minWidth: 10, children: _jsx(Text, { bold: true, color: node.style?.color, children: h }) }, i))) }), rows.map((row, i) => (_jsx(Box, { flexDirection: "row", children: row.map((cell, j) => (_jsx(Box, { flexGrow: 1, minWidth: 10, children: _jsx(Text, { color: node.style?.color, children: cell }) }, j))) }, i)))] }));
91
123
  };
92
124
  export const TAUILog = ({ node }) => {
93
125
  if (node.type !== 'Log')
94
126
  return null;
95
127
  const lines = node.lines || [];
96
- return (_jsx(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: lines.slice(-(node.maxLines || 100)).map((line, i) => (_jsx(Text, { children: line }, i))) }));
128
+ return (_jsx(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: lines.slice(-(node.maxLines || 100)).map((line, i) => (_jsx(Text, { color: node.style?.color, children: line }, i))) }));
97
129
  };
98
130
  export const TAUIRenderer = (props) => {
99
131
  const { node } = props;
@@ -110,6 +142,8 @@ export const TAUIRenderer = (props) => {
110
142
  return _jsx(TAUIButton, { ...props });
111
143
  case 'Input':
112
144
  return _jsx(TAUIInput, { ...props });
145
+ case 'Select':
146
+ return _jsx(TAUISelect, { ...props });
113
147
  case 'Progress':
114
148
  return _jsx(TAUIProgress, { ...props });
115
149
  case 'Table':
@@ -11,7 +11,7 @@ function getHtopData() {
11
11
  const loadAvg = os.loadavg();
12
12
  const cpus = os.cpus();
13
13
 
14
- // Mock process list since node:os doesn't provide it easily without spawning child processes
14
+ // Mock process list
15
15
  const mockProcesses = [
16
16
  ["1234", "root", loadAvg[0].toFixed(1), "1.2", "systemd"],
17
17
  ["5678", "user", (loadAvg[1] / 2).toFixed(1), "0.5", "bash"],
@@ -23,70 +23,98 @@ function getHtopData() {
23
23
  const doc: any = {
24
24
  version: "1.0",
25
25
  metadata: {
26
- title: "TAUI Real-time Dashboard",
26
+ title: "TAUI Official Dashboard",
27
27
  author: "Tariq Shams"
28
28
  },
29
29
  screen: {
30
- type: "Grid",
30
+ type: "Column",
31
31
  style: { backgroundColor: "black", color: "#FF8C00" },
32
- rows: ["12", "1fr", "3"],
33
- columns: ["1fr", "1fr"],
32
+ padding: 1,
33
+ gap: 1,
34
34
  children: [
35
35
  {
36
- id: "cpu_panel",
36
+ id: "header_panel",
37
37
  type: "Panel",
38
- title: "CPU Load",
39
- borderStyle: "rounded",
38
+ title: "TAUI Ecosystem Info",
39
+ borderStyle: "single",
40
40
  style: { color: "#FF8C00" },
41
41
  children: [
42
42
  {
43
- type: "Progress",
44
- value: Math.min(loadAvg[0] / cpus.length, 1),
45
- label: `Load 1m: ${(loadAvg[0]).toFixed(2)}`,
46
- style: { color: "#FF8C00" }
47
- },
48
- {
49
- type: "Text",
50
- content: `Arch: ${os.arch()} | CPUs: ${cpus.length}`,
51
- style: { color: "#FF8C00" }
43
+ type: "Box",
44
+ flexDirection: "column",
45
+ gap: 0,
46
+ children: [
47
+ { type: "Text", content: 'Official "@taui-standard/ink-adapter" fully compliant with TAUI 0001', style: { bold: true, color: "#FF8C00" } },
48
+ { type: "Text", content: "@taui-standard/core: Unified state and event runtime.", style: { color: "#FF8C00" } },
49
+ { type: "Text", content: "@taui-standard/validator: Strict schema and data validation.", style: { color: "#FF8C00" } },
50
+ { type: "Text", content: "All packages and the TAUI specification created by Tariq Shams", style: { italic: true, color: "#FF8C00" } }
51
+ ]
52
52
  }
53
53
  ]
54
54
  },
55
55
  {
56
- id: "mem_panel",
57
- type: "Panel",
58
- title: "Memory",
59
- borderStyle: "rounded",
60
- style: { color: "#FF8C00" },
56
+ type: "Grid",
57
+ rows: ["12", "1fr", "3"],
58
+ columns: ["1fr", "1fr"],
59
+ gap: 1,
61
60
  children: [
62
61
  {
63
- type: "Progress",
64
- value: usedMem / totalMem,
65
- label: `MEM ${(usedMem / 1024 / 1024 / 1024).toFixed(1)}G / ${(totalMem / 1024 / 1024 / 1024).toFixed(1)}G`,
66
- style: { color: "#FF8C00" }
62
+ id: "cpu_panel",
63
+ type: "Panel",
64
+ title: "CPU Load",
65
+ borderStyle: "rounded",
66
+ style: { color: "#FF8C00" },
67
+ children: [
68
+ {
69
+ type: "Progress",
70
+ value: Math.min(loadAvg[0] / cpus.length, 1),
71
+ label: `Load 1m: ${(loadAvg[0]).toFixed(2)}`,
72
+ style: { color: "#FF8C00" }
73
+ },
74
+ {
75
+ type: "Text",
76
+ content: `Arch: ${os.arch()} | CPUs: ${cpus.length}`,
77
+ style: { color: "#FF8C00" }
78
+ }
79
+ ]
80
+ },
81
+ {
82
+ id: "mem_panel",
83
+ type: "Panel",
84
+ title: "Memory",
85
+ borderStyle: "rounded",
86
+ style: { color: "#FF8C00" },
87
+ children: [
88
+ {
89
+ type: "Progress",
90
+ value: usedMem / totalMem,
91
+ label: `MEM ${(usedMem / 1024 / 1024 / 1024).toFixed(1)}G / ${(totalMem / 1024 / 1024 / 1024).toFixed(1)}G`,
92
+ style: { color: "#FF8C00" }
93
+ },
94
+ {
95
+ type: "Text",
96
+ content: `Free: ${(freeMem / 1024 / 1024 / 1024).toFixed(1)}G`,
97
+ style: { color: "#FF8C00" }
98
+ }
99
+ ]
67
100
  },
68
101
  {
69
- type: "Text",
70
- content: `Free: ${(freeMem / 1024 / 1024 / 1024).toFixed(1)}G`,
102
+ id: "process_table",
103
+ type: "Table",
104
+ headers: ["PID", "USER", "CPU%", "MEM%", "COMMAND"],
105
+ rows: mockProcesses,
71
106
  style: { color: "#FF8C00" }
107
+ },
108
+ {
109
+ id: "footer",
110
+ type: "Row",
111
+ children: [
112
+ { type: "Text", content: " [Q: Quit] [Tab: Navigate] [Enter: Action] ", style: { color: "#FF8C00", bold: true } },
113
+ { id: "refresh_btn", type: "Button", label: "Refresh Now", style: { color: "#FF8C00" } },
114
+ { id: "quit_kb", type: "KeyBinding", key: "q", shift: true, action: "quit" }
115
+ ]
72
116
  }
73
117
  ]
74
- },
75
- {
76
- id: "process_table",
77
- type: "Table",
78
- headers: ["PID", "USER", "CPU%", "MEM%", "COMMAND"],
79
- rows: mockProcesses,
80
- style: { color: "#FF8C00" }
81
- },
82
- {
83
- id: "footer",
84
- type: "Row",
85
- children: [
86
- { type: "Text", content: " [q: Quit] [Tab: Navigate] [Enter: Action] ", style: { color: "#FF8C00", bold: true } },
87
- { id: "refresh_btn", type: "Button", label: "Refresh Now" },
88
- { id: "quit_kb", type: "KeyBinding", key: "q", action: "quit" }
89
- ]
90
118
  }
91
119
  ]
92
120
  }
@@ -94,21 +122,16 @@ function getHtopData() {
94
122
  return doc;
95
123
  }
96
124
 
97
- // Initial set
98
125
  runtime.setDocument(getHtopData());
99
126
 
100
- // Update loop (every 2 seconds)
101
127
  const interval = setInterval(() => {
102
128
  try {
103
129
  runtime.setDocument(getHtopData());
104
- } catch (e) {
105
- // Suppress errors during shutdown
106
- }
130
+ } catch (e) {}
107
131
  }, 2000);
108
132
 
109
- // Handle interaction
110
133
  runtime.onEvent((event) => {
111
- if (event.type === 'key' && (event.payload?.key === 'q' || event.targetId === 'quit_kb')) {
134
+ if (event.type === 'key' && event.payload?.key === 'q' && event.payload?.shift) {
112
135
  clearInterval(interval);
113
136
  process.exit(0);
114
137
  }
@@ -8,82 +8,146 @@ let userEmail = '';
8
8
  let selectedPriority = 'medium';
9
9
  let logs: string[] = ['System initialized. Press "q" to exit.'];
10
10
 
11
- function updateUI() {
12
- runtime.setDocument({
11
+ function getDashboardData() {
12
+ const doc: any = {
13
13
  version: "1.0",
14
- metadata: { title: "TAUI Advanced Dashboard" },
14
+ metadata: {
15
+ title: "TAUI Official Interactive Demo",
16
+ author: "Tariq Shams"
17
+ },
15
18
  screen: {
16
- type: "Panel",
17
- title: "TAUI interactive Demo",
19
+ type: "Column",
20
+ style: { backgroundColor: "black", color: "#FF8C00" },
18
21
  padding: 1,
22
+ gap: 1,
19
23
  children: [
20
24
  {
21
- type: "KeyBinding",
22
- id: "exit-key",
23
- key: "q",
24
- action: "exit"
25
- },
26
- {
27
- type: "Column",
28
- gap: 1,
25
+ id: "header_panel",
26
+ type: "Panel",
27
+ title: "TAUI Ecosystem Info",
28
+ borderStyle: "single",
29
+ style: { color: "#FF8C00" },
29
30
  children: [
30
31
  {
31
- type: "Input",
32
- id: "email-input",
33
- placeholder: "User Email",
34
- value: userEmail
35
- },
36
- {
37
- type: "Select",
38
- id: "priority-select",
39
- value: selectedPriority,
40
- options: [
41
- { label: "Low Priority", value: "low" },
42
- { label: "Medium Priority", value: "medium" },
43
- { label: "High Priority", value: "high" }
32
+ type: "Box",
33
+ flexDirection: "column",
34
+ gap: 0,
35
+ children: [
36
+ { type: "Text", content: 'Official "@taui-standard/ink-adapter" fully compliant with TAUI 0001', style: { bold: true, color: "#FF8C00" } },
37
+ { type: "Text", content: "@taui-standard/core: Unified state and event runtime.", style: { color: "#FF8C00" } },
38
+ { type: "Text", content: "@taui-standard/validator: Strict schema and data validation.", style: { color: "#FF8C00" } },
39
+ { type: "Text", content: "All packages and the TAUI specification created by Tariq Shams", style: { italic: true, color: "#FF8C00" } }
44
40
  ]
45
- },
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ type: "Panel",
46
+ title: "Interactive controls",
47
+ borderStyle: "rounded",
48
+ style: { color: "#FF8C00" },
49
+ children: [
46
50
  {
47
- type: "Row",
48
- gap: 2,
51
+ type: "Column",
52
+ gap: 1,
49
53
  children: [
50
- { type: "Button", id: "submit-btn", label: "Deploy Agent" },
51
- { type: "Button", id: "clear-btn", label: "Clear Logs" }
54
+ {
55
+ id: "exit-key",
56
+ type: "KeyBinding",
57
+ key: "q",
58
+ shift: true,
59
+ action: "exit"
60
+ },
61
+ {
62
+ id: "email-input",
63
+ type: "Input",
64
+ placeholder: "Enter Target Email",
65
+ value: userEmail,
66
+ style: { color: "#FF8C00" }
67
+ },
68
+ {
69
+ type: "Text",
70
+ content: "Select Priority Level:",
71
+ style: { color: "#FF8C00" }
72
+ },
73
+ {
74
+ id: "priority-select",
75
+ type: "Select",
76
+ value: selectedPriority,
77
+ style: { color: "#FF8C00" },
78
+ options: [
79
+ { label: "Low Priority", value: "low" },
80
+ { label: "Medium Priority", value: "medium" },
81
+ { label: "High Priority", value: "high" }
82
+ ]
83
+ },
84
+ {
85
+ type: "Row",
86
+ gap: 2,
87
+ children: [
88
+ { id: "submit-btn", type: "Button", label: "Deploy Agent", style: { color: "#FF8C00" } },
89
+ { id: "clear-btn", type: "Button", label: "Clear Logs", style: { color: "#FF8C00" } }
90
+ ]
91
+ }
52
92
  ]
53
- },
93
+ }
94
+ ]
95
+ },
96
+ {
97
+ type: "Panel",
98
+ title: "Activity Stream",
99
+ borderStyle: "single",
100
+ style: { color: "#FF8C00" },
101
+ children: [
54
102
  {
55
- type: "Panel",
56
- title: "Activity Logs",
57
- children: logs.slice(-5).map((msg, i) => ({
58
- type: "Text",
59
- id: `log-${i}`,
60
- content: `> ${msg}`,
61
- style: { dim: true }
62
- }))
103
+ id: "activity-log",
104
+ type: "Log",
105
+ maxLines: 6,
106
+ lines: logs,
107
+ style: { color: "#FF8C00" }
63
108
  }
64
109
  ]
110
+ },
111
+ {
112
+ type: "Text",
113
+ content: " [Tab/Shift+Tab] Navigate | [Enter] Select/Action | [Q] Quit ",
114
+ style: { color: "#FF8C00", dim: true }
65
115
  }
66
116
  ]
67
117
  }
68
- });
118
+ };
119
+ return doc;
69
120
  }
70
121
 
71
122
  runtime.onEvent((event) => {
123
+ let changed = false;
124
+
72
125
  if (event.type === 'change' && event.targetId === 'email-input') {
73
126
  userEmail = (event.payload as any).value;
74
- logs.push(`Email changed: ${userEmail}`);
127
+ changed = true;
75
128
  } else if (event.type === 'change' && event.targetId === 'priority-select') {
76
129
  selectedPriority = (event.payload as any).value;
77
- logs.push(`Priority set to: ${selectedPriority}`);
130
+ logs.push(`Priority set to: ${selectedPriority.toUpperCase()}`);
131
+ changed = true;
78
132
  } else if (event.type === 'action' && event.targetId === 'submit-btn') {
79
- logs.push(`DEPLOYING agent to ${userEmail} [Priority: ${selectedPriority}]`);
133
+ if (!userEmail) {
134
+ logs.push("Error: Email is required.");
135
+ } else {
136
+ logs.push(`DEPLOYING agent to ${userEmail} [PRIORITY: ${selectedPriority.toUpperCase()}]`);
137
+ logs.push("Deployment status: IN_PROGRESS...");
138
+ }
139
+ changed = true;
80
140
  } else if (event.type === 'action' && event.targetId === 'clear-btn') {
81
141
  logs = ['Logs cleared.'];
82
- } else if (event.type === 'key' && event.targetId === 'exit-key') {
142
+ changed = true;
143
+ } else if (event.type === 'key' && event.payload?.key === 'q' && event.payload?.shift) {
83
144
  process.exit(0);
84
145
  }
85
- updateUI();
146
+
147
+ if (changed) {
148
+ runtime.setDocument(getDashboardData());
149
+ }
86
150
  });
87
151
 
88
- updateUI();
152
+ runtime.setDocument(getDashboardData());
89
153
  startTAUI(runtime);
@@ -13,6 +13,10 @@
13
13
  "@taui-standard/ink-adapter": "1.0.4",
14
14
  "@taui-standard/validator": "1.0.4",
15
15
  "tsx": "^4.7.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^20.11.0",
19
+ "@types/react": "^18.2.0"
16
20
  }
17
21
  },
18
22
  "node_modules/@alcalzone/ansi-tokenize": {
@@ -477,6 +481,34 @@
477
481
  "zod": "^3.22.4"
478
482
  }
479
483
  },
484
+ "node_modules/@types/node": {
485
+ "version": "20.19.30",
486
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz",
487
+ "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==",
488
+ "dev": true,
489
+ "license": "MIT",
490
+ "dependencies": {
491
+ "undici-types": "~6.21.0"
492
+ }
493
+ },
494
+ "node_modules/@types/prop-types": {
495
+ "version": "15.7.15",
496
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
497
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
498
+ "devOptional": true,
499
+ "license": "MIT"
500
+ },
501
+ "node_modules/@types/react": {
502
+ "version": "18.3.27",
503
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
504
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
505
+ "devOptional": true,
506
+ "license": "MIT",
507
+ "dependencies": {
508
+ "@types/prop-types": "*",
509
+ "csstype": "^3.2.2"
510
+ }
511
+ },
480
512
  "node_modules/ajv": {
481
513
  "version": "8.17.1",
482
514
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
@@ -653,6 +685,13 @@
653
685
  "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
654
686
  }
655
687
  },
688
+ "node_modules/csstype": {
689
+ "version": "3.2.3",
690
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
691
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
692
+ "devOptional": true,
693
+ "license": "MIT"
694
+ },
656
695
  "node_modules/emoji-regex": {
657
696
  "version": "10.6.0",
658
697
  "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
@@ -1135,6 +1174,13 @@
1135
1174
  "url": "https://github.com/sponsors/sindresorhus"
1136
1175
  }
1137
1176
  },
1177
+ "node_modules/undici-types": {
1178
+ "version": "6.21.0",
1179
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1180
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1181
+ "dev": true,
1182
+ "license": "MIT"
1183
+ },
1138
1184
  "node_modules/widest-line": {
1139
1185
  "version": "5.0.0",
1140
1186
  "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz",
@@ -13,5 +13,9 @@
13
13
  "@taui-standard/ink-adapter": "1.0.4",
14
14
  "@taui-standard/validator": "1.0.4",
15
15
  "tsx": "^4.7.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^20.11.0",
19
+ "@types/react": "^18.2.0"
16
20
  }
17
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taui-standard/ink-adapter",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "description": "Ink adapter for Terminal Agent UI (TAUI)",
6
6
  "repository": {
package/src/renderer.tsx CHANGED
@@ -50,7 +50,7 @@ export const TAUIBox: React.FC<RendererProps> = ({ node, runtime }) => {
50
50
  >
51
51
  {node.type === 'Panel' && node.title && (
52
52
  <Box position="absolute" marginTop={-1} marginLeft={1} paddingX={1}>
53
- <Text bold>{node.title}</Text>
53
+ <Text bold color={node.style?.color}>{node.title}</Text>
54
54
  </Box>
55
55
  )}
56
56
  {node.children?.map((child: TAUINode, idx: number) => (
@@ -74,8 +74,8 @@ export const TAUIButton: React.FC<RendererProps> = ({ node, runtime }) => {
74
74
  });
75
75
 
76
76
  return (
77
- <Box borderStyle="round" borderColor={isFocused ? 'cyan' : 'gray'} paddingX={1}>
78
- <Text color={isFocused ? 'cyan' : undefined}>{node.label}</Text>
77
+ <Box borderStyle="round" borderColor={isFocused ? (node.style?.color || 'white') : (node.style?.color || 'gray')} paddingX={1}>
78
+ <Text color={node.style?.color} bold={isFocused}>{node.label}</Text>
79
79
  </Box>
80
80
  );
81
81
  };
@@ -95,17 +95,66 @@ export const TAUIInput: React.FC<RendererProps> = ({ node, runtime }) => {
95
95
  return (
96
96
  <Box flexDirection="row" gap={1}>
97
97
  {node.placeholder && <Text dimColor>{node.placeholder}: </Text>}
98
- <Box borderStyle="single" borderColor={isFocused ? 'yellow' : 'gray'} paddingX={1} minWidth={10}>
98
+ <Box borderStyle="single" borderColor={isFocused ? (node.style?.color || 'white') : (node.style?.color || 'gray')} paddingX={1} minWidth={10}>
99
99
  {isFocused ? (
100
100
  <TextInput value={node.value || ''} onChange={onChange} mask={node.mask ? '*' : undefined} />
101
101
  ) : (
102
- <Text>{node.mask ? '*'.repeat((node.value || '').length) : (node.value || ' ')}</Text>
102
+ <Text color={node.style?.color}>{node.mask ? '*'.repeat((node.value || '').length) : (node.value || ' ')}</Text>
103
103
  )}
104
104
  </Box>
105
105
  </Box>
106
106
  );
107
107
  };
108
108
 
109
+ export const TAUISelect: React.FC<RendererProps> = ({ node, runtime }) => {
110
+ const { isFocused } = useFocus({ id: node.id });
111
+ const options = (node as any).options || [];
112
+ const currentValue = (node as any).value;
113
+ const currentIndex = options.findIndex((opt: any) => opt.value === currentValue);
114
+
115
+ useInput((input, key) => {
116
+ if (isFocused) {
117
+ if (key.upArrow || key.leftArrow) {
118
+ const nextIndex = (currentIndex - 1 + options.length) % options.length;
119
+ runtime.dispatchRawEvent({
120
+ type: 'change',
121
+ targetId: node.id,
122
+ payload: { value: options[nextIndex].value },
123
+ timestamp: new Date().toISOString()
124
+ });
125
+ } else if (key.downArrow || key.rightArrow) {
126
+ const nextIndex = (currentIndex + 1) % options.length;
127
+ runtime.dispatchRawEvent({
128
+ type: 'change',
129
+ targetId: node.id,
130
+ payload: { value: options[nextIndex].value },
131
+ timestamp: new Date().toISOString()
132
+ });
133
+ }
134
+ }
135
+ });
136
+
137
+ return (
138
+ <Box flexDirection="row" gap={1}>
139
+ {options.map((opt: any, i: number) => {
140
+ const isSelected = opt.value === currentValue;
141
+ return (
142
+ <Box
143
+ key={i}
144
+ borderStyle="single"
145
+ borderColor={isFocused && isSelected ? (node.style?.color || 'white') : 'gray'}
146
+ paddingX={1}
147
+ >
148
+ <Text color={node.style?.color} bold={isSelected}>
149
+ {isSelected ? '> ' : ''}{opt.label}
150
+ </Text>
151
+ </Box>
152
+ );
153
+ })}
154
+ </Box>
155
+ );
156
+ };
157
+
109
158
  export const TAUIProgress: React.FC<RendererProps> = ({ node }) => {
110
159
  const width = 20;
111
160
  const val = (node as any).value || 0;
@@ -114,9 +163,9 @@ export const TAUIProgress: React.FC<RendererProps> = ({ node }) => {
114
163
 
115
164
  return (
116
165
  <Box flexDirection="row" gap={1}>
117
- {(node as any).label && <Text>{(node as any).label}</Text>}
118
- <Text color="cyan">{bar}</Text>
119
- <Text>{Math.round(val * 100)}%</Text>
166
+ {(node as any).label && <Text color={node.style?.color}>{(node as any).label}</Text>}
167
+ <Text color={node.style?.color}>{bar}</Text>
168
+ <Text color={node.style?.color}>{Math.round(val * 100)}%</Text>
120
169
  </Box>
121
170
  );
122
171
  };
@@ -163,7 +212,7 @@ export const TAUITable: React.FC<RendererProps> = ({ node }) => {
163
212
  <Box flexDirection="row" borderStyle="single" borderBottom>
164
213
  {headers.map((h: string, i: number) => (
165
214
  <Box key={i} flexGrow={1} minWidth={10}>
166
- <Text bold color="cyan">{h}</Text>
215
+ <Text bold color={node.style?.color}>{h}</Text>
167
216
  </Box>
168
217
  ))}
169
218
  </Box>
@@ -171,7 +220,7 @@ export const TAUITable: React.FC<RendererProps> = ({ node }) => {
171
220
  <Box key={i} flexDirection="row">
172
221
  {row.map((cell: string, j: number) => (
173
222
  <Box key={j} flexGrow={1} minWidth={10}>
174
- <Text>{cell}</Text>
223
+ <Text color={node.style?.color}>{cell}</Text>
175
224
  </Box>
176
225
  ))}
177
226
  </Box>
@@ -186,7 +235,7 @@ export const TAUILog: React.FC<RendererProps> = ({ node }) => {
186
235
  return (
187
236
  <Box flexDirection="column" borderStyle="single" paddingX={1}>
188
237
  {lines.slice(-(node.maxLines || 100)).map((line: string, i: number) => (
189
- <Text key={i}>{line}</Text>
238
+ <Text key={i} color={node.style?.color}>{line}</Text>
190
239
  ))}
191
240
  </Box>
192
241
  );
@@ -207,6 +256,8 @@ export const TAUIRenderer: React.FC<RendererProps> = (props) => {
207
256
  return <TAUIButton {...props} />;
208
257
  case 'Input':
209
258
  return <TAUIInput {...props} />;
259
+ case 'Select':
260
+ return <TAUISelect {...props} />;
210
261
  case 'Progress':
211
262
  return <TAUIProgress {...props} />;
212
263
  case 'Table':
Binary file