@valyrianjs/terminal 0.2.0 → 0.2.2

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.
Files changed (102) hide show
  1. package/dist/ansi.d.ts +2 -0
  2. package/dist/ansi.d.ts.map +1 -1
  3. package/dist/ansi.js +23 -13
  4. package/dist/ansi.js.map +1 -1
  5. package/dist/events.d.ts.map +1 -1
  6. package/dist/events.js +10 -2
  7. package/dist/events.js.map +1 -1
  8. package/dist/frame-style.d.ts +7 -0
  9. package/dist/frame-style.d.ts.map +1 -0
  10. package/dist/frame-style.js +27 -0
  11. package/dist/frame-style.js.map +1 -0
  12. package/dist/keymap.d.ts.map +1 -1
  13. package/dist/keymap.js +4 -2
  14. package/dist/keymap.js.map +1 -1
  15. package/dist/layout.d.ts +5 -1
  16. package/dist/layout.d.ts.map +1 -1
  17. package/dist/layout.js +55 -24
  18. package/dist/layout.js.map +1 -1
  19. package/dist/mouse.d.ts +6 -0
  20. package/dist/mouse.d.ts.map +1 -1
  21. package/dist/mouse.js +38 -17
  22. package/dist/mouse.js.map +1 -1
  23. package/dist/primitives.d.ts.map +1 -1
  24. package/dist/primitives.js +8 -1
  25. package/dist/primitives.js.map +1 -1
  26. package/dist/render.d.ts.map +1 -1
  27. package/dist/render.js +266 -70
  28. package/dist/render.js.map +1 -1
  29. package/dist/runtime.d.ts.map +1 -1
  30. package/dist/runtime.js +13 -5
  31. package/dist/runtime.js.map +1 -1
  32. package/dist/session.d.ts.map +1 -1
  33. package/dist/session.js +325 -83
  34. package/dist/session.js.map +1 -1
  35. package/dist/text.d.ts +7 -0
  36. package/dist/text.d.ts.map +1 -1
  37. package/dist/text.js +114 -0
  38. package/dist/text.js.map +1 -1
  39. package/dist/theme.d.ts.map +1 -1
  40. package/dist/theme.js +3 -0
  41. package/dist/theme.js.map +1 -1
  42. package/dist/tree.d.ts.map +1 -1
  43. package/dist/tree.js +18 -4
  44. package/dist/tree.js.map +1 -1
  45. package/dist/types.d.ts +41 -4
  46. package/dist/types.d.ts.map +1 -1
  47. package/docs/api-reference.md +18 -8
  48. package/docs/cookbook.md +1 -1
  49. package/docs/interaction-model.md +10 -8
  50. package/docs/primitive-gallery.md +9 -5
  51. package/examples/basic.tsx +22 -0
  52. package/examples/cli.tsx +55 -0
  53. package/examples/demo.tsx +98 -0
  54. package/examples/docs/background-fill.tsx +107 -0
  55. package/examples/docs/component-composition.tsx +140 -0
  56. package/examples/docs/cursor.tsx +121 -0
  57. package/examples/docs/employees-list.tsx +138 -0
  58. package/examples/docs/hello.tsx +98 -0
  59. package/examples/docs/interactive-note.tsx +111 -0
  60. package/examples/docs/module-api-dashboard.tsx +307 -0
  61. package/examples/docs/module-flux-store.tsx +181 -0
  62. package/examples/docs/module-form-workflow.tsx +339 -0
  63. package/examples/docs/module-forms.tsx +218 -0
  64. package/examples/docs/module-money.tsx +175 -0
  65. package/examples/docs/module-native-store.tsx +188 -0
  66. package/examples/docs/module-pulses.tsx +142 -0
  67. package/examples/docs/module-query.tsx +209 -0
  68. package/examples/docs/module-request.tsx +194 -0
  69. package/examples/docs/module-state-workbench.tsx +283 -0
  70. package/examples/docs/module-tasks.tsx +223 -0
  71. package/examples/docs/module-translate.tsx +194 -0
  72. package/examples/docs/module-utils.tsx +168 -0
  73. package/examples/docs/module-valyrian-core.tsx +159 -0
  74. package/examples/docs/pizza-builder.tsx +463 -0
  75. package/examples/docs/primitive-activity-console.tsx +113 -0
  76. package/examples/docs/primitive-command-panel.tsx +186 -0
  77. package/examples/docs/primitive-data-explorer.tsx +155 -0
  78. package/examples/docs/primitive-input-workbench.tsx +128 -0
  79. package/examples/docs/primitive-layout-shell.tsx +115 -0
  80. package/examples/docs/responsive-split.tsx +186 -0
  81. package/examples/docs/style-system.tsx +209 -0
  82. package/examples/docs/theme-colors.tsx +225 -0
  83. package/examples/docs/virtualized-list-workbench.tsx +232 -0
  84. package/examples/opencode-dogfood-app.tsx +215 -0
  85. package/examples/opencode-dogfood-lifecycle.tsx +194 -0
  86. package/examples/opencode-dogfood.tsx +11 -0
  87. package/llms-full.txt +38 -22
  88. package/package.json +3 -2
  89. package/src/ansi.ts +23 -13
  90. package/src/events.ts +6 -2
  91. package/src/frame-style.ts +36 -0
  92. package/src/keymap.ts +4 -2
  93. package/src/layout.ts +59 -25
  94. package/src/mouse.ts +41 -16
  95. package/src/primitives.ts +8 -1
  96. package/src/render.ts +286 -71
  97. package/src/runtime.ts +13 -5
  98. package/src/session.ts +343 -79
  99. package/src/text.ts +148 -0
  100. package/src/theme.ts +3 -0
  101. package/src/tree.ts +19 -4
  102. package/src/types.ts +48 -3
