@promptbook/cli 0.113.0-1 → 0.113.0-3

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 (39) hide show
  1. package/README.md +1 -17
  2. package/agents/default/developer.book +3 -0
  3. package/apps/agents-server/src/app/admin/task-manager/TaskManagerClient.tsx +3 -3
  4. package/apps/agents-server/src/app/admin/task-manager/TaskManagerTaskRow.tsx +4 -0
  5. package/apps/agents-server/src/app/admin/task-manager/TaskManagerTasksCard.tsx +5 -5
  6. package/apps/agents-server/src/app/api/page-preview/check/route.ts +20 -5
  7. package/apps/agents-server/src/app/api/page-preview/input/route.ts +161 -0
  8. package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +21 -13
  9. package/apps/agents-server/src/app/api/page-preview/stream/route.ts +81 -0
  10. package/apps/agents-server/src/components/Header/HeaderHomepageLink.tsx +1 -1
  11. package/apps/agents-server/src/utils/chatTasksAdmin.ts +2 -1
  12. package/apps/agents-server/src/utils/createPagePreviewBrowserStream.ts +179 -0
  13. package/apps/agents-server/src/utils/getAdminChatTasksResponse.ts +56 -30
  14. package/apps/agents-server/src/utils/pagePreviewBrowserSessions.ts +234 -0
  15. package/esm/index.es.js +26 -5
  16. package/esm/index.es.js.map +1 -1
  17. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +1 -1
  18. package/esm/src/book-components/Chat/Chat/CitationIframePreview.test.d.ts +2 -0
  19. package/esm/src/version.d.ts +1 -1
  20. package/package.json +1 -1
  21. package/src/book-components/Chat/Chat/Chat.module.css +40 -12
  22. package/src/book-components/Chat/Chat/CitationIframePreview.tsx +108 -156
  23. package/src/book-components/Chat/Chat/createProgressCardChecklistMarkdown.ts +1 -8
  24. package/src/cli/promptbookCli.ts +28 -3
  25. package/src/other/templates/getTemplatesPipelineCollection.ts +738 -761
  26. package/src/version.ts +2 -2
  27. package/src/versions.txt +1 -0
  28. package/umd/index.umd.js +26 -5
  29. package/umd/index.umd.js.map +1 -1
  30. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +1 -1
  31. package/umd/src/book-components/Chat/Chat/CitationIframePreview.test.d.ts +2 -0
  32. package/umd/src/version.d.ts +1 -1
  33. package/apps/agents-server/src/app/api/page-preview/interact/route.ts +0 -179
  34. package/apps/agents-server/src/app/api/page-preview/live/route.ts +0 -67
  35. package/apps/agents-server/src/utils/pagePreview/livePagePreviewSessions.ts +0 -365
  36. package/apps/agents-server/src/utils/pagePreview/resolvePagePreviewRequestUrl.ts +0 -74
  37. package/esm/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +0 -12
  38. package/src/book-components/Chat/utils/livePagePreviewConstants.ts +0 -17
  39. package/umd/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +0 -12
@@ -9,7 +9,7 @@ export type CitationIframePreviewProps = {
9
9
  };
10
10
  /**
11
11
  * Renders a citation URL preview as an iframe when the target page allows embedding,
12
- * or falls back to a live server-side browser preview with an "Open in new tab" link
12
+ * or falls back to a live server-side browser stream with an "Open in new tab" link
13
13
  * when it does not (e.g. X-Frame-Options: DENY / SAMEORIGIN).
14
14
  *
15
15
  * Embedding capability is determined by `GET /api/page-preview/check?url=<url>`.
@@ -0,0 +1,2 @@
1
+ /** @jest-environment jsdom */
2
+ export {};
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
15
15
  export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
