railcode 0.1.19 → 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
package/dist/manifest.js CHANGED
@@ -16,6 +16,8 @@
16
16
  // email: true
17
17
  // adhoc_sql:
18
18
  // - warehouse
19
+ // agents:
20
+ // - sales-digest
19
21
  //
20
22
  // Also supported, matching PyYAML: flow lists (`saved_queries: [a, b]`), a flow
21
23
  // map for connectors (`connectors: {stripe: [GET /v1/balance]}`), a whole-
@@ -31,7 +33,15 @@
31
33
  // test/manifest-fixtures.json (also run by
32
34
  // backend/tests/test_manifest_parse_fixtures.py) keeps the two parsers pinned.
33
35
  export const APP_MANIFEST_NAME = "manifest.yaml";
34
- const ALLOWED_KEYS = ["run_as", "saved_queries", "connectors", "llm", "email", "adhoc_sql"];
36
+ const ALLOWED_KEYS = [
37
+ "run_as",
38
+ "saved_queries",
39
+ "connectors",
40
+ "llm",
41
+ "email",
42
+ "adhoc_sql",
43
+ "agents",
44
+ ];
35
45
  const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
36
46
  const SAVED_QUERY_NAME = /^[a-z0-9_]{1,80}$/;
37
47
  // PyYAML's YAML 1.1 plain-scalar resolver: a bare (unquoted) scalar matching one
@@ -385,6 +395,7 @@ export function parseManifestYaml(source) {
385
395
  llm: false,
386
396
  email: false,
387
397
  adhoc_sql: [],
398
+ agents: [],
388
399
  };
389
400
  for (const [key, entry] of entries) {
390
401
  if (key === "run_as") {
@@ -404,10 +415,12 @@ export function parseManifestYaml(source) {
404
415
  doc.connectors = parseConnectors(entry);
405
416
  }
406
417
  else {
407
- // saved_queries / adhoc_sql: a list of names.
418
+ // saved_queries / adhoc_sql / agents: a list of names.
408
419
  const items = parseStringList(entry, key);
409
420
  if (key === "saved_queries")
410
421
  doc.saved_queries = items;
422
+ else if (key === "agents")
423
+ doc.agents = items;
411
424
  else
412
425
  doc.adhoc_sql = items;
413
426
  }
@@ -629,6 +642,7 @@ function validateDoc(doc, lines) {
629
642
  }
630
643
  doc.saved_queries = [...new Set(doc.saved_queries)].sort();
631
644
  doc.adhoc_sql = [...new Set(doc.adhoc_sql)].sort();
645
+ doc.agents = [...new Set(doc.agents)].sort();
632
646
  for (const name of Object.keys(doc.connectors)) {
633
647
  doc.connectors[name] = [...new Set(doc.connectors[name])].sort();
634
648
  }
@@ -673,6 +687,9 @@ export function renderManifestSummary(doc) {
673
687
  if (doc.adhoc_sql.length > 0) {
674
688
  lines.push("warning: adhoc_sql grants ad-hoc SQL authority — scarce by design; prefer saved queries");
675
689
  }
690
+ if (doc.agents.length > 0) {
691
+ lines.push(`agents: ${doc.agents.join(", ")}`);
692
+ }
676
693
  return lines.join("\n");
677
694
  }
678
695
  export function renderManifestDeployOutcome(m) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "railcode",
3
- "version": "0.1.19",
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
@@ -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;
@@ -593,4 +602,5 @@
593
602
  target.serviceConnectors = serviceConnectors;
594
603
  target.query = query;
595
604
  target.savedQueries = savedQueries;
605
+ target.agents = agents;
596
606
  })();