@simplepush/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.mjs CHANGED
@@ -13,6 +13,9 @@ import { Client, DownloadError, OrgClient, TypeFilter, decryptSubmission, decryp
13
13
  import { mkdir, stat, writeFile } from "node:fs/promises";
14
14
  import { connect, createConnection } from "node:net";
15
15
  import path, { basename, extname, join } from "node:path";
16
+ //#region package.json
17
+ var version = "0.5.0";
18
+ //#endregion
16
19
  //#region src/errors.ts
17
20
  /** No CLI session saved — `sp auth login` hasn't been run (or was logged out). */
18
21
  var NotLoggedIn = class extends Data.TaggedError("NotLoggedIn") {};
@@ -1887,7 +1890,7 @@ const formatOption$4 = Options.choice("format", ["json", "pretty"]).pipe(Options
1887
1890
  const sinceOption$1 = mappedText("since", resolveSince).pipe(Options.optional, Options.withDescription("Only items created at or after this instant: ISO-8601, or a relative window like 7d, 12h, 30m."));
1888
1891
  const untilOption$1 = mappedText("until", (raw) => resolveUntil(raw).toISOString()).pipe(Options.optional, Options.withDescription("Only items created at or before this instant (same forms as --since)."));
1889
1892
  const memberOption$3 = Options.text("member").pipe(Options.optional, Options.withDescription("Only items involving this person: a usr_ id, or a member name on an org."));
1890
- const limitOption$1 = Options.integer("limit").pipe(Options.optional, Options.withDescription("Page size (server default 50). With --all this is the page size, not a cap."));
1893
+ const limitOption$2 = Options.integer("limit").pipe(Options.optional, Options.withDescription("Page size (server default 50). With --all this is the page size, not a cap."));
1891
1894
  const cursorOption = Options.text("cursor").pipe(Options.optional, Options.withDescription("Continue a previous page: pass the cursor it reported."));
1892
1895
  const allOption = Options.boolean("all").pipe(Options.withDescription("Follow cursors until the last page instead of printing one page and a cursor."));
1893
1896
  const STATUSES = [
@@ -2119,7 +2122,7 @@ function extractRaw(v) {
2119
2122
  const eventTypeOption = Options.text("type").pipe(Options.withDescription("Filter by event type. Repeatable."), Options.repeated);
2120
2123
  const sinceOption = mappedText("since", resolveSince).pipe(Options.withDescription("Replay from this point. Accepts `24h`, `7d`, or an ISO 8601 timestamp."), Options.optional);
2121
2124
  const untilOption = mappedText("until", resolveUntil).pipe(Options.withDescription("Stop at this timestamp. Forces a finite range, so `--follow` is ignored."), Options.optional);
2122
- const limitOption = Options.integer("limit").pipe(Options.withDescription("Maximum number of events to print, then exit."), Options.optional);
2125
+ const limitOption$1 = Options.integer("limit").pipe(Options.withDescription("Maximum number of events to print, then exit."), Options.optional);
2123
2126
  const followOption = Options.boolean("follow").pipe(Options.withAlias("f"), Options.withDescription("After history is drained, keep streaming live instead of exiting. Only meaningful with `--since`."));
2124
2127
  const memberOption$2 = Options.text("member").pipe(Options.optional, Options.withDescription("Only events by this person: a usr_ id, or the actor's name."));
2125
2128
  const formatOption$3 = Options.choice("format", [
@@ -2134,7 +2137,7 @@ const eventsCommand = Command.make("events", {
2134
2137
  member: memberOption$2,
2135
2138
  since: sinceOption,
2136
2139
  until: untilOption,
2137
- limit: limitOption,
2140
+ limit: limitOption$1,
2138
2141
  follow: followOption,
2139
2142
  format: formatOption$3,
2140
2143
  direct: directOption,
@@ -2417,7 +2420,7 @@ const getCommand = Command.make("get", {
2417
2420
  topic: topicOption,
2418
2421
  member: memberOption$3,
2419
2422
  group: groupOption,
2420
- limit: limitOption$1,
2423
+ limit: limitOption$2,
2421
2424
  cursor: cursorOption,
2422
2425
  all: allOption,
2423
2426
  format: formatOption$4,
@@ -2432,6 +2435,100 @@ const getCommand = Command.make("get", {
2432
2435
  return Effect.fail(new UserError({ message: `cannot read '${args.what}': ${hint}` }));
2433
2436
  })).pipe(Command.withDescription("Read what came back: 'tasks' or 'submissions' for filtered listings, a tsk_ id for one task with its subtasks, a sub_ id for one subtask, a grptsk_ id for a group's per-recipient roster."));
2434
2437
  //#endregion
2438
+ //#region src/commands/search.ts
2439
+ const KINDS = [
2440
+ "task",
2441
+ "subtask",
2442
+ "answer",
2443
+ "reply",
2444
+ "notification",
2445
+ "notification_answer",
2446
+ "submission"
2447
+ ];
2448
+ const kindOption = Options.text("kind").pipe(Options.repeated, Options.mapTryCatch((values) => {
2449
+ const flat = values.flatMap((v) => v.split(",")).map((s) => s.trim()).filter((s) => s.length > 0);
2450
+ const bad = flat.filter((s) => !KINDS.includes(s));
2451
+ if (bad.length > 0) throw new Error(`unknown kind ${bad.join(", ")} — use ${KINDS.join(", ")}`);
2452
+ return flat;
2453
+ }, (e) => HelpDoc.p(e instanceof Error ? e.message : String(e))), Options.withDescription("Only these hit kinds (repeatable or comma-separated)."));
2454
+ function parsePoint(raw) {
2455
+ const parts = raw.split(",");
2456
+ if (parts.length === 2) {
2457
+ const latitude = Number(parts[0].trim());
2458
+ const longitude = Number(parts[1].trim());
2459
+ if (Number.isFinite(latitude) && Number.isFinite(longitude) && Math.abs(latitude) <= 90 && Math.abs(longitude) <= 180) return {
2460
+ latitude,
2461
+ longitude
2462
+ };
2463
+ }
2464
+ throw new Error(`expected \`lat,lng\` with lat in [-90,90] and lng in [-180,180], got '${raw}'`);
2465
+ }
2466
+ const nearOption = Options.text("near").pipe(Options.mapTryCatch(parsePoint, (e) => HelpDoc.p(e instanceof Error ? e.message : String(e))), Options.optional, Options.withDescription("Center of a radius filter as `lat,lng`; requires --radius. Hits come back with their point and its distance, nearest first when no words are given."));
2467
+ const radiusOption = Options.integer("radius").pipe(Options.optional, Options.withDescription("Radius around --near, in meters (1..1000000)."));
2468
+ const withinOption = Options.text("within").pipe(Options.mapTryCatch((raw) => {
2469
+ const points = raw.split(";").map((s) => s.trim()).filter((s) => s.length > 0).map(parsePoint);
2470
+ if (points.length < 3) throw new Error("--within needs at least 3 `lat,lng` points separated by `;`");
2471
+ return points;
2472
+ }, (e) => HelpDoc.p(e instanceof Error ? e.message : String(e))), Options.optional, Options.withDescription("Polygon to search inside: `lat,lng` corners joined with `;` (at least 3). Not combinable with --near/--radius; hits carry no distance and come newest first without words."));
2473
+ const limitOption = Options.integer("limit").pipe(Options.optional, Options.withDescription("Best hits to return; server default 20, at most 100."));
2474
+ /** One block per hit: id, kind, when, then whatever the hit carries. */
2475
+ function formatHit(h) {
2476
+ return [
2477
+ `${h.ref} [${h.kind}] ${h.createdAt}`,
2478
+ h.title !== void 0 ? ` ${h.title}` : void 0,
2479
+ h.actor !== void 0 ? ` by: ${h.actor}` : void 0,
2480
+ h.snippet !== void 0 ? ` ${h.snippet}` : void 0,
2481
+ h.location !== void 0 ? ` at: ${h.location.latitude},${h.location.longitude}${h.location.distanceMeters !== void 0 ? ` (${Math.round(h.location.distanceMeters)}m away)` : ""}` : void 0
2482
+ ].filter((l) => l !== void 0).join("\n");
2483
+ }
2484
+ const searchCommand = Command.make("search", {
2485
+ words: Args.text({ name: "words" }).pipe(Args.withDescription("The words to look for; all must appear. Quote a phrase (nested quotes) for adjacency. Optional when --near/--radius or --within is given."), Args.repeated),
2486
+ kind: kindOption,
2487
+ since: sinceOption$1,
2488
+ until: untilOption$1,
2489
+ member: memberOption$3,
2490
+ near: nearOption,
2491
+ radius: radiusOption,
2492
+ within: withinOption,
2493
+ limit: limitOption,
2494
+ format: formatOption$4,
2495
+ ...credentialOptions
2496
+ }, (args) => withQueryClient(args, (ctx) => Effect.gen(function* () {
2497
+ const query = args.words.join(" ").trim();
2498
+ const near = Option.getOrUndefined(args.near);
2499
+ const radius = Option.getOrUndefined(args.radius);
2500
+ const within = Option.getOrUndefined(args.within);
2501
+ const problem = near !== void 0 && radius === void 0 ? "--near requires --radius (meters)" : radius !== void 0 && near === void 0 ? "--radius requires --near" : within !== void 0 && near !== void 0 ? "--within cannot be combined with --near/--radius" : query === "" && near === void 0 && within === void 0 ? "give words to search for, or an area (--near with --radius, or --within)" : void 0;
2502
+ if (problem !== void 0) return yield* Effect.fail(new UserError({ message: problem }));
2503
+ const opts = {
2504
+ ...args.kind.length > 0 ? { kind: args.kind } : {},
2505
+ ...Option.match(args.since, {
2506
+ onNone: () => ({}),
2507
+ onSome: (since) => ({ since })
2508
+ }),
2509
+ ...Option.match(args.until, {
2510
+ onNone: () => ({}),
2511
+ onSome: (until) => ({ until })
2512
+ }),
2513
+ ...Option.match(args.member, {
2514
+ onNone: () => ({}),
2515
+ onSome: (member) => ({ member })
2516
+ }),
2517
+ ...Option.match(args.limit, {
2518
+ onNone: () => ({}),
2519
+ onSome: (limit) => ({ limit })
2520
+ }),
2521
+ ...near !== void 0 && radius !== void 0 ? {
2522
+ near,
2523
+ radiusMeters: radius
2524
+ } : {},
2525
+ ...within !== void 0 ? { within } : {}
2526
+ };
2527
+ const res = yield* sdkCall("search", () => ctx.client.search(query === "" ? void 0 : query, opts));
2528
+ for (const h of res.hits) yield* ctx.out.print(args.format === "json" ? JSON.stringify(h) : formatHit(h));
2529
+ if (res.hits.length === 0 && args.format === "pretty") yield* ctx.out.info("no hits");
2530
+ }))).pipe(Command.withDescription("Ranked full-text and location search over everything this credential reads, across all time. Words match literally plus stemmed in the org's configured languages; --near/--radius or --within search by place instead of or on top of words."));
2531
+ //#endregion
2435
2532
  //#region src/input-spec.ts
2436
2533
  const SETTING_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
2437
2534
  function looksLikeSetting(seg) {
@@ -3715,7 +3812,7 @@ const fileInput$1 = repeatedText("file-input", void 0, "Add a file upload input.
3715
3812
  const locationInput$1 = repeatedText("location-input", void 0, "Add a location input (the recipient shares their device GPS position from the app). Format: `description[;key=value...]`. Settings: `required=true|false`. Repeatable.");
3716
3813
  const linkOption$1 = repeatedText("link", "l", "Attach a remote URL (a link attachment). Repeatable. For local files use --file.");
3717
3814
  const fileOption$1 = repeatedText("file", "f", "Attach a local file, uploaded as a file attachment (encrypted under the send's key — topic password or org master key — when the send is encrypted). Repeatable.");
3718
- const submitOption$1 = Options.boolean("submit").pipe(Options.withDescription("Require the recipient to explicitly submit the task. Without this, it auto-completes once the required inputs are filled."));
3815
+ const autoCommitOption$1 = Options.boolean("auto-commit").pipe(Options.withDescription("Complete the task as inputs are filled, without an explicit Submit. Default is form mode: the recipient submits everything at once."));
3719
3816
  const waitOption$1 = Options.boolean("wait").pipe(Options.withDescription("Block until the task is completed; print the result to stdout. Requires exactly one input on the request."));
3720
3817
  const replyOption$1 = Options.choice("reply", [
3721
3818
  "one-shot",
@@ -3744,7 +3841,7 @@ const taskCommand = Command.make("task", {
3744
3841
  "location-input": locationInput$1,
3745
3842
  link: linkOption$1,
3746
3843
  file: fileOption$1,
3747
- submit: submitOption$1,
3844
+ "auto-commit": autoCommitOption$1,
3748
3845
  wait: waitOption$1,
3749
3846
  reply: replyOption$1,
3750
3847
  member: memberOption,
@@ -3795,7 +3892,7 @@ const taskCommand = Command.make("task", {
3795
3892
  inputs,
3796
3893
  links: [...args.link],
3797
3894
  files: [...args.file],
3798
- autoCommit: !args.submit,
3895
+ autoCommit: args["auto-commit"],
3799
3896
  reply: Option.getOrUndefined(args.reply),
3800
3897
  markdown: args.markdown,
3801
3898
  noEncrypt: args["no-encrypt"],
@@ -3822,7 +3919,7 @@ const taskCommand = Command.make("task", {
3822
3919
  inputs,
3823
3920
  links: [...args.link],
3824
3921
  ...files.length > 0 ? { files } : {},
3825
- autoCommit: !args.submit,
3922
+ autoCommit: args["auto-commit"],
3826
3923
  ...Option.isSome(args.reply) ? { reply: args.reply.value } : {},
3827
3924
  ...args.markdown ? { contentFormat: "markdown" } : {},
3828
3925
  ...expiresAt !== void 0 ? { expiresAt } : {}
@@ -4029,7 +4126,7 @@ const photoInput = Options.text("photo-input").pipe(Options.withDescription("Add
4029
4126
  const voiceRecordingInput = Options.text("voice-recording-input").pipe(Options.withDescription("Add a voice recording input. Format: `description[;key=value...]`. Settings: `required=true|false`. Repeatable."), Options.repeated);
4030
4127
  const fileInput = Options.text("file-input").pipe(Options.withDescription("Add a file upload input. Format: `description[;key=value...]`. Settings: `required=true|false`. Repeatable."), Options.repeated);
4031
4128
  const locationInput = Options.text("location-input").pipe(Options.withDescription("Add a location input (the recipient shares their device GPS position from the app). Format: `description[;key=value...]`. Settings: `required=true|false`. Repeatable."), Options.repeated);
4032
- const submitOption = Options.boolean("submit").pipe(Options.withDescription("Require the recipient to explicitly submit the subtask. Without this, it auto-completes once the required inputs are filled."));
4129
+ const autoCommitOption = Options.boolean("auto-commit").pipe(Options.withDescription("Complete the subtask as inputs are filled, without an explicit Submit. Default is form mode: the recipient submits everything at once."));
4033
4130
  const waitOption = Options.boolean("wait").pipe(Options.withDescription("Block until the subtask is completed; print the result to stdout. Requires at least one input on the append. On a group append, the first member's completion wins."));
4034
4131
  const formatOption = Options.choice("format", ["text", "json"]).pipe(Options.withDescription("stdout format for an append: `text` (the bare sub_ id(s), default) or `json` (a `sent` line piped to `sp collect`)."), Options.withDefault("text"));
4035
4132
  const replyOption = Options.choice("reply", [
@@ -4054,7 +4151,7 @@ const subtaskCommand = Command.make("subtask", {
4054
4151
  "location-input": locationInput,
4055
4152
  link: linkOption,
4056
4153
  file: fileOption,
4057
- submit: submitOption,
4154
+ "auto-commit": autoCommitOption,
4058
4155
  wait: waitOption,
4059
4156
  format: formatOption,
4060
4157
  reply: replyOption,
@@ -4084,7 +4181,7 @@ const subtaskCommand = Command.make("subtask", {
4084
4181
  ...content !== void 0 ? { content } : {},
4085
4182
  ...inputs.length > 0 ? { inputs } : {},
4086
4183
  links: [...args.link],
4087
- autoCommit: !args.submit,
4184
+ autoCommit: args["auto-commit"],
4088
4185
  ...Option.isSome(args.reply) ? { reply: args.reply.value } : {},
4089
4186
  ...args.markdown ? { contentFormat: "markdown" } : {}
4090
4187
  };
@@ -4243,11 +4340,12 @@ const root = Command.make("simplepush").pipe(Command.withSubcommands([
4243
4340
  taskCommand,
4244
4341
  subtaskCommand,
4245
4342
  cancelCommand,
4246
- getCommand
4343
+ getCommand,
4344
+ searchCommand
4247
4345
  ]));
4248
4346
  const cli = Command.run(root, {
4249
4347
  name: "Simplepush CLI",
4250
- version: "0.2.0"
4348
+ version
4251
4349
  });
4252
4350
  const MainLive = Layer.mergeAll(CliOutput.Default, Sodium.Default, AuthStore.Default, VaultStore.Default, InviteStore.Default, Api.Default, VaultAccess.Default).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provideMerge(NodeContext.layer));
4253
4351
  /** Render any failure through the typed-error table, then re-fail so the