@glbgpt/cli 0.1.0-test.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.
Files changed (173) hide show
  1. package/README.md +111 -0
  2. package/dist/src/auth/authClient.d.ts +32 -0
  3. package/dist/src/auth/authClient.js +88 -0
  4. package/dist/src/auth/authClient.js.map +1 -0
  5. package/dist/src/auth/credentialStore.d.ts +22 -0
  6. package/dist/src/auth/credentialStore.js +51 -0
  7. package/dist/src/auth/credentialStore.js.map +1 -0
  8. package/dist/src/auth/loginFlow.d.ts +30 -0
  9. package/dist/src/auth/loginFlow.js +43 -0
  10. package/dist/src/auth/loginFlow.js.map +1 -0
  11. package/dist/src/auth/loopbackServer.d.ts +41 -0
  12. package/dist/src/auth/loopbackServer.js +141 -0
  13. package/dist/src/auth/loopbackServer.js.map +1 -0
  14. package/dist/src/auth/token.d.ts +8 -0
  15. package/dist/src/auth/token.js +15 -0
  16. package/dist/src/auth/token.js.map +1 -0
  17. package/dist/src/commands/accountCommand.d.ts +2 -0
  18. package/dist/src/commands/accountCommand.js +56 -0
  19. package/dist/src/commands/accountCommand.js.map +1 -0
  20. package/dist/src/commands/configCommand.d.ts +4 -0
  21. package/dist/src/commands/configCommand.js +53 -0
  22. package/dist/src/commands/configCommand.js.map +1 -0
  23. package/dist/src/commands/execCommand.d.ts +32 -0
  24. package/dist/src/commands/execCommand.js +122 -0
  25. package/dist/src/commands/execCommand.js.map +1 -0
  26. package/dist/src/commands/loginCommand.d.ts +11 -0
  27. package/dist/src/commands/loginCommand.js +96 -0
  28. package/dist/src/commands/loginCommand.js.map +1 -0
  29. package/dist/src/commands/mediaCommand.d.ts +41 -0
  30. package/dist/src/commands/mediaCommand.js +277 -0
  31. package/dist/src/commands/mediaCommand.js.map +1 -0
  32. package/dist/src/config/configFile.d.ts +30 -0
  33. package/dist/src/config/configFile.js +54 -0
  34. package/dist/src/config/configFile.js.map +1 -0
  35. package/dist/src/config/env.d.ts +6 -0
  36. package/dist/src/config/env.js +24 -0
  37. package/dist/src/config/env.js.map +1 -0
  38. package/dist/src/config/paths.d.ts +6 -0
  39. package/dist/src/config/paths.js +16 -0
  40. package/dist/src/config/paths.js.map +1 -0
  41. package/dist/src/core/catalog.d.ts +49 -0
  42. package/dist/src/core/catalog.js +96 -0
  43. package/dist/src/core/catalog.js.map +1 -0
  44. package/dist/src/core/httpChatBackend.d.ts +31 -0
  45. package/dist/src/core/httpChatBackend.js +134 -0
  46. package/dist/src/core/httpChatBackend.js.map +1 -0
  47. package/dist/src/core/id.d.ts +2 -0
  48. package/dist/src/core/id.js +5 -0
  49. package/dist/src/core/id.js.map +1 -0
  50. package/dist/src/core/runChat.d.ts +28 -0
  51. package/dist/src/core/runChat.js +49 -0
  52. package/dist/src/core/runChat.js.map +1 -0
  53. package/dist/src/core/session.d.ts +44 -0
  54. package/dist/src/core/session.js +70 -0
  55. package/dist/src/core/session.js.map +1 -0
  56. package/dist/src/core/sessionStore.d.ts +39 -0
  57. package/dist/src/core/sessionStore.js +106 -0
  58. package/dist/src/core/sessionStore.js.map +1 -0
  59. package/dist/src/core/toolRegistry.d.ts +19 -0
  60. package/dist/src/core/toolRegistry.js +53 -0
  61. package/dist/src/core/toolRegistry.js.map +1 -0
  62. package/dist/src/core/tools/generateImage.d.ts +10 -0
  63. package/dist/src/core/tools/generateImage.js +34 -0
  64. package/dist/src/core/tools/generateImage.js.map +1 -0
  65. package/dist/src/core/turnLoop.d.ts +31 -0
  66. package/dist/src/core/turnLoop.js +155 -0
  67. package/dist/src/core/turnLoop.js.map +1 -0
  68. package/dist/src/core/types.d.ts +137 -0
  69. package/dist/src/core/types.js +9 -0
  70. package/dist/src/core/types.js.map +1 -0
  71. package/dist/src/exec/input.d.ts +35 -0
  72. package/dist/src/exec/input.js +85 -0
  73. package/dist/src/exec/input.js.map +1 -0
  74. package/dist/src/exec/output.d.ts +39 -0
  75. package/dist/src/exec/output.js +84 -0
  76. package/dist/src/exec/output.js.map +1 -0
  77. package/dist/src/index.d.ts +2 -0
  78. package/dist/src/index.js +253 -0
  79. package/dist/src/index.js.map +1 -0
  80. package/dist/src/mcp/server.d.ts +38 -0
  81. package/dist/src/mcp/server.js +99 -0
  82. package/dist/src/mcp/server.js.map +1 -0
  83. package/dist/src/media/aiApiClient.d.ts +53 -0
  84. package/dist/src/media/aiApiClient.js +97 -0
  85. package/dist/src/media/aiApiClient.js.map +1 -0
  86. package/dist/src/media/errors.d.ts +7 -0
  87. package/dist/src/media/errors.js +70 -0
  88. package/dist/src/media/errors.js.map +1 -0
  89. package/dist/src/media/executor.d.ts +50 -0
  90. package/dist/src/media/executor.js +55 -0
  91. package/dist/src/media/executor.js.map +1 -0
  92. package/dist/src/media/generateImageTool.d.ts +19 -0
  93. package/dist/src/media/generateImageTool.js +120 -0
  94. package/dist/src/media/generateImageTool.js.map +1 -0
  95. package/dist/src/media/manifest.d.ts +23 -0
  96. package/dist/src/media/manifest.js +558 -0
  97. package/dist/src/media/manifest.js.map +1 -0
  98. package/dist/src/media/resolution.d.ts +13 -0
  99. package/dist/src/media/resolution.js +42 -0
  100. package/dist/src/media/resolution.js.map +1 -0
  101. package/dist/src/media/taskLedger.d.ts +17 -0
  102. package/dist/src/media/taskLedger.js +44 -0
  103. package/dist/src/media/taskLedger.js.map +1 -0
  104. package/dist/src/media/taskManager.d.ts +52 -0
  105. package/dist/src/media/taskManager.js +130 -0
  106. package/dist/src/media/taskManager.js.map +1 -0
  107. package/dist/src/media/types.d.ts +80 -0
  108. package/dist/src/media/types.js +15 -0
  109. package/dist/src/media/types.js.map +1 -0
  110. package/dist/src/tui/App.d.ts +34 -0
  111. package/dist/src/tui/App.js +540 -0
  112. package/dist/src/tui/App.js.map +1 -0
  113. package/dist/src/tui/MultilineInput.d.ts +36 -0
  114. package/dist/src/tui/MultilineInput.js +221 -0
  115. package/dist/src/tui/MultilineInput.js.map +1 -0
  116. package/dist/src/tui/atPopup.d.ts +21 -0
  117. package/dist/src/tui/atPopup.js +55 -0
  118. package/dist/src/tui/atPopup.js.map +1 -0
  119. package/dist/src/tui/attachments.d.ts +24 -0
  120. package/dist/src/tui/attachments.js +91 -0
  121. package/dist/src/tui/attachments.js.map +1 -0
  122. package/dist/src/tui/branding.d.ts +22 -0
  123. package/dist/src/tui/branding.js +119 -0
  124. package/dist/src/tui/branding.js.map +1 -0
  125. package/dist/src/tui/chatController.d.ts +188 -0
  126. package/dist/src/tui/chatController.js +614 -0
  127. package/dist/src/tui/chatController.js.map +1 -0
  128. package/dist/src/tui/commands.d.ts +13 -0
  129. package/dist/src/tui/commands.js +22 -0
  130. package/dist/src/tui/commands.js.map +1 -0
  131. package/dist/src/tui/confirmationBridge.d.ts +23 -0
  132. package/dist/src/tui/confirmationBridge.js +47 -0
  133. package/dist/src/tui/confirmationBridge.js.map +1 -0
  134. package/dist/src/tui/inputHistory.d.ts +7 -0
  135. package/dist/src/tui/inputHistory.js +64 -0
  136. package/dist/src/tui/inputHistory.js.map +1 -0
  137. package/dist/src/tui/markdown.d.ts +1 -0
  138. package/dist/src/tui/markdown.js +143 -0
  139. package/dist/src/tui/markdown.js.map +1 -0
  140. package/dist/src/tui/models.d.ts +30 -0
  141. package/dist/src/tui/models.js +54 -0
  142. package/dist/src/tui/models.js.map +1 -0
  143. package/dist/src/tui/run.d.ts +29 -0
  144. package/dist/src/tui/run.js +212 -0
  145. package/dist/src/tui/run.js.map +1 -0
  146. package/dist/src/tui/slashMedia.d.ts +36 -0
  147. package/dist/src/tui/slashMedia.js +86 -0
  148. package/dist/src/tui/slashMedia.js.map +1 -0
  149. package/dist/src/tui/visualLines.d.ts +38 -0
  150. package/dist/src/tui/visualLines.js +110 -0
  151. package/dist/src/tui/visualLines.js.map +1 -0
  152. package/dist/src/util/clipboardImage.d.ts +6 -0
  153. package/dist/src/util/clipboardImage.js +69 -0
  154. package/dist/src/util/clipboardImage.js.map +1 -0
  155. package/dist/src/util/hyperlink.d.ts +10 -0
  156. package/dist/src/util/hyperlink.js +33 -0
  157. package/dist/src/util/hyperlink.js.map +1 -0
  158. package/dist/src/util/imageRef.d.ts +15 -0
  159. package/dist/src/util/imageRef.js +46 -0
  160. package/dist/src/util/imageRef.js.map +1 -0
  161. package/dist/src/util/inlineImage.d.ts +13 -0
  162. package/dist/src/util/inlineImage.js +56 -0
  163. package/dist/src/util/inlineImage.js.map +1 -0
  164. package/dist/src/util/openBrowser.d.ts +5 -0
  165. package/dist/src/util/openBrowser.js +23 -0
  166. package/dist/src/util/openBrowser.js.map +1 -0
  167. package/dist/src/util/retry.d.ts +43 -0
  168. package/dist/src/util/retry.js +116 -0
  169. package/dist/src/util/retry.js.map +1 -0
  170. package/dist/src/version.d.ts +6 -0
  171. package/dist/src/version.js +16 -0
  172. package/dist/src/version.js.map +1 -0
  173. package/package.json +45 -0
