@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,175 @@
1
+ import { Money, formatMoney, parseMoneyInput } from "valyrian.js/money";
2
+ import { Pane, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
3
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
4
+
5
+ interface ModuleDemo {
6
+ session: TerminalSession;
7
+ dispatchKey(key: string): string;
8
+ output(): string;
9
+ ansiOutput(): string;
10
+ isRunning(): boolean;
11
+ destroy(): void;
12
+ }
13
+
14
+ type InvoiceLine = { name: string; unit: Money; quantity: number };
15
+ type InvoiceState = { selected: number; discount: boolean; localeIndex: number; lines: InvoiceLine[] };
16
+
17
+ const LOCALES = [
18
+ { label: "US / USD", locale: "en-US", currency: "USD" },
19
+ { label: "MX / USD", locale: "es-MX", currency: "USD" }
20
+ ] as const;
21
+ const INITIAL_LINES: InvoiceLine[] = [
22
+ { name: "Support seats", unit: parseMoneyInput("49.00", { locale: "en-US" }), quantity: 3 },
23
+ { name: "Priority add-on", unit: Money.fromDecimal(120), quantity: 1 },
24
+ { name: "Storage pack", unit: Money.fromCents(2500), quantity: 2 }
25
+ ];
26
+ const TAX_RATE = 0.16;
27
+ const DISCOUNT_RATE = 0.1;
28
+ const PANEL_STYLE = { color: "#f8fafc", background: "#111827", padding: { left: 1, right: 1 } };
29
+ const FOOTER_STYLE = { color: "#dbeafe", background: "#1e293b" };
30
+
31
+ function shouldRunSnapshot() {
32
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
33
+ }
34
+
35
+ function cloneLines() {
36
+ return INITIAL_LINES.map((line) => ({ ...line }));
37
+ }
38
+
39
+ function moneyView(value: Money, state: InvoiceState) {
40
+ const locale = LOCALES[state.localeIndex];
41
+ return formatMoney(value, { currency: locale.currency, locale: locale.locale, digits: 2 });
42
+ }
43
+
44
+ function invoiceTotals(state: InvoiceState) {
45
+ const subtotal = state.lines.reduce((total, line) => total.add(line.unit.multiply(line.quantity)), Money.fromCents(0));
46
+ const discount = state.discount ? subtotal.multiply(DISCOUNT_RATE) : Money.fromCents(0);
47
+ const taxable = subtotal.subtract(discount);
48
+ const tax = taxable.multiply(TAX_RATE);
49
+ const total = taxable.add(tax);
50
+ return { subtotal, discount, tax, total };
51
+ }
52
+
53
+ export function App({ state }: { state: InvoiceState }) {
54
+ const totals = invoiceTotals(state);
55
+ const locale = LOCALES[state.localeIndex];
56
+
57
+ return (
58
+ <Screen title="Invoice Calculator">
59
+ <Text style={{ color: "#ffffff", background: "#020617" }}>Invoice Calculator</Text>
60
+ <Pane style={PANEL_STYLE}>
61
+ <Text>Invoice Calculator</Text>
62
+ <Text>{`Locale: ${locale.label}`}</Text>
63
+ {state.lines.map((line, index) => (
64
+ <Text>{`${index === state.selected ? ">" : " "} ${line.name} x${line.quantity} — ${moneyView(line.unit.multiply(line.quantity), state)}`}</Text>
65
+ ))}
66
+ <Text>{`Subtotal: ${moneyView(totals.subtotal, state)}`}</Text>
67
+ <Text>{`Discount: ${state.discount ? moneyView(totals.discount, state) : "none"}`}</Text>
68
+ <Text>{`Tax: ${moneyView(totals.tax, state)}`}</Text>
69
+ <Text>{`Total: ${moneyView(totals.total, state)}`}</Text>
70
+ </Pane>
71
+ <Text style={FOOTER_STYLE}>J/K line +/- quantity D discount L locale Ctrl+C: quit</Text>
72
+ </Screen>
73
+ );
74
+ }
75
+
76
+ export function createModuleMoneyDemo(options: TerminalMountOptions = {}): ModuleDemo {
77
+ const state: InvoiceState = { selected: 0, discount: false, localeIndex: 0, lines: cloneLines() };
78
+ let running = true;
79
+ let session: TerminalSession;
80
+
81
+ function quit() {
82
+ running = false;
83
+ session.destroy();
84
+ }
85
+
86
+ function changeQuantity(delta: number) {
87
+ const line = state.lines[state.selected];
88
+ line.quantity = Math.max(0, line.quantity + delta);
89
+ }
90
+
91
+ session = mountTerminal(<App state={state} />, {
92
+ ...options,
93
+ cols: options.cols ?? 92,
94
+ rows: options.rows ?? 20,
95
+ keymap: {
96
+ ...options.keymap,
97
+ bindings: [
98
+ ...(options.keymap?.bindings || []),
99
+ { key: "j", command: { id: "invoice.next" }, scope: "global" },
100
+ { key: "J", command: { id: "invoice.next" }, scope: "global" },
101
+ { key: "k", command: { id: "invoice.previous" }, scope: "global" },
102
+ { key: "K", command: { id: "invoice.previous" }, scope: "global" },
103
+ { key: "+", command: { id: "invoice.increase" }, scope: "global" },
104
+ { key: "-", command: { id: "invoice.decrease" }, scope: "global" },
105
+ { key: "d", command: { id: "invoice.discount" }, scope: "global" },
106
+ { key: "D", command: { id: "invoice.discount" }, scope: "global" },
107
+ { key: "l", command: { id: "invoice.locale" }, scope: "global" },
108
+ { key: "L", command: { id: "invoice.locale" }, scope: "global" },
109
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
110
+ ],
111
+ onCommand(command, context) {
112
+ if (command.id === "invoice.next") {
113
+ state.selected = (state.selected + 1) % state.lines.length;
114
+ return true;
115
+ }
116
+ if (command.id === "invoice.previous") {
117
+ state.selected = (state.selected - 1 + state.lines.length) % state.lines.length;
118
+ return true;
119
+ }
120
+ if (command.id === "invoice.increase") {
121
+ changeQuantity(1);
122
+ return true;
123
+ }
124
+ if (command.id === "invoice.decrease") {
125
+ changeQuantity(-1);
126
+ return true;
127
+ }
128
+ if (command.id === "invoice.discount") {
129
+ state.discount = !state.discount;
130
+ return true;
131
+ }
132
+ if (command.id === "invoice.locale") {
133
+ state.localeIndex = (state.localeIndex + 1) % LOCALES.length;
134
+ return true;
135
+ }
136
+ if (command.id === "quit") {
137
+ quit();
138
+ return true;
139
+ }
140
+ return options.keymap?.onCommand?.(command, context);
141
+ }
142
+ }
143
+ });
144
+
145
+ return {
146
+ session,
147
+ dispatchKey(key: string) {
148
+ return session.dispatchKey(key);
149
+ },
150
+ output() {
151
+ return session.output();
152
+ },
153
+ ansiOutput() {
154
+ return session.ansiOutput();
155
+ },
156
+ isRunning() {
157
+ return running;
158
+ },
159
+ destroy() {
160
+ running = false;
161
+ session.destroy();
162
+ }
163
+ };
164
+ }
165
+
166
+ if (import.meta.main) {
167
+ if (shouldRunSnapshot()) {
168
+ const demo = createModuleMoneyDemo({ runtime: "headless", cols: 92, rows: 20 });
169
+ process.stdout.write(demo.output());
170
+ process.stdout.write("\n");
171
+ demo.destroy();
172
+ } else {
173
+ createModuleMoneyDemo();
174
+ }
175
+ }
@@ -0,0 +1,188 @@
1
+ import { createNativeStore, StorageType } from "valyrian.js/native-store";
2
+ import { Input, Pane, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
3
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
4
+
5
+ interface ModuleDemo {
6
+ session: TerminalSession;
7
+ dispatchKey(key: string): string;
8
+ output(): string;
9
+ ansiOutput(): string;
10
+ isRunning(): boolean;
11
+ destroy(): void;
12
+ }
13
+
14
+ type NativeStoreDemoOptions = TerminalMountOptions & {
15
+ storeKey?: string;
16
+ resetOnStart?: boolean;
17
+ };
18
+
19
+ interface WorkspaceSettingsState {
20
+ draftWorkspace: string;
21
+ restoredWorkspace: string;
22
+ savedWorkspace: string;
23
+ status: string;
24
+ }
25
+
26
+ const DEFAULT_WORKSPACE = "Northwind CLI";
27
+ const DEFAULT_STORE_KEY = "valyrian-terminal-docs.workspace-settings";
28
+ const PANEL_STYLE = { color: "#f8fafc", background: "#111827", padding: { left: 1, right: 1 } };
29
+ const FIELD_STYLE = { color: "#ffffff", background: "#0f3b3e", padding: { left: 1, right: 1 } };
30
+ const FOOTER_STYLE = { color: "#dbeafe", background: "#1e293b" };
31
+
32
+ function shouldRunSnapshot() {
33
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
34
+ }
35
+
36
+
37
+ function normalizeWorkspace(value: unknown) {
38
+ const workspace = String(value ?? "").trim();
39
+ return workspace.length > 0 ? workspace : DEFAULT_WORKSPACE;
40
+ }
41
+
42
+ export function App({ state, onSave }: { state: WorkspaceSettingsState; onSave: () => void }) {
43
+ return (
44
+ <Screen title="Persistent Workspace Settings">
45
+ <Text style={{ color: "#ffffff", background: "#020617" }}>Persistent Workspace Settings</Text>
46
+ <Pane style={FIELD_STYLE}>
47
+ <Text>Workspace name</Text>
48
+ <Input
49
+ id="workspace-name"
50
+ value={state.draftWorkspace}
51
+ placeholder="Workspace name"
52
+ onchange={(event) => {
53
+ state.draftWorkspace = event.value;
54
+ state.status = "editing workspace setting";
55
+ }}
56
+ onsubmit={onSave}
57
+ />
58
+ <Text>{`Draft workspace: ${state.draftWorkspace || "blank"}`}</Text>
59
+ </Pane>
60
+ <Pane style={PANEL_STYLE}>
61
+ <Text>{`Restored workspace: ${state.restoredWorkspace}`}</Text>
62
+ <Text>{`Saved workspace: ${state.savedWorkspace}`}</Text>
63
+ <Text>{`Settings status: ${state.status}`}</Text>
64
+ <Text>Local native-store keeps workspace settings outside the renderer.</Text>
65
+ </Pane>
66
+ <Text style={FOOTER_STYLE}>Type workspace | Enter: save setting | R: reset setting | Ctrl+C: quit</Text>
67
+ </Screen>
68
+ );
69
+ }
70
+
71
+ export function createModuleNativeStoreDemo(options: NativeStoreDemoOptions = {}): ModuleDemo {
72
+ const settings = createNativeStore<{ workspace: string }>(options.storeKey ?? DEFAULT_STORE_KEY, {}, StorageType.Session, true);
73
+ if (options.resetOnStart) {
74
+ settings.clear();
75
+ }
76
+
77
+ function loadWorkspace() {
78
+ const restored = normalizeWorkspace(settings.get("workspace"));
79
+ if (settings.get("workspace") !== restored) settings.set("workspace", restored);
80
+ return restored;
81
+ }
82
+
83
+ const restoredWorkspace = loadWorkspace();
84
+ const state: WorkspaceSettingsState = {
85
+ draftWorkspace: restoredWorkspace,
86
+ restoredWorkspace,
87
+ savedWorkspace: restoredWorkspace,
88
+ status: "loaded saved setting"
89
+ };
90
+ let running = true;
91
+ let session: TerminalSession;
92
+
93
+ function saveWorkspace() {
94
+ const workspace = normalizeWorkspace(state.draftWorkspace);
95
+ settings.set("workspace", workspace);
96
+ state.draftWorkspace = workspace;
97
+ state.savedWorkspace = workspace;
98
+ state.restoredWorkspace = workspace;
99
+ state.status = "saved setting";
100
+ }
101
+
102
+ function reloadWorkspace() {
103
+ settings.load();
104
+ const workspace = loadWorkspace();
105
+ state.draftWorkspace = workspace;
106
+ state.savedWorkspace = workspace;
107
+ state.restoredWorkspace = workspace;
108
+ state.status = "loaded saved setting";
109
+ }
110
+
111
+ function resetWorkspace() {
112
+ settings.clear();
113
+ settings.set("workspace", DEFAULT_WORKSPACE);
114
+ state.draftWorkspace = "";
115
+ state.savedWorkspace = DEFAULT_WORKSPACE;
116
+ state.restoredWorkspace = DEFAULT_WORKSPACE;
117
+ state.status = "reset to default";
118
+ }
119
+
120
+ function quit() {
121
+ running = false;
122
+ settings.cleanup();
123
+ session.destroy();
124
+ }
125
+
126
+ session = mountTerminal(<App state={state} onSave={saveWorkspace} />, {
127
+ ...options,
128
+ cols: options.cols ?? 92,
129
+ rows: options.rows ?? 18,
130
+ keymap: {
131
+ ...options.keymap,
132
+ bindings: [
133
+ ...(options.keymap?.bindings || []),
134
+ { key: "R", command: { id: "store.reset" }, scope: "global" },
135
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
136
+ ],
137
+ onCommand(command, context) {
138
+ if (command.id === "store.reset") {
139
+ resetWorkspace();
140
+ return true;
141
+ }
142
+ if (command.id === "quit") {
143
+ quit();
144
+ return true;
145
+ }
146
+ return options.keymap?.onCommand?.(command, context);
147
+ }
148
+ }
149
+ });
150
+
151
+ session.focus("workspace-name");
152
+
153
+ return {
154
+ session,
155
+ dispatchKey(key: string) {
156
+ return session.dispatchKey(key);
157
+ },
158
+ output() {
159
+ return session.output();
160
+ },
161
+ ansiOutput() {
162
+ return session.ansiOutput();
163
+ },
164
+ isRunning() {
165
+ return running;
166
+ },
167
+ destroy() {
168
+ running = false;
169
+ settings.cleanup();
170
+ session.destroy();
171
+ }
172
+ };
173
+ }
174
+
175
+ if (import.meta.main) {
176
+ if (shouldRunSnapshot()) {
177
+ const demo = createModuleNativeStoreDemo({ runtime: "headless", cols: 92, rows: 18, resetOnStart: true });
178
+ demo.dispatchKey("R");
179
+ demo.session.focus("workspace-name");
180
+ for (const key of "Support CLI") demo.dispatchKey(key);
181
+ demo.dispatchKey("ENTER");
182
+ process.stdout.write(demo.output());
183
+ process.stdout.write("\n");
184
+ demo.destroy();
185
+ } else {
186
+ createModuleNativeStoreDemo();
187
+ }
188
+ }
@@ -0,0 +1,142 @@
1
+ import { createPulse } from "valyrian.js/pulses";
2
+ import { Pane, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
3
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
4
+
5
+ interface ModuleDemo {
6
+ session: TerminalSession;
7
+ dispatchKey(key: string): string;
8
+ output(): string;
9
+ ansiOutput(): string;
10
+ isRunning(): boolean;
11
+ destroy(): void;
12
+ }
13
+
14
+ type ShiftPulses = {
15
+ open: () => number;
16
+ resolved: () => number;
17
+ priority: () => boolean;
18
+ };
19
+
20
+ const INITIAL_OPEN = 4;
21
+ const INITIAL_RESOLVED = 1;
22
+ const PANEL_STYLE = { color: "#f8fafc", background: "#111827", padding: { left: 1, right: 1 } };
23
+ const FOOTER_STYLE = { color: "#dbeafe", background: "#1e293b" };
24
+
25
+ function shouldRunSnapshot() {
26
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
27
+ }
28
+
29
+ export function App({ pulses }: { pulses: ShiftPulses }) {
30
+ const open = pulses.open();
31
+ const resolved = pulses.resolved();
32
+ const priority = pulses.priority();
33
+
34
+ return (
35
+ <Screen title="Live Shift/Ticket Counter">
36
+ <Text style={{ color: "#ffffff", background: "#020617" }}>Live Shift/Ticket Counter</Text>
37
+ <Pane style={PANEL_STYLE}>
38
+ <Text>Shift/Ticket Counter</Text>
39
+ <Text>{`Open tickets: ${open}`}</Text>
40
+ <Text>{`Resolved this shift: ${resolved}`}</Text>
41
+ <Text>{`Priority lane: ${priority ? "active" : "normal"}`}</Text>
42
+ <Text>{`Workload: ${open > 5 || priority ? "watch closely" : "steady"}`}</Text>
43
+ </Pane>
44
+ <Text style={FOOTER_STYLE}>A add ticket D resolve ticket P priority lane R reset Ctrl+C: quit</Text>
45
+ </Screen>
46
+ );
47
+ }
48
+
49
+ export function createModulePulsesDemo(options: TerminalMountOptions = {}): ModuleDemo {
50
+ const [openTickets, setOpenTickets] = createPulse(INITIAL_OPEN);
51
+ const [resolvedTickets, setResolvedTickets] = createPulse(INITIAL_RESOLVED);
52
+ const [priorityActive, setPriorityActive] = createPulse(false);
53
+ let running = true;
54
+ let session: TerminalSession;
55
+
56
+ function quit() {
57
+ running = false;
58
+ session.destroy();
59
+ }
60
+
61
+ function reset() {
62
+ setOpenTickets(INITIAL_OPEN);
63
+ setResolvedTickets(INITIAL_RESOLVED);
64
+ setPriorityActive(false);
65
+ }
66
+
67
+ session = mountTerminal(<App pulses={{ open: openTickets, resolved: resolvedTickets, priority: priorityActive }} />, {
68
+ ...options,
69
+ cols: options.cols ?? 92,
70
+ rows: options.rows ?? 16,
71
+ keymap: {
72
+ ...options.keymap,
73
+ bindings: [
74
+ ...(options.keymap?.bindings || []),
75
+ { key: "a", command: { id: "tickets.add" }, scope: "global" },
76
+ { key: "A", command: { id: "tickets.add" }, scope: "global" },
77
+ { key: "d", command: { id: "tickets.resolve" }, scope: "global" },
78
+ { key: "D", command: { id: "tickets.resolve" }, scope: "global" },
79
+ { key: "p", command: { id: "tickets.priority" }, scope: "global" },
80
+ { key: "P", command: { id: "tickets.priority" }, scope: "global" },
81
+ { key: "r", command: { id: "tickets.reset" }, scope: "global" },
82
+ { key: "R", command: { id: "tickets.reset" }, scope: "global" },
83
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
84
+ ],
85
+ onCommand(command, context) {
86
+ if (command.id === "tickets.add") {
87
+ setOpenTickets((value) => value + 1);
88
+ return true;
89
+ }
90
+ if (command.id === "tickets.resolve") {
91
+ setOpenTickets((value) => Math.max(0, value - 1));
92
+ setResolvedTickets((value) => value + 1);
93
+ return true;
94
+ }
95
+ if (command.id === "tickets.priority") {
96
+ setPriorityActive((value) => !value);
97
+ return true;
98
+ }
99
+ if (command.id === "tickets.reset") {
100
+ reset();
101
+ return true;
102
+ }
103
+ if (command.id === "quit") {
104
+ quit();
105
+ return true;
106
+ }
107
+ return options.keymap?.onCommand?.(command, context);
108
+ }
109
+ }
110
+ });
111
+
112
+ return {
113
+ session,
114
+ dispatchKey(key: string) {
115
+ return session.dispatchKey(key);
116
+ },
117
+ output() {
118
+ return session.output();
119
+ },
120
+ ansiOutput() {
121
+ return session.ansiOutput();
122
+ },
123
+ isRunning() {
124
+ return running;
125
+ },
126
+ destroy() {
127
+ running = false;
128
+ session.destroy();
129
+ }
130
+ };
131
+ }
132
+
133
+ if (import.meta.main) {
134
+ if (shouldRunSnapshot()) {
135
+ const demo = createModulePulsesDemo({ runtime: "headless", cols: 92, rows: 16 });
136
+ process.stdout.write(demo.output());
137
+ process.stdout.write("\n");
138
+ demo.destroy();
139
+ } else {
140
+ createModulePulsesDemo();
141
+ }
142
+ }