railcode 0.1.18 → 0.1.20

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/README.md CHANGED
@@ -22,6 +22,8 @@ railcode login --setup-token <token> Sign in with a one-time onboarding setup t
22
22
  railcode init <app> [--template react|static]
23
23
  railcode deploy Build (if configured) and deploy the app here
24
24
  railcode db <list|query> ... List data connectors / run read-only SQL
25
+ railcode agent <list|show|create|update|delete|run|schedule> ...
26
+ Manage org-scoped managed agents
25
27
  ```
26
28
 
27
29
  - **`login`** prompts for the server URL (default `https://api.railcode.app`),
@@ -60,6 +62,24 @@ railcode db <list|query> ... List data connectors / run read-only SQL
60
62
  list when omitted, `--params '<json-array>'` binds `$1, $2, …` placeholders
61
63
  (SQL is never interpolated), `--file <path>` reads SQL from a file, and
62
64
  `--json` prints the raw `{ columns, rows, rowcount, truncated }` envelope.
65
+ - **`agent`** manages org-scoped managed agents when your permissions allow it.
66
+ Manifests are JSON files in the same shape the API stores under
67
+ `agent.manifest`.
68
+ - `railcode agent list` lists agents; `--json` prints raw `AgentOut[]`.
69
+ - `railcode agent show <name|uuid>` prints one agent; `--manifest` prints only
70
+ its manifest JSON.
71
+ - `railcode agent pull <name|uuid> --output agent.json` writes the existing
72
+ manifest for editing.
73
+ - `railcode agent create --file agent.json` creates an agent; `update <agent>
74
+ --file agent.json` replaces an existing agent manifest; `delete <agent> --yes`
75
+ archives it.
76
+ - `railcode agent test --file agent.json --input '{"k":"v"}'` runs a draft
77
+ manifest without saving; `railcode agent run <agent> --input '{"k":"v"}'`
78
+ invokes a saved agent and prints the result. Use `--trace` for the step trace
79
+ and `--json` for the raw run detail.
80
+ - `railcode agent schedule set <agent> --cron "0 9 * * *" --timezone UTC`
81
+ creates or updates the agent's one schedule. `schedule show`, `pause`,
82
+ `resume`, `delete --yes`, and `run-now` operate on that single schedule.
63
83
 
64
84
  ## Configuration
65
85
 
package/dist/index.js CHANGED
@@ -43,6 +43,8 @@ Usage:
43
43
  railcode connector <list|docs|fetch> ... List connectors / show API docs / proxy a call
44
44
  railcode llm <providers|models> List the LLM providers/models apps can call
45
45
  railcode manifest <validate|show> ... Validate ${APP_MANIFEST_NAME} locally / show an app's ratified manifest
46
+ railcode agent <list|show|create|update|delete|run|schedule> ...
47
+ Manage org-scoped managed agents
46
48
  railcode --version
47
49
  railcode --help
48
50
 
@@ -97,6 +99,10 @@ async function main() {
97
99
  case "manifest":
98
100
  await commandManifest(args);
99
101
  return;
102
+ case "agent":
103
+ case "agents":
104
+ await commandAgent(args);
105
+ return;
100
106
  default:
101
107
  throw new CliError(`Unknown command: ${args.command}\n\n${HELP}`, 2);
102
108
  }
@@ -3534,6 +3540,550 @@ async function commandManifestShow(args) {
3534
3540
  }
3535
3541
  }
3536
3542
  // ---------------------------------------------------------------------------