16
16
  /**
17
17
  * Represents the version string of the Promptbook engine.
18
- * It follows semantic versioning (e.g., `0.113.0-0`).
18
+ * It follows semantic versioning (e.g., `0.113.0-1`).
19
19
  *
20
20
  * @generated
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/cli",
3
- "version": "0.113.0-1",
3
+ "version": "0.113.0-3",
4
4
  "description": "Promptbook: Create persistent AI agents that turn your company's scattered knowledge into action",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -3322,32 +3322,60 @@
3322
3322
  font-size: 0.9em;
3323
3323
  }
3324
3324
 
3325
- .citationBrowserPreviewFallback {
3325
+ .citationScreenshotFallback {
3326
3326
  width: 100%;
3327
3327
  position: relative;
3328
3328
  display: flex;
3329
3329
  flex-direction: column;
3330
3330
  }
3331
3331
 
3332
- .citationBrowserPreviewImage {
3332
+ .citationScreenshotImage {
3333
3333
  width: 100%;
3334
- aspect-ratio: 16 / 10;
3335
3334
  max-height: 60vh;
3336
3335
  min-height: 400px;
3337
- object-fit: contain;
3336
+ object-fit: cover;
3338
3337
  object-position: top;
3339
3338
  display: block;
3340
- background: #fff;
3341
- cursor: crosshair;
3342
- outline: none;
3343
- touch-action: none;
3344
3339
  }
3345
3340
 
3346
- .citationBrowserPreviewImage:focus-visible {
3347
- box-shadow: inset 0 0 0 3px rgba(0, 112, 243, 0.35);
3341
+ .citationScreenshotLink {
3342
+ display: block;
3343
+ text-align: center;
3344
+ padding: 10px 16px;
3345
+ background: #f0f0f0;
3346
+ color: #333;
3347
+ font-size: 0.9em;
3348
+ font-weight: 500;
3349
+ text-decoration: none;
3350
+ border-top: 1px solid #ddd;
3351
+ transition: background 0.15s;
3352
+ }
3353
+
3354
+ .citationScreenshotLink:hover {
3355
+ background: #e0e0e0;
3356
+ text-decoration: underline;
3357
+ }
3358
+
3359
+ .citationBrowserStreamFallback {
3360
+ width: 100%;
3361
+ position: relative;
3362
+ display: flex;
3363
+ flex-direction: column;
3364
+ }
3365
+
3366
+ .citationBrowserStreamImage {
3367
+ width: 100%;
3368
+ max-height: 60vh;
3369
+ min-height: 400px;
3370
+ object-fit: cover;
3371
+ object-position: top;
3372
+ display: block;
3373
+ cursor: crosshair;
3374
+ user-select: none;
3375
+ touch-action: none;
3348
3376
  }
3349
3377
 
3350
- .citationBrowserPreviewLink {
3378
+ .citationBrowserStreamLink {
3351
3379
  display: block;
3352
3380
  text-align: center;
3353
3381
  padding: 10px 16px;
@@ -3360,7 +3388,7 @@
3360
3388
  transition: background 0.15s;
3361
3389
  }
3362
3390
 
3363
- .citationBrowserPreviewLink:hover {
3391
+ .citationBrowserStreamLink:hover {
3364
3392
  background: #e0e0e0;
3365
3393
  text-decoration: underline;
3366
3394
  }
@@ -1,8 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useEffect, useId, useMemo, useState } from 'react';
4
- import type { KeyboardEvent, MouseEvent, WheelEvent } from 'react';
5
- import { LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT, LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH } from '../utils/livePagePreviewConstants';
3
+ import { useEffect, useMemo, useState, type MouseEvent, type WheelEvent } from 'react';
6
4
  import styles from './Chat.module.css';
7
5
 
8
6
  /**
@@ -20,47 +18,35 @@ export type CitationIframePreviewProps = {
20
18
  *
21
19
  * @private type of `<CitationIframePreview/>`
22
20
  */
23
- type EmbedStatus = 'loading' | 'embed' | 'browser';
21
+ type EmbedStatus = 'loading' | 'embed' | 'browser-stream';
24
22
 
25
23
  /**
26
- * Browser-preview interaction sent to the Agents Server preview route.
24
+ * Prefix shared with the Agents Server page-preview stream route.
27
25
  *
28
- * @private type of `<CitationIframePreview/>`
26
+ * @private component constant of `<CitationIframePreview/>`
29
27
  */
30
- type LivePagePreviewInteraction =
31
- | {
32
- readonly type: 'click';
33
- readonly x: number;
34
- readonly y: number;
35
- }
36
- | {
37
- readonly type: 'wheel';
38
- readonly deltaX: number;
39
- readonly deltaY: number;
40
- }
41
- | {
42
- readonly type: 'keyDown';
43
- readonly key: string;
44
- };
28
+ const BROWSER_PREVIEW_SESSION_ID_PREFIX = 'page-preview-';
45
29
 
