@madewithremy/admin 0.1.3 → 0.1.4

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,132 @@ 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 backend publishing what I think it is": run it in one
4052
+ shell, trigger the publishing method in another, and each matching publish
4053
+ prints as one JSON value ({channel, data, ts}). It ends on its own after
4054
+ --for seconds (exit 0), so it is safe to run from a script.
4055
+
4056
+ publish lets a frontend subscriber be verified before the backend trigger
4057
+ exists. The response's 'delivered' counts live subscriber connections per
4058
+ published channel \u2014 0 means nobody is listening right now, which is normal,
4059
+ not an error.
4060
+
4061
+ channels list is the mismatch-finder: a channel with subscribers but no
4062
+ recent publish (or the reverse) usually means the frontend and backend spell
4063
+ the channel differently. Window is 24h; channels are exact strings \u2014 there
4064
+ are no wildcards anywhere in app events.
4065
+
4066
+ Scopes: events never cross execution environments. --scope defaults to
4067
+ 'live'; use 'dev:<devSessionId>' to inspect a tunnel session's world or
4068
+ 'preview:<releaseId>' for a branch preview.`;
4069
+
3868
4070
  // src/ops/jewels.ts
3869
4071
  var JEWEL_RUN_TIMEOUT_MS = 6e5;
3870
4072
  function overview(ctx, params = {}) {
@@ -5148,6 +5350,7 @@ var SPECS = {
5148
5350
  ...dataSourcesSpecs,
5149
5351
  ...voiceSpecs,
5150
5352
  ...agentSpecs,
5353
+ ...eventsSpecs,
5151
5354
  ...jewelsSpecs,
5152
5355
  ...settingsSpecs,
5153
5356
  ...emailSpecs,
@@ -5171,6 +5374,7 @@ var HANDLERS = {
5171
5374
  ...dataSourcesHandlers,
5172
5375
  ...voiceHandlers,
5173
5376
  ...agentHandlers,
5377
+ ...eventsHandlers,
5174
5378
  ...jewelsHandlers,
5175
5379
  ...settingsHandlers,
5176
5380
  ...emailHandlers,
@@ -5194,6 +5398,7 @@ var GROUP_HELP = {
5194
5398
  datasources: dataSourcesHelp,
5195
5399
  voice: voiceHelp,
5196
5400
  agent: agentHelp,
5401
+ events: eventsHelp,
5197
5402
  jewels: jewelsHelp,
5198
5403
  settings: settingsHelp,
5199
5404
  email: emailHelp,
@@ -5274,6 +5479,7 @@ Commands:
5274
5479
  datasources Build and query searchable document corpora (RAG)
5275
5480
  voice Phone numbers, voice call log + transcripts, voice policy settings
5276
5481
  agent Agent (chat) conversation log + transcripts
5482
+ events Realtime events \u2014 live tail, test publish, channel activity
5277
5483
  jewels Monitor jewel shadowing; review + approve the proposal queue
5278
5484
  settings App settings \u2014 signup allowlist, test accounts, embed origins, toggles
5279
5485
  email Email \u2014 delivery log, blast stats, unsubscribes, inbox, custom domains
@@ -5305,6 +5511,7 @@ var GROUPS = [
5305
5511
  "datasources",
5306
5512
  "voice",
5307
5513
  "agent",
5514
+ "events",
5308
5515
  "jewels",
5309
5516
  "settings",
5310
5517
  "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.4",
4
4
  "description": "Admin SDK + CLI for managing production Remy apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",