@madewithremy/admin 0.1.31 → 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
@@ -2946,7 +2946,7 @@ var filesSpecs = {
2946
2946
  usage: "Usage: remy-admin files list"
2947
2947
  },
2948
2948
  "files rm": {
2949
- usage: "Usage: remy-admin files rm --store <name> --key <key> [--private]",
2949
+ usage: "Usage: remy-admin files rm --store <name> --key <key> [--public|--private]",
2950
2950
  flags: {
2951
2951
  ...ACCESS_FLAGS,
2952
2952
  store: { type: "string" },
@@ -2957,6 +2957,45 @@ var filesSpecs = {
2957
2957
  function resolveAccess(a) {
2958
2958
  return a.bool("private") ? "private" : "public";
2959
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
+ }
2960
2999
  function storeOf(a) {
2961
3000
  return a.str("store") || DEFAULT_STORE;
2962
3001
  }
@@ -2967,8 +3006,8 @@ function requireKeyFlag(a) {
2967
3006
  }
2968
3007
  return key;
2969
3008
  }
2970
- function refOf(a, key) {
2971
- 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 };
2972
3011
  }
2973
3012
  async function filesPut(ctx, a) {
2974
3013
  const file = a.req("file");
@@ -2994,7 +3033,7 @@ async function filesPut(ctx, a) {
2994
3033
  }
2995
3034
  async function filesGet(ctx, a) {
2996
3035
  const key = a.req("key");
2997
- const ref = refOf(a, key);
3036
+ const ref = await refOf(ctx, a, key);
2998
3037
  const { url } = await sign(
2999
3038
  ctx,
3000
3039
  ref,
@@ -3004,7 +3043,9 @@ async function filesGet(ctx, a) {
3004
3043
  const abs = path2.isAbsolute(outPath) ? outPath : path2.join(WORKSPACE_DIR, outPath);
3005
3044
  const res = await fetch(url);
3006
3045
  if (!res.ok || !res.body) {
3007
- 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
+ );
3008
3049
  }
3009
3050
  await pipeline(
3010
3051
  Readable.fromWeb(res.body),
@@ -3013,16 +3054,18 @@ async function filesGet(ctx, a) {
3013
3054
  out({ path: abs, size: fs2.statSync(abs).size });
3014
3055
  }
3015
3056
  async function filesSign(ctx, a) {
3016
- 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
+ );
3017
3060
  }
3018
3061
  async function filesStat(ctx, a) {
3019
- out(await stat(ctx, refOf(a, requireKeyFlag(a))));
3062
+ out(await stat(ctx, await refOf(ctx, a, requireKeyFlag(a))));
3020
3063
  }
3021
3064
  async function filesLs(ctx, a) {
3022
3065
  out(
3023
3066
  await ls(ctx, {
3024
3067
  store: storeOf(a),
3025
- access: resolveAccess(a),
3068
+ access: await readAccess(ctx, a),
3026
3069
  prefix: a.str("prefix"),
3027
3070
  q: a.str("q"),
3028
3071
  cursor: a.str("cursor"),
@@ -3042,7 +3085,13 @@ async function filesRm(ctx, a) {
3042
3085
  if (!key) {
3043
3086
  fatal("--key is required.");
3044
3087
  }
3045
- 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
+ );
3046
3095
  }
3047
3096
  var filesHandlers = {
3048
3097
  "files put": filesPut,
@@ -3066,12 +3115,12 @@ Subcommands:
3066
3115
 
3067
3116
  Usage:
3068
3117
  remy-admin files put [--public|--private] [--store <name>] [--key <key>] [--cache-control <value>] <file>
3069
- remy-admin files get [--private] [--store <name>] [--out <path>] <key>
3070
- remy-admin files sign [--private] [--store <name>] [--ttl <seconds>] --key <key>
3071
- remy-admin files stat [--private] [--store <name>] --key <key>
3072
- 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>]
3073
3122
  remy-admin files list
3074
- remy-admin files rm --store <name> --key <key> [--private]
3123
+ remy-admin files rm --store <name> --key <key> [--public|--private]
3075
3124
 
3076
3125
  Examples:
3077
3126
  remy-admin files put --public ./hero.jpg
@@ -3081,7 +3130,10 @@ Examples:
3081
3130
  remy-admin files rm --store handoff --key <key> --private
3082
3131
 
3083
3132
  Notes:
3084
- - 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.
3085
3137
  - Handing someone a large or sensitive file? Use a PRIVATE store + \`sign\`:
3086
3138
  the link is unguessable, expires (--ttl seconds, max 7 days), and the object
3087
3139
  stays deletable with \`rm\`. Never use the account media CDN
@@ -3347,6 +3399,21 @@ function entities(ctx, params) {
3347
3399
  }
3348
3400
  );
3349
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
+ }
3350
3417
  function configGet(ctx, slug) {
3351
3418
  return call(
3352
3419
  ctx,
@@ -4870,12 +4937,14 @@ Dictionary:
4870
4937
  background (a relink; \`dictionary status\` shows it), with nothing
4871
4938
  re-embedded. An unchanged dictionary commits nothing.
4872
4939
 
4873
- Matching ignores case and a tag's separators (P-101A, P101A, p 101a), takes
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
4874
4942
  whole words only (P-101AB is not P-101A), and prefers the longest name; a
4875
4943
  name two ids claim tags both. A name needs three letters or digits (two in
4876
4944
  Chinese, Japanese or Korean). The push reports ambiguous names, the most
4877
- claimed, and names that can't match their own text as written
4878
- (\`unmatchable\`). Up to 250,000 entries, 16 names each, a million names.
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.
4879
4948
 
4880
4949
  \`link\` shows what the current dictionary finds in any text: check one
4881
4950
  there before relying on it. --linker-version moves a dictionary to a newer
@@ -5179,7 +5248,8 @@ function mapTest(ctx, params) {
5179
5248
  ...selectionBody(params.selection),
5180
5249
  ...params.keys ? { keys: params.keys } : {},
5181
5250
  ...params.limit !== void 0 ? { limit: params.limit } : {},
5182
- ...params.dev ? { dev: true } : {}
5251
+ ...params.dev ? { dev: true } : {},
5252
+ ...params.releaseId ? { releaseId: params.releaseId } : {}
5183
5253
  }
5184
5254
  );
5185
5255
  }
@@ -5245,12 +5315,13 @@ var dataSourceMappersSpecs = {
5245
5315
  }
5246
5316
  },
5247
5317
  "datasources map test": {
5248
- 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]",
5249
5319
  flags: {
5250
5320
  source: { type: "string" },
5251
5321
  ...SELECTION_FLAGS,
5252
5322
  limit: { type: "number", min: 1 },
5253
5323
  dev: { type: "boolean" },
5324
+ release: { type: "string" },
5254
5325
  full: { type: "boolean" }
5255
5326
  },
5256
5327
  requireAnyOf: {
@@ -5362,7 +5433,8 @@ async function mapTest2(ctx, a) {
5362
5433
  selection: selectionOf(a),
5363
5434
  ...keysOf(a) ? { keys: keysOf(a) } : {},
5364
5435
  ...limit !== void 0 ? { limit } : {},
5365
- ...a.bool("dev") ? { dev: true } : {}
5436
+ ...a.bool("dev") ? { dev: true } : {},
5437
+ ...a.str("release") ? { releaseId: a.str("release") } : {}
5366
5438
  });
5367
5439
  const results = a.bool("full") ? result.results : result.results.map(
5368
5440
  (r) => r.outcome.kind === "documents" ? {
@@ -5496,7 +5568,7 @@ var dataSourceMappersHandlers = {
5496
5568
  var dataSourceMappersHelp = `
5497
5569
  Mapping raw objects into documents (mappers):
5498
5570
  remy-admin datasources inspect [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>]
5499
- 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]
5500
5572
  remy-admin datasources map deploy [--source <slug>] [--release <id>]
5501
5573
  remy-admin datasources remap [--source <slug>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]
5502
5574
  remy-admin datasources jobs quarantine <id> [--kind skip|error] [--stage map|read] [--reason <text>] [--cursor <id>] [--limit <n>]
@@ -5552,7 +5624,8 @@ function readerTest(ctx, params) {
5552
5624
  {
5553
5625
  slug: params.slug,
5554
5626
  ...params.documents ? { documents: params.documents } : {},
5555
- ...params.limit !== void 0 ? { limit: params.limit } : {}
5627
+ ...params.limit !== void 0 ? { limit: params.limit } : {},
5628
+ ...params.releaseId ? { releaseId: params.releaseId } : {}
5556
5629
  }
5557
5630
  );
5558
5631
  }
@@ -5593,12 +5666,13 @@ function reread(ctx, params) {
5593
5666
  // src/commands/dataSourceReaders.ts
5594
5667
  var dataSourceReadersSpecs = {
5595
5668
  "datasources reader test": {
5596
- usage: "Usage: remy-admin datasources reader test --dev [--source <slug>] [--documents id1,id2,...] [--limit <n>]",
5669
+ usage: "Usage: remy-admin datasources reader test --dev [--source <slug>] [--documents id1,id2,...] [--limit <n>] [--release <dev-release-id>]",
5597
5670
  flags: {
5598
5671
  source: { type: "string" },
5599
5672
  dev: { type: "boolean" },
5600
5673
  documents: { type: "string" },
5601
- limit: { type: "number", min: 1 }
5674
+ limit: { type: "number", min: 1 },
5675
+ release: { type: "string" }
5602
5676
  }
5603
5677
  },
5604
5678
  "datasources reader status": {
@@ -5643,7 +5717,8 @@ async function readerTest2(ctx, a) {
5643
5717
  const result = await readerTest(ctx, {
5644
5718
  slug,
5645
5719
  ...documents2 ? { documents: documents2 } : {},
5646
- ...limit !== void 0 ? { limit } : {}
5720
+ ...limit !== void 0 ? { limit } : {},
5721
+ ...a.str("release") ? { releaseId: a.str("release") } : {}
5647
5722
  });
5648
5723
  out({
5649
5724
  dataSource: slug,
@@ -5714,7 +5789,7 @@ var dataSourceReadersHandlers = {
5714
5789
  };
5715
5790
  var dataSourceReadersHelp = `
5716
5791
  Reading documents into the app (readers):
5717
- remy-admin datasources reader test --dev [--source <slug>] [--documents id1,id2] [--limit <n>]
5792
+ remy-admin datasources reader test --dev [--source <slug>] [--documents id1,id2] [--limit <n>] [--release <id>]
5718
5793
  remy-admin datasources reader status [--source <slug>]
5719
5794
  remy-admin datasources reader retry [--source <slug>] [--job <id>]
5720
5795
  remy-admin datasources reread [--source <slug>] [--unread] [--filter <k=v|json>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]
@@ -5725,12 +5800,15 @@ Reading documents into the app (readers):
5725
5800
  platform's batching, retries, budgets and pauses. Declare it in
5726
5801
  mindstudio.json:
5727
5802
  "dataSources": [{ "slug": "archive", "reader": { "path": "dist/datasources/archive.reader.ts" } }]
5728
- The file exports defineReader(Archive, { read, removed, cleared? }) from
5729
- @mindstudio-ai/agent. read(document) gets the document (id, sourceId,
5730
- filename, metadata, entities) and document.chunks(), its passages as
5731
- indexed with each one's entities; removed(documentId) is called when a
5732
- document leaves the source; cleared(sourceId) once when the source is
5733
- deleted. Throwing fails that document, which is retried. A document can be
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
5734
5812
  read more than once, so read() replaces the document's rows as a set,
5735
5813
  keyed by its id, and removed() deletes them. Options on the declaration:
5736
5814
  timeoutMs (per document, default 60000, max 300000) and concurrency
@@ -5885,6 +5963,19 @@ var dataSourcesSpecs = {
5885
5963
  candidate: { type: "boolean" }
5886
5964
  }
5887
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]",
5968
+ flags: {
5969
+ source: { type: "string" },
5970
+ filter: { type: "string" },
5971
+ entities: { type: "string" },
5972
+ contains: { type: "string" },
5973
+ phrase: { type: "string" },
5974
+ cursor: { type: "string" },
5975
+ limit: { type: "number", min: 1 },
5976
+ candidate: { type: "boolean" }
5977
+ }
5978
+ },
5888
5979
  "datasources move": {
5889
5980
  usage: "Usage: remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--chains <n>] [--restart] [--wait] [--timeout <sec>] | --stop",
5890
5981
  flags: {
@@ -6098,6 +6189,22 @@ async function dataSourcesEntities(ctx, a) {
6098
6189
  })
6099
6190
  );
6100
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
+ }
6101
6208
  function parseIdList(raw) {
6102
6209
  if (raw === void 0) {
6103
6210
  return void 0;
@@ -6362,6 +6469,7 @@ var dataSourcesHandlers = {
6362
6469
  "datasources config": dataSourcesConfig,
6363
6470
  "datasources count": dataSourcesCount,
6364
6471
  "datasources entities": dataSourcesEntities,
6472
+ "datasources passages": dataSourcesPassages,
6365
6473
  "datasources move": dataSourcesMove,
6366
6474
  "datasources revectorize": dataSourcesRevectorize,
6367
6475
  "datasources promote": dataSourcesPromote,
@@ -6379,6 +6487,11 @@ Subcommands:
6379
6487
  status Show a source's counts and its failed documents (--all walks every document)
6380
6488
  rm Remove a document, or every document matching a filter
6381
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
6382
6495
  create Create an empty source, optionally on dedicated capacity
6383
6496
  config Show or change how a corpus is processed and searched
6384
6497
  move Move a corpus between shared and dedicated capacity, data intact
@@ -6391,6 +6504,8 @@ Subcommands:
6391
6504
  map test Run a source's mapper over real objects and show the outcomes; nothing ingested
6392
6505
  map deploy Make a build's compiled mapper the source's active one (a branch build will do)
6393
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
6394
6509
  hydrate Reload an evicted index ahead of the first search
6395
6510
  reindex Recreate a dedicated source's collection with the current shape, refilled from stored vectors
6396
6511
  sample Draw a sample of a source into a new one with the same config
@@ -6408,6 +6523,7 @@ Usage:
6408
6523
  remy-admin datasources search [--source <slug>] [search options] <query>
6409
6524
  remy-admin datasources count [--source <slug>] [--filter <k=v,...|json>] [--entities <id,...>] [--contains <words>] [--phrase <text>] [--candidate]
6410
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]
6411
6527
  remy-admin datasources create --source <slug> [--name <name>] [--placement <resource-id|shared>] [rebuild settings...]
6412
6528
  remy-admin datasources config [--source <slug>] [--placement <resource-id|shared>] [settings...]
6413
6529
  remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--chains <n>] [--restart] [--wait] [--timeout <sec>]
@@ -9264,32 +9380,60 @@ function resolveRoute(argv) {
9264
9380
  if (sub && KEYS.has(two)) {
9265
9381
  return { key: two, argv: argv.slice(2) };
9266
9382
  }
9267
- if (group === "domains" && sub === "custom") {
9268
- return {
9269
- error: `Unknown subcommand: domains custom ${action ?? ""}. Run 'remy-admin domains --help'`
9270
- };
9383
+ if (group === "db" && sub) {
9384
+ return { key: "db query", argv: argv.slice(1) };
9271
9385
  }
9272
- if (group === "settings" && ["allowlist", "test-accounts", "frame-ancestors"].includes(sub ?? "")) {
9273
- return {
9274
- error: `Unknown subcommand: settings ${sub} ${action ?? ""}. Run 'remy-admin settings --help'`
9275
- };
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.`;
9276
9398
  }
9277
- if (group === "datasources" && (sub === "jobs" || sub === "eval" || sub === "map")) {
9278
- return {
9279
- error: `Unknown subcommand: datasources ${sub} ${action ?? ""}. Run 'remy-admin datasources --help'`
9280
- };
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);
9281
9409
  }
9282
- if (group === "email" && ["domains", "inbound-domains"].includes(sub ?? "")) {
9283
- return {
9284
- error: `Unknown subcommand: email ${sub} ${action ?? ""}. Run 'remy-admin email --help'`
9285
- };
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
+ }
9286
9423
  }
9287
- if (group === "db" && sub) {
9288
- 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
+ }
9289
9435
  }
9290
- return {
9291
- error: `Unknown subcommand: ${group} ${sub ?? ""}. Run 'remy-admin ${group} --help'`
9292
- };
9436
+ return void 0;
9293
9437
  }
9294
9438
  function resolveHelp(argv) {
9295
9439
  const group = argv[0];
@@ -9304,7 +9448,10 @@ function resolveHelp(argv) {
9304
9448
  return groupHelp;
9305
9449
  }
9306
9450
  const scan = group === "db" ? argv.slice(1, argv[1] === "query" ? 3 : 2) : argv.slice(1);
9307
- 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;
9308
9455
  }
9309
9456
 
9310
9457
  // src/prod.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madewithremy/admin",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Admin SDK + CLI for managing production Remy apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",