46
30
  /**
47
- * Pointer position translated into the live-preview browser viewport.
31
+ * Fallback suffix length used when `crypto.randomUUID` is unavailable.
48
32
  *
49
- * @private type of `<CitationIframePreview/>`
33
+ * @private component constant of `<CitationIframePreview/>`
50
34
  */
51
- type LivePagePreviewPointerPosition = {
52
- readonly x: number;
53
- readonly y: number;
54
- };
35
+ const BROWSER_PREVIEW_RANDOM_SUFFIX_LENGTH = 24;
55
36
 
56
37
  /**
57
- * Keyboard keys that should not be forwarded as standalone browser actions.
38
+ * Pointer ratio resolved from one browser preview interaction.
39
+ *
40
+ * @private type of `<CitationIframePreview/>`
58
41
  */
59
- const LIVE_PAGE_PREVIEW_IGNORED_KEYS = new Set(['Alt', 'CapsLock', 'Control', 'Meta', 'Shift']);
42
+ type BrowserPreviewPointerRatio = {
43
+ readonly xRatio: number;
44
+ readonly yRatio: number;
45
+ };
60
46
 
61
47
  /**
62
48
  * Renders a citation URL preview as an iframe when the target page allows embedding,
63
- * or falls back to a live server-side browser preview with an "Open in new tab" link
49
+ * or falls back to a live server-side browser stream with an "Open in new tab" link
64
50
  * when it does not (e.g. X-Frame-Options: DENY / SAMEORIGIN).
65
51
  *
66
52
  * Embedding capability is determined by `GET /api/page-preview/check?url=<url>`.
@@ -70,26 +56,27 @@ const LIVE_PAGE_PREVIEW_IGNORED_KEYS = new Set(['Alt', 'CapsLock', 'Control', 'M
70
56
  */