3543
+ // agent — managed agents (`railcode agent ...`)
3544
+ // ---------------------------------------------------------------------------
3545
+ const AGENT_HELP = `railcode agent — managed agents
3546
+
3547
+ Usage:
3548
+ railcode agent list List managed agents in this org
3549
+ railcode agent show <agent> Show one agent
3550
+ railcode agent pull <agent> [--output <path>]
3551
+ Write the agent manifest as JSON
3552
+ railcode agent create --file <path> Create an agent from a JSON manifest
3553
+ railcode agent update <agent> --file <path> Update an agent from a JSON manifest
3554
+ railcode agent delete <agent> [--yes] Delete/archive an agent
3555
+ railcode agent test --file <path> [opts] Run a draft manifest without saving
3556
+ railcode agent run <agent> [opts] Trigger a saved agent and print results
3557
+ railcode agent schedule <show|set|add|update|pause|resume|delete|run-now> <agent>
3558
+
3559
+ Agent options:
3560
+ --json Print the raw API response
3561
+ --file <path> JSON manifest file
3562
+ --output <path> Output path for pull (default stdout)
3563
+ --input '<json>' Invoke/test input JSON (default null)
3564
+ --input-file <path> Read invoke/test input JSON from a file
3565
+ --trace Include the run step trace in human output
3566
+ --yes Confirm delete without a prompt
3567
+
3568
+ Schedule options:
3569
+ --name <name> Schedule name (default: default on create)
3570
+ --cron <expr> Five-field cron expression
3571
+ --schedule <expr> Alias for --cron
3572
+ --timezone <iana> IANA timezone (default: UTC on create)
3573
+ --enabled / --disabled Initial or updated enabled state
3574
+
3575
+ Manifests are JSON files in the same shape the API stores under agent.manifest.
3576
+ Schedules are currently one per agent, so "set" creates the schedule if missing
3577
+ or updates the existing one.
3578
+ `;
3579
+ async function commandAgent(args) {
3580
+ const sub = args.rest[0] ?? "";
3581
+ switch (sub) {
3582
+ case "list":
3583
+ case "ls":
3584
+ await commandAgentList(args);
3585
+ return;
3586
+ case "show":
3587
+ case "get":
3588
+ await commandAgentShow(args);
3589
+ return;
3590
+ case "pull":
3591
+ case "export":
3592
+ await commandAgentPull(args);
3593
+ return;
3594
+ case "create":
3595
+ await commandAgentCreate(args);
3596
+ return;
3597
+ case "update":
3598
+ case "edit":
3599
+ await commandAgentUpdate(args);
3600
+ return;
3601
+ case "delete":
3602
+ case "rm":
3603
+ await commandAgentDelete(args);
3604
+ return;
3605
+ case "test":
3606
+ await commandAgentTest(args);
3607
+ return;
3608
+ case "run":
3609
+ case "invoke":
3610
+ await commandAgentRun(args);
3611
+ return;
3612
+ case "schedule":
3613
+ case "schedules":
3614
+ await commandAgentSchedule(args);
3615
+ return;
3616
+ case "":
3617
+ case "help":
3618
+ console.log(AGENT_HELP);
3619
+ return;
3620
+ default:
3621
+ throw new CliError(`Unknown agent command: ${sub}\n\n${AGENT_HELP}`, 2);
3622
+ }
3623
+ }
3624
+ async function commandAgentList(args) {
3625
+ assertKnownAgentOptions(args, ["json", "apiUrl"]);
3626
+ if (args.rest.length > 1) {
3627
+ throw new CliError(`railcode agent list takes no arguments (got "${args.rest[1]}").`, 2);
3628
+ }
3629
+ const config = requireLogin(args);
3630
+ const agents = await fetchAgents(config);
3631
+ if (args.options.json) {
3632
+ console.log(JSON.stringify(agents, null, 2));
3633
+ return;
3634
+ }
3635
+ if (agents.length === 0) {
3636
+ console.log("No managed agents in this organization.");
3637
+ return;
3638
+ }
3639
+ console.log(formatTable(["name", "model", "uuid", "updated"], agents.map((agent) => [
3640
+ agent.name,
3641
+ typeof agent.manifest.model === "string" ? agent.manifest.model : "-",
3642
+ agent.uuid,
3643
+ shortDate(agent.updated_at),
3644
+ ])));
3645
+ }
3646
+ async function commandAgentShow(args) {
3647
+ assertKnownAgentOptions(args, ["json", "manifest", "apiUrl"]);
3648
+ const ref = agentArg(args, "show");
3649
+ const config = requireLogin(args);
3650
+ const agent = await fetchAgent(config, ref);
3651
+ if (args.options.json) {
3652
+ console.log(JSON.stringify(agent, null, 2));
3653
+ return;
3654
+ }
3655
+ if (args.options.manifest) {
3656
+ console.log(JSON.stringify(agent.manifest, null, 2));
3657
+ return;
3658
+ }
3659
+ printAgent(agent);
3660
+ }
3661
+ async function commandAgentPull(args) {
3662
+ assertKnownAgentOptions(args, ["output", "json", "apiUrl"]);
3663
+ const ref = agentArg(args, "pull");
3664
+ const output = optionString(args, "output");
3665
+ const config = requireLogin(args);
3666
+ const agent = await fetchAgent(config, ref);
3667
+ const manifest = JSON.stringify(agent.manifest, null, 2) + "\n";
3668
+ if (output) {
3669
+ writeFileSync(resolve(process.cwd(), output), manifest, { mode: 0o644 });
3670
+ console.log(`Wrote ${output}`);
3671
+ return;
3672
+ }
3673
+ process.stdout.write(manifest);
3674
+ }
3675
+ async function commandAgentCreate(args) {
3676
+ assertKnownAgentOptions(args, ["file", "json", "apiUrl"]);
3677
+ const manifest = readAgentManifest(args);
3678
+ const config = requireLogin(args);
3679
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents`, {
3680
+ method: "POST",
3681
+ headers: { "Content-Type": "application/json" },
3682
+ body: JSON.stringify({ manifest }),
3683
+ });
3684
+ if (resp.status === 401)
3685
+ await reLoginOrThrow();
3686
+ const result = (await readJsonOrThrow(resp, "Create agent"));
3687
+ printAgentMutation(result, args.options.json === true, "created");
3688
+ }
3689
+ async function commandAgentUpdate(args) {
3690
+ assertKnownAgentOptions(args, ["file", "json", "apiUrl"]);
3691
+ const ref = agentArg(args, "update");
3692
+ const manifest = readAgentManifest(args);
3693
+ const config = requireLogin(args);
3694
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}`, {
3695
+ method: "PUT",
3696
+ headers: { "Content-Type": "application/json" },
3697
+ body: JSON.stringify({ manifest }),
3698
+ });
3699
+ if (resp.status === 401)
3700
+ await reLoginOrThrow();
3701
+ const result = (await readJsonOrThrow(resp, `Update agent "${ref}"`));
3702
+ printAgentMutation(result, args.options.json === true, "updated");
3703
+ }
3704
+ async function commandAgentDelete(args) {
3705
+ assertKnownAgentOptions(args, ["yes", "apiUrl"]);
3706
+ const ref = agentArg(args, "delete");
3707
+ if (args.options.yes !== true) {
3708
+ if (!process.stdin.isTTY) {
3709
+ throw new CliError("Pass --yes to delete an agent in a non-interactive session.", 2);
3710
+ }
3711
+ const answer = await prompt(`Delete agent "${ref}"? Its run history is kept. [y/N] `);
3712
+ if (!["y", "yes"].includes(answer.trim().toLowerCase())) {
3713
+ throw new CliError("Delete cancelled.", 1);
3714
+ }
3715
+ }
3716
+ const config = requireLogin(args);
3717
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}`, { method: "DELETE" });
3718
+ if (resp.status === 401)
3719
+ await reLoginOrThrow();
3720
+ if (!resp.ok) {
3721
+ throw new CliError(`Delete agent "${ref}" failed (${resp.status}): ${await errorDetail(resp)}`, 1);
3722
+ }
3723
+ console.log(`Agent "${ref}" deleted.`);
3724
+ }
3725
+ async function commandAgentTest(args) {
3726
+ assertKnownAgentOptions(args, ["file", "input", "inputFile", "json", "trace", "apiUrl"]);
3727
+ const manifest = readAgentManifest(args);
3728
+ const input = readJsonInput(args);
3729
+ const config = requireLogin(args);
3730
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/test`, {
3731
+ method: "POST",
3732
+ headers: { "Content-Type": "application/json" },
3733
+ body: JSON.stringify({ manifest, input }),
3734
+ });
3735
+ if (resp.status === 401)
3736
+ await reLoginOrThrow();
3737
+ const run = (await readJsonOrThrow(resp, "Test agent"));
3738
+ printAgentRun(run, { json: args.options.json === true, trace: args.options.trace === true });
3739
+ }
3740
+ async function commandAgentRun(args) {
3741
+ assertKnownAgentOptions(args, ["input", "inputFile", "json", "trace", "apiUrl"]);
3742
+ const ref = agentArg(args, "run");
3743
+ const input = readJsonInput(args);
3744
+ const config = requireLogin(args);
3745
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/invoke`, {
3746
+ method: "POST",
3747
+ headers: { "Content-Type": "application/json" },
3748
+ body: JSON.stringify({ input }),
3749
+ });
3750
+ if (resp.status === 401)
3751
+ await reLoginOrThrow();
3752
+ const run = (await readJsonOrThrow(resp, `Run agent "${ref}"`));
3753
+ printAgentRun(run, { json: args.options.json === true, trace: args.options.trace === true });
3754
+ }
3755
+ async function commandAgentSchedule(args) {
3756
+ const sub = args.rest[1] ?? "";
3757
+ switch (sub) {
3758
+ case "show":
3759
+ case "list":
3760
+ case "ls":
3761
+ await commandAgentScheduleShow(args);
3762
+ return;
3763
+ case "set":
3764
+ await commandAgentScheduleSet(args, "upsert");
3765
+ return;
3766
+ case "add":
3767
+ case "create":
3768
+ await commandAgentScheduleSet(args, "create");
3769
+ return;
3770
+ case "update":
3771
+ await commandAgentScheduleSet(args, "update");
3772
+ return;
3773
+ case "pause":
3774
+ await commandAgentScheduleEnabled(args, false);
3775
+ return;
3776
+ case "resume":
3777
+ await commandAgentScheduleEnabled(args, true);
3778
+ return;
3779
+ case "delete":
3780
+ case "rm":
3781
+ await commandAgentScheduleDelete(args);
3782
+ return;
3783
+ case "run-now":
3784
+ case "run":
3785
+ await commandAgentScheduleRunNow(args);
3786
+ return;
3787
+ case "":
3788
+ case "help":
3789
+ console.log(AGENT_HELP);
3790
+ return;
3791
+ default:
3792
+ throw new CliError(`Unknown agent schedule command: ${sub}\n\n${AGENT_HELP}`, 2);
3793
+ }
3794
+ }
3795
+ async function commandAgentScheduleShow(args) {
3796
+ assertKnownAgentOptions(args, ["json", "apiUrl"]);
3797
+ const ref = agentScheduleAgentArg(args, "show");
3798
+ const config = requireLogin(args);
3799
+ const schedules = await fetchAgentSchedules(config, ref);
3800
+ if (args.options.json) {
3801
+ console.log(JSON.stringify(schedules, null, 2));
3802
+ return;
3803
+ }
3804
+ if (schedules.length === 0) {
3805
+ console.log(`Agent "${ref}" has no schedule.`);
3806
+ return;
3807
+ }
3808
+ printSchedule(schedules[0]);
3809
+ }
3810
+ async function commandAgentScheduleSet(args, mode) {
3811
+ assertKnownAgentOptions(args, [
3812
+ "name",
3813
+ "cron",
3814
+ "schedule",
3815
+ "timezone",
3816
+ "enabled",
3817
+ "disabled",
3818
+ "json",
3819
+ "apiUrl",
3820
+ ]);
3821
+ const ref = agentScheduleAgentArg(args, mode === "upsert" ? "set" : mode);
3822
+ const config = requireLogin(args);
3823
+ const existing = await fetchAgentSchedules(config, ref);
3824
+ if (mode === "create" && existing.length > 0) {
3825
+ throw new CliError(`Agent "${ref}" already has a schedule. Use \`railcode agent schedule set ${ref}\` to update it.`, 1);
3826
+ }
3827
+ if (mode === "update" && existing.length === 0) {
3828
+ throw new CliError(`Agent "${ref}" has no schedule. Use \`railcode agent schedule set ${ref} --cron "0 9 * * *"\` to create one.`, 1);
3829
+ }
3830
+ const body = schedulePayload(args, existing.length === 0);
3831
+ const path = existing.length === 0
3832
+ ? `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules`
3833
+ : `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules/${encodeURIComponent(existing[0].uuid)}`;
3834
+ const resp = await authedFetch(config, path, {
3835
+ method: existing.length === 0 ? "POST" : "PATCH",
3836
+ headers: { "Content-Type": "application/json" },
3837
+ body: JSON.stringify(body),
3838
+ });
3839
+ if (resp.status === 401)
3840
+ await reLoginOrThrow();
3841
+ const schedule = (await readJsonOrThrow(resp, `${existing.length === 0 ? "Create" : "Update"} agent schedule`));
3842
+ if (args.options.json) {
3843
+ console.log(JSON.stringify(schedule, null, 2));
3844
+ return;
3845
+ }
3846
+ console.log(existing.length === 0 ? "Schedule created." : "Schedule updated.");
3847
+ printSchedule(schedule);
3848
+ }
3849
+ async function commandAgentScheduleEnabled(args, enabled) {
3850
+ assertKnownAgentOptions(args, ["json", "apiUrl"]);
3851
+ const ref = agentScheduleAgentArg(args, enabled ? "resume" : "pause");
3852
+ const config = requireLogin(args);
3853
+ const schedule = await requireSingleSchedule(config, ref);
3854
+ const updated = await patchAgentSchedule(config, ref, schedule.uuid, { enabled });
3855
+ if (args.options.json) {
3856
+ console.log(JSON.stringify(updated, null, 2));
3857
+ return;
3858
+ }
3859
+ console.log(enabled ? "Schedule resumed." : "Schedule paused.");
3860
+ printSchedule(updated);
3861
+ }
3862
+ async function commandAgentScheduleDelete(args) {
3863
+ assertKnownAgentOptions(args, ["yes", "apiUrl"]);
3864
+ const ref = agentScheduleAgentArg(args, "delete");
3865
+ const config = requireLogin(args);
3866
+ const schedule = await requireSingleSchedule(config, ref);
3867
+ if (args.options.yes !== true) {
3868
+ if (!process.stdin.isTTY) {
3869
+ throw new CliError("Pass --yes to delete a schedule in a non-interactive session.", 2);
3870
+ }
3871
+ const answer = await prompt(`Delete schedule "${schedule.name}" for agent "${ref}"? [y/N] `);
3872
+ if (!["y", "yes"].includes(answer.trim().toLowerCase())) {
3873
+ throw new CliError("Delete cancelled.", 1);
3874
+ }
3875
+ }
3876
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules/${encodeURIComponent(schedule.uuid)}`, { method: "DELETE" });
3877
+ if (resp.status === 401)
3878
+ await reLoginOrThrow();
3879
+ if (!resp.ok) {
3880
+ throw new CliError(`Delete agent schedule failed (${resp.status}): ${await errorDetail(resp)}`, 1);
3881
+ }
3882
+ console.log(`Schedule "${schedule.name}" deleted.`);
3883
+ }
3884
+ async function commandAgentScheduleRunNow(args) {
3885
+ assertKnownAgentOptions(args, ["json", "trace", "apiUrl"]);
3886
+ const ref = agentScheduleAgentArg(args, "run-now");
3887
+ const config = requireLogin(args);
3888
+ const schedule = await requireSingleSchedule(config, ref);
3889
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules/${encodeURIComponent(schedule.uuid)}/run-now`, { method: "POST" });
3890
+ if (resp.status === 401)
3891
+ await reLoginOrThrow();
3892
+ const run = (await readJsonOrThrow(resp, `Run schedule "${schedule.name}"`));
3893
+ printAgentRun(run, { json: args.options.json === true, trace: args.options.trace === true });
3894
+ }
3895
+ async function fetchAgents(config) {
3896
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents`);
3897
+ if (resp.status === 401)
3898
+ await reLoginOrThrow();
3899
+ return (await readJsonOrThrow(resp, "List agents"));
3900
+ }
3901
+ async function fetchAgent(config, ref) {
3902
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}`);
3903
+ if (resp.status === 401)
3904
+ await reLoginOrThrow();
3905
+ return (await readJsonOrThrow(resp, `Fetch agent "${ref}"`));
3906
+ }
3907
+ async function fetchAgentSchedules(config, ref) {
3908
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules`);
3909
+ if (resp.status === 401)
3910
+ await reLoginOrThrow();
3911
+ return (await readJsonOrThrow(resp, `List schedules for agent "${ref}"`));
3912
+ }
3913
+ async function patchAgentSchedule(config, ref, scheduleUuid, body) {
3914
+ const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules/${encodeURIComponent(scheduleUuid)}`, {
3915
+ method: "PATCH",
3916
+ headers: { "Content-Type": "application/json" },
3917
+ body: JSON.stringify(body),
3918
+ });
3919
+ if (resp.status === 401)
3920
+ await reLoginOrThrow();
3921
+ return (await readJsonOrThrow(resp, "Update agent schedule"));
3922
+ }
3923
+ async function requireSingleSchedule(config, ref) {
3924
+ const schedules = await fetchAgentSchedules(config, ref);
3925
+ if (schedules.length === 0) {
3926
+ throw new CliError(`Agent "${ref}" has no schedule.`, 1);
3927
+ }
3928
+ return schedules[0];
3929
+ }
3930
+ function readAgentManifest(args) {
3931
+ const path = optionString(args, "file");
3932
+ if (!path)
3933
+ throw new CliError("Pass --file <agent-manifest.json>.", 2);
3934
+ return readJsonObjectFile(path, "Agent manifest");
3935
+ }
3936
+ function readJsonObjectFile(path, label) {
3937
+ const resolved = resolve(process.cwd(), path);
3938
+ if (!existsSync(resolved))
3939
+ throw new CliError(`${label} file not found: ${path}`, 2);
3940
+ let parsed;
3941
+ try {
3942
+ parsed = JSON.parse(readFileSync(resolved, "utf8"));
3943
+ }
3944
+ catch (error) {
3945
+ throw new CliError(`${label} file is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, 2);
3946
+ }
3947
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3948
+ throw new CliError(`${label} file must contain a JSON object.`, 2);
3949
+ }
3950
+ return parsed;
3951
+ }
3952
+ function readJsonInput(args) {
3953
+ const inline = optionString(args, "input");
3954
+ const file = optionString(args, "inputFile");
3955
+ if (inline !== undefined && file !== undefined) {
3956
+ throw new CliError("Pass either --input or --input-file, not both.", 2);
3957
+ }
3958
+ if (inline === undefined && file === undefined)
3959
+ return null;
3960
+ const raw = inline ?? readFileSync(resolve(process.cwd(), file), "utf8");
3961
+ try {
3962
+ return JSON.parse(raw);
3963
+ }
3964
+ catch (error) {
3965
+ throw new CliError(`Agent input is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, 2);
3966
+ }
3967
+ }
3968
+ function schedulePayload(args, creating) {
3969
+ const body = {};
3970
+ const name = optionString(args, "name");
3971
+ const cron = optionString(args, "cron") ?? optionString(args, "schedule");
3972
+ const timezone = optionString(args, "timezone");
3973
+ if (args.options.enabled === true && args.options.disabled === true) {
3974
+ throw new CliError("Pass either --enabled or --disabled, not both.", 2);
3975
+ }
3976
+ if (name !== undefined)
3977
+ body.name = name;
3978
+ if (cron !== undefined)
3979
+ body.schedule = cron;
3980
+ if (timezone !== undefined)
3981
+ body.timezone = timezone;
3982
+ if (args.options.enabled === true)
3983
+ body.enabled = true;
3984
+ if (args.options.disabled === true)
3985
+ body.enabled = false;
3986
+ if (creating) {
3987
+ if (!body.name)
3988
+ body.name = "default";
3989
+ if (!body.schedule)
3990
+ throw new CliError("Creating an agent schedule requires --cron.", 2);
3991
+ if (!body.timezone)
3992
+ body.timezone = "UTC";
3993
+ if (body.enabled === undefined)
3994
+ body.enabled = true;
3995
+ }
3996
+ else if (Object.keys(body).length === 0) {
3997
+ throw new CliError("Nothing to update. Pass --name, --cron, --timezone, --enabled or --disabled.", 2);
3998
+ }
3999
+ return body;
4000
+ }
4001
+ function agentArg(args, command) {
4002
+ const ref = args.rest[1];
4003
+ if (!ref || args.rest.length > 2) {
4004
+ throw new CliError(`Usage: railcode agent ${command} <agent>`, 2);
4005
+ }
4006
+ return ref;
4007
+ }
4008
+ function agentScheduleAgentArg(args, command) {
4009
+ const ref = args.rest[2];
4010
+ if (!ref || args.rest.length > 3) {
4011
+ throw new CliError(`Usage: railcode agent schedule ${command} <agent>`, 2);
4012
+ }
4013
+ return ref;
4014
+ }
4015
+ function printAgentMutation(result, json, verb) {
4016
+ if (json) {
4017
+ console.log(JSON.stringify(result, null, 2));
4018
+ return;
4019
+ }
4020
+ console.log(`Agent "${result.agent.name}" ${verb}.`);
4021
+ console.log(`UUID: ${result.agent.uuid}`);
4022
+ if (result.warnings.length > 0) {
4023
+ console.log("");
4024
+ console.log("Warnings:");
4025
+ for (const warning of result.warnings)
4026
+ console.log(`- ${warning}`);
4027
+ }
4028
+ }
4029
+ function printAgent(agent) {
4030
+ console.log(agent.name);
4031
+ console.log(`UUID: ${agent.uuid}`);
4032
+ console.log(`Description: ${agent.description || "-"}`);
4033
+ console.log(`Model: ${typeof agent.manifest.model === "string" ? agent.manifest.model : "-"}`);
4034
+ console.log(`Updated: ${agent.updated_at}`);
4035
+ console.log(`Content hash: ${agent.content_hash}`);
4036
+ }
4037
+ function printAgentRun(run, options) {
4038
+ if (options.json) {
4039
+ console.log(JSON.stringify(run, null, 2));
4040
+ return;
4041
+ }
4042
+ console.log(`Status: ${run.status}`);
4043
+ console.log(`Request: ${run.request_id}`);
4044
+ if (run.error_code || run.error_message) {
4045
+ console.log(`Error: ${[run.error_code, run.error_message].filter(Boolean).join(" - ")}`);
4046
+ }
4047
+ console.log("Output:");
4048
+ console.log(JSON.stringify(run.output_json, null, 2));
4049
+ if (options.trace) {
4050
+ console.log("");
4051
+ if (run.steps.length === 0) {
4052
+ console.log("Trace: no steps");
4053
+ }
4054
+ else {
4055
+ console.log("Trace:");
4056
+ console.log(formatTable(["#", "kind", "status", "tool", "summary"], run.steps.map((step) => [
4057
+ String(step.step_index),
4058
+ step.kind,
4059
+ step.status,
4060
+ step.tool_name ?? "-",
4061
+ step.result_summary ?? step.args_summary ?? step.error_message ?? "-",
4062
+ ])));
4063
+ }
4064
+ }
4065
+ }
4066
+ function printSchedule(schedule) {
4067
+ console.log(schedule.name);
4068
+ console.log(`UUID: ${schedule.uuid}`);
4069
+ console.log(`Cron: ${schedule.schedule}`);
4070
+ console.log(`Timezone: ${schedule.timezone}`);
4071
+ console.log(`Enabled: ${schedule.enabled ? "yes" : "no"}`);
4072
+ console.log(`Next fire: ${schedule.next_fire_at}`);
4073
+ console.log(`Last fire: ${schedule.last_fire_at ?? "-"}`);
4074
+ console.log(`Last run: ${schedule.last_run_uuid ?? "-"}`);
4075
+ }
4076
+ function assertKnownAgentOptions(args, allowed) {
4077
+ for (const key of Object.keys(args.options)) {
4078
+ if (!allowed.includes(key)) {
4079
+ throw new CliError(`Unknown option --${camelToKebab(key)} for \`railcode agent\`.\n\n${AGENT_HELP}`, 2);
4080
+ }
4081
+ }
4082
+ }
4083
+ function shortDate(value) {
4084
+ return value.includes("T") ? value.slice(0, 10) : value;
4085
+ }
4086
+ // ---------------------------------------------------------------------------
3537
4087
  // db — list data connectors + run read-only SQL (`railcode db list|query`)