@@ -0,0 +1,140 @@
1
+ import { Screen, Text, mountTerminal } from "@valyrianjs/terminal";
2
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
3
+
4
+ interface Profile {
5
+ name: string;
6
+ role: string;
7
+ status: string;
8
+ }
9
+
10
+ interface ComponentCompositionState {
11
+ selectedIndex: number;
12
+ running: boolean;
13
+ }
14
+
15
+ export interface ComponentCompositionDemo {
16
+ session: TerminalSession;
17
+ dispatchKey(key: string): string;
18
+ output(): string;
19
+ ansiOutput(): string;
20
+ isRunning(): boolean;
21
+ destroy(): void;
22
+ }
23
+
24
+ const profiles: Profile[] = [
25
+ { name: "Maya", role: "Engineering", status: "Ready" },
26
+ { name: "Ana", role: "Design", status: "Reviewing" },
27
+ { name: "Luis", role: "Support", status: "Helping customers" }
28
+ ];
29
+
30
+ function createInitialState(): ComponentCompositionState {
31
+ return { selectedIndex: 0, running: true };
32
+ }
33
+
34
+ function shouldRunSnapshot() {
35
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
36
+ }
37
+
38
+ function StatusBadge({ status }: { status: string }) {
39
+ return <Text>Status: {status}</Text>;
40
+ }
41
+
42
+ function Greeting({ name }: { name: string }) {
43
+ return <Text>Hello, {name}</Text>;
44
+ }
45
+
46
+ function ProfileCard({ profile }: { profile: Profile }) {
47
+ return (
48
+ <>
49
+ <Greeting name={profile.name} />
50
+ <Text>Role: {profile.role}</Text>
51
+ <StatusBadge status={profile.status} />
52
+ </>
53
+ );
54
+ }
55
+
56
+ export function App({ state }: { state: ComponentCompositionState }) {
57
+ return (
58
+ <Screen title="Components">
59
+ <ProfileCard profile={profiles[state.selectedIndex]} />
60
+ <Text>N/P: switch card Ctrl+C: quit</Text>
61
+ </Screen>
62
+ );
63
+ }
64
+
65
+ export function createComponentCompositionDemo(options: TerminalMountOptions = {}): ComponentCompositionDemo {
66
+ const state = createInitialState();
67
+ let session: TerminalSession;
68
+
69
+ function quit() {
70
+ state.running = false;
71
+ session.destroy();
72
+ }
73
+
74
+ function move(delta: number) {
75
+ state.selectedIndex = (state.selectedIndex + delta + profiles.length) % profiles.length;
76
+ }
77
+
78
+ session = mountTerminal(<App state={state} />, {
79
+ ...options,
80
+ keymap: {
81
+ ...options.keymap,
82
+ bindings: [
83
+ ...(options.keymap?.bindings || []),
84
+ { key: "n", command: { id: "card.next" }, scope: "global" },
85
+ { key: "N", command: { id: "card.next" }, scope: "global" },
86
+ { key: "RIGHT", command: { id: "card.next" }, scope: "global" },
87
+ { key: "p", command: { id: "card.prev" }, scope: "global" },
88
+ { key: "P", command: { id: "card.prev" }, scope: "global" },
89
+ { key: "LEFT", command: { id: "card.prev" }, scope: "global" },
90
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
91
+ ],
92
+ onCommand(command, context) {
93
+ if (command.id === "card.next") {
94
+ move(1);
95
+ return true;
96
+ }
97
+ if (command.id === "card.prev") {
98
+ move(-1);
99
+ return true;
100
+ }
101
+ if (command.id === "quit") {
102
+ quit();
103
+ return true;
104
+ }
105
+ return options.keymap?.onCommand?.(command, context);
106
+ }
107
+ }
108
+ });
109
+
110
+ return {
111
+ session,
112
+ dispatchKey(key: string) {
113
+ return session.dispatchKey(key);
114
+ },
115
+ output() {
116
+ return session.output();
117
+ },
118
+ ansiOutput() {
119
+ return session.ansiOutput();
120
+ },
121
+ isRunning() {
122
+ return state.running;
123
+ },
124
+ destroy() {
125
+ state.running = false;
126
+ session.destroy();
127
+ }
128
+ };
129
+ }
130
+
131
+ if (import.meta.main) {
132
+ if (shouldRunSnapshot()) {
133
+ const demo = createComponentCompositionDemo({ runtime: "headless", cols: 44, rows: 8 });
134
+ process.stdout.write(demo.output());
135
+ process.stdout.write("\n");
136
+ demo.destroy();
137
+ } else {
138
+ createComponentCompositionDemo();
139
+ }
140
+ }
@@ -0,0 +1,121 @@
1
+ import { Input, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
2
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
3
+
4
+ interface CursorState {
5
+ value: string;
6
+ cursorNote: string;
7
+ running: boolean;
8
+ }
9
+
10
+ export interface CursorDemo {
11
+ session: TerminalSession;
12
+ dispatchKey(key: string): string;
13
+ output(): string;
14
+ ansiOutput(): string;
15
+ isRunning(): boolean;
16
+ destroy(): void;
17
+ }
18
+
19
+ function createInitialState(): CursorState {
20
+ return {
21
+ value: "ready",
22
+ cursorNote: "Terminal cursor hidden while this app owns the screen",
23
+ running: true
24
+ };
25
+ }
26
+
27
+ function shouldRunSnapshot() {
28
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
29
+ }
30
+
31
+ export function App({ state }: { state: CursorState }) {
32
+ return (
33
+ <Screen title="Cursor demo">
34
+ <Text>Cursor demo</Text>
35
+ <Text>App hides the terminal cursor while it owns the screen.</Text>
36
+ <Text>Focused input keeps its editing cursor visible.</Text>
37
+ <Input
38
+ id="search"
39
+ value={state.value}
40
+ placeholder="Type here"
41
+ onchange={(event) => {
42
+ state.value = event.value;
43
+ }}
44
+ />
45
+ <Text>{`Input value: ${state.value}`}</Text>
46
+ <Text>{state.cursorNote}</Text>
47
+ <Text>Type: edit field | C: cursor note | Ctrl+C: quit</Text>
48
+ </Screen>
49
+ );
50
+ }
51
+
52
+ export function createCursorDemo(options: TerminalMountOptions = {}): CursorDemo {
53
+ const state = createInitialState();
54
+ let session: TerminalSession;
55
+
56
+ function quit() {
57
+ state.running = false;
58
+ session.destroy();
59
+ }
60
+
61
+ session = mountTerminal(<App state={state} />, {
62
+ hideCursor: true,
63
+ restoreOnDestroy: true,
64
+ ...options,
65
+ keymap: {
66
+ ...options.keymap,
67
+ bindings: [
68
+ ...(options.keymap?.bindings || []),
69
+ { key: "c", command: { id: "cursor.note" }, scope: "global" },
70
+ { key: "C", command: { id: "cursor.note" }, scope: "global" },
71
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
72
+ ],
73
+ onCommand(command, context) {
74
+ if (command.id === "cursor.note") {
75
+ state.cursorNote = state.cursorNote.startsWith("Terminal cursor hidden")
76
+ ? "Terminal restores the cursor when the app closes"
77
+ : "Terminal cursor hidden while this app owns the screen";
78
+ return true;
79
+ }
80
+ if (command.id === "quit") {
81
+ quit();
82
+ return true;
83
+ }
84
+ return options.keymap?.onCommand?.(command, context);
85
+ }
86
+ }
87
+ });
88
+
89
+ session.focus("search");
90
+
91
+ return {
92
+ session,
93
+ dispatchKey(key: string) {
94
+ return session.dispatchKey(key);
95
+ },
96
+ output() {
97
+ return session.output();
98
+ },
99
+ ansiOutput() {
100
+ return session.ansiOutput();
101
+ },
102
+ isRunning() {
103
+ return state.running;
104
+ },
105
+ destroy() {
106
+ state.running = false;
107
+ session.destroy();
108
+ }
109
+ };
110
+ }
111
+
112
+ if (import.meta.main) {
113
+ if (shouldRunSnapshot()) {
114
+ const demo = createCursorDemo({ runtime: "headless", cols: 56, rows: 7 });
115
+ process.stdout.write(demo.output());
116
+ process.stdout.write("\n");
117
+ demo.destroy();
118
+ } else {
119
+ createCursorDemo();
120
+ }
121
+ }
@@ -0,0 +1,138 @@
1
+ import { Screen, Text, mountTerminal } from "@valyrianjs/terminal";
2
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
3
+
4
+ type Employee = {
5
+ name: string;
6
+ role: string;
7
+ };
8
+
9
+ interface EmployeesListState {
10
+ selectedIndex: number;
11
+ running: boolean;
12
+ }
13
+
14
+ export interface EmployeesListDemo {
15
+ session: TerminalSession;
16
+ dispatchKey(key: string): string;
17
+ output(): string;
18
+ ansiOutput(): string;
19
+ isRunning(): boolean;
20
+ destroy(): void;
21
+ }
22
+
23
+ const employees: Employee[] = [
24
+ { name: "Ana", role: "Design" },
25
+ { name: "Luis", role: "Support" },
26
+ { name: "Maya", role: "Engineering" }
27
+ ];
28
+
29
+ function createInitialState(): EmployeesListState {
30
+ return { selectedIndex: 0, running: true };
31
+ }
32
+
33
+ function shouldRunSnapshot() {
34
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
35
+ }
36
+
37
+ function EmployeeRow({ employee, selected }: { employee: Employee; selected: boolean }) {
38
+ return <Text>{`${selected ? ">" : " "} ${employee.name} — ${employee.role}`}</Text>;
39
+ }
40
+
41
+ function Employees({ items, selectedIndex }: { items: Employee[]; selectedIndex: number }) {
42
+ return (
43
+ <>
44
+ {items.map((employee, index) => (
45
+ <EmployeeRow employee={employee} selected={index === selectedIndex} />
46
+ ))}
47
+ </>
48
+ );
49
+ }
50
+
51
+ export function App({ state }: { state: EmployeesListState }) {
52
+ return (
53
+ <Screen title="Team">
54
+ <Employees items={employees} selectedIndex={state.selectedIndex} />
55
+ <Text>J/K or arrows: choose Ctrl+C: quit</Text>
56
+ </Screen>
57
+ );
58
+ }
59
+
60
+ export function createEmployeesListDemo(options: TerminalMountOptions = {}): EmployeesListDemo {
61
+ const state = createInitialState();
62
+ let session: TerminalSession;
63
+
64
+ function quit() {
65
+ state.running = false;
66
+ session.destroy();
67
+ }
68
+
69
+ function move(delta: number) {
70
+ const nextIndex = state.selectedIndex + delta;
71
+ state.selectedIndex = Math.max(0, Math.min(employees.length - 1, nextIndex));
72
+ }
73
+
74
+ session = mountTerminal(<App state={state} />, {
75
+ ...options,
76
+ keymap: {
77
+ ...options.keymap,
78
+ bindings: [
79
+ ...(options.keymap?.bindings || []),
80
+ { key: "j", command: { id: "employee.next" }, scope: "global" },
81
+ { key: "J", command: { id: "employee.next" }, scope: "global" },
82
+ { key: "DOWN", command: { id: "employee.next" }, scope: "global" },
83
+ { key: "ARROWDOWN", command: { id: "employee.next" }, scope: "global" },
84
+ { key: "k", command: { id: "employee.prev" }, scope: "global" },
85
+ { key: "K", command: { id: "employee.prev" }, scope: "global" },
86
+ { key: "UP", command: { id: "employee.prev" }, scope: "global" },
87
+ { key: "ARROWUP", command: { id: "employee.prev" }, scope: "global" },
88
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
89
+ ],
90
+ onCommand(command, context) {
91
+ if (command.id === "employee.next") {
92
+ move(1);
93
+ return true;
94
+ }
95
+ if (command.id === "employee.prev") {
96
+ move(-1);
97
+ return true;
98
+ }
99
+ if (command.id === "quit") {
100
+ quit();
101
+ return true;
102
+ }
103
+ return options.keymap?.onCommand?.(command, context);
104
+ }
105
+ }
106
+ });
107
+
108
+ return {
109
+ session,
110
+ dispatchKey(key: string) {
111
+ return session.dispatchKey(key);
112
+ },
113
+ output() {
114
+ return session.output();
115
+ },
116
+ ansiOutput() {
117
+ return session.ansiOutput();
118
+ },
119
+ isRunning() {
120
+ return state.running;
121
+ },
122
+ destroy() {
123
+ state.running = false;
124
+ session.destroy();
125
+ }
126
+ };
127
+ }
128
+
129
+ if (import.meta.main) {
130
+ if (shouldRunSnapshot()) {
131
+ const demo = createEmployeesListDemo({ runtime: "headless", cols: 44, rows: 8 });
132
+ process.stdout.write(demo.output());
133
+ process.stdout.write("\n");
134
+ demo.destroy();
135
+ } else {
136
+ createEmployeesListDemo();
137
+ }
138
+ }
@@ -0,0 +1,98 @@
1
+ import { Screen, Text, mountTerminal } from "@valyrianjs/terminal";
2
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
3
+
4
+ interface HelloState {
5
+ expanded: boolean;
6
+ running: boolean;
7
+ }
8
+
9
+ export interface HelloDemo {
10
+ session: TerminalSession;
11
+ dispatchKey(key: string): string;
12
+ output(): string;
13
+ ansiOutput(): string;
14
+ isRunning(): boolean;
15
+ destroy(): void;
16
+ }
17
+
18
+ function createInitialState(): HelloState {
19
+ return { expanded: false, running: true };
20
+ }
21
+
22
+ function shouldRunSnapshot() {
23
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
24
+ }
25
+
26
+ export function App({ state }: { state: HelloState }) {
27
+ return (
28
+ <Screen title="Hello terminal">
29
+ <Text>Hello from Valyrian Terminal</Text>
30
+ <Text>{state.expanded ? "Build terminal apps with plain components." : "Press Enter to see one detail."}</Text>
31
+ <Text>Enter: details Ctrl+C: quit</Text>
32
+ </Screen>
33
+ );
34
+ }
35
+
36
+ export function createHelloDemo(options: TerminalMountOptions = {}): HelloDemo {
37
+ const state = createInitialState();
38
+ let session: TerminalSession;
39
+
40
+ function quit() {
41
+ state.running = false;
42
+ session.destroy();
43
+ }
44
+
45
+ session = mountTerminal(<App state={state} />, {
46
+ ...options,
47
+ keymap: {
48
+ ...options.keymap,
49
+ bindings: [
50
+ ...(options.keymap?.bindings || []),
51
+ { key: "ENTER", command: { id: "details.toggle" }, scope: "global" },
52
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
53
+ ],
54
+ onCommand(command, context) {
55
+ if (command.id === "details.toggle") {
56
+ state.expanded = !state.expanded;
57
+ return true;
58
+ }
59
+ if (command.id === "quit") {
60
+ quit();
61
+ return true;
62
+ }
63
+ return options.keymap?.onCommand?.(command, context);
64
+ }
65
+ }
66
+ });
67
+
68
+ return {
69
+ session,
70
+ dispatchKey(key: string) {
71
+ return session.dispatchKey(key);
72
+ },
73
+ output() {
74
+ return session.output();
75
+ },
76
+ ansiOutput() {
77
+ return session.ansiOutput();
78
+ },
79
+ isRunning() {
80
+ return state.running;
81
+ },
82
+ destroy() {
83
+ state.running = false;
84
+ session.destroy();
85
+ }
86
+ };
87
+ }
88
+
89
+ if (import.meta.main) {
90
+ if (shouldRunSnapshot()) {
91
+ const demo = createHelloDemo({ runtime: "headless", cols: 42, rows: 6 });
92
+ process.stdout.write(demo.output());
93
+ process.stdout.write("\n");
94
+ demo.destroy();
95
+ } else {
96
+ createHelloDemo();
97
+ }
98
+ }
@@ -0,0 +1,111 @@
1
+ import { Input, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
2
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
3
+
4
+ interface InteractiveNoteState {
5
+ value: string;
6
+ saved: string;
7
+ running: boolean;
8
+ }
9
+
10
+ export interface InteractiveNoteDemo {
11
+ session: TerminalSession;
12
+ dispatchKey(key: string): string;
13
+ output(): string;
14
+ ansiOutput(): string;
15
+ isRunning(): boolean;
16
+ destroy(): void;
17
+ }
18
+
19
+ function createInitialState(): InteractiveNoteState {
20
+ return { value: "", saved: "", running: true };
21
+ }
22
+
23
+ function shouldRunSnapshot() {
24
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
25
+ }
26
+
27
+ export function App({ state }: { state: InteractiveNoteState }) {
28
+ const draftStatus = state.value.length === 0 ? "Draft: empty" : `Draft: ${state.value.length} chars`;
29
+
30
+ return (
31
+ <Screen title="Notes">
32
+ <Text>Notes</Text>
33
+ <Text>{draftStatus}</Text>
34
+ <Input
35
+ id="note"
36
+ value={state.value}
37
+ placeholder="Write a note"
38
+ onchange={(event) => {
39
+ state.value = event.value;
40
+ }}
41
+ onsubmit={(event) => {
42
+ state.saved = event.value;
43
+ state.value = "";
44
+ }}
45
+ />
46
+ <Text>{state.saved ? `Saved: ${state.saved}` : "Last saved: none"}</Text>
47
+ <Text>Enter save | Ctrl+C: quit</Text>
48
+ </Screen>
49
+ );
50
+ }
51
+
52
+ export function createInteractiveNoteDemo(options: TerminalMountOptions = {}): InteractiveNoteDemo {
53
+ const state = createInitialState();
54
+ let session: TerminalSession;
55
+
56
+ function quit() {
57
+ state.running = false;
58
+ session.destroy();
59
+ }
60
+
61
+ session = mountTerminal(<App state={state} />, {
62
+ ...options,
63
+ keymap: {
64
+ ...options.keymap,
65
+ bindings: [
66
+ ...(options.keymap?.bindings || []),
67
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
68
+ ],
69
+ onCommand(command, context) {
70
+ if (command.id === "quit") {
71
+ quit();
72
+ return true;
73
+ }
74
+ return options.keymap?.onCommand?.(command, context);
75
+ }
76
+ }
77
+ });
78
+
79
+ session.focus("note");
80
+
81
+ return {
82
+ session,
83
+ dispatchKey(key: string) {
84
+ return session.dispatchKey(key);
85
+ },
86
+ output() {
87
+ return session.output();
88
+ },
89
+ ansiOutput() {
90
+ return session.ansiOutput();
91
+ },
92
+ isRunning() {
93
+ return state.running;
94
+ },
95
+ destroy() {
96
+ state.running = false;
97
+ session.destroy();
98
+ }
99
+ };
100
+ }
101
+
102
+ if (import.meta.main) {
103
+ if (shouldRunSnapshot()) {
104
+ const demo = createInteractiveNoteDemo({ runtime: "headless", cols: 48, rows: 8 });
105
+ process.stdout.write(demo.output());
106
+ process.stdout.write("\n");
107
+ demo.destroy();
108
+ } else {
109
+ createInteractiveNoteDemo();
110
+ }
111
+ }