71
57
  export function CitationIframePreview({ src, title }: CitationIframePreviewProps) {
72
58
  const [status, setStatus] = useState<EmbedStatus>('loading');
59
+ const browserPreviewSessionId = useMemo(() => createBrowserPreviewSessionId(), [src]);
73
60
 
74
61
  useEffect(() => {
75
- let isCancelled = false;
62
+ let cancelled = false;
76
63
 
77
64
  fetch(`/api/page-preview/check?url=${encodeURIComponent(src)}`)
78
65
  .then((response) => response.json())
79
66
  .then((data: { canEmbed: boolean }) => {
80
- if (!isCancelled) {
81
- setStatus(data.canEmbed ? 'embed' : 'browser');
67
+ if (!cancelled) {
68
+ setStatus(data.canEmbed ? 'embed' : 'browser-stream');
82
69
  }
83
70
  })
84
71
  .catch(() => {
85
72
  // API not available — fall back to iframe (e.g. outside agents-server)
86
- if (!isCancelled) {
73
+ if (!cancelled) {
87
74
  setStatus('embed');
88
75
  }
89
76
  });
90
77
 
91
78
  return () => {
92
- isCancelled = true;
79
+ cancelled = true;
93
80
  };
94
81
  }, [src]);
95
82
 
@@ -97,157 +84,122 @@ export function CitationIframePreview({ src, title }: CitationIframePreviewProps
97
84
  return <div className={styles.citationIframeLoading}>Loading preview…</div>;
98
85
  }
99
86
 
100
- if (status === 'browser') {
101
- return <LiveBrowserPreview src={src} title={title} />;
87
+ if (status === 'browser-stream') {
88
+ return (
89
+ <div className={styles.citationBrowserStreamFallback}>
90
+ <img
91
+ src={`/api/page-preview/stream?url=${encodeURIComponent(src)}&sessionId=${encodeURIComponent(
92
+ browserPreviewSessionId,
93
+ )}`}
94
+ alt={`Live browser preview of ${title}`}
95
+ className={styles.citationBrowserStreamImage}
96
+ draggable={false}
97
+ onClick={(event) => {
98
+ const pointerRatio = resolveBrowserPreviewPointerRatio(event);
99
+ if (!pointerRatio) {
100
+ return;
101
+ }
102
+
103
+ sendBrowserPreviewInput({
104
+ sessionId: browserPreviewSessionId,
105
+ type: 'click',
106
+ ...pointerRatio,
107
+ });
108
+ }}
109
+ onWheel={(event) => {
110
+ event.preventDefault();
111
+ const pointerRatio = resolveBrowserPreviewPointerRatio(event);
112
+ if (!pointerRatio) {
113
+ return;
114
+ }
115
+
116
+ sendBrowserPreviewInput({
117
+ sessionId: browserPreviewSessionId,
118
+ type: 'wheel',
119
+ deltaX: event.deltaX,
120
+ deltaY: event.deltaY,
121
+ ...pointerRatio,
122
+ });
123
+ }}
124
+ />
125
+ <a href={src} target="_blank" rel="noopener noreferrer" className={styles.citationBrowserStreamLink}>
126
+ Open in new tab ↗
127
+ </a>
128
+ </div>
129
+ );
102
130
  }
103
131
 
104
132
  return <iframe src={src} className={styles.citationIframe} title={title} />;
105
133
  }
106
134
 
107
135
  /**
108
- * Renders and controls a streamed browser preview for non-embeddable citation URLs.
136
+ * Creates one client-side browser preview session id.
109
137
  *
110
- * @private component of `<CitationIframePreview/>`
138
+ * @returns Session id accepted by Agents Server page-preview stream routes.
111
139
  */
112
- function LiveBrowserPreview({ src, title }: CitationIframePreviewProps) {
113
- const reactSessionId = useId();
114
- const sessionId = useMemo(() => createLivePagePreviewSessionId(reactSessionId), [reactSessionId]);
115
- const streamSrc = useMemo(
116
- () => `/api/page-preview/live?url=${encodeURIComponent(src)}&sessionId=${encodeURIComponent(sessionId)}`,
117
- [sessionId, src],
118
- );
119
-
120
- const handleClick = useCallback(
121
- (event: MouseEvent<HTMLImageElement>) => {
122
- event.currentTarget.focus();
123
- const position = resolveLivePagePreviewPointerPosition(event);
124
- if (!position) {
125
- return;
126
- }
127
-
128
- void postLivePagePreviewInteraction(sessionId, {
129
- type: 'click',
130
- x: position.x,
131
- y: position.y,
132
- });
133
- },
134
- [sessionId],
135
- );
136
-
137
- const handleWheel = useCallback(
138
- (event: WheelEvent<HTMLImageElement>) => {
139
- event.preventDefault();
140
- void postLivePagePreviewInteraction(sessionId, {
141
- type: 'wheel',
142
- deltaX: event.deltaX,
143
- deltaY: event.deltaY,
144
- });
145
- },
146
- [sessionId],
147
- );
148
-
149
- const handleKeyDown = useCallback(
150
- (event: KeyboardEvent<HTMLImageElement>) => {
151
- if (event.altKey || event.ctrlKey || event.metaKey) {
152
- return;
153
- }
154
-
155
- const key = normalizeLivePagePreviewKey(event.key);
156
- if (!key) {
157
- return;
158
- }
159
-
160
- event.preventDefault();
161
- void postLivePagePreviewInteraction(sessionId, {
162
- type: 'keyDown',
163
- key,
164
- });
165
- },
166
- [sessionId],
167
- );
168
-
169
- return (
170
- <div className={styles.citationBrowserPreviewFallback}>
171
- <img
172
- src={streamSrc}
173
- alt={`Live browser preview of ${title}`}
174
- className={styles.citationBrowserPreviewImage}
175
- onClick={handleClick}
176
- onWheel={handleWheel}
177
- onKeyDown={handleKeyDown}
178
- tabIndex={0}
179
- />
180
- <a href={src} target="_blank" rel="noopener noreferrer" className={styles.citationBrowserPreviewLink}>
181
- Open in new tab ↗
182
- </a>
183
- </div>
184
- );
140
+ function createBrowserPreviewSessionId(): string {
141
+ const randomId =
142
+ globalThis.crypto && 'randomUUID' in globalThis.crypto
143
+ ? globalThis.crypto.randomUUID()
144
+ : createFallbackBrowserPreviewSessionSuffix();
145
+
146
+ return `${BROWSER_PREVIEW_SESSION_ID_PREFIX}${randomId}`.toLowerCase();
185
147
  }
186
148
 
187
149
  /**
188
- * Creates one browser-preview session id from React's hydration-stable id.
150
+ * Creates a sufficiently long fallback session suffix when Web Crypto UUIDs are unavailable.
189
151
  *
190
- * @param reactSessionId - React-generated id for the component instance.
191
- * @returns Session id shared by the stream and interaction endpoints.
152
+ * @returns Lowercase random suffix.
192
153
  */
193
- function createLivePagePreviewSessionId(reactSessionId: string): string {
194
- return `preview-${reactSessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
154
+ function createFallbackBrowserPreviewSessionSuffix(): string {
155
+ let suffix = '';
156
+
157
+ while (suffix.length < BROWSER_PREVIEW_RANDOM_SUFFIX_LENGTH) {
158
+ suffix += Math.random().toString(36).slice(2);
159
+ }
160
+
161
+ return suffix.slice(0, BROWSER_PREVIEW_RANDOM_SUFFIX_LENGTH);
195
162
  }
196
163
 
197
164
  /**
198
- * Converts one pointer event over the stream image to browser viewport coordinates.
165
+ * Resolves pointer coordinates as ratios inside the streamed preview image.
199
166
  *
200
- * @param event - Pointer event from the stream image.
201
- * @returns Browser viewport position or null when the image has no layout box.
167
+ * @param event - Mouse or wheel event fired inside the live preview image.
168
+ * @returns Pointer ratio or `null` when the image has no measurable size.
202
169
  */
203
- function resolveLivePagePreviewPointerPosition(
204
- event: MouseEvent<HTMLImageElement>,
205
- ): LivePagePreviewPointerPosition | null {
170
+ function resolveBrowserPreviewPointerRatio(
171
+ event: MouseEvent<HTMLImageElement> | WheelEvent<HTMLImageElement>,
172
+ ): BrowserPreviewPointerRatio | null {
206
173
  const rect = event.currentTarget.getBoundingClientRect();
207
174
  if (rect.width <= 0 || rect.height <= 0) {
208
175
  return null;
209
176
  }
210
177
 
211
178
  return {
212
- x: ((event.clientX - rect.left) / rect.width) * LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH,
213
- y: ((event.clientY - rect.top) / rect.height) * LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT,
179
+ xRatio: (event.clientX - rect.left) / rect.width,
180
+ yRatio: (event.clientY - rect.top) / rect.height,
214
181
  };
215
182
  }
216
183
 
217
184
  /**
218
- * Normalizes one browser keyboard key before forwarding it to Playwright.
185
+ * Sends one browser-preview interaction to the Agents Server stream session.
219
186
  *
220
- * @param key - Browser KeyboardEvent key.
221
- * @returns Playwright-compatible key name or null when the key should be ignored.
187
+ * @param payload - Browser-preview input payload.
222
188
  */
223
- function normalizeLivePagePreviewKey(key: string): string | null {
224
- if (!key || LIVE_PAGE_PREVIEW_IGNORED_KEYS.has(key)) {
225
- return null;
226
- }
227
-
228
- if (key === ' ') {
229
- return 'Space';
230
- }
231
-
232
- return key;
233
- }
234
-
235
- /**
236
- * Sends one live-preview interaction to the Agents Server.
237
- *
238
- * @param sessionId - Active live-preview session id.
239
- * @param interaction - Interaction to apply to the browser page.
240
- */
241
- async function postLivePagePreviewInteraction(
242
- sessionId: string,
243
- interaction: LivePagePreviewInteraction,
244
- ): Promise<void> {
245
- await fetch('/api/page-preview/interact', {
189
+ function sendBrowserPreviewInput(payload: {
190
+ readonly sessionId: string;
191
+ readonly type: 'click' | 'wheel';
192
+ readonly xRatio: number;
193
+ readonly yRatio: number;
194
+ readonly deltaX?: number;
195
+ readonly deltaY?: number;
196
+ }): void {
197
+ void fetch('/api/page-preview/input', {
246
198
  method: 'POST',
247
- headers: { 'Content-Type': 'application/json' },
248
- body: JSON.stringify({
249
- sessionId,
250
- ...interaction,
251
- }),
199
+ headers: {
200
+ 'Content-Type': 'application/json',
201
+ },
202
+ body: JSON.stringify(payload),
203
+ keepalive: true,
252
204
  }).catch(() => undefined);
253
205
  }
@@ -14,13 +14,6 @@ const PENDING_PROGRESS_MARKER_HTML = '<span data-chat-progress-marker="pending"
14
14
  */
15
15
  const COMPLETED_PROGRESS_MARKER_HTML = '<span data-chat-progress-marker="completed" aria-hidden="true"></span>';
16
16
 
17
- /**
18
- * Human-facing label shown for "now" progress updates.
19
- *
20
- * @private internal helper of `<ChatMessageItem/>`
21
- */
22
- const PROGRESS_NOW_LABEL = "What I'm Doing Now";
23
-
24
17
  /**
25
18
  * Human-facing label shown for "next" progress updates.
26
19
  *
@@ -85,7 +78,7 @@ function createProgressCardMarkdownSections(progressCard: NonNullable<ChatMessag
85
78
  }
86
79
 
87
80
  if (normalizedNow) {
88
- markdownSections.push(`**${PROGRESS_NOW_LABEL}:** ${normalizedNow}`);
81
+ markdownSections.push(normalizedNow);
89
82
  }
90
83
 
91
84
  const itemLines: Array<string> = [];
@@ -48,6 +48,13 @@ const DEPRECATED_TOP_LEVEL_COMMAND_MESSAGES: Readonly<Record<string, string>> =
48
48
  'start-pipelines-server': OLD_PIPELINE_SYSTEM_DEPRECATION_MESSAGE,
49
49
  };
50
50
 
51
+ /**
52
+ * Raw top-level CLI arguments that print the Promptbook version.
53
+ *
54
+ * @private internal constant of `promptbookCli`
55
+ */
56
+ const VERSION_OPTION_ARGUMENTS: ReadonlySet<string> = new Set(['-v', '--version']);
57
+
51
58
  /**
52
59
  * Runs CLI utilities of Promptbook package
53
60
  *
@@ -65,8 +72,15 @@ export async function promptbookCli(): Promise<void> {
65
72
  );
66
73
  }
67
74
 
68
- const cliArguments = process.argv.slice(2);
69
- const isVerbose = cliArguments.some((arg) => arg === '--verbose' || arg === '-v');
75
+ const commandLineArguments = process.argv.slice(2);
76
+ const isVersionRequested = isTopLevelVersionRequested(commandLineArguments);
77
+
78
+ if (isVersionRequested) {
79
+ console.info(PROMPTBOOK_ENGINE_VERSION);
80
+ return process.exit(0);
81
+ }
82
+
83
+ const isVerbose = commandLineArguments.some((argument) => argument === '--verbose' || argument === '-v');
70
84
  // <- TODO: Can be this be done with commander before the commander commands are initialized?
71
85
  if (isVerbose) {
72
86
  console.info(
@@ -104,7 +118,7 @@ export async function promptbookCli(): Promise<void> {
104
118
  // TODO: [🧠] Should it be here or not> $addGlobalOptionsToCommand(program);
105
119
  program.commands.forEach($addGlobalOptionsToCommand);
106
120
 
107
- if (cliArguments.length === 0) {
121
+ if (commandLineArguments.length === 0) {
108
122
  program.outputHelp();
109
123
  return process.exit(0);
110
124
  }
@@ -112,6 +126,17 @@ export async function promptbookCli(): Promise<void> {
112
126
  program.parse(process.argv);
113
127
  }
114
128
 
129
+ /**
130
+ * Checks whether the invocation asks for the root Promptbook CLI version.
131
+ *
132
+ * @private internal utility of `promptbookCli`
133
+ */
134
+ function isTopLevelVersionRequested(commandLineArguments: ReadonlyArray<string>): boolean {
135
+ const firstCommandLineArgument = commandLineArguments[0];
136
+
137
+ return firstCommandLineArgument !== undefined && VERSION_OPTION_ARGUMENTS.has(firstCommandLineArgument);
138
+ }
139
+
115
140
  /**
116
141
  * Adds one deprecation notice to each configured top-level legacy command.
117
142
  */