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.
- package/README.md +3 -4
- package/package.json +1 -1
- 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,
|
|
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
|
-
|
|
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(
|
|
594
|
+
console.log(JSON.stringify(safeData, null, 2));
|
|
550
595
|
return;
|
|
551
596
|
}
|
|
552
|
-
if (Array.isArray(
|
|
553
|
-
printTable(
|
|
597
|
+
if (Array.isArray(safeData)) {
|
|
598
|
+
printTable(safeData);
|
|
554
599
|
return;
|
|
555
600
|
}
|
|
556
|
-
if (isRecord(
|
|
557
|
-
const arrayEntry = findFirstArrayEntry(
|
|
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(
|
|
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(
|
|
614
|
+
printObject(safeData);
|
|
570
615
|
return;
|
|
571
616
|
}
|
|
572
|
-
if (
|
|
617
|
+
if (safeData === null || safeData === undefined) {
|
|
573
618
|
console.log('(empty)');
|
|
574
619
|
return;
|
|
575
620
|
}
|
|
576
|
-
console.log(String(
|
|
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
|
-
* - "
|
|
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
|
|
667
|
-
if (argv[1]?.includes('npm') || process.env.npm_lifecycle_event) {
|
|
668
|
-
return '
|
|
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
|
-
|
|
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$
|
|
1241
|
+
validateFlags(parsed, FLAGS$d.login);
|
|
1180
1242
|
await handleLogin(parsed);
|
|
1181
1243
|
return;
|
|
1182
1244
|
case 'logout':
|
|
1183
|
-
validateFlags(parsed, FLAGS$
|
|
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$
|
|
1250
|
+
validateFlags(parsed, FLAGS$d.whoami);
|
|
1189
1251
|
await handleWhoami(parsed);
|
|
1190
1252
|
return;
|
|
1191
1253
|
case 'token':
|
|
1192
|
-
validateFlags(parsed, FLAGS$
|
|
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
|
-
|
|
1218
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
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
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
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 '
|
|
10052
|
+
case 'use':
|
|
9519
10053
|
{
|
|
9520
|
-
validateFlags(parsed, FLAGS$
|
|
9521
|
-
const
|
|
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$
|
|
9537
|
-
const projectRef =
|
|
9538
|
-
|
|
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$
|
|
9573
|
-
const
|
|
10180
|
+
validateFlags(parsed, FLAGS$c.delete);
|
|
10181
|
+
const rawProjectRef = requirePositional(parsed, 0, '<projectRef>');
|
|
9574
10182
|
assertNoExtraPositionals(parsed, 1);
|
|
9575
|
-
const { orgHandle, projectHandle } =
|
|
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$
|
|
9602
|
-
const projectRef =
|
|
9603
|
-
|
|
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
|
|
9617
|
-
const snippet =
|
|
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:
|
|
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$
|
|
9632
|
-
const projectRef =
|
|
9633
|
-
|
|
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
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
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
|
|
9705
|
-
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
|
-
|
|
9713
|
-
|
|
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
|
|
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$
|
|
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
|
|
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
|
|
10694
|
+
const startedAt = performance.now();
|
|
10072
10695
|
let frame = 0;
|
|
10073
10696
|
let lastEventCount = 0;
|
|
10074
10697
|
let lastSignalCount = 0;
|
|
10075
|
-
|
|
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
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
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$
|
|
10154
|
-
const projectId =
|
|
10155
|
-
|
|
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 =
|
|
10188
|
-
|
|
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 =
|
|
10226
|
-
|
|
10227
|
-
|
|
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 =
|
|
10242
|
-
|
|
10243
|
-
|
|
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
|
|
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 =
|
|
10309
|
-
|
|
10310
|
-
|
|
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 =
|
|
10344
|
-
|
|
10345
|
-
|
|
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 =
|
|
10370
|
-
|
|
10371
|
-
|
|
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 =
|
|
10399
|
-
|
|
10400
|
-
|
|
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 =
|
|
10444
|
-
|
|
10445
|
-
|
|
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 =
|
|
10473
|
-
|
|
10474
|
-
|
|
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 =
|
|
10532
|
-
|
|
10533
|
-
|
|
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 =
|
|
10575
|
-
|
|
10576
|
-
|
|
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 =
|
|
10594
|
-
|
|
10595
|
-
|
|
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 =
|
|
10612
|
-
|
|
10613
|
-
|
|
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 =
|
|
10640
|
-
|
|
10641
|
-
|
|
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 =
|
|
10655
|
-
|
|
10656
|
-
|
|
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
|
|
10682
|
-
create
|
|
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
|
|
10685
|
-
status
|
|
10686
|
-
events
|
|
10687
|
-
update
|
|
10688
|
-
delete
|
|
10689
|
-
transcript
|
|
10690
|
-
timeline
|
|
10691
|
-
enriched-timeline
|
|
10692
|
-
screen
|
|
10693
|
-
media
|
|
10694
|
-
audio
|
|
10695
|
-
reprocess
|
|
10696
|
-
retry-media-merge
|
|
10697
|
-
watch
|
|
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$
|
|
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$
|
|
10765
|
-
const projectRef =
|
|
10766
|
-
|
|
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$
|
|
10785
|
-
const projectRef =
|
|
10786
|
-
|
|
10787
|
-
|
|
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$
|
|
10803
|
-
const projectRef =
|
|
10804
|
-
|
|
10805
|
-
|
|
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$
|
|
10825
|
-
const projectRef =
|
|
10826
|
-
|
|
10827
|
-
|
|
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$
|
|
10847
|
-
const projectRef =
|
|
10848
|
-
|
|
10849
|
-
|
|
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$
|
|
10865
|
-
const projectRef =
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
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$
|
|
10887
|
-
const projectRef =
|
|
10888
|
-
|
|
10889
|
-
|
|
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$
|
|
10905
|
-
const projectRef =
|
|
10906
|
-
|
|
10907
|
-
|
|
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$
|
|
10936
|
-
const projectRef =
|
|
10937
|
-
|
|
10938
|
-
|
|
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$
|
|
10956
|
-
const projectRef =
|
|
10957
|
-
|
|
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
|
|
10994
|
-
get
|
|
10995
|
-
annotate
|
|
10996
|
-
dismiss
|
|
10997
|
-
undismiss
|
|
10998
|
-
link
|
|
10999
|
-
unlink
|
|
11000
|
-
delete
|
|
11001
|
-
bulk-link
|
|
11002
|
-
bulk-delete
|
|
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$
|
|
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$
|
|
11151
|
-
const projectRef =
|
|
11152
|
-
|
|
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$
|
|
11185
|
-
const projectRef =
|
|
11186
|
-
|
|
11187
|
-
|
|
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$
|
|
11203
|
-
const projectRef =
|
|
11204
|
-
|
|
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$
|
|
11234
|
-
const projectRef =
|
|
11235
|
-
|
|
11236
|
-
|
|
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$
|
|
11275
|
-
const projectRef =
|
|
11276
|
-
|
|
11277
|
-
|
|
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$
|
|
11324
|
-
const projectRef =
|
|
11325
|
-
|
|
11326
|
-
|
|
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$
|
|
11342
|
-
const projectRef =
|
|
11343
|
-
|
|
11344
|
-
|
|
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$
|
|
11379
|
-
const projectRef =
|
|
11380
|
-
|
|
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
|
|
11417
|
-
get
|
|
11418
|
-
create
|
|
11419
|
-
create-from-signals
|
|
11420
|
-
update
|
|
11421
|
-
delete
|
|
11422
|
-
|
|
11423
|
-
push
|
|
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$
|
|
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$
|
|
11510
|
-
const projectId =
|
|
11511
|
-
|
|
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$
|
|
11522
|
-
const projectId =
|
|
11523
|
-
|
|
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$
|
|
11598
|
-
const projectId =
|
|
11599
|
-
|
|
11600
|
-
|
|
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$
|
|
11614
|
-
const projectId =
|
|
11615
|
-
|
|
11616
|
-
|
|
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$
|
|
11654
|
-
const projectId =
|
|
11655
|
-
|
|
11656
|
-
|
|
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$
|
|
11683
|
-
const projectId =
|
|
11684
|
-
|
|
11685
|
-
|
|
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$
|
|
11704
|
-
const projectId =
|
|
11705
|
-
|
|
11706
|
-
|
|
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$
|
|
11727
|
-
const projectId =
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
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$
|
|
11745
|
-
const projectId =
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
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$
|
|
11768
|
-
const projectId =
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
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
|
|
11798
|
-
create
|
|
11799
|
-
get
|
|
11800
|
-
update
|
|
11801
|
-
delete
|
|
11802
|
-
set-questions
|
|
11803
|
-
list-responses
|
|
11804
|
-
get-response
|
|
11805
|
-
qualify-response
|
|
11806
|
-
disqualify-response
|
|
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$
|
|
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$
|
|
11926
|
-
const projectId =
|
|
11927
|
-
|
|
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$
|
|
11938
|
-
const projectId =
|
|
11939
|
-
|
|
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$
|
|
12010
|
-
const projectId =
|
|
12011
|
-
|
|
12012
|
-
|
|
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$
|
|
12026
|
-
const projectId =
|
|
12027
|
-
|
|
12028
|
-
|
|
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$
|
|
12074
|
-
const projectId =
|
|
12075
|
-
|
|
12076
|
-
|
|
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$
|
|
12103
|
-
const projectId =
|
|
12104
|
-
|
|
12105
|
-
|
|
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$
|
|
12126
|
-
const projectId =
|
|
12127
|
-
|
|
12128
|
-
|
|
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$
|
|
12147
|
-
const projectId =
|
|
12148
|
-
|
|
12149
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
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$
|
|
12246
|
-
const projectId =
|
|
12247
|
-
|
|
12248
|
-
|
|
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$
|
|
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
|
|
12309
|
-
create
|
|
12310
|
-
get
|
|
12311
|
-
update
|
|
12312
|
-
delete
|
|
12313
|
-
export
|
|
12314
|
-
import
|
|
12315
|
-
reprocess
|
|
12316
|
-
validate-script
|
|
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$
|
|
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$
|
|
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: '
|
|
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
|
|
12763
|
-
get
|
|
12764
|
-
list
|
|
12765
|
-
delete
|
|
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
|
|
12778
|
-
usertold config
|
|
12779
|
-
usertold config
|
|
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 --
|
|
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 =
|
|
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
|
|
12995
|
-
const snippet =
|
|
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
|
-
|
|
13022
|
-
|
|
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
|
|
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
|
-
|
|
13038
|
-
USERTOLD_API_KEY=... usertold init --
|
|
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 --
|
|
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
|
|
13696
|
+
async function resolveOrgHandleForInit(parsed, env) {
|
|
13054
13697
|
const orgHandle = parsed.options.org;
|
|
13055
|
-
if (
|
|
13056
|
-
|
|
13698
|
+
if (orgHandle && orgHandle !== 'true') {
|
|
13699
|
+
return orgHandle;
|
|
13057
13700
|
}
|
|
13058
|
-
return
|
|
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>
|
|
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.
|
|
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$
|
|
15498
|
+
subcommands: buildSubcommands(FLAGS$d)
|
|
14768
15499
|
},
|
|
14769
15500
|
{
|
|
14770
15501
|
name: 'project',
|
|
14771
|
-
subcommands: buildSubcommands(FLAGS$
|
|
15502
|
+
subcommands: buildSubcommands(FLAGS$c)
|
|
14772
15503
|
},
|
|
14773
15504
|
{
|
|
14774
15505
|
name: 'session',
|
|
14775
|
-
subcommands: buildSubcommands(FLAGS$
|
|
15506
|
+
subcommands: buildSubcommands(FLAGS$b)
|
|
14776
15507
|
},
|
|
14777
15508
|
{
|
|
14778
15509
|
name: 'signal',
|
|
14779
|
-
subcommands: buildSubcommands(FLAGS$
|
|
15510
|
+
subcommands: buildSubcommands(FLAGS$a)
|
|
14780
15511
|
},
|
|
14781
15512
|
{
|
|
14782
15513
|
name: 'task',
|
|
14783
|
-
subcommands: buildSubcommands(FLAGS$
|
|
15514
|
+
subcommands: buildSubcommands(FLAGS$9)
|
|
14784
15515
|
},
|
|
14785
15516
|
{
|
|
14786
15517
|
name: 'screener',
|
|
14787
|
-
subcommands: buildSubcommands(FLAGS$
|
|
15518
|
+
subcommands: buildSubcommands(FLAGS$8)
|
|
14788
15519
|
},
|
|
14789
15520
|
{
|
|
14790
15521
|
name: 'study',
|
|
14791
|
-
subcommands: buildSubcommands(FLAGS$
|
|
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$
|
|
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.
|
|
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,
|
|
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
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
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
|
-
--
|
|
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,
|
|
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 };
|