@hasna/events 0.1.4 → 0.1.6

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.
@@ -1,2 +1,6 @@
1
1
  #!/usr/bin/env bun
2
- export {};
2
+ export interface RunEventsCliOptions {
3
+ programName?: string;
4
+ source?: string;
5
+ }
6
+ export declare function runEventsCli(argv?: string[], options?: RunEventsCliOptions): Promise<void>;
package/dist/cli/index.js CHANGED
@@ -631,29 +631,34 @@ function output(parsed, value, human) {
631
631
  }
632
632
  human();
633
633
  }
634
- function printHelp() {
635
- console.log(`events ${version()}
634
+ function commandName(options) {
635
+ return options.programName ?? "events";
636
+ }
637
+ function printHelp(options = {}) {
638
+ const name = commandName(options);
639
+ console.log(`${name} ${version()}
636
640
 
637
641
  Usage:
638
- events [--dir <path>] [--json] webhooks add <url|command> [options]
639
- events [--dir <path>] [--json] webhooks list
640
- events [--dir <path>] [--json] webhooks remove <id>
641
- events [--dir <path>] [--json] webhooks test <id>
642
- events [--dir <path>] [--json] events emit <type> --source <source> [options]
643
- events [--dir <path>] [--json] events list [--limit <n>]
644
- events [--dir <path>] [--json] events replay [--id <event-id>] [--dry-run]
642
+ ${name} [--dir <path>] [--json] webhooks add <url|command> [options]
643
+ ${name} [--dir <path>] [--json] webhooks list
644
+ ${name} [--dir <path>] [--json] webhooks remove <id>
645
+ ${name} [--dir <path>] [--json] webhooks test <id>
646
+ ${name} [--dir <path>] [--json] events emit <type>${options.source ? "" : " --source <source>"} [options]
647
+ ${name} [--dir <path>] [--json] events list [--limit <n>]
648
+ ${name} [--dir <path>] [--json] events replay [--id <event-id>] [--dry-run]
645
649
 
646
650
  Environment:
647
651
  HASNA_EVENTS_DIR or HASNA_EVENTS_HOME overrides the default ${getEventsDataDir()}`);
648
652
  }
649
- function printWebhooksHelp() {
650
- console.log(`events webhooks
653
+ function printWebhooksHelp(options = {}) {
654
+ const name = commandName(options);
655
+ console.log(`${name} webhooks
651
656
 
652
657
  Usage:
653
- events [--dir <path>] [--json] webhooks add <url|command> [options]
654
- events [--dir <path>] [--json] webhooks list
655
- events [--dir <path>] [--json] webhooks remove <id>
656
- events [--dir <path>] [--json] webhooks test <id>
658
+ ${name} [--dir <path>] [--json] webhooks add <url|command> [options]
659
+ ${name} [--dir <path>] [--json] webhooks list
660
+ ${name} [--dir <path>] [--json] webhooks remove <id>
661
+ ${name} [--dir <path>] [--json] webhooks test <id>
657
662
 
658
663
  Options:
659
664
  --id <id> Channel id for add
@@ -667,16 +672,17 @@ Options:
667
672
  --redact <path> Redaction path, repeatable
668
673
  --no-deliver Available on events emit`);
669
674
  }
670
- function printEventsHelp() {
671
- console.log(`events events
675
+ function printEventsHelp(options = {}) {
676
+ const name = commandName(options);
677
+ console.log(`${name} events
672
678
 
673
679
  Usage:
674
- events [--dir <path>] [--json] events emit <type> --source <source> [options]
675
- events [--dir <path>] [--json] events list [--limit <n>]
676
- events [--dir <path>] [--json] events replay [--id <event-id>] [--dry-run]
680
+ ${name} [--dir <path>] [--json] events emit <type>${options.source ? "" : " --source <source>"} [options]
681
+ ${name} [--dir <path>] [--json] events list [--limit <n>]
682
+ ${name} [--dir <path>] [--json] events replay [--id <event-id>] [--dry-run]
677
683
 
678
684
  Options:
679
- --source <source> Event source
685
+ --source <source> Event source${options.source ? ` (default: ${options.source})` : ""}
680
686
  --subject <subject> Event subject
681
687
  --severity <severity> Event severity
682
688
  --message <message> Human-readable event message
@@ -686,11 +692,11 @@ Options:
686
692
  --no-deliver Record without delivering webhooks
687
693
  --dry-run Preview replay matches without delivery`);
688
694
  }
689
- async function main(argv = process.argv.slice(2)) {
695
+ async function runEventsCli(argv = process.argv.slice(2), options = {}) {
690
696
  const parsed = parseGlobalArgs(argv);
691
697
  const [group, command, ...tail] = parsed.rest;
692
698
  if (!group || group === "--help" || group === "-h") {
693
- printHelp();
699
+ printHelp(options);
694
700
  return;
695
701
  }
696
702
  if (group === "--version" || group === "-v") {
@@ -701,23 +707,23 @@ async function main(argv = process.argv.slice(2)) {
701
707
  const client = new EventsClient({ store });
702
708
  if (group === "webhooks") {
703
709
  if (!command || command === "--help" || command === "-h") {
704
- printWebhooksHelp();
710
+ printWebhooksHelp(options);
705
711
  return;
706
712
  }
707
- await handleWebhooks(client, command, tail, parsed);
713
+ await handleWebhooks(client, command, tail, parsed, options);
708
714
  return;
709
715
  }
710
716
  if (group === "events") {
711
717
  if (!command || command === "--help" || command === "-h") {
712
- printEventsHelp();
718
+ printEventsHelp(options);
713
719
  return;
714
720
  }
715
- await handleEvents(client, command, tail, parsed);
721
+ await handleEvents(client, command, tail, parsed, options);
716
722
  return;
717
723
  }
718
724
  throw new Error(`Unknown command group: ${group}`);
719
725
  }
720
- async function handleWebhooks(client, command, tail, parsed) {
726
+ async function handleWebhooks(client, command, tail, parsed, options) {
721
727
  if (command === "add") {
722
728
  const args = [...tail];
723
729
  const transport = takeOption(args, "--transport") ?? "webhook";
@@ -785,7 +791,7 @@ async function handleWebhooks(client, command, tail, parsed) {
785
791
  if (!id)
786
792
  throw new Error("webhooks test requires a channel id");
787
793
  const result = await client.testChannel(id, {
788
- source: takeOption(args, "--source") ?? "hasna.events",
794
+ source: takeOption(args, "--source") ?? options.source ?? "hasna.events",
789
795
  type: takeOption(args, "--type") ?? "events.test",
790
796
  subject: takeOption(args, "--subject") ?? id,
791
797
  data: parseJsonOption(takeOption(args, "--data"), { test: true })
@@ -795,13 +801,13 @@ async function handleWebhooks(client, command, tail, parsed) {
795
801
  }
796
802
  throw new Error(`Unknown webhooks command: ${command ?? ""}`);
797
803
  }
798
- async function handleEvents(client, command, tail, parsed) {
804
+ async function handleEvents(client, command, tail, parsed, options) {
799
805
  if (command === "emit") {
800
806
  const args = [...tail];
801
807
  const type = args.shift();
802
808
  if (!type)
803
809
  throw new Error("events emit requires an event type");
804
- const source = takeOption(args, "--source");
810
+ const source = takeOption(args, "--source") ?? options.source;
805
811
  if (!source)
806
812
  throw new Error("events emit requires --source");
807
813
  const noDeliver = takeFlag(args, "--no-deliver");
@@ -870,13 +876,18 @@ function severityOption(value) {
870
876
  throw new Error(`Invalid severity: ${value}`);
871
877
  return value;
872
878
  }
873
- main().catch((error) => {
874
- const parsed = parseGlobalArgs(process.argv.slice(2));
875
- const message = error instanceof Error ? error.message : String(error);
876
- if (parsed.json) {
877
- console.log(JSON.stringify({ error: message }, null, 2));
878
- } else {
879
- console.error(message);
880
- }
881
- process.exit(1);
882
- });
879
+ if (import.meta.main) {
880
+ runEventsCli().catch((error) => {
881
+ const parsed = parseGlobalArgs(process.argv.slice(2));
882
+ const message = error instanceof Error ? error.message : String(error);
883
+ if (parsed.json) {
884
+ console.log(JSON.stringify({ error: message }, null, 2));
885
+ } else {
886
+ console.error(message);
887
+ }
888
+ process.exit(1);
889
+ });
890
+ }
891
+ export {
892
+ runEventsCli
893
+ };
package/dist/commander.js CHANGED
@@ -594,9 +594,15 @@ function print(value, json, text) {
594
594
  else
595
595
  console.log(text);
596
596
  }
597
+ function hasJsonOption(options) {
598
+ return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
599
+ }
600
+ function wantsJson(actionOptions, command) {
601
+ return hasJsonOption(actionOptions) || hasJsonOption(command);
602
+ }
597
603
  function registerWebhookCommands(program, options) {
598
604
  const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
599
- webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions) => {
605
+ webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
600
606
  const timestamp = new Date().toISOString();
601
607
  const channel = {
602
608
  id: actionOptions.id,
@@ -617,11 +623,11 @@ function registerWebhookCommands(program, options) {
617
623
  throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
618
624
  }
619
625
  const saved = await createClient(options).addChannel(channel);
620
- print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
626
+ print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
621
627
  });
622
- webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
628
+ webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
623
629
  const channels = await createClient(options).listChannels();
624
- if (actionOptions.json) {
630
+ if (wantsJson(actionOptions, command)) {
625
631
  console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
626
632
  return;
627
633
  }
@@ -633,11 +639,11 @@ function registerWebhookCommands(program, options) {
633
639
  console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
634
640
  }
635
641
  });
636
- webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
642
+ webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
637
643
  const removed = await createClient(options).removeChannel(id);
638
- print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
644
+ print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
639
645
  });
640
- webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
646
+ webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
641
647
  const result = await createClient(options).testChannel(id, {
642
648
  source: options.source,
643
649
  type: actionOptions.type,
@@ -645,13 +651,13 @@ function registerWebhookCommands(program, options) {
645
651
  message: actionOptions.message,
646
652
  data: parseJsonObject(actionOptions.data, { test: true })
647
653
  });
648
- print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
654
+ print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
649
655
  });
650
656
  return webhooks;
651
657
  }
652
658
  function registerEventCommands(program, options) {
653
659
  const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
654
- events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions) => {
660
+ events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions, command) => {
655
661
  const result = await createClient(options).emit({
656
662
  source: actionOptions.source ?? options.source,
657
663
  type,
@@ -662,9 +668,9 @@ function registerEventCommands(program, options) {
662
668
  data: parseJsonObject(actionOptions.data, {}),
663
669
  metadata: parseJsonObject(actionOptions.metadata, {})
664
670
  }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
665
- print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
671
+ print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
666
672
  });
667
- events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
673
+ events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
668
674
  let rows = await createClient(options).listEvents();
669
675
  if (actionOptions.source)
670
676
  rows = rows.filter((event) => event.source === actionOptions.source);
@@ -672,7 +678,7 @@ function registerEventCommands(program, options) {
672
678
  rows = rows.filter((event) => event.type === actionOptions.type);
673
679
  if (actionOptions.limit)
674
680
  rows = rows.slice(-actionOptions.limit);
675
- if (actionOptions.json) {
681
+ if (wantsJson(actionOptions, command)) {
676
682
  console.log(JSON.stringify(rows, null, 2));
677
683
  return;
678
684
  }
@@ -683,14 +689,14 @@ function registerEventCommands(program, options) {
683
689
  for (const event of rows)
684
690
  console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
685
691
  });
686
- events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
692
+ events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
687
693
  const result = await createClient(options).replay({
688
694
  eventId: actionOptions.id,
689
695
  source: actionOptions.source,
690
696
  type: actionOptions.type,
691
697
  dryRun: actionOptions.dryRun
692
698
  });
693
- print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
699
+ print(result, wantsJson(actionOptions, command), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
694
700
  });
695
701
  return events;
696
702
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/events",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Shared event envelopes, local subscriptions, and webhook delivery for Hasna open-source apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,6 +33,10 @@
33
33
  "./commander": {
34
34
  "types": "./dist/commander.d.ts",
35
35
  "import": "./dist/commander.js"
36
+ },
37
+ "./cli": {
38
+ "types": "./dist/cli/index.d.ts",
39
+ "import": "./dist/cli/index.js"
36
40
  }
37
41
  },
38
42
  "files": [