@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,212 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from 'ink';
3
+ import { App } from './App.js';
4
+ import { sessionsDir } from '../config/paths.js';
5
+ import { ChatController } from './chatController.js';
6
+ import { AuthClient } from '../auth/authClient.js';
7
+ import { getDefaultChatModel } from './models.js';
8
+ import { Session } from '../core/session.js';
9
+ import { newId } from '../core/id.js';
10
+ import { SessionStore } from '../core/sessionStore.js';
11
+ import { TurnLoop } from '../core/turnLoop.js';
12
+ import { ToolRegistry } from '../core/toolRegistry.js';
13
+ import { HttpChatBackend } from '../core/httpChatBackend.js';
14
+ import { createStubGenerateImageTool } from '../core/tools/generateImage.js';
15
+ import { createGenerateImageTool, createGenerateVideoTool } from '../media/generateImageTool.js';
16
+ import { AiApiClient } from '../media/aiApiClient.js';
17
+ import { GenerationExecutor } from '../media/executor.js';
18
+ import { TaskManager } from '../media/taskManager.js';
19
+ import { TaskLedger } from '../media/taskLedger.js';
20
+ import { ConfirmationBridge, TuiConfirmer } from './confirmationBridge.js';
21
+ import { createInputHistory } from './inputHistory.js';
22
+ import { resolveToken } from '../auth/token.js';
23
+ import { CredentialStore } from '../auth/credentialStore.js';
24
+ import { browserLogin } from '../auth/loginFlow.js';
25
+ import { openBrowser } from '../util/openBrowser.js';
26
+ import { apiBaseUrl, webBaseUrl, lang } from '../config/env.js';
27
+ import { ensureCatalog } from '../core/catalog.js';
28
+ import path from 'node:path';
29
+ /**
30
+ * Wire the chat engine to the Ink TUI and render it.
31
+ *
32
+ * The token comes from stored credentials (`glbgpt login`), or the GLBGPT_TOKEN env
33
+ * override. GLBGPT_BASE_URL points at test vs prod.
34
+ */
35
+ export async function runTui(options = {}) {
36
+ const backend = options.backend ?? (await buildHttpBackend());
37
+ if (!backend) {
38
+ console.error('Not signed in. Run `glbgpt login` first (or set GLBGPT_TOKEN).');
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ const token = await resolveToken();
43
+ // Server-driven model catalog: cached copy applies now, refresh continues in the background.
44
+ await ensureCatalog({ baseUrl: apiBaseUrl(), token });
45
+ const session = options.session ?? new Session({ id: newId(), model: getDefaultChatModel() });
46
+ // The token can be hot-swapped mid-session by `/login`; every API consumer reads this holder
47
+ // (chat backend, media client, account closures) so a re-auth takes effect without restart.
48
+ let currentToken = token;
49
+ let mediaClient;
50
+ // Media stack: the chat image/video tools confirm via the TUI bridge, submit, and poll in the background.
51
+ const confirmationBridge = new ConfirmationBridge();
52
+ let taskManager;
53
+ const registry = new ToolRegistry();
54
+ const extras = {};
55
+ if (!options.imageTool && token) {
56
+ mediaClient = new AiApiClient({ baseUrl: apiBaseUrl(), token });
57
+ const executor = new GenerationExecutor(mediaClient, new TuiConfirmer(confirmationBridge));
58
+ taskManager = new TaskManager(mediaClient, { outputDir: path.join(process.cwd(), 'glbgpt-output') });
59
+ const ledger = new TaskLedger();
60
+ const imageTool = createGenerateImageTool({ executor, taskManager, ledger });
61
+ const videoTool = createGenerateVideoTool({ executor, taskManager, ledger });
62
+ registry.register(imageTool);
63
+ registry.register(videoTool);
64
+ // Slash commands run the same tool instances the model uses (same confirm/ledger/polling path).
65
+ extras.mediaTools = { image: imageTool, video: videoTool };
66
+ extras.listRecentTasks = () => ledger.list(10);
67
+ extras.fetchAccountLine = async () => {
68
+ const info = await new AuthClient({ baseUrl: apiBaseUrl() }).userInfo(currentToken);
69
+ const bits = [info.plan && `plan: ${info.plan}`, info.balance !== undefined && `balance: ${info.balance}`];
70
+ return bits.filter(Boolean).join(' · ') || null;
71
+ };
72
+ extras.fetchAccount = async () => {
73
+ const info = await new AuthClient({ baseUrl: apiBaseUrl() }).userInfo(currentToken);
74
+ const lines = [`account: ${info.email ?? '(unknown)'}`];
75
+ if (info.plan)
76
+ lines.push(`plan: ${info.plan}`);
77
+ if (info.balance !== undefined)
78
+ lines.push(`balance: ${info.balance} credits`);
79
+ return lines.join('\n');
80
+ };
81
+ extras.fetchTransactions = async () => {
82
+ const bills = await new AuthClient({ baseUrl: apiBaseUrl() }).bills(currentToken, 1, 10);
83
+ if (bills.length === 0)
84
+ return 'No transactions yet.';
85
+ // cost is USD; show credits (1 credit ≈ $0.001) to match the generation estimates.
86
+ return bills
87
+ .map((b) => `${(b.createdAt ?? '').padEnd(20)} ${String(Math.round(b.cost * 1000)).padStart(6)} cr ${b.model}`)
88
+ .join('\n');
89
+ };
90
+ }
91
+ else {
92
+ registry.register(options.imageTool ?? createStubGenerateImageTool());
93
+ }
94
+ // /login (re-auth): run the browser loopback flow, then hot-swap the new token everywhere.
95
+ // Note: `--code` paste isn't offered in-TUI (it needs a stdin prompt Ink owns) — headless
96
+ // users re-auth with `glbgpt login --code` in a shell.
97
+ extras.reauth = async (mode) => {
98
+ const cs = new CredentialStore();
99
+ const result = await browserLogin({
100
+ webBaseUrl: webBaseUrl(),
101
+ lang: lang(),
102
+ authClient: new AuthClient({ baseUrl: apiBaseUrl() }),
103
+ credentialStore: cs,
104
+ openUrl: (url) => {
105
+ controller.note(`Open this URL to authorize (only if you started this login yourself):\n ${url}`);
106
+ if (mode === 'browser')
107
+ openBrowser(url);
108
+ },
109
+ });
110
+ const creds = await cs.load();
111
+ if (!creds?.token)
112
+ throw new Error('Login did not produce a token.');
113
+ currentToken = creds.token;
114
+ if (backend instanceof HttpChatBackend)
115
+ backend.setToken(currentToken);
116
+ mediaClient?.setToken(currentToken);
117
+ return `Logged in${result.email ? ` as ${result.email}` : ''}.`;
118
+ };
119
+ extras.logout = async () => {
120
+ const removed = await new CredentialStore().clear();
121
+ // The in-memory token stays live so the current session isn't cut off mid-conversation;
122
+ // the removal takes full effect on the next start.
123
+ return removed
124
+ ? 'Signed out — stored credentials removed. This session stays active until you exit.'
125
+ : 'You are not signed in.';
126
+ };
127
+ const turnLoop = new TurnLoop(backend, registry);
128
+ const controller = new ChatController(session, turnLoop, extras);
129
+ const store = new SessionStore(sessionsDir());
130
+ if (taskManager) {
131
+ taskManager.subscribe((e) => {
132
+ controller.handleTaskEvent(e);
133
+ // Persist on terminal task events too — the completion note goes into the session
134
+ // while idle, and would otherwise be lost to `resume` until the next turn.
135
+ if (e.type !== 'task_progress') {
136
+ void store.save(controller.getSession(), controller.getPersistableItems());
137
+ // Terminal bell: generations take minutes and the user has often switched away;
138
+ // most terminals turn this into a badge/attention bounce when unfocused.
139
+ process.stdout.write('\x07');
140
+ }
141
+ });
142
+ }
143
+ if (options.session) {
144
+ // Resuming: show the prior conversation in the timeline.
145
+ controller.hydrateFromSession(options.timeline);
146
+ }
147
+ // Persist the current session after each turn so `resume` has data (/new swaps to a new session).
148
+ let wasBusy = false;
149
+ controller.subscribe((state) => {
150
+ if (wasBusy && !state.busy) {
151
+ void store.save(controller.getSession(), controller.getPersistableItems());
152
+ }
153
+ wasBusy = state.busy;
154
+ });
155
+ const sessionPicker = {
156
+ listSessions: () => store.list(10),
157
+ loadSession: async (id) => {
158
+ try {
159
+ return await store.loadWithTimeline(id);
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ },
165
+ };
166
+ // First run = no stored conversations yet → show the one-time command navigation.
167
+ const firstRun = !options.session && (await store.list(1)).length === 0;
168
+ const app = render(_jsx(App, { controller: controller, confirmationBridge: confirmationBridge, sessionPicker: sessionPicker, firstRun: firstRun, fetchAccountLine: extras.fetchAccountLine, inputHistory: createInputHistory() }),
169
+ // Ctrl+C is handled in-app (clear/interrupt, then confirm-to-exit) — never an instant kill.
170
+ { exitOnCtrlC: false });
171
+ if (options.initialPrompt && options.initialPrompt.trim()) {
172
+ void controller.submit(options.initialPrompt.trim());
173
+ }
174
+ await app.waitUntilExit();
175
+ // Exit summary (Codex/Claude Code convention). Chat tokens are informational;
176
+ // the authoritative bill is `glbgpt account transactions`.
177
+ const u = controller.getState().sessionUsage;
178
+ const mins = Math.max(1, Math.round((Date.now() - controller.startedAt) / 60000));
179
+ const bits = [
180
+ `${u.turns} turn${u.turns === 1 ? '' : 's'}`,
181
+ `${(u.promptTokens + u.completionTokens).toLocaleString()} tokens`,
182
+ ...(u.mediaSubmits > 0 ? [`${u.mediaSubmits} media task${u.mediaSubmits === 1 ? '' : 's'} (~${u.mediaCreditsEstimated} credits est.)`] : []),
183
+ `${mins}m`,
184
+ ];
185
+ console.error(`Session: ${bits.join(' · ')} — authoritative spend: glbgpt account transactions`);
186
+ }
187
+ async function buildHttpBackend() {
188
+ const token = await resolveToken();
189
+ if (!token)
190
+ return null;
191
+ return new HttpChatBackend({ baseUrl: apiBaseUrl(), token });
192
+ }
193
+ /**
194
+ * Load a session for `resume`. `target` is a session id, or "last"/undefined for the
195
+ * most recent one. Returns null when no matching session exists.
196
+ */
197
+ export async function loadResumeSession(target) {
198
+ const store = new SessionStore(sessionsDir());
199
+ if (!target || target === 'last') {
200
+ const last = await store.loadLast();
201
+ if (!last)
202
+ return null;
203
+ return store.loadWithTimeline(last.id);
204
+ }
205
+ try {
206
+ return await store.loadWithTimeline(target);
207
+ }
208
+ catch {
209
+ return null;
210
+ }
211
+ }
212
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/tui/run.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { ConfirmInfo } from '../media/executor.js';
2
+ /**
3
+ * Flag syntax for the TUI's /image and /video commands — the SAME flag names as the
4
+ * top-level CLI commands, so there is one syntax to learn:
5
+ *
6
+ * /image a cute cat --ar 16:9 --res 2K -m nano-banana-2 --seed 7 -n 2
7
+ * /video waves --duration 6 --no-audio --negative-prompt "low quality"
8
+ *
9
+ * Values with spaces use double quotes. Non-flag words form the prompt (flags may appear
10
+ * anywhere). Unknown flags are an error — never silently ignored.
11
+ */
12
+ export interface SlashMediaArgs {
13
+ prompt: string;
14
+ model?: string;
15
+ aspectRatio?: string;
16
+ resolution?: string;
17
+ duration?: number;
18
+ seed?: number;
19
+ n?: number;
20
+ negativePrompt?: string;
21
+ generateAudio?: boolean;
22
+ }
23
+ export type SlashMediaParse = {
24
+ ok: true;
25
+ args: SlashMediaArgs;
26
+ } | {
27
+ ok: false;
28
+ error: string;
29
+ };
30
+ export declare function parseSlashMedia(rest: string): SlashMediaParse;
31
+ /**
32
+ * Reconstruct the full slash command for a pending confirmation (the [e] Edit prefill),
33
+ * including every parameter currently set — editing any field is one keystroke away, and
34
+ * the prefill doubles as syntax teaching.
35
+ */
36
+ export declare function buildSlashCommand(info: ConfirmInfo): string;
@@ -0,0 +1,86 @@
1
+ /** Flags taking a value, keyed by every accepted spelling. */
2
+ const VALUE_FLAGS = {
3
+ '-m': 'model',
4
+ '--model': 'model',
5
+ '--ar': 'aspectRatio',
6
+ '--aspect-ratio': 'aspectRatio',
7
+ '--res': 'resolution',
8
+ '--resolution': 'resolution',
9
+ '--duration': 'duration',
10
+ '--seed': 'seed',
11
+ '-n': 'n',
12
+ '--count': 'n',
13
+ '--negative-prompt': 'negativePrompt',
14
+ };
15
+ const NUMERIC = new Set(['duration', 'seed', 'n']);
16
+ export function parseSlashMedia(rest) {
17
+ // Tokenize honoring double quotes: "low quality" is one token.
18
+ const tokens = [...rest.matchAll(/"([^"]*)"|(\S+)/g)].map((m) => m[1] ?? m[2]);
19
+ const promptWords = [];
20
+ const args = { prompt: '' };
21
+ for (let i = 0; i < tokens.length; i++) {
22
+ const token = tokens[i];
23
+ if (token === '--audio') {
24
+ args.generateAudio = true;
25
+ }
26
+ else if (token === '--no-audio') {
27
+ args.generateAudio = false;
28
+ }
29
+ else if (token in VALUE_FLAGS) {
30
+ const key = VALUE_FLAGS[token];
31
+ const value = tokens[++i];
32
+ if (value === undefined || (value.startsWith('-') && value.length > 1 && isNaN(Number(value)))) {
33
+ return { ok: false, error: `Flag ${token} needs a value.` };
34
+ }
35
+ if (NUMERIC.has(key)) {
36
+ const num = Number(value);
37
+ if (!Number.isFinite(num))
38
+ return { ok: false, error: `Flag ${token} needs a number, got "${value}".` };
39
+ args[key] = num;
40
+ }
41
+ else {
42
+ args[key] = value;
43
+ }
44
+ }
45
+ else if (token.startsWith('--') || (token.startsWith('-') && token.length === 2 && isNaN(Number(token)))) {
46
+ return {
47
+ ok: false,
48
+ error: `Unknown flag ${token}. Known: -m --ar --res --duration --seed -n --negative-prompt --audio/--no-audio.`,
49
+ };
50
+ }
51
+ else {
52
+ promptWords.push(token);
53
+ }
54
+ }
55
+ args.prompt = promptWords.join(' ').trim();
56
+ if (!args.prompt)
57
+ return { ok: false, error: 'A prompt is required.' };
58
+ return { ok: true, args };
59
+ }
60
+ /**
61
+ * Reconstruct the full slash command for a pending confirmation (the [e] Edit prefill),
62
+ * including every parameter currently set — editing any field is one keystroke away, and
63
+ * the prefill doubles as syntax teaching.
64
+ */
65
+ export function buildSlashCommand(info) {
66
+ const p = info.params;
67
+ const parts = [`/${info.type}`, p.prompt, '-m', info.model];
68
+ if (p.aspectRatio)
69
+ parts.push('--ar', p.aspectRatio);
70
+ if (p.resolution)
71
+ parts.push('--res', p.resolution);
72
+ if (p.duration !== undefined)
73
+ parts.push('--duration', String(p.duration));
74
+ if (p.seed !== undefined)
75
+ parts.push('--seed', String(p.seed));
76
+ if (p.n !== undefined && p.n !== 1)
77
+ parts.push('-n', String(p.n));
78
+ if (p.negativePrompt)
79
+ parts.push('--negative-prompt', `"${p.negativePrompt}"`);
80
+ if (p.generateAudio === true)
81
+ parts.push('--audio');
82
+ if (p.generateAudio === false)
83
+ parts.push('--no-audio');
84
+ return parts.join(' ');
85
+ }
86
+ //# sourceMappingURL=slashMedia.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slashMedia.js","sourceRoot":"","sources":["../../../src/tui/slashMedia.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Visual-line model for the composer (the Claude Code Cursor.ts / Codex textarea.rs
3
+ * architecture): the editor's ↑/↓ move between WRAPPED rows as the user sees them, in
4
+ * DISPLAY-WIDTH columns (a CJK char occupies 2 columns), not UTF-16 offsets. All offsets
5
+ * in this module are UTF-16 code-unit indices into the original string; iteration is by
6
+ * code point so surrogate pairs are never split.
7
+ */
8
+ export interface VisualLine {
9
+ /** Offset of the first char of this row (inclusive). */
10
+ start: number;
11
+ /** Offset just past the last char of this row (exclusive; excludes any '\n'). */
12
+ end: number;
13
+ text: string;
14
+ }
15
+ /** Terminal display width of one code point (wcwidth-lite: East Asian Wide + emoji = 2). */
16
+ export declare function charWidth(cp: number): number;
17
+ /** Display width of a string (sum of code-point widths). */
18
+ export declare function displayWidth(s: string): number;
19
+ /**
20
+ * Split `value` into visual rows: logical lines (split on '\n'), each greedily wrapped to
21
+ * `columns` display cells. Without `columns` each logical line is one row (no wrap info).
22
+ * A row boundary never splits a code point. Empty logical lines produce one empty row, so
23
+ * every cursor offset maps to exactly one row.
24
+ */
25
+ export declare function visualLines(value: string, columns?: number): VisualLine[];
26
+ /**
27
+ * Index of the row containing cursor offset `c`: the LAST row whose start ≤ c. At a soft-wrap
28
+ * boundary (c == rowN.end == rowN+1.start) this lands on the next row — the cursor renders at
29
+ * the head of the continuation row, matching where typed text would go.
30
+ */
31
+ export declare function rowIndexFor(rows: VisualLine[], c: number): number;
32
+ /** Display column of offset `c` within its row. */
33
+ export declare function columnInRow(row: VisualLine, c: number): number;
34
+ /**
35
+ * Offset within `row` at display column `col` (never overshoots into the middle of a wide
36
+ * char; past-end columns clamp to the row end — Claude Code Cursor.up()'s column rule).
37
+ */
38
+ export declare function offsetAtColumn(row: VisualLine, col: number): number;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Visual-line model for the composer (the Claude Code Cursor.ts / Codex textarea.rs
3
+ * architecture): the editor's ↑/↓ move between WRAPPED rows as the user sees them, in
4
+ * DISPLAY-WIDTH columns (a CJK char occupies 2 columns), not UTF-16 offsets. All offsets
5
+ * in this module are UTF-16 code-unit indices into the original string; iteration is by
6
+ * code point so surrogate pairs are never split.
7
+ */
8
+ /** Terminal display width of one code point (wcwidth-lite: East Asian Wide + emoji = 2). */
9
+ export function charWidth(cp) {
10
+ if (cp === 0)
11
+ return 0;
12
+ if (cp < 32 || (cp >= 0x7f && cp < 0xa0))
13
+ return 0; // control chars
14
+ if ((cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
15
+ (cp >= 0x2e80 && cp <= 0x303e) || // CJK radicals, punctuation
16
+ (cp >= 0x3041 && cp <= 0x33ff) || // Hiragana … CJK compat
17
+ (cp >= 0x3400 && cp <= 0x4dbf) || // CJK ext A
18
+ (cp >= 0x4e00 && cp <= 0x9fff) || // CJK unified
19
+ (cp >= 0xa000 && cp <= 0xa4cf) || // Yi
20
+ (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul syllables
21
+ (cp >= 0xf900 && cp <= 0xfaff) || // CJK compat ideographs
22
+ (cp >= 0xfe30 && cp <= 0xfe4f) || // CJK compat forms
23
+ (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth forms
24
+ (cp >= 0xffe0 && cp <= 0xffe6) ||
25
+ (cp >= 0x1f300 && cp <= 0x1faff) || // emoji blocks
26
+ (cp >= 0x20000 && cp <= 0x3fffd) // CJK ext B+
27
+ ) {
28
+ return 2;
29
+ }
30
+ return 1;
31
+ }
32
+ /** Display width of a string (sum of code-point widths). */
33
+ export function displayWidth(s) {
34
+ let w = 0;
35
+ for (const ch of s)
36
+ w += charWidth(ch.codePointAt(0) ?? 0);
37
+ return w;
38
+ }
39
+ /**
40
+ * Split `value` into visual rows: logical lines (split on '\n'), each greedily wrapped to
41
+ * `columns` display cells. Without `columns` each logical line is one row (no wrap info).
42
+ * A row boundary never splits a code point. Empty logical lines produce one empty row, so
43
+ * every cursor offset maps to exactly one row.
44
+ */
45
+ export function visualLines(value, columns) {
46
+ const rows = [];
47
+ let offset = 0;
48
+ for (const logical of value.split('\n')) {
49
+ if (!columns || columns < 2) {
50
+ rows.push({ start: offset, end: offset + logical.length, text: logical });
51
+ }
52
+ else {
53
+ let segStart = 0;
54
+ let width = 0;
55
+ let i = 0;
56
+ while (i < logical.length) {
57
+ const cp = logical.codePointAt(i) ?? 0;
58
+ const units = cp > 0xffff ? 2 : 1;
59
+ const w = charWidth(cp);
60
+ if (width + w > columns && i > segStart) {
61
+ rows.push({ start: offset + segStart, end: offset + i, text: logical.slice(segStart, i) });
62
+ segStart = i;
63
+ width = 0;
64
+ }
65
+ width += w;
66
+ i += units;
67
+ }
68
+ rows.push({ start: offset + segStart, end: offset + logical.length, text: logical.slice(segStart) });
69
+ }
70
+ offset += logical.length + 1; // +1 skips the '\n'
71
+ }
72
+ return rows;
73
+ }
74
+ /**
75
+ * Index of the row containing cursor offset `c`: the LAST row whose start ≤ c. At a soft-wrap
76
+ * boundary (c == rowN.end == rowN+1.start) this lands on the next row — the cursor renders at
77
+ * the head of the continuation row, matching where typed text would go.
78
+ */
79
+ export function rowIndexFor(rows, c) {
80
+ let idx = 0;
81
+ for (let i = 0; i < rows.length; i++) {
82
+ if (rows[i].start <= c)
83
+ idx = i;
84
+ else
85
+ break;
86
+ }
87
+ return idx;
88
+ }
89
+ /** Display column of offset `c` within its row. */
90
+ export function columnInRow(row, c) {
91
+ return displayWidth(row.text.slice(0, Math.max(0, c - row.start)));
92
+ }
93
+ /**
94
+ * Offset within `row` at display column `col` (never overshoots into the middle of a wide
95
+ * char; past-end columns clamp to the row end — Claude Code Cursor.up()'s column rule).
96
+ */
97
+ export function offsetAtColumn(row, col) {
98
+ let w = 0;
99
+ let i = 0;
100
+ while (i < row.text.length) {
101
+ const cp = row.text.codePointAt(i) ?? 0;
102
+ const cw = charWidth(cp);
103
+ if (w + cw > col)
104
+ break;
105
+ w += cw;
106
+ i += cp > 0xffff ? 2 : 1;
107
+ }
108
+ return row.start + i;
109
+ }
110
+ //# sourceMappingURL=visualLines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visualLines.js","sourceRoot":"","sources":["../../../src/tui/visualLines.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Read an image from the system clipboard into a temp PNG file (Claude Code's
3
+ * imagePaste.ts approach: osascript «class PNGf» on macOS, xclip/wl-paste on Linux).
4
+ * Returns the file path, or null when the clipboard has no image / platform unsupported.
5
+ */
6
+ export declare function readClipboardImage(run?: (cmd: string, args: string[]) => Promise<unknown>): Promise<string | null>;
@@ -0,0 +1,69 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promises as fs } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { IMAGE_EXT } from './imageRef.js';
7
+ const execFileP = promisify(execFile);
8
+ /**
9
+ * Read an image from the system clipboard into a temp PNG file (Claude Code's
10
+ * imagePaste.ts approach: osascript «class PNGf» on macOS, xclip/wl-paste on Linux).
11
+ * Returns the file path, or null when the clipboard has no image / platform unsupported.
12
+ */
13
+ export async function readClipboardImage(run = (cmd, args) => execFileP(cmd, args)) {
14
+ const dest = path.join(tmpdir(), `glbgpt-clipboard-${Date.now()}.png`);
15
+ if (process.platform === 'darwin') {
16
+ try {
17
+ await run('osascript', [
18
+ '-e',
19
+ 'set png_data to (the clipboard as «class PNGf»)',
20
+ '-e',
21
+ `set fp to open for access POSIX file "${dest}" with write permission`,
22
+ '-e',
23
+ 'write png_data to fp',
24
+ '-e',
25
+ 'close access fp',
26
+ ]);
27
+ }
28
+ catch {
29
+ // Not PNG DATA — copying a file in Finder puts a file REFERENCE (furl) on the
30
+ // clipboard instead (Claude Code's getPath command handles exactly this).
31
+ try {
32
+ const out = (await run('osascript', [
33
+ '-e',
34
+ 'get POSIX path of (the clipboard as «class furl»)',
35
+ ]));
36
+ const p = (out.stdout ?? '').trim();
37
+ if (p && IMAGE_EXT.test(p))
38
+ return p; // use the file directly
39
+ }
40
+ catch {
41
+ /* no furl either */
42
+ }
43
+ return null; // clipboard has neither image data nor an image file
44
+ }
45
+ }
46
+ else if (process.platform === 'linux') {
47
+ try {
48
+ await run('sh', ['-c', `xclip -selection clipboard -t image/png -o > "${dest}" 2>/dev/null || wl-paste --type image/png > "${dest}"`]);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ else {
55
+ return null;
56
+ }
57
+ try {
58
+ const stat = await fs.stat(dest);
59
+ if (stat.size === 0) {
60
+ await fs.rm(dest, { force: true });
61
+ return null;
62
+ }
63
+ return dest;
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ //# sourceMappingURL=clipboardImage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboardImage.js","sourceRoot":"","sources":["../../../src/util/clipboardImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEtC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAyD,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC;IAE5F,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAEvE,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,WAAW,EAAE;gBACrB,IAAI;gBACJ,iDAAiD;gBACjD,IAAI;gBACJ,yCAAyC,IAAI,yBAAyB;gBACtE,IAAI;gBACJ,sBAAsB;gBACtB,IAAI;gBACJ,iBAAiB;aAClB,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,8EAA8E;YAC9E,0EAA0E;YAC1E,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE;oBAClC,IAAI;oBACJ,mDAAmD;iBACpD,CAAC,CAAwB,CAAC;gBAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpC,IAAI,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;oBAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB;YAChE,CAAC;YAAC,MAAM,CAAC;gBACP,oBAAoB;YACtB,CAAC;YACD,OAAO,IAAI,CAAC,CAAC,qDAAqD;QACpE,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,iDAAiD,IAAI,iDAAiD,IAAI,GAAG,CAAC,CAAC,CAAC;QACzI,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpB,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACnC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * OSC 8 terminal hyperlinks (cmd+click in iTerm2/WezTerm/kitty). On unsupported terminals
3
+ * the plain text is returned unchanged — never emit the escape blindly, some terminals
4
+ * print garbage for unknown OSC sequences.
5
+ */
6
+ export declare function supportsHyperlinks(env?: NodeJS.ProcessEnv): boolean;
7
+ /** Wrap `text` as a clickable link to `url` when the terminal supports it. */
8
+ export declare function hyperlink(text: string, url: string, env?: NodeJS.ProcessEnv): string;
9
+ /** file:// URL for a local path (spaces and unicode escaped). */
10
+ export declare function fileUrl(localPath: string): string;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * OSC 8 terminal hyperlinks (cmd+click in iTerm2/WezTerm/kitty). On unsupported terminals
3
+ * the plain text is returned unchanged — never emit the escape blindly, some terminals
4
+ * print garbage for unknown OSC sequences.
5
+ */
6
+ // Terminals with OSC 8 support, matched against TERM_PROGRAM *and* LC_TERMINAL — inside
7
+ // tmux TERM_PROGRAM becomes 'tmux' while LC_TERMINAL still names the outer terminal
8
+ // (Claude Code's supports-hyperlinks gates on both for exactly this reason).
9
+ const HYPERLINK_TERMINALS = ['iTerm.app', 'iTerm2', 'WezTerm', 'vscode', 'ghostty', 'Hyper', 'kitty', 'alacritty'];
10
+ export function supportsHyperlinks(env = process.env) {
11
+ if (env.FORCE_HYPERLINK && env.FORCE_HYPERLINK !== '0')
12
+ return true;
13
+ if (HYPERLINK_TERMINALS.includes(env.TERM_PROGRAM ?? '') || HYPERLINK_TERMINALS.includes(env.LC_TERMINAL ?? ''))
14
+ return true;
15
+ if (env.KITTY_WINDOW_ID || env.TERM?.includes('kitty'))
16
+ return true; // kitty over SSH loses the env var
17
+ if (env.VTE_VERSION && Number(env.VTE_VERSION) >= 5000)
18
+ return true; // GNOME Terminal ≥ 0.50
19
+ if (env.WT_SESSION)
20
+ return true; // Windows Terminal
21
+ return false;
22
+ }
23
+ /** Wrap `text` as a clickable link to `url` when the terminal supports it. */
24
+ export function hyperlink(text, url, env = process.env) {
25
+ if (!supportsHyperlinks(env))
26
+ return text;
27
+ return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
28
+ }
29
+ /** file:// URL for a local path (spaces and unicode escaped). */
30
+ export function fileUrl(localPath) {
31
+ return `file://${encodeURI(localPath).replace(/#/g, '%23')}`;
32
+ }
33
+ //# sourceMappingURL=hyperlink.js.map