3538
4088
  //
3539
4089
  // Data connectors are org-wide, so these commands use the app-less org data
@@ -4382,8 +4932,9 @@ class CliError extends Error {
4382
4932
  // dev — local development server (`railcode dev`)
4383
4933
  //
4384
4934
  // Serves the app on a single loopback origin and emulates the /_api/* data plane:
4385
- // identity is synthetic, KV + files live on local disk. llm/sql/connections proxy
4386
- // to the real instance (added in a later milestone). Mirrors railcode-core's dev.
4935
+ // identity is synthetic, KV + files live on local disk. llm/sql/queries/connectors/email
4936
+ // proxy to the real instance as the signed-in user (their token + org grants) via the
4937
+ // org/user-scoped routes — no deploy needed. Mirrors railcode-core's dev.
4387
4938
  // ---------------------------------------------------------------------------
4388
4939
  const DEFAULT_DEV_PORT = 7331;
4389
4940
  const DEFAULT_ASSET_PORT = 5173;
@@ -4922,8 +5473,10 @@ async function handleLocalApi(ctx, req, res, url) {
4922
5473
  path === "/llm/generate" ||
4923
5474
  path === "/llm/stream" ||
4924
5475
  path === "/llm/providers" ||
5476
+ path === "/email" ||
5477
+ path === "/email/send" ||
4925
5478
  path === "/service-connectors" ||
4926
- path === "/service-connectors/request") {
5479
+ path.startsWith("/service-connectors/")) {
4927
5480
  await forwardCompute(ctx, req, res, path, url);
4928
5481
  return;
4929
5482
  }
@@ -5113,27 +5666,29 @@ function devCreds(config) {
5113
5666
  return null;
5114
5667
  return { apiUrl: normalizeApiOrigin(apiUrl), token, orgUuid };
5115
5668
  }
5116
- // Resolve the server-side app for proxying lazily and memoized. Local dev must
5117
- // not create product apps: an app becomes real on first successful deploy, not on
5118
- // a dev-time SQL/LLM/connector call.
5119
- function resolveDevAppUuid(ctx, creds) {
5120
- const base = `${creds.apiUrl}/api/organizations/${creds.orgUuid}/apps`;
5121
- const authed = { Authorization: `Bearer ${creds.token}` };
5122
- if (!ctx.appUuidPromise) {
5123
- ctx.appUuidPromise = (async () => {
5124
- try {
5125
- const resp = await fetch(base, { headers: authed });
5126
- if (!resp.ok)
5127
- return null;
5128
- const apps = (await resp.json());
5129
- return apps.find((a) => a.app_slug === ctx.app && a.status === "active")?.uuid ?? null;
5130
- }
5131
- catch {
5132
- return null;
5133
- }
5134
- })();
5135
- }
5136
- return ctx.appUuidPromise;
5669
+ // Map a local-dev /_api compute path to the real org/user-scoped upstream URL. Local
5670
+ // dev invokes compute as the signed-in user (their personal token + org grants), NOT
5671
+ // as a deployed app — so no `railcode deploy` is needed first. The app slug still
5672
+ // namespaces local KV/files, but plays no part here. Deployed apps keep their own
5673
+ // bearer app-scoped routes (.../apps/{app}/...); this is deliberately the user plane.
5674
+ export function devComputeTarget(creds, path, search) {
5675
+ const org = `${creds.apiUrl}/api/organizations/${creds.orgUuid}`;
5676
+ if (path === "/connections")
5677
+ return `${org}/data/connections${search}`;
5678
+ if (path === "/sql")
5679
+ return `${org}/data/sql${search}`;
5680
+ if (path === "/queries" || path.startsWith("/queries/"))
5681
+ return `${org}/data${path}${search}`;
5682
+ if (path === "/service-connectors" || path.startsWith("/service-connectors/")) {
5683
+ return `${org}/data${path}${search}`;
5684
+ }
5685
+ if (path === "/llm/providers" || path === "/llm/generate" || path === "/llm/stream") {
5686
+ return `${org}${path}${search}`;
5687
+ }
5688
+ if (path === "/email" || path === "/email/send") {
5689
+ return `${org}${path}${search}`;
5690
+ }
5691
+ return null;
5137
5692
  }
5138
5693
  export function devUpstreamAuthFallback(status, degradeOk) {
5139
5694
  if (status !== 401)
@@ -5164,19 +5719,13 @@ async function forwardCompute(ctx, req, res, path, url) {
5164
5719
  return sendJson(res, 200, []);
5165
5720
  return sendJson(res, 503, {
5166
5721
  error: "not_logged_in",
5167
- message: "Run `railcode login` to use LLM and connectors in dev.",
5722
+ message: "Run `railcode login` to use LLM, SQL, saved queries, connectors, and email in dev.",
5168
5723
  });
5169
5724
  }
5170
- const appUuid = await resolveDevAppUuid(ctx, creds);
5171
- if (!appUuid) {
5172
- if (degradeOk)
5173
- return sendJson(res, 200, []);
5174
- return sendJson(res, 503, {
5175
- error: "app_unavailable",
5176
- message: "Deploy this app once before using LLM, SQL, saved queries, or connectors in dev.",
5177
- });
5725
+ const target = devComputeTarget(creds, path, url.search);
5726
+ if (!target) {
5727
+ return sendJson(res, 404, { detail: "not found" });
5178
5728
  }
5179
- const target = `${creds.apiUrl}/api/organizations/${creds.orgUuid}/apps/${appUuid}${path}${url.search}`;
5180
5729
  const body = req.method === "POST" ? await readBody(req) : undefined;
5181
5730
  const headers = {
5182
5731
  Authorization: `Bearer ${creds.token}`,
@@ -5409,10 +5958,11 @@ function printDevBanner(ctx, config) {
5409
5958
  const creds = devCreds(config);
5410
5959
  if (creds) {
5411
5960
  console.log(` account: ${config.email ?? "?"} @ ${creds.apiUrl}`);
5412
- console.log(` proxied: llm/sql/connectors → real instance (REAL spend + data)`);
5961
+ console.log(` compute: llm/sql/queries/connectors/email → real instance (REAL spend + data)`);
5962
+ console.log(` runs as: you — your saved token + org permissions (no deploy needed)`);
5413
5963
  }
5414
5964
  else {
5415
- console.log(` account: not logged in — llm/sql return 503, connectors empty`);
5965
+ console.log(` account: not logged in — llm/sql/email return 503, connectors empty`);
5416
5966
  }
5417
5967
  console.log("");
5418
5968
  console.log(" Ctrl-C to stop.");
package/dist/manifest.js CHANGED
@@ -13,8 +13,11 @@
13
13
  // - GET /v1/balance
14
14
  // - GET /v1/customers/*
15
15
  // llm: true
16
+ // email: true
16
17
  // adhoc_sql:
17
18
  // - warehouse
19
+ // agents:
20
+ // - sales-digest
18
21
  //
19
22
  // Also supported, matching PyYAML: flow lists (`saved_queries: [a, b]`), a flow
20
23
  // map for connectors (`connectors: {stripe: [GET /v1/balance]}`), a whole-
@@ -30,7 +33,15 @@
30
33
  // test/manifest-fixtures.json (also run by
31
34
  // backend/tests/test_manifest_parse_fixtures.py) keeps the two parsers pinned.
32
35
  export const APP_MANIFEST_NAME = "manifest.yaml";
33
- const ALLOWED_KEYS = ["run_as", "saved_queries", "connectors", "llm", "adhoc_sql"];
36
+ const ALLOWED_KEYS = [
37
+ "run_as",
38
+ "saved_queries",
39
+ "connectors",
40
+ "llm",
41
+ "email",
42
+ "adhoc_sql",
43
+ "agents",
44
+ ];
34
45
  const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
35
46
  const SAVED_QUERY_NAME = /^[a-z0-9_]{1,80}$/;
36
47
  // PyYAML's YAML 1.1 plain-scalar resolver: a bare (unquoted) scalar matching one
@@ -322,7 +333,7 @@ function listString(s, key, num) {
322
333
  throw new ManifestParseError(`Empty list item under "${key}"`, num);
323
334
  return value;
324
335
  }
325
- function parseBool(s, num) {
336
+ function parseBool(s, key, num) {
326
337
  // A quoted scalar is a STRING even if it spells a boolean — as on the server.
327
338
  if (!s.quoted) {
328
339
  if (YAML_TRUE.test(s.value))
@@ -330,7 +341,7 @@ function parseBool(s, num) {
330
341
  if (YAML_FALSE.test(s.value))
331
342
  return false;
332
343
  }
333
- throw new ManifestParseError(`"llm" must be true or false (got "${s.value}")`, num);
344
+ throw new ManifestParseError(`"${key}" must be true or false (got "${s.value}")`, num);
334
345
  }
335
346
  // Strip the optional document markers: one leading "---" and one trailing "..."
336
347
  // are no-ops. Anything more is a multi-document stream, which the server
@@ -382,7 +393,9 @@ export function parseManifestYaml(source) {
382
393
  saved_queries: [],
383
394
  connectors: {},
384
395
  llm: false,
396
+ email: false,
385
397
  adhoc_sql: [],
398
+ agents: [],
386
399
  };
387
400
  for (const [key, entry] of entries) {
388
401
  if (key === "run_as") {
@@ -393,16 +406,21 @@ export function parseManifestYaml(source) {
393
406
  doc.run_as = value;
394
407
  }
395
408
  else if (key === "llm") {
396
- doc.llm = parseBool(inlineScalar(entry, key), entry.keyLine.num);
409
+ doc.llm = parseBool(inlineScalar(entry, key), key, entry.keyLine.num);
410
+ }
411
+ else if (key === "email") {
412
+ doc.email = parseBool(inlineScalar(entry, key), key, entry.keyLine.num);
397
413
  }
398
414
  else if (key === "connectors") {
399
415
  doc.connectors = parseConnectors(entry);
400
416
  }
401
417
  else {
402
- // saved_queries / adhoc_sql: a list of names.
418
+ // saved_queries / adhoc_sql / agents: a list of names.
403
419
  const items = parseStringList(entry, key);
404
420
  if (key === "saved_queries")
405
421
  doc.saved_queries = items;
422
+ else if (key === "agents")
423
+ doc.agents = items;
406
424
  else
407
425
  doc.adhoc_sql = items;
408
426
  }
@@ -624,6 +642,7 @@ function validateDoc(doc, lines) {
624
642
  }
625
643
  doc.saved_queries = [...new Set(doc.saved_queries)].sort();
626
644
  doc.adhoc_sql = [...new Set(doc.adhoc_sql)].sort();
645
+ doc.agents = [...new Set(doc.agents)].sort();
627
646
  for (const name of Object.keys(doc.connectors)) {
628
647
  doc.connectors[name] = [...new Set(doc.connectors[name])].sort();
629
648
  }
@@ -644,6 +663,8 @@ export function documentOperations(doc) {
644
663
  }
645
664
  if (doc.llm)
646
665
  ops.push({ type: "llm", id: "*", label: "LLM access" });
666
+ if (doc.email)
667
+ ops.push({ type: "email", id: "*", label: "email sending" });
647
668
  for (const name of doc.adhoc_sql) {
648
669
  ops.push({ type: "connector", id: name, label: `ad-hoc SQL on "${name}"` });
649
670
  }
@@ -666,6 +687,9 @@ export function renderManifestSummary(doc) {
666
687
  if (doc.adhoc_sql.length > 0) {
667
688
  lines.push("warning: adhoc_sql grants ad-hoc SQL authority — scarce by design; prefer saved queries");
668
689
  }
690
+ if (doc.agents.length > 0) {
691
+ lines.push(`agents: ${doc.agents.join(", ")}`);
692
+ }
669
693
  return lines.join("\n");
670
694
  }
671
695
  export function renderManifestDeployOutcome(m) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "railcode",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Developer CLI for the multi-tenant Railcode platform: log in, scaffold, and deploy static apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/static/sdk.js CHANGED
@@ -175,8 +175,8 @@
175
175
  }
176
176
  return shorten(String(res), 48);
177
177
  }
178
- function track(kind, label2, thunk) {
179
- const entry = { kind, label: label2, status: "pending", t0: performance.now() };
178
+ function track(kind, label3, thunk) {
179
+ const entry = { kind, label: label3, status: "pending", t0: performance.now() };
180
180
  Inspector.add(entry);
181
181
  return thunk().then(
182
182
  (res) => {
@@ -195,8 +195,8 @@
195
195
  }
196
196
  );
197
197
  }
198
- function trackStream(kind, label2, streamFactory) {
199
- const entry = { kind, label: label2, status: "pending", t0: performance.now() };
198
+ function trackStream(kind, label3, streamFactory) {
199
+ const entry = { kind, label: label3, status: "pending", t0: performance.now() };
200
200
  Inspector.add(entry);
201
201
  return async function* () {
202
202
  let textLen = 0;
@@ -219,6 +219,15 @@
219
219
  }();
220
220
  }
221
221
 
222
+ // ../sdk/src/agents.ts
223
+ async function doInvoke(name, input) {
224
+ return call("POST", `/agents/${encodeURIComponent(name)}`, { body: { input } });
225
+ }
226
+ function invoke(name, input) {
227
+ return track("agents", `agents.invoke(${q(name)})`, () => doInvoke(name, input));
228
+ }
229
+ var agents = { invoke };
230
+
222
231
  // ../sdk/src/databases.ts
223
232
  var QUERY_LABEL_MAX = 80;
224
233
  var PARAMS_LABEL_MAX = 40;
@@ -234,10 +243,10 @@
234
243
  };
235
244
  var runSQL = (engine, connection, query2, params) => {
236
245
  const trimmed = shorten(query2.replace(/\s+/g, " ").trim(), QUERY_LABEL_MAX);
237
- const label2 = `${engine ?? "data"}(${q(connection)}).runSQL(${q(trimmed)}${params && params.length ? `, ${shorten(JSON.stringify(params), PARAMS_LABEL_MAX)}` : ""})`;
246
+ const label3 = `${engine ?? "data"}(${q(connection)}).runSQL(${q(trimmed)}${params && params.length ? `, ${shorten(JSON.stringify(params), PARAMS_LABEL_MAX)}` : ""})`;
238
247
  return track(
239
248
  "sql",
240
- label2,
249
+ label3,
241
250
  () => call("POST", "/sql", {
242
251
  // engine is advisory; omit it for the generic data() namespace so the backend
243
252
  // dispatches on the connection's stored kind. postgres() pins engine="postgres".
@@ -387,6 +396,32 @@
387
396
  return resp.markdown;
388
397
  });
389
398
 
399
+ // ../sdk/src/email.ts
400
+ function label(opts) {
401
+ const to = Array.isArray(opts.to) ? `${opts.to.length} recipients` : opts.to;
402
+ return `email.send(${q(shorten(`${to}: ${opts.subject}`, 70))})`;
403
+ }
404
+ function body(opts) {
405
+ const out = {
406
+ to: opts.to,
407
+ subject: opts.subject
408
+ };
409
+ if (opts.html !== void 0) out.html = opts.html;
410
+ if (opts.text !== void 0) out.text = opts.text;
411
+ if (opts.cc !== void 0) out.cc = opts.cc;
412
+ if (opts.bcc !== void 0) out.bcc = opts.bcc;
413
+ if (opts.replyTo !== void 0) out.replyTo = opts.replyTo;
414
+ return out;
415
+ }
416
+ function send2(opts) {
417
+ return track(
418
+ "email",
419
+ label(opts),
420
+ () => call("POST", "/email/send", { body: body(opts) })
421
+ );
422
+ }
423
+ var email = { send: send2 };
424
+
390
425
  // ../sdk/src/files.ts
391
426
  function contentTypeOf(data2, explicit) {
392
427
  if (explicit) return explicit;
@@ -431,12 +466,12 @@
431
466
  var appUsers = () => track("app-users", "appUsers()", () => call("GET", "/app-users"));
432
467
 
433
468
  // ../sdk/src/llm.ts
434
- function label(method, input, opts = {}) {
469
+ function label2(method, input, opts = {}) {
435
470
  const preview = typeof input === "string" ? shorten(input.replace(/\s+/g, " ").trim(), 70) : `${input.length} message${input.length === 1 ? "" : "s"}`;
436
471
  const output = opts.output?.type === "json" ? " \u2192 json" : "";
437
472
  return `llm.${method}(${q(preview)})${output}`;
438
473
  }
439
- function body(input, opts = {}) {
474
+ function body2(input, opts = {}) {
440
475
  const out = Array.isArray(input) ? { messages: input } : { input };
441
476
  if (opts.model !== void 0) out.model = opts.model;
442
477
  if (opts.provider !== void 0) out.provider = opts.provider;
@@ -452,7 +487,7 @@
452
487
  method: "POST",
453
488
  credentials: "include",
454
489
  headers: { "Content-Type": "application/json" },
455
- body: JSON.stringify(body(input, opts))
490
+ body: JSON.stringify(body2(input, opts))
456
491
  });
457
492
  if (!resp.ok) throw new ApiError(resp.status, await resp.text());
458
493
  return await resp.json();
@@ -465,7 +500,7 @@
465
500
  method: "POST",
466
501
  credentials: "include",
467
502
  headers: { "Content-Type": "application/json" },
468
- body: JSON.stringify(body(input, opts))
503
+ body: JSON.stringify(body2(input, opts))
469
504
  });
470
505
  if (!resp.ok) throw new ApiError(resp.status, await resp.text());
471
506
  if (!resp.body) throw new Error("LLM stream response has no body.");
@@ -493,10 +528,10 @@
493
528
  }
494
529
  }
495
530
  function generate(input, opts = {}) {
496
- return track("llm", label("generate", input, opts), () => doGenerate(input, opts));
531
+ return track("llm", label2("generate", input, opts), () => doGenerate(input, opts));
497
532
  }
498
533
  function stream(input, opts = {}) {
499
- return trackStream("llm", label("stream", input, opts), () => doStream(input, opts));
534
+ return trackStream("llm", label2("stream", input, opts), () => doStream(input, opts));
500
535
  }
501
536
  var llm = { generate, stream };
502
537
  var llmProviders = () => track("llm", "llmProviders()", () => call("GET", "/llm/providers"));
@@ -504,10 +539,10 @@
504
539
  // ../sdk/src/queries.ts
505
540
  var PARAMS_LABEL_MAX2 = 40;
506
541
  var query = (name, params) => {
507
- const label2 = `query(${q(name)}${params && Object.keys(params).length ? `, ${shorten(JSON.stringify(params), PARAMS_LABEL_MAX2)}` : ""})`;
542
+ const label3 = `query(${q(name)}${params && Object.keys(params).length ? `, ${shorten(JSON.stringify(params), PARAMS_LABEL_MAX2)}` : ""})`;
508
543
  return track(
509
544
  "sql",
510
- label2,
545
+ label3,
511
546
  () => call("POST", `/queries/${encPath(name)}`, {
512
547
  body: { params: params || {} }
513
548
  }).then(toSqlRows)
@@ -518,22 +553,22 @@
518
553
  // ../sdk/src/service-connectors.ts
519
554
  var PATH_LABEL_MAX = 60;
520
555
  var toResponse = (env) => {
521
- const body2 = env.body ?? "";
556
+ const body3 = env.body ?? "";
522
557
  return {
523
558
  status: env.status,
524
559
  ok: env.ok,
525
560
  headers: env.headers || {},
526
561
  truncated: Boolean(env.truncated),
527
- text: () => Promise.resolve(body2),
528
- json: () => Promise.resolve(JSON.parse(body2))
562
+ text: () => Promise.resolve(body3),
563
+ json: () => Promise.resolve(JSON.parse(body3))
529
564
  };
530
565
  };
531
566
  var request = (name, path, opts) => {
532
567
  const method = (opts?.method || "GET").toUpperCase();
533
- const label2 = `connector(${q(name)}).fetch(${q(method)} ${q(shorten(path, PATH_LABEL_MAX))})`;
568
+ const label3 = `connector(${q(name)}).fetch(${q(method)} ${q(shorten(path, PATH_LABEL_MAX))})`;
534
569
  return track(
535
570
  "connector",
536
- label2,
571
+ label3,
537
572
  () => call("POST", "/service-connectors/request", {
538
573
  body: { connector: name, method, path, body: opts?.body }
539
574
  }).then(toResponse)
@@ -555,6 +590,7 @@
555
590
  target.me = me;
556
591
  target.appUsers = appUsers;
557
592
  target.designSystem = designSystem;
593
+ target.email = email;
558
594
  target.llm = llm;
559
595
  target.llmProviders = llmProviders;
560
596
  target.data = data;
@@ -566,4 +602,5 @@
566
602
  target.serviceConnectors = serviceConnectors;
567
603
  target.query = query;
568
604
  target.savedQueries = savedQueries;
605
+ target.agents = agents;
569
606
  })();