@madewithremy/admin 0.1.2 → 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) {
@@ -2381,8 +2457,9 @@ var prerenderSpecs = {
2381
2457
  }
2382
2458
  },
2383
2459
  "prerender get": {
2384
- usage: "Usage: remy-admin prerender get <path>",
2385
- positionals: [{ name: "path", required: true }]
2460
+ usage: "Usage: remy-admin prerender get <path> [--host <hostname>]",
2461
+ positionals: [{ name: "path", required: true }],
2462
+ flags: { host: { type: "string" } }
2386
2463
  },
2387
2464
  "prerender pages": {
2388
2465
  usage: "Usage: remy-admin prerender pages [--cursor <c>]",
@@ -2404,7 +2481,7 @@ async function prerenderGet(ctx, a) {
2404
2481
  const rawPath = a.req("path");
2405
2482
  const normalized = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
2406
2483
  const isDev = !ctx.baseUrl.includes("api.mindstudio.ai");
2407
- const host = `${ctx.appId}${isDev ? "-dev" : ""}.madewithremy.com`;
2484
+ const host = a.str("host")?.replace(/^https?:\/\//, "").replace(/\/+$/, "") || `${ctx.appId}${isDev ? "-dev" : ""}.madewithremy.com`;
2408
2485
  const url = `https://${host}${normalized}`;
2409
2486
  const res = await fetchWithTimeout(
2410
2487
  url,
@@ -2448,6 +2525,7 @@ Usage:
2448
2525
  remy-admin prerender invalidate <path...> Purge specific paths
2449
2526
  remy-admin prerender invalidate --all Purge every snapshot for the app
2450
2527
  remy-admin prerender get <path> Print { url, status, html } as a crawler sees it
2528
+ remy-admin prerender get <path> --host <h> Same, against a specific hostname
2451
2529
 
2452
2530
  Notes:
2453
2531
  - 'pages'/'view' read what's stored for the LIVE release; 'get' exercises the real
@@ -2455,12 +2533,17 @@ Notes:
2455
2533
  returns the live SPA and triggers a render \u2014 re-run to see it.
2456
2534
  - 'view' errors with snapshot_not_found when the path has no snapshot yet.
2457
2535
  - Invalidation targets the current LIVE release; a deploy already invalidates on its own.
2536
+ - Mounted pages: when another app mounts this one, 'pages' lists paths carrying that
2537
+ app's mount prefix, and those pages exist only on that app's host \u2014 pass --host to
2538
+ verify them with 'get'. 'invalidate' takes this app's OWN paths and purges the
2539
+ mounted copies with them.
2458
2540
 
2459
2541
  Examples:
2460
2542
  remy-admin prerender pages
2461
2543
  remy-admin prerender view /u/abc123
2462
2544
  remy-admin prerender invalidate --all
2463
- remy-admin prerender get /u/abc123`;
2545
+ remy-admin prerender get /u/abc123
2546
+ remy-admin prerender get /demos/vector-search/blog/x --host example.com`;
2464
2547
 
2465
2548
  // src/commands/files.ts
2466
2549
  import fs2 from "fs";
@@ -3781,6 +3864,209 @@ Notes:
3781
3864
  Transcripts ('sessions get') are the primary way to debug and iterate on a
3782
3865
  voice persona.`;
3783
3866
 
3867
+ // src/ops/agent.ts
3868
+ function threadsList(ctx, params = {}) {
3869
+ return call(
3870
+ ctx,
3871
+ "GET",
3872
+ `/_internal/v2/apps/${ctx.appId}/agent-threads${qs(params)}`
3873
+ );
3874
+ }
3875
+ function threadsGet(ctx, threadId) {
3876
+ return call(
3877
+ ctx,
3878
+ "GET",
3879
+ `/_internal/v2/apps/${ctx.appId}/agent-threads/${seg(threadId)}`
3880
+ );
3881
+ }
3882
+
3883
+ // src/commands/agent.ts
3884
+ var agentSpecs = {
3885
+ "agent threads list": {
3886
+ usage: "Usage: remy-admin agent threads list [--limit 20] [--cursor <nextCursor>]",
3887
+ flags: {
3888
+ limit: { type: "number", min: 1, max: 100 },
3889
+ cursor: { type: "string" }
3890
+ }
3891
+ },
3892
+ "agent threads get": {
3893
+ usage: "Usage: remy-admin agent threads get <threadId>",
3894
+ positionals: [{ name: "threadId", required: true }]
3895
+ }
3896
+ };
3897
+ async function threadsList2(ctx, a) {
3898
+ out(
3899
+ await threadsList(ctx, {
3900
+ limit: a.num("limit"),
3901
+ cursor: a.str("cursor")
3902
+ })
3903
+ );
3904
+ }
3905
+ async function threadsGet2(ctx, a) {
3906
+ out(await threadsGet(ctx, a.req("threadId")));
3907
+ }
3908
+ var agentHandlers = {
3909
+ "agent threads list": threadsList2,
3910
+ "agent threads get": threadsGet2
3911
+ };
3912
+ var agentHelp = `remy-admin agent \u2014 Conversation log for the app's agent (chat) interface.
3913
+
3914
+ Subcommands:
3915
+ threads list Every conversation, newest activity first (transcripts excluded)
3916
+ threads get One conversation with its full transcript
3917
+
3918
+ Usage:
3919
+ remy-admin agent threads list [--limit 20] [--cursor <nextCursor>]
3920
+ remy-admin agent threads get <threadId>
3921
+
3922
+ Examples:
3923
+ remy-admin agent threads list --limit 10
3924
+ remy-admin agent threads get 4f6c\u2026
3925
+
3926
+ Notes:
3927
+ Transcripts are how you iterate on an agent: after the user tests it, read
3928
+ 'threads get' for what was actually said and which tools ran with which
3929
+ arguments, then fix the system prompt and tool descriptions from that evidence
3930
+ rather than from guesses.
3931
+
3932
+ In the list, 'toolErrorCount' and 'hasTurnError' point at the conversations
3933
+ worth opening \u2014 a failed tool call, or a turn that broke (model error, rate
3934
+ limit, credits) instead of replying. 'devSession' is true for your own test
3935
+ conversations through the dev tunnel, so you can tell them from real traffic.
3936
+
3937
+ In a transcript, each message is the stored conversation's own shape: 'user'
3938
+ for a person's message and also for a tool result (which carries
3939
+ 'toolCallId'), 'assistant' for the agent (carrying 'toolCalls' when it asked
3940
+ for tools). A method tool call also carries a 'requestId' \u2014 pass it to
3941
+ 'remy-admin requests get <requestId>' for that call's input, output, stdout
3942
+ and error. Client tools run in the browser, so they have no requestId.`;
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
+
3784
4070
  // src/ops/jewels.ts
3785
4071
  var JEWEL_RUN_TIMEOUT_MS = 6e5;
3786
4072
  function overview(ctx, params = {}) {
@@ -5063,6 +5349,8 @@ var SPECS = {
5063
5349
  ...filesSpecs,
5064
5350
  ...dataSourcesSpecs,
5065
5351
  ...voiceSpecs,
5352
+ ...agentSpecs,
5353
+ ...eventsSpecs,
5066
5354
  ...jewelsSpecs,
5067
5355
  ...settingsSpecs,
5068
5356
  ...emailSpecs,
@@ -5085,6 +5373,8 @@ var HANDLERS = {
5085
5373
  ...filesHandlers,
5086
5374
  ...dataSourcesHandlers,
5087
5375
  ...voiceHandlers,
5376
+ ...agentHandlers,
5377
+ ...eventsHandlers,
5088
5378
  ...jewelsHandlers,
5089
5379
  ...settingsHandlers,
5090
5380
  ...emailHandlers,
@@ -5107,6 +5397,8 @@ var GROUP_HELP = {
5107
5397
  files: filesHelp,
5108
5398
  datasources: dataSourcesHelp,
5109
5399
  voice: voiceHelp,
5400
+ agent: agentHelp,
5401
+ events: eventsHelp,
5110
5402
  jewels: jewelsHelp,
5111
5403
  settings: settingsHelp,
5112
5404
  email: emailHelp,
@@ -5186,6 +5478,8 @@ Commands:
5186
5478
  files Store, retrieve, share (signed links) + manage files in the app's stores
5187
5479
  datasources Build and query searchable document corpora (RAG)
5188
5480
  voice Phone numbers, voice call log + transcripts, voice policy settings
5481
+ agent Agent (chat) conversation log + transcripts
5482
+ events Realtime events \u2014 live tail, test publish, channel activity
5189
5483
  jewels Monitor jewel shadowing; review + approve the proposal queue
5190
5484
  settings App settings \u2014 signup allowlist, test accounts, embed origins, toggles
5191
5485
  email Email \u2014 delivery log, blast stats, unsubscribes, inbox, custom domains
@@ -5216,6 +5510,8 @@ var GROUPS = [
5216
5510
  "files",
5217
5511
  "datasources",
5218
5512
  "voice",
5513
+ "agent",
5514
+ "events",
5219
5515
  "jewels",
5220
5516
  "settings",
5221
5517
  "email",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madewithremy/admin",
3
- "version": "0.1.2",
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",