@madewithremy/admin 0.1.3 → 0.1.5

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
@@ -1887,6 +1887,82 @@ async function streamToStdout(ctx, apiPath, body) {
1887
1887
  }
1888
1888
  }
1889
1889
  }
1890
+ async function tailSseToStdout(ctx, apiPath) {
1891
+ const controller = new AbortController();
1892
+ let idleTimer;
1893
+ let idled = false;
1894
+ const armIdleTimer = () => {
1895
+ if (idleTimer) {
1896
+ clearTimeout(idleTimer);
1897
+ }
1898
+ idleTimer = setTimeout(() => {
1899
+ idled = true;
1900
+ controller.abort();
1901
+ }, STREAM_IDLE_TIMEOUT_MS);
1902
+ };
1903
+ let complete = false;
1904
+ try {
1905
+ armIdleTimer();
1906
+ const res = await fetch(`${ctx.baseUrl}${apiPath}`, {
1907
+ method: "GET",
1908
+ headers: { ...authHeaders(ctx), Accept: "text/event-stream" },
1909
+ signal: controller.signal
1910
+ });
1911
+ if (!res.ok) {
1912
+ fatal(
1913
+ `API GET ${apiPath} returned ${res.status}: ${JSON.stringify(await readBody(res))}`
1914
+ );
1915
+ }
1916
+ if (!res.body) {
1917
+ fatal("Tail response has no body");
1918
+ }
1919
+ const reader = res.body.getReader();
1920
+ const decoder = new TextDecoder();
1921
+ let buffer = "";
1922
+ const emit = (line) => {
1923
+ if (!line.startsWith("data: ")) {
1924
+ return;
1925
+ }
1926
+ try {
1927
+ const value = JSON.parse(line.slice(6));
1928
+ if (value?.type === "tail_complete") {
1929
+ complete = true;
1930
+ return;
1931
+ }
1932
+ out(value);
1933
+ } catch {
1934
+ }
1935
+ };
1936
+ while (true) {
1937
+ const { done, value } = await reader.read();
1938
+ if (done) {
1939
+ emit(buffer);
1940
+ break;
1941
+ }
1942
+ armIdleTimer();
1943
+ buffer += decoder.decode(value, { stream: true });
1944
+ const lines = buffer.split("\n");
1945
+ buffer = lines.pop();
1946
+ for (const line of lines) {
1947
+ emit(line);
1948
+ }
1949
+ }
1950
+ } catch (err) {
1951
+ if (complete) {
1952
+ return;
1953
+ }
1954
+ if (idled || err?.name === "AbortError") {
1955
+ fatal(
1956
+ `Tail GET ${apiPath} stalled \u2014 no data for ${STREAM_IDLE_TIMEOUT_MS / 1e3}s`
1957
+ );
1958
+ }
1959
+ throw err;
1960
+ } finally {
1961
+ if (idleTimer) {
1962
+ clearTimeout(idleTimer);
1963
+ }
1964
+ }
1965
+ }
1890
1966
 
1891
1967
  // src/ops/methods.ts
