@madewithremy/admin 0.1.30 → 0.1.32

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, path7, status2, body) {
40
- super(`API ${method} ${path7} returned ${status2}: ${JSON.stringify(body)}`);
39
+ constructor(method, path8, status2, body) {
40
+ super(`API ${method} ${path8} returned ${status2}: ${JSON.stringify(body)}`);
41
41
  this.method = method;
42
- this.path = path7;
42
+ this.path = path8;
43
43
  this.status = status2;
44
44
  this.body = body;
45
45
  this.name = "AdminApiError";
@@ -1036,6 +1036,13 @@ function summarizeRelease(release) {
1036
1036
  publishedAt: release.publishedAt ?? null,
1037
1037
  ...release.previewUrl ? { previewUrl: release.previewUrl } : {}
1038
1038
  };
1039
+ const readers = (release.dataSources ?? []).filter((d) => d.reader);
1040
+ if (readers.length > 0 && release.status === "live") {
1041
+ summary2.readers = readers.map((d) => ({
1042
+ slug: d.slug,
1043
+ unread: d.readerUnread ?? 0
1044
+ }));
1045
+ }
1039
1046
  if (release.status !== "failed") {
1040
1047
  return summary2;
1041
1048
  }
@@ -2939,7 +2946,7 @@ var filesSpecs = {
2939
2946
  usage: "Usage: remy-admin files list"
2940
2947
  },
2941
2948
  "files rm": {
2942
- usage: "Usage: remy-admin files rm --store <name> --key <key> [--private]",
2949
+ usage: "Usage: remy-admin files rm --store <name> --key <key> [--public|--private]",
2943
2950
  flags: {
2944
2951
  ...ACCESS_FLAGS,
2945
2952
  store: { type: "string" },
@@ -2950,6 +2957,45 @@ var filesSpecs = {
2950
2957
  function resolveAccess(a) {
2951
2958
  return a.bool("private") ? "private" : "public";
2952
2959
  }
2960
+ async function readAccess(ctx, a, key) {
2961
+ if (a.bool("private")) {
2962
+ return "private";
2963
+ }
2964
+ if (a.bool("public")) {
2965
+ return "public";
2966
+ }
2967
+ const store = storeOf(a);
2968
+ const { stores } = await summary(ctx);
2969
+ const held = stores.filter((s) => s.store === store && s.objectCount > 0);
2970
+ if (held.length === 1) {
2971
+ return held[0].access;
2972
+ }
2973
+ if (held.length === 0) {
2974
+ if (key === void 0) {
2975
+ return "public";
2976
+ }
2977
+ fatal(`Store "${store}" holds no objects, public or private.`);
2978
+ }
2979
+ if (key === void 0) {
2980
+ const counts = held.map((s) => `${s.objectCount.toLocaleString()} ${s.access}`).join(", ");
2981
+ fatal(
2982
+ `Store "${store}" holds objects in both halves (${counts}): pass --public or --private.`
2983
+ );
2984
+ }
2985
+ for (const access of ["private", "public"]) {
2986
+ try {
2987
+ await stat(ctx, { store, access, key });
2988
+ return access;
2989
+ } catch (err) {
2990
+ if (!(err instanceof AdminApiError) || err.status !== 404) {
2991
+ throw err;
2992
+ }
2993
+ }
2994
+ }
2995
+ fatal(
2996
+ `No object "${key}" in store "${store}" (looked in its private and public halves).`
2997
+ );
2998
+ }
2953
2999
  function storeOf(a) {
2954
3000
  return a.str("store") || DEFAULT_STORE;
2955
3001
  }
@@ -2960,8 +3006,8 @@ function requireKeyFlag(a) {
2960
3006
  }
2961
3007
  return key;
2962
3008
  }
2963
- function refOf(a, key) {
2964
- return { store: storeOf(a), access: resolveAccess(a), key };
3009
+ async function refOf(ctx, a, key) {
3010
+ return { store: storeOf(a), access: await readAccess(ctx, a, key), key };
2965
3011
  }
2966
3012
  async function filesPut(ctx, a) {
2967
3013
  const file = a.req("file");
@@ -2987,7 +3033,7 @@ async function filesPut(ctx, a) {
2987
3033
  }
2988
3034
  async function filesGet(ctx, a) {
2989
3035
  const key = a.req("key");
2990
- const ref = refOf(a, key);
3036
+ const ref = await refOf(ctx, a, key);
2991
3037
  const { url } = await sign(
2992
3038
  ctx,
2993
3039
  ref,
@@ -2997,7 +3043,9 @@ async function filesGet(ctx, a) {
2997
3043
  const abs = path2.isAbsolute(outPath) ? outPath : path2.join(WORKSPACE_DIR, outPath);
2998
3044
  const res = await fetch(url);
2999
3045
  if (!res.ok || !res.body) {
3000
- fatal(`Download failed: ${res.status} ${res.statusText}`);
3046
+ fatal(
3047
+ res.status === 403 || res.status === 404 ? `No object "${key}" in the ${ref.access} half of store "${ref.store}" (download answered ${res.status}).` : `Download failed: ${res.status} ${res.statusText}`
3048
+ );
3001
3049
  }
3002
3050
  await pipeline(
3003
3051
  Readable.fromWeb(res.body),
@@ -3006,16 +3054,18 @@ async function filesGet(ctx, a) {
3006
3054
  out({ path: abs, size: fs2.statSync(abs).size });
3007
3055
  }
3008
3056
  async function filesSign(ctx, a) {
3009
- out(await sign(ctx, refOf(a, requireKeyFlag(a)), a.num("ttl")));
3057
+ out(
3058
+ await sign(ctx, await refOf(ctx, a, requireKeyFlag(a)), a.num("ttl"))
3059
+ );
3010
3060
  }
3011
3061
  async function filesStat(ctx, a) {
3012
- out(await stat(ctx, refOf(a, requireKeyFlag(a))));
3062
+ out(await stat(ctx, await refOf(ctx, a, requireKeyFlag(a))));
3013
3063
  }
3014
3064
  async function filesLs(ctx, a) {
3015
3065
  out(
3016
3066
  await ls(ctx, {
3017
3067
  store: storeOf(a),
3018
- access: resolveAccess(a),
3068
+ access: await readAccess(ctx, a),
3019
3069
  prefix: a.str("prefix"),
3020
3070
  q: a.str("q"),
3021
3071
  cursor: a.str("cursor"),
@@ -3035,7 +3085,13 @@ async function filesRm(ctx, a) {
3035
3085
  if (!key) {
3036
3086
  fatal("--key is required.");
3037
3087
  }
3038
- out(await remove(ctx, { store, access: resolveAccess(a), key }));
3088
+ out(
3089
+ await remove(ctx, {
3090
+ store,
3091
+ access: await readAccess(ctx, a, key),
3092
+ key
3093
+ })
3094
+ );
3039
3095
  }
3040
3096
  var filesHandlers = {
3041
3097
  "files put": filesPut,
@@ -3059,12 +3115,12 @@ Subcommands:
3059
3115
 
3060
3116
  Usage:
3061
3117
  remy-admin files put [--public|--private] [--store <name>] [--key <key>] [--cache-control <value>] <file>
3062
- remy-admin files get [--private] [--store <name>] [--out <path>] <key>
3063
- remy-admin files sign [--private] [--store <name>] [--ttl <seconds>] --key <key>
3064
- remy-admin files stat [--private] [--store <name>] --key <key>
3065
- remy-admin files ls [--private] [--store <name>] [--prefix <prefix>] [--q <substring>] [--limit <n>]
3118
+ remy-admin files get [--public|--private] [--store <name>] [--out <path>] <key>
3119
+ remy-admin files sign [--public|--private] [--store <name>] [--ttl <seconds>] --key <key>
3120
+ remy-admin files stat [--public|--private] [--store <name>] --key <key>
3121
+ remy-admin files ls [--public|--private] [--store <name>] [--prefix <prefix>] [--q <substring>] [--limit <n>]
3066
3122
  remy-admin files list
3067
- remy-admin files rm --store <name> --key <key> [--private]
3123
+ remy-admin files rm --store <name> --key <key> [--public|--private]
3068
3124
 
3069
3125
  Examples:
3070
3126
  remy-admin files put --public ./hero.jpg
@@ -3074,7 +3130,10 @@ Examples:
3074
3130
  remy-admin files rm --store handoff --key <key> --private
3075
3131
 
3076
3132
  Notes:
3077
- - Defaults to a public store named 'assets'; pass --private / --store to change.
3133
+ - Defaults to the store named 'assets'. put writes public unless --private.
3134
+ Reads (get, sign, stat, ls, rm) without --public or --private use the half
3135
+ of the store that holds objects; in a store holding both, a key is found
3136
+ wherever it is, and ls asks which.
3078
3137
  - Handing someone a large or sensitive file? Use a PRIVATE store + \`sign\`:
3079
3138
  the link is unguessable, expires (--ttl seconds, max 7 days), and the object
3080
3139
  stays deletable with \`rm\`. Never use the account media CDN
@@ -3092,8 +3151,8 @@ Notes:
3092
3151
  - Public image URLs accept transform params, e.g. ?w=400&fit=cover.`;
3093
3152
 
3094
3153
  // src/commands/dataSources.ts
3095
- import fs5 from "fs";
3096
- import path5 from "path";
3154
+ import fs6 from "fs";
3155
+ import path6 from "path";
3097
3156
 
3098
3157
  // src/ops/dataSources.ts
3099
3158
  import { createHash as createHash2 } from "crypto";
@@ -3111,7 +3170,7 @@ var summarize = (d) => ({
3111
3170
  ...d.errorMessage ? { error: d.errorMessage } : {}
3112
3171
  });
3113
3172
  async function addDocument(ctx, params) {
3114
- const { slug, filename, content, metadata, onProgress } = params;
3173
+ const { slug, filename, content, metadata, entities: entities2, onProgress } = params;
3115
3174
  const contentHash = createHash2("sha256").update(content).digest("hex");
3116
3175
  const token = await call(
3117
3176
  ctx,
@@ -3121,7 +3180,8 @@ async function addDocument(ctx, params) {
3121
3180
  slug,
3122
3181
  filename,
3123
3182
  contentHash,
3124
- ...metadata ? { metadata } : {}
3183
+ ...metadata ? { metadata } : {},
3184
+ ...entities2 ? { entities: entities2 } : {}
3125
3185
  }
3126
3186
  );
3127
3187
  if (token.alreadyCurrent) {
@@ -3147,7 +3207,8 @@ async function addDocument(ctx, params) {
3147
3207
  contentHash,
3148
3208
  contentType: token.contentType,
3149
3209
  size: content.length,
3150
- ...metadata ? { metadata } : {}
3210
+ ...metadata ? { metadata } : {},
3211
+ ...entities2 ? { entities: entities2 } : {}
3151
3212
  }
3152
3213
  );
3153
3214
  return {
@@ -3324,6 +3385,35 @@ function count(ctx, params) {
3324
3385
  ...candidate ? { candidate: true } : {}
3325
3386
  });
3326
3387
  }
3388
+ function entities(ctx, params) {
3389
+ const { slug, filter, limit, candidate } = params;
3390
+ return call(
3391
+ ctx,
3392
+ "POST",
3393
+ `${base(ctx.appId)}/entities`,
3394
+ {
3395
+ slug,
3396
+ ...filter ? { filter } : {},
3397
+ ...limit !== void 0 ? { limit } : {},
3398
+ ...candidate ? { candidate: true } : {}
3399
+ }
3400
+ );
3401
+ }
3402
+ function passages(ctx, params) {
3403
+ const { slug, filter, cursor, limit, candidate } = params;
3404
+ return call(
3405
+ ctx,
3406
+ "POST",
3407
+ `${base(ctx.appId)}/passages`,
3408
+ {
3409
+ slug,
3410
+ ...filter ? { filter } : {},
3411
+ ...cursor ? { cursor } : {},
3412
+ ...limit !== void 0 ? { limit } : {},
3413
+ ...candidate ? { candidate: true } : {}
3414
+ }
3415
+ );
3416
+ }
3327
3417
  function configGet(ctx, slug) {
3328
3418
  return call(
3329
3419
  ctx,
@@ -3485,6 +3575,67 @@ function deleteSource(ctx, slug) {
3485
3575
  { slug }
3486
3576
  );
3487
3577
  }
3578
+ var DICTIONARY_PAGE_ENTRIES = 1e4;
3579
+ var DICTIONARY_PAGE_BYTES = 4e6;
3580
+ async function pushDictionary(ctx, params) {
3581
+ const { slug, entries } = params;
3582
+ const stage = await call(
3583
+ ctx,
3584
+ "POST",
3585
+ `${base(ctx.appId)}/dictionary/stage`,
3586
+ {
3587
+ slug,
3588
+ ...params.linkerVersion !== void 0 ? { linkerVersion: params.linkerVersion } : {}
3589
+ }
3590
+ );
3591
+ const perPage = Math.min(
3592
+ DICTIONARY_PAGE_ENTRIES,
3593
+ stage.pageMaxEntries || DICTIONARY_PAGE_ENTRIES
3594
+ );
3595
+ let pages2 = 0;
3596
+ let start2 = 0;
3597
+ while (start2 < entries.length) {
3598
+ let end = start2;
3599
+ let bytes = 0;
3600
+ while (end < entries.length && end - start2 < perPage) {
3601
+ const size = JSON.stringify(entries[end]).length;
3602
+ if (end > start2 && bytes + size > DICTIONARY_PAGE_BYTES) {
3603
+ break;
3604
+ }
3605
+ bytes += size;
3606
+ end += 1;
3607
+ }
3608
+ await call(ctx, "POST", `${base(ctx.appId)}/dictionary/page`, {
3609
+ slug,
3610
+ stageId: stage.stageId,
3611
+ page: pages2,
3612
+ entries: entries.slice(start2, end)
3613
+ });
3614
+ pages2 += 1;
3615
+ start2 = end;
3616
+ params.onProgress?.(start2, entries.length);
3617
+ }
3618
+ return call(
3619
+ ctx,
3620
+ "POST",
3621
+ `${base(ctx.appId)}/dictionary/commit`,
3622
+ { slug, stageId: stage.stageId, pages: pages2 }
3623
+ );
3624
+ }
3625
+ function dictionary(ctx, params) {
3626
+ return call(
3627
+ ctx,
3628
+ "POST",
3629
+ `${base(ctx.appId)}/dictionary`,
3630
+ { slug: params.slug }
3631
+ );
3632
+ }
3633
+ function link(ctx, params) {
3634
+ return call(ctx, "POST", `${base(ctx.appId)}/link`, {
3635
+ slug: params.slug,
3636
+ text: params.text
3637
+ });
3638
+ }
3488
3639
 
3489
3640
  // src/ops/dataSourceJobs.ts
3490
3641
  var DEFAULT_WAIT_TIMEOUT_MS2 = 60 * 60 * 1e3;
@@ -3643,6 +3794,39 @@ function describeProgress(job, start2) {
3643
3794
  // src/commands/_shared/dataSources.ts
3644
3795
  var DEFAULT_SOURCE = "default";
3645
3796
  var sourceOf = (a) => a.str("source") || DEFAULT_SOURCE;
3797
+ function parseKeyValuePairs(raw, flag) {
3798
+ if (raw === void 0) {
3799
+ return void 0;
3800
+ }
3801
+ const pairs2 = {};
3802
+ for (const part of raw.split(",")) {
3803
+ const trimmed = part.trim();
3804
+ if (!trimmed) {
3805
+ continue;
3806
+ }
3807
+ const eq = trimmed.indexOf("=");
3808
+ if (eq <= 0) {
3809
+ fatal(`--${flag} entries must be key=value (got "${trimmed}").`);
3810
+ }
3811
+ const key = trimmed.slice(0, eq).trim();
3812
+ const value = trimmed.slice(eq + 1).trim();
3813
+ pairs2[key] = value === "true" ? true : value === "false" ? false : value !== "" && !Number.isNaN(Number(value)) ? Number(value) : value;
3814
+ }
3815
+ if (Object.keys(pairs2).length === 0) {
3816
+ fatal(`--${flag} needs at least one key=value pair.`);
3817
+ }
3818
+ return pairs2;
3819
+ }
3820
+ function documentSelectorOf(raw) {
3821
+ if (raw.trimStart().startsWith("{")) {
3822
+ try {
3823
+ return JSON.parse(raw);
3824
+ } catch (err) {
3825
+ fatal(`--filter is not valid JSON: ${err.message}`);
3826
+ }
3827
+ }
3828
+ return { metadata: parseKeyValuePairs(raw, "filter") };
3829
+ }
3646
3830
  var timeoutOf = (a) => {
3647
3831
  const seconds = a.str("timeout");
3648
3832
  return seconds ? { timeoutMs: Number(seconds) * 1e3 } : {};
@@ -4658,9 +4842,118 @@ Retrieval evals:
4658
4842
  with --candidate, compare, promote or drop.
4659
4843
  `;
4660
4844
 
4661
- // src/commands/dataSourceJobs.ts
4845
+ // src/commands/dataSourceDictionary.ts
4662
4846
  import fs4 from "fs";
4663
4847
  import path4 from "path";
4848
+ var dataSourceDictionarySpecs = {
4849
+ "datasources dictionary push": {
4850
+ usage: "Usage: remy-admin datasources dictionary push [--source <slug>] [--linker-version <n>] <file.jsonl>",
4851
+ positionals: [{ name: "file", required: true }],
4852
+ flags: {
4853
+ source: { type: "string" },
4854
+ "linker-version": { type: "number", min: 1 }
4855
+ }
4856
+ },
4857
+ "datasources dictionary status": {
4858
+ usage: "Usage: remy-admin datasources dictionary status [--source <slug>]",
4859
+ flags: {
4860
+ source: { type: "string" }
4861
+ }
4862
+ },
4863
+ "datasources link": {
4864
+ usage: "Usage: remy-admin datasources link [--source <slug>] <text>",
4865
+ positionals: [{ name: "text", required: true }],
4866
+ flags: {
4867
+ source: { type: "string" }
4868
+ }
4869
+ }
4870
+ };
4871
+ function readEntries(file) {
4872
+ const abs = path4.isAbsolute(file) ? file : path4.join(WORKSPACE_DIR, file);
4873
+ let content;
4874
+ try {
4875
+ content = fs4.readFileSync(abs, "utf8");
4876
+ } catch (err) {
4877
+ fatal(`Could not read "${file}": ${err.message}`);
4878
+ }
4879
+ const entries = [];
4880
+ content.split("\n").forEach((line, i) => {
4881
+ const trimmed = line.trim();
4882
+ if (!trimmed) {
4883
+ return;
4884
+ }
4885
+ let parsed;
4886
+ try {
4887
+ parsed = JSON.parse(trimmed);
4888
+ } catch {
4889
+ fatal(`${file}:${i + 1} is not valid JSON.`);
4890
+ }
4891
+ if (typeof parsed?.id !== "string" || !Array.isArray(parsed?.names) || !parsed.names.every((name) => typeof name === "string")) {
4892
+ fatal(`${file}:${i + 1} needs { "id": "...", "names": ["...", ...] }.`);
4893
+ }
4894
+ entries.push({ id: parsed.id, names: parsed.names });
4895
+ });
4896
+ if (entries.length === 0) {
4897
+ fatal(`"${file}" has no entries.`);
4898
+ }
4899
+ return entries;
4900
+ }
4901
+ async function dictionaryPush(ctx, a) {
4902
+ const entries = readEntries(a.req("file"));
4903
+ const linkerVersion = a.num("linker-version");
4904
+ out(
4905
+ await pushDictionary(ctx, {
4906
+ slug: sourceOf(a),
4907
+ entries,
4908
+ ...linkerVersion !== void 0 ? { linkerVersion } : {},
4909
+ onProgress: (sent, total) => progress(`pushing ${sent.toLocaleString()}/${total.toLocaleString()}\u2026`)
4910
+ })
4911
+ );
4912
+ }
4913
+ async function dictionaryStatus(ctx, a) {
4914
+ out(await dictionary(ctx, { slug: sourceOf(a) }));
4915
+ }
4916
+ async function link2(ctx, a) {
4917
+ out(await link(ctx, { slug: sourceOf(a), text: a.req("text") }));
4918
+ }
4919
+ var dataSourceDictionaryHandlers = {
4920
+ "datasources dictionary push": dictionaryPush,
4921
+ "datasources dictionary status": dictionaryStatus,
4922
+ "datasources link": link2
4923
+ };
4924
+ var dataSourceDictionaryHelp = `
4925
+ Dictionary:
4926
+ remy-admin datasources dictionary push [--source <slug>] [--linker-version <n>] <file.jsonl>
4927
+ remy-admin datasources dictionary status [--source <slug>]
4928
+ remy-admin datasources link [--source <slug>] <text>
4929
+
4930
+ A dictionary is your own entity ids and the names each goes by, a
4931
+ projection of your tables: one JSONL line per entry,
4932
+ {"id": "P-101A", "names": ["P-101A", "Main Feed Pump"]}. Push it whole; it
4933
+ replaces the source's dictionary. From then on every passage carries the
4934
+ ids whose names its text mentions, beside the ones its document asserts,
4935
+ so --entities on search and count, and \`datasources entities\`, find a tag
4936
+ wherever it's written. Passages indexed before are re-tagged in the
4937
+ background (a relink; \`dictionary status\` shows it), with nothing
4938
+ re-embedded. An unchanged dictionary commits nothing.
4939
+
4940
+ Matching ignores case and a tag's separators (P-101A, P101A, p 101a) and
4941
+ an inch or foot mark after a size (8"-P-1057-A1A is 8-P-1057-A1A), takes
4942
+ whole words only (P-101AB is not P-101A), and prefers the longest name; a
4943
+ name two ids claim tags both. A name needs three letters or digits (two in
4944
+ Chinese, Japanese or Korean). The push reports ambiguous names, the most
4945
+ claimed (each with one name as pushed), and names that can't match their
4946
+ own text as written (\`unmatchable\`). Up to 250,000 entries, 16 names
4947
+ each, a million names.
4948
+
4949
+ \`link\` shows what the current dictionary finds in any text: check one
4950
+ there before relying on it. --linker-version moves a dictionary to a newer
4951
+ matching rule; without it a push keeps the one its dictionary has.
4952
+ `;
4953
+
4954
+ // src/commands/dataSourceJobs.ts
4955
+ import fs5 from "fs";
4956
+ import path5 from "path";
4664
4957
  var dataSourceJobsSpecs = {
4665
4958
  "datasources jobs start": {
4666
4959
  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>] [--priority] [--approve] [--wait] [--timeout <sec>]",
@@ -4738,10 +5031,10 @@ async function jobsStart(ctx, a) {
4738
5031
  }
4739
5032
  let source;
4740
5033
  if (manifest) {
4741
- const abs = path4.isAbsolute(manifest) ? manifest : path4.join(WORKSPACE_DIR, manifest);
5034
+ const abs = path5.isAbsolute(manifest) ? manifest : path5.join(WORKSPACE_DIR, manifest);
4742
5035
  let content;
4743
5036
  try {
4744
- content = fs4.readFileSync(abs);
5037
+ content = fs5.readFileSync(abs);
4745
5038
  } catch (err) {
4746
5039
  fatal(`Could not read manifest "${manifest}": ${err.message}`);
4747
5040
  }
@@ -4867,12 +5160,13 @@ Bulk ingestion (jobs):
4867
5160
  A job loads a whole corpus at once. It reads either every object under a
4868
5161
  prefix of one of the app's file stores (\`files put\` fills one; an app can
4869
5162
  write one at runtime) or a manifest you upload: JSONL, one line per document,
4870
- {"url": "https://\u2026", "filename": "x.pdf", "externalId": "\u2026", "metadata": {\u2026}}.
4871
- filename, externalId and metadata are optional; the URL must be public.
5163
+ {"url": "https://\u2026", "filename": "x.pdf", "externalId": "\u2026", "metadata": {\u2026},
5164
+ "entities": ["\u2026"]}. Everything but the URL is optional; it must be public.
4872
5165
 
4873
- Nothing is spent until you say so. The platform counts the source, reads a
4874
- sample of about a hundred documents, and writes a plan: documents, chunks,
4875
- cost per stage (embedding, extraction, contextual) at today's rates, storage,
5166
+ Nothing past the sample runs until you say so. The platform counts the
5167
+ source, reads a sample of about a hundred documents, and writes a plan:
5168
+ documents, chunks, cost per stage (embedding, extraction, contextual, and
5169
+ the mapper's own model calls, priced from the sample's) at today's rates, storage,
4876
5170
  whether it fits where the source lives, and a duration. \`jobs status\` shows
4877
5171
  it; \`jobs approve\` starts the run. Two gates: the corpus has to fit the
4878
5172
  source's placement (a shared-pool source over the per-source cap answers
@@ -4954,7 +5248,8 @@ function mapTest(ctx, params) {
4954
5248
  ...selectionBody(params.selection),
4955
5249
  ...params.keys ? { keys: params.keys } : {},
4956
5250
  ...params.limit !== void 0 ? { limit: params.limit } : {},
4957
- ...params.dev ? { dev: true } : {}
5251
+ ...params.dev ? { dev: true } : {},
5252
+ ...params.releaseId ? { releaseId: params.releaseId } : {}
4958
5253
  }
4959
5254
  );
4960
5255
  }
@@ -4982,6 +5277,7 @@ function quarantine(ctx, params) {
4982
5277
  "GET",
4983
5278
  `${base5(ctx.appId)}/jobs/${params.id}/quarantine${qs({
4984
5279
  ...params.kind ? { kind: params.kind } : {},
5280
+ ...params.stage ? { stage: params.stage } : {},
4985
5281
  ...params.reason ? { reason: params.reason } : {},
4986
5282
  ...params.cursor ? { cursor: params.cursor } : {},
4987
5283
  ...params.limit !== void 0 ? { limit: params.limit } : {}
@@ -5019,12 +5315,13 @@ var dataSourceMappersSpecs = {
5019
5315
  }
5020
5316
  },
5021
5317
  "datasources map test": {
5022
- usage: "Usage: remy-admin datasources map test [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --connector) [--keys k1,k2,...] [--limit <n>] [--dev] [--full]",
5318
+ usage: "Usage: remy-admin datasources map test [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --connector) [--keys k1,k2,...] [--limit <n>] [--dev [--release <dev-release-id>]] [--full]",
5023
5319
  flags: {
5024
5320
  source: { type: "string" },
5025
5321
  ...SELECTION_FLAGS,
5026
5322
  limit: { type: "number", min: 1 },
5027
5323
  dev: { type: "boolean" },
5324
+ release: { type: "string" },
5028
5325
  full: { type: "boolean" }
5029
5326
  },
5030
5327
  requireAnyOf: {
@@ -5050,10 +5347,11 @@ var dataSourceMappersSpecs = {
5050
5347
  }
5051
5348
  },
5052
5349
  "datasources jobs quarantine": {
5053
- usage: "Usage: remy-admin datasources jobs quarantine <id> [--kind skip|error] [--reason <text>] [--cursor <id>] [--limit <n>]",
5350
+ usage: "Usage: remy-admin datasources jobs quarantine <id> [--kind skip|error] [--stage map|read] [--reason <text>] [--cursor <id>] [--limit <n>]",
5054
5351
  positionals: [{ name: "id", required: true }],
5055
5352
  flags: {
5056
5353
  kind: { type: "string" },
5354
+ stage: { type: "string" },
5057
5355
  reason: { type: "string" },
5058
5356
  cursor: { type: "string" },
5059
5357
  limit: { type: "number", min: 1 }
@@ -5135,7 +5433,8 @@ async function mapTest2(ctx, a) {
5135
5433
  selection: selectionOf(a),
5136
5434
  ...keysOf(a) ? { keys: keysOf(a) } : {},
5137
5435
  ...limit !== void 0 ? { limit } : {},
5138
- ...a.bool("dev") ? { dev: true } : {}
5436
+ ...a.bool("dev") ? { dev: true } : {},
5437
+ ...a.str("release") ? { releaseId: a.str("release") } : {}
5139
5438
  });
5140
5439
  const results = a.bool("full") ? result.results : result.results.map(
5141
5440
  (r) => r.outcome.kind === "documents" ? {
@@ -5206,18 +5505,32 @@ async function quarantine2(ctx, a) {
5206
5505
  const id = a.req("id");
5207
5506
  const limit = a.num("limit");
5208
5507
  const kind = kindOf(a);
5508
+ const stage = a.str("stage");
5509
+ if (stage !== void 0 && stage !== "map" && stage !== "read") {
5510
+ fatal(`--stage must be "map" or "read" (got "${stage}").`);
5511
+ }
5209
5512
  const reason = a.str("reason");
5210
5513
  const cursor = a.str("cursor");
5211
5514
  const result = await quarantine(ctx, {
5212
5515
  id,
5213
5516
  ...kind ? { kind } : {},
5517
+ ...stage ? { stage } : {},
5214
5518
  ...reason ? { reason } : {},
5215
5519
  ...cursor ? { cursor } : {},
5216
5520
  ...limit !== void 0 ? { limit } : {}
5217
5521
  });
5522
+ const { skipped, errors, readErrors } = result.counts;
5523
+ const notes = [
5524
+ ...skipped + errors > 0 ? [
5525
+ `Mapping: fix the mapper, push, \`datasources map deploy\`, then \`datasources jobs replay ${id}\` to run these through it again.`
5526
+ ] : [],
5527
+ ...readErrors > 0 ? [
5528
+ `Reading: fix the reader and publish, then \`datasources reader retry --source ${result.dataSource.slug} --job ${id}\` reads these again.`
5529
+ ] : []
5530
+ ];
5218
5531
  out({
5219
5532
  ...result,
5220
- note: result.counts.skipped + result.counts.errors === 0 ? "Nothing quarantined: the mapper produced documents (or deletions) for every object." : `Fix the mapper, push, \`datasources map deploy\`, then \`datasources jobs replay ${id}\` to run these through it again.`
5533
+ note: notes.length === 0 ? "Nothing quarantined: the mapper produced documents (or deletions) for every object, and the reader read every document." : notes.join(" ")
5221
5534
  });
5222
5535
  }
5223
5536
  async function replay2(ctx, a) {
@@ -5255,10 +5568,10 @@ var dataSourceMappersHandlers = {
5255
5568
  var dataSourceMappersHelp = `
5256
5569
  Mapping raw objects into documents (mappers):
5257
5570
  remy-admin datasources inspect [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>]
5258
- remy-admin datasources map test [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>] [--dev] [--full]
5571
+ remy-admin datasources map test [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>] [--dev [--release <id>]] [--full]
5259
5572
  remy-admin datasources map deploy [--source <slug>] [--release <id>]
5260
5573
  remy-admin datasources remap [--source <slug>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]
5261
- remy-admin datasources jobs quarantine <id> [--kind skip|error] [--reason <text>] [--cursor <id>] [--limit <n>]
5574
+ remy-admin datasources jobs quarantine <id> [--kind skip|error] [--stage map|read] [--reason <text>] [--cursor <id>] [--limit <n>]
5262
5575
  remy-admin datasources jobs replay <id> [--kind skip|error] [--wait] [--timeout <sec>]
5263
5576
 
5264
5577
  A source takes every object it is given as one document. When the file is
@@ -5299,6 +5612,234 @@ Mapping raw objects into documents (mappers):
5299
5612
  pauses with pauseReason 'skips' for a look at the quarantine.
5300
5613
  `;
5301
5614
 
5615
+ // src/ops/dataSourceReaders.ts
5616
+ function base6(appId) {
5617
+ return `/_internal/v2/apps/${appId}/datasources`;
5618
+ }
5619
+ function readerTest(ctx, params) {
5620
+ return call(
5621
+ ctx,
5622
+ "POST",
5623
+ `${base6(ctx.appId)}/reader/test`,
5624
+ {
5625
+ slug: params.slug,
5626
+ ...params.documents ? { documents: params.documents } : {},
5627
+ ...params.limit !== void 0 ? { limit: params.limit } : {},
5628
+ ...params.releaseId ? { releaseId: params.releaseId } : {}
5629
+ }
5630
+ );
5631
+ }
5632
+ function readerStatus(ctx, params) {
5633
+ return call(
5634
+ ctx,
5635
+ "POST",
5636
+ `${base6(ctx.appId)}/reader/status`,
5637
+ { slug: params.slug }
5638
+ );
5639
+ }
5640
+ function readerRetry(ctx, params) {
5641
+ return call(
5642
+ ctx,
5643
+ "POST",
5644
+ `${base6(ctx.appId)}/reader/retry`,
5645
+ {
5646
+ slug: params.slug,
5647
+ ...params.jobId ? { jobId: params.jobId } : {}
5648
+ }
5649
+ );
5650
+ }
5651
+ function reread(ctx, params) {
5652
+ return call(
5653
+ ctx,
5654
+ "POST",
5655
+ `${base6(ctx.appId)}/reread`,
5656
+ {
5657
+ slug: params.slug,
5658
+ ...params.unread ? { unread: true } : {},
5659
+ ...params.filter ? { filter: params.filter } : {},
5660
+ ...params.budgetDollars !== void 0 ? { budgetDollars: params.budgetDollars } : {},
5661
+ ...params.limit !== void 0 ? { limit: params.limit } : {}
5662
+ }
5663
+ );
5664
+ }
5665
+
5666
+ // src/commands/dataSourceReaders.ts
5667
+ var dataSourceReadersSpecs = {
5668
+ "datasources reader test": {
5669
+ usage: "Usage: remy-admin datasources reader test --dev [--source <slug>] [--documents id1,id2,...] [--limit <n>] [--release <dev-release-id>]",
5670
+ flags: {
5671
+ source: { type: "string" },
5672
+ dev: { type: "boolean" },
5673
+ documents: { type: "string" },
5674
+ limit: { type: "number", min: 1 },
5675
+ release: { type: "string" }
5676
+ }
5677
+ },
5678
+ "datasources reader status": {
5679
+ usage: "Usage: remy-admin datasources reader status [--source <slug>]",
5680
+ flags: {
5681
+ source: { type: "string" }
5682
+ }
5683
+ },
5684
+ "datasources reader retry": {
5685
+ usage: "Usage: remy-admin datasources reader retry [--source <slug>] [--job <id>]",
5686
+ flags: {
5687
+ source: { type: "string" },
5688
+ job: { type: "string" }
5689
+ }
5690
+ },
5691
+ "datasources reread": {
5692
+ usage: "Usage: remy-admin datasources reread [--source <slug>] [--unread] [--filter <k=v,...|json>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]",
5693
+ flags: {
5694
+ source: { type: "string" },
5695
+ unread: { type: "boolean" },
5696
+ filter: { type: "string" },
5697
+ budget: { type: "number", min: 0.01 },
5698
+ limit: { type: "number", min: 1 },
5699
+ wait: { type: "boolean" },
5700
+ timeout: { type: "string" }
5701
+ }
5702
+ }
5703
+ };
5704
+ async function readerTest2(ctx, a) {
5705
+ if (!a.bool("dev")) {
5706
+ fatal(
5707
+ "reader test runs the dev session's reader only: pass --dev. The live reader reads through deliveries and `datasources reread`."
5708
+ );
5709
+ }
5710
+ const slug = sourceOf(a);
5711
+ const raw = a.str("documents");
5712
+ const documents2 = raw?.split(",").map((id) => id.trim()).filter(Boolean);
5713
+ if (raw !== void 0 && documents2?.length === 0) {
5714
+ fatal("--documents needs at least one document id.");
5715
+ }
5716
+ const limit = a.num("limit");
5717
+ const result = await readerTest(ctx, {
5718
+ slug,
5719
+ ...documents2 ? { documents: documents2 } : {},
5720
+ ...limit !== void 0 ? { limit } : {},
5721
+ ...a.str("release") ? { releaseId: a.str("release") } : {}
5722
+ });
5723
+ out({
5724
+ dataSource: slug,
5725
+ ...result,
5726
+ note: "Ran the LOCAL reader through the dev session, writing into the dev session's database. Query its tables to check what it wrote; publish to make it the live reader, then `datasources reread --unread` reads what it has not."
5727
+ });
5728
+ if (result.failed > 0) {
5729
+ throw new CliError(
5730
+ `The reader failed on ${result.failed} of ${result.documents.length} document(s).`,
5731
+ EXIT.buildFailed
5732
+ );
5733
+ }
5734
+ }
5735
+ async function readerStatus2(ctx, a) {
5736
+ const slug = sourceOf(a);
5737
+ const result = await readerStatus(ctx, { slug });
5738
+ const { reader, deliveries, documents: documents2 } = result;
5739
+ out({
5740
+ dataSource: slug,
5741
+ ...result,
5742
+ note: !reader ? `The live release declares no reader for "${slug}". Declare one in mindstudio.json's dataSources and publish.` : deliveries.parked > 0 ? `${deliveries.parked} delivery(ies) failed six times and parked. Fix the reader and publish, then \`datasources reader retry\`.` : documents2.unread > 0 ? `${documents2.unread.toLocaleString()} live document(s) not read yet. \`datasources reread --unread\` plans reading them.` : "Every live document has been read."
5743
+ });
5744
+ }
5745
+ async function readerRetry2(ctx, a) {
5746
+ const slug = sourceOf(a);
5747
+ const jobId = a.str("job");
5748
+ const result = await readerRetry(ctx, {
5749
+ slug,
5750
+ ...jobId ? { jobId } : {}
5751
+ });
5752
+ out({
5753
+ dataSource: slug,
5754
+ ...jobId ? { job: jobId } : {},
5755
+ ...result,
5756
+ note: result.retried === 0 ? jobId ? `Job ${jobId}'s reader failed on nothing.` : "Nothing parked." : `Queued ${result.retried.toLocaleString()} delivery(ies) again; \`datasources reader status\` shows them drain.`
5757
+ });
5758
+ }
5759
+ async function reread2(ctx, a) {
5760
+ const slug = sourceOf(a);
5761
+ const raw = a.str("filter");
5762
+ const budget = a.num("budget");
5763
+ const limit = a.num("limit");
5764
+ const { job } = await reread(ctx, {
5765
+ slug,
5766
+ ...a.bool("unread") ? { unread: true } : {},
5767
+ ...raw !== void 0 ? { filter: documentSelectorOf(raw) } : {},
5768
+ ...budget !== void 0 ? { budgetDollars: budget } : {},
5769
+ ...limit !== void 0 ? { limit } : {}
5770
+ });
5771
+ if (!a.bool("wait")) {
5772
+ out({
5773
+ dataSource: slug,
5774
+ job,
5775
+ note: `Planning: the reread prices reading from the reader's history, or reads a first slice for real to measure it. \`datasources jobs status ${job.id}\` shows the plan; \`datasources jobs approve ${job.id}\` runs it.`
5776
+ });
5777
+ return;
5778
+ }
5779
+ await waitAndReport(ctx, a, job.id, "plan", {
5780
+ dataSource: slug,
5781
+ action: "reread"
5782
+ });
5783
+ }
5784
+ var dataSourceReadersHandlers = {
5785
+ "datasources reader test": readerTest2,
5786
+ "datasources reader status": readerStatus2,
5787
+ "datasources reader retry": readerRetry2,
5788
+ "datasources reread": reread2
5789
+ };
5790
+ var dataSourceReadersHelp = `
5791
+ Reading documents into the app (readers):
5792
+ remy-admin datasources reader test --dev [--source <slug>] [--documents id1,id2] [--limit <n>] [--release <id>]
5793
+ remy-admin datasources reader status [--source <slug>]
5794
+ remy-admin datasources reader retry [--source <slug>] [--job <id>]
5795
+ remy-admin datasources reread [--source <slug>] [--unread] [--filter <k=v|json>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]
5796
+
5797
+ A READER is app code the platform runs over every document a source
5798
+ builds, writing what it finds into the app's own tables: the extraction an
5799
+ app would otherwise run from a cron over allDocuments(), with the
5800
+ platform's batching, retries, budgets and pauses. Declare it in
5801
+ mindstudio.json:
5802
+ "dataSources": [{ "slug": "archive", "reader": { "path": "dist/datasources/archive.reader.ts" } }]
5803
+ The file exports defineReader(Archive, { read, removed, cleared?,
5804
+ relinked? }) from @mindstudio-ai/agent. read(document) gets the document
5805
+ (id, sourceId, filename, metadata, entities, url), document.chunks(), its
5806
+ passages as indexed with each one's entities, and document.bytes(), the
5807
+ file it came from; removed(documentId) is called when a document leaves
5808
+ the source; cleared(sourceId) once when the source is deleted; relinked(
5809
+ document) when a new dictionary re-tagged a document already read, for
5810
+ rows derived from its tags. Throwing fails that document, which is
5811
+ retried. A document can be
5812
+ read more than once, so read() replaces the document's rows as a set,
5813
+ keyed by its id, and removed() deletes them. Options on the declaration:
5814
+ timeoutMs (per document, default 60000, max 300000) and concurrency
5815
+ (frames in flight for the source, default 16, max 64).
5816
+
5817
+ The reader always runs against the LIVE release, its code and its
5818
+ database: nothing reads until the app publishes one. From then on every
5819
+ document whose build lands on the active version is read, every removed
5820
+ document delivered as removed, whether it came through add(), a sync or a
5821
+ job. A job over a reader source reads as its last stage: the job stays
5822
+ running until its documents are read, \`jobs status\` says "reading N of M",
5823
+ a document the reader fails on lands in the job's quarantine (stage read),
5824
+ and a reader failing on far more documents than it did at first pauses the
5825
+ job with pauseReason 'reader'.
5826
+
5827
+ The loop: \`reader test --dev\` runs the LOCAL reader through the dev
5828
+ session over real documents, writing into the dev session's database, and
5829
+ prints each outcome; publish, and \`releases wait\` reports each reader's
5830
+ unread documents; \`reread --unread\` reads them. \`reread\` is a job that
5831
+ plans first: it prices reading from what the live reader has done (after a
5832
+ hundred documents), or reads a first slice for real and measures spend,
5833
+ failures and the live database's growth, then waits for \`jobs approve\`.
5834
+ Without --unread it reads every built document again (a changed reader).
5835
+ \`reader status\` shows the reader, its deliveries (pending, in flight,
5836
+ retrying, parked, recent errors), unread documents, the measured cost per
5837
+ document and the live database's size. Deliveries outside jobs retry after
5838
+ 1, 5, 15, 60 and 240 minutes, then park; \`reader retry\` sends parked ones
5839
+ again, and \`reader retry --job <id>\` the documents a job's reader failed
5840
+ on.
5841
+ `;
5842
+
5302
5843
  // src/commands/dataSources.ts
5303
5844
  var INGEST_FLAGS = {
5304
5845
  chunking: { type: "string" },
@@ -5329,11 +5870,12 @@ var CONFIG_FLAGS = {
5329
5870
  };
5330
5871
  var dataSourcesSpecs = {
5331
5872
  "datasources add": {
5332
- usage: "Usage: remy-admin datasources add [--source <slug>] [--metadata <k=v,...>] [--wait] [--timeout <sec>] <file...>",
5873
+ usage: "Usage: remy-admin datasources add [--source <slug>] [--metadata <k=v,...>] [--entities <id,...>] [--wait] [--timeout <sec>] <file...>",
5333
5874
  positionals: [{ name: "file", required: true, variadic: true }],
5334
5875
  flags: {
5335
5876
  source: { type: "string" },
5336
5877
  metadata: { type: "string" },
5878
+ entities: { type: "string" },
5337
5879
  wait: { type: "boolean" },
5338
5880
  timeout: { type: "string" }
5339
5881
  }
@@ -5362,7 +5904,7 @@ var dataSourcesSpecs = {
5362
5904
  }
5363
5905
  },
5364
5906
  "datasources search": {
5365
- 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>",
5907
+ usage: "Usage: remy-admin datasources search [--source <slug>] [--top-k <n>] [--mode <hybrid|semantic|lexical>] [--filter <k=v,...|json>] [--entities <id,...>] [--phrase <text>] [--contains <words>] [--max-per-document <n>] [--rerank <true|false>] [--hybrid <true|false>] [--highlight] [--candidate] <query>",
5366
5908
  positionals: [{ name: "query", required: true }],
5367
5909
  flags: {
5368
5910
  source: { type: "string" },
@@ -5372,6 +5914,7 @@ var dataSourcesSpecs = {
5372
5914
  hybrid: { type: "string" },
5373
5915
  mode: { type: "string" },
5374
5916
  filter: { type: "string" },
5917
+ entities: { type: "string" },
5375
5918
  phrase: { type: "string" },
5376
5919
  contains: { type: "string" },
5377
5920
  "max-per-document": { type: "number", min: 1 },
@@ -5398,12 +5941,38 @@ var dataSourcesSpecs = {
5398
5941
  }
5399
5942
  },
5400
5943
  "datasources count": {
5401
- usage: "Usage: remy-admin datasources count [--source <slug>] [--filter <k=v,...|json>] [--contains <words>] [--phrase <text>] [--candidate]",
5944
+ usage: "Usage: remy-admin datasources count [--source <slug>] [--filter <k=v,...|json>] [--entities <id,...>] [--contains <words>] [--phrase <text>] [--candidate]",
5945
+ flags: {
5946
+ source: { type: "string" },
5947
+ filter: { type: "string" },
5948
+ entities: { type: "string" },
5949
+ contains: { type: "string" },
5950
+ phrase: { type: "string" },
5951
+ candidate: { type: "boolean" }
5952
+ }
5953
+ },
5954
+ "datasources entities": {
5955
+ usage: "Usage: remy-admin datasources entities [--source <slug>] [--filter <k=v,...|json>] [--entities <id,...>] [--contains <words>] [--phrase <text>] [--limit <n>] [--candidate]",
5956
+ flags: {
5957
+ source: { type: "string" },
5958
+ filter: { type: "string" },
5959
+ entities: { type: "string" },
5960
+ contains: { type: "string" },
5961
+ phrase: { type: "string" },
5962
+ limit: { type: "number", min: 1 },
5963
+ candidate: { type: "boolean" }
5964
+ }
5965
+ },
5966
+ "datasources passages": {
5967
+ usage: "Usage: remy-admin datasources passages [--source <slug>] [--filter <k=v,...|json>] [--entities <id,...>] [--contains <words>] [--phrase <text>] [--cursor <cursor>] [--limit <n>] [--candidate]",
5402
5968
  flags: {
5403
5969
  source: { type: "string" },
5404
5970
  filter: { type: "string" },
5971
+ entities: { type: "string" },
5405
5972
  contains: { type: "string" },
5406
5973
  phrase: { type: "string" },
5974
+ cursor: { type: "string" },
5975
+ limit: { type: "number", min: 1 },
5407
5976
  candidate: { type: "boolean" }
5408
5977
  }
5409
5978
  },
@@ -5449,22 +6018,24 @@ async function dataSourcesAdd(ctx, a) {
5449
6018
  const files = a.rest("file");
5450
6019
  const slug = sourceOf(a);
5451
6020
  const metadata = parseKeyValuePairs(a.str("metadata"), "metadata");
6021
+ const entities2 = parseIdList(a.str("entities"));
5452
6022
  const results = [];
5453
6023
  for (const file of files) {
5454
- const abs = path5.isAbsolute(file) ? file : path5.join(WORKSPACE_DIR, file);
6024
+ const abs = path6.isAbsolute(file) ? file : path6.join(WORKSPACE_DIR, file);
5455
6025
  let bytes;
5456
6026
  try {
5457
- bytes = fs5.readFileSync(abs);
6027
+ bytes = fs6.readFileSync(abs);
5458
6028
  } catch (err) {
5459
6029
  fatal(`Could not read file "${file}": ${err.message}`);
5460
6030
  }
5461
- const filename = path5.basename(file);
6031
+ const filename = path6.basename(file);
5462
6032
  results.push(
5463
6033
  await addDocument(ctx, {
5464
6034
  slug,
5465
6035
  filename,
5466
6036
  content: bytes,
5467
6037
  ...metadata ? { metadata } : {},
6038
+ ...entities2 ? { entities: entities2 } : {},
5468
6039
  onProgress: progress
5469
6040
  })
5470
6041
  );
@@ -5535,6 +6106,7 @@ async function dataSourcesStatus(ctx, a) {
5535
6106
  activeVersion: source.activeVersion,
5536
6107
  candidate: source.candidate,
5537
6108
  job: source.job,
6109
+ relink: source.relink,
5538
6110
  failures: (failures?.documents ?? []).map(summarize),
5539
6111
  moreFailures: !!failures?.nextCursor
5540
6112
  });
@@ -5550,16 +6122,7 @@ async function dataSourcesRm(ctx, a) {
5550
6122
  out({ deleted: documentId });
5551
6123
  return;
5552
6124
  }
5553
- let filter;
5554
- if (rawFilter.trimStart().startsWith("{")) {
5555
- try {
5556
- filter = JSON.parse(rawFilter);
5557
- } catch (err) {
5558
- fatal(`--filter is not valid JSON: ${err.message}`);
5559
- }
5560
- } else {
5561
- filter = { metadata: parseKeyValuePairs(rawFilter, "filter") };
5562
- }
6125
+ const filter = documentSelectorOf(rawFilter);
5563
6126
  const slug = sourceOf(a);
5564
6127
  const result = await rmWhereAll(ctx, {
5565
6128
  slug,
@@ -5614,6 +6177,40 @@ async function dataSourcesCount(ctx, a) {
5614
6177
  })
5615
6178
  );
5616
6179
  }
6180
+ async function dataSourcesEntities(ctx, a) {
6181
+ const filter = filterFromFlags(a);
6182
+ const limit = a.num("limit");
6183
+ out(
6184
+ await entities(ctx, {
6185
+ slug: sourceOf(a),
6186
+ ...filter ? { filter } : {},
6187
+ ...limit !== void 0 ? { limit } : {},
6188
+ ...a.bool("candidate") ? { candidate: true } : {}
6189
+ })
6190
+ );
6191
+ }
6192
+ async function dataSourcesPassages(ctx, a) {
6193
+ const filter = filterFromFlags(a);
6194
+ const limit = a.num("limit");
6195
+ const cursor = a.str("cursor");
6196
+ const result = await passages(ctx, {
6197
+ slug: sourceOf(a),
6198
+ ...filter ? { filter } : {},
6199
+ ...cursor ? { cursor } : {},
6200
+ ...limit !== void 0 ? { limit } : {},
6201
+ ...a.bool("candidate") ? { candidate: true } : {}
6202
+ });
6203
+ out({
6204
+ ...result,
6205
+ ...result.nextCursor ? { note: `More: pass --cursor ${result.nextCursor} for the next page.` } : {}
6206
+ });
6207
+ }
6208
+ function parseIdList(raw) {
6209
+ if (raw === void 0) {
6210
+ return void 0;
6211
+ }
6212
+ return raw.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
6213
+ }
5617
6214
  function filterFromFlags(a) {
5618
6215
  let filter = {};
5619
6216
  const raw = a.str("filter");
@@ -5631,6 +6228,10 @@ function filterFromFlags(a) {
5631
6228
  }
5632
6229
  }
5633
6230
  }
6231
+ const entities2 = parseIdList(a.str("entities"));
6232
+ if (entities2?.length) {
6233
+ filter.entities = entities2;
6234
+ }
5634
6235
  const phrase = a.str("phrase");
5635
6236
  if (phrase) {
5636
6237
  filter.phrase = phrase;
@@ -5641,29 +6242,6 @@ function filterFromFlags(a) {
5641
6242
  }
5642
6243
  return Object.keys(filter).length ? filter : void 0;
5643
6244
  }
5644
- function parseKeyValuePairs(raw, flag) {
5645
- if (raw === void 0) {
5646
- return void 0;
5647
- }
5648
- const pairs2 = {};
5649
- for (const part of raw.split(",")) {
5650
- const trimmed = part.trim();
5651
- if (!trimmed) {
5652
- continue;
5653
- }
5654
- const eq = trimmed.indexOf("=");
5655
- if (eq <= 0) {
5656
- fatal(`--${flag} entries must be key=value (got "${trimmed}").`);
5657
- }
5658
- const key = trimmed.slice(0, eq).trim();
5659
- const value = trimmed.slice(eq + 1).trim();
5660
- pairs2[key] = value === "true" ? true : value === "false" ? false : value !== "" && !Number.isNaN(Number(value)) ? Number(value) : value;
5661
- }
5662
- if (Object.keys(pairs2).length === 0) {
5663
- fatal(`--${flag} needs at least one key=value pair.`);
5664
- }
5665
- return pairs2;
5666
- }
5667
6245
  function triState2(a, flag, key) {
5668
6246
  const raw = a.str(flag);
5669
6247
  if (raw === void 0) {
@@ -5890,6 +6468,8 @@ var dataSourcesHandlers = {
5890
6468
  "datasources create": dataSourcesCreate,
5891
6469
  "datasources config": dataSourcesConfig,
5892
6470
  "datasources count": dataSourcesCount,
6471
+ "datasources entities": dataSourcesEntities,
6472
+ "datasources passages": dataSourcesPassages,
5893
6473
  "datasources move": dataSourcesMove,
5894
6474
  "datasources revectorize": dataSourcesRevectorize,
5895
6475
  "datasources promote": dataSourcesPromote,
@@ -5907,6 +6487,11 @@ Subcommands:
5907
6487
  status Show a source's counts and its failed documents (--all walks every document)
5908
6488
  rm Remove a document, or every document matching a filter
5909
6489
  search Query a corpus \u2014 useful to sanity-check one you just built
6490
+ count How many passages match a filter (words, a phrase, entities, metadata)
6491
+ passages Every passage matching a filter, unranked, a page at a time
6492
+ entities Which entities the matching passages are about, most first
6493
+ dictionary push|status: send a source its dictionary of names, or show the current one
6494
+ link What the source's dictionary finds in a piece of text
5910
6495
  create Create an empty source, optionally on dedicated capacity
5911
6496
  config Show or change how a corpus is processed and searched
5912
6497
  move Move a corpus between shared and dedicated capacity, data intact
@@ -5919,6 +6504,8 @@ Subcommands:
5919
6504
  map test Run a source's mapper over real objects and show the outcomes; nothing ingested
5920
6505
  map deploy Make a build's compiled mapper the source's active one (a branch build will do)
5921
6506
  remap Re-apply the active mapper to every raw copy the source holds
6507
+ reader test|status|retry: run the dev session's reader, see its deliveries, resend failed ones
6508
+ reread Read a source's documents again with its live reader, priced first
5922
6509
  hydrate Reload an evicted index ahead of the first search
5923
6510
  reindex Recreate a dedicated source's collection with the current shape, refilled from stored vectors
5924
6511
  sample Draw a sample of a source into a new one with the same config
@@ -5929,12 +6516,14 @@ Subcommands:
5929
6516
  delete Delete a whole data source \u2014 documents, vectors and versions
5930
6517
 
5931
6518
  Usage:
5932
- remy-admin datasources add [--source <slug>] [--metadata <k=v,...>] [--wait] [--timeout <sec>] <file...>
6519
+ remy-admin datasources add [--source <slug>] [--metadata <k=v,...>] [--entities <id,...>] [--wait] [--timeout <sec>] <file...>
5933
6520
  remy-admin datasources list
5934
6521
  remy-admin datasources status [--source <slug>] [--status error] [--all]
5935
6522
  remy-admin datasources rm [--source <slug>] (--document <id> | --filter <k=v,...|json>)
5936
6523
  remy-admin datasources search [--source <slug>] [search options] <query>
5937
- remy-admin datasources count [--source <slug>] [--filter <k=v,...|json>] [--contains <words>] [--phrase <text>] [--candidate]
6524
+ remy-admin datasources count [--source <slug>] [--filter <k=v,...|json>] [--entities <id,...>] [--contains <words>] [--phrase <text>] [--candidate]
6525
+ remy-admin datasources entities [--source <slug>] [--filter <k=v,...|json>] [--entities <id,...>] [--limit <n>] [--candidate]
6526
+ remy-admin datasources passages [--source <slug>] [--filter <k=v,...|json>] [--entities <id,...>] [--contains <words>] [--phrase <text>] [--cursor <c>] [--limit <n>] [--candidate]
5938
6527
  remy-admin datasources create --source <slug> [--name <name>] [--placement <resource-id|shared>] [rebuild settings...]
5939
6528
  remy-admin datasources config [--source <slug>] [--placement <resource-id|shared>] [settings...]
5940
6529
  remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--chains <n>] [--restart] [--wait] [--timeout <sec>]
@@ -5955,6 +6544,15 @@ Count:
5955
6544
  count (none exists), and not the set search returns. The SDK's
5956
6545
  \`Source.count(filter)\` is the same call for an app.
5957
6546
 
6547
+ Entities:
6548
+ A document can assert what it is about as your own ids (\`add --entities\`,
6549
+ a mapper's \`entities\`, a manifest line's \`entities\`), and every passage
6550
+ carries them. \`--entities a,b\` on search and count narrows to passages
6551
+ about any of them. \`entities\` lists which entities the matching passages
6552
+ are about, most first, with how many passages carry each (approximate, and
6553
+ passages, not documents): filter on one entity to see what appears beside
6554
+ it. The SDK's \`Source.entities({ filter, limit })\` is the same call.
6555
+
5958
6556
  Search options:
5959
6557
  --top-k <n> Results to return (default 5, max 50)
5960
6558
  --mode <m> hybrid (default) | semantic | lexical. Lexical is
@@ -5963,11 +6561,12 @@ Search options:
5963
6561
  --filter <k=v,...> Match document metadata set at add time. Values
5964
6562
  coerce: true/false and numbers become typed.
5965
6563
  Pass a JSON object for the full grammar:
5966
- metadata, filename, documentIds, pages, contains,
5967
- phrase \u2014 e.g. --filter '{"pages":{"max":3}}'.
6564
+ metadata, filename, documentIds, entities, pages,
6565
+ contains, phrase \u2014 e.g. --filter '{"pages":{"max":3}}'.
5968
6566
  Metadata takes numeric ranges via {gte,lte}: store
5969
6567
  dates as integers (YYYYMMDD or epoch seconds), then
5970
6568
  --filter '{"metadata":{"date":{"gte":20250101}}}'
6569
+ --entities <id,...> Chunk must be about any of these entities
5971
6570
  --phrase <text> Chunk must contain this exact word sequence
5972
6571
  --contains <words> Chunk must contain ALL these words, any order
5973
6572
  --max-per-document <n> Cap hits per document; backfills from others
@@ -6056,6 +6655,8 @@ Notes:
6056
6655
  Re-adding an unchanged file is free: no upload, no re-embedding. Re-adding
6057
6656
  with a different --metadata updates the tags in place, also free.
6058
6657
  --metadata tags documents for search-time filtering (scalars, \u226416 keys).
6658
+ --entities names what a document is about, as your own ids (\u2264256, no spaces
6659
+ or commas); an empty --entities clears them. Also free on a re-add.
6059
6660
  rm --filter removes every matching document in pages of a thousand: k=v
6060
6661
  pairs match metadata; a JSON object takes the full selector \u2014 metadata,
6061
6662
  filename, documentIds, externalIdPrefix (the key a job or connector
@@ -6095,7 +6696,7 @@ Examples:
6095
6696
  remy-admin datasources revectorize --source archive-sample --max-chars 900 --wait
6096
6697
  remy-admin datasources eval run --set <setId> --candidate --label small-chunks --wait
6097
6698
  remy-admin datasources eval compare <runA> <runB>
6098
- ${dataSourceJobsHelp}${dataSourceConnectorsHelp}${dataSourceMappersHelp}${dataSourceEvalsHelp}`;
6699
+ ${dataSourceJobsHelp}${dataSourceConnectorsHelp}${dataSourceMappersHelp}${dataSourceReadersHelp}${dataSourceEvalsHelp}${dataSourceDictionaryHelp}`;
6099
6700
 
6100
6701
  // src/ops/voice.ts
6101
6702
  function normalizeE164(input) {
@@ -7932,21 +8533,21 @@ Examples:
7932
8533
  // src/ops/infra.ts
7933
8534
  var DEFAULT_WAIT_TIMEOUT_MS4 = 10 * 60 * 1e3;
7934
8535
  var POLL_MS5 = 3e3;
7935
- function base6(appId) {
8536
+ function base7(appId) {
7936
8537
  return `/_internal/v2/apps/${appId}/infra`;
7937
8538
  }
7938
8539
  function list14(ctx, params) {
7939
8540
  return call(
7940
8541
  ctx,
7941
8542
  "GET",
7942
- `${base6(ctx.appId)}${params?.includeDestroyed ? "?includeDestroyed=true" : ""}`
8543
+ `${base7(ctx.appId)}${params?.includeDestroyed ? "?includeDestroyed=true" : ""}`
7943
8544
  );
7944
8545
  }
7945
8546
  function get12(ctx, params) {
7946
8547
  return call(
7947
8548
  ctx,
7948
8549
  "GET",
7949
- `${base6(ctx.appId)}/${encodeURIComponent(params.id)}`
8550
+ `${base7(ctx.appId)}/${encodeURIComponent(params.id)}`
7950
8551
  );
7951
8552
  }
7952
8553
  function logs(ctx, params) {
@@ -7961,11 +8562,11 @@ function logs(ctx, params) {
7961
8562
  return call(
7962
8563
  ctx,
7963
8564
  "GET",
7964
- `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/logs${search2 ? `?${search2}` : ""}`
8565
+ `${base7(ctx.appId)}/${encodeURIComponent(params.id)}/logs${search2 ? `?${search2}` : ""}`
7965
8566
  );
7966
8567
  }
7967
8568
  function provision(ctx, params) {
7968
- return call(ctx, "POST", base6(ctx.appId), {
8569
+ return call(ctx, "POST", base7(ctx.appId), {
7969
8570
  offeringId: params.offeringId,
7970
8571
  ...params.name ? { name: params.name } : {}
7971
8572
  });
@@ -7974,28 +8575,28 @@ function hibernate(ctx, params) {
7974
8575
  return call(
7975
8576
  ctx,
7976
8577
  "POST",
7977
- `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/hibernate`
8578
+ `${base7(ctx.appId)}/${encodeURIComponent(params.id)}/hibernate`
7978
8579
  );
7979
8580
  }
7980
8581
  function resume2(ctx, params) {
7981
8582
  return call(
7982
8583
  ctx,
7983
8584
  "POST",
7984
- `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/resume`
8585
+ `${base7(ctx.appId)}/${encodeURIComponent(params.id)}/resume`
7985
8586
  );
7986
8587
  }
7987
8588
  function destroy(ctx, params) {
7988
8589
  return call(
7989
8590
  ctx,
7990
8591
  "POST",
7991
- `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/destroy`
8592
+ `${base7(ctx.appId)}/${encodeURIComponent(params.id)}/destroy`
7992
8593
  );
7993
8594
  }
7994
8595
  function resize(ctx, params) {
7995
8596
  return call(
7996
8597
  ctx,
7997
8598
  "POST",
7998
- `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/resize`,
8599
+ `${base7(ctx.appId)}/${encodeURIComponent(params.id)}/resize`,
7999
8600
  { offeringId: params.offeringId }
8000
8601
  );
8001
8602
  }
@@ -8003,7 +8604,7 @@ function rename(ctx, params) {
8003
8604
  return call(
8004
8605
  ctx,
8005
8606
  "POST",
8006
- `${base6(ctx.appId)}/${encodeURIComponent(params.id)}/rename`,
8607
+ `${base7(ctx.appId)}/${encodeURIComponent(params.id)}/rename`,
8007
8608
  { name: params.name }
8008
8609
  );
8009
8610
  }
@@ -8572,7 +9173,9 @@ var SPECS = {
8572
9173
  ...dataSourceJobsSpecs,
8573
9174
  ...dataSourceConnectorsSpecs,
8574
9175
  ...dataSourceEvalsSpecs,
9176
+ ...dataSourceDictionarySpecs,
8575
9177
  ...dataSourceMappersSpecs,
9178
+ ...dataSourceReadersSpecs,
8576
9179
  ...voiceSpecs,
8577
9180
  ...agentSpecs,
8578
9181
  ...tasksSpecs,
@@ -8603,7 +9206,9 @@ var HANDLERS = {
8603
9206
  ...dataSourceJobsHandlers,
8604
9207
  ...dataSourceConnectorsHandlers,
8605
9208
  ...dataSourceEvalsHandlers,
9209
+ ...dataSourceDictionaryHandlers,
8606
9210
  ...dataSourceMappersHandlers,
9211
+ ...dataSourceReadersHandlers,
8607
9212
  ...voiceHandlers,
8608
9213
  ...agentHandlers,
8609
9214
  ...tasksHandlers,
@@ -8644,8 +9249,8 @@ var GROUP_HELP = {
8644
9249
  };
8645
9250
 
8646
9251
  // src/ctx.ts
8647
- import fs6 from "fs";
8648
- import path6 from "path";
9252
+ import fs7 from "fs";
9253
+ import path7 from "path";
8649
9254
 
8650
9255
  // src/parseJsonConfig.ts
8651
9256
  import JSON5 from "json5";
@@ -8674,10 +9279,10 @@ function parseJsonConfig(raw) {
8674
9279
  // src/ctx.ts
8675
9280
  var DEFAULT_BASE_URL = "https://api.mindstudio.ai";
8676
9281
  function loadWorkspaceAppId(workspaceDir = WORKSPACE_DIR) {
8677
- const manifestPath = path6.join(workspaceDir, "mindstudio.json");
9282
+ const manifestPath = path7.join(workspaceDir, "mindstudio.json");
8678
9283
  let raw;
8679
9284
  try {
8680
- raw = fs6.readFileSync(manifestPath, "utf-8");
9285
+ raw = fs7.readFileSync(manifestPath, "utf-8");
8681
9286
  } catch (err) {
8682
9287
  if (err.code === "ENOENT") {
8683
9288
  throw new Error(`mindstudio.json not found at ${manifestPath}`);
@@ -8775,32 +9380,60 @@ function resolveRoute(argv) {
8775
9380
  if (sub && KEYS.has(two)) {
8776
9381
  return { key: two, argv: argv.slice(2) };
8777
9382
  }
8778
- if (group === "domains" && sub === "custom") {
8779
- return {
8780
- error: `Unknown subcommand: domains custom ${action ?? ""}. Run 'remy-admin domains --help'`
8781
- };
9383
+ if (group === "db" && sub) {
9384
+ return { key: "db query", argv: argv.slice(1) };
8782
9385
  }
8783
- if (group === "settings" && ["allowlist", "test-accounts", "frame-ancestors"].includes(sub ?? "")) {
8784
- return {
8785
- error: `Unknown subcommand: settings ${sub} ${action ?? ""}. Run 'remy-admin settings --help'`
8786
- };
9386
+ return { error: unknownSubcommand(group, sub, action) };
9387
+ }
9388
+ function keysUnder(words) {
9389
+ const prefix = `${words.join(" ")} `;
9390
+ return [...KEYS].filter((key) => key.startsWith(prefix)).sort();
9391
+ }
9392
+ function unknownSubcommand(group, sub, action) {
9393
+ const asked = [group, sub, action].filter(Boolean).join(" ");
9394
+ const nested = sub ? keysUnder([group, sub]) : [];
9395
+ if (nested.length > 0) {
9396
+ const actions = nested.map((key) => key.split(" ")[2]);
9397
+ return `Unknown subcommand: ${asked}. '${group} ${sub}' takes: ${actions.join(", ")}. Run 'remy-admin ${group} ${sub} --help' for their usage.`;
8787
9398
  }
8788
- if (group === "datasources" && (sub === "jobs" || sub === "eval" || sub === "map")) {
8789
- return {
8790
- error: `Unknown subcommand: datasources ${sub} ${action ?? ""}. Run 'remy-admin datasources --help'`
8791
- };
9399
+ const subs = [...new Set(keysUnder([group]).map((key) => key.split(" ")[1]))];
9400
+ return `Unknown subcommand: ${asked}. '${group}' takes: ${subs.join(", ")}. Run 'remy-admin ${group} --help'.`;
9401
+ }
9402
+ function leadingWords(argv) {
9403
+ const words = [];
9404
+ for (const token of argv) {
9405
+ if (token.startsWith("-")) {
9406
+ break;
9407
+ }
9408
+ words.push(token);
8792
9409
  }
8793
- if (group === "email" && ["domains", "inbound-domains"].includes(sub ?? "")) {
8794
- return {
8795
- error: `Unknown subcommand: email ${sub} ${action ?? ""}. Run 'remy-admin email --help'`
8796
- };
9410
+ return words;
9411
+ }
9412
+ function commandHelp(argv) {
9413
+ const words = leadingWords(argv);
9414
+ const group = words[0];
9415
+ const guide = `Run 'remy-admin ${group} --help' for the ${group} guide.`;
9416
+ for (let n = Math.min(words.length, 3); n >= 2; n--) {
9417
+ const key = words.slice(0, n).join(" ");
9418
+ if (KEYS.has(key)) {
9419
+ return `${SPECS[key].usage}
9420
+
9421
+ ${guide}`;
9422
+ }
8797
9423
  }
8798
- if (group === "db" && sub) {
8799
- return { key: "db query", argv: argv.slice(1) };
9424
+ if (words.length >= 2) {
9425
+ const under = keysUnder(words.slice(0, 2));
9426
+ if (under.length > 0) {
9427
+ const usages = under.map(
9428
+ (key) => ` ${SPECS[key].usage.replace(/^Usage:\s*/, "")}`
9429
+ );
9430
+ return `Usage:
9431
+ ${usages.join("\n")}
9432
+
9433
+ ${guide}`;
9434
+ }
8800
9435
  }
8801
- return {
8802
- error: `Unknown subcommand: ${group} ${sub ?? ""}. Run 'remy-admin ${group} --help'`
8803
- };
9436
+ return void 0;
8804
9437
  }
8805
9438
  function resolveHelp(argv) {
8806
9439
  const group = argv[0];
@@ -8815,7 +9448,10 @@ function resolveHelp(argv) {
8815
9448
  return groupHelp;
8816
9449
  }
8817
9450
  const scan = group === "db" ? argv.slice(1, argv[1] === "query" ? 3 : 2) : argv.slice(1);
8818
- return scan.some((t) => t === "--help" || t === "-h") ? groupHelp : void 0;
9451
+ if (!scan.some((t) => t === "--help" || t === "-h")) {
9452
+ return void 0;
9453
+ }
9454
+ return commandHelp(argv) ?? groupHelp;
8819
9455
  }
8820
9456
 
8821
9457
  // src/prod.ts