@simplepush/cli 0.5.0 → 0.7.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
@@ -14,7 +14,7 @@ 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
16
  //#region package.json
17
- var version = "0.5.0";
17
+ var version = "0.7.0";
18
18
  //#endregion
19
19
  //#region src/errors.ts
20
20
  /** No CLI session saved — `sp auth login` hasn't been run (or was logged out). */
@@ -573,6 +573,46 @@ const quietOption = Options.boolean("quiet").pipe(Options.withAlias("q"), Option
573
573
  /** Shared helper for repeatable validated text options. */
574
574
  const mappedText = (name, parse) => Options.text(name).pipe(Options.mapTryCatch(parse, toHelp));
575
575
  //#endregion
576
+ //#region src/services/browser.ts
577
+ /** The command line that opens `url` on `platform`. Pure, so it can be tested
578
+ * for every platform from one machine. */
579
+ function browserLaunch(platform, url) {
580
+ switch (platform) {
581
+ case "darwin": return {
582
+ command: "open",
583
+ args: [url],
584
+ windowsVerbatimArguments: false
585
+ };
586
+ case "win32": return {
587
+ command: "cmd",
588
+ args: [
589
+ "/c",
590
+ "start",
591
+ "\"\"",
592
+ url.replace(/&/g, "^&")
593
+ ],
594
+ windowsVerbatimArguments: true
595
+ };
596
+ default: return {
597
+ command: "xdg-open",
598
+ args: [url],
599
+ windowsVerbatimArguments: false
600
+ };
601
+ }
602
+ }
603
+ /** Fire-and-forget. A failure to spawn is swallowed: the caller has already
604
+ * printed the URL, so the user can open it by hand. */
605
+ const openInBrowser = (url) => Effect.sync(() => {
606
+ const launch = browserLaunch(process.platform, url);
607
+ try {
608
+ spawn(launch.command, launch.args, {
609
+ detached: true,
610
+ stdio: "ignore",
611
+ windowsVerbatimArguments: launch.windowsVerbatimArguments
612
+ }).unref();
613
+ } catch {}
614
+ });
615
+ //#endregion
576
616
  //#region src/format.ts
577
617
  function formatInstant(iso) {
578
618
  const d = new Date(iso);
@@ -661,21 +701,6 @@ const startCallbackServer = Effect.gen(function* () {
661
701
  awaitCallback: Deferred.await(callback)
662
702
  };
663
703
  });
664
- const openInBrowser = (url) => Effect.sync(() => {
665
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
666
- const args = process.platform === "win32" ? [
667
- "/c",
668
- "start",
669
- "",
670
- url
671
- ] : [url];
672
- try {
673
- spawn(cmd, args, {
674
- detached: true,
675
- stdio: "ignore"
676
- }).unref();
677
- } catch {}
678
- });
679
704
  function preferDeviceFlow() {
680
705
  if (process.env.SP_AUTH_DEVICE === "1") return true;
681
706
  if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return true;
@@ -2476,7 +2501,7 @@ function formatHit(h) {
2476
2501
  return [
2477
2502
  `${h.ref} [${h.kind}] ${h.createdAt}`,
2478
2503
  h.title !== void 0 ? ` ${h.title}` : void 0,
2479
- h.actor !== void 0 ? ` by: ${h.actor}` : void 0,
2504
+ h.actor !== void 0 ? ` by: ${h.actor.name !== void 0 ? `${h.actor.name} (${h.actor.publicId})` : h.actor.publicId}` : void 0,
2480
2505
  h.snippet !== void 0 ? ` ${h.snippet}` : void 0,
2481
2506
  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
2507
  ].filter((l) => l !== void 0).join("\n");
@@ -2883,6 +2908,7 @@ const broadcastOption$1 = Options.boolean("broadcast").pipe(Options.withAlias("b
2883
2908
  const orgTopicOption$1 = Options.text("org-topic").pipe(Options.withAlias("o"), Options.withDescription("Send to an org topic by value (from `sp org topics list`). Org send; mutually exclusive with --member, --broadcast, and -k/--topic (the personal topic)."), Options.optional);
2884
2909
  const tagOption$1 = Options.text("tag").pipe(Options.withDescription("Optional notification tag — recipients can use it to coalesce / replace prior notifications with the same tag."), Options.optional);
2885
2910
  const imageOption = Options.text("image").pipe(Options.withDescription("Image URL to show in the push (PNG/JPEG/GIF). Renders on iOS + Android. Mutually exclusive with --audio. URLs only — file uploads are SDK-only."), Options.optional);
2911
+ const linkOption$2 = Options.text("link").pipe(Options.withAlias("l"), Options.withDescription("A URL shown as an \"Open link\" button on the push. Any scheme: https opens the browser, an app's deep link (unifi-protect://...) opens that app. Mutually exclusive with the input flags."), Options.optional);
2886
2912
  const audioOption = Options.text("audio").pipe(Options.withDescription("Audio URL to play inline in the push (AIFF/WAV/MP3/M4A; iOS only). Mutually exclusive with --image. URLs only."), Options.optional);
2887
2913
  const noEncryptOption$2 = Options.boolean("no-encrypt").pipe(Options.withDescription("Send the body in plaintext even when an unlocked vault is available."));
2888
2914
  const actionInputOption = Options.text("action-input").pipe(Options.withAlias("a"), Options.withDescription("Add an actions input: tap-buttons the recipient answers with (e.g. Accept/Deny). Format: `[description;]key=Label[:style],...`, actions comma-separated; style is default|destructive. Use `\\,` for a literal comma in a label. A notification carries at most one input."), Options.optional);
@@ -2932,6 +2958,7 @@ const notifyCommand = Command.make("notify", {
2932
2958
  tag: tagOption$1,
2933
2959
  image: imageOption,
2934
2960
  audio: audioOption,
2961
+ link: linkOption$2,
2935
2962
  "text-input": textInputOption,
2936
2963
  "choice-input": choiceInputOption,
2937
2964
  "action-input": actionInputOption,
@@ -2961,6 +2988,7 @@ const notifyCommand = Command.make("notify", {
2961
2988
  const message = args.content;
2962
2989
  const imageUrl = Option.getOrUndefined(args.image);
2963
2990
  const audioUrl = Option.getOrUndefined(args.audio);
2991
+ const linkUrl = Option.getOrUndefined(args.link);
2964
2992
  if (imageUrl !== void 0 && audioUrl !== void 0) return yield* Effect.fail(new UserError({ message: "only one of --image or --audio may be set" }));
2965
2993
  const mediaUrl = imageUrl ?? audioUrl;
2966
2994
  const mediaKind = imageUrl !== void 0 ? "image" : "audio";
@@ -2969,6 +2997,7 @@ const notifyCommand = Command.make("notify", {
2969
2997
  if (notifyMediaContentType(mediaUrl, mediaKind) === null) return yield* Effect.fail(new UserError({ message: `--${mediaKind} URL must point to a supported ${mediaKind} type (by extension); got "${mediaUrl}"` }));
2970
2998
  }
2971
2999
  const input = yield* parseNotificationInput(args["text-input"], Option.getOrUndefined(args["choice-input"]), Option.getOrUndefined(args["action-input"]));
3000
+ if (input !== void 0 && linkUrl !== void 0) return yield* Effect.fail(new UserError({ message: "a notification carries either an input or --link, not both: the input's buttons take the action slots, so the link would never be shown" }));
2972
3001
  if (isOrgTarget) {
2973
3002
  const api = yield* Api;
2974
3003
  const vault = yield* (yield* VaultAccess).forSendOrPlaintext(args.noEncrypt);
@@ -2985,7 +3014,8 @@ const notifyCommand = Command.make("notify", {
2985
3014
  ...tagOpt !== void 0 ? { tag: tagOpt } : {},
2986
3015
  ...input !== void 0 ? { input } : {},
2987
3016
  ...imageUrl !== void 0 ? { image: imageUrl } : {},
2988
- ...audioUrl !== void 0 ? { audio: audioUrl } : {}
3017
+ ...audioUrl !== void 0 ? { audio: audioUrl } : {},
3018
+ ...linkUrl !== void 0 ? { link: linkUrl } : {}
2989
3019
  };
2990
3020
  yield* Effect.scoped(Effect.gen(function* () {
2991
3021
  const client = yield* acquireOrgClient({
@@ -3045,7 +3075,8 @@ const notifyCommand = Command.make("notify", {
3045
3075
  ...tagOpt !== void 0 ? { tag: tagOpt } : {},
3046
3076
  ...input !== void 0 ? { input } : {},
3047
3077
  ...imageUrl !== void 0 ? { image: imageUrl } : {},
3048
- ...audioUrl !== void 0 ? { audio: audioUrl } : {}
3078
+ ...audioUrl !== void 0 ? { audio: audioUrl } : {},
3079
+ ...linkUrl !== void 0 ? { link: linkUrl } : {}
3049
3080
  };
3050
3081
  yield* Effect.scoped(Effect.gen(function* () {
3051
3082
  const client = yield* acquireClient({