1892
1968
  async function list6(ctx) {
@@ -3865,6 +3941,134 @@ Notes:
3865
3941
  'remy-admin requests get <requestId>' for that call's input, output, stdout
3866
3942
  and error. Client tools run in the browser, so they have no requestId.`;
3867
3943
 
3944
+ // src/ops/events.ts
3945
+ function channelsList(ctx, params = {}) {
3946
+ return call(
3947
+ ctx,
3948
+ "GET",
3949
+ `/_internal/v2/apps/${ctx.appId}/events/channels${qs(params)}`
3950
+ );
3951
+ }
3952
+ function publish(ctx, params) {
3953
+ return call(
3954
+ ctx,
3955
+ "POST",
3956
+ `/_internal/v2/apps/${ctx.appId}/events/publish`,
3957
+ { channels: params.channels, data: params.data, scope: params.scope }
3958
+ );
3959
+ }
3960
+
3961
+ // src/commands/events.ts
3962
+ var eventsSpecs = {
3963
+ "events tail": {
3964
+ usage: "Usage: remy-admin events tail [<channel>] [--scope live] [--for 60]",
3965
+ positionals: [{ name: "channel" }],
3966
+ flags: {
3967
+ scope: { type: "string" },
3968
+ for: { type: "number", min: 1, max: 600 }
3969
+ }
3970
+ },
3971
+ "events publish": {
3972
+ usage: "Usage: remy-admin events publish <channel> <json> [--scope live]\nPublishes exactly as the app's own events.publish would \u2014 same validation, delivery, and metering.",
3973
+ positionals: [
3974
+ { name: "channel", required: true },
3975
+ { name: "json", required: true }
3976
+ ],
3977
+ flags: {
3978
+ scope: { type: "string" }
3979
+ }
3980
+ },
3981
+ "events channels list": {
3982
+ usage: "Usage: remy-admin events channels list [--scope live]",
3983
+ flags: {
3984
+ scope: { type: "string" }
3985
+ }
3986
+ }
3987
+ };
3988
+ async function eventsTail(ctx, a) {
3989
+ const params = new URLSearchParams();
3990
+ const channel = a.str("channel");
3991
+ if (channel) {
3992
+ params.set("channel", channel);
3993
+ }
3994
+ const scope = a.str("scope");
3995
+ if (scope) {
3996
+ params.set("scope", scope);
3997
+ }
3998
+ const forSeconds = a.num("for");
3999
+ if (forSeconds !== void 0) {
4000
+ params.set("forSeconds", String(forSeconds));
4001
+ }
4002
+ const query3 = params.toString();
4003
+ await tailSseToStdout(
4004
+ ctx,
4005
+ `/_internal/v2/apps/${ctx.appId}/events/tail${query3 ? `?${query3}` : ""}`
4006
+ );
4007
+ }
4008
+ async function eventsPublish(ctx, a) {
4009
+ const raw = a.req("json");
4010
+ let data;
4011
+ try {
4012
+ data = JSON.parse(raw);
4013
+ } catch {
4014
+ fatal(`Invalid JSON payload: ${raw}`);
4015
+ }
4016
+ out(
4017
+ await publish(ctx, {
4018
+ channels: a.req("channel"),
4019
+ data,
4020
+ scope: a.str("scope")
4021
+ })
4022
+ );
4023
+ }
4024
+ async function eventsChannelsList(ctx, a) {
4025
+ out(await channelsList(ctx, { scope: a.str("scope") }));
4026
+ }
4027
+ var eventsHandlers = {
4028
+ "events tail": eventsTail,
4029
+ "events publish": eventsPublish,
4030
+ "events channels list": eventsChannelsList
4031
+ };
4032
+ var eventsHelp = `remy-admin events \u2014 Observe and test the app's realtime events (publish/subscribe).
4033
+
4034
+ Subcommands:
4035
+ tail Print publishes live as they happen (bounded; default 60s, max 600)
4036
+ publish Publish a test event, exactly as the app's own events.publish would
4037
+ channels list Channels with recent publishes/subscriptions + live subscriber counts
4038
+
4039
+ Usage:
4040
+ remy-admin events tail [<channel>] [--scope live] [--for 60]
4041
+ remy-admin events publish <channel> <json> [--scope live]
4042
+ remy-admin events channels list [--scope live]
4043
+
4044
+ Examples:
4045
+ remy-admin events tail --for 30
4046
+ remy-admin events tail jobs:usr_123
4047
+ remy-admin events publish jobs:usr_123 '{"type":"ping"}'
4048
+ remy-admin events channels list
4049
+
4050
+ Notes:
4051
+ tail answers "is my code publishing what I think it is": run it in one
4052
+ shell, trigger the publish in another, and each matching publish prints as
4053
+ one JSON value ({id, channel, data, ts}). Every publish appears regardless
4054
+ of origin \u2014 a backend method, this CLI, or a client with a publish-capable
4055
+ grant. It ends on its own after --for seconds (exit 0), so it is safe to
4056
+ run from a script.
4057
+
4058
+ publish lets a frontend subscriber be verified before the backend trigger
4059
+ exists. The response's 'delivered' counts live subscriber connections per
4060
+ published channel \u2014 0 means nobody is listening right now, which is normal,
4061
+ not an error.
4062
+
4063
+ channels list is the mismatch-finder: a channel with subscribers but no
4064
+ recent publish (or the reverse) usually means the frontend and backend spell
4065
+ the channel differently. Window is 24h; channels are exact strings \u2014 there
4066
+ are no wildcards anywhere in app events.
4067
+
4068
+ Scopes: events never cross execution environments. --scope defaults to
4069
+ 'live'; use 'dev:<devSessionId>' to inspect a tunnel session's world or
4070
+ 'preview:<releaseId>' for a branch preview.`;
4071
+
3868
4072
  // src/ops/jewels.ts
3869
4073
  var JEWEL_RUN_TIMEOUT_MS = 6e5;
3870
4074
  function overview(ctx, params = {}) {
@@ -5148,6 +5352,7 @@ var SPECS = {
5148
5352
  ...dataSourcesSpecs,
5149
5353
  ...voiceSpecs,
5150
5354
  ...agentSpecs,
5355
+ ...eventsSpecs,
5151
5356
  ...jewelsSpecs,
5152
5357
  ...settingsSpecs,
5153
5358
  ...emailSpecs,
@@ -5171,6 +5376,7 @@ var HANDLERS = {
5171
5376
  ...dataSourcesHandlers,
5172
5377
  ...voiceHandlers,
5173
5378
  ...agentHandlers,
5379
+ ...eventsHandlers,
5174
5380
  ...jewelsHandlers,
5175
5381
  ...settingsHandlers,
5176
5382
  ...emailHandlers,
@@ -5194,6 +5400,7 @@ var GROUP_HELP = {
5194
5400
  datasources: dataSourcesHelp,
5195
5401
  voice: voiceHelp,
5196
5402
  agent: agentHelp,
5403
+ events: eventsHelp,
5197
5404
  jewels: jewelsHelp,
5198
5405
  settings: settingsHelp,
5199
5406
  email: emailHelp,
@@ -5274,6 +5481,7 @@ Commands:
5274
5481
  datasources Build and query searchable document corpora (RAG)
5275
5482
  voice Phone numbers, voice call log + transcripts, voice policy settings
5276
5483
  agent Agent (chat) conversation log + transcripts
5484
+ events Realtime events \u2014 live tail, test publish, channel activity
5277
5485
  jewels Monitor jewel shadowing; review + approve the proposal queue
5278
5486
  settings App settings \u2014 signup allowlist, test accounts, embed origins, toggles
5279
5487
  email Email \u2014 delivery log, blast stats, unsubscribes, inbox, custom domains
@@ -5305,6 +5513,7 @@ var GROUPS = [
5305
5513
  "datasources",
5306
5514
  "voice",
5307
5515
  "agent",
5516
+ "events",
5308
5517
  "jewels",
5309
5518
  "settings",
5310
5519
  "email",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madewithremy/admin",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Admin SDK + CLI for managing production Remy apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",