@@ -0,0 +1,221 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { visualLines, rowIndexFor, columnInRow, offsetAtColumn } from './visualLines.js';
5
+ /**
6
+ * Multi-line composer input (replaces the single-line ink-text-input).
7
+ *
8
+ * Newline conventions from the references: a trailing `\` + Enter (Claude Code's
9
+ * documented binding — works in every terminal) or Ctrl+J (Codex/readline). Enter
10
+ * submits. Pasted chunks containing newlines insert literally instead of submitting
11
+ * per line. Characters with ctrl/meta modifiers are never inserted (the root fix for
12
+ * ink-text-input's stray 'v' on Ctrl+V).
13
+ *
14
+ * ↑/↓ move the cursor between VISUAL rows (wrapped lines, display-width columns, with
15
+ * column memory — the Claude Code Cursor.ts / Codex textarea.rs model), and history is
16
+ * arbitrated INSIDE the editor (CC's upOrHistoryUp): only when the cursor cannot move
17
+ * any further does ↑/↓ escalate to onHistoryUp/onHistoryDown. The ladder:
18
+ * ↑ → move up a row → (top row) jump to start → (at start) recall history
19
+ * ↓ → symmetric, ending in onHistoryDown
20
+ * While `recallActive` (the parent is mid history-browse and the entry is unedited),
21
+ * boundary ↑/↓ keep browsing instead (Codex should_handle_navigation).
22
+ */
23
+ export function MultilineInput({ value, onChange, onSubmit, onHistoryUp, onHistoryDown, placeholder = '', focus = true, verticalNav = true, recallActive = false, columns, }) {
24
+ const [, forceRender] = useState(0);
25
+ // Refs are the live source of truth: multiple key events can arrive in ONE event-loop
26
+ // batch (fast typing, pastes, ctrl+j+text), and React state/closures would make each
27
+ // callback act on stale values, silently dropping edits.
28
+ const valueRef = useRef(value);
29
+ const cursorRef = useRef(value.length);
30
+ const lastEmitted = useRef(value);
31
+ // Column memory (Codex preferred_col): moving through a short row and back into a long
32
+ // one keeps the original column. Any non-vertical key resets it.
33
+ const preferredCol = useRef(null);
34
+ // Controlled component: when the PARENT replaces the value (history recall, Tab
35
+ // completion, Ctrl+V insertion, post-submit clear), jump the cursor to the end.
36
+ if (value !== lastEmitted.current) {
37
+ lastEmitted.current = value;
38
+ valueRef.current = value;
39
+ cursorRef.current = value.length;
40
+ preferredCol.current = null;
41
+ }
42
+ // Code-point boundaries: cursor ops must never split an emoji's surrogate pair — an
43
+ // unpaired surrogate makes the submitted string invalid JSON at the API layer. (The
44
+ // reference editor, claude-code-source Cursor.ts, goes further with grapheme segmentation;
45
+ // code-point level covers the data-corruption class.)
46
+ const isLow = (s, i) => {
47
+ const code = s.charCodeAt(i);
48
+ return code >= 0xdc00 && code <= 0xdfff;
49
+ };
50
+ const snapPair = (s, n) => (n > 0 && n < s.length && isLow(s, n) ? n - 1 : n);
51
+ const prevBoundary = (s, n) => (n <= 0 ? 0 : snapPair(s, n - 1) < n - 1 ? n - 2 : n - 1);
52
+ const nextBoundary = (s, n) => (n >= s.length ? s.length : n + (isLow(s, n + 1) ? 2 : 1));
53
+ // The cursor never lands INSIDE an "[Image #N]" placeholder — editing there would break
54
+ // the token so it can no longer expand to the pasted image (Claude Code Cursor.ts
55
+ // snapOutOfImageRef, same regex).
56
+ const snapPlaceholder = (s, n, toward) => {
57
+ const re = /\[Image #\d+\]/g;
58
+ let m;
59
+ while ((m = re.exec(s)) !== null) {
60
+ if (n > m.index && n < m.index + m[0].length)
61
+ return toward === 'start' ? m.index : m.index + m[0].length;
62
+ if (m.index >= n)
63
+ break;
64
+ }
65
+ return n;
66
+ };
67
+ const setC = (n, toward = 'start') => {
68
+ cursorRef.current = snapPlaceholder(valueRef.current, snapPair(valueRef.current, n), toward);
69
+ forceRender((x) => x + 1);
70
+ };
71
+ const emit = (next) => {
72
+ valueRef.current = next;
73
+ lastEmitted.current = next;
74
+ onChange(next);
75
+ };
76
+ const insertAt = (text) => {
77
+ const v = valueRef.current;
78
+ const c = Math.min(cursorRef.current, v.length);
79
+ emit(v.slice(0, c) + text + v.slice(c));
80
+ cursorRef.current = c + text.length;
81
+ };
82
+ // Paste-burst guard (Claude Code's usePasteHandler / Codex's paste_burst.rs): a paste can
83
+ // reach us as MULTIPLE stdin chunks, and a chunk boundary landing on a newline delivers a
84
+ // lone \r that ink parses as key.return. A return within a short window after a bulk
85
+ // chunk is part of the paste — insert a newline instead of submitting.
86
+ const lastBulkAt = useRef(0);
87
+ const noteBulk = (input) => {
88
+ // A lone linefeed is the Ctrl+J keystroke, not paste content.
89
+ if (input.length >= 16 || (input.includes('\n') && input.length > 1))
90
+ lastBulkAt.current = Date.now();
91
+ };
92
+ const inPasteBurst = () => Date.now() - lastBulkAt.current < 150;
93
+ useInput((input, key) => {
94
+ const v = valueRef.current;
95
+ const c = Math.min(cursorRef.current, v.length);
96
+ // Any editing key means the user is interacting — a paste delivers only text chunks —
97
+ // so it ends the burst window immediately.
98
+ if (key.backspace || key.delete || key.leftArrow || key.rightArrow || key.upArrow || key.downArrow || key.ctrl || key.meta || key.escape || key.tab) {
99
+ lastBulkAt.current = 0;
100
+ }
101
+ // Any key other than ↑/↓ drops the remembered column.
102
+ if (!key.upArrow && !key.downArrow)
103
+ preferredCol.current = null;
104
+ if (key.return) {
105
+ // Mid-paste \r is content, not a submit (see paste-burst guard above).
106
+ if (inPasteBurst()) {
107
+ lastBulkAt.current = Date.now(); // consecutive pasted lines extend the burst
108
+ insertAt('\n');
109
+ forceRender((x) => x + 1);
110
+ return;
111
+ }
112
+ // Backslash at END of the current line + Enter continues onto a new line (Claude
113
+ // Code convention). Cursor-relative only is wrong: Enter with the cursor just past
114
+ // a mid-line backslash ("run C:\\tools\\ now") must still submit.
115
+ if (c > 0 && v[c - 1] === '\\' && (c === v.length || v[c] === '\n')) {
116
+ emit(v.slice(0, c - 1) + '\n' + v.slice(c));
117
+ cursorRef.current = c;
118
+ return;
119
+ }
120
+ onSubmit(v);
121
+ cursorRef.current = 0;
122
+ return;
123
+ }
124
+ if (key.backspace || key.delete) {
125
+ if (c > 0) {
126
+ // A placeholder deletes atomically — a half-eaten "[Image #1" can never expand.
127
+ const ph = /\[Image #\d+\]$/.exec(v.slice(0, c));
128
+ const from = ph ? c - ph[0].length : prevBoundary(v, c);
129
+ emit(v.slice(0, from) + v.slice(c));
130
+ cursorRef.current = from;
131
+ }
132
+ return;
133
+ }
134
+ if (key.leftArrow) {
135
+ setC(prevBoundary(v, c), 'start');
136
+ return;
137
+ }
138
+ if (key.rightArrow) {
139
+ setC(nextBoundary(v, c), 'end');
140
+ return;
141
+ }
142
+ if (key.upArrow || key.downArrow) {
143
+ if (!verticalNav)
144
+ return; // a popup owns ↑/↓ right now
145
+ // Mid history-browse with the entry unedited: boundary ↑/↓ keep browsing
146
+ // (Codex should_handle_navigation — recall parks the cursor at the end).
147
+ if (recallActive && (c === 0 || c === v.length)) {
148
+ preferredCol.current = null;
149
+ if (key.upArrow)
150
+ onHistoryUp?.();
151
+ else
152
+ onHistoryDown?.();
153
+ return;
154
+ }
155
+ const rows = visualLines(v, columns);
156
+ const idx = rowIndexFor(rows, c);
157
+ if (key.upArrow) {
158
+ if (idx === 0) {
159
+ // Top row: first jump to the very start; already there → recall history.
160
+ preferredCol.current = null;
161
+ if (c > 0)
162
+ setC(0, 'start');
163
+ else
164
+ onHistoryUp?.();
165
+ return;
166
+ }
167
+ const col = preferredCol.current ?? columnInRow(rows[idx], c);
168
+ preferredCol.current = col;
169
+ setC(offsetAtColumn(rows[idx - 1], col), 'start');
170
+ return;
171
+ }
172
+ // downArrow
173
+ if (idx === rows.length - 1) {
174
+ // Bottom row: first jump to the very end; already there → history down.
175
+ preferredCol.current = null;
176
+ if (c < v.length)
177
+ setC(v.length, 'end');
178
+ else
179
+ onHistoryDown?.();
180
+ return;
181
+ }
182
+ const col = preferredCol.current ?? columnInRow(rows[idx], c);
183
+ preferredCol.current = col;
184
+ setC(offsetAtColumn(rows[idx + 1], col), 'start');
185
+ return;
186
+ }
187
+ if (key.ctrl && input === 'a') {
188
+ setC(v.lastIndexOf('\n', c - 1) + 1, 'start');
189
+ return;
190
+ }
191
+ if (key.ctrl && input === 'e') {
192
+ const next = v.indexOf('\n', c);
193
+ setC(next === -1 ? v.length : next, 'end');
194
+ return;
195
+ }
196
+ if (key.ctrl || key.meta || key.escape || key.tab)
197
+ return; // never insert modified/control chars
198
+ if (input) {
199
+ // A bare linefeed is Ctrl+J (ink reports it as plain input '\n'); pasted chunks
200
+ // keep their newlines, with CR normalized, instead of submitting per line.
201
+ noteBulk(input);
202
+ insertAt(input.replace(/\r\n?/g, '\n'));
203
+ }
204
+ }, { isActive: focus });
205
+ if (!value && placeholder) {
206
+ return (_jsxs(Text, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(Text, { color: "gray", dimColor: true, children: placeholder })] }));
207
+ }
208
+ // Render the SAME visual rows the navigation model uses (pre-wrapped, so ink never
209
+ // re-wraps and the cursor cell always sits on the row the ↑/↓ arithmetic predicts).
210
+ const rows = visualLines(value, columns);
211
+ const cursor = Math.min(cursorRef.current, value.length);
212
+ const cursorRow = focus ? rowIndexFor(rows, cursor) : -1;
213
+ return (_jsx(Box, { flexDirection: "column", children: rows.map((row, i) => {
214
+ if (i !== cursorRow)
215
+ return _jsx(Text, { children: row.text || ' ' }, i);
216
+ const at = Math.max(0, cursor - row.start);
217
+ const cellEnd = at + (isLow(row.text, at + 1) ? 2 : 1);
218
+ return (_jsxs(Text, { children: [row.text.slice(0, at), _jsx(Text, { inverse: true, children: row.text.slice(at, cellEnd) || ' ' }), row.text.slice(cellEnd)] }, i));
219
+ }) }));
220
+ }
221
+ //# sourceMappingURL=MultilineInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultilineInput.js","sourceRoot":"","sources":["../../../src/tui/MultilineInput.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Candidates for the `@` attachment popup (Claude Code's file-mention popup, scoped to our
3
+ * needs: keywords + image files/directories browsed from the typed prefix — no repo-wide
4
+ * index, we only ever attach images).
5
+ */
6
+ export interface AtCandidate {
7
+ /** Text that replaces the partial after `@` when chosen. */
8
+ insert: string;
9
+ label: string;
10
+ hint: string;
11
+ isDir?: boolean;
12
+ }
13
+ export interface AtPopupDeps {
14
+ readDir?: (dir: string) => Promise<Array<{
15
+ name: string;
16
+ isDirectory: boolean;
17
+ }>>;
18
+ homeDir?: string;
19
+ }
20
+ /** Compute candidates for the text typed after `@` ('' | 'im' | './fo' | '~/Pic' ...). */
21
+ export declare function atCandidates(partial: string, deps?: AtPopupDeps): Promise<AtCandidate[]>;
@@ -0,0 +1,55 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { IMAGE_EXT } from '../util/imageRef.js';
5
+ const KEYWORDS = [
6
+ { insert: 'image', label: '@image', hint: 'the last generated image' },
7
+ { insert: 'clipboard', label: '@clipboard', hint: 'image on the clipboard' },
8
+ ];
9
+ const MAX = 8;
10
+ // Every keystroke after `@` recomputes candidates; a short TTL keeps typing from hammering
11
+ // the filesystem while still noticing new screenshots between popups.
12
+ const dirCache = new Map();
13
+ const DIR_CACHE_TTL_MS = 2000;
14
+ async function defaultReadDir(dir) {
15
+ const hit = dirCache.get(dir);
16
+ if (hit && Date.now() - hit.at < DIR_CACHE_TTL_MS)
17
+ return hit.entries;
18
+ const entries = (await fs.readdir(dir, { withFileTypes: true })).map((e) => ({
19
+ name: e.name,
20
+ isDirectory: e.isDirectory(),
21
+ }));
22
+ dirCache.set(dir, { at: Date.now(), entries });
23
+ return entries;
24
+ }
25
+ /** Compute candidates for the text typed after `@` ('' | 'im' | './fo' | '~/Pic' ...). */
26
+ export async function atCandidates(partial, deps = {}) {
27
+ const readDir = deps.readDir ?? defaultReadDir;
28
+ const home = deps.homeDir ?? os.homedir();
29
+ const keywords = KEYWORDS.filter((k) => k.insert.startsWith(partial.toLowerCase()));
30
+ // Path browsing: everything up to the last '/' is the directory, the rest is the prefix.
31
+ const slash = partial.lastIndexOf('/');
32
+ const dirPart = slash >= 0 ? partial.slice(0, slash + 1) : '';
33
+ const prefix = (slash >= 0 ? partial.slice(slash + 1) : partial).toLowerCase();
34
+ const dirForFs = dirPart === '' ? '.' : dirPart.startsWith('~/') ? path.join(home, dirPart.slice(2)) : dirPart;
35
+ let files = [];
36
+ try {
37
+ const entries = await readDir(dirForFs);
38
+ files = entries
39
+ .filter((e) => !e.name.startsWith('.') && (e.isDirectory || IMAGE_EXT.test(e.name)))
40
+ .filter((e) => e.name.toLowerCase().startsWith(prefix))
41
+ .sort((a, b) => Number(b.isDirectory) - Number(a.isDirectory) || a.name.localeCompare(b.name))
42
+ .slice(0, MAX)
43
+ .map((e) => ({
44
+ insert: `${dirPart}${e.name}${e.isDirectory ? '/' : ''}`,
45
+ label: `${dirPart}${e.name}${e.isDirectory ? '/' : ''}`,
46
+ hint: e.isDirectory ? 'directory' : 'image file',
47
+ isDir: e.isDirectory,
48
+ }));
49
+ }
50
+ catch {
51
+ files = []; // unreadable dir → keywords only
52
+ }
53
+ return [...keywords, ...files].slice(0, MAX);
54
+ }
55
+ //# sourceMappingURL=atPopup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atPopup.js","sourceRoot":"","sources":["../../../src/tui/atPopup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { ContentPart } from '../core/types.js';
2
+ export interface AttachmentContext {
3
+ /** Output URL of the most recently completed generation (for `@image`). */
4
+ lastMediaUrl?: string;
5
+ /** Injectable file reader (tests). */
6
+ readFile?: (p: string) => Promise<Buffer>;
7
+ /** Injectable clipboard-image reader (tests); returns a temp file path or null. */
8
+ readClipboard?: () => Promise<string | null>;
9
+ }
10
+ export type AttachmentResult = {
11
+ kind: 'none';
12
+ } | {
13
+ kind: 'parts';
14
+ parts: ContentPart[];
15
+ } | {
16
+ kind: 'error';
17
+ message: string;
18
+ };
19
+ /**
20
+ * Parse `@` references out of a message. The full original text (including the `@` tokens)
21
+ * stays as the text part so the model sees what the user wrote; images are appended as
22
+ * image_url parts. Returns `none` when there is nothing to attach.
23
+ */
24
+ export declare function resolveAttachments(input: string, ctx?: AttachmentContext): Promise<AttachmentResult>;
@@ -0,0 +1,91 @@
1
+ import { resolveImageRef, ImageRefError, IMAGE_EXT } from '../util/imageRef.js';
2
+ import { readClipboardImage } from '../util/clipboardImage.js';
3
+ /**
4
+ * `@` attachment references in TUI input (contract §6): turn a message like
5
+ * "how does @image look?" into multimodal content parts so the model can SEE the image.
6
+ *
7
+ * Recognized forms — anything else stays plain text (so emails/@handles are untouched):
8
+ * - `@image` / `@last`: the most recently completed generation (its hosted output URL).
9
+ * - `@clipboard`: the image currently on the system clipboard (screenshot → ask).
10
+ * - `@<path>` where the token looks like an image file (starts with ./ ../ / ~ or ends in an
11
+ * image extension): the local file, inlined as a data URL (≤ 8MB).
12
+ */
13
+ const PATHISH = /^(\.{1,2}\/|\/|~\/)/;
14
+ /**
15
+ * Extract attachment tokens from the input:
16
+ * - `@image` / `@last` match as keywords even when CJK text follows immediately
17
+ * ("看看@image这个图" — users don't add a space before Chinese text);
18
+ * - path-ish tokens (`@./a.png`, `@/abs/b.jpg`, `@~/c.webp`, `@shot.png`) match up to
19
+ * whitespace, requiring a path prefix or an image extension.
20
+ * Everything else (emails, @handles) stays plain text.
21
+ */
22
+ function extractTokens(input) {
23
+ const tokens = [];
24
+ for (const m of input.matchAll(/@(image|last|clipboard)(?![a-zA-Z0-9_.-])/g))
25
+ tokens.push(m[1]);
26
+ // Quoted form for names with spaces (macOS screenshots): @"Screenshot 2026 ….png"
27
+ let rest = input;
28
+ for (const m of input.matchAll(/@"([^"\n]+)"/g)) {
29
+ const token = m[1];
30
+ if (PATHISH.test(token) || IMAGE_EXT.test(token))
31
+ tokens.push(token);
32
+ rest = rest.replace(m[0], ' '); // don't re-match fragments of it below
33
+ }
34
+ for (const m of rest.matchAll(/@(\S+)/g)) {
35
+ const token = m[1];
36
+ if (token === 'image' || token === 'last')
37
+ continue; // already handled above
38
+ if (PATHISH.test(token) || IMAGE_EXT.test(token))
39
+ tokens.push(token);
40
+ }
41
+ return tokens;
42
+ }
43
+ /**
44
+ * Parse `@` references out of a message. The full original text (including the `@` tokens)
45
+ * stays as the text part so the model sees what the user wrote; images are appended as
46
+ * image_url parts. Returns `none` when there is nothing to attach.
47
+ */
48
+ export async function resolveAttachments(input, ctx = {}) {
49
+ const tokens = extractTokens(input);
50
+ if (tokens.length === 0)
51
+ return { kind: 'none' };
52
+ const images = [];
53
+ for (const token of tokens) {
54
+ if (token === 'image' || token === 'last') {
55
+ if (!ctx.lastMediaUrl) {
56
+ return { kind: 'error', message: 'No generated image to reference yet — create one first (/image ...).' };
57
+ }
58
+ images.push({ type: 'image_url', image_url: { url: ctx.lastMediaUrl } });
59
+ continue;
60
+ }
61
+ if (token === 'clipboard') {
62
+ const file = await (ctx.readClipboard ?? readClipboardImage)();
63
+ if (!file) {
64
+ return { kind: 'error', message: 'No image on the clipboard (copy a screenshot first).' };
65
+ }
66
+ try {
67
+ const url = await resolveImageRef(file, ctx.readFile);
68
+ images.push({ type: 'image_url', image_url: { url } });
69
+ }
70
+ catch (err) {
71
+ if (err instanceof ImageRefError)
72
+ return { kind: 'error', message: err.message };
73
+ throw err;
74
+ }
75
+ continue;
76
+ }
77
+ try {
78
+ const url = await resolveImageRef(token, ctx.readFile);
79
+ images.push({ type: 'image_url', image_url: { url } });
80
+ }
81
+ catch (err) {
82
+ if (err instanceof ImageRefError) {
83
+ // Show the reference the way the user typed it (with the @ prefix).
84
+ return { kind: 'error', message: err.message.replace(token, `@${token}`) };
85
+ }
86
+ throw err;
87
+ }
88
+ }
89
+ return { kind: 'parts', parts: [{ type: 'text', text: input }, ...images] };
90
+ }
91
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../../src/tui/attachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * GlobalGPT brand block for the TUI welcome area (baseline §2/§3).
3
+ *
4
+ * LOGO_FRAMES is a pre-rendered clockwise-sweep reveal of the official logo (10 braille
5
+ * frames baked by scripts/render-logo.mjs --frames; the last frame is the full mark)
6
+ * (docs/assets/globalgpt-logo.png → 20×20 → 2×4-dot braille cells, each cell colored by its
7
+ * dominant brand color: coral 241,91,102 · orange 241,137,31 · purple 237,91,239). Generated
8
+ * at build time — no runtime image dependency. Terminals without truecolor skip the raster;
9
+ * the name and info lines never depend on it. Layout mirrors the compact competitor style
10
+ * (5 rows, logo left / info right).
11
+ */
12
+ export declare const LOGO_FRAMES: string[][];
13
+ /** The full mark (final reveal frame). */
14
+ export declare const LOGO_LINES: string[];
15
+ export declare const TAGLINE = "Chat, images, and video \u2014 your GlobalGPT account in the terminal.";
16
+ /** Whether the terminal advertises 24-bit color (the raster needs it). */
17
+ export declare function supportsTruecolor(env?: NodeJS.ProcessEnv): boolean;
18
+ /**
19
+ * The product name with a per-character purple→coral→orange gradient (bold), matching the
20
+ * logo's arcs. Falls back to plain bold on non-truecolor terminals.
21
+ */
22
+ export declare function brandTitle(text?: string, env?: NodeJS.ProcessEnv): string;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * GlobalGPT brand block for the TUI welcome area (baseline §2/§3).
3
+ *
4
+ * LOGO_FRAMES is a pre-rendered clockwise-sweep reveal of the official logo (10 braille
5
+ * frames baked by scripts/render-logo.mjs --frames; the last frame is the full mark)
6
+ * (docs/assets/globalgpt-logo.png → 20×20 → 2×4-dot braille cells, each cell colored by its
7
+ * dominant brand color: coral 241,91,102 · orange 241,137,31 · purple 237,91,239). Generated
8
+ * at build time — no runtime image dependency. Terminals without truecolor skip the raster;
9
+ * the name and info lines never depend on it. Layout mirrors the compact competitor style
10
+ * (5 rows, logo left / info right).
11
+ */
12
+ export const LOGO_FRAMES = [
13
+ [
14
+ ' \x1b[38;2;237;91;239m⣿⣶⡆\x1b[0m',
15
+ ' \x1b[38;2;237;91;239m⠉\x1b[0m',
16
+ ' \x1b[38;2;241;91;102m⠁\x1b[0m',
17
+ '',
18
+ '',
19
+ ],
20
+ [
21
+ ' \x1b[38;2;237;91;239m⣿⣶⣦⡀\x1b[0m',
22
+ ' \x1b[38;2;237;91;239m⠉⠙⠻⠆\x1b[0m',
23
+ ' \x1b[38;2;241;91;102m⠛\x1b[0m',
24
+ '',
25
+ '',
26
+ ],
27
+ [
28
+ ' \x1b[38;2;237;91;239m⣿⣶⣦⡀\x1b[0m',
29
+ ' \x1b[38;2;237;91;239m⠉⠙⠻⡆\x1b[0m',
30
+ ' \x1b[38;2;241;91;102m⠛⠶⢤⡀\x1b[0m\x1b[38;2;237;91;239m⠘\x1b[0m',
31
+ '',
32
+ '',
33
+ ],
34
+ [
35
+ ' \x1b[38;2;237;91;239m⣿⣶⣦⡀\x1b[0m',
36
+ ' \x1b[38;2;237;91;239m⠉⠙⠻⡆\x1b[0m',
37
+ ' \x1b[38;2;241;91;102m⢿⣶⣤⡀\x1b[0m\x1b[38;2;237;91;239m⠘\x1b[0m',
38
+ ' \x1b[38;2;241;91;102m⠙⢿⣿\x1b[0m',
39
+ ' \x1b[38;2;241;91;102m⠁\x1b[0m',
40
+ ],
41
+ [
42
+ ' \x1b[38;2;237;91;239m⣿⣶⣦⡀\x1b[0m',
43
+ ' \x1b[38;2;237;91;239m⠉⠙⠻⡆\x1b[0m',
44
+ ' \x1b[38;2;241;91;102m⣿⣶⣤⡀\x1b[0m\x1b[38;2;237;91;239m⠘\x1b[0m',
45
+ ' \x1b[38;2;241;91;102m⠈⠙⢿⣿\x1b[0m',
46
+ ' \x1b[38;2;241;137;31m⡦\x1b[0m \x1b[38;2;241;91;102m⠁\x1b[0m',
47
+ ],
48
+ [
49
+ ' \x1b[38;2;237;91;239m⣿⣶⣦⡀\x1b[0m',
50
+ ' \x1b[38;2;237;91;239m⠉⠙⠻⡆\x1b[0m',
51
+ ' \x1b[38;2;241;91;102m⢀⣿⣶⣤⡀\x1b[0m\x1b[38;2;237;91;239m⠘\x1b[0m',
52
+ ' \x1b[38;2;241;137;31m⣀\x1b[0m \x1b[38;2;241;91;102m⠈⠙⢿⣿\x1b[0m',
53
+ ' \x1b[38;2;241;137;31m⠘⠿⣿⡦\x1b[0m \x1b[38;2;241;91;102m⠁\x1b[0m',
54
+ ],
55
+ [
56
+ ' \x1b[38;2;237;91;239m⣿⣶⣦⡀\x1b[0m',
57
+ ' \x1b[38;2;237;91;239m⠉⠙⠻⡆\x1b[0m',
58
+ ' \x1b[38;2;241;91;102m⢤⣿⣶⣤⡀\x1b[0m\x1b[38;2;237;91;239m⠘\x1b[0m',
59
+ '\x1b[38;2;241;137;31m⠐⣦⣄⣀\x1b[0m \x1b[38;2;241;91;102m⠈⠙⢿⣿\x1b[0m',
60
+ ' \x1b[38;2;241;137;31m⠈⠛⠿⣿⡦\x1b[0m \x1b[38;2;241;91;102m⠁\x1b[0m',
61
+ ],
62
+ [
63
+ ' \x1b[38;2;237;91;239m⣿⣶⣦⡀\x1b[0m',
64
+ ' \x1b[38;2;237;91;239m⠉⠙⠻⡆\x1b[0m',
65
+ '\x1b[38;2;241;137;31m⡀\x1b[0m\x1b[38;2;241;91;102m⠈⠓⠶⢤⣿⣶⣤⡀\x1b[0m\x1b[38;2;237;91;239m⠘\x1b[0m',
66
+ '\x1b[38;2;241;137;31m⠘⣦⣄⣀\x1b[0m \x1b[38;2;241;91;102m⠈⠙⢿⣿\x1b[0m',
67
+ ' \x1b[38;2;241;137;31m⠈⠛⠿⣿⡦\x1b[0m \x1b[38;2;241;91;102m⠁\x1b[0m',
68
+ ],
69
+ [
70
+ ' \x1b[38;2;241;91;102m⢀⡀\x1b[0m \x1b[38;2;237;91;239m⣿⣶⣦⡀\x1b[0m',
71
+ ' \x1b[38;2;241;91;102m⣿⣷⣄\x1b[0m \x1b[38;2;237;91;239m⠉⠙⠻⡆\x1b[0m',
72
+ '\x1b[38;2;241;137;31m⡀\x1b[0m\x1b[38;2;241;91;102m⠈⠛⠿⢷⣿⣶⣤⡀\x1b[0m\x1b[38;2;237;91;239m⠘\x1b[0m',
73
+ '\x1b[38;2;241;137;31m⠘⣦⣄⣀\x1b[0m \x1b[38;2;241;91;102m⠈⠙⢿⣿\x1b[0m',
74
+ ' \x1b[38;2;241;137;31m⠈⠛⠿⣿⡦\x1b[0m \x1b[38;2;241;91;102m⠁\x1b[0m',
75
+ ],
76
+ [
77
+ ' \x1b[38;2;241;91;102m⢀⡀\x1b[0m \x1b[38;2;237;91;239m⠸⣿⣶⣦⡀\x1b[0m',
78
+ ' \x1b[38;2;241;91;102m⣿⣷⣄⡀\x1b[0m \x1b[38;2;237;91;239m⠉⠙⠻⡆\x1b[0m',
79
+ '\x1b[38;2;241;137;31m⡀\x1b[0m\x1b[38;2;241;91;102m⠈⠛⠿⢿⣿⣶⣤⡀\x1b[0m\x1b[38;2;237;91;239m⠘\x1b[0m',
80
+ '\x1b[38;2;241;137;31m⠘⣦⣄⣀\x1b[0m \x1b[38;2;241;91;102m⠈⠙⢿⣿\x1b[0m',
81
+ ' \x1b[38;2;241;137;31m⠈⠛⠿⣿⡦\x1b[0m \x1b[38;2;241;91;102m⠁\x1b[0m',
82
+ ],
83
+ ];
84
+ /** The full mark (final reveal frame). */
85
+ export const LOGO_LINES = LOGO_FRAMES[LOGO_FRAMES.length - 1];
86
+ export const TAGLINE = 'Chat, images, and video — your GlobalGPT account in the terminal.';
87
+ /** Whether the terminal advertises 24-bit color (the raster needs it). */
88
+ export function supportsTruecolor(env = process.env) {
89
+ const ct = (env.COLORTERM ?? '').toLowerCase();
90
+ return ct.includes('truecolor') || ct.includes('24bit');
91
+ }
92
+ /** The three brand colors, in the logo's arc order. */
93
+ const BRAND_STOPS = [
94
+ [237, 91, 239], // purple
95
+ [241, 91, 102], // coral
96
+ [241, 137, 31], // orange
97
+ ];
98
+ /**
99
+ * The product name with a per-character purple→coral→orange gradient (bold), matching the
100
+ * logo's arcs. Falls back to plain bold on non-truecolor terminals.
101
+ */
102
+ export function brandTitle(text = 'GlobalGPT CLI', env = process.env) {
103
+ if (!supportsTruecolor(env))
104
+ return `\x1b[1m${text}\x1b[0m`;
105
+ const chars = [...text];
106
+ const out = ['\x1b[1m'];
107
+ for (let i = 0; i < chars.length; i++) {
108
+ const t = chars.length === 1 ? 0 : i / (chars.length - 1);
109
+ // Two segments: purple→coral for t∈[0,.5), coral→orange for t∈[.5,1].
110
+ const seg = t < 0.5 ? 0 : 1;
111
+ const local = t < 0.5 ? t * 2 : (t - 0.5) * 2;
112
+ const [a, b] = [BRAND_STOPS[seg], BRAND_STOPS[seg + 1]];
113
+ const mix = (x, y) => Math.round(x + (y - x) * local);
114
+ out.push(`\x1b[38;2;${mix(a[0], b[0])};${mix(a[1], b[1])};${mix(a[2], b[2])}m${chars[i]}`);
115
+ }
116
+ out.push('\x1b[0m');
117
+ return out.join('');
118
+ }
119
+ //# sourceMappingURL=branding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branding.js","sourceRoot":"","sources":["../../../src/tui/branding.ts"],"names":[],"mappings":"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"}