usertold 1.18.0 → 1.20.0

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 (3) hide show
  1. package/README.md +3 -4
  2. package/package.json +1 -1
  3. package/usertold +1402 -680
package/usertold CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import process$2 from 'node:process';
3
- import { readFile, rm, mkdir, writeFile } from 'node:fs/promises';
3
+ import { readFile, mkdir, writeFile, rm } from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path, { extname } from 'node:path';
6
6
  import { randomBytes, createHash } from 'node:crypto';
@@ -9,6 +9,34 @@ import { spawn } from 'node:child_process';
9
9
  import { setTimeout as setTimeout$1 } from 'node:timers/promises';
10
10
  import { createInterface } from 'node:readline';
11
11
 
12
+ const CANONICAL_WIDGET_EMBED_ORIGIN = 'https://usertold.ai';
13
+ function resolveWidgetEmbedOriginForEnvironment(env) {
14
+ switch(env){
15
+ case 'production':
16
+ return CANONICAL_WIDGET_EMBED_ORIGIN;
17
+ case 'stage':
18
+ case 'staging':
19
+ return 'https://usertold-stage.krasnoperov.me';
20
+ case 'local':
21
+ return 'https://local.krasnoperov.me:3001';
22
+ default:
23
+ throw new Error(`Unknown environment "${env}". Valid options: production, stage, local`);
24
+ }
25
+ }
26
+ function buildWidgetScriptSrc(origin) {
27
+ return `${origin.replace(/\/+$/, '')}/v1/widget.js`;
28
+ }
29
+ function buildWidgetEmbedSnippet(params) {
30
+ const attrs = [
31
+ params.async ? 'async' : null,
32
+ `src="${buildWidgetScriptSrc(params.origin)}"`,
33
+ `data-project-key="${params.projectKey}"`,
34
+ params.screenerId ? `data-screener-id="${params.screenerId}"` : null,
35
+ params.studyId ? `data-study-id="${params.studyId}"` : null
36
+ ].filter(Boolean);
37
+ return `<script ${attrs.join(' ')}></script>`;
38
+ }
39
+
12
40
  const DEFAULT_ENVIRONMENT = 'production';
13
41
  const CONFIG_DIR_NAME = 'usertold-cli';
14
42
  const CONFIG_FILE_NAME = 'config.json';
@@ -55,6 +83,27 @@ async function loadStoredConfig(environment) {
55
83
  const env = environment || DEFAULT_ENVIRONMENT;
56
84
  return multiConfig.configs[env] || null;
57
85
  }
