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 +20 -0
- package/dist/index.js +550 -0
- package/dist/manifest.js +19 -2
- package/package.json +1 -1
- package/static/sdk.js +10 -0
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 = [
|
|
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
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
|
})();
|