orizu 0.1.4 → 0.1.6

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.
package/src/index.ts CHANGED
@@ -11,7 +11,13 @@ import { parseDatasetFile } from './file-parser.js'
11
11
  import { streamJsonlRowChunks } from './jsonl-stream.js'
12
12
  import { parseDatasetReference } from './dataset-download.js'
13
13
  import { parseGlobalFlags } from './global-flags.js'
14
- import { authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags } from './http.js'
14
+ import {
15
+ assertSecureTokenTransport,
16
+ authedFetch,
17
+ getBaseUrl,
18
+ resolveLoginBaseUrl,
19
+ setGlobalFlags,
20
+ } from './http.js'
15
21
  import { formatTaskCreateError } from './task-create-error.js'
16
22
  import { LoginResponse } from './types.js'
17
23
 
@@ -163,10 +169,39 @@ function createCodeChallenge(verifier: string): string {
163
169
  return createHash('sha256').update(verifier).digest('base64url')
164
170
  }
165
171
 
172
+ function sanitizeTerminalText(value: unknown): string {
173
+ return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '')
174
+ }
175
+
176
+ function validateBrowserUrl(url: string, expectedOrigin?: string): URL {
177
+ let parsed: URL
178
+ try {
179
+ parsed = new URL(url)
180
+ } catch {
181
+ throw new Error('Server returned an invalid browser URL.')
182
+ }
183
+
184
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
185
+ throw new Error('Server returned an unsupported browser URL scheme.')
186
+ }
187
+
188
+ if (parsed.username || parsed.password) {
189
+ throw new Error('Server returned a browser URL containing credentials.')
190
+ }
191
+
192
+ if (expectedOrigin && parsed.origin !== expectedOrigin) {
193
+ throw new Error('Server returned a browser URL for an unexpected origin.')
194
+ }
195
+
196
+ return parsed
197
+ }
198
+
166
199
  function openInBrowser(url: string) {
200
+ const parsed = validateBrowserUrl(url)
167
201
  const platform = process.platform
202
+ const href = parsed.href
168
203
  if (platform === 'darwin') {
169
- spawn('open', [url], {
204
+ spawn('open', [href], {
170
205
  detached: true,
171
206
  stdio: 'ignore',
172
207
  }).unref()
@@ -174,7 +209,7 @@ function openInBrowser(url: string) {
174
209
  }
175
210
 
176
211
  if (platform === 'win32') {
177
- spawn('cmd', ['/c', 'start', '', url], {
212
+ spawn('rundll32.exe', ['url.dll,FileProtocolHandler', href], {
178
213
  detached: true,
179
214
  stdio: 'ignore',
180
215
  windowsHide: true,
@@ -182,18 +217,28 @@ function openInBrowser(url: string) {
182
217
  return
183
218
  }
184
219
 
185
- spawn('xdg-open', [url], {
220
+ spawn('xdg-open', [href], {
186
221
  detached: true,
187
222
  stdio: 'ignore',
188
223
  }).unref()
189
224
  }
190
225
 
191
226
  function formatTerminalLink(url: string): string {
227
+ const safeUrl = sanitizeTerminalText(url)
228
+ try {
229
+ const parsed = validateBrowserUrl(safeUrl)
230
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
231
+ return safeUrl
232
+ }
233
+ } catch {
234
+ return safeUrl
235
+ }
236
+
192
237
  if (!isInteractiveTerminal()) {
193
- return url
238
+ return safeUrl
194
239
  }
195
240
 
196
- return `\u001B]8;;${url}\u0007${url}\u001B]8;;\u0007`
241
+ return `\u001B]8;;${safeUrl}\u0007${safeUrl}\u001B]8;;\u0007`
197
242
  }
198
243
 
199
244
  async function parseJsonResponse<T>(response: Response, context: string): Promise<T> {
@@ -203,7 +248,7 @@ async function parseJsonResponse<T>(response: Response, context: string): Promis
203
248
  if (!contentType.includes('application/json')) {
204
249
  throw new Error(
205
250
  `${context} returned non-JSON response (status ${response.status}). ` +
206
- `Body preview: ${rawBody.slice(0, 180)}`
251
+ `Body preview: ${sanitizeTerminalText(rawBody.slice(0, 180))}`
207
252
  )
208
253
  }
209
254
 
@@ -212,7 +257,7 @@ async function parseJsonResponse<T>(response: Response, context: string): Promis
212
257
  } catch {
213
258
  throw new Error(
214
259
  `${context} returned invalid JSON (status ${response.status}). ` +
215
- `Body preview: ${rawBody.slice(0, 180)}`
260
+ `Body preview: ${sanitizeTerminalText(rawBody.slice(0, 180))}`
216
261
  )
217
262
  }
218
263
  }
@@ -236,9 +281,9 @@ async function promptSelect<T>(
236
281
  return items[0]
237
282
  }
238
283
 
239
- console.log(`\n${title}`)
284
+ console.log(`\n${sanitizeTerminalText(title)}`)
240
285
  items.forEach((item, index) => {
241
- console.log(` ${index + 1}. ${label(item, index)}`)
286
+ console.log(` ${index + 1}. ${sanitizeTerminalText(label(item, index))}`)
242
287
  })
243
288
 
244
289
  const rl = createInterface({ input, output })
@@ -354,7 +399,7 @@ async function resolveProjectSlug(projectArg: string | null): Promise<string> {
354
399
 
355
400
  const matchedTeam = teams.find(team => team.slug === teamSlug)
356
401
  if (!matchedTeam) {
357
- console.error(`Team '${teamSlug}' not found in your accessible teams.`)
402
+ console.error(`Team '${sanitizeTerminalText(teamSlug)}' not found in your accessible teams.`)
358
403
  const selectedTeam = await promptSelect(
359
404
  'Select a team',
360
405
  teams,
@@ -375,7 +420,7 @@ async function resolveProjectSlug(projectArg: string | null): Promise<string> {
375
420
  const matchedProject = projects.find(project => project.slug === projectSlug)
376
421
 
377
422
  if (!matchedProject) {
378
- console.error(`Project '${projectSlug}' not found in team '${matchedTeam.slug}'.`)
423
+ console.error(`Project '${sanitizeTerminalText(projectSlug)}' not found in team '${sanitizeTerminalText(matchedTeam.slug)}'.`)
379
424
  const selectedProject = await promptSelect(
380
425
  `Select a project in ${matchedTeam.slug}`,
381
426
  projects,
@@ -461,9 +506,9 @@ function printTeams(teams: Team[]) {
461
506
  }
462
507
 
463
508
  const rows = teams.map(team => ({
464
- slug: team.slug,
465
- name: team.name || '-',
466
- role: team.role || '-',
509
+ slug: sanitizeTerminalText(team.slug),
510
+ name: sanitizeTerminalText(team.name || '-'),
511
+ role: sanitizeTerminalText(team.role || '-'),
467
512
  }))
468
513
 
469
514
  const slugWidth = Math.max('TEAM SLUG'.length, ...rows.map(row => row.slug.length))
@@ -489,9 +534,9 @@ function printProjects(projects: Project[]) {
489
534
  }
490
535
 
491
536
  const rows = projects.map(project => ({
492
- project: `${project.teamSlug}/${project.slug}`,
493
- name: project.name || '-',
494
- role: project.role || '-',
537
+ project: sanitizeTerminalText(`${project.teamSlug}/${project.slug}`),
538
+ name: sanitizeTerminalText(project.name || '-'),
539
+ role: sanitizeTerminalText(project.role || '-'),
495
540
  }))
496
541
 
497
542
  const projectWidth = Math.max('TEAM/PROJECT'.length, ...rows.map(row => row.project.length))
@@ -519,11 +564,11 @@ function printTasks(tasks: Task[]) {
519
564
  }
520
565
 
521
566
  const rows = tasks.map(task => ({
522
- id: task.id,
523
- name: task.title || '-',
524
- status: task.status || '-',
567
+ id: sanitizeTerminalText(task.id),
568
+ name: sanitizeTerminalText(task.title || '-'),
569
+ status: sanitizeTerminalText(task.status || '-'),
525
570
  project: task.teamSlug && task.projectSlug
526
- ? `${task.teamSlug}/${task.projectSlug}`
571
+ ? sanitizeTerminalText(`${task.teamSlug}/${task.projectSlug}`)
527
572
  : 'unknown-project',
528
573
  }))
529
574
 
@@ -552,8 +597,8 @@ function printApps(apps: AppSummary[]) {
552
597
  }
553
598
 
554
599
  const rows = apps.map(app => ({
555
- id: app.id,
556
- name: app.name || '-',
600
+ id: sanitizeTerminalText(app.id),
601
+ name: sanitizeTerminalText(app.name || '-'),
557
602
  version: `v${app.currentVersionNum || 1}`,
558
603
  }))
559
604
 
@@ -576,10 +621,10 @@ function printTeamMembers(members: TeamMember[]) {
576
621
  }
577
622
 
578
623
  const rows = members.map(member => ({
579
- id: member.id,
580
- userId: member.user_id || '-',
581
- email: member.email || '-',
582
- role: member.role || '-',
624
+ id: sanitizeTerminalText(member.id),
625
+ userId: sanitizeTerminalText(member.user_id || '-'),
626
+ email: sanitizeTerminalText(member.email || '-'),
627
+ role: sanitizeTerminalText(member.role || '-'),
583
628
  }))
584
629
 
585
630
  const idWidth = Math.max('MEMBER ID'.length, ...rows.map(row => row.id.length))
@@ -602,9 +647,9 @@ function printTeamMembers(members: TeamMember[]) {
602
647
 
603
648
  function printTaskStatusSummary(data: TaskStatusPayload) {
604
649
  const task = data.task
605
- console.log(`Task: ${task.title} (${task.id})`)
606
- console.log(`Status: ${task.status}`)
607
- console.log(`Project: ${task.teamSlug}/${task.projectSlug}`)
650
+ console.log(`Task: ${sanitizeTerminalText(task.title)} (${sanitizeTerminalText(task.id)})`)
651
+ console.log(`Status: ${sanitizeTerminalText(task.status)}`)
652
+ console.log(`Project: ${sanitizeTerminalText(`${task.teamSlug}/${task.projectSlug}`)}`)
608
653
  console.log(`Progress: ${task.progressPercentage}%`)
609
654
  console.log(`Counts: completed=${task.counts.completed}, in_progress=${task.counts.inProgress}, pending=${task.counts.pending}, skipped=${task.counts.skipped}`)
610
655
  console.log(`Required assignments: ${task.totalRequiredAssignments} (${task.datasetRowCount} rows x ${task.requiredAssignmentsPerRow})`)
@@ -613,7 +658,7 @@ function printTaskStatusSummary(data: TaskStatusPayload) {
613
658
  console.log('\nAssignees')
614
659
  task.assignees.forEach(assignee => {
615
660
  console.log(
616
- ` ${assignee.email}: total=${assignee.total}, completed=${assignee.completed}, in_progress=${assignee.inProgress}, pending=${assignee.pending}, skipped=${assignee.skipped}`
661
+ ` ${sanitizeTerminalText(assignee.email)}: total=${assignee.total}, completed=${assignee.completed}, in_progress=${assignee.inProgress}, pending=${assignee.pending}, skipped=${assignee.skipped}`
617
662
  )
618
663
  })
619
664
  }
@@ -748,6 +793,7 @@ function renderCliAuthBrowserPage(status: 'success' | 'error'): string {
748
793
 
749
794
  async function login() {
750
795
  const baseUrl = resolveLoginBaseUrl()
796
+ assertSecureTokenTransport(baseUrl)
751
797
  const codeVerifier = createCodeVerifier()
752
798
  const codeChallenge = createCodeChallenge(codeVerifier)
753
799
  const callbackPort = resolveAuthCallbackPort()
@@ -807,8 +853,9 @@ async function login() {
807
853
  response,
808
854
  'CLI auth start'
809
855
  )
810
- console.log(`Opening browser for login: ${authorizeUrl}`)
811
- openInBrowser(authorizeUrl)
856
+ const safeAuthorizeUrl = validateBrowserUrl(authorizeUrl, baseUrl).href
857
+ console.log(`Opening browser for login: ${sanitizeTerminalText(safeAuthorizeUrl)}`)
858
+ openInBrowser(safeAuthorizeUrl)
812
859
  } catch (error) {
813
860
  server.close()
814
861
  reject(error)
@@ -834,7 +881,7 @@ async function login() {
834
881
  expiresAt: loginData.expiresAt,
835
882
  })
836
883
 
837
- console.log(`Logged in as ${loginData.user.email ?? loginData.user.id}`)
884
+ console.log(`Logged in as ${sanitizeTerminalText(loginData.user.email ?? loginData.user.id)}`)
838
885
  }
839
886
 
840
887
  async function whoami() {
@@ -844,28 +891,42 @@ async function whoami() {
844
891
  }
845
892
 
846
893
  const data = await response.json() as { user: { id: string; email: string | null } }
847
- console.log(data.user.email ?? data.user.id)
894
+ console.log(sanitizeTerminalText(data.user.email ?? data.user.id))
848
895
  }
849
896
 
850
897
  async function logout() {
851
898
  const baseUrl = getBaseUrl()
852
899
  const credentials = getServerCredentials(baseUrl)
853
900
  if (!credentials) {
854
- console.log(`Already logged out for ${baseUrl}.`)
901
+ console.log(`Already logged out for ${sanitizeTerminalText(baseUrl)}.`)
855
902
  return
856
903
  }
857
904
 
858
- await fetch(`${baseUrl}/api/cli/auth/logout`, {
859
- method: 'POST',
860
- headers: {
861
- 'Content-Type': 'application/json',
862
- Authorization: `Bearer ${credentials.accessToken}`,
863
- },
864
- body: JSON.stringify({ refreshToken: credentials.refreshToken }),
865
- }).catch(() => undefined)
905
+ let remoteLogoutError: string | null = null
906
+ try {
907
+ assertSecureTokenTransport(baseUrl)
908
+ const response = await fetch(`${baseUrl}/api/cli/auth/logout`, {
909
+ method: 'POST',
910
+ headers: {
911
+ 'Content-Type': 'application/json',
912
+ Authorization: `Bearer ${credentials.accessToken}`,
913
+ },
914
+ body: JSON.stringify({ refreshToken: credentials.refreshToken }),
915
+ })
916
+
917
+ if (!response.ok) {
918
+ remoteLogoutError = sanitizeTerminalText(await response.text()).slice(0, 180)
919
+ }
920
+ } catch (error) {
921
+ const message = error instanceof Error ? error.message : String(error)
922
+ remoteLogoutError = sanitizeTerminalText(message).slice(0, 180)
923
+ }
866
924
 
867
925
  clearServerCredentials(baseUrl)
868
- console.log(`Logged out from ${baseUrl}.`)
926
+ if (remoteLogoutError) {
927
+ console.warn(`Warning: remote logout failed: ${remoteLogoutError}`)
928
+ }
929
+ console.log(`Logged out from ${sanitizeTerminalText(baseUrl)}.`)
869
930
  }
870
931
 
871
932
  async function listTeams() {
@@ -914,7 +975,7 @@ async function createTeam() {
914
975
  }
915
976
 
916
977
  const data = await parseJsonResponse<{ team: Team }>(response, 'Team create')
917
- console.log(`Created team: ${data.team.name} (${data.team.slug})`)
978
+ console.log(`Created team: ${sanitizeTerminalText(data.team.name)} (${sanitizeTerminalText(data.team.slug)})`)
918
979
  }
919
980
 
920
981
  async function listProjects() {
@@ -955,7 +1016,7 @@ async function createProject() {
955
1016
  const data = await parseJsonResponse<{
956
1017
  project: { id: string; name: string; slug: string; teamSlug: string }
957
1018
  }>(response, 'Project create')
958
- console.log(`Created project ${data.project.teamSlug}/${data.project.slug}`)
1019
+ console.log(`Created project ${sanitizeTerminalText(`${data.project.teamSlug}/${data.project.slug}`)}`)
959
1020
  }
960
1021
 
961
1022
  async function listTasks() {
@@ -1032,9 +1093,9 @@ async function createAppFromFile() {
1032
1093
  app: { id: string; name: string; versionNum: number; componentName?: string }
1033
1094
  warnings?: string[]
1034
1095
  }>(response, 'App create')
1035
- console.log(`Created app ${data.app.name} (${data.app.id}) v${data.app.versionNum}`)
1096
+ console.log(`Created app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) v${data.app.versionNum}`)
1036
1097
  if (data.warnings?.length) {
1037
- console.log(`Warnings: ${data.warnings.join('; ')}`)
1098
+ console.log(`Warnings: ${sanitizeTerminalText(data.warnings.join('; '))}`)
1038
1099
  }
1039
1100
  }
1040
1101
 
@@ -1077,9 +1138,9 @@ async function updateAppFromFile() {
1077
1138
  app: { id: string; name: string; versionNum: number; componentName?: string }
1078
1139
  warnings?: string[]
1079
1140
  }>(response, 'App update')
1080
- console.log(`Updated app ${data.app.name} (${data.app.id}) to v${data.app.versionNum}`)
1141
+ console.log(`Updated app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) to v${data.app.versionNum}`)
1081
1142
  if (data.warnings?.length) {
1082
- console.log(`Warnings: ${data.warnings.join('; ')}`)
1143
+ console.log(`Warnings: ${sanitizeTerminalText(data.warnings.join('; '))}`)
1083
1144
  }
1084
1145
  }
1085
1146
 
@@ -1125,7 +1186,7 @@ async function linkAppDataset() {
1125
1186
  }>(response, 'App link dataset')
1126
1187
 
1127
1188
  console.log(
1128
- `Linked dataset ${data.linkedDataset.name} (${data.linkedDataset.id}) to app ${data.app.name} (${data.app.id}) version ${data.versionNum}`
1189
+ `Linked dataset ${sanitizeTerminalText(data.linkedDataset.name)} (${sanitizeTerminalText(data.linkedDataset.id)}) to app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) version ${data.versionNum}`
1129
1190
  )
1130
1191
  }
1131
1192
 
@@ -1222,14 +1283,14 @@ async function createTask() {
1222
1283
  const baseUrl = getBaseUrl()
1223
1284
  const taskUrl = `${baseUrl}/d/${projectSlug}/tasks/${data.task.id}`
1224
1285
  console.log(
1225
- `Created task ${data.task.title} (${data.task.id}) [${data.task.status}]` +
1226
- `\n Task ID: ${data.task.id}` +
1227
- `\n Dataset ID: ${datasetId}` +
1228
- `\n Version: v${data.task.versionNum} (${data.task.versionId})` +
1286
+ `Created task ${sanitizeTerminalText(data.task.title)} (${sanitizeTerminalText(data.task.id)}) [${sanitizeTerminalText(data.task.status)}]` +
1287
+ `\n Task ID: ${sanitizeTerminalText(data.task.id)}` +
1288
+ `\n Dataset ID: ${sanitizeTerminalText(datasetId)}` +
1289
+ `\n Version: v${data.task.versionNum} (${sanitizeTerminalText(data.task.versionId)})` +
1229
1290
  `\n Labels/row: ${data.task.requiredAssignmentsPerRow}` +
1230
1291
  `\n Assignments: ${data.assignmentsCreated}` +
1231
- (data.warning ? `\n Warning: ${data.warning}` : '') +
1232
- `\n URL: ${taskUrl}`
1292
+ (data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : '') +
1293
+ `\n URL: ${sanitizeTerminalText(taskUrl)}`
1233
1294
  )
1234
1295
  }
1235
1296
 
@@ -1319,7 +1380,7 @@ async function updateTaskStatus(targetStatus: 'paused' | 'active') {
1319
1380
 
1320
1381
  const data = await parseJsonResponse<{ task: { id: string; status: string } }>(response, 'Task status update')
1321
1382
  const action = targetStatus === 'paused' ? 'Paused' : 'Unpaused'
1322
- console.log(`${action} task ${data.task.id} [${data.task.status}]`)
1383
+ console.log(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`)
1323
1384
  }
1324
1385
 
1325
1386
  interface AppDetailPayload {
@@ -1373,10 +1434,10 @@ async function appDetail() {
1373
1434
  return
1374
1435
  }
1375
1436
 
1376
- console.log(`App: ${detail.name} (${detail.id})`)
1377
- console.log(` Project: ${detail.teamSlug}/${detail.projectSlug}`)
1437
+ console.log(`App: ${sanitizeTerminalText(detail.name)} (${sanitizeTerminalText(detail.id)})`)
1438
+ console.log(` Project: ${sanitizeTerminalText(`${detail.teamSlug}/${detail.projectSlug}`)}`)
1378
1439
  if (detail.currentVersion) {
1379
- console.log(` Current version: v${detail.currentVersion.versionNum} (${detail.currentVersion.versionId})`)
1440
+ console.log(` Current version: v${detail.currentVersion.versionNum} (${sanitizeTerminalText(detail.currentVersion.versionId)})`)
1380
1441
  console.log(` Input schema: ${detail.currentVersion.inputJsonSchema ? 'defined' : 'none'}`)
1381
1442
  console.log(` Output schema: ${detail.currentVersion.outputJsonSchema ? 'defined' : 'none'}`)
1382
1443
  } else {
@@ -1384,10 +1445,10 @@ async function appDetail() {
1384
1445
  }
1385
1446
  console.log(` Compatible datasets: ${detail.compatibleDatasetsCount}/${detail.totalDatasetsCount}`)
1386
1447
  if (detail.createdByEmail) {
1387
- console.log(` Created by: ${detail.createdByName || detail.createdByEmail}`)
1448
+ console.log(` Created by: ${sanitizeTerminalText(detail.createdByName || detail.createdByEmail)}`)
1388
1449
  }
1389
- console.log(` Created: ${detail.createdAt}`)
1390
- console.log(` Updated: ${detail.updatedAt}`)
1450
+ console.log(` Created: ${sanitizeTerminalText(detail.createdAt)}`)
1451
+ console.log(` Updated: ${sanitizeTerminalText(detail.updatedAt)}`)
1391
1452
  }
1392
1453
 
1393
1454
  async function listTeamMembers() {
@@ -1412,7 +1473,7 @@ async function addTeamMember() {
1412
1473
  throw new Error(`Failed to add team member: ${await response.text()}`)
1413
1474
  }
1414
1475
  const data = await parseJsonResponse<{ member: TeamMember }>(response, 'Team member add')
1415
- console.log(`Added team member ${data.member.email} (${data.member.id})`)
1476
+ console.log(`Added team member ${sanitizeTerminalText(data.member.email)} (${sanitizeTerminalText(data.member.id)})`)
1416
1477
  }
1417
1478
 
1418
1479
  async function removeTeamMember() {
@@ -1435,7 +1496,7 @@ async function removeTeamMember() {
1435
1496
  if (!response.ok) {
1436
1497
  throw new Error(`Failed to remove team member: ${await response.text()}`)
1437
1498
  }
1438
- console.log(`Removed team member ${member.email}`)
1499
+ console.log(`Removed team member ${sanitizeTerminalText(member.email)}`)
1439
1500
  }
1440
1501
 
1441
1502
  async function changeTeamMemberRole() {
@@ -1469,7 +1530,7 @@ async function changeTeamMemberRole() {
1469
1530
  throw new Error(`Failed to update member role: ${await response.text()}`)
1470
1531
  }
1471
1532
 
1472
- console.log(`Updated ${member.email} role to ${role}`)
1533
+ console.log(`Updated ${sanitizeTerminalText(member.email)} role to ${sanitizeTerminalText(role)}`)
1473
1534
  }
1474
1535
 
1475
1536
  async function createDatasetFromRows(
@@ -1521,7 +1582,7 @@ async function uploadJsonlDatasetInChunks(
1521
1582
 
1522
1583
  throw new Error(
1523
1584
  `Upload stopped while reading the next JSONL chunk: ${message}\n` +
1524
- `Dataset ${dataset.name} (${dataset.id}) was created and ${totalUploaded} rows were uploaded. ` +
1585
+ `Dataset ${sanitizeTerminalText(dataset.name)} (${sanitizeTerminalText(dataset.id)}) was created and ${totalUploaded} rows were uploaded. ` +
1525
1586
  `Fix the file, remove the first ${totalUploaded} rows, and run ` +
1526
1587
  `orizu datasets append --dataset ${dataset.id} --file <remaining-file>.`
1527
1588
  )
@@ -1568,7 +1629,7 @@ async function uploadJsonlDatasetInChunks(
1568
1629
  throw new Error('Dataset file contains no rows')
1569
1630
  }
1570
1631
 
1571
- console.log(`Uploaded dataset ${dataset.name} (${dataset.id}) with ${dataset.rowCount} rows.`)
1632
+ console.log(`Uploaded dataset ${sanitizeTerminalText(dataset.name)} (${sanitizeTerminalText(dataset.id)}) with ${dataset.rowCount} rows.`)
1572
1633
  if (dataset.url) {
1573
1634
  console.log(`View dataset: ${formatTerminalLink(dataset.url)}`)
1574
1635
  }
@@ -1595,7 +1656,7 @@ async function uploadDataset() {
1595
1656
  const { rows, sourceType } = parseDatasetFile(file)
1596
1657
  const data = await createDatasetFromRows(project, datasetName, sourceType, rows)
1597
1658
 
1598
- console.log(`Uploaded dataset ${data.dataset.name} (${data.dataset.id}) with ${data.dataset.rowCount} rows.`)
1659
+ console.log(`Uploaded dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) with ${data.dataset.rowCount} rows.`)
1599
1660
  if (data.dataset.url) {
1600
1661
  console.log(`View dataset: ${formatTerminalLink(data.dataset.url)}`)
1601
1662
  }
@@ -1647,7 +1708,7 @@ async function downloadDataset() {
1647
1708
  const bytes = new Uint8Array(await response.arrayBuffer())
1648
1709
  writeFileSync(filename, bytes)
1649
1710
 
1650
- console.log(`Saved dataset ${datasetId} (${format.toUpperCase()}) to ${filename}`)
1711
+ console.log(`Saved dataset ${sanitizeTerminalText(datasetId)} (${format.toUpperCase()}) to ${sanitizeTerminalText(filename)}`)
1651
1712
  }
1652
1713
 
1653
1714
  const MAX_INPUT_FILE_SIZE_BYTES = 50 * 1024 * 1024 // 50 MB
@@ -1719,7 +1780,7 @@ async function appendJsonlDatasetRowsInChunks(datasetId: string, file: string) {
1719
1780
  }
1720
1781
 
1721
1782
  console.log(
1722
- `Appended ${totalAppended} rows to dataset ${lastResult.dataset.name} (${lastResult.dataset.id}). New row count: ${lastResult.dataset.rowCount}`
1783
+ `Appended ${totalAppended} rows to dataset ${sanitizeTerminalText(lastResult.dataset.name)} (${sanitizeTerminalText(lastResult.dataset.id)}). New row count: ${lastResult.dataset.rowCount}`
1723
1784
  )
1724
1785
  }
1725
1786
 
@@ -1782,7 +1843,7 @@ async function appendDatasetRows() {
1782
1843
  if (rows.length <= APPEND_CHUNK_SIZE_ROWS) {
1783
1844
  const data = await appendChunk(datasetId, rows)
1784
1845
  console.log(
1785
- `Appended ${data.appendedCount} rows to dataset ${data.dataset.name} (${data.dataset.id}). New row count: ${data.dataset.rowCount}`
1846
+ `Appended ${data.appendedCount} rows to dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). New row count: ${data.dataset.rowCount}`
1786
1847
  )
1787
1848
  return
1788
1849
  }
@@ -1813,7 +1874,7 @@ async function appendDatasetRows() {
1813
1874
 
1814
1875
  if (lastResult) {
1815
1876
  console.log(
1816
- `Appended ${totalAppended} rows to dataset ${lastResult.dataset.name} (${lastResult.dataset.id}). New row count: ${lastResult.dataset.rowCount}`
1877
+ `Appended ${totalAppended} rows to dataset ${sanitizeTerminalText(lastResult.dataset.name)} (${sanitizeTerminalText(lastResult.dataset.id)}). New row count: ${lastResult.dataset.rowCount}`
1817
1878
  )
1818
1879
  }
1819
1880
  }
@@ -1874,7 +1935,7 @@ async function editDatasetRows() {
1874
1935
  }>(response, 'Dataset edit rows')
1875
1936
 
1876
1937
  console.log(
1877
- `Updated ${data.updatedCount} rows in dataset ${data.dataset.name} (${data.dataset.id}). Current row count: ${data.dataset.rowCount}`
1938
+ `Updated ${data.updatedCount} rows in dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). Current row count: ${data.dataset.rowCount}`
1878
1939
  )
1879
1940
  }
1880
1941
 
@@ -1913,7 +1974,7 @@ async function deleteDatasetRows() {
1913
1974
  }>(response, 'Dataset delete rows')
1914
1975
 
1915
1976
  console.log(
1916
- `Deleted ${data.deletedCount} rows from dataset ${data.dataset.name} (${data.dataset.id}). New row count: ${data.dataset.rowCount}`
1977
+ `Deleted ${data.deletedCount} rows from dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). New row count: ${data.dataset.rowCount}`
1917
1978
  )
1918
1979
  }
1919
1980
 
@@ -1951,7 +2012,7 @@ async function lockDataset() {
1951
2012
  }>(response, 'Dataset lock')
1952
2013
 
1953
2014
  console.log(
1954
- `Locked dataset ${data.dataset.name} (${data.dataset.id}) at ${data.dataset.lockedAt}. Row count: ${data.dataset.rowCount}`
2015
+ `Locked dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) at ${sanitizeTerminalText(data.dataset.lockedAt)}. Row count: ${data.dataset.rowCount}`
1955
2016
  )
1956
2017
  }
1957
2018
 
@@ -1988,7 +2049,7 @@ async function cloneDataset() {
1988
2049
  }>(response, 'Dataset clone')
1989
2050
 
1990
2051
  console.log(
1991
- `Cloned dataset ${data.dataset.parentDatasetId} -> ${data.dataset.name} (${data.dataset.id}). Row count: ${data.dataset.rowCount}`
2052
+ `Cloned dataset ${sanitizeTerminalText(data.dataset.parentDatasetId)} -> ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). Row count: ${data.dataset.rowCount}`
1992
2053
  )
1993
2054
  }
1994
2055
 
@@ -2018,7 +2079,7 @@ async function downloadAnnotations() {
2018
2079
  const bytes = new Uint8Array(await response.arrayBuffer())
2019
2080
  writeFileSync(filename, bytes)
2020
2081
 
2021
- console.log(`Saved ${format.toUpperCase()} export to ${filename}`)
2082
+ console.log(`Saved ${format.toUpperCase()} export to ${sanitizeTerminalText(filename)}`)
2022
2083
  }
2023
2084
 
2024
2085
  async function main() {
@@ -2187,6 +2248,6 @@ async function main() {
2187
2248
  }
2188
2249
 
2189
2250
  main().catch(error => {
2190
- console.error(error instanceof Error ? error.message : 'Unknown error')
2251
+ console.error(sanitizeTerminalText(error instanceof Error ? error.message : 'Unknown error'))
2191
2252
  process.exit(1)
2192
2253
  })