86
+ async function loadCurrentProjectRef(environment) {
87
+ const multiConfig = await loadMultiEnvConfig();
88
+ if (!multiConfig) return null;
89
+ const env = environment || DEFAULT_ENVIRONMENT;
90
+ const currentProjectRef = multiConfig.preferences?.[env]?.currentProjectRef;
91
+ return typeof currentProjectRef === 'string' && currentProjectRef.length > 0 ? currentProjectRef : null;
92
+ }
93
+ async function saveCurrentProjectRef(environment, projectRef) {
94
+ const multiConfig = await loadMultiEnvConfig() || {
95
+ configs: {}
96
+ };
97
+ const preferences = multiConfig.preferences ?? {};
98
+ preferences[environment] = {
99
+ ...preferences[environment],
100
+ currentProjectRef: projectRef
101
+ };
102
+ await saveMultiEnvConfig({
103
+ ...multiConfig,
104
+ preferences
105
+ });
106
+ }
58
107
  async function removeConfig(environment) {
59
108
  if (!environment) {
60
109
  // Remove all configs
@@ -84,17 +133,7 @@ function resolveBaseUrl(env) {
84
133
  if (process.env.USERTOLD_API_BASE) {
85
134
  return process.env.USERTOLD_API_BASE.replace(/\/$/, '');
86
135
  }
87
- switch(env){
88
- case 'production':
89
- return 'https://usertold.ai';
90
- case 'stage':
91
- case 'staging':
92
- return 'https://usertold-stage.krasnoperov.me';
93
- case 'local':
94
- return 'https://local.krasnoperov.me:3001';
95
- default:
96
- throw new Error(`Unknown environment "${env}". Valid options: production, stage, local`);
97
- }
136
+ return resolveWidgetEmbedOriginForEnvironment(env);
98
137
  }
99
138
 
100
139
  const EXIT_ERROR = 1;
@@ -533,6 +572,11 @@ function isCloudflare1010Response(response) {
533
572
  return text.includes('access denied') && text.includes("browser's signature");
534
573
  }
535
574
 
575
+ const SENSITIVE_OUTPUT_KEYS = new Set([
576
+ 'secret_key',
577
+ 'wrapped_dek',
578
+ 'settings_json'
579
+ ]);
536
580
  /**
537
581
  * Returns true when output should be JSON:
538
582
  * - --json flag
@@ -545,35 +589,36 @@ function isCloudflare1010Response(response) {
545
589
  return false;
546
590
  }
547
591
  function printOutput(data, parsed) {
592
+ const safeData = redactSensitiveOutput(data);
548
593
  if (isJsonOutput(parsed)) {
549
- console.log(JSON.stringify(data, null, 2));
594
+ console.log(JSON.stringify(safeData, null, 2));
550
595
  return;
551
596
  }
552
- if (Array.isArray(data)) {
553
- printTable(data);
597
+ if (Array.isArray(safeData)) {
598
+ printTable(safeData);
554
599
  return;
555
600
  }
556
- if (isRecord(data)) {
557
- const arrayEntry = findFirstArrayEntry(data);
601
+ if (isRecord(safeData)) {
602
+ const arrayEntry = findFirstArrayEntry(safeData);
558
603
  if (arrayEntry) {
559
604
  const [key, value] = arrayEntry;
560
605
  console.log(`${key}:`);
561
606
  printTable(value);
562
- const meta = Object.fromEntries(Object.entries(data).filter(([entryKey])=>entryKey !== key));
607
+ const meta = Object.fromEntries(Object.entries(safeData).filter(([entryKey])=>entryKey !== key));
563
608
  if (Object.keys(meta).length > 0) {
564
609
  console.log('');
565
610
  printObject(meta);
566
611
  }
567
612
  return;
568
613
  }
569
- printObject(data);
614
+ printObject(safeData);
570
615
  return;
571
616
  }
572
- if (data === null || data === undefined) {
617
+ if (safeData === null || safeData === undefined) {
573
618
  console.log('(empty)');
574
619
  return;
575
620
  }
576
- console.log(String(data));
621
+ console.log(String(safeData));
577
622
  }
578
623
  function printTable(rows) {
579
624
  if (rows.length === 0) {
@@ -647,6 +692,22 @@ function truncate(value, max) {
647
692
  function isRecord(value) {
648
693
  return typeof value === 'object' && value !== null && !Array.isArray(value);
649
694
  }
695
+ function redactSensitiveOutput(value) {
696
+ if (Array.isArray(value)) {
697
+ return value.map(redactSensitiveOutput);
698
+ }
699
+ if (!isRecord(value)) {
700
+ return value;
701
+ }
702
+ const redacted = {};
703
+ for (const [key, child] of Object.entries(value)){
704
+ if (SENSITIVE_OUTPUT_KEYS.has(key)) {
705
+ continue;
706
+ }
707
+ redacted[key] = redactSensitiveOutput(child);
708
+ }
709
+ return redacted;
710
+ }
650
711
 
651
712
  const DEFAULT_CLIENT_ID = 'usertold-cli';
652
713
  const DEFAULT_REDIRECT_PORT = 8765;
@@ -655,7 +716,7 @@ const AUTH_SCOPES = 'openid profile email';
655
716
  * Detect how the CLI was invoked and return the appropriate command string.
656
717
  * Examples:
657
718
  * - "usertold" when installed globally or run as bundled binary
658
- * - "npm run cli" when run via package.json script (local dev)
719
+ * - "pnpm run cli" when run via package.json script (local dev)
659
720
  * - "npx usertold" when run via npx
660
721
  */ function detectCliCommand() {
661
722
  const argv = process.argv;
@@ -663,9 +724,9 @@ const AUTH_SCOPES = 'openid profile email';
663
724
  if (argv[1]?.includes('usertold')) {
664
725
  return 'usertold';
665
726
  }
666
- // Check if run via npm/yarn run
667
- if (argv[1]?.includes('npm') || process.env.npm_lifecycle_event) {
668
- return 'npm run cli';
727
+ // Check if run via package manager script
728
+ if (argv[1]?.includes('pnpm') || argv[1]?.includes('npm') || process.env.npm_lifecycle_event) {
729
+ return 'pnpm run cli';
669
730
  }
670
731
  // Check if run via npx
671
732
  if (argv[0]?.includes('npx') || process.env.npm_execpath?.includes('npx')) {
@@ -1154,7 +1215,11 @@ async function handleLogout(parsed) {
1154
1215
  }
1155
1216
  }
1156
1217
 
1157
- const FLAGS$e = {
1218
+ // `status` is kept as a runtime alias for `whoami` in the switch below but is
1219
+ // intentionally absent here so it does not surface in `usertold introspect`
1220
+ // or in the AUTH_HELP Commands list. Remove the case entirely in the next
1221
+ // minor to retire the alias.
1222
+ const FLAGS$d = {
1158
1223
  login: [
1159
1224
  'token',
1160
1225
  'no-browser',
@@ -1164,9 +1229,6 @@ const FLAGS$e = {
1164
1229
  whoami: [
1165
1230
  'no-verify'
1166
1231
  ],
1167
- status: [
1168
- 'no-verify'
1169
- ],
1170
1232
  token: []
1171
1233
  };
1172
1234
  async function handleAuthCommand(subcommand, parsed) {
@@ -1176,20 +1238,20 @@ async function handleAuthCommand(subcommand, parsed) {
1176
1238
  }
1177
1239
  switch(subcommand){
1178
1240
  case 'login':
1179
- validateFlags(parsed, FLAGS$e.login);
1241
+ validateFlags(parsed, FLAGS$d.login);
1180
1242
  await handleLogin(parsed);
1181
1243
  return;
1182
1244
  case 'logout':
1183
- validateFlags(parsed, FLAGS$e.logout);
1245
+ validateFlags(parsed, FLAGS$d.logout);
1184
1246
  await handleLogout(parsed);
1185
1247
  return;
1186
1248
  case 'whoami':
1187
1249
  case 'status':
1188
- validateFlags(parsed, FLAGS$e.whoami);
1250
+ validateFlags(parsed, FLAGS$d.whoami);
1189
1251
  await handleWhoami(parsed);
1190
1252
  return;
1191
1253
  case 'token':
1192
- validateFlags(parsed, FLAGS$e.token);
1254
+ validateFlags(parsed, FLAGS$d.token);
1193
1255
  await handleToken(parsed);
1194
1256
  return;
1195
1257
  default:
@@ -1204,7 +1266,6 @@ Commands:
1204
1266
  login [--env <env>] Authenticate and store bearer token
1205
1267
  logout [--env <env>] Remove credentials (or all envs if omitted)
1206
1268
  whoami [--env <env>] Show current authenticated user/profile and personal_org_handle
1207
- status Alias for whoami
1208
1269
  token [--env <env>] Output current token for piping
1209
1270
 
1210
1271
  Options:
@@ -1214,16 +1275,19 @@ Options:
1214
1275
  --no-verify Skip server-side token verification (whoami)
1215
1276
 
1216
1277
  Discovery:
1217
- Run \`usertold auth whoami --json\` and read \`profile.personal_org_handle\`
1218
- before \`usertold project create <orgHandle>\` or \`usertold init --org <orgHandle>\`.
1278
+ \`usertold project list\`, \`usertold project create\`, and \`usertold init\`
1279
+ default to \`profile.personal_org_handle\`. Pass --org only when targeting
1280
+ a different workspace.
1219
1281
 
1220
1282
  Examples:
1221
1283
  usertold auth login
1222
1284
  usertold auth logout
1223
1285
  usertold auth whoami
1224
1286
  usertold auth whoami --json
1225
- usertold auth status
1226
1287
  usertold auth token --json
1288
+
1289
+ Deprecated aliases (kept for backward compatibility, will be removed in a future release):
1290
+ status Use \`whoami\` instead
1227
1291
  `;
1228
1292
  function printAuthHelp() {
1229
1293
  console.log(AUTH_HELP);
@@ -6050,6 +6114,90 @@ function getCreditPacks(environment) {
6050
6114
  }
6051
6115
  /** Display-only alias (credits/cents/label are identical across environments). */ getCreditPacks();
6052
6116
 
6117
+ const TARGET_SURFACES = [
6118
+ 'product_under_test',
6119
+ 'usertold_widget_interview',
6120
+ 'interviewer_conductor_behavior',
6121
+ 'ambiguous_needs_review'
6122
+ ];
6123
+ const TARGET_SURFACE_FILTERS = [
6124
+ ...TARGET_SURFACES,
6125
+ 'all'
6126
+ ];
6127
+ const TARGET_SURFACE_SET = new Set(TARGET_SURFACES);
6128
+ function isTargetSurface(value) {
6129
+ return typeof value === 'string' && TARGET_SURFACE_SET.has(value);
6130
+ }
6131
+ function normalizeTargetSurface(value) {
6132
+ return isTargetSurface(value) ? value : 'product_under_test';
6133
+ }
6134
+
6135
+ const TASK_STATUSES = [
6136
+ 'backlog',
6137
+ 'ready',
6138
+ 'in_progress',
6139
+ 'done',
6140
+ 'wont_fix'
6141
+ ];
6142
+ const CLOSED_TASK_STATUSES = [
6143
+ 'done',
6144
+ 'wont_fix'
6145
+ ];
6146
+ const TASK_STATUS_TRANSITION_SOURCES = [
6147
+ 'user',
6148
+ 'mcp',
6149
+ 'linear_webhook',
6150
+ 'github_webhook',
6151
+ // Legacy value retained so historical status rows remain readable.
6152
+ 'impact_measurement',
6153
+ 'consolidation_reopen',
6154
+ 'system'
6155
+ ];
6156
+ new Set(TASK_STATUSES);
6157
+ new Set(CLOSED_TASK_STATUSES);
6158
+ new Set(TASK_STATUS_TRANSITION_SOURCES);
6159
+
6160
+ const USER_DISPLAY_NAME_MAX_LENGTH = 80;
6161
+ const WORKSPACE_HANDLE_MIN_LENGTH = 3;
6162
+ const WORKSPACE_HANDLE_MAX_LENGTH = 64;
6163
+ const WORKSPACE_NAME_MAX_LENGTH = 80;
6164
+ const WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,62}[a-z0-9])$/;
6165
+ function hasControlCharacters(value) {
6166
+ for(let index = 0; index < value.length; index += 1){
6167
+ const code = value.charCodeAt(index);
6168
+ if (code <= 31 || code === 127) {
6169
+ return true;
6170
+ }
6171
+ }
6172
+ return false;
6173
+ }
6174
+ function validateWorkspaceName(value) {
6175
+ const trimmed = value.trim();
6176
+ if (!trimmed) {
6177
+ return 'Organization name is required';
6178
+ }
6179
+ if (trimmed.length > WORKSPACE_NAME_MAX_LENGTH) {
6180
+ return `Organization name must be ${WORKSPACE_NAME_MAX_LENGTH} characters or fewer`;
6181
+ }
6182
+ if (hasControlCharacters(trimmed)) {
6183
+ return 'Organization name cannot include control characters';
6184
+ }
6185
+ return null;
6186
+ }
6187
+ function validateWorkspaceHandle(value) {
6188
+ const trimmed = value.trim();
6189
+ if (!trimmed) {
6190
+ return 'Workspace handle is required';
6191
+ }
6192
+ if (trimmed.length < WORKSPACE_HANDLE_MIN_LENGTH || trimmed.length > WORKSPACE_HANDLE_MAX_LENGTH) {
6193
+ return `Workspace handle must be ${WORKSPACE_HANDLE_MIN_LENGTH}-${WORKSPACE_HANDLE_MAX_LENGTH} characters`;
6194
+ }
6195
+ if (!WORKSPACE_HANDLE_PATTERN.test(trimmed)) {
6196
+ return 'Use lowercase letters, numbers, and hyphens. Start and end with a letter or number.';
6197
+ }
6198
+ return null;
6199
+ }
6200
+
6053
6201
  // ============================================================================
6054
6202
  // Shared API Request Contracts — Zod schemas are the single source of truth.
6055
6203
  // Types are derived via z.infer<>.
@@ -6072,7 +6220,8 @@ const ApiTaskCreateRequestSchema = object({
6072
6220
  effort_estimate: string().nullable().optional()
6073
6221
  });
6074
6222
  const ApiTaskPatchRequestSchema = ApiTaskCreateRequestSchema.partial().extend({
6075
- status: string().optional()
6223
+ status: _enum(TASK_STATUSES).optional(),
6224
+ status_reason: string().nullable().optional()
6076
6225
  });
6077
6226
  const ApiTaskCreateFromSignalsRequestSchema = object({
6078
6227
  title: string(),
@@ -6081,8 +6230,10 @@ const ApiTaskCreateFromSignalsRequestSchema = object({
6081
6230
  signal_ids: array(string()).min(1)
6082
6231
  });
6083
6232
  const ApiTaskListQuerySchema = object({
6084
- status: string().optional(),
6233
+ status: _enum(TASK_STATUSES).optional(),
6085
6234
  type: string().optional(),
6235
+ target_surface: _enum(TARGET_SURFACE_FILTERS).optional(),
6236
+ include_closed: string().optional(),
6086
6237
  session_id: string().optional(),
6087
6238
  min_priority: string().optional(),
6088
6239
  limit: string().optional(),
@@ -6091,6 +6242,7 @@ const ApiTaskListQuerySchema = object({
6091
6242
  // --- Signals ---
6092
6243
  const ApiSessionSignalCreateRequestSchema = object({
6093
6244
  signal_type: string(),
6245
+ target_surface: _enum(TARGET_SURFACES).optional(),
6094
6246
  quote: string(),
6095
6247
  context: string().optional(),
6096
6248
  timestamp_ms: number().optional(),
@@ -6099,10 +6251,13 @@ const ApiSessionSignalCreateRequestSchema = object({
6099
6251
  });
6100
6252
  const ApiSignalPatchRequestSchema = object({
6101
6253
  signal_type: string().optional(),
6254
+ target_surface: _enum(TARGET_SURFACES).nullable().optional(),
6102
6255
  confidence: number().optional(),
6256
+ intensity: number().nullable().optional(),
6103
6257
  quote: string().optional(),
6104
6258
  analysis: string().nullable().optional(),
6105
6259
  context: string().nullable().optional(),
6260
+ timestamp_ms: number().nullable().optional(),
6106
6261
  headline: string().nullable().optional(),
6107
6262
  claim: string().nullable().optional(),
6108
6263
  reconstruction: string().nullable().optional()
@@ -6133,12 +6288,22 @@ const ApiSignalBulkDeleteRequestSchema = object({
6133
6288
  });
6134
6289
  const ApiSignalListQuerySchema = object({
6135
6290
  type: string().optional(),
6291
+ target_surface: _enum(TARGET_SURFACE_FILTERS).optional(),
6136
6292
  session_id: string().optional(),
6137
6293
  task_id: string().optional(),
6138
6294
  search: string().optional(),
6139
6295
  min_confidence: string().optional(),
6140
6296
  dismissed: string().optional(),
6297
+ include_closed: string().optional(),
6141
6298
  review_status: string().optional(),
6299
+ review_state: _enum([
6300
+ 'active',
6301
+ 'needs_review',
6302
+ 'active_linked',
6303
+ 'active_unlinked',
6304
+ 'possible_recurrence',
6305
+ 'resolved'
6306
+ ]).optional(),
6142
6307
  limit: string().optional(),
6143
6308
  offset: string().optional()
6144
6309
  });
@@ -6285,13 +6450,13 @@ const ApiSessionProcessingStatusRequestSchema = object({
6285
6450
  });
6286
6451
  // --- User ---
6287
6452
  const ApiUserProfileUpdateRequestSchema = object({
6288
- name: string().optional()
6453
+ name: string().max(USER_DISPLAY_NAME_MAX_LENGTH).refine((value)=>!hasControlCharacters(value), 'Name cannot include control characters').optional()
6289
6454
  });
6290
6455
  // --- Auth ---
6291
6456
  const ApiAuthOnboardingRequestSchema = object({
6292
6457
  acceptTerms: boolean(),
6293
- personalOrgHandle: string().optional(),
6294
- personalOrgName: string().optional()
6458
+ personalOrgHandle: string().max(WORKSPACE_HANDLE_MAX_LENGTH).refine((value)=>validateWorkspaceHandle(value) === null, 'Workspace handle is invalid').optional(),
6459
+ personalOrgName: string().max(WORKSPACE_NAME_MAX_LENGTH).refine((value)=>validateWorkspaceName(value) === null, 'Organization name is invalid').optional()
6295
6460
  });
6296
6461
  const ApiOAuthApprovalDecisionRequestSchema = object({
6297
6462
  requestId: string(),
@@ -6324,6 +6489,9 @@ const ApiSdkSessionCreateRequestSchema = object({
6324
6489
  interview_mode: _enum(INTERVIEW_MODES).optional(),
6325
6490
  study_id: string().optional()
6326
6491
  });
6492
+ const ApiSdkSessionConsentRequestSchema = object({
6493
+ consent_copy_version: string().min(1).max(64)
6494
+ });
6327
6495
  const ApiSdkUploadStartRequestSchema = object({
6328
6496
  session_id: string()
6329
6497
  });
@@ -6369,7 +6537,22 @@ const ApiConductorCheckpointRequestSchema = object({
6369
6537
  fromTitle: string().optional(),
6370
6538
  targetUrl: string().optional(),
6371
6539
  ts: number()
6372
- }).optional()
6540
+ }).optional(),
6541
+ // Widget telemetry events that must land even if the WS is mid-reconnect at
6542
+ // unload time. The checkpoint API uses sendBeacon / fetch keepalive, so it
6543
+ // survives page death; the WS queue does not. The DO persists each entry to
6544
+ // session_events as event_type=`widget.<name>`. Capped to keep beacon
6545
+ // payloads small — the only intended caller is the STS navigation
6546
+ // terminator, but the array shape leaves room for adjacent terminators
6547
+ // (e.g. tts_turn_completed) without another schema change.
6548
+ widget_events: array(object({
6549
+ name: string().min(1).max(64),
6550
+ ts: number(),
6551
+ data: record(string(), unknown())
6552
+ })).max(8).optional()
6553
+ });
6554
+ const ApiConductorRealtimeCallRequestSchema = object({
6555
+ sdp: string().min(1)
6373
6556
  });
6374
6557
  // --- Billing request schemas ---
6375
6558
  const ApiBillingCheckoutRequestSchema = object({
@@ -6380,6 +6563,12 @@ const ApiBillingEventsQuerySchema = object({
6380
6563
  limit: string().optional(),
6381
6564
  offset: string().optional()
6382
6565
  });
6566
+ const ApiAdminCreditsGrantRequestSchema = object({
6567
+ email: string().trim().toLowerCase().email(),
6568
+ credits: number().int().positive().max(100000),
6569
+ reason: string().trim().min(1).max(160),
6570
+ grant_id: string().trim().min(1).max(160).optional()
6571
+ });
6383
6572
 
6384
6573
  const ApiSuccessResponseSchema = object({
6385
6574
  success: boolean()
@@ -6407,7 +6596,6 @@ const ApiProjectSchema = object({
6407
6596
  github_installation_id: string().nullable(),
6408
6597
  linear_team_id: string().nullable(),
6409
6598
  public_key: string(),
6410
- secret_key: string(),
6411
6599
  created_at: string(),
6412
6600
  updated_at: string()
6413
6601
  });
@@ -6422,7 +6610,8 @@ const ApiProjectsListResponseSchema = object({
6422
6610
  });
6423
6611
  const ApiProjectDetailResponseSchema = object({
6424
6612
  project: ApiProjectSchema,
6425
- members: array(ApiProjectMemberSchema)
6613
+ members: array(ApiProjectMemberSchema),
6614
+ current_user_org_role: string().nullable()
6426
6615
  });
6427
6616
  const ApiProjectMutationResponseSchema = object({
6428
6617
  project: ApiProjectSchema
@@ -6640,11 +6829,46 @@ const ApiEnrichedTimelineEntrySchema = object({
6640
6829
  meta: record(string(), unknown()).optional()
6641
6830
  });
6642
6831
 
6832
+ const ApiSignalLinkedTaskSchema = object({
6833
+ id: string(),
6834
+ title: string(),
6835
+ status: _enum(TASK_STATUSES),
6836
+ linear_issue_id: string().nullable().optional(),
6837
+ linear_issue_url: string().nullable().optional(),
6838
+ linear_issue_status: string().nullable().optional()
6839
+ });
6840
+ const ApiSignalEvidenceResolutionSchema = object({
6841
+ task_id: string().optional(),
6842
+ resolved_at: string().nullable(),
6843
+ resolved_by_provider: string().nullable(),
6844
+ resolved_by_provider_issue_id: string().nullable(),
6845
+ resolution_reason: string().nullable()
6846
+ });
6847
+ const ApiSignalRecurrenceCandidateSchema = object({
6848
+ id: string(),
6849
+ related_task_id: string(),
6850
+ related_resolved_signal_id: string().nullable(),
6851
+ confidence: number(),
6852
+ reason: string().nullable(),
6853
+ status: _enum([
6854
+ 'candidate',
6855
+ 'confirmed',
6856
+ 'dismissed'
6857
+ ]),
6858
+ created_at: string(),
6859
+ reviewed_at: string().nullable(),
6860
+ related_task: object({
6861
+ id: string(),
6862
+ title: string(),
6863
+ status: _enum(TASK_STATUSES)
6864
+ }).nullable().optional()
6865
+ });
6643
6866
  const ApiSignalSchema = object({
6644
6867
  id: string(),
6645
6868
  project_id: string(),
6646
6869
  session_id: string(),
6647
6870
  signal_type: string(),
6871
+ target_surface: _enum(TARGET_SURFACES),
6648
6872
  confidence: number(),
6649
6873
  intensity: number().nullable(),
6650
6874
  quote: string(),
@@ -6674,11 +6898,22 @@ const ApiSignalSchema = object({
6674
6898
  dismissed_reason: string().nullable(),
6675
6899
  dismissed_by: string().nullable(),
6676
6900
  source: string().nullable(),
6677
- created_at: string()
6901
+ created_at: string(),
6902
+ linked_task: ApiSignalLinkedTaskSchema.nullable().optional(),
6903
+ evidence_resolution: ApiSignalEvidenceResolutionSchema.nullable().optional(),
6904
+ recurrence_candidates: array(ApiSignalRecurrenceCandidateSchema).optional()
6678
6905
  });
6679
6906
  const ApiSignalsListResponseSchema = object({
6680
6907
  signals: array(ApiSignalSchema),
6681
- total: number()
6908
+ total: number(),
6909
+ inbox_counts: object({
6910
+ active: number(),
6911
+ needs_review: number(),
6912
+ active_linked: number(),
6913
+ active_unlinked: number(),
6914
+ possible_recurrence: number(),
6915
+ resolved: number()
6916
+ }).optional()
6682
6917
  });
6683
6918
  const ApiSignalResponseSchema = object({
6684
6919
  signal: ApiSignalSchema
@@ -6701,7 +6936,8 @@ const ApiTaskSchema = object({
6701
6936
  title: string(),
6702
6937
  description: string().nullable(),
6703
6938
  task_type: string(),
6704
- status: string(),
6939
+ target_surface: _enum(TARGET_SURFACES),
6940
+ status: _enum(TASK_STATUSES),
6705
6941
  priority_score: number(),
6706
6942
  priority_label: string(),
6707
6943
  effort_estimate: string().nullable(),
@@ -6711,14 +6947,55 @@ const ApiTaskSchema = object({
6711
6947
  implementation_pr_url: string().nullable(),
6712
6948
  implementation_pr_number: number().nullable(),
6713
6949
  deployed_at: string().nullable(),
6714
- baseline_signal_rate: number().nullable(),
6715
- current_signal_rate: number().nullable(),
6716
- impact_score: number().nullable(),
6717
- measurement_started_at: string().nullable(),
6718
- measurement_completed_at: string().nullable(),
6719
6950
  created_at: string(),
6720
6951
  updated_at: string()
6721
6952
  });
6953
+ const ApiTaskStatusHistorySchema = object({
6954
+ id: string(),
6955
+ task_id: string(),
6956
+ project_id: string(),
6957
+ from_status: _enum(TASK_STATUSES).nullable(),
6958
+ to_status: _enum(TASK_STATUSES),
6959
+ source: _enum(TASK_STATUS_TRANSITION_SOURCES),
6960
+ reason: string().nullable(),
6961
+ actor_id: string().nullable(),
6962
+ created_at: string()
6963
+ });
6964
+ const ApiTaskRelatedTaskSchema = object({
6965
+ relation_id: string(),
6966
+ relation_type: string(),
6967
+ reason: string().nullable(),
6968
+ score: number().nullable(),
6969
+ source: string(),
6970
+ created_at: string(),
6971
+ updated_at: string(),
6972
+ task: ApiTaskSchema
6973
+ });
6974
+ const ApiTaskEvidenceResolutionSchema = object({
6975
+ resolved_at: string().nullable(),
6976
+ resolved_by_provider: string().nullable(),
6977
+ resolved_by_provider_issue_id: string().nullable(),
6978
+ resolution_reason: string().nullable()
6979
+ });
6980
+ const ApiTaskEvidenceSignalSchema = ApiSignalSchema.extend({
6981
+ evidence_resolution: ApiTaskEvidenceResolutionSchema.nullable().optional()
6982
+ });
6983
+ const ApiTaskRecurrenceCandidateSchema = object({
6984
+ id: string(),
6985
+ project_id: string(),
6986
+ new_signal_id: string(),
6987
+ related_task_id: string(),
6988
+ related_resolved_signal_id: string().nullable(),
6989
+ confidence: number(),
6990
+ reason: string().nullable(),
6991
+ status: _enum([
6992
+ 'candidate',
6993
+ 'confirmed',
6994
+ 'dismissed'
6995
+ ]),
6996
+ created_at: string(),
6997
+ reviewed_at: string().nullable()
6998
+ });
6722
6999
  const ApiProviderStateSchema = object({
6723
7000
  id: string(),
6724
7001
  task_id: string(),
@@ -6732,21 +7009,20 @@ const ApiProviderStateSchema = object({
6732
7009
  created_at: string(),
6733
7010
  updated_at: string()
6734
7011
  });
6735
- const ApiTaskImpactResultSchema = object({
6736
- taskId: string(),
6737
- baselineRate: number(),
6738
- currentRate: number(),
6739
- reductionPercent: number(),
6740
- sessionsAnalyzed: number(),
6741
- measurementDays: number()
6742
- });
6743
7012
  const ApiTasksListResponseSchema = object({
6744
- tasks: array(ApiTaskSchema),
7013
+ tasks: array(ApiTaskSchema.extend({
7014
+ relation_count: number().optional(),
7015
+ provider_states: array(ApiProviderStateSchema).optional(),
7016
+ recurrence_candidates: array(ApiTaskRecurrenceCandidateSchema).optional()
7017
+ })),
6745
7018
  total: number()
6746
7019
  });
6747
7020
  const ApiTaskDetailResponseSchema = object({
6748
7021
  task: ApiTaskSchema,
6749
- signals: array(ApiSignalSchema)
7022
+ signals: array(ApiTaskEvidenceSignalSchema),
7023
+ status_history: array(ApiTaskStatusHistorySchema).optional(),
7024
+ related_tasks: array(ApiTaskRelatedTaskSchema).optional(),
7025
+ recurrence_candidates: array(ApiTaskRecurrenceCandidateSchema).optional()
6750
7026
  });
6751
7027
  const ApiTaskResponseSchema = object({
6752
7028
  task: ApiTaskSchema
@@ -6754,9 +7030,6 @@ const ApiTaskResponseSchema = object({
6754
7030
  const ApiTaskProviderStateResponseSchema = object({
6755
7031
  providers: array(ApiProviderStateSchema)
6756
7032
  });
6757
- const ApiTaskMeasureImpactResponseSchema = object({
6758
- impact: ApiTaskImpactResultSchema
6759
- });
6760
7033
  const ApiReadyTasksResponseSchema = object({
6761
7034
  tasks: array(ApiTaskSchema)
6762
7035
  });
@@ -6998,18 +7271,37 @@ const ApiOAuthDynamicClientRegistrationResponseSchema = object({
6998
7271
  client_secret_expires_at: number()
6999
7272
  });
7000
7273
 
7274
+ const BILLING_EVENT_DISPLAY_KINDS = [
7275
+ 'purchase',
7276
+ 'bonus',
7277
+ 'usage',
7278
+ 'other'
7279
+ ];
7280
+ const BILLING_EVENT_AMOUNT_DIRECTIONS = [
7281
+ 'credit',
7282
+ 'debit',
7283
+ 'neutral'
7284
+ ];
7285
+
7001
7286
  const ApiBillingEventSchema = object({
7002
7287
  id: string(),
7003
7288
  user_id: number(),
7004
7289
  session_id: string(),
7005
7290
  project_id: string(),
7006
7291
  event_type: string(),
7292
+ display_label: string(),
7293
+ display_kind: _enum(BILLING_EVENT_DISPLAY_KINDS),
7294
+ amount_direction: _enum(BILLING_EVENT_AMOUNT_DIRECTIONS),
7007
7295
  amount_cents: number(),
7296
+ signed_amount_cents: number(),
7008
7297
  polar_synced: number(),
7009
7298
  created_at: string()
7010
7299
  });
7011
7300
  const ApiBillingStatusSchema = object({
7012
7301
  credit_balance_cents: number(),
7302
+ available_interview_count: number(),
7303
+ grace_debt_cents: number(),
7304
+ grace_debt_limit_cents: number(),
7013
7305
  has_payment_method: boolean(),
7014
7306
  accrued_usage_cents: number(),
7015
7307
  spending_cap_cents: number(),
@@ -7066,7 +7358,14 @@ const ApiGitHubSelectRepoResponseSchema = object({
7066
7358
  });
7067
7359
 
7068
7360
  const ApiSdkSessionCreateResponseSchema = object({
7069
- session_id: string()
7361
+ session_id: string(),
7362
+ session_token: string()
7363
+ });
7364
+ const ApiSdkRuntimeSessionTokenResponseSchema = object({
7365
+ session_token: string()
7366
+ });
7367
+ const ApiSdkSessionConsentResponseSchema = object({
7368
+ success: literal(true)
7070
7369
  });
7071
7370
  const ApiSdkAudioUploadResponseSchema = object({
7072
7371
  success: boolean()
@@ -7088,6 +7387,16 @@ const ApiConductorCheckpointResponseSchema = object({
7088
7387
  success: boolean(),
7089
7388
  checkpointed: boolean()
7090
7389
  });
7390
+ const ApiConductorOriginGuardErrorResponseSchema = object({
7391
+ error: string(),
7392
+ code: _enum([
7393
+ 'origin_required',
7394
+ 'origin_invalid',
7395
+ 'origin_not_allowed'
7396
+ ]),
7397
+ retryable: literal(false),
7398
+ action: string()
7399
+ });
7091
7400
  const ApiConductorTranscriptionSecretResponseSchema = object({
7092
7401
  client_secret: string(),
7093
7402
  expires_at: number(),
@@ -7097,7 +7406,15 @@ const ApiConductorSTSSecretResponseSchema = object({
7097
7406
  client_secret: string(),
7098
7407
  expires_at: number(),
7099
7408
  model: string(),
7100
- voice: string()
7409
+ voice: string(),
7410
+ reasoning_enabled: boolean()
7411
+ });
7412
+ const ApiConductorRealtimeCallResponseSchema = object({
7413
+ sdp: string(),
7414
+ call_id: string().nullable(),
7415
+ model: string(),
7416
+ reasoning_enabled: boolean(),
7417
+ sideband_enabled: boolean()
7101
7418
  });
7102
7419
  const ApiConductorEndResponseSchema = object({
7103
7420
  success: boolean(),
@@ -7148,6 +7465,7 @@ const ApiSdkScreenerResponseSchema = object({
7148
7465
  const ApiSdkScreenerSubmitResponseSchema = object({
7149
7466
  qualified: boolean(),
7150
7467
  session_id: string().optional(),
7468
+ session_token: string().optional(),
7151
7469
  study_id: string().nullable().optional(),
7152
7470
  study_handle: string().nullable().optional(),
7153
7471
  message: string()
@@ -7177,6 +7495,13 @@ const ApiOverviewResponseSchema = object({
7177
7495
  total: number(),
7178
7496
  by_status: record(string(), number())
7179
7497
  }),
7498
+ attention: object({
7499
+ recent_sessions_with_evidence: number(),
7500
+ active_evidence: number(),
7501
+ tasks_awaiting_handoff: number(),
7502
+ tasks_in_linear: number(),
7503
+ recurrence_candidates: number()
7504
+ }),
7180
7505
  screeners: object({
7181
7506
  total: number(),
7182
7507
  active: number(),
@@ -7262,6 +7587,19 @@ const ApiAdminEmbedBackfillResponseSchema = object({
7262
7587
  signals_failed: number(),
7263
7588
  tasks_failed: number()
7264
7589
  });
7590
+ const ApiAdminCreditsGrantResponseSchema = object({
7591
+ user_id: number(),
7592
+ email: string(),
7593
+ credits: number(),
7594
+ amount_cents: number(),
7595
+ event_type: literal('credits.bonus'),
7596
+ grant_key: string(),
7597
+ reason: string(),
7598
+ applied: boolean(),
7599
+ credit_balance_cents: number(),
7600
+ available_interview_count: number(),
7601
+ grace_debt_cents: number()
7602
+ });
7265
7603
 
7266
7604
  const ApiAuthGithubLoginQuerySchema = object({
7267
7605
  return_to: string().optional()
@@ -7469,6 +7807,11 @@ const LinearTaskPushResponseSchema = object({
7469
7807
  pushed: boolean(),
7470
7808
  alreadyPushed: boolean()
7471
7809
  });
7810
+ const LinearDisconnectResponseSchema = ApiSuccessResponseSchema.extend({
7811
+ unlinkedOrg: boolean(),
7812
+ revokedInstallation: boolean(),
7813
+ remainingActiveOrgLinks: number().int().nonnegative()
7814
+ });
7472
7815
  const linearApiContracts = {
7473
7816
  linearConnect: defineContract({
7474
7817
  method: 'GET',
@@ -7481,6 +7824,17 @@ const linearApiContracts = {
7481
7824
  }),
7482
7825
  response: unknown()
7483
7826
  }),
7827
+ linearClaim: defineContract({
7828
+ method: 'GET',
7829
+ path: '/api/orgs/:orgHandle/linear/claim',
7830
+ pathParams: [
7831
+ 'orgHandle'
7832
+ ],
7833
+ query: object({
7834
+ return_to: string().optional()
7835
+ }),
7836
+ response: unknown()
7837
+ }),
7484
7838
  linearCallback: defineContract({
7485
7839
  method: 'GET',
7486
7840
  path: '/api/linear/callback',
@@ -7506,7 +7860,7 @@ const linearApiContracts = {
7506
7860
  pathParams: [
7507
7861
  'orgHandle'
7508
7862
  ],
7509
- response: ApiSuccessResponseSchema
7863
+ response: LinearDisconnectResponseSchema
7510
7864
  }),
7511
7865
  linearTeams: defineContract({
7512
7866
  method: 'GET',
@@ -7565,6 +7919,13 @@ const adminApiContracts = {
7565
7919
  path: '/api/admin/embed-backfill',
7566
7920
  pathParams: [],
7567
7921
  response: ApiAdminEmbedBackfillResponseSchema
7922
+ }),
7923
+ adminCreditsGrant: defineContract({
7924
+ method: 'POST',
7925
+ path: '/api/admin/credits/grant',
7926
+ pathParams: [],
7927
+ body: ApiAdminCreditsGrantRequestSchema,
7928
+ response: ApiAdminCreditsGrantResponseSchema
7568
7929
  })
7569
7930
  };
7570
7931
 
@@ -7577,6 +7938,7 @@ const ApiOAuthAuthorizeQuerySchema = object({
7577
7938
  response_type: string().optional(),
7578
7939
  code_challenge: string().optional(),
7579
7940
  code_challenge_method: string().optional(),
7941
+ scope: string().optional(),
7580
7942
  state: string().optional()
7581
7943
  });
7582
7944
  const ApiOAuthTokenRequestSchema = object({
@@ -7930,6 +8292,12 @@ const screenersApiContracts = {
7930
8292
  })
7931
8293
  };
7932
8294
 
8295
+ const conductorOriginGuardResponses = {
8296
+ 403: {
8297
+ description: 'Origin rejected by conductor runtime guard',
8298
+ schema: ApiConductorOriginGuardErrorResponseSchema
8299
+ }
8300
+ };
7933
8301
  const sdkApiContracts = {
7934
8302
  sdkSessionCreate: defineContract({
7935
8303
  method: 'POST',
@@ -7938,6 +8306,23 @@ const sdkApiContracts = {
7938
8306
  body: ApiSdkSessionCreateRequestSchema,
7939
8307
  response: ApiSdkSessionCreateResponseSchema
7940
8308
  }),
8309
+ sdkSessionRuntimeToken: defineContract({
8310
+ method: 'POST',
8311
+ path: '/api/sdk/sessions/:sessionId/runtime-token',
8312
+ pathParams: [
8313
+ 'sessionId'
8314
+ ],
8315
+ response: ApiSdkRuntimeSessionTokenResponseSchema
8316
+ }),
8317
+ sdkSessionConsent: defineContract({
8318
+ method: 'POST',
8319
+ path: '/api/sdk/sessions/:sessionId/consent',
8320
+ pathParams: [
8321
+ 'sessionId'
8322
+ ],
8323
+ body: ApiSdkSessionConsentRequestSchema,
8324
+ response: ApiSdkSessionConsentResponseSchema
8325
+ }),
7941
8326
  sdkAudioUpload: defineContract({
7942
8327
  method: 'POST',
7943
8328
  path: '/api/sdk/audio/upload',
@@ -7959,7 +8344,8 @@ const sdkApiContracts = {
7959
8344
  'sessionId'
7960
8345
  ],
7961
8346
  body: ApiConductorCheckpointRequestSchema,
7962
- response: ApiConductorCheckpointResponseSchema
8347
+ response: ApiConductorCheckpointResponseSchema,
8348
+ responses: conductorOriginGuardResponses
7963
8349
  }),
7964
8350
  conductorWs: defineContract({
7965
8351
  method: 'GET',
@@ -7970,7 +8356,8 @@ const sdkApiContracts = {
7970
8356
  // WebSocket upgrade endpoint — returns 101 Switching Protocols, not JSON.
7971
8357
  // z.unknown() is used because the contract system requires a response schema
7972
8358
  // but no JSON body is returned. See conductor.ts route for 101/426 docs.
7973
- response: unknown().describe('WebSocket upgrade — no JSON response body')
8359
+ response: unknown().describe('WebSocket upgrade — no JSON response body'),
8360
+ responses: conductorOriginGuardResponses
7974
8361
  }),
7975
8362
  conductorTranscriptionSecret: defineContract({
7976
8363
  method: 'POST',
@@ -7978,7 +8365,8 @@ const sdkApiContracts = {
7978
8365
  pathParams: [
7979
8366
  'sessionId'
7980
8367
  ],
7981
- response: ApiConductorTranscriptionSecretResponseSchema
8368
+ response: ApiConductorTranscriptionSecretResponseSchema,
8369
+ responses: conductorOriginGuardResponses
7982
8370
  }),
7983
8371
  conductorSTSSecret: defineContract({
7984
8372
  method: 'POST',
@@ -7986,7 +8374,18 @@ const sdkApiContracts = {
7986
8374
  pathParams: [
7987
8375
  'sessionId'
7988
8376
  ],
7989
- response: ApiConductorSTSSecretResponseSchema
8377
+ response: ApiConductorSTSSecretResponseSchema,
8378
+ responses: conductorOriginGuardResponses
8379
+ }),
8380
+ conductorRealtimeCall: defineContract({
8381
+ method: 'POST',
8382
+ path: '/api/sdk/conductor/:sessionId/realtime-call',
8383
+ pathParams: [
8384
+ 'sessionId'
8385
+ ],
8386
+ body: ApiConductorRealtimeCallRequestSchema,
8387
+ response: ApiConductorRealtimeCallResponseSchema,
8388
+ responses: conductorOriginGuardResponses
7990
8389
  }),
7991
8390
  conductorEnd: defineContract({
7992
8391
  method: 'POST',
@@ -7994,7 +8393,8 @@ const sdkApiContracts = {
7994
8393
  pathParams: [
7995
8394
  'sessionId'
7996
8395
  ],
7997
- response: ApiConductorEndResponseSchema
8396
+ response: ApiConductorEndResponseSchema,
8397
+ responses: conductorOriginGuardResponses
7998
8398
  })
7999
8399
  };
8000
8400
 
@@ -8586,16 +8986,6 @@ const tasksApiContracts = {
8586
8986
  ],
8587
8987
  response: ApiSuccessResponseSchema
8588
8988
  }),
8589
- taskMeasureImpact: defineContract({
8590
- method: 'POST',
8591
- path: '/api/orgs/:orgHandle/projects/:projectHandle/tasks/:taskId/measure',
8592
- pathParams: [
8593
- 'orgHandle',
8594
- 'projectHandle',
8595
- 'taskId'
8596
- ],
8597
- response: ApiTaskMeasureImpactResponseSchema
8598
- }),
8599
8989
  taskProviderState: defineContract({
8600
8990
  method: 'GET',
8601
8991
  path: '/api/orgs/:orgHandle/projects/:projectHandle/tasks/:taskId/provider-state',
@@ -8938,10 +9328,6 @@ const dashboardApiRouteMeta = {
8938
9328
  method: 'DELETE',
8939
9329
  path: '/api/orgs/:orgHandle/projects/:projectHandle/tasks/:taskId'
8940
9330
  },
8941
- taskMeasureImpact: {
8942
- method: 'POST',
8943
- path: '/api/orgs/:orgHandle/projects/:projectHandle/tasks/:taskId/measure'
8944
- },
8945
9331
  taskProviderState: {
8946
9332
  method: 'GET',
8947
9333
  path: '/api/orgs/:orgHandle/projects/:projectHandle/tasks/:taskId/provider-state'
@@ -9090,6 +9476,10 @@ const dashboardApiRouteMeta = {
9090
9476
  method: 'GET',
9091
9477
  path: '/api/orgs/:orgHandle/linear/connect'
9092
9478
  },
9479
+ linearClaim: {
9480
+ method: 'GET',
9481
+ path: '/api/orgs/:orgHandle/linear/claim'
9482
+ },
9093
9483
  linearCallback: {
9094
9484
  method: 'GET',
9095
9485
  path: '/api/linear/callback'
@@ -9130,6 +9520,14 @@ const dashboardApiRouteMeta = {
9130
9520
  method: 'POST',
9131
9521
  path: '/api/sdk/sessions'
9132
9522
  },
9523
+ sdkSessionRuntimeToken: {
9524
+ method: 'POST',
9525
+ path: '/api/sdk/sessions/:sessionId/runtime-token'
9526
+ },
9527
+ sdkSessionConsent: {
9528
+ method: 'POST',
9529
+ path: '/api/sdk/sessions/:sessionId/consent'
9530
+ },
9133
9531
  sdkAudioUpload: {
9134
9532
  method: 'POST',
9135
9533
  path: '/api/sdk/audio/upload'
@@ -9154,6 +9552,10 @@ const dashboardApiRouteMeta = {
9154
9552
  method: 'POST',
9155
9553
  path: '/api/sdk/conductor/:sessionId/sts-secret'
9156
9554
  },
9555
+ conductorRealtimeCall: {
9556
+ method: 'POST',
9557
+ path: '/api/sdk/conductor/:sessionId/realtime-call'
9558
+ },
9157
9559
  conductorEnd: {
9158
9560
  method: 'POST',
9159
9561
  path: '/api/sdk/conductor/:sessionId/end'
@@ -9193,6 +9595,10 @@ const dashboardApiRouteMeta = {
9193
9595
  adminEmbedBackfill: {
9194
9596
  method: 'POST',
9195
9597
  path: '/api/admin/embed-backfill'
9598
+ },
9599
+ adminCreditsGrant: {
9600
+ method: 'POST',
9601
+ path: '/api/admin/credits/grant'
9196
9602
  }
9197
9603
  };
9198
9604
  function buildDashboardApiPath$1(keyOrPath, params = {}, query) {
@@ -9351,7 +9757,7 @@ function requestContractJson(key, options) {
9351
9757
  function requestProjectContract(options) {
9352
9758
  const project = requireCanonicalProjectRef(options.projectRef, options.sourceLabel ?? '<projectRef>');
9353
9759
  const scopedPathParams = {
9354
- ...options.pathParams ?? {},
9760
+ ...options.pathParams,
9355
9761
  orgHandle: project.orgHandle,
9356
9762
  projectHandle: project.projectHandle
9357
9763
  };
@@ -9399,7 +9805,7 @@ async function requestContractBinary(key, options) {
9399
9805
  async function requestProjectContractText(key, options) {
9400
9806
  const project = requireCanonicalProjectRef(options.projectRef, options.sourceLabel ?? '<projectRef>');
9401
9807
  const scopedPathParams = {
9402
- ...options.pathParams ?? {},
9808
+ ...options.pathParams,
9403
9809
  orgHandle: project.orgHandle,
9404
9810
  projectHandle: project.projectHandle
9405
9811
  };
@@ -9411,7 +9817,7 @@ async function requestProjectContractText(key, options) {
9411
9817
  async function requestProjectContractBinary(key, options) {
9412
9818
  const project = requireCanonicalProjectRef(options.projectRef, options.sourceLabel ?? '<projectRef>');
9413
9819
  const scopedPathParams = {
9414
- ...options.pathParams ?? {},
9820
+ ...options.pathParams,
9415
9821
  orgHandle: project.orgHandle,
9416
9822
  projectHandle: project.projectHandle
9417
9823
  };
@@ -9436,90 +9842,290 @@ function mergeHeaders(defaults, overrides) {
9436
9842
  return undefined;
9437
9843
  }
9438
9844
  return {
9439
- ...defaults ?? {},
9440
- ...overrides ?? {}
9845
+ ...defaults,
9846
+ ...overrides
9441
9847
  };
9442
9848
  }
9443
9849
 
9444
- const FLAGS$d = {
9445
- list: [
9446
- 'org'
9447
- ],
9448
- create: [
9449
- 'name',
9450
- 'handle',
9451
- 'description',
9452
- 'repo',
9453
- 'org'
9454
- ],
9455
- get: [],
9456
- update: [
9457
- 'name',
9458
- 'handle',
9459
- 'description',
9460
- 'repo',
9461
- 'branch'
9462
- ],
9463
- delete: [],
9464
- snippet: [],
9465
- status: []
9466
- };
9467
- async function handleProjectCommand(subcommand, parsed) {
9468
- if (!subcommand || hasHelpFlag(parsed) || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
9469
- printProjectHelp();
9470
- return;
9850
+ async function resolveDefaultOrgHandle(env, commandLabel) {
9851
+ const profile = await requestContractJson('userProfileGet', {
9852
+ env
9853
+ });
9854
+ const orgHandle = profile.personal_org_handle;
9855
+ if (!orgHandle) {
9856
+ failArgs(`No default organization handle is available for "${commandLabel}". ` + 'Run "usertold auth whoami --json" to inspect your profile, or pass an org handle explicitly.');
9471
9857
  }
9472
- const env = parseEnvironment(parsed);
9473
- switch(subcommand){
9474
- case 'list':
9475
- {
9476
- validateFlags(parsed, FLAGS$d.list);
9477
- const hasOrgOption = parsed.options.org && parsed.options.org !== 'true';
9478
- const orgHandle = hasOrgOption ? parsed.options.org : requireOrgHandleForProjectSetup(parsed, 'project list');
9479
- assertNoExtraPositionals(parsed, hasOrgOption ? 0 : 1);
9480
- const data = await requestContractJson('projectList', {
9481
- env,
9482
- pathParams: {
9483
- orgHandle
9484
- }
9485
- });
9486
- printOutput(data, parsed);
9487
- return;
9488
- }
9489
- case 'create':
9490
- {
9491
- validateFlags(parsed, FLAGS$d.create);
9492
- const hasOrgOption = parsed.options.org && parsed.options.org !== 'true';
9493
- const orgHandle = hasOrgOption ? parsed.options.org : requireOrgHandleForProjectSetup(parsed, 'project create');
9494
- assertNoExtraPositionals(parsed, hasOrgOption ? 0 : 1);
9495
- const name = requireOption(parsed, 'name');
9496
- const body = {
9497
- name
9498
- };
9499
- if (parsed.options.handle && parsed.options.handle !== 'true') {
9500
- body.handle = parsed.options.handle;
9501
- }
9502
- if (parsed.options.description && parsed.options.description !== 'true') {
9503
- body.description = parsed.options.description;
9504
- }
9505
- if (parsed.options.repo && parsed.options.repo !== 'true') {
9506
- body.github_repo_url = parsed.options.repo;
9507
- }
9508
- const data = await requestContractJson('projectCreate', {
9509
- env,
9510
- pathParams: {
9511
- orgHandle
9512
- },
9513
- body
9514
- });
9515
- printOutput(data, parsed);
9858
+ return orgHandle;
9859
+ }
9860
+ async function resolveProjectRefWithDefaults(projectRef, env, sourceLabel = '<projectRef>') {
9861
+ const value = projectRef.trim();
9862
+ if (!value) {
9863
+ failArgs(`Missing ${sourceLabel}. Run "usertold project list" or "usertold project use <projectRef>".`);
9864
+ }
9865
+ if (value.includes('/') || value.startsWith('prj_')) {
9866
+ return resolveProjectRefToCanonical(value, env);
9867
+ }
9868
+ const orgHandle = await resolveDefaultOrgHandle(env, sourceLabel);
9869
+ return {
9870
+ orgHandle,
9871
+ projectHandle: value
9872
+ };
9873
+ }
9874
+ async function consumeProjectRef(parsed, env, options) {
9875
+ const explicitProjectRef = parsed.positionals.length > options.resourceArgCount;
9876
+ const rawProjectRef = explicitProjectRef ? parsed.positionals[0] : await requireCurrentProjectRef(env, options.commandLabel);
9877
+ const args = explicitProjectRef ? parsed.positionals.slice(1) : parsed.positionals;
9878
+ if (args.length < options.resourceArgCount) {
9879
+ failArgs(`Missing required argument for "${options.commandLabel}". ` + `Expected ${options.resourceArgCount} resource argument(s) after the project.`);
9880
+ }
9881
+ assertNoExtraPositionals({
9882
+ ...parsed,
9883
+ positionals: args
9884
+ }, options.resourceArgCount);
9885
+ const canonical = await resolveProjectRefWithDefaults(rawProjectRef, env, '<projectRef>');
9886
+ return {
9887
+ projectRef: `${canonical.orgHandle}/${canonical.projectHandle}`,
9888
+ args,
9889
+ usedCurrentProject: !explicitProjectRef
9890
+ };
9891
+ }
9892
+ async function requireCurrentProjectRef(env, commandLabel) {
9893
+ const currentProjectRef = await loadCurrentProjectRef(env);
9894
+ if (!currentProjectRef) {
9895
+ failArgs(`Missing project for "${commandLabel}". ` + 'Pass <projectRef>, or set a default with "usertold project use <projectRef>".');
9896
+ }
9897
+ return currentProjectRef;
9898
+ }
9899
+
9900
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
9901
+ function formatDuration$1(seconds) {
9902
+ if (seconds === null || seconds < 0) return '-';
9903
+ const mins = Math.floor(seconds / 60);
9904
+ const secs = seconds % 60;
9905
+ return `${mins}m ${secs}s`;
9906
+ }
9907
+ function formatDate(iso) {
9908
+ const d = new Date(iso);
9909
+ return d.toLocaleDateString('en-US', {
9910
+ month: 'short',
9911
+ day: 'numeric'
9912
+ });
9913
+ }
9914
+ function check(value) {
9915
+ return value ? '[x]' : '[ ]';
9916
+ }
9917
+ function formatSignalBreakdown(byType) {
9918
+ const parts = Object.entries(byType).sort((a, b)=>b[1] - a[1]).map(([type, count])=>`${count} ${type}`);
9919
+ return parts.length > 0 ? ` (${parts.join(', ')})` : '';
9920
+ }
9921
+ function formatTaskBreakdown(byStatus) {
9922
+ const parts = Object.entries(byStatus).sort((a, b)=>b[1] - a[1]).map(([status, count])=>`${count} ${status}`);
9923
+ return parts.length > 0 ? ` (${parts.join(', ')})` : '';
9924
+ }
9925
+ function formatOverview(data) {
9926
+ const { sessions, signals, tasks, screeners, setup, recent_sessions, top_tasks } = data;
9927
+ console.log(`Sessions: ${sessions.total} total (${sessions.completed} completed, ${sessions.active} active)`);
9928
+ console.log(`Signals: ${signals.total} total${formatSignalBreakdown(signals.by_type)}`);
9929
+ console.log(`Tasks: ${tasks.total} total${formatTaskBreakdown(tasks.by_status)}`);
9930
+ console.log(`Screeners: ${screeners.total} total (${screeners.active} active, ${screeners.total_views} views, ${screeners.total_qualified} qualified)`);
9931
+ console.log('');
9932
+ console.log('Setup:');
9933
+ console.log(` ${check(setup.has_api_keys)} API keys configured`);
9934
+ console.log(` ${check(setup.has_active_study)} Active study`);
9935
+ console.log(` ${check(setup.has_active_screener)} Active screener`);
9936
+ console.log(` ${check(setup.has_linked_active_screener)} Active screener linked to study`);
9937
+ console.log(` ${check(setup.has_sessions)} Has sessions`);
9938
+ if (recent_sessions.length > 0) {
9939
+ console.log('');
9940
+ console.log('Recent sessions:');
9941
+ for (const s of recent_sessions){
9942
+ const name = s.participant_name || 'anonymous';
9943
+ const dur = formatDuration$1(s.duration_seconds);
9944
+ const date = formatDate(s.created_at);
9945
+ const id = s.id.length > 12 ? `${s.id.slice(0, 12)}...` : s.id;
9946
+ console.log(` ${id} ${s.status.padEnd(10)} ${dur.padEnd(8)} ${String(s.signal_count).padStart(2)} signals ${date} ${name}`);
9947
+ }
9948
+ }
9949
+ if (top_tasks.length > 0) {
9950
+ console.log('');
9951
+ console.log('Top tasks:');
9952
+ for (const t of top_tasks){
9953
+ const id = t.id.length > 12 ? `${t.id.slice(0, 12)}...` : t.id;
9954
+ const title = t.title.length > 50 ? `${t.title.slice(0, 47)}...` : t.title;
9955
+ console.log(` ${id} p${String(t.priority_score).padStart(2)} ${String(t.signal_count).padStart(2)} signals ${title}`);
9956
+ }
9957
+ }
9958
+ }
9959
+ // ─── Command ─────────────────────────────────────────────────────────────────
9960
+ async function handleOverviewCommand(parsed) {
9961
+ if (hasHelpFlag(parsed)) {
9962
+ printOverviewHelp();
9963
+ return;
9964
+ }
9965
+ const env = parseEnvironment(parsed);
9966
+ const { projectRef } = await consumeProjectRef(parsed, env, {
9967
+ resourceArgCount: 0,
9968
+ commandLabel: 'overview'
9969
+ });
9970
+ const data = await requestProjectContract({
9971
+ env,
9972
+ key: 'overview',
9973
+ projectRef,
9974
+ sourceLabel: '<projectRef>'
9975
+ });
9976
+ if (isJsonOutput(parsed)) {
9977
+ printOutput(data, parsed);
9978
+ } else {
9979
+ formatOverview(data);
9980
+ }
9981
+ }
9982
+ const OVERVIEW_HELP = `
9983
+ Usage:
9984
+ usertold overview [projectRef] [options]
9985
+
9986
+ Displays dashboard overview for a project: session counts, signal breakdown,
9987
+ task status, screener summary, recent sessions, and top tasks.
9988
+
9989
+ Options:
9990
+ --env <env> stage | production | local
9991
+ --json JSON output
9992
+
9993
+ Examples:
9994
+ usertold project use acme/checkout
9995
+ usertold overview
9996
+ usertold overview acme/checkout
9997
+ usertold overview acme/checkout --json
9998
+ `;
9999
+ function printOverviewHelp() {
10000
+ console.log(OVERVIEW_HELP);
10001
+ }
10002
+
10003
+ const FLAGS$c = {
10004
+ list: [
10005
+ 'org'
10006
+ ],
10007
+ use: [],
10008
+ current: [],
10009
+ create: [
10010
+ 'name',
10011
+ 'handle',
10012
+ 'description',
10013
+ 'repo',
10014
+ 'org'
10015
+ ],
10016
+ get: [],
10017
+ update: [
10018
+ 'name',
10019
+ 'handle',
10020
+ 'description',
10021
+ 'repo',
10022
+ 'branch'
10023
+ ],
10024
+ delete: [],
10025
+ snippet: [],
10026
+ status: [],
10027
+ overview: []
10028
+ };
10029
+ async function handleProjectCommand(subcommand, parsed) {
10030
+ if (!subcommand || hasHelpFlag(parsed) || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
10031
+ printProjectHelp();
10032
+ return;
10033
+ }
10034
+ const env = parseEnvironment(parsed);
10035
+ switch(subcommand){
10036
+ case 'list':
10037
+ {
10038
+ validateFlags(parsed, FLAGS$c.list);
10039
+ const hasOrgOption = parsed.options.org && parsed.options.org !== 'true';
10040
+ const positionalOrgHandle = parsed.positionals[0];
10041
+ const orgHandle = hasOrgOption ? parsed.options.org : positionalOrgHandle ?? await resolveDefaultOrgHandle(env, 'project list');
10042
+ assertNoExtraPositionals(parsed, hasOrgOption ? 0 : positionalOrgHandle ? 1 : 0);
10043
+ const data = await requestContractJson('projectList', {
10044
+ env,
10045
+ pathParams: {
10046
+ orgHandle
10047
+ }
10048
+ });
10049
+ printOutput(data, parsed);
9516
10050
  return;
9517
10051
  }
9518
- case 'get':
10052
+ case 'use':
9519
10053
  {
9520
- validateFlags(parsed, FLAGS$d.get);
9521
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10054
+ validateFlags(parsed, FLAGS$c.use);
10055
+ const rawProjectRef = requirePositional(parsed, 0, '<projectRef>');
9522
10056
  assertNoExtraPositionals(parsed, 1);
10057
+ const project = await resolveProjectRefWithDefaults(rawProjectRef, env, '<projectRef>');
10058
+ const data = await requestContractJson('projectGet', {
10059
+ env,
10060
+ pathParams: {
10061
+ orgHandle: project.orgHandle,
10062
+ projectHandle: project.projectHandle
10063
+ }
10064
+ });
10065
+ const projectRef = `${project.orgHandle}/${project.projectHandle}`;
10066
+ await saveCurrentProjectRef(env, projectRef);
10067
+ if (isJsonOutput(parsed)) {
10068
+ printOutput({
10069
+ current_project: projectRef,
10070
+ project: data.project
10071
+ }, parsed);
10072
+ } else {
10073
+ console.log(`Current project (${env}): ${projectRef}`);
10074
+ }
10075
+ return;
10076
+ }
10077
+ case 'current':
10078
+ {
10079
+ validateFlags(parsed, FLAGS$c.current);
10080
+ assertNoExtraPositionals(parsed, 0);
10081
+ const project = await resolveProjectRefWithDefaults(await requireCurrentProjectRef(env, 'project current'), env);
10082
+ const projectRef = `${project.orgHandle}/${project.projectHandle}`;
10083
+ if (isJsonOutput(parsed)) {
10084
+ console.log(JSON.stringify({
10085
+ current_project: projectRef
10086
+ }, null, 2));
10087
+ } else {
10088
+ console.log(`Current project (${env}): ${projectRef}`);
10089
+ }
10090
+ return;
10091
+ }
10092
+ case 'create':
10093
+ {
10094
+ validateFlags(parsed, FLAGS$c.create);
10095
+ const hasOrgOption = parsed.options.org && parsed.options.org !== 'true';
10096
+ const positionalOrgHandle = parsed.positionals[0];
10097
+ const orgHandle = hasOrgOption ? parsed.options.org : positionalOrgHandle ?? await resolveDefaultOrgHandle(env, 'project create');
10098
+ assertNoExtraPositionals(parsed, hasOrgOption ? 0 : positionalOrgHandle ? 1 : 0);
10099
+ const name = requireOption(parsed, 'name');
10100
+ const body = {
10101
+ name
10102
+ };
10103
+ if (parsed.options.handle && parsed.options.handle !== 'true') {
10104
+ body.handle = parsed.options.handle;
10105
+ }
10106
+ if (parsed.options.description && parsed.options.description !== 'true') {
10107
+ body.description = parsed.options.description;
10108
+ }
10109
+ if (parsed.options.repo && parsed.options.repo !== 'true') {
10110
+ body.github_repo_url = parsed.options.repo;
10111
+ }
10112
+ const data = await requestContractJson('projectCreate', {
10113
+ env,
10114
+ pathParams: {
10115
+ orgHandle
10116
+ },
10117
+ body
10118
+ });
10119
+ printOutput(data, parsed);
10120
+ return;
10121
+ }
10122
+ case 'get':
10123
+ {
10124
+ validateFlags(parsed, FLAGS$c.get);
10125
+ const { projectRef } = await consumeProjectRef(parsed, env, {
10126
+ resourceArgCount: 0,
10127
+ commandLabel: 'project get'
10128
+ });
9523
10129
  const { orgHandle, projectHandle } = requireCanonicalProjectRef(projectRef, '<projectRef>');
9524
10130
  const data = await requestContractJson('projectGet', {
9525
10131
  env,
@@ -9533,9 +10139,11 @@ async function handleProjectCommand(subcommand, parsed) {
9533
10139
  }
9534
10140
  case 'update':
9535
10141
  {
9536
- validateFlags(parsed, FLAGS$d.update);
9537
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
9538
- assertNoExtraPositionals(parsed, 1);
10142
+ validateFlags(parsed, FLAGS$c.update);
10143
+ const { projectRef } = await consumeProjectRef(parsed, env, {
10144
+ resourceArgCount: 0,
10145
+ commandLabel: 'project update'
10146
+ });
9539
10147
  const { orgHandle, projectHandle } = requireCanonicalProjectRef(projectRef, '<projectRef>');
9540
10148
  const body = {};
9541
10149
  if (parsed.options.name && parsed.options.name !== 'true') {
@@ -9569,10 +10177,10 @@ async function handleProjectCommand(subcommand, parsed) {
9569
10177
  }
9570
10178
  case 'delete':
9571
10179
  {
9572
- validateFlags(parsed, FLAGS$d.delete);
9573
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10180
+ validateFlags(parsed, FLAGS$c.delete);
10181
+ const rawProjectRef = requirePositional(parsed, 0, '<projectRef>');
9574
10182
  assertNoExtraPositionals(parsed, 1);
9575
- const { orgHandle, projectHandle } = requireCanonicalProjectRef(projectRef, '<projectRef>');
10183
+ const { orgHandle, projectHandle } = await resolveProjectRefWithDefaults(rawProjectRef, env, '<projectRef>');
9576
10184
  if (getBooleanOption(parsed, 'dry-run')) {
9577
10185
  if (isJsonOutput(parsed)) {
9578
10186
  console.log(JSON.stringify({
@@ -9598,9 +10206,11 @@ async function handleProjectCommand(subcommand, parsed) {
9598
10206
  }
9599
10207
  case 'snippet':
9600
10208
  {
9601
- validateFlags(parsed, FLAGS$d.snippet);
9602
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
9603
- assertNoExtraPositionals(parsed, 1);
10209
+ validateFlags(parsed, FLAGS$c.snippet);
10210
+ const { projectRef } = await consumeProjectRef(parsed, env, {
10211
+ resourceArgCount: 0,
10212
+ commandLabel: 'project snippet'
10213
+ });
9604
10214
  const { orgHandle, projectHandle } = requireCanonicalProjectRef(projectRef, '<projectRef>');
9605
10215
  const data = await requestContractJson('projectGet', {
9606
10216
  env,
@@ -9613,13 +10223,16 @@ async function handleProjectCommand(subcommand, parsed) {
9613
10223
  if (!publicKey) {
9614
10224
  failNotFound('Could not retrieve project public key');
9615
10225
  }
9616
- const baseUrl = resolveBaseUrl(env);
9617
- const snippet = `<script src="${baseUrl}/v1/widget.js" data-project-key="${publicKey}"></script>`;
10226
+ const widgetOrigin = resolveWidgetEmbedOriginForEnvironment(env);
10227
+ const snippet = buildWidgetEmbedSnippet({
10228
+ origin: widgetOrigin,
10229
+ projectKey: publicKey
10230
+ });
9618
10231
  if (isJsonOutput(parsed)) {
9619
10232
  console.log(JSON.stringify({
9620
10233
  snippet,
9621
10234
  public_key: publicKey,
9622
- base_url: baseUrl
10235
+ base_url: widgetOrigin
9623
10236
  }));
9624
10237
  } else {
9625
10238
  console.log(snippet);
@@ -9628,9 +10241,11 @@ async function handleProjectCommand(subcommand, parsed) {
9628
10241
  }
9629
10242
  case 'status':
9630
10243
  {
9631
- validateFlags(parsed, FLAGS$d.status);
9632
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
9633
- assertNoExtraPositionals(parsed, 1);
10244
+ validateFlags(parsed, FLAGS$c.status);
10245
+ const { projectRef } = await consumeProjectRef(parsed, env, {
10246
+ resourceArgCount: 0,
10247
+ commandLabel: 'project status'
10248
+ });
9634
10249
  const { orgHandle, projectHandle } = requireCanonicalProjectRef(projectRef, '<projectRef>');
9635
10250
  // Parallel fetch of project, settings, overview
9636
10251
  const [projectData, settingsData, overviewData] = await Promise.all([
@@ -9686,6 +10301,12 @@ async function handleProjectCommand(subcommand, parsed) {
9686
10301
  }
9687
10302
  return;
9688
10303
  }
10304
+ case 'overview':
10305
+ {
10306
+ validateFlags(parsed, FLAGS$c.overview);
10307
+ await handleOverviewCommand(parsed);
10308
+ return;
10309
+ }
9689
10310
  default:
9690
10311
  fail(`Unknown project command: ${subcommand}`);
9691
10312
  }
@@ -9696,40 +10317,42 @@ Usage:
9696
10317
 
9697
10318
  Commands:
9698
10319
  list
9699
- list <orgHandle>
9700
- create <orgHandle> --name <name> [--handle <handle>] [--description <text>] [--repo <url>]
9701
- get <projectRef>
9702
- update <projectRef> [--name <name>] [--handle <handle>] [--description <text>] [--repo <url>] [--branch <name>]
10320
+ list [<orgHandle>]
10321
+ use <projectRef> Set the current project for this environment
10322
+ current Show the current project for this environment
10323
+ create [<orgHandle>] --name <name> [--handle <handle>] [--description <text>] [--repo <url>]
10324
+ get [projectRef]
10325
+ update [projectRef] [--name <name>] [--handle <handle>] [--description <text>] [--repo <url>] [--branch <name>]
9703
10326
  delete <projectRef>
9704
- snippet <projectRef> Output widget embed snippet
9705
- status <projectRef> Show project readiness status
10327
+ snippet [projectRef] Output widget embed snippet
10328
+ status [projectRef] Show project readiness status
10329
+ overview [projectRef] Dashboard summary: sessions, signals, tasks, screeners
9706
10330
 
9707
10331
  Options:
9708
10332
  --env <env> stage | production | local
9709
10333
  --json JSON output
9710
10334
 
9711
10335
  Discovery:
9712
- Need an org handle for \`list\` or \`create\`? Run \`usertold auth whoami --json\`
9713
- and use \`profile.personal_org_handle\` for your personal workspace.
10336
+ \`list\` and \`create\` default to your personal workspace from \`usertold auth whoami --json\`.
10337
+ Pass \`<orgHandle>\` or \`--org <orgHandle>\` to target another workspace.
10338
+ Set a current project with \`usertold project use <projectRef>\` to omit <projectRef>
10339
+ from project-scoped commands.
9714
10340
 
9715
10341
  Examples:
9716
10342
  usertold auth whoami --json
10343
+ usertold project list --env stage
9717
10344
  usertold project list acme --env stage
9718
- usertold project create acme --name "Demo" --description "Smoke test"
10345
+ usertold project create --name "Demo" --description "Smoke test"
10346
+ usertold project use acme/checkout
9719
10347
  usertold project update acme/checkout --repo https://github.com/acme/app --branch main
9720
10348
  usertold project snippet acme/checkout
9721
10349
  usertold project status acme/checkout --json
10350
+ usertold project overview acme/checkout
10351
+ usertold project overview acme/checkout --json
9722
10352
  `;
9723
10353
  function printProjectHelp() {
9724
10354
  console.log(PROJECT_HELP);
9725
10355
  }
9726
- function requireOrgHandleForProjectSetup(parsed, commandLabel) {
9727
- const orgHandle = parsed.positionals[0];
9728
- if (!orgHandle) {
9729
- failArgs(`Missing required organization handle for "${commandLabel}". ` + 'Discover it with "usertold auth whoami --json" and use "profile.personal_org_handle" as <orgHandle> or --org.');
9730
- }
9731
- return orgHandle;
9732
- }
9733
10356
 
9734
10357
  const PROCESSING_STATUS_FILTERS = new Set([
9735
10358
  'failed',
@@ -9919,7 +10542,7 @@ function readProcessedStatus(event) {
9919
10542
  }
9920
10543
  }
9921
10544
 
9922
- const FLAGS$c = {
10545
+ const FLAGS$b = {
9923
10546
  list: [
9924
10547
  'status',
9925
10548
  'study',
@@ -9963,7 +10586,7 @@ function projectApi(projectRef) {
9963
10586
  return buildProjectApiPathFromRef(projectRef, '', '<projectRef>');
9964
10587
  }
9965
10588
  // ─── Helpers ─────────────────────────────────────────────────────────────────
9966
- function formatDuration$1(seconds) {
10589
+ function formatDuration(seconds) {
9967
10590
  if (seconds == null || seconds < 0) return '-';
9968
10591
  const mins = Math.floor(seconds / 60);
9969
10592
  const secs = seconds % 60;
@@ -10068,11 +10691,11 @@ const SPINNER_FRAMES = [
10068
10691
  '\u280F'
10069
10692
  ];
10070
10693
  async function pollUntilDone(opts) {
10071
- const deadline = opts.timeoutMs === Infinity ? Infinity : Date.now() + opts.timeoutMs;
10694
+ const startedAt = performance.now();
10072
10695
  let frame = 0;
10073
10696
  let lastEventCount = 0;
10074
10697
  let lastSignalCount = 0;
10075
- for(;;){
10698
+ while(true){
10076
10699
  const status = await fetchProcessingStatus(opts.env, opts.projectId, opts.sessionId);
10077
10700
  if (opts.json) {
10078
10701
  console.log(JSON.stringify(status));
@@ -10132,13 +10755,16 @@ async function pollUntilDone(opts) {
10132
10755
  if (!opts.json) process.stderr.write('\n');
10133
10756
  process.exit(status.status === 'failed' ? 1 : 0);
10134
10757
  }
10135
- if (Date.now() >= deadline) {
10136
- if (!opts.json) process.stderr.write('\n');
10137
- process.stderr.write('Timed out waiting for processing to complete.\n');
10138
- process.exit(2);
10139
- }
10758
+ // Deadline is enforced *after* the status check so that a terminal state
10759
+ // arriving during the final sleep window is still observed. Monotonic
10760
+ // clock so wall-clock jumps (NTP, manual time changes) can't shorten or
10761
+ // extend the wait.
10762
+ if (opts.timeoutMs !== Infinity && performance.now() - startedAt >= opts.timeoutMs) break;
10140
10763
  await setTimeout$1(opts.intervalMs);
10141
10764
  }
10765
+ if (!opts.json) process.stderr.write('\n');
10766
+ process.stderr.write('Timed out waiting for processing to complete.\n');
10767
+ process.exit(2);
10142
10768
  }
10143
10769
  // ─── Command handler ─────────────────────────────────────────────────────────
10144
10770
  async function handleSessionCommand(subcommand, parsed) {
@@ -10150,9 +10776,11 @@ async function handleSessionCommand(subcommand, parsed) {
10150
10776
  switch(subcommand){
10151
10777
  case 'list':
10152
10778
  {
10153
- validateFlags(parsed, FLAGS$c.list);
10154
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10155
- assertNoExtraPositionals(parsed, 1);
10779
+ validateFlags(parsed, FLAGS$b.list);
10780
+ const { projectRef: projectId } = await consumeProjectRef(parsed, env, {
10781
+ resourceArgCount: 0,
10782
+ commandLabel: 'session list'
10783
+ });
10156
10784
  const params = new URLSearchParams();
10157
10785
  if (parsed.options.status && parsed.options.status !== 'true') {
10158
10786
  params.set('status', parsed.options.status);
@@ -10184,8 +10812,10 @@ async function handleSessionCommand(subcommand, parsed) {
10184
10812
  }
10185
10813
  case 'create':
10186
10814
  {
10187
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10188
- assertNoExtraPositionals(parsed, 1);
10815
+ const { projectRef: projectId } = await consumeProjectRef(parsed, env, {
10816
+ resourceArgCount: 0,
10817
+ commandLabel: 'session create'
10818
+ });
10189
10819
  const body = {};
10190
10820
  if (parsed.options.name && parsed.options.name !== 'true') {
10191
10821
  body.participant_name = parsed.options.name;
@@ -10222,9 +10852,11 @@ async function handleSessionCommand(subcommand, parsed) {
10222
10852
  }
10223
10853
  case 'get':
10224
10854
  {
10225
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10226
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10227
- assertNoExtraPositionals(parsed, 2);
10855
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
10856
+ resourceArgCount: 1,
10857
+ commandLabel: 'session get'
10858
+ });
10859
+ const sessionId = args[0];
10228
10860
  const data = await requestProjectContractJson('sessionGet', {
10229
10861
  env,
10230
10862
  projectRef: projectId,
@@ -10238,9 +10870,11 @@ async function handleSessionCommand(subcommand, parsed) {
10238
10870
  // ── Phase 1.1: session status ──────────────────────────────────────────
10239
10871
  case 'status':
10240
10872
  {
10241
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10242
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10243
- assertNoExtraPositionals(parsed, 2);
10873
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
10874
+ resourceArgCount: 1,
10875
+ commandLabel: 'session status'
10876
+ });
10877
+ const sessionId = args[0];
10244
10878
  const data = await fetchSessionDetail(env, projectId, sessionId);
10245
10879
  const { session, events } = data;
10246
10880
  const processingDetails = deriveProcessingDetails(events);
@@ -10293,7 +10927,7 @@ async function handleSessionCommand(subcommand, parsed) {
10293
10927
  console.log(`Session: ${session.id}`);
10294
10928
  console.log(`Status: ${session.status}`);
10295
10929
  console.log(`Processing: ${processingDetail}`);
10296
- console.log(`Duration: ${formatDuration$1(session.duration_seconds)}`);
10930
+ console.log(`Duration: ${formatDuration(session.duration_seconds)}`);
10297
10931
  console.log(`Audio: ${hasAudio ? 'available' : 'not available'}`);
10298
10932
  console.log(`Screen: ${hasScreen ? 'available' : 'not available'}`);
10299
10933
  console.log(`Signals: ${signalCount}`);
@@ -10305,9 +10939,11 @@ async function handleSessionCommand(subcommand, parsed) {
10305
10939
  // ── Phase 1.2: session events ──────────────────────────────────────────
10306
10940
  case 'events':
10307
10941
  {
10308
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10309
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10310
- assertNoExtraPositionals(parsed, 2);
10942
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
10943
+ resourceArgCount: 1,
10944
+ commandLabel: 'session events'
10945
+ });
10946
+ const sessionId = args[0];
10311
10947
  const data = await fetchSessionDetail(env, projectId, sessionId);
10312
10948
  let { events } = data;
10313
10949
  const sessionStartMs = data.session.started_at ? new Date(data.session.started_at).getTime() : events.length > 0 ? events[0].timestamp_ms : 0;
@@ -10340,9 +10976,11 @@ async function handleSessionCommand(subcommand, parsed) {
10340
10976
  }
10341
10977
  case 'update':
10342
10978
  {
10343
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10344
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10345
- assertNoExtraPositionals(parsed, 2);
10979
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
10980
+ resourceArgCount: 1,
10981
+ commandLabel: 'session update'
10982
+ });
10983
+ const sessionId = args[0];
10346
10984
  const body = {};
10347
10985
  if (parsed.options.status && parsed.options.status !== 'true') {
10348
10986
  body.status = parsed.options.status;
@@ -10366,9 +11004,11 @@ async function handleSessionCommand(subcommand, parsed) {
10366
11004
  }
10367
11005
  case 'delete':
10368
11006
  {
10369
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10370
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10371
- assertNoExtraPositionals(parsed, 2);
11007
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11008
+ resourceArgCount: 1,
11009
+ commandLabel: 'session delete'
11010
+ });
11011
+ const sessionId = args[0];
10372
11012
  if (getBooleanOption(parsed, 'dry-run')) {
10373
11013
  if (isJsonOutput(parsed)) {
10374
11014
  console.log(JSON.stringify({
@@ -10395,9 +11035,11 @@ async function handleSessionCommand(subcommand, parsed) {
10395
11035
  // ── Phase 2.2: transcript (R2 first, then fallback) ────────────────────
10396
11036
  case 'transcript':
10397
11037
  {
10398
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10399
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10400
- assertNoExtraPositionals(parsed, 2);
11038
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11039
+ resourceArgCount: 1,
11040
+ commandLabel: 'session transcript'
11041
+ });
11042
+ const sessionId = args[0];
10401
11043
  const useRaw = getBooleanOption(parsed, 'raw');
10402
11044
  // Try R2 transcript first (unless --raw)
10403
11045
  if (!useRaw && !getBooleanOption(parsed, 'json')) {
@@ -10440,9 +11082,11 @@ async function handleSessionCommand(subcommand, parsed) {
10440
11082
  // ── Phase 2.4: timeline ────────────────────────────────────────────────
10441
11083
  case 'timeline':
10442
11084
  {
10443
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10444
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10445
- assertNoExtraPositionals(parsed, 2);
11085
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11086
+ resourceArgCount: 1,
11087
+ commandLabel: 'session timeline'
11088
+ });
11089
+ const sessionId = args[0];
10446
11090
  const r2Text = await requestProjectContractText('sessionTimeline', {
10447
11091
  env,
10448
11092
  projectRef: projectId,
@@ -10469,9 +11113,11 @@ async function handleSessionCommand(subcommand, parsed) {
10469
11113
  // ── Enriched timeline ────────────────────────────────────────────────
10470
11114
  case 'enriched-timeline':
10471
11115
  {
10472
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10473
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10474
- assertNoExtraPositionals(parsed, 2);
11116
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11117
+ resourceArgCount: 1,
11118
+ commandLabel: 'session enriched-timeline'
11119
+ });
11120
+ const sessionId = args[0];
10475
11121
  const r2Text = await requestProjectContractText('sessionEnrichedTimeline', {
10476
11122
  env,
10477
11123
  projectRef: projectId,
@@ -10528,9 +11174,11 @@ async function handleSessionCommand(subcommand, parsed) {
10528
11174
  // ── Phase 1.4: media (absolute URLs) ───────────────────────────────────
10529
11175
  case 'media':
10530
11176
  {
10531
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10532
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10533
- assertNoExtraPositionals(parsed, 2);
11177
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11178
+ resourceArgCount: 1,
11179
+ commandLabel: 'session media'
11180
+ });
11181
+ const sessionId = args[0];
10534
11182
  const baseUrl = resolveBaseUrl(env);
10535
11183
  const data = await requestProjectContractJson('sessionGet', {
10536
11184
  env,
@@ -10571,9 +11219,11 @@ async function handleSessionCommand(subcommand, parsed) {
10571
11219
  // ── Phase 2.5: audio download ──────────────────────────────────────────
10572
11220
  case 'audio':
10573
11221
  {
10574
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10575
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10576
- assertNoExtraPositionals(parsed, 2);
11222
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11223
+ resourceArgCount: 1,
11224
+ commandLabel: 'session audio'
11225
+ });
11226
+ const sessionId = args[0];
10577
11227
  const outputOpt = parsed.options.output && parsed.options.output !== 'true' ? parsed.options.output : null;
10578
11228
  const outPath = outputOpt || `${sessionId}-audio.webm`;
10579
11229
  const buffer = await requestProjectContractBinary('sessionMediaAudioFull', {
@@ -10590,9 +11240,11 @@ async function handleSessionCommand(subcommand, parsed) {
10590
11240
  // ── Screen download ─────────────────────────────────────────────────
10591
11241
  case 'screen':
10592
11242
  {
10593
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10594
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10595
- assertNoExtraPositionals(parsed, 2);
11243
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11244
+ resourceArgCount: 1,
11245
+ commandLabel: 'session screen'
11246
+ });
11247
+ const sessionId = args[0];
10596
11248
  const outputOpt = parsed.options.output && parsed.options.output !== 'true' ? parsed.options.output : null;
10597
11249
  const outPath = outputOpt || `${sessionId}-screen.webm`;
10598
11250
  const buffer = await requestProjectContractBinary('sessionMediaScreenFull', {
@@ -10608,9 +11260,11 @@ async function handleSessionCommand(subcommand, parsed) {
10608
11260
  }
10609
11261
  case 'reprocess':
10610
11262
  {
10611
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10612
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10613
- assertNoExtraPositionals(parsed, 2);
11263
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11264
+ resourceArgCount: 1,
11265
+ commandLabel: 'session reprocess'
11266
+ });
11267
+ const sessionId = args[0];
10614
11268
  const data = await requestProjectContractJson('sessionReprocess', {
10615
11269
  env,
10616
11270
  projectRef: projectId,
@@ -10636,9 +11290,11 @@ async function handleSessionCommand(subcommand, parsed) {
10636
11290
  }
10637
11291
  case 'retry-media-merge':
10638
11292
  {
10639
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10640
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10641
- assertNoExtraPositionals(parsed, 2);
11293
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11294
+ resourceArgCount: 1,
11295
+ commandLabel: 'session retry-media-merge'
11296
+ });
11297
+ const sessionId = args[0];
10642
11298
  const data = await requestProjectContractJson('sessionRetryMediaMerge', {
10643
11299
  env,
10644
11300
  projectRef: projectId,
@@ -10651,9 +11307,11 @@ async function handleSessionCommand(subcommand, parsed) {
10651
11307
  }
10652
11308
  case 'watch':
10653
11309
  {
10654
- const projectId = requirePositional(parsed, 0, '<projectRef>');
10655
- const sessionId = requirePositional(parsed, 1, '<sessionId>');
10656
- assertNoExtraPositionals(parsed, 2);
11310
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
11311
+ resourceArgCount: 1,
11312
+ commandLabel: 'session watch'
11313
+ });
11314
+ const sessionId = args[0];
10657
11315
  const intervalStr = parsed.options.interval && parsed.options.interval !== 'true' ? parsed.options.interval : '4';
10658
11316
  const intervalSec = parseInt(intervalStr, 10);
10659
11317
  if (isNaN(intervalSec) || intervalSec < 1) fail('--interval must be a positive integer (seconds)');
@@ -10678,23 +11336,23 @@ Usage:
10678
11336
  usertold session <command> [options]
10679
11337
 
10680
11338
  Commands:
10681
- list <projectRef> [--status <status>] [--processing-status <failed|done>] [--study <studyId>] [--limit <n>] [--offset <n>]
10682
- create <projectRef> [--name <participant>] [--email <email>] [--mode <voice|text|async>]
11339
+ list [projectRef] [--status <status>] [--processing-status <failed|done>] [--study <studyId>] [--limit <n>] [--offset <n>]
11340
+ create [projectRef] [--name <participant>] [--email <email>] [--mode <voice|text|async>]
10683
11341
  end <sessionId> --key <projectPublicOrSecretKey>
10684
- get <projectRef> <sessionId>
10685
- status <projectRef> <sessionId> Show session + processing status summary
10686
- events <projectRef> <sessionId> [--type <type>] Show session events (processing|conductor|all)
10687
- update <projectRef> <sessionId> [--status <status>] [--summary "..."]
10688
- delete <projectRef> <sessionId>
10689
- transcript <projectRef> <sessionId> Print transcript (--raw for messages-only fallback)
10690
- timeline <projectRef> <sessionId> Print session timeline from R2
10691
- enriched-timeline <projectRef> <sessionId> Print enriched timeline (diarized + annotations)
10692
- screen <projectRef> <sessionId> [--output file.webm] Download screen recording
10693
- media <projectRef> <sessionId> Show audio/screen chunk counts and media URLs
10694
- audio <projectRef> <sessionId> [--chunk <n>] [--output file.webm] Download audio
10695
- reprocess <projectRef> <sessionId> [--wait] [--timeout <s>]
10696
- retry-media-merge <projectRef> <sessionId> Retry audio/screen media merge
10697
- watch <projectRef> <sessionId> [--interval <s>] [--verbose] [--signals] Poll processing status until done
11342
+ get [projectRef] <sessionId>
11343
+ status [projectRef] <sessionId> Show session + processing status summary
11344
+ events [projectRef] <sessionId> [--type <type>] Show session events (processing|conductor|all)
11345
+ update [projectRef] <sessionId> [--status <status>] [--summary "..."]
11346
+ delete [projectRef] <sessionId>
11347
+ transcript [projectRef] <sessionId> Print transcript (--raw for messages-only fallback)
11348
+ timeline [projectRef] <sessionId> Print session timeline from R2
11349
+ enriched-timeline [projectRef] <sessionId> Print enriched timeline (diarized + annotations)
11350
+ screen [projectRef] <sessionId> [--output file.webm] Download screen recording
11351
+ media [projectRef] <sessionId> Show audio/screen chunk counts and media URLs
11352
+ audio [projectRef] <sessionId> [--chunk <n>] [--output file.webm] Download audio
11353
+ reprocess [projectRef] <sessionId> [--wait] [--timeout <s>]
11354
+ retry-media-merge [projectRef] <sessionId> Retry audio/screen media merge
11355
+ watch [projectRef] <sessionId> [--interval <s>] [--verbose] [--signals] Poll processing status until done
10698
11356
 
10699
11357
  Options:
10700
11358
  --wait Block until processing completes (reprocess, study reprocess)
@@ -10703,6 +11361,8 @@ Options:
10703
11361
  --json JSON output
10704
11362
 
10705
11363
  Examples:
11364
+ usertold project use acme/checkout
11365
+ usertold session status ses_123
10706
11366
  usertold session status acme/checkout ses_123
10707
11367
  usertold session events acme/checkout ses_123 --type processing
10708
11368
  usertold session transcript acme/checkout ses_123
@@ -10722,9 +11382,10 @@ function printSessionHelp() {
10722
11382
  console.log(SESSION_HELP);
10723
11383
  }
10724
11384
 
10725
- const FLAGS$b = {
11385
+ const FLAGS$a = {
10726
11386
  list: [
10727
11387
  'type',
11388
+ 'target-surface',
10728
11389
  'session',
10729
11390
  'task',
10730
11391
  'search',
@@ -10761,9 +11422,11 @@ async function handleSignalCommand(subcommand, parsed) {
10761
11422
  switch(subcommand){
10762
11423
  case 'list':
10763
11424
  {
10764
- validateFlags(parsed, FLAGS$b.list);
10765
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10766
- assertNoExtraPositionals(parsed, 1);
11425
+ validateFlags(parsed, FLAGS$a.list);
11426
+ const { projectRef } = await consumeProjectRef(parsed, env, {
11427
+ resourceArgCount: 0,
11428
+ commandLabel: 'signal list'
11429
+ });
10767
11430
  const query = buildSignalQuery(parsed);
10768
11431
  const data = await requestProjectContract({
10769
11432
  env,
@@ -10781,10 +11444,12 @@ async function handleSignalCommand(subcommand, parsed) {
10781
11444
  }
10782
11445
  case 'get':
10783
11446
  {
10784
- validateFlags(parsed, FLAGS$b.get);
10785
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10786
- const signalId = requirePositional(parsed, 1, '<signalId>');
10787
- assertNoExtraPositionals(parsed, 2);
11447
+ validateFlags(parsed, FLAGS$a.get);
11448
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11449
+ resourceArgCount: 1,
11450
+ commandLabel: 'signal get'
11451
+ });
11452
+ const signalId = args[0];
10788
11453
  const data = await requestProjectContract({
10789
11454
  env,
10790
11455
  key: 'signalGet',
@@ -10799,10 +11464,12 @@ async function handleSignalCommand(subcommand, parsed) {
10799
11464
  }
10800
11465
  case 'annotate':
10801
11466
  {
10802
- validateFlags(parsed, FLAGS$b.annotate);
10803
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10804
- const signalId = requirePositional(parsed, 1, '<signalId>');
10805
- assertNoExtraPositionals(parsed, 2);
11467
+ validateFlags(parsed, FLAGS$a.annotate);
11468
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11469
+ resourceArgCount: 1,
11470
+ commandLabel: 'signal annotate'
11471
+ });
11472
+ const signalId = args[0];
10806
11473
  const text = requireOption(parsed, 'text');
10807
11474
  const data = await requestProjectContract({
10808
11475
  env,
@@ -10821,10 +11488,12 @@ async function handleSignalCommand(subcommand, parsed) {
10821
11488
  }
10822
11489
  case 'dismiss':
10823
11490
  {
10824
- validateFlags(parsed, FLAGS$b.dismiss);
10825
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10826
- const signalId = requirePositional(parsed, 1, '<signalId>');
10827
- assertNoExtraPositionals(parsed, 2);
11491
+ validateFlags(parsed, FLAGS$a.dismiss);
11492
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11493
+ resourceArgCount: 1,
11494
+ commandLabel: 'signal dismiss'
11495
+ });
11496
+ const signalId = args[0];
10828
11497
  const reason = requireOption(parsed, 'reason');
10829
11498
  const data = await requestProjectContract({
10830
11499
  env,
@@ -10843,10 +11512,12 @@ async function handleSignalCommand(subcommand, parsed) {
10843
11512
  }
10844
11513
  case 'undismiss':
10845
11514
  {
10846
- validateFlags(parsed, FLAGS$b.undismiss);
10847
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10848
- const signalId = requirePositional(parsed, 1, '<signalId>');
10849
- assertNoExtraPositionals(parsed, 2);
11515
+ validateFlags(parsed, FLAGS$a.undismiss);
11516
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11517
+ resourceArgCount: 1,
11518
+ commandLabel: 'signal undismiss'
11519
+ });
11520
+ const signalId = args[0];
10850
11521
  const data = await requestProjectContract({
10851
11522
  env,
10852
11523
  key: 'signalUndismiss',
@@ -10861,11 +11532,12 @@ async function handleSignalCommand(subcommand, parsed) {
10861
11532
  }
10862
11533
  case 'link':
10863
11534
  {
10864
- validateFlags(parsed, FLAGS$b.link);
10865
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10866
- const signalId = requirePositional(parsed, 1, '<signalId>');
10867
- const taskId = requirePositional(parsed, 2, '<taskId>');
10868
- assertNoExtraPositionals(parsed, 3);
11535
+ validateFlags(parsed, FLAGS$a.link);
11536
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11537
+ resourceArgCount: 2,
11538
+ commandLabel: 'signal link'
11539
+ });
11540
+ const [signalId, taskId] = args;
10869
11541
  const data = await requestProjectContract({
10870
11542
  env,
10871
11543
  key: 'signalLink',
@@ -10883,10 +11555,12 @@ async function handleSignalCommand(subcommand, parsed) {
10883
11555
  }
10884
11556
  case 'unlink':
10885
11557
  {
10886
- validateFlags(parsed, FLAGS$b.unlink);
10887
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10888
- const signalId = requirePositional(parsed, 1, '<signalId>');
10889
- assertNoExtraPositionals(parsed, 2);
11558
+ validateFlags(parsed, FLAGS$a.unlink);
11559
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11560
+ resourceArgCount: 1,
11561
+ commandLabel: 'signal unlink'
11562
+ });
11563
+ const signalId = args[0];
10890
11564
  const data = await requestProjectContract({
10891
11565
  env,
10892
11566
  key: 'signalUnlink',
@@ -10901,10 +11575,12 @@ async function handleSignalCommand(subcommand, parsed) {
10901
11575
  }
10902
11576
  case 'delete':
10903
11577
  {
10904
- validateFlags(parsed, FLAGS$b.delete);
10905
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10906
- const signalId = requirePositional(parsed, 1, '<signalId>');
10907
- assertNoExtraPositionals(parsed, 2);
11578
+ validateFlags(parsed, FLAGS$a.delete);
11579
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11580
+ resourceArgCount: 1,
11581
+ commandLabel: 'signal delete'
11582
+ });
11583
+ const signalId = args[0];
10908
11584
  if (getBooleanOption(parsed, 'dry-run')) {
10909
11585
  if (isJsonOutput(parsed)) {
10910
11586
  console.log(JSON.stringify({
@@ -10932,10 +11608,12 @@ async function handleSignalCommand(subcommand, parsed) {
10932
11608
  }
10933
11609
  case 'bulk-link':
10934
11610
  {
10935
- validateFlags(parsed, FLAGS$b['bulk-link']);
10936
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10937
- const taskId = requirePositional(parsed, 1, '<taskId>');
10938
- assertNoExtraPositionals(parsed, 2);
11611
+ validateFlags(parsed, FLAGS$a['bulk-link']);
11612
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11613
+ resourceArgCount: 1,
11614
+ commandLabel: 'signal bulk-link'
11615
+ });
11616
+ const taskId = args[0];
10939
11617
  const signalIds = parseSignalIdsOption(parsed);
10940
11618
  const data = await requestProjectContract({
10941
11619
  env,
@@ -10952,9 +11630,11 @@ async function handleSignalCommand(subcommand, parsed) {
10952
11630
  }
10953
11631
  case 'bulk-delete':
10954
11632
  {
10955
- validateFlags(parsed, FLAGS$b['bulk-delete']);
10956
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
10957
- assertNoExtraPositionals(parsed, 1);
11633
+ validateFlags(parsed, FLAGS$a['bulk-delete']);
11634
+ const { projectRef } = await consumeProjectRef(parsed, env, {
11635
+ resourceArgCount: 0,
11636
+ commandLabel: 'signal bulk-delete'
11637
+ });
10958
11638
  const signalIds = parseSignalIdsOption(parsed);
10959
11639
  if (getBooleanOption(parsed, 'dry-run')) {
10960
11640
  if (isJsonOutput(parsed)) {
@@ -10990,19 +11670,20 @@ Usage:
10990
11670
  usertold signal <command> [options]
10991
11671
 
10992
11672
  Commands:
10993
- list <projectRef> [filters] List signals (excludes dismissed by default)
10994
- get <projectRef> <signalId> Get signal details
10995
- annotate <projectRef> <signalId> --text "..." Add human annotation
10996
- dismiss <projectRef> <signalId> --reason "..." Soft-exclude signal
10997
- undismiss <projectRef> <signalId> Restore dismissed signal
10998
- link <projectRef> <signalId> <taskId> Link signal to task
10999
- unlink <projectRef> <signalId> Unlink signal from task
11000
- delete <projectRef> <signalId> Permanently delete a signal
11001
- bulk-link <projectRef> <taskId> --signals <id1,id2,...> Link multiple signals to one task
11002
- bulk-delete <projectRef> --signals <id1,id2,...> Permanently delete multiple signals
11673
+ list [projectRef] [filters] List signals (excludes dismissed by default)
11674
+ get [projectRef] <signalId> Get signal details
11675
+ annotate [projectRef] <signalId> --text "..." Add human annotation
11676
+ dismiss [projectRef] <signalId> --reason "..." Soft-exclude signal
11677
+ undismiss [projectRef] <signalId> Restore dismissed signal
11678
+ link [projectRef] <signalId> <taskId> Link signal to task
11679
+ unlink [projectRef] <signalId> Unlink signal from task
11680
+ delete [projectRef] <signalId> Permanently delete a signal
11681
+ bulk-link [projectRef] <taskId> --signals <id1,id2,...> Link multiple signals to one task
11682
+ bulk-delete [projectRef] --signals <id1,id2,...> Permanently delete multiple signals
11003
11683
 
11004
11684
  List filters:
11005
11685
  --type <type> Signal type (struggling_moment, desired_outcome, etc.)
11686
+ --target-surface <surface> Filter by target surface (defaults to product_under_test; use all for every surface)
11006
11687
  --session <sessionId> Filter by session
11007
11688
  --task <taskId> Filter by task (use "none" for unlinked)
11008
11689
  --search <text> Search in quotes
@@ -11017,6 +11698,8 @@ Options:
11017
11698
  --json JSON output
11018
11699
 
11019
11700
  Examples:
11701
+ usertold project use acme/checkout
11702
+ usertold signal list --type struggling_moment
11020
11703
  usertold signal list acme/checkout --type struggling_moment
11021
11704
  usertold signal list acme/checkout --dismissed
11022
11705
  usertold signal list acme/checkout --all --limit 50
@@ -11040,6 +11723,7 @@ function printSignalCards(data) {
11040
11723
  for (const s of signals){
11041
11724
  const conf = typeof s.confidence === 'number' ? `${Math.round(s.confidence * 100)}%` : '';
11042
11725
  console.log(`[${s.signal_type}] ${s.id} (confidence: ${conf})`);
11726
+ console.log(` Target surface: ${s.target_surface}`);
11043
11727
  console.log(` "${s.quote}"`);
11044
11728
  if (s.context) {
11045
11729
  const contextParts = s.context.split('\n').filter(Boolean).map((l)=>l.trim()).join(' ');
@@ -11059,6 +11743,9 @@ function buildSignalQuery(parsed) {
11059
11743
  if (parsed.options.type && parsed.options.type !== 'true') {
11060
11744
  query.type = parsed.options.type;
11061
11745
  }
11746
+ if (parsed.options['target-surface'] && parsed.options['target-surface'] !== 'true') {
11747
+ query.target_surface = parsed.options['target-surface'];
11748
+ }
11062
11749
  if (parsed.options.session && parsed.options.session !== 'true') {
11063
11750
  query.session_id = parsed.options.session;
11064
11751
  }
@@ -11094,10 +11781,11 @@ function parseSignalIdsOption(parsed) {
11094
11781
  return signalIds;
11095
11782
  }
11096
11783
 
11097
- const FLAGS$a = {
11784
+ const FLAGS$9 = {
11098
11785
  list: [
11099
11786
  'status',
11100
11787
  'type',
11788
+ 'target-surface',
11101
11789
  'session',
11102
11790
  'min-priority',
11103
11791
  'limit',
@@ -11126,7 +11814,6 @@ const FLAGS$a = {
11126
11814
  'priority'
11127
11815
  ],
11128
11816
  delete: [],
11129
- measure: [],
11130
11817
  push: [
11131
11818
  'provider'
11132
11819
  ],
@@ -11147,9 +11834,11 @@ async function handleTaskCommand(subcommand, parsed) {
11147
11834
  switch(subcommand){
11148
11835
  case 'list':
11149
11836
  {
11150
- validateFlags(parsed, FLAGS$a.list);
11151
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11152
- assertNoExtraPositionals(parsed, 1);
11837
+ validateFlags(parsed, FLAGS$9.list);
11838
+ const { projectRef } = await consumeProjectRef(parsed, env, {
11839
+ resourceArgCount: 0,
11840
+ commandLabel: 'task list'
11841
+ });
11153
11842
  const query = {};
11154
11843
  if (parsed.options.status && parsed.options.status !== 'true') {
11155
11844
  query.status = parsed.options.status;
@@ -11157,6 +11846,9 @@ async function handleTaskCommand(subcommand, parsed) {
11157
11846
  if (parsed.options.type && parsed.options.type !== 'true') {
11158
11847
  query.type = parsed.options.type;
11159
11848
  }
11849
+ if (parsed.options['target-surface'] && parsed.options['target-surface'] !== 'true') {
11850
+ query.target_surface = parsed.options['target-surface'];
11851
+ }
11160
11852
  if (parsed.options.session && parsed.options.session !== 'true') {
11161
11853
  query.session_id = parsed.options.session;
11162
11854
  }
@@ -11181,10 +11873,12 @@ async function handleTaskCommand(subcommand, parsed) {
11181
11873
  }
11182
11874
  case 'get':
11183
11875
  {
11184
- validateFlags(parsed, FLAGS$a.get);
11185
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11186
- const taskId = requirePositional(parsed, 1, '<taskId>');
11187
- assertNoExtraPositionals(parsed, 2);
11876
+ validateFlags(parsed, FLAGS$9.get);
11877
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11878
+ resourceArgCount: 1,
11879
+ commandLabel: 'task get'
11880
+ });
11881
+ const taskId = args[0];
11188
11882
  const data = await requestProjectContract({
11189
11883
  env,
11190
11884
  key: 'taskGet',
@@ -11199,9 +11893,11 @@ async function handleTaskCommand(subcommand, parsed) {
11199
11893
  }
11200
11894
  case 'create':
11201
11895
  {
11202
- validateFlags(parsed, FLAGS$a.create);
11203
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11204
- assertNoExtraPositionals(parsed, 1);
11896
+ validateFlags(parsed, FLAGS$9.create);
11897
+ const { projectRef } = await consumeProjectRef(parsed, env, {
11898
+ resourceArgCount: 0,
11899
+ commandLabel: 'task create'
11900
+ });
11205
11901
  const title = requireOption(parsed, 'title');
11206
11902
  const body = {
11207
11903
  title
@@ -11230,10 +11926,12 @@ async function handleTaskCommand(subcommand, parsed) {
11230
11926
  }
11231
11927
  case 'update':
11232
11928
  {
11233
- validateFlags(parsed, FLAGS$a.update);
11234
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11235
- const taskId = requirePositional(parsed, 1, '<taskId>');
11236
- assertNoExtraPositionals(parsed, 2);
11929
+ validateFlags(parsed, FLAGS$9.update);
11930
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11931
+ resourceArgCount: 1,
11932
+ commandLabel: 'task update'
11933
+ });
11934
+ const taskId = args[0];
11237
11935
  const body = {};
11238
11936
  if (parsed.options.title && parsed.options.title !== 'true') {
11239
11937
  body.title = parsed.options.title;
@@ -11271,10 +11969,12 @@ async function handleTaskCommand(subcommand, parsed) {
11271
11969
  }
11272
11970
  case 'delete':
11273
11971
  {
11274
- validateFlags(parsed, FLAGS$a.delete);
11275
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11276
- const taskId = requirePositional(parsed, 1, '<taskId>');
11277
- assertNoExtraPositionals(parsed, 2);
11972
+ validateFlags(parsed, FLAGS$9.delete);
11973
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
11974
+ resourceArgCount: 1,
11975
+ commandLabel: 'task delete'
11976
+ });
11977
+ const taskId = args[0];
11278
11978
  if (getBooleanOption(parsed, 'dry-run')) {
11279
11979
  if (isJsonOutput(parsed)) {
11280
11980
  console.log(JSON.stringify({
@@ -11300,30 +12000,14 @@ async function handleTaskCommand(subcommand, parsed) {
11300
12000
  printOutput(data, parsed);
11301
12001
  return;
11302
12002
  }
11303
- case 'measure':
11304
- {
11305
- validateFlags(parsed, FLAGS$a.measure);
11306
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11307
- const taskId = requirePositional(parsed, 1, '<taskId>');
11308
- assertNoExtraPositionals(parsed, 2);
11309
- const data = await requestProjectContract({
11310
- env,
11311
- key: 'taskMeasureImpact',
11312
- projectRef,
11313
- sourceLabel: '<projectRef>',
11314
- pathParams: {
11315
- taskId: taskId
11316
- }
11317
- });
11318
- printOutput(data, parsed);
11319
- return;
11320
- }
11321
12003
  case 'push-status':
11322
12004
  {
11323
- validateFlags(parsed, FLAGS$a['push-status']);
11324
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11325
- const taskId = requirePositional(parsed, 1, '<taskId>');
11326
- assertNoExtraPositionals(parsed, 2);
12005
+ validateFlags(parsed, FLAGS$9['push-status']);
12006
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
12007
+ resourceArgCount: 1,
12008
+ commandLabel: 'task push-status'
12009
+ });
12010
+ const taskId = args[0];
11327
12011
  const data = await requestProjectContract({
11328
12012
  env,
11329
12013
  key: 'taskProviderState',
@@ -11338,10 +12022,12 @@ async function handleTaskCommand(subcommand, parsed) {
11338
12022
  }
11339
12023
  case 'push':
11340
12024
  {
11341
- validateFlags(parsed, FLAGS$a.push);
11342
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11343
- const taskId = requirePositional(parsed, 1, '<taskId>');
11344
- assertNoExtraPositionals(parsed, 2);
12025
+ validateFlags(parsed, FLAGS$9.push);
12026
+ const { projectRef, args } = await consumeProjectRef(parsed, env, {
12027
+ resourceArgCount: 1,
12028
+ commandLabel: 'task push'
12029
+ });
12030
+ const taskId = args[0];
11345
12031
  const provider = parsed.options.provider && parsed.options.provider !== 'true' ? parsed.options.provider : null;
11346
12032
  if (provider && provider !== 'github' && provider !== 'linear' && provider !== 'auto') {
11347
12033
  fail('--provider must be one of: github, linear, auto');
@@ -11375,9 +12061,11 @@ async function handleTaskCommand(subcommand, parsed) {
11375
12061
  }
11376
12062
  case 'create-from-signals':
11377
12063
  {
11378
- validateFlags(parsed, FLAGS$a['create-from-signals']);
11379
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
11380
- assertNoExtraPositionals(parsed, 1);
12064
+ validateFlags(parsed, FLAGS$9['create-from-signals']);
12065
+ const { projectRef } = await consumeProjectRef(parsed, env, {
12066
+ resourceArgCount: 0,
12067
+ commandLabel: 'task create-from-signals'
12068
+ });
11381
12069
  const title = requireOption(parsed, 'title');
11382
12070
  const signalsStr = requireOption(parsed, 'signals');
11383
12071
  const signalIds = signalsStr.split(',').map((s)=>s.trim()).filter(Boolean);
@@ -11413,29 +12101,31 @@ Usage:
11413
12101
  usertold task <command> [options]
11414
12102
 
11415
12103
  Commands:
11416
- list <projectRef> [--status <status>] [--type <type>] [--session <sessionId>] [--min-priority <n>] [--limit <n>] [--offset <n>]
11417
- get <projectRef> <taskId>
11418
- create <projectRef> --title "..." [--description "..."] [--type <type>] [--effort <xs|s|m|l|xl>] [--priority <0-100>]
11419
- create-from-signals <projectRef> --title "..." --signals <id1,id2,...> [--description "..."] [--type <type>]
11420
- update <projectRef> <taskId> [--title] [--description] [--type] [--status] [--effort] [--priority]
11421
- delete <projectRef> <taskId>
11422
- measure <projectRef> <taskId>
11423
- push <projectRef> <taskId> [--provider <github|linear|auto>]
11424
- push-status <projectRef> <taskId> Check provider state (GitHub issue URL, status)
12104
+ list [projectRef] [--status <status>] [--type <type>] [--target-surface <surface|all>] [--session <sessionId>] [--min-priority <n>] [--limit <n>] [--offset <n>]
12105
+ get [projectRef] <taskId>
12106
+ create [projectRef] --title "..." [--description "..."] [--type <type>] [--effort <xs|s|m|l|xl>] [--priority <0-100>]
12107
+ create-from-signals [projectRef] --title "..." --signals <id1,id2,...> [--description "..."] [--type <type>]
12108
+ update [projectRef] <taskId> [--title] [--description] [--type] [--status] [--effort] [--priority]
12109
+ delete [projectRef] <taskId>
12110
+ push [projectRef] <taskId> [--provider <github|linear|auto>] Send to provider ('auto' uses project settings)
12111
+ push-status [projectRef] <taskId> Inspect provider state — issue/PR URL, sync status, last update
11425
12112
 
11426
12113
  Options:
11427
12114
  --env <env> stage | production | local
12115
+ --target-surface <surface> defaults to product_under_test; use all for every surface
11428
12116
  --json JSON output
11429
12117
 
11430
12118
  Examples:
12119
+ usertold project use acme/checkout
12120
+ usertold task list --status ready
11431
12121
  usertold task list acme/checkout --status ready
11432
12122
  usertold task list acme/checkout --type bug --limit 10
12123
+ usertold task list acme/checkout --target-surface all
11433
12124
  usertold task list acme/checkout --min-priority 60
11434
12125
  usertold task create acme/checkout --title "Fix checkout bug" --type bug --effort s
11435
12126
  usertold task create-from-signals acme/checkout --title "Fix onboarding" --signals sig_1,sig_2,sig_3
11436
12127
  usertold task update acme/checkout tsk_456 --status in_progress --effort m
11437
12128
  usertold task delete acme/checkout tsk_456
11438
- usertold task measure acme/checkout tsk_456
11439
12129
  usertold task push acme/checkout tsk_456
11440
12130
  usertold task push acme/checkout tsk_456 --provider linear
11441
12131
  usertold task push-status acme/checkout tsk_456
@@ -11456,7 +12146,7 @@ function isRetryableTaskPushFailure(response, error) {
11456
12146
  return error instanceof Error;
11457
12147
  }
11458
12148
 
11459
- const FLAGS$9 = {
12149
+ const FLAGS$8 = {
11460
12150
  list: [],
11461
12151
  create: [
11462
12152
  'title',
@@ -11506,9 +12196,11 @@ async function handleScreenerCommand(subcommand, parsed) {
11506
12196
  switch(subcommand){
11507
12197
  case 'list':
11508
12198
  {
11509
- validateFlags(parsed, FLAGS$9.list);
11510
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11511
- assertNoExtraPositionals(parsed, 1);
12199
+ validateFlags(parsed, FLAGS$8.list);
12200
+ const { projectRef: projectId } = await consumeProjectRef(parsed, env, {
12201
+ resourceArgCount: 0,
12202
+ commandLabel: 'screener list'
12203
+ });
11512
12204
  const data = await requestProjectContractJson('screenersList', {
11513
12205
  env,
11514
12206
  projectRef: projectId
@@ -11518,9 +12210,11 @@ async function handleScreenerCommand(subcommand, parsed) {
11518
12210
  }
11519
12211
  case 'create':
11520
12212
  {
11521
- validateFlags(parsed, FLAGS$9.create);
11522
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11523
- assertNoExtraPositionals(parsed, 1);
12213
+ validateFlags(parsed, FLAGS$8.create);
12214
+ const { projectRef: projectId } = await consumeProjectRef(parsed, env, {
12215
+ resourceArgCount: 0,
12216
+ commandLabel: 'screener create'
12217
+ });
11524
12218
  const title = requireOption(parsed, 'title');
11525
12219
  const body = {
11526
12220
  title
@@ -11594,10 +12288,12 @@ async function handleScreenerCommand(subcommand, parsed) {
11594
12288
  }
11595
12289
  case 'get':
11596
12290
  {
11597
- validateFlags(parsed, FLAGS$9.get);
11598
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11599
- const screenerId = requirePositional(parsed, 1, '<screenerRef>');
11600
- assertNoExtraPositionals(parsed, 2);
12291
+ validateFlags(parsed, FLAGS$8.get);
12292
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12293
+ resourceArgCount: 1,
12294
+ commandLabel: 'screener get'
12295
+ });
12296
+ const screenerId = args[0];
11601
12297
  const data = await requestProjectContractJson('screenerGet', {
11602
12298
  env,
11603
12299
  projectRef: projectId,
@@ -11610,10 +12306,12 @@ async function handleScreenerCommand(subcommand, parsed) {
11610
12306
  }
11611
12307
  case 'update':
11612
12308
  {
11613
- validateFlags(parsed, FLAGS$9.update);
11614
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11615
- const screenerId = requirePositional(parsed, 1, '<screenerRef>');
11616
- assertNoExtraPositionals(parsed, 2);
12309
+ validateFlags(parsed, FLAGS$8.update);
12310
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12311
+ resourceArgCount: 1,
12312
+ commandLabel: 'screener update'
12313
+ });
12314
+ const screenerId = args[0];
11617
12315
  const body = {};
11618
12316
  for (const field of [
11619
12317
  'title',
@@ -11650,10 +12348,12 @@ async function handleScreenerCommand(subcommand, parsed) {
11650
12348
  }
11651
12349
  case 'delete':
11652
12350
  {
11653
- validateFlags(parsed, FLAGS$9.delete);
11654
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11655
- const screenerId = requirePositional(parsed, 1, '<screenerRef>');
11656
- assertNoExtraPositionals(parsed, 2);
12351
+ validateFlags(parsed, FLAGS$8.delete);
12352
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12353
+ resourceArgCount: 1,
12354
+ commandLabel: 'screener delete'
12355
+ });
12356
+ const screenerId = args[0];
11657
12357
  if (getBooleanOption(parsed, 'dry-run')) {
11658
12358
  if (isJsonOutput(parsed)) {
11659
12359
  console.log(JSON.stringify({
@@ -11679,10 +12379,12 @@ async function handleScreenerCommand(subcommand, parsed) {
11679
12379
  }
11680
12380
  case 'set-questions':
11681
12381
  {
11682
- validateFlags(parsed, FLAGS$9['set-questions']);
11683
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11684
- const screenerId = requirePositional(parsed, 1, '<screenerRef>');
11685
- assertNoExtraPositionals(parsed, 2);
12382
+ validateFlags(parsed, FLAGS$8['set-questions']);
12383
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12384
+ resourceArgCount: 1,
12385
+ commandLabel: 'screener set-questions'
12386
+ });
12387
+ const screenerId = args[0];
11686
12388
  const questionsInput = requireOption(parsed, 'questions');
11687
12389
  const questions = await parseJsonOrFile(questionsInput, '--questions');
11688
12390
  const data = await requestProjectContractJson('screenerSetQuestions', {
@@ -11700,10 +12402,12 @@ async function handleScreenerCommand(subcommand, parsed) {
11700
12402
  }
11701
12403
  case 'list-responses':
11702
12404
  {
11703
- validateFlags(parsed, FLAGS$9['list-responses']);
11704
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11705
- const screenerId = requirePositional(parsed, 1, '<screenerRef>');
11706
- assertNoExtraPositionals(parsed, 2);
12405
+ validateFlags(parsed, FLAGS$8['list-responses']);
12406
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12407
+ resourceArgCount: 1,
12408
+ commandLabel: 'screener list-responses'
12409
+ });
12410
+ const screenerId = args[0];
11707
12411
  const data = await requestProjectContractJson('screenerGet', {
11708
12412
  env,
11709
12413
  projectRef: projectId,
@@ -11723,11 +12427,12 @@ async function handleScreenerCommand(subcommand, parsed) {
11723
12427
  }
11724
12428
  case 'get-response':
11725
12429
  {
11726
- validateFlags(parsed, FLAGS$9['get-response']);
11727
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11728
- const screenerId = requirePositional(parsed, 1, '<screenerRef>');
11729
- const responseId = requirePositional(parsed, 2, '<responseId>');
11730
- assertNoExtraPositionals(parsed, 3);
12430
+ validateFlags(parsed, FLAGS$8['get-response']);
12431
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12432
+ resourceArgCount: 2,
12433
+ commandLabel: 'screener get-response'
12434
+ });
12435
+ const [screenerId, responseId] = args;
11731
12436
  const data = await requestProjectContractJson('screenerResponseGet', {
11732
12437
  env,
11733
12438
  projectRef: projectId,
@@ -11741,11 +12446,12 @@ async function handleScreenerCommand(subcommand, parsed) {
11741
12446
  }
11742
12447
  case 'qualify-response':
11743
12448
  {
11744
- validateFlags(parsed, FLAGS$9['qualify-response']);
11745
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11746
- const screenerId = requirePositional(parsed, 1, '<screenerRef>');
11747
- const responseId = requirePositional(parsed, 2, '<responseId>');
11748
- assertNoExtraPositionals(parsed, 3);
12449
+ validateFlags(parsed, FLAGS$8['qualify-response']);
12450
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12451
+ resourceArgCount: 2,
12452
+ commandLabel: 'screener qualify-response'
12453
+ });
12454
+ const [screenerId, responseId] = args;
11749
12455
  const reason = parsed.options.reason && parsed.options.reason !== 'true' ? parsed.options.reason : undefined;
11750
12456
  const data = await requestProjectContractJson('screenerResponsePatch', {
11751
12457
  env,
@@ -11764,11 +12470,12 @@ async function handleScreenerCommand(subcommand, parsed) {
11764
12470
  }
11765
12471
  case 'disqualify-response':
11766
12472
  {
11767
- validateFlags(parsed, FLAGS$9['disqualify-response']);
11768
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11769
- const screenerId = requirePositional(parsed, 1, '<screenerRef>');
11770
- const responseId = requirePositional(parsed, 2, '<responseId>');
11771
- assertNoExtraPositionals(parsed, 3);
12473
+ validateFlags(parsed, FLAGS$8['disqualify-response']);
12474
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12475
+ resourceArgCount: 2,
12476
+ commandLabel: 'screener disqualify-response'
12477
+ });
12478
+ const [screenerId, responseId] = args;
11772
12479
  const reason = requireOption(parsed, 'reason');
11773
12480
  const data = await requestProjectContractJson('screenerResponsePatch', {
11774
12481
  env,
@@ -11794,16 +12501,16 @@ Usage:
11794
12501
  usertold screener <command> [options]
11795
12502
 
11796
12503
  Commands:
11797
- list <projectRef>
11798
- create <projectRef> --title <title> [options]
11799
- get <projectRef> <screenerRef>
11800
- update <projectRef> <screenerRef> [--title ...] [--status draft|active|paused|closed]
11801
- delete <projectRef> <screenerRef>
11802
- set-questions <projectRef> <screenerRef> --questions <json|@file>
11803
- list-responses <projectRef> <screenerRef>
11804
- get-response <projectRef> <screenerRef> <responseId>
11805
- qualify-response <projectRef> <screenerRef> <responseId> [--reason "..."]
11806
- disqualify-response <projectRef> <screenerRef> <responseId> --reason "..."
12504
+ list [projectRef]
12505
+ create [projectRef] --title <title> [options]
12506
+ get [projectRef] <screenerRef>
12507
+ update [projectRef] <screenerRef> [--title ...] [--status draft|active|paused|closed]
12508
+ delete [projectRef] <screenerRef>
12509
+ set-questions [projectRef] <screenerRef> --questions <json|@file>
12510
+ list-responses [projectRef] <screenerRef>
12511
+ get-response [projectRef] <screenerRef> <responseId>
12512
+ qualify-response [projectRef] <screenerRef> <responseId> [--reason "..."]
12513
+ disqualify-response [projectRef] <screenerRef> <responseId> --reason "..."
11807
12514
 
11808
12515
  Create options:
11809
12516
  --title <title> Screener title (required)
@@ -11835,6 +12542,8 @@ General options:
11835
12542
  --json JSON output
11836
12543
 
11837
12544
  Examples:
12545
+ usertold project use acme/checkout
12546
+ usertold screener create --title "Power User Study" --handle power-users --activate --questions @screener.json
11838
12547
  usertold screener create acme/checkout --title "Power User Study" --handle power-users --activate --questions @screener.json
11839
12548
  usertold screener update acme/checkout power-users --status active
11840
12549
  usertold screener set-questions acme/checkout power-users --questions @questions.json
@@ -11873,7 +12582,7 @@ function extractMarkdownH2Section(markdown, sectionName) {
11873
12582
  };
11874
12583
  }
11875
12584
 
11876
- const FLAGS$8 = {
12585
+ const FLAGS$7 = {
11877
12586
  list: [],
11878
12587
  create: [
11879
12588
  'title',
@@ -11883,6 +12592,7 @@ const FLAGS$8 = {
11883
12592
  'screener-id',
11884
12593
  'goals',
11885
12594
  'script',
12595
+ 'allowed-origins',
11886
12596
  'activate'
11887
12597
  ],
11888
12598
  get: [],
@@ -11894,7 +12604,8 @@ const FLAGS$8 = {
11894
12604
  'type',
11895
12605
  'screener-id',
11896
12606
  'goals',
11897
- 'script'
12607
+ 'script',
12608
+ 'allowed-origins'
11898
12609
  ],
11899
12610
  delete: [],
11900
12611
  export: [],
@@ -11922,9 +12633,11 @@ async function handleStudyCommand(subcommand, parsed) {
11922
12633
  switch(subcommand){
11923
12634
  case 'list':
11924
12635
  {
11925
- validateFlags(parsed, FLAGS$8.list);
11926
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11927
- assertNoExtraPositionals(parsed, 1);
12636
+ validateFlags(parsed, FLAGS$7.list);
12637
+ const { projectRef: projectId } = await consumeProjectRef(parsed, env, {
12638
+ resourceArgCount: 0,
12639
+ commandLabel: 'study list'
12640
+ });
11928
12641
  const data = await requestProjectContractJson('studiesList', {
11929
12642
  env,
11930
12643
  projectRef: projectId
@@ -11934,9 +12647,11 @@ async function handleStudyCommand(subcommand, parsed) {
11934
12647
  }
11935
12648
  case 'create':
11936
12649
  {
11937
- validateFlags(parsed, FLAGS$8.create);
11938
- const projectId = requirePositional(parsed, 0, '<projectRef>');
11939
- assertNoExtraPositionals(parsed, 1);
12650
+ validateFlags(parsed, FLAGS$7.create);
12651
+ const { projectRef: projectId } = await consumeProjectRef(parsed, env, {
12652
+ resourceArgCount: 0,
12653
+ commandLabel: 'study create'
12654
+ });
11940
12655
  const title = requireOption(parsed, 'title');
11941
12656
  const body = {
11942
12657
  title
@@ -11969,6 +12684,10 @@ async function handleStudyCommand(subcommand, parsed) {
11969
12684
  if (scriptInput && scriptInput !== 'true') {
11970
12685
  body.script = await parseJsonOrFile(scriptInput, '--script');
11971
12686
  }
12687
+ // Allowed origins: comma-separated list; empty string clears to []
12688
+ if ('allowed-origins' in parsed.options) {
12689
+ body.allowed_origins = parseAllowedOrigins(parsed.options['allowed-origins']);
12690
+ }
11972
12691
  const data = await requestProjectContractJson('studyCreate', {
11973
12692
  env,
11974
12693
  projectRef: projectId,
@@ -12006,10 +12725,12 @@ async function handleStudyCommand(subcommand, parsed) {
12006
12725
  }
12007
12726
  case 'get':
12008
12727
  {
12009
- validateFlags(parsed, FLAGS$8.get);
12010
- const projectId = requirePositional(parsed, 0, '<projectRef>');
12011
- const studyId = requirePositional(parsed, 1, '<studyRef>');
12012
- assertNoExtraPositionals(parsed, 2);
12728
+ validateFlags(parsed, FLAGS$7.get);
12729
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12730
+ resourceArgCount: 1,
12731
+ commandLabel: 'study get'
12732
+ });
12733
+ const studyId = args[0];
12013
12734
  const data = await requestProjectContractJson('studyGet', {
12014
12735
  env,
12015
12736
  projectRef: projectId,
@@ -12022,10 +12743,12 @@ async function handleStudyCommand(subcommand, parsed) {
12022
12743
  }
12023
12744
  case 'update':
12024
12745
  {
12025
- validateFlags(parsed, FLAGS$8.update);
12026
- const projectId = requirePositional(parsed, 0, '<projectRef>');
12027
- const studyId = requirePositional(parsed, 1, '<studyRef>');
12028
- assertNoExtraPositionals(parsed, 2);
12746
+ validateFlags(parsed, FLAGS$7.update);
12747
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12748
+ resourceArgCount: 1,
12749
+ commandLabel: 'study update'
12750
+ });
12751
+ const studyId = args[0];
12029
12752
  const body = {};
12030
12753
  // Optional string fields
12031
12754
  for (const field of [
@@ -12054,8 +12777,12 @@ async function handleStudyCommand(subcommand, parsed) {
12054
12777
  if (scriptInput && scriptInput !== 'true') {
12055
12778
  body.script = await parseJsonOrFile(scriptInput, '--script');
12056
12779
  }
12780
+ // Allowed origins: comma-separated list; empty string clears to []
12781
+ if ('allowed-origins' in parsed.options) {
12782
+ body.allowed_origins = parseAllowedOrigins(parsed.options['allowed-origins']);
12783
+ }
12057
12784
  if (Object.keys(body).length === 0) {
12058
- fail('No update fields provided. Use --title, --handle, --description, --status, --type, --goals, --script, --screener-id.');
12785
+ fail('No update fields provided. Use --title, --handle, --description, --status, --type, --goals, --script, --screener-id, --allowed-origins.');
12059
12786
  }
12060
12787
  const data = await requestProjectContractJson('studyPatch', {
12061
12788
  env,
@@ -12070,10 +12797,12 @@ async function handleStudyCommand(subcommand, parsed) {
12070
12797
  }
12071
12798
  case 'delete':
12072
12799
  {
12073
- validateFlags(parsed, FLAGS$8.delete);
12074
- const projectId = requirePositional(parsed, 0, '<projectRef>');
12075
- const studyId = requirePositional(parsed, 1, '<studyRef>');
12076
- assertNoExtraPositionals(parsed, 2);
12800
+ validateFlags(parsed, FLAGS$7.delete);
12801
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12802
+ resourceArgCount: 1,
12803
+ commandLabel: 'study delete'
12804
+ });
12805
+ const studyId = args[0];
12077
12806
  if (getBooleanOption(parsed, 'dry-run')) {
12078
12807
  if (isJsonOutput(parsed)) {
12079
12808
  console.log(JSON.stringify({
@@ -12099,10 +12828,12 @@ async function handleStudyCommand(subcommand, parsed) {
12099
12828
  }
12100
12829
  case 'export':
12101
12830
  {
12102
- validateFlags(parsed, FLAGS$8.export);
12103
- const projectId = requirePositional(parsed, 0, '<projectRef>');
12104
- const studyId = requirePositional(parsed, 1, '<studyRef>');
12105
- assertNoExtraPositionals(parsed, 2);
12831
+ validateFlags(parsed, FLAGS$7.export);
12832
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12833
+ resourceArgCount: 1,
12834
+ commandLabel: 'study export'
12835
+ });
12836
+ const studyId = args[0];
12106
12837
  const data = await requestProjectContractJson('studyGet', {
12107
12838
  env,
12108
12839
  projectRef: projectId,
@@ -12122,10 +12853,12 @@ async function handleStudyCommand(subcommand, parsed) {
12122
12853
  }
12123
12854
  case 'import':
12124
12855
  {
12125
- validateFlags(parsed, FLAGS$8.import);
12126
- const projectId = requirePositional(parsed, 0, '<projectRef>');
12127
- const studyId = requirePositional(parsed, 1, '<studyRef>');
12128
- assertNoExtraPositionals(parsed, 2);
12856
+ validateFlags(parsed, FLAGS$7.import);
12857
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12858
+ resourceArgCount: 1,
12859
+ commandLabel: 'study import'
12860
+ });
12861
+ const studyId = args[0];
12129
12862
  const scriptInput = requireOption(parsed, 'script');
12130
12863
  const script = await parseJsonOrFile(scriptInput, '--script');
12131
12864
  const data = await requestProjectContractJson('studyPatch', {
@@ -12143,10 +12876,12 @@ async function handleStudyCommand(subcommand, parsed) {
12143
12876
  }
12144
12877
  case 'reprocess':
12145
12878
  {
12146
- validateFlags(parsed, FLAGS$8.reprocess);
12147
- const projectId = requirePositional(parsed, 0, '<projectRef>');
12148
- const studyId = requirePositional(parsed, 1, '<studyRef>');
12149
- assertNoExtraPositionals(parsed, 2);
12879
+ validateFlags(parsed, FLAGS$7.reprocess);
12880
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12881
+ resourceArgCount: 1,
12882
+ commandLabel: 'study reprocess'
12883
+ });
12884
+ const studyId = args[0];
12150
12885
  // Fetch all completed sessions in the study
12151
12886
  const data = await requestProjectContractJson('sessionsList', {
12152
12887
  env,
@@ -12178,7 +12913,8 @@ async function handleStudyCommand(subcommand, parsed) {
12178
12913
  const timeoutStr = parsed.options.timeout && parsed.options.timeout !== 'true' ? parsed.options.timeout : '600';
12179
12914
  const timeoutSec = parseInt(timeoutStr, 10);
12180
12915
  if (isNaN(timeoutSec) || timeoutSec < 1) fail('--timeout must be a positive integer (seconds)');
12181
- const deadline = Date.now() + timeoutSec * 1000;
12916
+ const timeoutMs = timeoutSec * 1000;
12917
+ const startedAt = performance.now();
12182
12918
  const jsonMode = isJsonOutput(parsed);
12183
12919
  const spinnerFrames = [
12184
12920
  '\u280B',
@@ -12193,7 +12929,7 @@ async function handleStudyCommand(subcommand, parsed) {
12193
12929
  '\u280F'
12194
12930
  ];
12195
12931
  let frame = 0;
12196
- for(;;){
12932
+ while(true){
12197
12933
  const batchStatus = await requestProjectContractJson('sessionProcessingStatus', {
12198
12934
  env,
12199
12935
  projectRef: projectId,
@@ -12232,20 +12968,23 @@ async function handleStudyCommand(subcommand, parsed) {
12232
12968
  }
12233
12969
  process.exit(s.failed > 0 ? 1 : 0);
12234
12970
  }
12235
- if (Date.now() >= deadline) {
12236
- if (!jsonMode) process.stderr.write('\n');
12237
- process.stderr.write('Timed out waiting for processing to complete.\n');
12238
- process.exit(2);
12239
- }
12971
+ // Deadline is enforced *after* the status check so that a completion
12972
+ // arriving during the final sleep window is still observed.
12973
+ if (performance.now() - startedAt >= timeoutMs) break;
12240
12974
  await setTimeout$1(5000);
12241
12975
  }
12976
+ if (!jsonMode) process.stderr.write('\n');
12977
+ process.stderr.write('Timed out waiting for processing to complete.\n');
12978
+ process.exit(2);
12242
12979
  }
12243
12980
  case 'validate-script':
12244
12981
  {
12245
- validateFlags(parsed, FLAGS$8['validate-script']);
12246
- const projectId = requirePositional(parsed, 0, '<projectRef>');
12247
- const studyId = requirePositional(parsed, 1, '<studyRef>');
12248
- assertNoExtraPositionals(parsed, 2);
12982
+ validateFlags(parsed, FLAGS$7['validate-script']);
12983
+ const { projectRef: projectId, args } = await consumeProjectRef(parsed, env, {
12984
+ resourceArgCount: 1,
12985
+ commandLabel: 'study validate-script'
12986
+ });
12987
+ const studyId = args[0];
12249
12988
  const body = {};
12250
12989
  const scriptInput = parsed.options.script;
12251
12990
  if (scriptInput && scriptInput !== 'true') {
@@ -12267,7 +13006,7 @@ async function handleStudyCommand(subcommand, parsed) {
12267
13006
  }
12268
13007
  case 'guide':
12269
13008
  {
12270
- validateFlags(parsed, FLAGS$8.guide);
13009
+ validateFlags(parsed, FLAGS$7.guide);
12271
13010
  assertNoExtraPositionals(parsed, 0);
12272
13011
  const markdown = await loadStudyDesignGuideMarkdown(env);
12273
13012
  if (markdown === null) {
@@ -12305,15 +13044,15 @@ Usage:
12305
13044
  usertold study <command> [options]
12306
13045
 
12307
13046
  Commands:
12308
- list <projectRef> List all studies
12309
- create <projectRef> --title <title> [options] Create a study
12310
- get <projectRef> <studyRef> Get study detail
12311
- update <projectRef> <studyRef> [options] Update study fields (use --status active to activate)
12312
- delete <projectRef> <studyRef> Delete a study
12313
- export <projectRef> <studyRef> Export script_json to stdout
12314
- import <projectRef> <studyRef> --script <file> Import script from file
12315
- reprocess <projectRef> <studyRef> [--wait] [--timeout <s>] Reprocess all completed sessions
12316
- validate-script <projectRef> <studyRef> [--script <json|@file>] [--type <type>] Validate script and return summary
13047
+ list [projectRef] List all studies
13048
+ create [projectRef] --title <title> [options] Create a study
13049
+ get [projectRef] <studyRef> Get study detail
13050
+ update [projectRef] <studyRef> [options] Update study fields (use --status active to activate)
13051
+ delete [projectRef] <studyRef> Delete a study
13052
+ export [projectRef] <studyRef> Export script_json to stdout
13053
+ import [projectRef] <studyRef> --script <file> Import script from file
13054
+ reprocess [projectRef] <studyRef> [--wait] [--timeout <s>] Reprocess all completed sessions
13055
+ validate-script [projectRef] <studyRef> [--script <json|@file>] [--type <type>] Validate script and return summary
12317
13056
  guide [--section <name>] [--format json] Print study design guide
12318
13057
 
12319
13058
  Create/update options:
@@ -12325,6 +13064,7 @@ Create/update options:
12325
13064
  --goals <json|@file> Goals array: [{ "id": "g1", "description": "..." }]
12326
13065
  --script <json|@file> Full script JSON (StudyScriptV2 format)
12327
13066
  --screener-id <screenerRef> Link to a screener
13067
+ --allowed-origins <list> Comma-separated list of origins allowed to embed (e.g. "https://a.com,https://b.com"). Use --allowed-origins= to clear.
12328
13068
  --activate Set status to active after create
12329
13069
 
12330
13070
  Reprocess options:
@@ -12340,10 +13080,13 @@ General options:
12340
13080
  --json JSON output
12341
13081
 
12342
13082
  Examples:
13083
+ usertold project use acme/checkout
13084
+ usertold study list
12343
13085
  usertold study list acme/checkout
12344
13086
  usertold study create acme/checkout --title "Checkout Study" --handle checkout-q1 --type usability --activate
12345
13087
  usertold study update acme/checkout checkout-q1 --status active
12346
13088
  usertold study update acme/checkout checkout-q1 --status paused
13089
+ usertold study update acme/checkout checkout-q1 --allowed-origins "https://learnspeakrepeat.com"
12347
13090
  usertold study export acme/checkout checkout-q1 > script.json
12348
13091
  usertold study import acme/checkout checkout-q1 --script @script.json
12349
13092
  usertold study guide
@@ -12356,8 +13099,14 @@ Examples:
12356
13099
  function printStudyHelp() {
12357
13100
  console.log(STUDY_HELP);
12358
13101
  }
13102
+ function parseAllowedOrigins(value) {
13103
+ if (value === undefined || value === 'true') {
13104
+ fail('--allowed-origins requires a value. Pass a comma-separated list of origins, or --allowed-origins= to clear.');
13105
+ }
13106
+ return value.split(',').map((part)=>part.trim()).filter((part)=>part.length > 0);
13107
+ }
12359
13108
 
12360
- const FLAGS$7 = {};
13109
+ const FLAGS$6 = {};
12361
13110
  const ALLOWED_METHODS = new Set([
12362
13111
  'GET',
12363
13112
  'POST',
@@ -12449,7 +13198,7 @@ function extractApiError(json, text) {
12449
13198
  return text.trim() || 'Request failed';
12450
13199
  }
12451
13200
 
12452
- const FLAGS$6 = {
13201
+ const FLAGS$5 = {
12453
13202
  status: [],
12454
13203
  history: [
12455
13204
  'limit',
@@ -12515,141 +13264,17 @@ function printBillingHelp() {
12515
13264
  console.log(BILLING_HELP);
12516
13265
  }
12517
13266
 
12518
- const FLAGS$5 = {};
12519
- // ─── Helpers ─────────────────────────────────────────────────────────────────
12520
- function formatDuration(seconds) {
12521
- if (seconds === null || seconds < 0) return '-';
12522
- const mins = Math.floor(seconds / 60);
12523
- const secs = seconds % 60;
12524
- return `${mins}m ${secs}s`;
12525
- }
12526
- function formatDate(iso) {
12527
- const d = new Date(iso);
12528
- return d.toLocaleDateString('en-US', {
12529
- month: 'short',
12530
- day: 'numeric'
12531
- });
12532
- }
12533
- function check(value) {
12534
- return value ? '[x]' : '[ ]';
12535
- }
12536
- function formatSignalBreakdown(byType) {
12537
- const parts = Object.entries(byType).sort((a, b)=>b[1] - a[1]).map(([type, count])=>`${count} ${type}`);
12538
- return parts.length > 0 ? ` (${parts.join(', ')})` : '';
12539
- }
12540
- function formatTaskBreakdown(byStatus) {
12541
- const parts = Object.entries(byStatus).sort((a, b)=>b[1] - a[1]).map(([status, count])=>`${count} ${status}`);
12542
- return parts.length > 0 ? ` (${parts.join(', ')})` : '';
12543
- }
12544
- function formatOverview(data) {
12545
- const { sessions, signals, tasks, screeners, setup, recent_sessions, top_tasks } = data;
12546
- console.log(`Sessions: ${sessions.total} total (${sessions.completed} completed, ${sessions.active} active)`);
12547
- console.log(`Signals: ${signals.total} total${formatSignalBreakdown(signals.by_type)}`);
12548
- console.log(`Tasks: ${tasks.total} total${formatTaskBreakdown(tasks.by_status)}`);
12549
- console.log(`Screeners: ${screeners.total} total (${screeners.active} active, ${screeners.total_views} views, ${screeners.total_qualified} qualified)`);
12550
- console.log('');
12551
- console.log('Setup:');
12552
- console.log(` ${check(setup.has_api_keys)} API keys configured`);
12553
- console.log(` ${check(setup.has_active_study)} Active study`);
12554
- console.log(` ${check(setup.has_active_screener)} Active screener`);
12555
- console.log(` ${check(setup.has_linked_active_screener)} Active screener linked to study`);
12556
- console.log(` ${check(setup.has_sessions)} Has sessions`);
12557
- if (recent_sessions.length > 0) {
12558
- console.log('');
12559
- console.log('Recent sessions:');
12560
- for (const s of recent_sessions){
12561
- const name = s.participant_name || 'anonymous';
12562
- const dur = formatDuration(s.duration_seconds);
12563
- const date = formatDate(s.created_at);
12564
- const id = s.id.length > 12 ? `${s.id.slice(0, 12)}...` : s.id;
12565
- console.log(` ${id} ${s.status.padEnd(10)} ${dur.padEnd(8)} ${String(s.signal_count).padStart(2)} signals ${date} ${name}`);
12566
- }
12567
- }
12568
- if (top_tasks.length > 0) {
12569
- console.log('');
12570
- console.log('Top tasks:');
12571
- for (const t of top_tasks){
12572
- const id = t.id.length > 12 ? `${t.id.slice(0, 12)}...` : t.id;
12573
- const title = t.title.length > 50 ? `${t.title.slice(0, 47)}...` : t.title;
12574
- console.log(` ${id} p${String(t.priority_score).padStart(2)} ${String(t.signal_count).padStart(2)} signals ${title}`);
12575
- }
12576
- }
12577
- }
12578
- // ─── Command ─────────────────────────────────────────────────────────────────
12579
- async function handleOverviewCommand(parsed) {
12580
- if (hasHelpFlag(parsed)) {
12581
- printOverviewHelp();
12582
- return;
12583
- }
12584
- const projectRef = requirePositional(parsed, 0, '<projectRef>');
12585
- assertNoExtraPositionals(parsed, 1);
12586
- const env = parseEnvironment(parsed);
12587
- const data = await requestProjectContract({
12588
- env,
12589
- key: 'overview',
12590
- projectRef,
12591
- sourceLabel: '<projectRef>'
12592
- });
12593
- if (isJsonOutput(parsed)) {
12594
- printOutput(data, parsed);
12595
- } else {
12596
- formatOverview(data);
12597
- }
12598
- }
12599
- const OVERVIEW_HELP = `
12600
- Usage:
12601
- usertold overview <projectRef> [options]
12602
-
12603
- Displays dashboard overview for a project: session counts, signal breakdown,
12604
- task status, screener summary, recent sessions, and top tasks.
12605
-
12606
- Options:
12607
- --env <env> stage | production | local
12608
- --json JSON output
12609
-
12610
- Examples:
12611
- usertold overview acme/checkout
12612
- usertold overview acme/checkout --json
12613
- `;
12614
- function printOverviewHelp() {
12615
- console.log(OVERVIEW_HELP);
12616
- }
12617
-
12618
- /**
12619
- * Resolve the project reference from --project flag or USERTOLD_PROJECT_ID env var.
12620
- */ function resolveProjectRefInput(parsed) {
12621
- const fromFlag = parsed.options.project;
12622
- if (fromFlag && fromFlag !== 'true') {
12623
- return fromFlag;
12624
- }
12625
- return process.env.USERTOLD_PROJECT_ID ?? null;
12626
- }
12627
- /**
12628
- * Same as resolveProjectRefInput but throws if missing.
12629
- */ function requireProjectRefInput(parsed) {
12630
- const projectRef = resolveProjectRefInput(parsed);
12631
- if (!projectRef) {
12632
- fail('Missing --project flag or USERTOLD_PROJECT_ID environment variable');
12633
- }
12634
- return projectRef;
12635
- }
12636
-
12637
13267
  const FLAGS$4 = {
12638
13268
  set: [
12639
- 'project',
12640
13269
  'key',
12641
13270
  'value',
12642
13271
  'no-validate'
12643
13272
  ],
12644
13273
  get: [
12645
- 'project',
12646
13274
  'key'
12647
13275
  ],
12648
- list: [
12649
- 'project'
12650
- ],
13276
+ list: [],
12651
13277
  delete: [
12652
- 'project',
12653
13278
  'key'
12654
13279
  ]
12655
13280
  };
@@ -12659,13 +13284,14 @@ async function handleConfigCommand(subcommand, parsed) {
12659
13284
  return;
12660
13285
  }
12661
13286
  const env = parseEnvironment(parsed);
12662
- const rawProjectRef = requireProjectRefInput(parsed);
12663
- const canonical = await resolveProjectRefToCanonical(rawProjectRef, env);
12664
- const projectRef = `${canonical.orgHandle}/${canonical.projectHandle}`;
12665
13287
  switch(subcommand){
12666
13288
  case 'set':
12667
13289
  {
12668
13290
  validateFlags(parsed, FLAGS$4.set);
13291
+ const { projectRef } = await consumeProjectRef(parsed, env, {
13292
+ resourceArgCount: 0,
13293
+ commandLabel: 'config set'
13294
+ });
12669
13295
  const key = requireOption(parsed, 'key');
12670
13296
  const value = requireOption(parsed, 'value');
12671
13297
  // Optionally validate first
@@ -12674,7 +13300,7 @@ async function handleConfigCommand(subcommand, parsed) {
12674
13300
  env,
12675
13301
  key: 'settingsValidate',
12676
13302
  projectRef,
12677
- sourceLabel: '--project',
13303
+ sourceLabel: '<projectRef>',
12678
13304
  body: {
12679
13305
  key,
12680
13306
  value
@@ -12702,6 +13328,10 @@ async function handleConfigCommand(subcommand, parsed) {
12702
13328
  case 'get':
12703
13329
  {
12704
13330
  validateFlags(parsed, FLAGS$4.get);
13331
+ const { projectRef } = await consumeProjectRef(parsed, env, {
13332
+ resourceArgCount: 0,
13333
+ commandLabel: 'config get'
13334
+ });
12705
13335
  const key = requireOption(parsed, 'key');
12706
13336
  const data = await requestProjectContract({
12707
13337
  env,
@@ -12725,6 +13355,10 @@ async function handleConfigCommand(subcommand, parsed) {
12725
13355
  case 'list':
12726
13356
  {
12727
13357
  validateFlags(parsed, FLAGS$4.list);
13358
+ const { projectRef } = await consumeProjectRef(parsed, env, {
13359
+ resourceArgCount: 0,
13360
+ commandLabel: 'config list'
13361
+ });
12728
13362
  const data = await requestProjectContract({
12729
13363
  env,
12730
13364
  key: 'settingsGet',
@@ -12737,6 +13371,10 @@ async function handleConfigCommand(subcommand, parsed) {
12737
13371
  case 'delete':
12738
13372
  {
12739
13373
  validateFlags(parsed, FLAGS$4.delete);
13374
+ const { projectRef } = await consumeProjectRef(parsed, env, {
13375
+ resourceArgCount: 0,
13376
+ commandLabel: 'config delete'
13377
+ });
12740
13378
  const key = requireOption(parsed, 'key');
12741
13379
  const data = await requestProjectContract({
12742
13380
  env,
@@ -12756,27 +13394,28 @@ async function handleConfigCommand(subcommand, parsed) {
12756
13394
  }
12757
13395
  const CONFIG_HELP = `
12758
13396
  Usage:
12759
- usertold config <command> [options]
13397
+ usertold config <command> [projectRef] [options]
12760
13398
 
12761
13399
  Commands:
12762
- set --project <org/project> --key <KEY> --value <val> [--no-validate]
12763
- get --project <org/project> --key <KEY>
12764
- list --project <org/project>
12765
- delete --project <org/project> --key <KEY>
13400
+ set [projectRef] --key <KEY> --value <val> [--no-validate]
13401
+ get [projectRef] --key <KEY>
13402
+ list [projectRef]
13403
+ delete [projectRef] --key <KEY>
12766
13404
 
12767
13405
  Allowed keys:
12768
13406
  openai_api_key OpenAI API key for interviews, signal extraction, transcription & realtime
12769
13407
 
12770
13408
  Options:
12771
- --project <org/project> Project reference (or set USERTOLD_PROJECT_ID env var)
12772
13409
  --env <env> stage | production | local
12773
13410
  --json JSON output
12774
13411
  --no-validate Skip key validation on set
12775
13412
 
12776
13413
  Examples:
12777
- usertold config set --project acme/checkout --key openai_api_key --value sk-xxx
12778
- usertold config list --project acme/checkout
12779
- usertold config delete --project acme/checkout --key openai_api_key
13414
+ usertold project use acme/checkout
13415
+ usertold config set --key openai_api_key --value sk-xxx
13416
+ usertold config set acme/checkout --key openai_api_key --value sk-xxx
13417
+ usertold config list acme/checkout
13418
+ usertold config delete acme/checkout --key openai_api_key
12780
13419
  `;
12781
13420
  function printConfigHelp() {
12782
13421
  console.log(CONFIG_HELP);
@@ -12875,7 +13514,7 @@ async function handleInitCommand(parsed) {
12875
13514
  if (!config || config.token.expiresAt <= Date.now()) {
12876
13515
  if (interactive) {
12877
13516
  console.error(`No valid token for environment "${env}".`);
12878
- console.error(`Run: usertold auth login -- --env ${env}`);
13517
+ console.error(`Run: usertold auth login --env ${env}`);
12879
13518
  failAuth('Authentication required. Run auth login first.');
12880
13519
  } else {
12881
13520
  failAuth('No valid token. Set USERTOLD_API_KEY or run auth login.');
@@ -12893,7 +13532,7 @@ async function handleInitCommand(parsed) {
12893
13532
  }
12894
13533
  }
12895
13534
  if (!json) console.error(`Creating project "${projectName}"...`);
12896
- const targetOrgHandle = requireOrgHandleForInit(parsed);
13535
+ const targetOrgHandle = await resolveOrgHandleForInit(parsed, env);
12897
13536
  const projectData = await requestContract({
12898
13537
  env,
12899
13538
  key: 'projectCreate',
@@ -12991,8 +13630,11 @@ async function handleInitCommand(parsed) {
12991
13630
  if (!json) console.error('Study and screener activated.');
12992
13631
  }
12993
13632
  // Step 5: Widget snippet
12994
- const baseUrl = resolveBaseUrl(env);
12995
- const snippet = `<script src="${baseUrl}/v1/widget.js" data-project-key="${publicKey}"></script>`;
13633
+ const widgetOrigin = resolveWidgetEmbedOriginForEnvironment(env);
13634
+ const snippet = buildWidgetEmbedSnippet({
13635
+ origin: widgetOrigin,
13636
+ projectKey: publicKey
13637
+ });
12996
13638
  result.widget_snippet = snippet;
12997
13639
  // Output
12998
13640
  if (json) {
@@ -13018,12 +13660,13 @@ const INIT_HELP = `
13018
13660
  Usage:
13019
13661
  usertold init [options]
13020
13662
 
13021
- Interactive project setup wizard. Creates a project, optionally configures
13022
- BYOK keys, and creates a study (auto-creates screener).
13663
+ Bootstrap a project, optionally configure BYOK keys, and create a study
13664
+ (auto-creates a screener). Runs interactively when stdout is a TTY; pass
13665
+ --yes to skip prompts for non-interactive use (the agent path).
13023
13666
  GitHub integration is managed via the GitHub App (install from project settings).
13024
13667
 
13025
13668
  Options:
13026
- --org <orgHandle> Target organization handle for created project
13669
+ --org <orgHandle> Target organization handle (defaults to your personal workspace)
13027
13670
  --name <name> Project name (prompted if interactive)
13028
13671
  --openai-key <key> OpenAI API key for interviews, analysis, and voice
13029
13672
  --study-title <title> Study title (default: "User Research Study")
@@ -13034,8 +13677,8 @@ Options:
13034
13677
  --format json Same as --json
13035
13678
 
13036
13679
  Non-interactive mode:
13037
- Discover your org first with \`usertold auth whoami --json\`, then run:
13038
- USERTOLD_API_KEY=... usertold init --org <orgHandle> --name "My Project" --yes --format json
13680
+ Defaults to your personal workspace. Pass --org only when targeting another org:
13681
+ USERTOLD_API_KEY=... usertold init --name "My Project" --yes --format json
13039
13682
 
13040
13683
  Note:
13041
13684
  If the wizard fails mid-way, earlier resources (e.g. project) will persist.
@@ -13044,22 +13687,28 @@ Note:
13044
13687
  Examples:
13045
13688
  usertold init
13046
13689
  usertold auth whoami --json
13047
- usertold init --org acme --name "Demo" --yes --env local
13690
+ usertold init --name "Demo" --yes --env local
13048
13691
  usertold init --org acme --name "Demo" --openai-key sk-xxx --yes --json
13049
13692
  `;
13050
13693
  function printInitHelp() {
13051
13694
  console.log(INIT_HELP);
13052
13695
  }
13053
- function requireOrgHandleForInit(parsed) {
13696
+ async function resolveOrgHandleForInit(parsed, env) {
13054
13697
  const orgHandle = parsed.options.org;
13055
- if (!orgHandle || orgHandle === 'true') {
13056
- failArgs('Missing required option: --org. ' + 'Discover it with "usertold auth whoami --json" and use "profile.personal_org_handle".');
13698
+ if (orgHandle && orgHandle !== 'true') {
13699
+ return orgHandle;
13057
13700
  }
13058
- return orgHandle;
13701
+ return resolveDefaultOrgHandle(env, 'init');
13059
13702
  }
13060
13703
 
13061
13704
  const FLAGS$1 = {
13062
- 'embed-backfill': []
13705
+ 'embed-backfill': [],
13706
+ credits: [
13707
+ 'email',
13708
+ 'credits',
13709
+ 'reason',
13710
+ 'grant-id'
13711
+ ]
13063
13712
  };
13064
13713
  async function handleAdminCommand(subcommand, parsed) {
13065
13714
  if (!subcommand || hasHelpFlag(parsed) || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
@@ -13070,6 +13719,7 @@ async function handleAdminCommand(subcommand, parsed) {
13070
13719
  switch(subcommand){
13071
13720
  case 'embed-backfill':
13072
13721
  {
13722
+ validateFlags(parsed, FLAGS$1['embed-backfill']);
13073
13723
  console.log('Running embedding backfill for all signals and tasks...');
13074
13724
  const data = await requestContractJson('adminEmbedBackfill', {
13075
13725
  env
@@ -13082,6 +13732,56 @@ async function handleAdminCommand(subcommand, parsed) {
13082
13732
  }
13083
13733
  return;
13084
13734
  }
13735
+ case 'credits':
13736
+ {
13737
+ validateFlags(parsed, FLAGS$1.credits);
13738
+ const action = parsed.positionals[0];
13739
+ if (action !== 'grant') {
13740
+ fail(`Unknown admin credits command: ${action ?? ''}`.trim());
13741
+ }
13742
+ assertNoExtraPositionals(parsed, 1);
13743
+ const email = requireOption(parsed, 'email').trim().toLowerCase();
13744
+ const reason = requireOption(parsed, 'reason').trim();
13745
+ const creditsRaw = requireOption(parsed, 'credits');
13746
+ const credits = Number(creditsRaw);
13747
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
13748
+ failArgs('--email must be a valid email address');
13749
+ }
13750
+ if (!Number.isInteger(credits) || credits <= 0 || credits > 100000) {
13751
+ failArgs('--credits must be a positive integer no larger than 100000');
13752
+ }
13753
+ if (!reason) {
13754
+ failArgs('--reason cannot be empty');
13755
+ }
13756
+ const grantIdRaw = parsed.options['grant-id'];
13757
+ const grantId = grantIdRaw && grantIdRaw !== 'true' ? grantIdRaw.trim() : undefined;
13758
+ if (grantIdRaw === 'true') {
13759
+ failArgs('--grant-id requires a value');
13760
+ }
13761
+ if (grantIdRaw && !grantId) {
13762
+ failArgs('--grant-id cannot be empty');
13763
+ }
13764
+ const data = await requestContractJson('adminCreditsGrant', {
13765
+ env,
13766
+ body: {
13767
+ email,
13768
+ credits,
13769
+ reason,
13770
+ ...grantId ? {
13771
+ grant_id: grantId
13772
+ } : {}
13773
+ }
13774
+ });
13775
+ if (parsed.options.json === 'true' || parsed.options.format === 'json') {
13776
+ printOutput(data, parsed);
13777
+ return;
13778
+ }
13779
+ const verb = data.applied ? 'Granted' : 'Already granted';
13780
+ console.log(`${verb} ${data.credits} bonus credit(s) to ${data.email}`);
13781
+ console.log(`Grant key: ${data.grant_key}`);
13782
+ console.log(`Available interviews: ${data.available_interview_count}`);
13783
+ return;
13784
+ }
13085
13785
  default:
13086
13786
  fail(`Unknown admin command: ${subcommand}`);
13087
13787
  }
@@ -13092,6 +13792,7 @@ Usage:
13092
13792
 
13093
13793
  Commands:
13094
13794
  embed-backfill Backfill Vectorize embeddings for all existing signals and tasks
13795
+ credits grant Grant bonus credits by user email
13095
13796
 
13096
13797
  Options:
13097
13798
  --env <env> stage | production | local
@@ -13100,6 +13801,7 @@ Options:
13100
13801
  Examples:
13101
13802
  usertold admin embed-backfill --env production
13102
13803
  usertold admin embed-backfill --env stage --json
13804
+ usertold admin credits grant --email user@example.com --credits 10 --reason "signup bonus" --env production
13103
13805
  `;
13104
13806
  function printAdminHelp() {
13105
13807
  console.log(ADMIN_HELP);
@@ -13114,12 +13816,19 @@ Return ONLY a JSON array. Do not include markdown fences or extra text.
13114
13816
 
13115
13817
  Each signal must include ALL of these fields:
13116
13818
  - signal_type: one of ["struggling_moment","desired_outcome","workaround","hiring_criteria","firing_moment","emotional_response","smooth_completion","critical_error","recovery_success","decision_point"]
13819
+ - target_surface: one of ["product_under_test","usertold_widget_interview","interviewer_conductor_behavior","ambiguous_needs_review"]. Classify what the evidence is about:
13820
+ - product_under_test: the customer's product, site, workflow, content, pricing, onboarding, checkout, or other experience being researched.
13821
+ - usertold_widget_interview: UserTold participant-facing interview/widget UX, recording, microphone, transcript, consent, embedded prompt UI, or session mechanics.
13822
+ - interviewer_conductor_behavior: the AI interviewer/conductor's questions, timing, probing, interruptions, tone, instructions, or behavior.
13823
+ - ambiguous_needs_review: unclear or mixed evidence that cannot be safely assigned to exactly one of the above.
13117
13824
  - headline: one-line summary of the signal (NOT the quote itself — a descriptive label)
13118
13825
  - quote: direct participant quote, minimum ~15 words unless behavioral evidence compensates. NEVER interviewer text.
13119
13826
  - observed_facts: array of strict factual bullet strings — what was literally seen or heard. No interpretation. Each bullet starts with a verb (e.g., "Said ...", "Clicked ...", "Paused 4 seconds before ...", "Scrolled past ...")
13120
13827
  - claim: the smallest defensible interpretation of what the observed facts mean for the user's experience. One sentence. Must not exceed what observed_facts support. NEVER prescribe solutions.
13121
13828
  - reconstruction: 2-4 sentence before/during/after narrative. What led to this moment, what happened, and what followed.
13122
13829
  - evidence_grade: self-assessment of evidence strength — one of ["direct","strong_circumstantial","weak"]. "direct" = explicit quote + matching behavior. "strong_circumstantial" = clear behavioral pattern without explicit statement. "weak" = single ambiguous cue.
13830
+ - transcript_uncertain: boolean. Set true only when the direct quote appears to contain likely speech-recognition drift, phonetic approximations, mixed-language artifacts, or unusual tokens that could change how a reviewer interprets the evidence.
13831
+ - transcript_uncertainty_note: null unless transcript_uncertain is true. When true, write one concise reviewer note that names the uncertain token or phrase and, only when supported by page titles, URLs, product glossary terms, observed DOM text, or nearby context, suggests a normalized interpretation. Never rewrite the quote itself.
13123
13832
  - page_url: the page URL where this moment occurred. Look for the most recent [page] line before this moment in the transcript, or consult the Page Navigation History section. Set to null only if the session has no page/navigation data.
13124
13833
  - page_title: the page title from the same source as page_url. Set to null only if unavailable.
13125
13834
  - preceding_actions: array of 2-5 user actions leading up to this moment, from [event] and [page] lines that occur before this moment. Include clicks, navigation, focus changes — any user-initiated action. Set to null only if there are genuinely no [event] or [page] lines within 60 seconds before this moment.
@@ -13136,6 +13845,7 @@ Each signal must include ALL of these fields:
13136
13845
  Example output for a single signal:
13137
13846
  {
13138
13847
  "signal_type": "struggling_moment",
13848
+ "target_surface": "product_under_test",
13139
13849
  "headline": "Cannot navigate backwards in multi-step checkout",
13140
13850
  "quote": "I filled in my whole shipping address and now I can't go back to change it without losing everything",
13141
13851
  "observed_facts": [
@@ -13147,6 +13857,8 @@ Example output for a single signal:
13147
13857
  "claim": "User cannot navigate backwards in multi-step checkout flow, causing loss of earlier input.",
13148
13858
  "reconstruction": "User completed shipping address and advanced to payment. Realized the address was wrong and looked for a way to go back. Scrolled the payment page, found no back button, and used browser back — which cleared the shipping form. Gave up and restarted checkout.",
13149
13859
  "evidence_grade": "direct",
13860
+ "transcript_uncertain": false,
13861
+ "transcript_uncertainty_note": null,
13150
13862
  "page_url": "/checkout/step3",
13151
13863
  "page_title": "Payment",
13152
13864
  "preceding_actions": ["Clicked 'Continue' on shipping", "Scrolled up and down on payment page", "Clicked browser back button"],
@@ -13167,10 +13879,13 @@ Guidelines:
13167
13879
  - Use [page] lines in the transcript for inline chronological page context — they show which page the user was on when they spoke. Also use the Page Navigation History section (if present) for the full page visit list.
13168
13880
  - Use [event] lines for behavioral context — clicks, navigation, form interactions that precede each signal.
13169
13881
  - If the same issue appears at different points in the session (e.g., during a task and again in the debrief), extract both — they carry different context and together show reinforcement.
13882
+ - Keep product-under-test evidence separate from UserTold-owned evidence. If the user struggles with the interview widget, recording, consent, microphone, AI interviewer, question flow, or conductor behavior, do not classify that as product_under_test even if it interrupts a product task.
13170
13883
  - Analysis must describe the user's experience and its impact. NEVER prescribe solutions, implementation direction, or what engineering should build. Bad: "Add a back button to fix navigation." Good: "User cannot navigate backwards in multi-step flow, causing loss of earlier input."
13171
13884
  - Prioritize concrete, evidence-backed signals over vague sentiments.
13172
13885
  - page_url contains only the path — domain is never available. Never prefix a domain. If no [page] line precedes this moment, set page_url to null.
13173
13886
  - context should include observed facts — what was literally seen or heard, starting with verbs (e.g., "Said ...", "Clicked ...", "Paused 4 seconds before ..."). This grounds the analysis in verifiable behavior.
13887
+ - Preserve quote verbatim even when transcript_uncertain is true. Put any normalized reading in transcript_uncertainty_note, not in quote.
13888
+ - Use visible page titles, URLs, product terms, and DOM text as anchors for domain terms. For example, if a quote contains a phonetically similar or mixed-language token but the page context clearly shows a matching product/page term, flag uncertainty and suggest that term in transcript_uncertainty_note. If no safe normalized reading exists, flag the uncertainty without inventing one.
13174
13889
 
13175
13890
  ## Audio Behavioral Cues
13176
13891
  The transcript includes inline audio annotations that provide important context:
@@ -13214,6 +13929,7 @@ Examples of needs_context=true:
13214
13929
  - Participant says "I don't know where to go" — need to know the task instruction and current page
13215
13930
  - Participant laughs and clicks something — need to know what they clicked
13216
13931
  - Participant says "oh, that's weird" — need to know what the UI showed
13932
+ - Participant quote contains unusual, phonetically approximated, or mixed-language domain terms — need page title, URL, or DOM text before suggesting any normalized reading
13217
13933
 
13218
13934
  Examples of needs_context=false:
13219
13935
  - Participant explicitly states frustration with a named feature
@@ -13336,6 +14052,7 @@ const extractedSignalSchema = object({
13336
14052
  'recovery_success',
13337
14053
  'decision_point'
13338
14054
  ]),
14055
+ target_surface: _enum(TARGET_SURFACES).nullish().transform((v)=>normalizeTargetSurface(v)),
13339
14056
  quote: string().min(1),
13340
14057
  context: string().nullish().transform((v)=>v ?? ''),
13341
14058
  analysis: string().nullish().transform((v)=>v ?? ''),
@@ -13359,7 +14076,9 @@ const extractedSignalSchema = object({
13359
14076
  observed_facts: array(string()).nullish(),
13360
14077
  evidence_grade: _enum(EVIDENCE_GRADES).nullish().transform((v)=>v ?? 'weak'),
13361
14078
  window_start_ms: number().nullish(),
13362
- window_end_ms: number().nullish()
14079
+ window_end_ms: number().nullish(),
14080
+ transcript_uncertain: boolean().nullish().transform((v)=>v ?? false),
14081
+ transcript_uncertainty_note: string().nullish()
13363
14082
  });
13364
14083
  /**
13365
14084
  * Try to extract a JSON array from text using multiple strategies:
@@ -13411,6 +14130,7 @@ const extractedSignalSchema = object({
13411
14130
  const s = result.data;
13412
14131
  valid.push({
13413
14132
  signal_type: s.signal_type,
14133
+ target_surface: s.target_surface,
13414
14134
  quote: s.quote,
13415
14135
  context: s.context,
13416
14136
  analysis: s.analysis,
@@ -13429,7 +14149,9 @@ const extractedSignalSchema = object({
13429
14149
  observed_facts: s.observed_facts ?? undefined,
13430
14150
  evidence_grade: s.evidence_grade ?? undefined,
13431
14151
  window_start_ms: s.window_start_ms ?? undefined,
13432
- window_end_ms: s.window_end_ms ?? undefined
14152
+ window_end_ms: s.window_end_ms ?? undefined,
14153
+ transcript_uncertain: s.transcript_uncertain || undefined,
14154
+ transcript_uncertainty_note: s.transcript_uncertainty_note ?? undefined
13433
14155
  });
13434
14156
  } else {
13435
14157
  const issues = result.error.issues.map((e)=>`${e.path.join('.')}: ${e.message}`).join(', ');
@@ -13622,8 +14344,12 @@ async function openAIFetch(url, init, options) {
13622
14344
  if (!RETRYABLE_STATUSES.has(response.status) || attempt >= retries) {
13623
14345
  return response;
13624
14346
  }
13625
- // Retryable status — consume body to free connection
13626
14347
  lastResponse = response;
14348
+ const responseBody = await response.clone().text().catch(()=>'');
14349
+ if (options?.shouldRetryResponse && !await options.shouldRetryResponse(response, responseBody)) {
14350
+ return response;
14351
+ }
14352
+ // Retryable status — consume body to free connection
13627
14353
  await response.text().catch(()=>{});
13628
14354
  // Respect Retry-After header on 429
13629
14355
  if (response.status === 429) {
@@ -14531,11 +15257,15 @@ const EXTRACT_HELP = `
14531
15257
  usertold extract — run signal extraction on local files
14532
15258
 
14533
15259
  Usage:
14534
- usertold extract <transcript-file> --key <openai-key> [options]
15260
+ usertold extract <transcript-file> [options]
14535
15261
 
14536
15262
  Arguments:
14537
15263
  transcript-file Path to transcript (.vtt, .txt, or pre-formatted text)
14538
15264
 
15265
+ Auth:
15266
+ Pass --key <key> OR set the OPENAI_API_KEY environment variable. The CLI
15267
+ fails fast with a clear message if neither is provided.
15268
+
14539
15269
  Options:
14540
15270
  --key <key> OpenAI API key (or set OPENAI_API_KEY env var)
14541
15271
  --events <file> Events JSONL file (one JSON object per line)
@@ -14648,6 +15378,7 @@ function printSignal(sig, index) {
14648
15378
  const grade = sig.evidence_grade ? `Grade: ${sig.evidence_grade} | ` : '';
14649
15379
  const time = sig.timestamp_ms != null ? ` | At: ${formatMs(sig.timestamp_ms)}` : '';
14650
15380
  console.log(` ${grade}Confidence: ${(sig.confidence * 100).toFixed(0)}% | Intensity: ${(sig.intensity * 100).toFixed(0)}%${time}`);
15381
+ console.log(` Surface: ${sig.target_surface}`);
14651
15382
  console.log(` Quote: "${sig.quote}"`);
14652
15383
  if (sig.observed_facts && sig.observed_facts.length > 0) {
14653
15384
  console.log(' Observed facts:');
@@ -14734,7 +15465,7 @@ function printExtractHelp() {
14734
15465
  console.log(EXTRACT_HELP);
14735
15466
  }
14736
15467
 
14737
- const CLI_VERSION$1 = '1.18.0';
15468
+ const CLI_VERSION$1 = '1.20.0';
14738
15469
  const GLOBAL_FLAGS = [
14739
15470
  'env',
14740
15471
  'json',
@@ -14764,38 +15495,34 @@ function buildCommandSurface() {
14764
15495
  const commands = [
14765
15496
  {
14766
15497
  name: 'auth',
14767
- subcommands: buildSubcommands(FLAGS$e)
15498
+ subcommands: buildSubcommands(FLAGS$d)
14768
15499
  },
14769
15500
  {
14770
15501
  name: 'project',
14771
- subcommands: buildSubcommands(FLAGS$d)
15502
+ subcommands: buildSubcommands(FLAGS$c)
14772
15503
  },
14773
15504
  {
14774
15505
  name: 'session',
14775
- subcommands: buildSubcommands(FLAGS$c)
15506
+ subcommands: buildSubcommands(FLAGS$b)
14776
15507
  },
14777
15508
  {
14778
15509
  name: 'signal',
14779
- subcommands: buildSubcommands(FLAGS$b)
15510
+ subcommands: buildSubcommands(FLAGS$a)
14780
15511
  },
14781
15512
  {
14782
15513
  name: 'task',
14783
- subcommands: buildSubcommands(FLAGS$a)
15514
+ subcommands: buildSubcommands(FLAGS$9)
14784
15515
  },
14785
15516
  {
14786
15517
  name: 'screener',
14787
- subcommands: buildSubcommands(FLAGS$9)
15518
+ subcommands: buildSubcommands(FLAGS$8)
14788
15519
  },
14789
15520
  {
14790
15521
  name: 'study',
14791
- subcommands: buildSubcommands(FLAGS$8)
15522
+ subcommands: buildSubcommands(FLAGS$7)
14792
15523
  },
14793
15524
  {
14794
15525
  name: 'billing',
14795
- subcommands: buildSubcommands(FLAGS$6)
14796
- },
14797
- {
14798
- name: 'overview',
14799
15526
  subcommands: buildSubcommands(FLAGS$5)
14800
15527
  },
14801
15528
  {
@@ -14812,7 +15539,7 @@ function buildCommandSurface() {
14812
15539
  },
14813
15540
  {
14814
15541
  name: 'api',
14815
- subcommands: buildSubcommands(FLAGS$7)
15542
+ subcommands: buildSubcommands(FLAGS$6)
14816
15543
  },
14817
15544
  {
14818
15545
  name: 'init',
@@ -14847,7 +15574,7 @@ const SHELLS = [
14847
15574
  'fish'
14848
15575
  ];
14849
15576
  async function handleCompletionsCommand(shell, parsed) {
14850
- if (!shell || hasHelpFlag(parsed)) {
15577
+ if (!shell || hasHelpFlag(parsed) || shell === '--help' || shell === '-h' || shell === 'help') {
14851
15578
  printCompletionsHelp();
14852
15579
  return;
14853
15580
  }
@@ -15030,7 +15757,7 @@ function printCompletionsHelp() {
15030
15757
  console.log(COMPLETIONS_HELP);
15031
15758
  }
15032
15759
 
15033
- const CLI_VERSION = '1.18.0';
15760
+ const CLI_VERSION = '1.20.0';
15034
15761
  function detectJsonMode() {
15035
15762
  const argv = process$2.argv.slice(2);
15036
15763
  if (argv.includes('--json')) return true;
@@ -15166,12 +15893,6 @@ async function dispatch(command, argv) {
15166
15893
  await handleBillingCommand(subcommand, parsed);
15167
15894
  return;
15168
15895
  }
15169
- case 'overview':
15170
- {
15171
- const parsed = parseArgs(argv);
15172
- await handleOverviewCommand(parsed);
15173
- return;
15174
- }
15175
15896
  case 'config':
15176
15897
  {
15177
15898
  const [subcommand, ...rest] = argv;
@@ -15237,27 +15958,28 @@ const ROOT_HELP = `Usage: usertold <group> <subcommand> [options]
15237
15958
 
15238
15959
  Groups:
15239
15960
  auth login, logout, whoami, token
15240
- project list, create, get, update, delete
15241
- session list, create, end, get, update, delete, transcript
15242
- signal list, get, annotate, dismiss, link, unlink
15243
- task list, get, create, update, delete, push, measure
15244
- screener list, create, get, update, delete, set-questions
15245
- study list, create, get, update, delete, export, import
15961
+ project list, use, current, create, get, update, delete, snippet, status, overview
15962
+ session list, create, end, get, status, events, update, delete, transcript, timeline, enriched-timeline, screen, media, audio, reprocess, retry-media-merge, watch
15963
+ signal list, get, annotate, dismiss, undismiss, link, unlink, delete, bulk-link, bulk-delete
15964
+ task list, get, create, create-from-signals, update, delete, push, push-status
15965
+ screener list, create, get, update, delete, set-questions, list-responses, get-response, qualify-response, disqualify-response
15966
+ study list, create, get, update, delete, export, import, reprocess, validate-script, guide
15246
15967
  billing status, history
15247
- overview project dashboard
15248
- config set, get, list, delete
15249
- setup provider setup
15250
- init interactive setup wizard
15968
+ config set, get, list, delete (uses current project when omitted)
15969
+ setup stub group GitHub App install moved to the dashboard
15970
+ init bootstrap project + study + screener (TTY-interactive or --yes)
15251
15971
  api raw HTTP passthrough
15252
15972
  extract offline signal extraction from local files
15253
- admin admin operations
15973
+ admin admin operations (elevated auth)
15254
15974
  completions shell completions (bash, zsh, fish)
15255
15975
  introspect full command surface as JSON
15256
15976
 
15257
15977
  Options:
15258
- --json structured JSON output
15978
+ --json structured JSON output (alias: --format json)
15259
15979
  --env <env> stage | production | local (default: ${DEFAULT_ENVIRONMENT})
15260
- --dry-run show what would happen without executing
15980
+ --local shortcut for --env local
15981
+ --dry-run preview a destructive call without executing it
15982
+ (honored by 'delete' on every resource and by 'task push')
15261
15983
  --help group or subcommand help
15262
15984
 
15263
15985
  Exit codes:
@@ -15274,4 +15996,4 @@ function printRootHelp() {
15274
15996
  }
15275
15997
  void main();
15276
15998
 
15277
- export { ADMIN_HELP, API_HELP, AUTH_HELP, BILLING_HELP, COMPLETIONS_HELP, CONFIG_HELP, EXTRACT_HELP, INIT_HELP, OVERVIEW_HELP, PROJECT_HELP, ROOT_HELP, SCREENER_HELP, SESSION_HELP, SETUP_HELP, SIGNAL_HELP, STUDY_HELP, TASK_HELP, printAdminHelp, printApiHelp, printAuthHelp, printBillingHelp, printCompletionsHelp, printConfigHelp, printExtractHelp, printInitHelp, printOverviewHelp, printProjectHelp, printRootHelp, printScreenerHelp, printSessionHelp, printSetupHelp, printSignalHelp, printStudyHelp, printTaskHelp };
15999
+ export { ADMIN_HELP, API_HELP, AUTH_HELP, BILLING_HELP, COMPLETIONS_HELP, CONFIG_HELP, EXTRACT_HELP, INIT_HELP, PROJECT_HELP, ROOT_HELP, SCREENER_HELP, SESSION_HELP, SETUP_HELP, SIGNAL_HELP, STUDY_HELP, TASK_HELP, printAdminHelp, printApiHelp, printAuthHelp, printBillingHelp, printCompletionsHelp, printConfigHelp, printExtractHelp, printInitHelp, printProjectHelp, printRootHelp, printScreenerHelp, printSessionHelp, printSetupHelp, printSignalHelp, printStudyHelp, printTaskHelp };