@madewithremy/admin 0.1.7 → 0.1.8

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/dist/prod.js CHANGED
@@ -36,10 +36,10 @@ function fatal(message) {
36
36
  throw new CliError(message);
37
37
  }
38
38
  var AdminApiError = class extends Error {
39
- constructor(method, path5, status2, body) {
40
- super(`API ${method} ${path5} returned ${status2}: ${JSON.stringify(body)}`);
39
+ constructor(method, path7, status2, body) {
40
+ super(`API ${method} ${path7} returned ${status2}: ${JSON.stringify(body)}`);
41
41
  this.method = method;
42
- this.path = path5;
42
+ this.path = path7;
43
43
  this.status = status2;
44
44
  this.body = body;
45
45
  this.name = "AdminApiError";
@@ -935,7 +935,7 @@ async function waitForCommit(ctx, params) {
935
935
  const { commitSha, onProgress } = params;
936
936
  const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
937
937
  const shortSha = commitSha.slice(0, 8);
938
- const start = Date.now();
938
+ const start2 = Date.now();
939
939
  let release;
940
940
  while (true) {
941
941
  const r = await byCommit(ctx, commitSha);
@@ -948,7 +948,7 @@ async function waitForCommit(ctx, params) {
948
948
  `Failed to resolve release for ${shortSha}: HTTP ${r.status} ${JSON.stringify(r.body)}`
949
949
  );
950
950
  }
951
- if (Date.now() - start > RESOLVE_GRACE_MS) {
951
+ if (Date.now() - start2 > RESOLVE_GRACE_MS) {
952
952
  return {
953
953
  outcome: "not_found",
954
954
  error: `No release created for commit ${commitSha} within ${RESOLVE_GRACE_MS / 1e3}s \u2014 either the push hasn't registered yet or this SHA never produced a build`
@@ -976,7 +976,7 @@ async function waitForCommit(ctx, params) {
976
976
  summary: summarizeRelease(release)
977
977
  };
978
978
  }
979
- if (Date.now() - start > timeoutMs) {
979
+ if (Date.now() - start2 > timeoutMs) {
980
980
  return {
981
981
  outcome: "timeout",
982
982
  release,
@@ -985,7 +985,7 @@ async function waitForCommit(ctx, params) {
985
985
  };
986
986
  }
987
987
  onProgress?.(
988
- `${release.status}\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
988
+ `${release.status}\u2026 (${Math.round((Date.now() - start2) / 1e3)}s)`
989
989
  );
990
990
  await sleep(POLL_MS);
991
991
  const r = await byCommit(ctx, commitSha);
@@ -1247,14 +1247,14 @@ async function diagnosticsGet(ctx, a) {
1247
1247
  const releaseId = await resolveReleaseId(ctx, a);
1248
1248
  if (a.bool("wait")) {
1249
1249
  const timeout = (a.num("timeout") ?? 120) * 1e3;
1250
- const start = Date.now();
1250
+ const start2 = Date.now();
1251
1251
  while (true) {
1252
1252
  const release2 = await getRelease(ctx, releaseId);
1253
1253
  if (release2.diagnostics) {
1254
1254
  out(release2.diagnostics);
1255
1255
  return;
1256
1256
  }
1257
- if (Date.now() - start > timeout) {
1257
+ if (Date.now() - start2 > timeout) {
1258
1258
  out(DIAGNOSTICS_PENDING);
1259
1259
  return;
1260
1260
  }
@@ -2869,11 +2869,55 @@ Notes:
2869
2869
  - Public image URLs accept transform params, e.g. ?w=400&fit=cover.`;
2870
2870
 
2871
2871
  // src/commands/dataSources.ts
2872
- import fs3 from "fs";
2873
- import path3 from "path";
2872
+ import fs5 from "fs";
2873
+ import path5 from "path";
2874
2874
 
2875
2875
  // src/ops/dataSources.ts
2876
2876
  import { createHash as createHash2 } from "crypto";
2877
+
2878
+ // src/poll.ts
2879
+ async function pollUntil(read, isSettled, options) {
2880
+ const start2 = Date.now();
2881
+ let transientSince = null;
2882
+ while (true) {
2883
+ let value;
2884
+ try {
2885
+ value = await read();
2886
+ transientSince = null;
2887
+ } catch (err) {
2888
+ if (!isTransientError(err)) {
2889
+ throw err;
2890
+ }
2891
+ transientSince ??= Date.now();
2892
+ if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
2893
+ throw err;
2894
+ }
2895
+ options.onProgress?.(
2896
+ `retrying after a gateway error\u2026 (${elapsedSeconds(start2)}s)`
2897
+ );
2898
+ await sleep(options.pollMs);
2899
+ continue;
2900
+ }
2901
+ if (isSettled(value)) {
2902
+ return { settled: true, value };
2903
+ }
2904
+ if (Date.now() - start2 > options.timeoutMs) {
2905
+ return { settled: false, value };
2906
+ }
2907
+ if (options.describe) {
2908
+ options.onProgress?.(options.describe(value, start2));
2909
+ }
2910
+ await sleep(options.pollMs);
2911
+ }
2912
+ }
2913
+ function elapsedSeconds(start2) {
2914
+ return Math.round((Date.now() - start2) / 1e3);
2915
+ }
2916
+ function timedOut(timeoutMs, still) {
2917
+ return `Timed out after ${Math.round(timeoutMs / 1e3)}s ${still}`;
2918
+ }
2919
+
2920
+ // src/ops/dataSources.ts
2877
2921
  var DEFAULT_WAIT_TIMEOUT_MS = 15 * 60 * 1e3;
2878
2922
  var POLL_MS2 = 3e3;
2879
2923
  function base(appId) {
@@ -2903,7 +2947,12 @@ async function addDocument(ctx, params) {
2903
2947
  );
2904
2948
  if (token.alreadyCurrent) {
2905
2949
  onProgress?.(`${filename}: unchanged, skipped`);
2906
- return { filename, skipped: true, document: token.document };
2950
+ return {
2951
+ filename,
2952
+ skipped: true,
2953
+ document: token.document,
2954
+ documents: token.documents ?? [token.document]
2955
+ };
2907
2956
  }
2908
2957
  onProgress?.(
2909
2958
  `${filename}: uploading ${(content.length / 1024 / 1024).toFixed(1)}MB\u2026`
@@ -2926,7 +2975,9 @@ async function addDocument(ctx, params) {
2926
2975
  filename,
2927
2976
  skipped: false,
2928
2977
  queued: confirmed.queued,
2929
- document: confirmed.document
2978
+ document: confirmed.document,
2979
+ documents: confirmed.documents ?? (confirmed.document ? [confirmed.document] : []),
2980
+ outcome: confirmed.outcome
2930
2981
  };
2931
2982
  }
2932
2983
  async function waitForIngest(ctx, params) {
@@ -2936,50 +2987,64 @@ async function waitForIngest(ctx, params) {
2936
2987
  return { status: "up-to-date", documents: [] };
2937
2988
  }
2938
2989
  const wanted = new Set(documentIds);
2939
- const start = Date.now();
2940
- let transientSince = null;
2941
- for (; ; ) {
2942
- let docs;
2943
- try {
2944
- docs = await allDocuments(ctx, { slug, ids: documentIds });
2945
- transientSince = null;
2946
- } catch (err) {
2947
- if (!isTransientError(err)) {
2948
- throw err;
2949
- }
2950
- transientSince ??= Date.now();
2951
- if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
2952
- throw err;
2953
- }
2954
- onProgress?.(
2955
- `retrying after a gateway error\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
2956
- );
2957
- await sleep(POLL_MS2);
2958
- continue;
2959
- }
2960
- const tracked = (docs ?? []).filter((d) => wanted.has(d.id));
2961
- const pending = tracked.filter((d) => d.status === "processing");
2962
- const failed = tracked.filter((d) => d.status === "error");
2963
- if (pending.length === 0) {
2964
- return {
2965
- status: failed.length ? "error" : "done",
2966
- documents: tracked.map(summarize)
2967
- };
2990
+ const pendingOf = (docs) => docs.filter((d) => d.status === "processing");
2991
+ const outcome = await pollUntil(
2992
+ async () => {
2993
+ const docs = await allDocuments(ctx, { slug, ids: documentIds });
2994
+ return (docs ?? []).filter((d) => wanted.has(d.id));
2995
+ },
2996
+ (tracked2) => pendingOf(tracked2).length === 0,
2997
+ {
2998
+ timeoutMs,
2999
+ pollMs: POLL_MS2,
3000
+ describe: (tracked2, start2) => `ingesting\u2026 ${tracked2.length - pendingOf(tracked2).length}/${tracked2.length} done (${elapsedSeconds(start2)}s)`,
3001
+ onProgress
2968
3002
  }
2969
- if (Date.now() - start > timeoutMs) {
2970
- return {
2971
- status: "timeout",
2972
- documents: tracked.map(summarize),
2973
- error: `Timed out after ${Math.round(timeoutMs / 1e3)}s with ${pending.length} document(s) still processing`
2974
- };
3003
+ );
3004
+ const tracked = outcome.value;
3005
+ if (!outcome.settled) {
3006
+ return {
3007
+ status: "timeout",
3008
+ documents: tracked.map(summarize),
3009
+ error: timedOut(
3010
+ timeoutMs,
3011
+ `with ${pendingOf(tracked).length} document(s) still processing`
3012
+ )
3013
+ };
3014
+ }
3015
+ return {
3016
+ status: tracked.some((d) => d.status === "error") ? "error" : "done",
3017
+ documents: tracked.map(summarize)
3018
+ };
3019
+ }
3020
+ async function waitForCandidate(ctx, params) {
3021
+ const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
3022
+ const pendingOf = (docs2) => docs2.filter((d) => d.status === "processing");
3023
+ const outcome = await pollUntil(
3024
+ () => allDocuments(ctx, { slug: params.slug, candidate: true }),
3025
+ (docs2) => pendingOf(docs2).length === 0,
3026
+ {
3027
+ timeoutMs,
3028
+ pollMs: POLL_MS2,
3029
+ describe: (docs2, start2) => `rebuilding\u2026 ${docs2.length - pendingOf(docs2).length}/${docs2.length} done (${elapsedSeconds(start2)}s)`,
3030
+ onProgress: params.onProgress
2975
3031
  }
2976
- onProgress?.(
2977
- `ingesting\u2026 ${tracked.length - pending.length}/${tracked.length} done (${Math.round(
2978
- (Date.now() - start) / 1e3
2979
- )}s)`
2980
- );
2981
- await sleep(POLL_MS2);
3032
+ );
3033
+ const docs = outcome.value;
3034
+ if (!outcome.settled) {
3035
+ return {
3036
+ status: "timeout",
3037
+ documents: docs.map(summarize),
3038
+ error: timedOut(
3039
+ timeoutMs,
3040
+ `with ${pendingOf(docs).length} document(s) still building`
3041
+ )
3042
+ };
2982
3043
  }
3044
+ return {
3045
+ status: docs.some((d) => d.status === "error") ? "error" : "ready",
3046
+ documents: docs.map(summarize)
3047
+ };
2983
3048
  }
2984
3049
  var DOCUMENTS_IDS_MAX = 200;
2985
3050
  function documents(ctx, params) {
@@ -3087,110 +3152,1758 @@ function move(ctx, params) {
3087
3152
  }
3088
3153
  async function waitForMove(ctx, params) {
3089
3154
  const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
3090
- const start = Date.now();
3091
- let transientSince = null;
3092
- for (; ; ) {
3093
- let source;
3094
- try {
3155
+ const outcome = await pollUntil(
3156
+ async () => {
3095
3157
  const { dataSources } = await list8(ctx);
3096
- source = dataSources.find((s) => s.slug === params.slug);
3097
- transientSince = null;
3098
- } catch (err) {
3099
- if (!isTransientError(err)) {
3100
- throw err;
3101
- }
3102
- transientSince ??= Date.now();
3103
- if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
3104
- throw err;
3158
+ const source2 = dataSources.find((s) => s.slug === params.slug);
3159
+ if (!source2) {
3160
+ throw new Error(`Data source "${params.slug}" not found.`);
3105
3161
  }
3106
- params.onProgress?.(
3107
- `retrying after a gateway error\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
3108
- );
3109
- await sleep(POLL_MS2);
3110
- continue;
3162
+ return source2;
3163
+ },
3164
+ (source2) => !source2.migration || source2.migration.error !== null,
3165
+ {
3166
+ timeoutMs,
3167
+ pollMs: POLL_MS2,
3168
+ describe: (source2, start2) => `moving\u2026 ${source2.migration.copied}/${source2.migration.total} documents (${elapsedSeconds(start2)}s)`,
3169
+ onProgress: params.onProgress
3111
3170
  }
3112
- if (!source) {
3113
- throw new Error(`Data source "${params.slug}" not found.`);
3171
+ );
3172
+ const source = outcome.value;
3173
+ const migration = source.migration;
3174
+ if (!outcome.settled) {
3175
+ return {
3176
+ status: "timeout",
3177
+ source,
3178
+ error: timedOut(
3179
+ timeoutMs,
3180
+ `with ${migration.copied}/${migration.total} documents copied`
3181
+ )
3182
+ };
3183
+ }
3184
+ if (migration?.error) {
3185
+ return { status: "failed", source, error: migration.error };
3186
+ }
3187
+ return { status: "done", source };
3188
+ }
3189
+ function revectorize(ctx, params) {
3190
+ const { slug, ingest } = params;
3191
+ return call(
3192
+ ctx,
3193
+ "POST",
3194
+ `${base(ctx.appId)}/revectorize`,
3195
+ {
3196
+ slug,
3197
+ ...ingest ? { ingest } : {}
3114
3198
  }
3115
- const migration = source.migration;
3116
- if (!migration) {
3117
- return { status: "done", source };
3199
+ );
3200
+ }
3201
+ function promote(ctx, params) {
3202
+ return call(
3203
+ ctx,
3204
+ "POST",
3205
+ `${base(ctx.appId)}/promote`,
3206
+ {
3207
+ slug: params.slug,
3208
+ ...params.force ? { force: true } : {}
3118
3209
  }
3119
- if (migration.error) {
3120
- return { status: "failed", source, error: migration.error };
3210
+ );
3211
+ }
3212
+ function drop(ctx, params) {
3213
+ return call(
3214
+ ctx,
3215
+ "POST",
3216
+ `${base(ctx.appId)}/versions/drop`,
3217
+ {
3218
+ slug: params.slug,
3219
+ ...params.version !== void 0 ? { version: params.version } : {}
3121
3220
  }
3122
- if (Date.now() - start > timeoutMs) {
3123
- return {
3124
- status: "timeout",
3125
- source,
3126
- error: `Timed out after ${Math.round(timeoutMs / 1e3)}s with ${migration.copied}/${migration.total} documents copied`
3127
- };
3221
+ );
3222
+ }
3223
+ function rm(ctx, params) {
3224
+ return call(
3225
+ ctx,
3226
+ "POST",
3227
+ `${base(ctx.appId)}/documents/delete`,
3228
+ {
3229
+ slug: params.slug,
3230
+ documentId: params.documentId
3231
+ }
3232
+ );
3233
+ }
3234
+ function rmWhere(ctx, params) {
3235
+ return call(
3236
+ ctx,
3237
+ "POST",
3238
+ `${base(ctx.appId)}/documents/delete-many`,
3239
+ { slug: params.slug, filter: params.filter }
3240
+ );
3241
+ }
3242
+ async function rmWhereAll(ctx, params) {
3243
+ let deleted = 0;
3244
+ while (true) {
3245
+ const page = await rmWhere(ctx, params);
3246
+ deleted += page.deleted;
3247
+ if (page.remaining === 0 || page.deleted === 0) {
3248
+ return { deleted };
3128
3249
  }
3129
3250
  params.onProgress?.(
3130
- `moving\u2026 ${migration.copied}/${migration.total} documents (${Math.round(
3131
- (Date.now() - start) / 1e3
3132
- )}s)`
3251
+ `removed ${deleted.toLocaleString()} so far, ${page.remaining.toLocaleString()} remaining\u2026`
3252
+ );
3253
+ }
3254
+ }
3255
+ function deleteSource(ctx, slug) {
3256
+ return call(
3257
+ ctx,
3258
+ "POST",
3259
+ `${base(ctx.appId)}/delete`,
3260
+ { slug }
3261
+ );
3262
+ }
3263
+
3264
+ // src/ops/dataSourceJobs.ts
3265
+ var DEFAULT_WAIT_TIMEOUT_MS2 = 60 * 60 * 1e3;
3266
+ var POLL_MS3 = 5e3;
3267
+ function base2(appId) {
3268
+ return `/_internal/v2/apps/${appId}/datasources/jobs`;
3269
+ }
3270
+ function start(ctx, params) {
3271
+ const { slug, source, approve: approve2, budgetDollars, concurrency, limit } = params;
3272
+ return call(ctx, "POST", base2(ctx.appId), {
3273
+ slug,
3274
+ source,
3275
+ ...approve2 ? { approve: true } : {},
3276
+ ...budgetDollars !== void 0 ? { budgetDollars } : {},
3277
+ ...concurrency !== void 0 ? { concurrency } : {},
3278
+ ...limit !== void 0 ? { limit } : {}
3279
+ });
3280
+ }
3281
+ async function uploadManifest(ctx, params) {
3282
+ const token = await call(
3283
+ ctx,
3284
+ "POST",
3285
+ `${base2(ctx.appId)}/manifest-token`,
3286
+ { slug: params.slug }
3287
+ );
3288
+ await uploadDirect(token.upload, params.content, "manifest.jsonl");
3289
+ return { key: token.key };
3290
+ }
3291
+ function list9(ctx, params) {
3292
+ return call(
3293
+ ctx,
3294
+ "GET",
3295
+ `${base2(ctx.appId)}${qs({ slug: params?.slug })}`
3296
+ );
3297
+ }
3298
+ function get7(ctx, params) {
3299
+ return call(
3300
+ ctx,
3301
+ "GET",
3302
+ `${base2(ctx.appId)}/${encodeURIComponent(params.id)}`
3303
+ );
3304
+ }
3305
+ function approve(ctx, params) {
3306
+ return call(
3307
+ ctx,
3308
+ "POST",
3309
+ `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/approve`
3310
+ );
3311
+ }
3312
+ function pause(ctx, params) {
3313
+ return call(
3314
+ ctx,
3315
+ "POST",
3316
+ `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/pause`
3317
+ );
3318
+ }
3319
+ function resume(ctx, params) {
3320
+ return call(
3321
+ ctx,
3322
+ "POST",
3323
+ `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/resume`
3324
+ );
3325
+ }
3326
+ function cancel(ctx, params) {
3327
+ return call(
3328
+ ctx,
3329
+ "POST",
3330
+ `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/cancel`
3331
+ );
3332
+ }
3333
+ function waitForPlan(ctx, params) {
3334
+ return settle(ctx, params, (job) => job.state !== "planning");
3335
+ }
3336
+ function waitForJob(ctx, params) {
3337
+ return settle(
3338
+ ctx,
3339
+ params,
3340
+ (job) => !["planning", "planned", "running"].includes(job.state) || job.state === "planned" && (job.error !== null || !job.autoApprove)
3341
+ );
3342
+ }
3343
+ async function settle(ctx, params, isSettled) {
3344
+ const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS2;
3345
+ const outcome = await pollUntil(
3346
+ async () => (await get7(ctx, { id: params.id })).job,
3347
+ isSettled,
3348
+ {
3349
+ timeoutMs,
3350
+ pollMs: POLL_MS3,
3351
+ describe: describeProgress,
3352
+ onProgress: params.onProgress
3353
+ }
3354
+ );
3355
+ const job = outcome.value;
3356
+ if (!outcome.settled) {
3357
+ return {
3358
+ status: "timeout",
3359
+ job,
3360
+ error: timedOut(timeoutMs, `; job is still ${job.state}`)
3361
+ };
3362
+ }
3363
+ if (job.state === "failed" || job.state === "cancelled") {
3364
+ return {
3365
+ status: "failed",
3366
+ job,
3367
+ error: job.error ?? `The job was ${job.state}.`
3368
+ };
3369
+ }
3370
+ if (job.state === "paused") {
3371
+ return {
3372
+ status: "failed",
3373
+ job,
3374
+ error: job.error ?? `The job paused (${job.pauseReason ?? "user"}). Resume it to continue.`
3375
+ };
3376
+ }
3377
+ if (job.state === "planned" && (job.error !== null || job.autoApprove)) {
3378
+ return {
3379
+ status: "planned",
3380
+ job,
3381
+ error: job.error ?? "The plan is ready but the job did not start. Approve it once the cause is fixed."
3382
+ };
3383
+ }
3384
+ return { status: "done", job };
3385
+ }
3386
+ function describeProgress(job, start2) {
3387
+ const elapsed = `(${elapsedSeconds(start2)}s)`;
3388
+ if (job.state === "planning") {
3389
+ return job.enumerationDone ? `planning\u2026 reading a sample of ${job.counts.objectsSeen.toLocaleString()} objects ${elapsed}` : `planning\u2026 ${job.counts.objectsSeen.toLocaleString()} objects counted ${elapsed}`;
3390
+ }
3391
+ const total = job.plan?.projected.documents ?? job.counts.objectsSeen;
3392
+ const processed = job.counts.documentsDone + job.counts.documentsSkipped + job.counts.documentsFailed;
3393
+ const spend = `$${job.estimatedDollars.toFixed(2)}${job.budgetDollars !== null ? ` of $${job.budgetDollars.toFixed(2)}` : ""}`;
3394
+ return `${job.state}\u2026 ${processed.toLocaleString()}/${total.toLocaleString()} documents \xB7 ${spend} ${elapsed}`;
3395
+ }
3396
+
3397
+ // src/commands/_shared/dataSources.ts
3398
+ var DEFAULT_SOURCE = "default";
3399
+ var sourceOf = (a) => a.str("source") || DEFAULT_SOURCE;
3400
+ var timeoutOf = (a) => {
3401
+ const seconds = a.str("timeout");
3402
+ return seconds ? { timeoutMs: Number(seconds) * 1e3 } : {};
3403
+ };
3404
+ function failOnWait(result) {
3405
+ if (result.status === "timeout") {
3406
+ throw new CliError(result.error ?? "Timed out.", EXIT.timeout);
3407
+ }
3408
+ if (result.status === "failed" || result.status === "error") {
3409
+ throw new CliError(result.error ?? "Failed.", EXIT.buildFailed);
3410
+ }
3411
+ if (result.status === "planned") {
3412
+ throw new CliError(
3413
+ `${result.error ?? "The plan is ready but the job did not start."} Fix the cause, then \`datasources jobs approve <id>\`.`,
3414
+ EXIT.buildFailed
3133
3415
  );
3134
- await sleep(POLL_MS2);
3135
3416
  }
3136
3417
  }
3137
- function revectorize(ctx, params) {
3138
- const { slug, ingest } = params;
3418
+ async function waitAndReport(ctx, a, id, mode, summary2) {
3419
+ const wait = mode === "plan" ? waitForPlan : waitForJob;
3420
+ const result = await wait(ctx, { id, ...timeoutOf(a), onProgress: progress });
3421
+ out({ ...summary2, ...result });
3422
+ failOnWait(result);
3423
+ }
3424
+
3425
+ // src/ops/dataSourceConnectors.ts
3426
+ function base3(appId) {
3427
+ return `/_internal/v2/apps/${appId}/datasources`;
3428
+ }
3429
+ function connect(ctx, params) {
3430
+ const {
3431
+ slug,
3432
+ bucket,
3433
+ region,
3434
+ prefix,
3435
+ endpoint,
3436
+ accessKeyIdSecret,
3437
+ secretAccessKeySecret,
3438
+ deletions,
3439
+ budgetDollarsPerSync
3440
+ } = params;
3441
+ return call(
3442
+ ctx,
3443
+ "POST",
3444
+ `${base3(ctx.appId)}/connector`,
3445
+ {
3446
+ slug,
3447
+ bucket,
3448
+ region,
3449
+ ...prefix !== void 0 ? { prefix } : {},
3450
+ ...endpoint !== void 0 ? { endpoint } : {},
3451
+ accessKeyIdSecret,
3452
+ secretAccessKeySecret,
3453
+ ...deletions !== void 0 ? { deletions } : {},
3454
+ ...budgetDollarsPerSync !== void 0 ? { budgetDollarsPerSync } : {}
3455
+ }
3456
+ );
3457
+ }
3458
+ function get8(ctx, params) {
3459
+ return call(
3460
+ ctx,
3461
+ "GET",
3462
+ `${base3(ctx.appId)}/connector${qs({ slug: params.slug })}`
3463
+ );
3464
+ }
3465
+ function disconnect(ctx, params) {
3466
+ return call(
3467
+ ctx,
3468
+ "POST",
3469
+ `${base3(ctx.appId)}/connector/remove`,
3470
+ { slug: params.slug }
3471
+ );
3472
+ }
3473
+ function sync(ctx, params) {
3474
+ return call(ctx, "POST", `${base3(ctx.appId)}/sync`, {
3475
+ slug: params.slug,
3476
+ ...params.budgetDollars !== void 0 ? { budgetDollars: params.budgetDollars } : {}
3477
+ });
3478
+ }
3479
+
3480
+ // src/commands/dataSourceConnectors.ts
3481
+ var dataSourceConnectorsSpecs = {
3482
+ "datasources connect": {
3483
+ usage: "Usage: remy-admin datasources connect --source <slug> --bucket <name> --region <aws-region> [--prefix <p>] [--endpoint <url>] --access-key-secret <NAME> --secret-key-secret <NAME> [--deletions mirror|keep] [--budget-per-sync <dollars>]",
3484
+ flags: {
3485
+ source: { type: "string" },
3486
+ bucket: { type: "string" },
3487
+ region: { type: "string" },
3488
+ prefix: { type: "string" },
3489
+ endpoint: { type: "string" },
3490
+ "access-key-secret": { type: "string" },
3491
+ "secret-key-secret": { type: "string" },
3492
+ deletions: { type: "string" },
3493
+ "budget-per-sync": { type: "number", min: 0.01 }
3494
+ }
3495
+ },
3496
+ "datasources connector": {
3497
+ usage: "Usage: remy-admin datasources connector [--source <slug>]",
3498
+ flags: { source: { type: "string" } }
3499
+ },
3500
+ "datasources disconnect": {
3501
+ usage: "Usage: remy-admin datasources disconnect --source <slug>",
3502
+ flags: { source: { type: "string" } }
3503
+ },
3504
+ "datasources sync": {
3505
+ usage: "Usage: remy-admin datasources sync [--source <slug>] [--budget <dollars>] [--wait] [--timeout <sec>]",
3506
+ flags: {
3507
+ source: { type: "string" },
3508
+ budget: { type: "number", min: 0.01 },
3509
+ wait: { type: "boolean" },
3510
+ timeout: { type: "string" }
3511
+ }
3512
+ }
3513
+ };
3514
+ async function connect2(ctx, a) {
3515
+ const slug = a.str("source");
3516
+ if (!slug) {
3517
+ fatal("--source is required \u2014 a connector is bound to one named source.");
3518
+ }
3519
+ const bucket = a.str("bucket");
3520
+ const region = a.str("region");
3521
+ const accessKeyIdSecret = a.str("access-key-secret");
3522
+ const secretAccessKeySecret = a.str("secret-key-secret");
3523
+ for (const [flag, value] of [
3524
+ ["bucket", bucket],
3525
+ ["region", region],
3526
+ ["access-key-secret", accessKeyIdSecret],
3527
+ ["secret-key-secret", secretAccessKeySecret]
3528
+ ]) {
3529
+ if (!value) {
3530
+ fatal(`--${flag} is required.`);
3531
+ }
3532
+ }
3533
+ const deletions = a.str("deletions");
3534
+ if (deletions && deletions !== "mirror" && deletions !== "keep") {
3535
+ fatal(`--deletions must be "mirror" or "keep" (got "${deletions}").`);
3536
+ }
3537
+ const prefix = a.str("prefix");
3538
+ const endpoint = a.str("endpoint");
3539
+ const budget = a.num("budget-per-sync");
3540
+ const { connector } = await connect(ctx, {
3541
+ slug,
3542
+ bucket,
3543
+ region,
3544
+ ...prefix !== void 0 ? { prefix } : {},
3545
+ ...endpoint !== void 0 ? { endpoint } : {},
3546
+ accessKeyIdSecret,
3547
+ secretAccessKeySecret,
3548
+ ...deletions ? { deletions } : {},
3549
+ ...budget !== void 0 ? { budgetDollarsPerSync: budget } : {}
3550
+ });
3551
+ out({
3552
+ dataSource: slug,
3553
+ connector,
3554
+ note: "Connected. Run `datasources sync --source " + slug + " --wait` to load the bucket; the first sync plans first and stops for `datasources jobs approve <id>` when the plan is over the per-sync budget."
3555
+ });
3556
+ }
3557
+ async function show(ctx, a) {
3558
+ const slug = sourceOf(a);
3559
+ out({ dataSource: slug, ...await get8(ctx, { slug }) });
3560
+ }
3561
+ async function disconnect2(ctx, a) {
3562
+ const slug = a.str("source");
3563
+ if (!slug) {
3564
+ fatal(
3565
+ "--source is required \u2014 disconnecting never falls back to the default source."
3566
+ );
3567
+ }
3568
+ const result = await disconnect(ctx, { slug });
3569
+ out({
3570
+ dataSource: slug,
3571
+ ...result,
3572
+ note: "The documents stay. Connect again to follow a bucket; unchanged files are recognised by content and cost nothing to re-sync."
3573
+ });
3574
+ }
3575
+ async function sync2(ctx, a) {
3576
+ const slug = sourceOf(a);
3577
+ const budget = a.num("budget");
3578
+ const { job } = await sync(ctx, {
3579
+ slug,
3580
+ ...budget !== void 0 ? { budgetDollars: budget } : {}
3581
+ });
3582
+ if (!a.bool("wait")) {
3583
+ out({
3584
+ dataSource: slug,
3585
+ job,
3586
+ note: "Syncing. Poll `datasources jobs status <id>`, or re-run with --wait. A plan over the per-sync budget waits in `planned` for `datasources jobs approve <id>`."
3587
+ });
3588
+ return;
3589
+ }
3590
+ await waitAndReport(ctx, a, job.id, "job", {
3591
+ dataSource: slug,
3592
+ action: "sync"
3593
+ });
3594
+ }
3595
+ var dataSourceConnectorsHandlers = {
3596
+ "datasources connect": connect2,
3597
+ "datasources connector": show,
3598
+ "datasources disconnect": disconnect2,
3599
+ "datasources sync": sync2
3600
+ };
3601
+ var dataSourceConnectorsHelp = `
3602
+ Following an S3 bucket (connectors):
3603
+ remy-admin datasources connect --source <slug> --bucket <name> --region <aws-region> [--prefix <p>] --access-key-secret <NAME> --secret-key-secret <NAME> [--deletions mirror|keep] [--budget-per-sync <dollars>]
3604
+ remy-admin datasources connector [--source <slug>]
3605
+ remy-admin datasources sync [--source <slug>] [--budget <dollars>] [--wait] [--timeout <sec>]
3606
+ remy-admin datasources disconnect --source <slug>
3607
+
3608
+ A connector makes a bucket the customer owns the origin of a source. The
3609
+ credentials are the NAMES of two app secrets \u2014 set them first with
3610
+ \`remy-admin secrets set ARCHIVE_S3_KEY --prod <value>\` (or in the dashboard);
3611
+ never put a key on a command line or in code. connect checks that both
3612
+ secrets exist and that the keys can list the prefix before it records
3613
+ anything, and refuses otherwise with the S3 error.
3614
+
3615
+ sync lists the bucket, compares every object's ETag with what was ingested
3616
+ before, and runs a job (see above) over what is new or changed: the same
3617
+ plan, the same gates, auto-approved under the connector's per-sync budget.
3618
+ A plan over the budget \u2014 the first backfill of a big bucket \u2014 waits in
3619
+ \`planned\` for \`jobs approve <id>\`; the nightly deltas run unattended.
3620
+ A sync over an unchanged bucket costs nothing. Keys that disappeared from
3621
+ the bucket are removed from the source at the end of a full sync under the
3622
+ default \`--deletions mirror\`; \`--deletions keep\` leaves them. A changed
3623
+ object replaces its document; the old one is kept as lineage.
3624
+
3625
+ Scheduling is the app's: a nightly sync is an ordinary cron interface job
3626
+ whose method calls \`Source.sync()\` from the SDK. There is no platform
3627
+ schedule to configure.
3628
+
3629
+ On a source without a mapper, objects whose extension no extractor reads
3630
+ are skipped, not failed. Re-running connect with the same bucket, region,
3631
+ prefix and endpoint updates credentials and policy in place; pointing a
3632
+ source at a different bucket needs a disconnect first. disconnect keeps
3633
+ every document \u2014 only the link and its object index go.
3634
+ `;
3635
+
3636
+ // src/commands/dataSourceEvals.ts
3637
+ import fs3 from "fs";
3638
+ import path3 from "path";
3639
+
3640
+ // src/ops/dataSourceEvals.ts
3641
+ var DEFAULT_WAIT_TIMEOUT_MS3 = 30 * 60 * 1e3;
3642
+ var POLL_MS4 = 4e3;
3643
+ function base4(appId) {
3644
+ return `/_internal/v2/apps/${appId}/datasources`;
3645
+ }
3646
+ function hydrate(ctx, params) {
3647
+ return call(
3648
+ ctx,
3649
+ "POST",
3650
+ `${base4(ctx.appId)}/hydrate`,
3651
+ { slug: params.slug }
3652
+ );
3653
+ }
3654
+ async function waitForHydration(ctx, params) {
3655
+ const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS3;
3656
+ const outcome = await pollUntil(
3657
+ async () => {
3658
+ const { dataSources } = await list8(ctx);
3659
+ const source2 = dataSources.find((s) => s.slug === params.slug);
3660
+ if (!source2) {
3661
+ throw new Error(`Data source "${params.slug}" not found.`);
3662
+ }
3663
+ return source2;
3664
+ },
3665
+ (source2) => !source2.hydration || source2.hydration.error !== null,
3666
+ {
3667
+ timeoutMs,
3668
+ pollMs: POLL_MS4,
3669
+ describe: (source2, start2) => `loading\u2026 ${source2.hydration.copied}/${source2.hydration.total} documents (${elapsedSeconds(start2)}s)`,
3670
+ onProgress: params.onProgress
3671
+ }
3672
+ );
3673
+ const source = outcome.value;
3674
+ const hydration = source.hydration;
3675
+ if (!outcome.settled) {
3676
+ return {
3677
+ status: "timeout",
3678
+ source,
3679
+ error: timedOut(
3680
+ timeoutMs,
3681
+ `with ${hydration.copied}/${hydration.total} documents loaded`
3682
+ )
3683
+ };
3684
+ }
3685
+ if (hydration?.error) {
3686
+ return { status: "failed", source, error: hydration.error };
3687
+ }
3688
+ return { status: "done", source };
3689
+ }
3690
+ function sample(ctx, params) {
3691
+ const { slug, size, as, filter, stratify, placement } = params;
3692
+ return call(
3693
+ ctx,
3694
+ "POST",
3695
+ `${base4(ctx.appId)}/sample`,
3696
+ {
3697
+ slug,
3698
+ size,
3699
+ ...as ? { as } : {},
3700
+ ...filter ? { filter } : {},
3701
+ ...stratify ? { stratify } : {},
3702
+ ...placement !== void 0 ? { placement } : {}
3703
+ }
3704
+ );
3705
+ }
3706
+ function createSet(ctx, params) {
3707
+ const { slug, name, description, size, style, modelId, tags, seed } = params;
3708
+ return call(ctx, "POST", `${base4(ctx.appId)}/eval/sets`, {
3709
+ slug,
3710
+ name,
3711
+ ...description ? { description } : {},
3712
+ ...size !== void 0 ? { size } : {},
3713
+ ...style ? { style } : {},
3714
+ ...modelId ? { modelId } : {},
3715
+ ...tags?.length ? { tags } : {},
3716
+ ...seed !== void 0 ? { seed } : {}
3717
+ });
3718
+ }
3719
+ function listSets(ctx, params) {
3720
+ return call(
3721
+ ctx,
3722
+ "GET",
3723
+ `${base4(ctx.appId)}/eval/sets${qs({ slug: params?.slug })}`
3724
+ );
3725
+ }
3726
+ function getSet(ctx, params) {
3727
+ return call(
3728
+ ctx,
3729
+ "GET",
3730
+ `${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}${qs({
3731
+ queries: params.queries ? "true" : void 0
3732
+ })}`
3733
+ );
3734
+ }
3735
+ function deleteSet(ctx, params) {
3736
+ return call(
3737
+ ctx,
3738
+ "POST",
3739
+ `${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}/delete`
3740
+ );
3741
+ }
3742
+ function addQueries(ctx, params) {
3743
+ return call(
3744
+ ctx,
3745
+ "POST",
3746
+ `${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}/queries`,
3747
+ { queries: params.queries }
3748
+ );
3749
+ }
3750
+ function listQueries(ctx, params) {
3751
+ return call(
3752
+ ctx,
3753
+ "GET",
3754
+ `${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}/queries`
3755
+ );
3756
+ }
3757
+ function deleteQuery(ctx, params) {
3758
+ return call(
3759
+ ctx,
3760
+ "POST",
3761
+ `${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}/queries/delete`,
3762
+ { queryId: params.queryId }
3763
+ );
3764
+ }
3765
+ function run(ctx, params) {
3766
+ const { setId, slug, candidate, label, retrieval } = params;
3767
+ return call(ctx, "POST", `${base4(ctx.appId)}/eval/runs`, {
3768
+ setId,
3769
+ ...slug ? { slug } : {},
3770
+ ...candidate ? { candidate: true } : {},
3771
+ ...label ? { label } : {},
3772
+ ...retrieval && Object.keys(retrieval).length ? { retrieval } : {}
3773
+ });
3774
+ }
3775
+ function listRuns(ctx, params) {
3776
+ return call(
3777
+ ctx,
3778
+ "GET",
3779
+ `${base4(ctx.appId)}/eval/runs${qs({ setId: params?.setId, slug: params?.slug })}`
3780
+ );
3781
+ }
3782
+ function getRun(ctx, params) {
3783
+ return call(
3784
+ ctx,
3785
+ "GET",
3786
+ `${base4(ctx.appId)}/eval/runs/${encodeURIComponent(params.id)}${qs({
3787
+ queries: params.queries ? "true" : void 0,
3788
+ worst: params.worst
3789
+ })}`
3790
+ );
3791
+ }
3792
+ function compare(ctx, params) {
3793
+ return call(
3794
+ ctx,
3795
+ "GET",
3796
+ `${base4(ctx.appId)}/eval/runs/compare${qs({ a: params.a, b: params.b })}`
3797
+ );
3798
+ }
3799
+ async function waitForRun(ctx, params) {
3800
+ const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS3;
3801
+ const outcome = await pollUntil(
3802
+ async () => (await getRun(ctx, { id: params.id })).run,
3803
+ (run4) => run4.state === "done" || run4.state === "failed",
3804
+ {
3805
+ timeoutMs,
3806
+ pollMs: POLL_MS4,
3807
+ describe: (run4, start2) => `${run4.state}\u2026 ${run4.completed}/${run4.total} queries (${elapsedSeconds(start2)}s)`,
3808
+ onProgress: params.onProgress
3809
+ }
3810
+ );
3811
+ const run3 = outcome.value;
3812
+ if (!outcome.settled) {
3813
+ return {
3814
+ status: "timeout",
3815
+ run: run3,
3816
+ error: timedOut(
3817
+ timeoutMs,
3818
+ `; run is ${run3.state} (${run3.completed}/${run3.total})`
3819
+ )
3820
+ };
3821
+ }
3822
+ if (run3.state === "failed") {
3823
+ return { status: "failed", run: run3, error: run3.error ?? "The run failed." };
3824
+ }
3825
+ return { status: "done", run: run3 };
3826
+ }
3827
+ async function waitForSet(ctx, params) {
3828
+ const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS3;
3829
+ const outcome = await pollUntil(
3830
+ async () => (await getSet(ctx, { id: params.id })).set,
3831
+ (set4) => set4.state === "ready" || set4.state === "failed",
3832
+ {
3833
+ timeoutMs,
3834
+ pollMs: POLL_MS4,
3835
+ describe: (set4, start2) => `${set4.state}\u2026 (${elapsedSeconds(start2)}s)`,
3836
+ onProgress: params.onProgress
3837
+ }
3838
+ );
3839
+ const set3 = outcome.value;
3840
+ if (!outcome.settled) {
3841
+ return {
3842
+ status: "timeout",
3843
+ set: set3,
3844
+ error: timedOut(timeoutMs, `; set is still ${set3.state}`)
3845
+ };
3846
+ }
3847
+ if (set3.state === "failed") {
3848
+ return { status: "failed", set: set3, error: set3.error ?? "Generation failed." };
3849
+ }
3850
+ return { status: "done", set: set3 };
3851
+ }
3852
+
3853
+ // src/commands/dataSourceEvals.ts
3854
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3855
+ var dataSourceEvalsSpecs = {
3856
+ "datasources hydrate": {
3857
+ usage: "Usage: remy-admin datasources hydrate [--source <slug>] [--wait] [--timeout <sec>]",
3858
+ flags: {
3859
+ source: { type: "string" },
3860
+ wait: { type: "boolean" },
3861
+ timeout: { type: "string" }
3862
+ }
3863
+ },
3864
+ "datasources sample": {
3865
+ usage: "Usage: remy-admin datasources sample --source <slug> --size <n> [--as <slug>] [--filter <k=v,...|json>] [--stratify <metadata-key>] [--placement <resource-id|shared>] [--wait] [--timeout <sec>]",
3866
+ flags: {
3867
+ source: { type: "string" },
3868
+ size: { type: "number", min: 1 },
3869
+ as: { type: "string" },
3870
+ filter: { type: "string" },
3871
+ stratify: { type: "string" },
3872
+ placement: { type: "string" },
3873
+ wait: { type: "boolean" },
3874
+ timeout: { type: "string" }
3875
+ },
3876
+ requireAnyOf: {
3877
+ flags: ["size"],
3878
+ message: "--size <n> is required (documents to draw, up to 2000)."
3879
+ }
3880
+ },
3881
+ "datasources eval create": {
3882
+ usage: "Usage: remy-admin datasources eval create --source <slug> --name <name> [--size <n>] [--style cloze|question] [--model <id>] [--tags <a,b>] [--description <text>] [--seed <n>] [--wait] [--timeout <sec>]",
3883
+ flags: {
3884
+ source: { type: "string" },
3885
+ name: { type: "string" },
3886
+ size: { type: "number", min: 0 },
3887
+ style: { type: "string" },
3888
+ model: { type: "string" },
3889
+ tags: { type: "string" },
3890
+ description: { type: "string" },
3891
+ seed: { type: "number", min: 0 },
3892
+ wait: { type: "boolean" },
3893
+ timeout: { type: "string" }
3894
+ },
3895
+ requireAnyOf: { flags: ["name"], message: "--name is required." }
3896
+ },
3897
+ "datasources eval list": {
3898
+ usage: "Usage: remy-admin datasources eval list [--source <slug>]",
3899
+ flags: { source: { type: "string" } }
3900
+ },
3901
+ "datasources eval get": {
3902
+ usage: "Usage: remy-admin datasources eval get <setId> [--queries]",
3903
+ positionals: [{ name: "id", required: true }],
3904
+ flags: { queries: { type: "boolean" } }
3905
+ },
3906
+ "datasources eval delete": {
3907
+ usage: "Usage: remy-admin datasources eval delete <setId>",
3908
+ positionals: [{ name: "id", required: true }]
3909
+ },
3910
+ "datasources eval add": {
3911
+ usage: "Usage: remy-admin datasources eval add --set <id> --query <text> (--expect <ref,...> | --expect-file <name,...> | --expect-external <id,...>) [--passage <text>] [--tags <a,b>]",
3912
+ flags: {
3913
+ set: { type: "string" },
3914
+ query: { type: "string" },
3915
+ expect: { type: "string" },
3916
+ "expect-file": { type: "string" },
3917
+ "expect-external": { type: "string" },
3918
+ passage: { type: "string" },
3919
+ tags: { type: "string" }
3920
+ },
3921
+ requireAnyOf: {
3922
+ flags: ["expect", "expect-file", "expect-external"],
3923
+ message: "Say which document should come back: --expect <documentId|filename|externalId,...>, --expect-file <filename,...> or --expect-external <externalId,...>."
3924
+ }
3925
+ },
3926
+ "datasources eval import": {
3927
+ usage: "Usage: remy-admin datasources eval import --set <id> <file.jsonl>",
3928
+ positionals: [{ name: "file", required: true }],
3929
+ flags: { set: { type: "string" } }
3930
+ },
3931
+ "datasources eval queries": {
3932
+ usage: "Usage: remy-admin datasources eval queries --set <id>",
3933
+ flags: { set: { type: "string" } }
3934
+ },
3935
+ "datasources eval rm": {
3936
+ usage: "Usage: remy-admin datasources eval rm --set <id> <queryId>",
3937
+ positionals: [{ name: "queryId", required: true }],
3938
+ flags: { set: { type: "string" } }
3939
+ },
3940
+ "datasources eval run": {
3941
+ usage: "Usage: remy-admin datasources eval run --set <id> [--source <slug>] [--candidate] [--label <text>] [--mode hybrid|semantic|lexical] [--rerank true|false] [--rerank-model <id>] [--candidates <n>] [--hybrid true|false] [--top-k <n>] [--wait] [--timeout <sec>]",
3942
+ flags: {
3943
+ set: { type: "string" },
3944
+ source: { type: "string" },
3945
+ candidate: { type: "boolean" },
3946
+ label: { type: "string" },
3947
+ mode: { type: "string" },
3948
+ rerank: { type: "string" },
3949
+ "rerank-model": { type: "string" },
3950
+ candidates: { type: "number", min: 1 },
3951
+ hybrid: { type: "string" },
3952
+ "top-k": { type: "number", min: 1 },
3953
+ wait: { type: "boolean" },
3954
+ timeout: { type: "string" }
3955
+ },
3956
+ requireAnyOf: { flags: ["set"], message: "--set <id> is required." }
3957
+ },
3958
+ "datasources eval runs": {
3959
+ usage: "Usage: remy-admin datasources eval runs [--set <id>] [--source <slug>]",
3960
+ flags: { set: { type: "string" }, source: { type: "string" } }
3961
+ },
3962
+ "datasources eval result": {
3963
+ usage: "Usage: remy-admin datasources eval result <runId> [--queries] [--worst <n>]",
3964
+ positionals: [{ name: "id", required: true }],
3965
+ flags: { queries: { type: "boolean" }, worst: { type: "number", min: 1 } }
3966
+ },
3967
+ "datasources eval compare": {
3968
+ usage: "Usage: remy-admin datasources eval compare <runA> <runB>",
3969
+ positionals: [
3970
+ { name: "a", required: true },
3971
+ { name: "b", required: true }
3972
+ ]
3973
+ }
3974
+ };
3975
+ var requireSet = (a) => {
3976
+ const id = a.str("set");
3977
+ if (!id) {
3978
+ fatal("--set <id> is required.");
3979
+ }
3980
+ return id;
3981
+ };
3982
+ var splitList = (raw) => (raw ?? "").split(",").map((s) => s.trim()).filter(Boolean);
3983
+ var triState = (a, flag) => {
3984
+ const raw = a.str(flag);
3985
+ if (raw === void 0) {
3986
+ return void 0;
3987
+ }
3988
+ if (raw !== "true" && raw !== "false") {
3989
+ fatal(`--${flag} must be "true" or "false" (got "${raw}").`);
3990
+ }
3991
+ return raw === "true";
3992
+ };
3993
+ function selectorFromFlag(raw) {
3994
+ if (raw === void 0) {
3995
+ return void 0;
3996
+ }
3997
+ if (raw.trimStart().startsWith("{")) {
3998
+ try {
3999
+ return JSON.parse(raw);
4000
+ } catch (err) {
4001
+ fatal(`--filter is not valid JSON: ${err.message}`);
4002
+ }
4003
+ }
4004
+ const metadata = {};
4005
+ for (const part of raw.split(",")) {
4006
+ const trimmed = part.trim();
4007
+ if (!trimmed) {
4008
+ continue;
4009
+ }
4010
+ const eq = trimmed.indexOf("=");
4011
+ if (eq <= 0) {
4012
+ fatal(`--filter entries must be key=value (got "${trimmed}").`);
4013
+ }
4014
+ const key = trimmed.slice(0, eq).trim();
4015
+ const value = trimmed.slice(eq + 1).trim();
4016
+ metadata[key] = value === "true" ? true : value === "false" ? false : value !== "" && !Number.isNaN(Number(value)) ? Number(value) : value;
4017
+ }
4018
+ return { metadata };
4019
+ }
4020
+ async function hydrate2(ctx, a) {
4021
+ const slug = a.str("source") || DEFAULT_SOURCE;
4022
+ const started = await hydrate(ctx, { slug });
4023
+ if (started.resident || !a.bool("wait")) {
4024
+ out({
4025
+ dataSource: slug,
4026
+ ...started,
4027
+ ...started.resident ? { note: "The index is already loaded." } : {
4028
+ note: "Loading in the background. `datasources list` shows progress; re-run with --wait to block."
4029
+ }
4030
+ });
4031
+ return;
4032
+ }
4033
+ const result = await waitForHydration(ctx, {
4034
+ slug,
4035
+ ...timeoutOf(a),
4036
+ onProgress: progress
4037
+ });
4038
+ out({ dataSource: slug, action: "hydrate", ...result });
4039
+ failOnWait(result);
4040
+ }
4041
+ async function sampleCmd(ctx, a) {
4042
+ const slug = a.str("source");
4043
+ if (!slug) {
4044
+ fatal("--source is required \u2014 a sample is drawn from one named source.");
4045
+ }
4046
+ const placementRaw = a.str("placement");
4047
+ const placement = placementRaw === void 0 ? void 0 : placementRaw === "shared" ? "shared" : { resourceId: placementRaw };
4048
+ const result = await sample(ctx, {
4049
+ slug,
4050
+ size: a.num("size"),
4051
+ ...a.str("as") ? { as: a.str("as") } : {},
4052
+ ...a.str("filter") ? { filter: selectorFromFlag(a.str("filter")) } : {},
4053
+ ...a.str("stratify") ? { stratify: a.str("stratify") } : {},
4054
+ ...placement !== void 0 ? { placement } : {}
4055
+ });
4056
+ if (!a.bool("wait")) {
4057
+ out({
4058
+ ...result,
4059
+ note: `Sampling into "${result.dataSource.slug}". Poll \`datasources jobs status ${result.job.id}\` or re-run with --wait.`
4060
+ });
4061
+ return;
4062
+ }
4063
+ await waitAndReport(ctx, a, result.job.id, "job", {
4064
+ dataSource: result.dataSource,
4065
+ action: "sample"
4066
+ });
4067
+ }
4068
+ async function evalCreate(ctx, a) {
4069
+ const slug = a.str("source") || DEFAULT_SOURCE;
4070
+ const style = a.str("style");
4071
+ if (style && style !== "cloze" && style !== "question") {
4072
+ fatal(`--style must be "cloze" or "question" (got "${style}").`);
4073
+ }
4074
+ const size = a.num("size");
4075
+ const seed = a.num("seed");
4076
+ const { set: set3 } = await createSet(ctx, {
4077
+ slug,
4078
+ name: a.str("name"),
4079
+ ...a.str("description") ? { description: a.str("description") } : {},
4080
+ ...size !== void 0 ? { size } : {},
4081
+ ...style ? { style } : {},
4082
+ ...a.str("model") ? { modelId: a.str("model") } : {},
4083
+ ...a.str("tags") ? { tags: splitList(a.str("tags")) } : {},
4084
+ ...seed !== void 0 ? { seed } : {}
4085
+ });
4086
+ const generating = set3.state === "queued" || set3.state === "generating";
4087
+ if (!generating || !a.bool("wait")) {
4088
+ out({
4089
+ dataSource: slug,
4090
+ set: set3,
4091
+ note: generating ? "Generating in the background. `datasources eval get <id>` shows the state; re-run with --wait to block." : "Empty set. Add queries with `datasources eval add` or `eval import`."
4092
+ });
4093
+ return;
4094
+ }
4095
+ const result = await waitForSet(ctx, {
4096
+ id: set3.id,
4097
+ ...timeoutOf(a),
4098
+ onProgress: progress
4099
+ });
4100
+ out({ dataSource: slug, action: "create", ...result });
4101
+ failOnWait(result);
4102
+ }
4103
+ async function evalList(ctx, a) {
4104
+ const slug = a.str("source");
4105
+ out(await listSets(ctx, slug ? { slug } : void 0));
4106
+ }
4107
+ async function evalGet(ctx, a) {
4108
+ out(
4109
+ await getSet(ctx, {
4110
+ id: a.req("id"),
4111
+ ...a.bool("queries") ? { queries: true } : {}
4112
+ })
4113
+ );
4114
+ }
4115
+ async function evalDelete(ctx, a) {
4116
+ out(await deleteSet(ctx, { id: a.req("id") }));
4117
+ }
4118
+ function expectFromFlags(a) {
4119
+ const expect = {};
4120
+ for (const ref of splitList(a.str("expect"))) {
4121
+ if (UUID_RE.test(ref)) {
4122
+ (expect.documentIds ??= []).push(ref);
4123
+ } else if (ref.includes(".")) {
4124
+ (expect.filenames ??= []).push(ref);
4125
+ } else {
4126
+ (expect.externalIds ??= []).push(ref);
4127
+ }
4128
+ }
4129
+ for (const name of splitList(a.str("expect-file"))) {
4130
+ (expect.filenames ??= []).push(name);
4131
+ }
4132
+ for (const id of splitList(a.str("expect-external"))) {
4133
+ (expect.externalIds ??= []).push(id);
4134
+ }
4135
+ return expect;
4136
+ }
4137
+ async function evalAdd(ctx, a) {
4138
+ const id = requireSet(a);
4139
+ const query3 = a.str("query");
4140
+ if (!query3) {
4141
+ fatal("--query is required.");
4142
+ }
4143
+ const result = await addQueries(ctx, {
4144
+ id,
4145
+ queries: [
4146
+ {
4147
+ query: query3,
4148
+ expect: expectFromFlags(a),
4149
+ ...a.str("passage") ? { passage: a.str("passage") } : {},
4150
+ ...a.str("tags") ? { tags: splitList(a.str("tags")) } : {}
4151
+ }
4152
+ ]
4153
+ });
4154
+ out({ set: id, ...result });
4155
+ }
4156
+ async function evalImport(ctx, a) {
4157
+ const id = requireSet(a);
4158
+ const file = a.req("file");
4159
+ const abs = path3.isAbsolute(file) ? file : path3.join(WORKSPACE_DIR, file);
4160
+ let content;
4161
+ try {
4162
+ content = fs3.readFileSync(abs, "utf8");
4163
+ } catch (err) {
4164
+ fatal(`Could not read "${file}": ${err.message}`);
4165
+ }
4166
+ const rows = [];
4167
+ content.split("\n").forEach((line, i) => {
4168
+ const trimmed = line.trim();
4169
+ if (!trimmed) {
4170
+ return;
4171
+ }
4172
+ let parsed;
4173
+ try {
4174
+ parsed = JSON.parse(trimmed);
4175
+ } catch {
4176
+ fatal(`${file}:${i + 1} is not valid JSON.`);
4177
+ }
4178
+ if (typeof parsed?.query !== "string" || typeof parsed?.expect !== "object") {
4179
+ fatal(
4180
+ `${file}:${i + 1} needs { "query": "...", "expect": { "filenames" | "documentIds" | "externalIds" | "contentHashes": [...] } }.`
4181
+ );
4182
+ }
4183
+ rows.push(parsed);
4184
+ });
4185
+ if (rows.length === 0) {
4186
+ fatal(`"${file}" has no queries.`);
4187
+ }
4188
+ let added = 0;
4189
+ let queryCount = 0;
4190
+ for (let i = 0; i < rows.length; i += 500) {
4191
+ progress(`importing ${Math.min(i + 500, rows.length)}/${rows.length}\u2026`);
4192
+ const result = await addQueries(ctx, {
4193
+ id,
4194
+ queries: rows.slice(i, i + 500)
4195
+ });
4196
+ added += result.added;
4197
+ queryCount = result.queryCount;
4198
+ }
4199
+ out({ set: id, added, queryCount });
4200
+ }
4201
+ async function evalQueries(ctx, a) {
4202
+ out(await listQueries(ctx, { id: requireSet(a) }));
4203
+ }
4204
+ async function evalRm(ctx, a) {
4205
+ out(
4206
+ await deleteQuery(ctx, {
4207
+ id: requireSet(a),
4208
+ queryId: a.req("queryId")
4209
+ })
4210
+ );
4211
+ }
4212
+ async function evalRun(ctx, a) {
4213
+ const mode = a.str("mode");
4214
+ if (mode && !["hybrid", "semantic", "lexical"].includes(mode)) {
4215
+ fatal(`--mode must be "hybrid", "semantic" or "lexical" (got "${mode}").`);
4216
+ }
4217
+ const rerank = triState(a, "rerank");
4218
+ const hybrid = triState(a, "hybrid");
4219
+ const rerankModel = a.str("rerank-model");
4220
+ const candidates = a.num("candidates");
4221
+ const topK = a.num("top-k");
4222
+ const retrieval = {
4223
+ ...mode ? { mode } : {},
4224
+ ...hybrid !== void 0 ? { hybrid } : {},
4225
+ ...rerank !== void 0 || rerankModel || candidates !== void 0 ? {
4226
+ rerank: {
4227
+ ...rerank !== void 0 ? { enabled: rerank } : {},
4228
+ ...rerankModel ? { modelId: rerankModel } : {},
4229
+ ...candidates !== void 0 ? { candidates } : {}
4230
+ }
4231
+ } : {},
4232
+ ...topK !== void 0 ? { topK } : {}
4233
+ };
4234
+ const { run: run3 } = await run(ctx, {
4235
+ setId: a.str("set"),
4236
+ ...a.str("source") ? { slug: a.str("source") } : {},
4237
+ ...a.bool("candidate") ? { candidate: true } : {},
4238
+ ...a.str("label") ? { label: a.str("label") } : {},
4239
+ retrieval
4240
+ });
4241
+ if (!a.bool("wait")) {
4242
+ out({
4243
+ run: run3,
4244
+ note: "Running in the background. `datasources eval result <id>` shows the numbers when it is done; re-run with --wait to block."
4245
+ });
4246
+ return;
4247
+ }
4248
+ const result = await waitForRun(ctx, {
4249
+ id: run3.id,
4250
+ ...timeoutOf(a),
4251
+ onProgress: progress
4252
+ });
4253
+ out({ action: "run", ...result });
4254
+ failOnWait(result);
4255
+ }
4256
+ async function evalRuns(ctx, a) {
4257
+ const setId = a.str("set");
4258
+ const slug = a.str("source");
4259
+ out(
4260
+ await listRuns(ctx, {
4261
+ ...setId ? { setId } : {},
4262
+ ...slug ? { slug } : {}
4263
+ })
4264
+ );
4265
+ }
4266
+ async function evalResult(ctx, a) {
4267
+ const worst = a.num("worst");
4268
+ out(
4269
+ await getRun(ctx, {
4270
+ id: a.req("id"),
4271
+ ...a.bool("queries") ? { queries: true } : {},
4272
+ ...worst !== void 0 ? { worst } : {}
4273
+ })
4274
+ );
4275
+ }
4276
+ async function evalCompare(ctx, a) {
4277
+ out(await compare(ctx, { a: a.req("a"), b: a.req("b") }));
4278
+ }
4279
+ var dataSourceEvalsHandlers = {
4280
+ "datasources hydrate": hydrate2,
4281
+ "datasources sample": sampleCmd,
4282
+ "datasources eval create": evalCreate,
4283
+ "datasources eval list": evalList,
4284
+ "datasources eval get": evalGet,
4285
+ "datasources eval delete": evalDelete,
4286
+ "datasources eval add": evalAdd,
4287
+ "datasources eval import": evalImport,
4288
+ "datasources eval queries": evalQueries,
4289
+ "datasources eval rm": evalRm,
4290
+ "datasources eval run": evalRun,
4291
+ "datasources eval runs": evalRuns,
4292
+ "datasources eval result": evalResult,
4293
+ "datasources eval compare": evalCompare
4294
+ };
4295
+ var dataSourceEvalsHelp = `
4296
+ Warming and sampling:
4297
+ remy-admin datasources hydrate [--source <slug>] [--wait]
4298
+ remy-admin datasources sample --source <slug> --size <n> [--as <slug>] [--filter <k=v,...|json>] [--stratify <key>] [--placement <id|shared>] [--wait]
4299
+
4300
+ A large source whose index was evicted (the shared pool holds a working set,
4301
+ not everything) answers searches with index_warming while it reloads in the
4302
+ background; small sources reload inside the search and never show it.
4303
+ \`hydrate\` starts that reload ahead of time \u2014 before a demo, say \u2014 and
4304
+ \`--wait\` blocks until the index is back. \`datasources list\` shows the
4305
+ reload's progress under \`hydration\`.
4306
+
4307
+ \`sample\` draws documents from a source into a new one that shares its
4308
+ exact pinned config, so what you measure on the sample says something about
4309
+ the parent. Uniform by default; --stratify <metadata key> takes each value
4310
+ in proportion; --filter scopes the draw. It runs as a job (auto-approved;
4311
+ extraction is reused, so the plan is chunk + embed only). Documents keep
4312
+ their content hashes, which is what lets a query set built on the sample
4313
+ score the parent as well. Up to 2,000 documents.
4314
+
4315
+ Retrieval evals:
4316
+ remy-admin datasources eval create --source <slug> --name <name> [--size <n>] [--style cloze|question] [--model <id>] [--tags <a,b>] [--wait]
4317
+ remy-admin datasources eval list [--source <slug>]
4318
+ remy-admin datasources eval get <setId> [--queries]
4319
+ remy-admin datasources eval add --set <id> --query <text> --expect <ref,...> [--passage <text>] [--tags <a,b>]
4320
+ remy-admin datasources eval import --set <id> <file.jsonl>
4321
+ remy-admin datasources eval queries --set <id>
4322
+ remy-admin datasources eval rm --set <id> <queryId>
4323
+ remy-admin datasources eval delete <setId>
4324
+ remy-admin datasources eval run --set <id> [--source <slug>] [--candidate] [--label <text>] [retrieval options] [--wait]
4325
+ remy-admin datasources eval runs [--set <id>] [--source <slug>]
4326
+ remy-admin datasources eval result <runId> [--queries] [--worst <n>]
4327
+ remy-admin datasources eval compare <runA> <runB>
4328
+
4329
+ A query SET is a list of questions with the documents that should come
4330
+ back. \`create --size N\` generates them from the corpus: the default
4331
+ \`cloze\` style holds one sentence out of a chunk and uses it as the query
4332
+ (free and deterministic, but it flatters keyword matching \u2014 a real user
4333
+ does not type sentences from the document); \`--style question\` has a
4334
+ chat model write the question a user would ask (a model call per query,
4335
+ cost recorded on the set). Both record the document AND the chunk that
4336
+ answers, and both skip sentences that restate the title. Add real user
4337
+ questions with \`add\` (--expect takes document ids, filenames or external
4338
+ ids, comma-separated; every reference must exist) or \`import\` a JSONL of
4339
+ {"query","expect":{"filenames":[...]},"passage"?,"tags"?}.
4340
+
4341
+ A RUN scores a set against one target: the set's own source by default,
4342
+ --source another (the parent of a sample, or a sample of the parent \u2014
4343
+ expectations resolve by content hash), --candidate the target's candidate
4344
+ version. Every query is a real search through the same code apps use, ten
4345
+ results deep, with explain on. Retrieval options override the target's
4346
+ live config for the run only: --mode, --rerank true|false, --rerank-model
4347
+ <id>, --candidates <n>, --hybrid true|false, --top-k <n>. Runs are capped
4348
+ at 2,000 queries and cost what 2,000 searches cost.
4349
+
4350
+ \`result\` prints the aggregates \u2014 recall@1/5/10, chunk recall, MRR,
4351
+ nDCG@10, latency p50/p95, cost per query, how often reranking ran, and
4352
+ which branch (dense, lexical, both) found the expected document, per tag
4353
+ too \u2014 plus \`--worst N\` for the queries that missed and what came back
4354
+ instead, or \`--queries\` for every row. \`compare\` puts two runs side by
4355
+ side with deltas and, when they share a set, per-query wins and losses.
4356
+ The output is JSON; the table is yours to write.
4357
+
4358
+ The workflow: sample the source, create a set on the sample, run it against
4359
+ the live version, revectorize with the change you want to test, run again
4360
+ with --candidate, compare, promote or drop.
4361
+ `;
4362
+
4363
+ // src/commands/dataSourceJobs.ts
4364
+ import fs4 from "fs";
4365
+ import path4 from "path";
4366
+ var dataSourceJobsSpecs = {
4367
+ "datasources jobs start": {
4368
+ usage: "Usage: remy-admin datasources jobs start [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --manifest <file.jsonl>) [--limit <n>] [--budget <dollars>] [--concurrency <n>] [--approve] [--wait] [--timeout <sec>]",
4369
+ flags: {
4370
+ source: { type: "string" },
4371
+ store: { type: "string" },
4372
+ access: { type: "string" },
4373
+ prefix: { type: "string" },
4374
+ manifest: { type: "string" },
4375
+ limit: { type: "number", min: 1 },
4376
+ budget: { type: "number", min: 0.01 },
4377
+ concurrency: { type: "number", min: 1 },
4378
+ approve: { type: "boolean" },
4379
+ wait: { type: "boolean" },
4380
+ timeout: { type: "string" }
4381
+ },
4382
+ requireAnyOf: {
4383
+ flags: ["store", "manifest"],
4384
+ message: "Give the job something to read: --store <name> [--prefix <p>], or --manifest <file.jsonl>."
4385
+ }
4386
+ },
4387
+ "datasources jobs list": {
4388
+ usage: "Usage: remy-admin datasources jobs list [--source <slug>]",
4389
+ flags: { source: { type: "string" } }
4390
+ },
4391
+ "datasources jobs status": {
4392
+ usage: "Usage: remy-admin datasources jobs status <id> [--wait] [--timeout <sec>]",
4393
+ positionals: [{ name: "id", required: true }],
4394
+ flags: { wait: { type: "boolean" }, timeout: { type: "string" } }
4395
+ },
4396
+ "datasources jobs approve": {
4397
+ usage: "Usage: remy-admin datasources jobs approve <id> [--wait] [--timeout <sec>]",
4398
+ positionals: [{ name: "id", required: true }],
4399
+ flags: { wait: { type: "boolean" }, timeout: { type: "string" } }
4400
+ },
4401
+ "datasources jobs pause": {
4402
+ usage: "Usage: remy-admin datasources jobs pause <id>",
4403
+ positionals: [{ name: "id", required: true }]
4404
+ },
4405
+ "datasources jobs resume": {
4406
+ usage: "Usage: remy-admin datasources jobs resume <id> [--wait] [--timeout <sec>]",
4407
+ positionals: [{ name: "id", required: true }],
4408
+ flags: { wait: { type: "boolean" }, timeout: { type: "string" } }
4409
+ },
4410
+ "datasources jobs cancel": {
4411
+ usage: "Usage: remy-admin datasources jobs cancel <id>",
4412
+ positionals: [{ name: "id", required: true }]
4413
+ }
4414
+ };
4415
+ async function jobsStart(ctx, a) {
4416
+ const slug = sourceOf(a);
4417
+ const store = a.str("store");
4418
+ const manifest = a.str("manifest");
4419
+ if (store && manifest) {
4420
+ fatal("Give --store or --manifest, not both.");
4421
+ }
4422
+ let source;
4423
+ if (manifest) {
4424
+ const abs = path4.isAbsolute(manifest) ? manifest : path4.join(WORKSPACE_DIR, manifest);
4425
+ let content;
4426
+ try {
4427
+ content = fs4.readFileSync(abs);
4428
+ } catch (err) {
4429
+ fatal(`Could not read manifest "${manifest}": ${err.message}`);
4430
+ }
4431
+ progress(`uploading manifest (${(content.length / 1024).toFixed(0)} KB)\u2026`);
4432
+ const { key } = await uploadManifest(ctx, { slug, content });
4433
+ source = { type: "manifest", key };
4434
+ } else {
4435
+ const access = a.str("access") ?? "private";
4436
+ if (access !== "private" && access !== "public") {
4437
+ fatal(`--access must be "private" or "public" (got "${access}").`);
4438
+ }
4439
+ source = {
4440
+ type: "store",
4441
+ store,
4442
+ access,
4443
+ prefix: a.str("prefix") ?? ""
4444
+ };
4445
+ }
4446
+ const budget = a.num("budget");
4447
+ const concurrency = a.num("concurrency");
4448
+ const limit = a.num("limit");
4449
+ const { job } = await start(ctx, {
4450
+ slug,
4451
+ source,
4452
+ ...a.bool("approve") ? { approve: true } : {},
4453
+ ...budget !== void 0 ? { budgetDollars: budget } : {},
4454
+ ...concurrency !== void 0 ? { concurrency } : {},
4455
+ ...limit !== void 0 ? { limit } : {}
4456
+ });
4457
+ if (!a.bool("wait")) {
4458
+ out({
4459
+ dataSource: slug,
4460
+ job,
4461
+ note: job.autoApprove ? "Planning, then running once the plan passes the gates. Poll `datasources jobs status <id>` or re-run with --wait." : "Planning. When the plan is ready, review it with `datasources jobs status <id>` and run `datasources jobs approve <id>`."
4462
+ });
4463
+ return;
4464
+ }
4465
+ await waitAndReport(ctx, a, job.id, job.autoApprove ? "job" : "plan", {
4466
+ dataSource: slug,
4467
+ action: "start",
4468
+ ...job.autoApprove ? {} : { note: "Review the plan, then `datasources jobs approve <id>`." }
4469
+ });
4470
+ }
4471
+ async function jobsList(ctx, a) {
4472
+ const slug = a.str("source");
4473
+ out(await list9(ctx, slug ? { slug } : void 0));
4474
+ }
4475
+ async function jobsStatus(ctx, a) {
4476
+ const id = a.req("id");
4477
+ if (!a.bool("wait")) {
4478
+ out(await get7(ctx, { id }));
4479
+ return;
4480
+ }
4481
+ await waitAndReport(ctx, a, id, "job", { action: "status" });
4482
+ }
4483
+ async function jobsApprove(ctx, a) {
4484
+ const id = a.req("id");
4485
+ const { job } = await approve(ctx, { id });
4486
+ if (!a.bool("wait")) {
4487
+ out({
4488
+ job,
4489
+ note: "Running. Poll `datasources jobs status <id>` or re-run with --wait."
4490
+ });
4491
+ return;
4492
+ }
4493
+ await waitAndReport(ctx, a, id, "job", { action: "approve" });
4494
+ }
4495
+ async function jobsPause(ctx, a) {
4496
+ out(await pause(ctx, { id: a.req("id") }));
4497
+ }
4498
+ async function jobsResume(ctx, a) {
4499
+ const id = a.req("id");
4500
+ const { job } = await resume(ctx, { id });
4501
+ if (!a.bool("wait")) {
4502
+ out({ job });
4503
+ return;
4504
+ }
4505
+ await waitAndReport(ctx, a, id, "job", { action: "resume" });
4506
+ }
4507
+ async function jobsCancel(ctx, a) {
4508
+ out(await cancel(ctx, { id: a.req("id") }));
4509
+ }
4510
+ var dataSourceJobsHandlers = {
4511
+ "datasources jobs start": jobsStart,
4512
+ "datasources jobs list": jobsList,
4513
+ "datasources jobs status": jobsStatus,
4514
+ "datasources jobs approve": jobsApprove,
4515
+ "datasources jobs pause": jobsPause,
4516
+ "datasources jobs resume": jobsResume,
4517
+ "datasources jobs cancel": jobsCancel
4518
+ };
4519
+ var dataSourceJobsHelp = `
4520
+ Bulk ingestion (jobs):
4521
+ remy-admin datasources jobs start [--source <slug>] --store <name> [--access <private|public>] [--prefix <p>] [options]
4522
+ remy-admin datasources jobs start [--source <slug>] --manifest <file.jsonl> [options]
4523
+ remy-admin datasources jobs list [--source <slug>]
4524
+ remy-admin datasources jobs status <id> [--wait] [--timeout <sec>]
4525
+ remy-admin datasources jobs approve <id> [--wait] [--timeout <sec>]
4526
+ remy-admin datasources jobs pause|resume|cancel <id>
4527
+
4528
+ A job loads a whole corpus at once. It reads either every object under a
4529
+ prefix of one of the app's file stores (\`files put\` fills one; an app can
4530
+ write one at runtime) or a manifest you upload: JSONL, one line per document,
4531
+ {"url": "https://\u2026", "filename": "x.pdf", "externalId": "\u2026", "metadata": {\u2026}}.
4532
+ filename, externalId and metadata are optional; the URL must be public.
4533
+
4534
+ Nothing is spent until you say so. The platform counts the source, reads a
4535
+ sample of about a hundred documents, and writes a plan: documents, chunks,
4536
+ cost per stage (embedding, extraction, contextual) at today's rates, storage,
4537
+ whether it fits where the source lives, and a duration. \`jobs status\` shows
4538
+ it; \`jobs approve\` starts the run. Two gates: the corpus has to fit the
4539
+ source's placement (a shared-pool source over the per-source cap answers
4540
+ plan_requires_dedicated: place it on dedicated capacity first), and the
4541
+ workspace has to be able to cover the projection (insufficient_credits).
4542
+
4543
+ Options on start:
4544
+ --approve Run as soon as the plan passes the gates, no separate approve
4545
+ --budget <dollars> Pause when estimated spend reaches this (default 1.5x the plan)
4546
+ --concurrency <n> Batches in flight at once (default 8, max 64)
4547
+ --limit <n> Only the first N objects: a cheap sample of the corpus
4548
+ --wait Block until the plan is ready (or, with --approve, until done)
4549
+
4550
+ While running, documents are read in batches of fifty with one embedding
4551
+ call per batch. Unchanged documents are skipped by content hash, so re-running
4552
+ a job over the same source costs nothing new. Per-document failures are kept
4553
+ on the job (\`jobs status\` lists the last twenty) and the run continues; five
4554
+ failed batches in a row pause it, as does the budget. \`jobs resume\` continues
4555
+ from the checkpoint and retries failed batches; \`jobs cancel\` stops it and
4556
+ keeps what was indexed. Search works on the partial corpus throughout.
4557
+
4558
+ One bulk operation per source: a job refuses to start during a move or while
4559
+ a candidate version exists, and moves, re-vectorizes and deletes refuse while
4560
+ a job is in flight (data_source_busy). Interactive \`add\` keeps working.
4561
+
4562
+ --wait exit codes: 0 done \xB7 1 the job failed, paused, was cancelled, or sits
4563
+ in planned because a gate refused it (approve once the cause is fixed) \xB7
4564
+ 2 still running when --timeout elapsed (default 3600s).
4565
+ `;
4566
+
4567
+ // src/ops/dataSourceMappers.ts
4568
+ function base5(appId) {
4569
+ return `/_internal/v2/apps/${appId}/datasources`;
4570
+ }
4571
+ function selectionBody(selection) {
4572
+ if ("connector" in selection) {
4573
+ return { connector: true };
4574
+ }
4575
+ return {
4576
+ store: selection.store,
4577
+ ...selection.access ? { access: selection.access } : {},
4578
+ ...selection.prefix !== void 0 ? { prefix: selection.prefix } : {}
4579
+ };
4580
+ }
4581
+ function inspect(ctx, params) {
3139
4582
  return call(
3140
4583
  ctx,
3141
4584
  "POST",
3142
- `${base(ctx.appId)}/revectorize`,
4585
+ `${base5(ctx.appId)}/inspect`,
3143
4586
  {
3144
- slug,
3145
- ...ingest ? { ingest } : {}
4587
+ ...params.slug ? { slug: params.slug } : {},
4588
+ ...selectionBody(params.selection),
4589
+ ...params.keys ? { keys: params.keys } : {},
4590
+ ...params.limit !== void 0 ? { limit: params.limit } : {}
3146
4591
  }
3147
4592
  );
3148
4593
  }
3149
- function promote(ctx, params) {
4594
+ function mapTest(ctx, params) {
3150
4595
  return call(
3151
4596
  ctx,
3152
4597
  "POST",
3153
- `${base(ctx.appId)}/promote`,
4598
+ `${base5(ctx.appId)}/map-test`,
3154
4599
  {
3155
4600
  slug: params.slug,
3156
- ...params.force ? { force: true } : {}
4601
+ ...selectionBody(params.selection),
4602
+ ...params.keys ? { keys: params.keys } : {},
4603
+ ...params.limit !== void 0 ? { limit: params.limit } : {},
4604
+ ...params.dev ? { dev: true } : {}
3157
4605
  }
3158
4606
  );
3159
4607
  }
3160
- function drop(ctx, params) {
4608
+ function remap(ctx, params) {
4609
+ return call(ctx, "POST", `${base5(ctx.appId)}/remap`, {
4610
+ slug: params.slug,
4611
+ ...params.budgetDollars !== void 0 ? { budgetDollars: params.budgetDollars } : {},
4612
+ ...params.limit !== void 0 ? { limit: params.limit } : {}
4613
+ });
4614
+ }
4615
+ function quarantine(ctx, params) {
3161
4616
  return call(
3162
4617
  ctx,
3163
- "POST",
3164
- `${base(ctx.appId)}/versions/drop`,
3165
- {
3166
- slug: params.slug,
3167
- ...params.version !== void 0 ? { version: params.version } : {}
3168
- }
4618
+ "GET",
4619
+ `${base5(ctx.appId)}/jobs/${params.id}/quarantine${qs({
4620
+ ...params.kind ? { kind: params.kind } : {},
4621
+ ...params.reason ? { reason: params.reason } : {},
4622
+ ...params.cursor ? { cursor: params.cursor } : {},
4623
+ ...params.limit !== void 0 ? { limit: params.limit } : {}
4624
+ })}`
3169
4625
  );
3170
4626
  }
3171
- function rm(ctx, params) {
4627
+ function replay(ctx, params) {
3172
4628
  return call(
3173
4629
  ctx,
3174
4630
  "POST",
3175
- `${base(ctx.appId)}/documents/delete`,
3176
- {
3177
- slug: params.slug,
3178
- documentId: params.documentId
3179
- }
4631
+ `${base5(ctx.appId)}/jobs/${params.id}/replay`,
4632
+ params.kind ? { kind: params.kind } : {}
3180
4633
  );
3181
4634
  }
3182
- function deleteSource(ctx, slug) {
3183
- return call(
3184
- ctx,
3185
- "POST",
3186
- `${base(ctx.appId)}/delete`,
3187
- { slug }
4635
+
4636
+ // src/commands/dataSourceMappers.ts
4637
+ var SELECTION_FLAGS = {
4638
+ store: { type: "string" },
4639
+ access: { type: "string" },
4640
+ prefix: { type: "string" },
4641
+ connector: { type: "boolean" },
4642
+ keys: { type: "string" }
4643
+ };
4644
+ var dataSourceMappersSpecs = {
4645
+ "datasources inspect": {
4646
+ usage: "Usage: remy-admin datasources inspect [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --connector) [--keys k1,k2,...] [--limit <n>]",
4647
+ flags: {
4648
+ source: { type: "string" },
4649
+ ...SELECTION_FLAGS,
4650
+ limit: { type: "number", min: 1 }
4651
+ },
4652
+ requireAnyOf: {
4653
+ flags: ["store", "connector"],
4654
+ message: "Name the objects: --store <name> [--prefix <p>], or --connector for the source's bucket."
4655
+ }
4656
+ },
4657
+ "datasources map test": {
4658
+ usage: "Usage: remy-admin datasources map test [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --connector) [--keys k1,k2,...] [--limit <n>] [--dev] [--full]",
4659
+ flags: {
4660
+ source: { type: "string" },
4661
+ ...SELECTION_FLAGS,
4662
+ limit: { type: "number", min: 1 },
4663
+ dev: { type: "boolean" },
4664
+ full: { type: "boolean" }
4665
+ },
4666
+ requireAnyOf: {
4667
+ flags: ["store", "connector"],
4668
+ message: "Name the objects: --store <name> [--prefix <p>], or --connector for the source's bucket."
4669
+ }
4670
+ },
4671
+ "datasources remap": {
4672
+ usage: "Usage: remy-admin datasources remap [--source <slug>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]",
4673
+ flags: {
4674
+ source: { type: "string" },
4675
+ budget: { type: "number", min: 0.01 },
4676
+ limit: { type: "number", min: 1 },
4677
+ wait: { type: "boolean" },
4678
+ timeout: { type: "string" }
4679
+ }
4680
+ },
4681
+ "datasources jobs quarantine": {
4682
+ usage: "Usage: remy-admin datasources jobs quarantine <id> [--kind skip|error] [--reason <text>] [--cursor <id>] [--limit <n>]",
4683
+ positionals: [{ name: "id", required: true }],
4684
+ flags: {
4685
+ kind: { type: "string" },
4686
+ reason: { type: "string" },
4687
+ cursor: { type: "string" },
4688
+ limit: { type: "number", min: 1 }
4689
+ }
4690
+ },
4691
+ "datasources jobs replay": {
4692
+ usage: "Usage: remy-admin datasources jobs replay <id> [--kind skip|error] [--wait] [--timeout <sec>]",
4693
+ positionals: [{ name: "id", required: true }],
4694
+ flags: {
4695
+ kind: { type: "string" },
4696
+ wait: { type: "boolean" },
4697
+ timeout: { type: "string" }
4698
+ }
4699
+ }
4700
+ };
4701
+ function selectionOf(a) {
4702
+ const store = a.str("store");
4703
+ if (a.bool("connector") && store) {
4704
+ fatal("Give --store or --connector, not both.");
4705
+ }
4706
+ if (a.bool("connector")) {
4707
+ return { connector: true };
4708
+ }
4709
+ const access = a.str("access") ?? "private";
4710
+ if (access !== "private" && access !== "public") {
4711
+ fatal(`--access must be "private" or "public" (got "${access}").`);
4712
+ }
4713
+ return {
4714
+ store,
4715
+ access,
4716
+ ...a.str("prefix") !== void 0 ? { prefix: a.str("prefix") } : {}
4717
+ };
4718
+ }
4719
+ function keysOf(a) {
4720
+ const raw = a.str("keys");
4721
+ if (!raw) {
4722
+ return void 0;
4723
+ }
4724
+ const keys = raw.split(",").map((k) => k.trim()).filter(Boolean);
4725
+ if (keys.length === 0) {
4726
+ fatal("--keys needs at least one key.");
4727
+ }
4728
+ return keys;
4729
+ }
4730
+ function kindOf(a) {
4731
+ const kind = a.str("kind");
4732
+ if (kind === void 0) {
4733
+ return void 0;
4734
+ }
4735
+ if (kind !== "skip" && kind !== "error") {
4736
+ fatal(`--kind must be "skip" or "error" (got "${kind}").`);
4737
+ }
4738
+ return kind;
4739
+ }
4740
+ async function inspect2(ctx, a) {
4741
+ const slug = a.str("source");
4742
+ const selection = selectionOf(a);
4743
+ if ("connector" in selection && !slug) {
4744
+ fatal("--connector needs --source: the bucket is the source's.");
4745
+ }
4746
+ const limit = a.num("limit");
4747
+ const result = await inspect(ctx, {
4748
+ ...slug ? { slug } : {},
4749
+ selection,
4750
+ ...keysOf(a) ? { keys: keysOf(a) } : {},
4751
+ ...limit !== void 0 ? { limit } : {}
4752
+ });
4753
+ out({
4754
+ ...slug ? { dataSource: slug } : {},
4755
+ ...result,
4756
+ note: result.report.truncated ? `Listed the first ${result.report.objects.toLocaleString()} objects; raise --limit to see more of the shape.` : `Listed every object (${result.report.objects.toLocaleString()}).`
4757
+ });
4758
+ }
4759
+ async function mapTest2(ctx, a) {
4760
+ const slug = sourceOf(a);
4761
+ const limit = a.num("limit");
4762
+ const result = await mapTest(ctx, {
4763
+ slug,
4764
+ selection: selectionOf(a),
4765
+ ...keysOf(a) ? { keys: keysOf(a) } : {},
4766
+ ...limit !== void 0 ? { limit } : {},
4767
+ ...a.bool("dev") ? { dev: true } : {}
4768
+ });
4769
+ const results = a.bool("full") ? result.results : result.results.map(
4770
+ (r) => r.outcome.kind === "documents" ? {
4771
+ ...r,
4772
+ outcome: {
4773
+ ...r.outcome,
4774
+ documents: r.outcome.documents.map((d) => ({
4775
+ ...d,
4776
+ preview: d.preview.slice(0, 200)
4777
+ }))
4778
+ }
4779
+ } : r
3188
4780
  );
4781
+ out({
4782
+ dataSource: slug,
4783
+ ...result,
4784
+ results,
4785
+ note: result.dev ? "Ran the LOCAL mapper through the dev session. Nothing was ingested; deploy to make it live." : "Ran the live release's mapper. Nothing was ingested."
4786
+ });
4787
+ if (result.counts.error > 0) {
4788
+ throw new CliError(
4789
+ `${result.counts.error} of ${result.objects} object(s) errored in the mapper.`,
4790
+ EXIT.buildFailed
4791
+ );
4792
+ }
4793
+ }
4794
+ async function remap2(ctx, a) {
4795
+ const slug = sourceOf(a);
4796
+ const budget = a.num("budget");
4797
+ const limit = a.num("limit");
4798
+ const { job, mapper } = await remap(ctx, {
4799
+ slug,
4800
+ ...budget !== void 0 ? { budgetDollars: budget } : {},
4801
+ ...limit !== void 0 ? { limit } : {}
4802
+ });
4803
+ if (!a.bool("wait")) {
4804
+ out({
4805
+ dataSource: slug,
4806
+ job,
4807
+ mapper,
4808
+ note: "Remapping from the raw copies. Poll `datasources jobs status <id>`, or re-run with --wait. Unchanged documents are skipped by hash; changed ones supersede their predecessors."
4809
+ });
4810
+ return;
4811
+ }
4812
+ await waitAndReport(ctx, a, job.id, "job", {
4813
+ dataSource: slug,
4814
+ action: "remap",
4815
+ mapper
4816
+ });
4817
+ }
4818
+ async function quarantine2(ctx, a) {
4819
+ const id = a.req("id");
4820
+ const limit = a.num("limit");
4821
+ const kind = kindOf(a);
4822
+ const reason = a.str("reason");
4823
+ const cursor = a.str("cursor");
4824
+ const result = await quarantine(ctx, {
4825
+ id,
4826
+ ...kind ? { kind } : {},
4827
+ ...reason ? { reason } : {},
4828
+ ...cursor ? { cursor } : {},
4829
+ ...limit !== void 0 ? { limit } : {}
4830
+ });
4831
+ out({
4832
+ ...result,
4833
+ note: result.counts.skipped + result.counts.errors === 0 ? "Nothing quarantined: the mapper produced documents (or deletions) for every object." : `Fix the mapper, deploy, then \`datasources jobs replay ${id}\` to run these through it again.`
4834
+ });
4835
+ }
4836
+ async function replay2(ctx, a) {
4837
+ const id = a.req("id");
4838
+ const kind = kindOf(a);
4839
+ const { job, mapper, dataSource } = await replay(ctx, {
4840
+ id,
4841
+ ...kind ? { kind } : {}
4842
+ });
4843
+ if (!a.bool("wait")) {
4844
+ out({
4845
+ dataSource: dataSource.slug,
4846
+ fromJob: id,
4847
+ job,
4848
+ mapper,
4849
+ note: "Replaying the quarantined objects through the current mapper. Poll `datasources jobs status <id>`, or re-run with --wait."
4850
+ });
4851
+ return;
4852
+ }
4853
+ await waitAndReport(ctx, a, job.id, "job", {
4854
+ dataSource: dataSource.slug,
4855
+ fromJob: id,
4856
+ action: "replay",
4857
+ mapper
4858
+ });
3189
4859
  }
4860
+ var dataSourceMappersHandlers = {
4861
+ "datasources inspect": inspect2,
4862
+ "datasources map test": mapTest2,
4863
+ "datasources remap": remap2,
4864
+ "datasources jobs quarantine": quarantine2,
4865
+ "datasources jobs replay": replay2
4866
+ };
4867
+ var dataSourceMappersHelp = `
4868
+ Mapping raw objects into documents (mappers):
4869
+ remy-admin datasources inspect [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>]
4870
+ remy-admin datasources map test [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>] [--dev] [--full]
4871
+ remy-admin datasources remap [--source <slug>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]
4872
+ remy-admin datasources jobs quarantine <id> [--kind skip|error] [--reason <text>] [--cursor <id>] [--limit <n>]
4873
+ remy-admin datasources jobs replay <id> [--kind skip|error] [--wait] [--timeout <sec>]
4874
+
4875
+ A source takes every object it is given as one document. When the file is
4876
+ not the document \u2014 JSON records that should become markdown plus metadata,
4877
+ a JSONL object holding many articles, a kill notice that means "remove this
4878
+ story" \u2014 the app declares a MAPPER for the source in mindstudio.json:
4879
+ "dataSources": [{ "slug": "archive", "mapper": { "path": "dist/datasources/archive.mapper.ts" } }]
4880
+ The file exports defineMapper(Archive, { map }) from @mindstudio-ai/agent.
4881
+ map(object) reads the raw object (object.key, .bytes(), .text(), .json())
4882
+ and answers documents([{ externalId, title, markdown, metadata?, replaces? }]),
4883
+ passthrough() (ingest the raw object as-is), skip(reason) or
4884
+ deletes([externalId, ...]); throwing is the object's error. It may call
4885
+ models or fetch \u2014 every call is spend per object. Everything entering a
4886
+ mapped source is mapped: jobs, syncs and Source.add() alike.
4887
+
4888
+ The loop: \`inspect\` profiles the objects (key shapes, extensions, sizes,
4889
+ JSON key signatures of ten sampled heads) before a line is written;
4890
+ \`map test --dev\` runs the LOCAL mapper through the running dev session
4891
+ over real objects and prints every outcome without ingesting anything;
4892
+ deploy; a job or sync runs the compiled mapper; \`jobs quarantine <id>\`
4893
+ lists what it skipped or failed on, by reason; fix, deploy, \`jobs replay
4894
+ <id>\` runs just those objects again; \`remap\` re-applies a changed mapper
4895
+ to every raw copy the source holds \u2014 no origin traffic, unchanged markdown
4896
+ skipped by hash, changed documents superseding their predecessors.
4897
+
4898
+ externalId is the identity the platform replaces by: a later object
4899
+ producing the same id supersedes the earlier document; \`replaces\` names
4900
+ ids this document supersedes (a writethru's original); deletes([...])
4901
+ removes by id. A plan on a mapped source records the mapper's outcome mix
4902
+ on its sample; a run whose skip share climbs past twice that (floor 5%)
4903
+ pauses with pauseReason 'skips' for a look at the quarantine.
4904
+ `;
3190
4905
 
3191
4906
  // src/commands/dataSources.ts
3192
- var DEFAULT_SOURCE = "default";
3193
- var POLL_MS3 = 3e3;
3194
4907
  var INGEST_FLAGS = {
3195
4908
  chunking: { type: "string" },
3196
4909
  "max-chars": { type: "number", min: 200 },
@@ -3237,8 +4950,16 @@ var dataSourcesSpecs = {
3237
4950
  flags: { source: { type: "string" } }
3238
4951
  },
3239
4952
  "datasources rm": {
3240
- usage: "Usage: remy-admin datasources rm [--source <slug>] --document <id>",
3241
- flags: { source: { type: "string" }, document: { type: "string" } }
4953
+ usage: "Usage: remy-admin datasources rm [--source <slug>] (--document <id> | --filter <k=v,...|json>)",
4954
+ flags: {
4955
+ source: { type: "string" },
4956
+ document: { type: "string" },
4957
+ filter: { type: "string" }
4958
+ },
4959
+ requireAnyOf: {
4960
+ flags: ["document", "filter"],
4961
+ message: "Say what to remove: --document <id> for one document, or --filter <k=v,...|json> for every document that matches."
4962
+ }
3242
4963
  },
3243
4964
  "datasources search": {
3244
4965
  usage: "Usage: remy-admin datasources search [--source <slug>] [--top-k <n>] [--mode <hybrid|semantic|lexical>] [--filter <k=v,...|json>] [--phrase <text>] [--contains <words>] [--max-per-document <n>] [--rerank <true|false>] [--hybrid <true|false>] [--highlight] [--candidate] <query>",
@@ -3311,21 +5032,20 @@ var dataSourcesSpecs = {
3311
5032
  flags: { source: { type: "string" } }
3312
5033
  }
3313
5034
  };
3314
- var sourceOf = (a) => a.str("source") || DEFAULT_SOURCE;
3315
5035
  async function dataSourcesAdd(ctx, a) {
3316
5036
  const files = a.rest("file");
3317
5037
  const slug = sourceOf(a);
3318
5038
  const metadata = parseKeyValuePairs(a.str("metadata"), "metadata");
3319
5039
  const results = [];
3320
5040
  for (const file of files) {
3321
- const abs = path3.isAbsolute(file) ? file : path3.join(WORKSPACE_DIR, file);
5041
+ const abs = path5.isAbsolute(file) ? file : path5.join(WORKSPACE_DIR, file);
3322
5042
  let bytes;
3323
5043
  try {
3324
- bytes = fs3.readFileSync(abs);
5044
+ bytes = fs5.readFileSync(abs);
3325
5045
  } catch (err) {
3326
5046
  fatal(`Could not read file "${file}": ${err.message}`);
3327
5047
  }
3328
- const filename = path3.basename(file);
5048
+ const filename = path5.basename(file);
3329
5049
  results.push(
3330
5050
  await addDocument(ctx, {
3331
5051
  slug,
@@ -3341,32 +5061,24 @@ async function dataSourcesAdd(ctx, a) {
3341
5061
  const timeoutMs = timeoutSec ? timeoutSec * 1e3 : DEFAULT_WAIT_TIMEOUT_MS;
3342
5062
  const result = await waitForIngest(ctx, {
3343
5063
  slug,
3344
- documentIds: results.filter((r) => !r.skipped).map((r) => r.document.id),
5064
+ // Every document the files became: a mapped source can make several
5065
+ // from one file, or none (a deletes outcome).
5066
+ documentIds: results.filter((r) => !r.skipped).flatMap((r) => r.documents.map((d) => d.id)),
3345
5067
  timeoutMs,
3346
5068
  onProgress: progress
3347
5069
  });
3348
- if (result.status === "timeout") {
3349
- out({
3350
- dataSource: slug,
3351
- status: result.status,
3352
- error: result.error,
3353
- documents: result.documents
3354
- });
3355
- process.exit(EXIT.timeout);
3356
- }
3357
- if (result.status === "error") {
3358
- out({
3359
- dataSource: slug,
3360
- status: result.status,
3361
- documents: result.documents
3362
- });
3363
- process.exit(EXIT.buildFailed);
3364
- }
3365
5070
  out({
3366
5071
  dataSource: slug,
3367
5072
  status: result.status,
5073
+ ...result.error ? { error: result.error } : {},
3368
5074
  documents: result.documents
3369
5075
  });
5076
+ if (result.status === "timeout") {
5077
+ throw new CliError(result.error ?? "Timed out.", EXIT.timeout);
5078
+ }
5079
+ if (result.status === "error") {
5080
+ throw new CliError("At least one document failed.", EXIT.buildFailed);
5081
+ }
3370
5082
  return;
3371
5083
  }
3372
5084
  out({ dataSource: slug, documents: results });
@@ -3384,11 +5096,32 @@ async function dataSourcesStatus(ctx, a) {
3384
5096
  }
3385
5097
  async function dataSourcesRm(ctx, a) {
3386
5098
  const documentId = a.str("document");
3387
- if (!documentId) {
3388
- fatal("--document is required.");
5099
+ const rawFilter = a.str("filter");
5100
+ if (documentId && rawFilter) {
5101
+ fatal("Give --document or --filter, not both.");
5102
+ }
5103
+ if (documentId) {
5104
+ await rm(ctx, { slug: sourceOf(a), documentId });
5105
+ out({ deleted: documentId });
5106
+ return;
5107
+ }
5108
+ let filter;
5109
+ if (rawFilter.trimStart().startsWith("{")) {
5110
+ try {
5111
+ filter = JSON.parse(rawFilter);
5112
+ } catch (err) {
5113
+ fatal(`--filter is not valid JSON: ${err.message}`);
5114
+ }
5115
+ } else {
5116
+ filter = { metadata: parseKeyValuePairs(rawFilter, "filter") };
3389
5117
  }
3390
- await rm(ctx, { slug: sourceOf(a), documentId });
3391
- out({ deleted: documentId });
5118
+ const slug = sourceOf(a);
5119
+ const result = await rmWhereAll(ctx, {
5120
+ slug,
5121
+ filter,
5122
+ onProgress: progress
5123
+ });
5124
+ out({ dataSource: slug, filter, ...result });
3392
5125
  }
3393
5126
  async function dataSourcesDelete(ctx, a) {
3394
5127
  const slug = a.str("source");
@@ -3403,8 +5136,8 @@ async function dataSourcesDelete(ctx, a) {
3403
5136
  async function dataSourcesSearch(ctx, a) {
3404
5137
  const topK = a.num("top-k");
3405
5138
  const retrieval = {
3406
- ...triState(a, "rerank", "rerank"),
3407
- ...triState(a, "hybrid", "hybrid")
5139
+ ...triState2(a, "rerank", "rerank"),
5140
+ ...triState2(a, "hybrid", "hybrid")
3408
5141
  };
3409
5142
  const mode = a.str("mode");
3410
5143
  if (mode && !["hybrid", "semantic", "lexical"].includes(mode)) {
@@ -3476,7 +5209,7 @@ function parseKeyValuePairs(raw, flag) {
3476
5209
  }
3477
5210
  return pairs2;
3478
5211
  }
3479
- function triState(a, flag, key) {
5212
+ function triState2(a, flag, key) {
3480
5213
  const raw = a.str(flag);
3481
5214
  if (raw === void 0) {
3482
5215
  return {};
@@ -3508,11 +5241,11 @@ function configFromFlags(a) {
3508
5241
  chunking.dropBlockTypes = dropBlocks.split(",").map((s) => s.trim()).filter(Boolean);
3509
5242
  }
3510
5243
  const contextual = {
3511
- ...triState(a, "contextual", "enabled"),
5244
+ ...triState2(a, "contextual", "enabled"),
3512
5245
  ...a.str("contextual-model") ? { modelId: a.str("contextual-model") } : {}
3513
5246
  };
3514
5247
  const images = {
3515
- ...triState(a, "describe-images", "describe"),
5248
+ ...triState2(a, "describe-images", "describe"),
3516
5249
  ...a.str("image-model") ? { modelId: a.str("image-model") } : {}
3517
5250
  };
3518
5251
  const embeddingModel = a.str("embedding-model");
@@ -3538,10 +5271,10 @@ function configFromFlags(a) {
3538
5271
  ingest.extraction = { modelId: extractionModel };
3539
5272
  }
3540
5273
  const retrieval = {
3541
- ...triState(a, "hybrid", "hybrid")
5274
+ ...triState2(a, "hybrid", "hybrid")
3542
5275
  };
3543
5276
  const rerank = {
3544
- ...triState(a, "rerank", "enabled"),
5277
+ ...triState2(a, "rerank", "enabled"),
3545
5278
  ...a.str("rerank-model") ? { modelId: a.str("rerank-model") } : {}
3546
5279
  };
3547
5280
  if (Object.keys(rerank).length) {
@@ -3623,10 +5356,10 @@ async function dataSourcesMove(ctx, a) {
3623
5356
  ..."error" in result ? { error: result.error } : {}
3624
5357
  });
3625
5358
  if (result.status === "failed") {
3626
- process.exit(EXIT.buildFailed);
5359
+ throw new CliError(result.error, EXIT.buildFailed);
3627
5360
  }
3628
5361
  if (result.status === "timeout") {
3629
- process.exit(EXIT.timeout);
5362
+ throw new CliError(result.error, EXIT.timeout);
3630
5363
  }
3631
5364
  }
3632
5365
  async function dataSourcesRevectorize(ctx, a) {
@@ -3641,42 +5374,24 @@ async function dataSourcesRevectorize(ctx, a) {
3641
5374
  return;
3642
5375
  }
3643
5376
  const timeoutSec = a.num("timeout");
3644
- const timeoutMs = timeoutSec ? timeoutSec * 1e3 : DEFAULT_WAIT_TIMEOUT_MS;
3645
- const start = Date.now();
3646
- for (; ; ) {
3647
- const documents2 = await allDocuments(ctx, {
3648
- slug,
3649
- candidate: true
3650
- });
3651
- const pending = documents2.filter((d) => d.status === "processing");
3652
- const failed = documents2.filter((d) => d.status === "error");
3653
- if (pending.length === 0) {
3654
- out({
3655
- dataSource: slug,
3656
- candidateVersion: started.candidateVersion,
3657
- status: failed.length ? "error" : "ready",
3658
- documents: (documents2 ?? []).map(summarize),
3659
- note: failed.length ? "Some documents failed. Promote with --force to accept, or fix and re-run." : "Run `promote` to make this version live."
3660
- });
3661
- if (failed.length) {
3662
- process.exit(EXIT.buildFailed);
3663
- }
3664
- return;
3665
- }
3666
- if (Date.now() - start > timeoutMs) {
3667
- out({
3668
- dataSource: slug,
3669
- status: "timeout",
3670
- error: `Timed out after ${Math.round(timeoutMs / 1e3)}s with ${pending.length} document(s) still building`
3671
- });
3672
- process.exit(EXIT.timeout);
3673
- }
3674
- progress(
3675
- `rebuilding\u2026 ${documents2.length - pending.length}/${documents2.length} done (${Math.round(
3676
- (Date.now() - start) / 1e3
3677
- )}s)`
3678
- );
3679
- await sleep(POLL_MS3);
5377
+ const result = await waitForCandidate(ctx, {
5378
+ slug,
5379
+ ...timeoutSec ? { timeoutMs: timeoutSec * 1e3 } : {},
5380
+ onProgress: progress
5381
+ });
5382
+ if (result.status === "timeout") {
5383
+ out({ dataSource: slug, status: "timeout", error: result.error });
5384
+ throw new CliError(result.error ?? "Timed out.", EXIT.timeout);
5385
+ }
5386
+ out({
5387
+ dataSource: slug,
5388
+ candidateVersion: started.candidateVersion,
5389
+ status: result.status,
5390
+ documents: result.documents,
5391
+ note: result.status === "error" ? "Some documents failed. Promote with --force to accept, or fix and re-run." : "Run `promote` to make this version live."
5392
+ });
5393
+ if (result.status === "error") {
5394
+ throw new CliError("At least one document failed.", EXIT.buildFailed);
3680
5395
  }
3681
5396
  }
3682
5397
  async function dataSourcesPromote(ctx, a) {
@@ -3719,11 +5434,22 @@ Subcommands:
3719
5434
  add Add one or more documents (skips unchanged files)
3720
5435
  list List data sources with document counts
3721
5436
  status Show per-document ingest state
3722
- rm Remove a document and its vectors
5437
+ rm Remove a document, or every document matching a filter
3723
5438
  search Query a corpus \u2014 useful to sanity-check one you just built
3724
5439
  create Create an empty source, optionally on dedicated capacity
3725
5440
  config Show or change how a corpus is processed and searched
3726
5441
  move Move a corpus between shared and dedicated capacity, data intact
5442
+ jobs Bulk-load a corpus from a file store or a manifest, with a plan first
5443
+ connect Follow an S3 bucket the customer owns (credentials = app secret names)
5444
+ sync Bring a connected source up to date with its bucket
5445
+ connector Show a source's connector and what it tracks
5446
+ disconnect Stop following the bucket; documents stay
5447
+ inspect Profile raw objects (key shapes, types, sizes, JSON keys) before writing a mapper
5448
+ map test Run a source's mapper over real objects and show the outcomes; nothing ingested
5449
+ remap Re-apply the live mapper to every raw copy the source holds
5450
+ hydrate Reload an evicted index ahead of the first search
5451
+ sample Draw a sample of a source into a new one with the same config
5452
+ eval Query sets and runs: measure recall, MRR and latency, compare versions
3727
5453
  revectorize Rebuild a corpus under new settings, alongside the live one
3728
5454
  promote Make a rebuilt version live
3729
5455
  drop Discard a candidate or a superseded version
@@ -3733,11 +5459,15 @@ Usage:
3733
5459
  remy-admin datasources add [--source <slug>] [--metadata <k=v,...>] [--wait] [--timeout <sec>] <file...>
3734
5460
  remy-admin datasources list
3735
5461
  remy-admin datasources status [--source <slug>]
3736
- remy-admin datasources rm [--source <slug>] --document <id>
5462
+ remy-admin datasources rm [--source <slug>] (--document <id> | --filter <k=v,...|json>)
3737
5463
  remy-admin datasources search [--source <slug>] [search options] <query>
3738
5464
  remy-admin datasources create --source <slug> [--name <name>] [--placement <resource-id|shared>] [rebuild settings...]
3739
5465
  remy-admin datasources config [--source <slug>] [--placement <resource-id|shared>] [settings...]
3740
5466
  remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--wait] [--timeout <sec>]
5467
+ remy-admin datasources jobs start|list|status|approve|pause|resume|cancel \u2026 (see below)
5468
+ remy-admin datasources connect|sync|connector|disconnect \u2026 (see below)
5469
+ remy-admin datasources hydrate|sample \u2026 (see below)
5470
+ remy-admin datasources eval create|list|get|add|import|queries|rm|delete|run|runs|result|compare \u2026 (see below)
3741
5471
  remy-admin datasources revectorize [--source <slug>] [settings...] [--wait]
3742
5472
  remy-admin datasources promote [--source <slug>] [--force]
3743
5473
  remy-admin datasources drop [--source <slug>] [--version <n>]
@@ -3830,6 +5560,11 @@ Notes:
3830
5560
  Re-adding an unchanged file is free: no upload, no re-embedding. Re-adding
3831
5561
  with a different --metadata updates the tags in place, also free.
3832
5562
  --metadata tags documents for search-time filtering (scalars, \u226416 keys).
5563
+ rm --filter removes every matching document in pages of a thousand: k=v
5564
+ pairs match metadata; a JSON object takes the full selector \u2014 metadata,
5565
+ filename, documentIds, externalIdPrefix (the key a job or connector
5566
+ recorded, e.g. '{"externalIdPrefix":"archive/2019/"}'). An empty filter
5567
+ is refused; to remove everything, delete the source.
3833
5568
  delete requires an explicit --source (no default) and refuses while documents
3834
5569
  are still ingesting. Extraction caches survive deletion, so re-ingesting
3835
5570
  the same files elsewhere costs no re-extraction.
@@ -3847,7 +5582,24 @@ Examples:
3847
5582
  remy-admin datasources revectorize --source policies --max-chars 900 --wait
3848
5583
  remy-admin datasources search --source policies --candidate "payment terms"
3849
5584
  remy-admin datasources promote --source policies
3850
- `;
5585
+
5586
+ # Load a whole corpus: plan first, approve, watch it run
5587
+ remy-admin datasources jobs start --source archive --store raw --prefix 2024/ --wait
5588
+ remy-admin datasources jobs approve <id> --wait
5589
+
5590
+ # Follow a customer's bucket: secrets by name, first sync stops for approval
5591
+ remy-admin secrets set ARCHIVE_S3_KEY --prod <value> # never on a command line you share
5592
+ remy-admin datasources connect --source archive --bucket acme-docs --region us-east-1 --prefix contracts/ --access-key-secret ARCHIVE_S3_KEY --secret-key-secret ARCHIVE_S3_SECRET --budget-per-sync 5
5593
+ remy-admin datasources sync --source archive --wait
5594
+
5595
+ # Measure before you promote: sample, build a query set, run live vs candidate
5596
+ remy-admin datasources sample --source archive --size 300 --wait
5597
+ remy-admin datasources eval create --source archive-sample --name base --size 150 --wait
5598
+ remy-admin datasources eval run --set <setId> --label live --wait
5599
+ remy-admin datasources revectorize --source archive-sample --max-chars 900 --wait
5600
+ remy-admin datasources eval run --set <setId> --candidate --label small-chunks --wait
5601
+ remy-admin datasources eval compare <runA> <runB>
5602
+ ${dataSourceJobsHelp}${dataSourceConnectorsHelp}${dataSourceMappersHelp}${dataSourceEvalsHelp}`;
3851
5603
 
3852
5604
  // src/ops/voice.ts
3853
5605
  function normalizeE164(input) {
@@ -4422,7 +6174,7 @@ function runs(ctx, params = {}) {
4422
6174
  `/_internal/v2/apps/${ctx.appId}/jewels/training-runs${qs(params)}`
4423
6175
  );
4424
6176
  }
4425
- function getRun(ctx, runId) {
6177
+ function getRun2(ctx, runId) {
4426
6178
  return call(
4427
6179
  ctx,
4428
6180
  "GET",
@@ -4582,9 +6334,9 @@ async function jewelsTimeseries(ctx, a) {
4582
6334
  );
4583
6335
  }
4584
6336
  async function jewelsResolve(ctx, a) {
4585
- const approve = a.bool("approve");
6337
+ const approve2 = a.bool("approve");
4586
6338
  const dismiss = a.bool("dismiss");
4587
- if (approve === dismiss) {
6339
+ if (approve2 === dismiss) {
4588
6340
  fatal("Pass exactly one of --approve or --dismiss.");
4589
6341
  }
4590
6342
  if (dismiss && a.str("input") !== void 0) {
@@ -4592,7 +6344,7 @@ async function jewelsResolve(ctx, a) {
4592
6344
  }
4593
6345
  const params = {
4594
6346
  itemId: a.req("itemId"),
4595
- action: approve ? "approve" : "dismiss"
6347
+ action: approve2 ? "approve" : "dismiss"
4596
6348
  };
4597
6349
  if (a.str("input") !== void 0) {
4598
6350
  params.input = parseJsonFlag(a, "input");
@@ -4640,28 +6392,28 @@ async function jewelsTrain(ctx, a) {
4640
6392
  let lastLogLen = 0;
4641
6393
  while (Date.now() < deadline) {
4642
6394
  await sleep(TRAIN_POLL_MS);
4643
- const { run: run2 } = await getRun(ctx, runId);
4644
- const log = Array.isArray(run2.log) ? run2.log : [];
6395
+ const { run: run3 } = await getRun2(ctx, runId);
6396
+ const log = Array.isArray(run3.log) ? run3.log : [];
4645
6397
  const fresh = log.slice(lastLogLen);
4646
6398
  lastLogLen = log.length;
4647
6399
  const isStatus = (x) => x?.kind === "status";
4648
6400
  const isLoss = (x) => x?.kind === "loss";
4649
6401
  for (const e of fresh.filter(isStatus)) {
4650
- out({ status: run2.status, at: e.ts, message: e.message });
6402
+ out({ status: run3.status, at: e.ts, message: e.message });
4651
6403
  }
4652
6404
  const losses = fresh.filter(isLoss);
4653
6405
  if (losses.length) {
4654
6406
  const last = losses[losses.length - 1];
4655
- const p = run2.progress;
6407
+ const p = run3.progress;
4656
6408
  out({
4657
- status: run2.status,
6409
+ status: run3.status,
4658
6410
  step: `${last.step}/${p?.totalSteps ?? "?"}`,
4659
6411
  loss: last.loss
4660
6412
  });
4661
6413
  }
4662
- if (run2.status === "complete" || run2.status === "failed") {
4663
- out(compactRunLog(run2));
4664
- if (run2.status === "failed") {
6414
+ if (run3.status === "complete" || run3.status === "failed") {
6415
+ out(compactRunLog(run3));
6416
+ if (run3.status === "failed") {
4665
6417
  process.exitCode = 1;
4666
6418
  }
4667
6419
  return;
@@ -4671,19 +6423,19 @@ async function jewelsTrain(ctx, a) {
4671
6423
  `Timed out waiting for run ${runId} (still in flight; check 'jewels run ${runId}').`
4672
6424
  );
4673
6425
  }
4674
- function compactRunLog(run2) {
4675
- let out2 = { ...run2 };
4676
- if (Array.isArray(run2.log)) {
4677
- const statusEntries = run2.log.filter((e) => e?.kind === "status");
4678
- const lossPoints = run2.log.length - statusEntries.length;
6426
+ function compactRunLog(run3) {
6427
+ let out2 = { ...run3 };
6428
+ if (Array.isArray(run3.log)) {
6429
+ const statusEntries = run3.log.filter((e) => e?.kind === "status");
6430
+ const lossPoints = run3.log.length - statusEntries.length;
4679
6431
  out2 = { ...out2, log: { statusEntries, lossPoints } };
4680
6432
  }
4681
- if (Array.isArray(run2.report?.candidates)) {
6433
+ if (Array.isArray(run3.report?.candidates)) {
4682
6434
  out2 = {
4683
6435
  ...out2,
4684
6436
  report: {
4685
- ...run2.report,
4686
- candidates: run2.report.candidates.map(
6437
+ ...run3.report,
6438
+ candidates: run3.report.candidates.map(
4687
6439
  (c) => Array.isArray(c?.predictions) ? { ...c, predictions: c.predictions.length } : c
4688
6440
  )
4689
6441
  }
@@ -4700,8 +6452,8 @@ async function jewelsRuns(ctx, a) {
4700
6452
  );
4701
6453
  }
4702
6454
  async function jewelsRunGet(ctx, a) {
4703
- const { run: run2 } = await getRun(ctx, a.req("runId"));
4704
- out({ run: compactRunLog(run2) });
6455
+ const { run: run3 } = await getRun2(ctx, a.req("runId"));
6456
+ out({ run: compactRunLog(run3) });
4705
6457
  }
4706
6458
  async function jewelsGrade(ctx, a) {
4707
6459
  out(await grade(ctx, a.req("runId")));
@@ -5148,14 +6900,14 @@ async function resolveDomainId(ctx, direction, domain) {
5148
6900
  }
5149
6901
  return match.id;
5150
6902
  }
5151
- function list9(ctx, params = {}) {
6903
+ function list10(ctx, params = {}) {
5152
6904
  return call(
5153
6905
  ctx,
5154
6906
  "GET",
5155
6907
  `/_internal/v2/apps/${ctx.appId}/outbound-email/messages${qs(params)}`
5156
6908
  );
5157
6909
  }
5158
- function get7(ctx, messageId) {
6910
+ function get9(ctx, messageId) {
5159
6911
  return call(
5160
6912
  ctx,
5161
6913
  "GET",
@@ -5347,7 +7099,7 @@ var emailSpecs = {
5347
7099
  };
5348
7100
  async function emailList(ctx, a) {
5349
7101
  out(
5350
- await list9(ctx, {
7102
+ await list10(ctx, {
5351
7103
  status: a.str("status"),
5352
7104
  kind: a.str("kind"),
5353
7105
  recipient: a.str("recipient"),
@@ -5361,7 +7113,7 @@ async function emailList(ctx, a) {
5361
7113
  );
5362
7114
  }
5363
7115
  async function emailGet(ctx, a) {
5364
- out(await get7(ctx, a.req("messageId")));
7116
+ out(await get9(ctx, a.req("messageId")));
5365
7117
  }
5366
7118
  async function emailStats(ctx, a) {
5367
7119
  out(await stats2(ctx, { start: a.str("start"), end: a.str("end") }));
@@ -5537,14 +7289,14 @@ Examples:
5537
7289
  `;
5538
7290
 
5539
7291
  // src/ops/cron.ts
5540
- function list10(ctx, params = {}) {
7292
+ function list11(ctx, params = {}) {
5541
7293
  return call(
5542
7294
  ctx,
5543
7295
  "GET",
5544
7296
  `/_internal/v2/apps/${ctx.appId}/cron/overview${qs(params)}`
5545
7297
  );
5546
7298
  }
5547
- function run(ctx, route) {
7299
+ function run2(ctx, route) {
5548
7300
  return call(
5549
7301
  ctx,
5550
7302
  "POST",
@@ -5566,10 +7318,10 @@ var cronSpecs = {
5566
7318
  }
5567
7319
  };
5568
7320
  async function cronList(ctx, a) {
5569
- out(await list10(ctx, { runs: a.num("runs") }));
7321
+ out(await list11(ctx, { runs: a.num("runs") }));
5570
7322
  }
5571
7323
  async function cronRun(ctx, a) {
5572
- out(await run(ctx, a.req("route")));
7324
+ out(await run2(ctx, a.req("route")));
5573
7325
  }
5574
7326
  var cronHandlers = {
5575
7327
  "cron list": cronList,
@@ -5600,27 +7352,27 @@ Examples:
5600
7352
  remy-admin cron run dailyDigest`;
5601
7353
 
5602
7354
  // src/ops/infra.ts
5603
- var DEFAULT_WAIT_TIMEOUT_MS2 = 10 * 60 * 1e3;
5604
- var POLL_MS4 = 3e3;
5605
- function base2(appId) {
7355
+ var DEFAULT_WAIT_TIMEOUT_MS4 = 10 * 60 * 1e3;
7356
+ var POLL_MS5 = 3e3;
7357
+ function base6(appId) {
5606
7358
  return `/_internal/v2/apps/${appId}/infra`;
5607
7359
  }
5608
- function list11(ctx, params) {
7360
+ function list12(ctx, params) {
5609
7361
  return call(
5610
7362
  ctx,
5611
7363
  "GET",
5612
- `${base2(ctx.appId)}${params?.includeDestroyed ? "?includeDestroyed=true" : ""}`
7364
+ `${base6(ctx.appId)}${params?.includeDestroyed ? "?includeDestroyed=true" : ""}`
5613
7365
  );
5614
7366
  }
5615
- function get8(ctx, params) {
7367
+ function get10(ctx, params) {
5616
7368
  return call(
5617
7369
  ctx,
5618
7370
  "GET",
5619
- `${base2(ctx.appId)}/${encodeURIComponent(params.id)}`
7371
+ `${base6(ctx.appId)}/${encodeURIComponent(params.id)}`
5620
7372
  );
5621
7373
  }
5622
7374
  function provision(ctx, params) {
5623
- return call(ctx, "POST", base2(ctx.appId), {
7375
+ return call(ctx, "POST", base6(ctx.appId), {
5624
7376
  offeringId: params.offeringId,
5625
7377
  ...params.name ? { name: params.name } : {}
5626
7378
  });
@@ -5629,28 +7381,28 @@ function hibernate(ctx, params) {
5629
7381
  return call(
5630
7382
  ctx,
5631
7383
  "POST",
5632
- `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/hibernate`
7384
+ `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/hibernate`
5633
7385
  );
5634
7386
  }
5635
- function resume(ctx, params) {
7387
+ function resume2(ctx, params) {
5636
7388
  return call(
5637
7389
  ctx,
5638
7390
  "POST",
5639
- `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/resume`
7391
+ `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/resume`
5640
7392
  );
5641
7393
  }
5642
7394
  function destroy(ctx, params) {
5643
7395
  return call(
5644
7396
  ctx,
5645
7397
  "POST",
5646
- `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/destroy`
7398
+ `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/destroy`
5647
7399
  );
5648
7400
  }
5649
7401
  function resize(ctx, params) {
5650
7402
  return call(
5651
7403
  ctx,
5652
7404
  "POST",
5653
- `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/resize`,
7405
+ `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/resize`,
5654
7406
  { offeringId: params.offeringId }
5655
7407
  );
5656
7408
  }
@@ -5658,56 +7410,40 @@ function rename(ctx, params) {
5658
7410
  return call(
5659
7411
  ctx,
5660
7412
  "POST",
5661
- `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/rename`,
7413
+ `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/rename`,
5662
7414
  { name: params.name }
5663
7415
  );
5664
7416
  }
5665
7417
  async function waitForPhase(ctx, params) {
5666
- const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS2;
7418
+ const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS4;
5667
7419
  const wanted = new Set(params.phases);
5668
- const start = Date.now();
5669
- let transientSince = null;
5670
- for (; ; ) {
5671
- let resource;
5672
- try {
5673
- ({ resource } = await get8(ctx, { id: params.id }));
5674
- transientSince = null;
5675
- } catch (err) {
5676
- if (!isTransientError(err)) {
5677
- throw err;
5678
- }
5679
- transientSince ??= Date.now();
5680
- if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
5681
- throw err;
5682
- }
5683
- params.onProgress?.(
5684
- `retrying after a gateway error\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
5685
- );
5686
- await sleep(POLL_MS4);
5687
- continue;
5688
- }
5689
- if (wanted.has(resource.phase) && (params.until?.(resource) ?? true)) {
5690
- return { status: "done", resource };
5691
- }
5692
- if (resource.phase === "failed") {
5693
- return {
5694
- status: "failed",
5695
- resource,
5696
- error: resource.lastError ?? "The platform could not complete the change."
5697
- };
5698
- }
5699
- if (Date.now() - start > timeoutMs) {
5700
- return {
5701
- status: "timeout",
5702
- resource,
5703
- error: `Timed out after ${Math.round(timeoutMs / 1e3)}s; resource is still ${resource.phase}`
5704
- };
7420
+ const arrived = (resource2) => wanted.has(resource2.phase) && (params.until?.(resource2) ?? true);
7421
+ const outcome = await pollUntil(
7422
+ async () => (await get10(ctx, { id: params.id })).resource,
7423
+ (resource2) => arrived(resource2) || resource2.phase === "failed",
7424
+ {
7425
+ timeoutMs,
7426
+ pollMs: POLL_MS5,
7427
+ describe: (resource2, start2) => `${resource2.phase}${resource2.detail ? ` \xB7 ${resource2.detail}` : ""}${resource2.resizingTo ? ` \xB7 resizing to ${resource2.resizingTo.label}` : ""}\u2026 (${elapsedSeconds(start2)}s)`,
7428
+ onProgress: params.onProgress
5705
7429
  }
5706
- params.onProgress?.(
5707
- `${resource.phase}${resource.detail ? ` \xB7 ${resource.detail}` : ""}${resource.resizingTo ? ` \xB7 resizing to ${resource.resizingTo.label}` : ""}\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
5708
- );
5709
- await sleep(POLL_MS4);
7430
+ );
7431
+ const resource = outcome.value;
7432
+ if (!outcome.settled) {
7433
+ return {
7434
+ status: "timeout",
7435
+ resource,
7436
+ error: timedOut(timeoutMs, `; resource is still ${resource.phase}`)
7437
+ };
5710
7438
  }
7439
+ if (arrived(resource)) {
7440
+ return { status: "done", resource };
7441
+ }
7442
+ return {
7443
+ status: "failed",
7444
+ resource,
7445
+ error: resource.lastError ?? "The platform could not complete the change."
7446
+ };
5711
7447
  }
5712
7448
 
5713
7449
  // src/commands/infra.ts
@@ -5768,7 +7504,7 @@ var infraSpecs = {
5768
7504
  }
5769
7505
  }
5770
7506
  };
5771
- async function waitAndReport(ctx, a, id, phases, summary2, until) {
7507
+ async function waitAndReport2(ctx, a, id, phases, summary2, until) {
5772
7508
  const timeoutSec = a.str("timeout");
5773
7509
  const result = await waitForPhase(ctx, {
5774
7510
  id,
@@ -5786,10 +7522,10 @@ async function waitAndReport(ctx, a, id, phases, summary2, until) {
5786
7522
  }
5787
7523
  }
5788
7524
  async function infraList(ctx, a) {
5789
- out(await list11(ctx, { includeDestroyed: a.bool("include-destroyed") }));
7525
+ out(await list12(ctx, { includeDestroyed: a.bool("include-destroyed") }));
5790
7526
  }
5791
7527
  async function infraGet(ctx, a) {
5792
- out(await get8(ctx, { id: a.req("id") }));
7528
+ out(await get10(ctx, { id: a.req("id") }));
5793
7529
  }
5794
7530
  function pricingOf(resource) {
5795
7531
  const offering = resource.offering;
@@ -5819,7 +7555,7 @@ async function infraProvision(ctx, a) {
5819
7555
  });
5820
7556
  return;
5821
7557
  }
5822
- await waitAndReport(ctx, a, resource.id, ["active"], {
7558
+ await waitAndReport2(ctx, a, resource.id, ["active"], {
5823
7559
  action: "provision",
5824
7560
  pricing
5825
7561
  });
@@ -5831,16 +7567,16 @@ async function infraHibernate(ctx, a) {
5831
7567
  out({ resource });
5832
7568
  return;
5833
7569
  }
5834
- await waitAndReport(ctx, a, id, ["hibernated"], { action: "hibernate" });
7570
+ await waitAndReport2(ctx, a, id, ["hibernated"], { action: "hibernate" });
5835
7571
  }
5836
7572
  async function infraResume(ctx, a) {
5837
7573
  const id = a.req("id");
5838
- const { resource } = await resume(ctx, { id });
7574
+ const { resource } = await resume2(ctx, { id });
5839
7575
  if (!a.bool("wait")) {
5840
7576
  out({ resource });
5841
7577
  return;
5842
7578
  }
5843
- await waitAndReport(ctx, a, id, ["active"], { action: "resume" });
7579
+ await waitAndReport2(ctx, a, id, ["active"], { action: "resume" });
5844
7580
  }
5845
7581
  async function infraDestroy(ctx, a) {
5846
7582
  const id = a.req("id");
@@ -5849,7 +7585,7 @@ async function infraDestroy(ctx, a) {
5849
7585
  out({ resource });
5850
7586
  return;
5851
7587
  }
5852
- await waitAndReport(ctx, a, id, ["destroyed"], { action: "destroy" });
7588
+ await waitAndReport2(ctx, a, id, ["destroyed"], { action: "destroy" });
5853
7589
  }
5854
7590
  async function infraRename(ctx, a) {
5855
7591
  out(
@@ -5877,7 +7613,7 @@ async function infraResize(ctx, a) {
5877
7613
  });
5878
7614
  return;
5879
7615
  }
5880
- await waitAndReport(
7616
+ await waitAndReport2(
5881
7617
  ctx,
5882
7618
  a,
5883
7619
  id,
@@ -5983,6 +7719,10 @@ var SPECS = {
5983
7719
  ...prerenderSpecs,
5984
7720
  ...filesSpecs,
5985
7721
  ...dataSourcesSpecs,
7722
+ ...dataSourceJobsSpecs,
7723
+ ...dataSourceConnectorsSpecs,
7724
+ ...dataSourceEvalsSpecs,
7725
+ ...dataSourceMappersSpecs,
5986
7726
  ...voiceSpecs,
5987
7727
  ...agentSpecs,
5988
7728
  ...eventsSpecs,
@@ -6008,6 +7748,10 @@ var HANDLERS = {
6008
7748
  ...prerenderHandlers,
6009
7749
  ...filesHandlers,
6010
7750
  ...dataSourcesHandlers,
7751
+ ...dataSourceJobsHandlers,
7752
+ ...dataSourceConnectorsHandlers,
7753
+ ...dataSourceEvalsHandlers,
7754
+ ...dataSourceMappersHandlers,
6011
7755
  ...voiceHandlers,
6012
7756
  ...agentHandlers,
6013
7757
  ...eventsHandlers,
@@ -6044,8 +7788,8 @@ var GROUP_HELP = {
6044
7788
  };
6045
7789
 
6046
7790
  // src/ctx.ts
6047
- import fs4 from "fs";
6048
- import path4 from "path";
7791
+ import fs6 from "fs";
7792
+ import path6 from "path";
6049
7793
 
6050
7794
  // src/parseJsonConfig.ts
6051
7795
  import JSON5 from "json5";
@@ -6074,10 +7818,10 @@ function parseJsonConfig(raw) {
6074
7818
  // src/ctx.ts
6075
7819
  var DEFAULT_BASE_URL = "https://api.mindstudio.ai";
6076
7820
  function loadWorkspaceAppId(workspaceDir = WORKSPACE_DIR) {
6077
- const manifestPath = path4.join(workspaceDir, "mindstudio.json");
7821
+ const manifestPath = path6.join(workspaceDir, "mindstudio.json");
6078
7822
  let raw;
6079
7823
  try {
6080
- raw = fs4.readFileSync(manifestPath, "utf-8");
7824
+ raw = fs6.readFileSync(manifestPath, "utf-8");
6081
7825
  } catch (err) {
6082
7826
  if (err.code === "ENOENT") {
6083
7827
  throw new Error(`mindstudio.json not found at ${manifestPath}`);
@@ -6181,6 +7925,11 @@ function resolveRoute(argv) {
6181
7925
  error: `Unknown subcommand: settings ${sub} ${action ?? ""}. Run 'remy-admin settings --help'`
6182
7926
  };
6183
7927
  }
7928
+ if (group === "datasources" && (sub === "jobs" || sub === "eval" || sub === "map")) {
7929
+ return {
7930
+ error: `Unknown subcommand: datasources ${sub} ${action ?? ""}. Run 'remy-admin datasources --help'`
7931
+ };
7932
+ }
6184
7933
  if (group === "email" && ["domains", "inbound-domains"].includes(sub ?? "")) {
6185
7934
  return {
6186
7935
  error: `Unknown subcommand: email ${sub} ${action ?? ""}. Run 'remy-admin email --help'`