railcode 0.1.19 → 0.1.21
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 +21 -1
- package/dist/db.js +1 -1
- package/dist/index.js +759 -10
- package/dist/manifest.js +19 -2
- package/package.json +1 -1
- package/static/sdk.js +145 -27
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`),
|
|
@@ -56,10 +58,28 @@ railcode db <list|query> ... List data connectors / run read-only SQL
|
|
|
56
58
|
`railcode db list` (aliases `ls`, `connections`) prints each connector's
|
|
57
59
|
`name` + `engine` (`--json` for the raw array). `railcode db query "<sql>"`
|
|
58
60
|
(alias `sql`) runs SQL against `--connection` (default `default`) and prints a
|
|
59
|
-
table + row count; `--engine <postgres|bigquery|
|
|
61
|
+
table + row count; `--engine <postgres|bigquery|turso>` is inferred from the connector
|
|
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/db.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// the command layer in index.ts does the HTTP, file reads, and printing.
|
|
4
4
|
export class DbError extends Error {
|
|
5
5
|
}
|
|
6
|
-
export const DB_ENGINES = ["postgres", "bigquery", "
|
|
6
|
+
export const DB_ENGINES = ["postgres", "bigquery", "turso"];
|
|
7
7
|
// `--params '<json-array>'` → the positional $1,$2,… values. Empty when omitted.
|
|
8
8
|
export function parseSqlParams(raw) {
|
|
9
9
|
if (raw === undefined)
|
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
|
}
|
|
@@ -820,7 +826,7 @@ export default defineConfig({
|
|
|
820
826
|
}
|
|
821
827
|
function reactRailcodeTypes() {
|
|
822
828
|
return `type Me = {
|
|
823
|
-
user: { uuid: string; name: string; email: string };
|
|
829
|
+
user: { uuid: string; name: string; email: string; is_admin: boolean; roles: RoleRef[] };
|
|
824
830
|
app: { uuid: string; slug: string; name: string };
|
|
825
831
|
org: { uuid: string; slug: string; name: string };
|
|
826
832
|
};
|
|
@@ -829,9 +835,12 @@ type AppUser = {
|
|
|
829
835
|
uuid: string;
|
|
830
836
|
name: string;
|
|
831
837
|
email: string;
|
|
832
|
-
|
|
838
|
+
is_admin: boolean;
|
|
833
839
|
};
|
|
834
840
|
|
|
841
|
+
type RoleRef = { uuid: string; name: string };
|
|
842
|
+
type OrgRole = RoleRef & { description: string; is_member: boolean };
|
|
843
|
+
|
|
835
844
|
type KvRecord<T = unknown> = {
|
|
836
845
|
key: string;
|
|
837
846
|
value: T;
|
|
@@ -875,7 +884,7 @@ type SqlRows = Array<Record<string, unknown>> & {
|
|
|
875
884
|
};
|
|
876
885
|
|
|
877
886
|
type DataConnectorInfo = {
|
|
878
|
-
engine: "postgres" | "bigquery" | "
|
|
887
|
+
engine: "postgres" | "bigquery" | "turso" | string;
|
|
879
888
|
name: string;
|
|
880
889
|
};
|
|
881
890
|
|
|
@@ -954,8 +963,25 @@ type LlmModelInfo = { model: string; default: boolean };
|
|
|
954
963
|
|
|
955
964
|
type LlmProviderInfo = { provider: string; default: boolean; models: LlmModelInfo[] };
|
|
956
965
|
|
|
966
|
+
type EmailSendOptions = {
|
|
967
|
+
to: string | string[];
|
|
968
|
+
subject: string;
|
|
969
|
+
html?: string;
|
|
970
|
+
text?: string;
|
|
971
|
+
cc?: string | string[];
|
|
972
|
+
bcc?: string | string[];
|
|
973
|
+
replyTo?: string;
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
type EmailSendResult = {
|
|
977
|
+
id: string;
|
|
978
|
+
status: string;
|
|
979
|
+
requestId: string;
|
|
980
|
+
};
|
|
981
|
+
|
|
957
982
|
declare const me: () => Promise<Me>;
|
|
958
983
|
declare const appUsers: () => Promise<AppUser[]>;
|
|
984
|
+
declare const roles: () => Promise<OrgRole[]>;
|
|
959
985
|
declare const designSystem: () => Promise<string>;
|
|
960
986
|
declare const db: { collection<T = unknown>(name: string): Collection<T> };
|
|
961
987
|
declare const files: {
|
|
@@ -967,7 +993,6 @@ declare const files: {
|
|
|
967
993
|
declare const data: DatabaseNamespace;
|
|
968
994
|
declare const postgres: DatabaseNamespace;
|
|
969
995
|
declare const bigquery: DatabaseNamespace;
|
|
970
|
-
declare const snowflake: DatabaseNamespace;
|
|
971
996
|
declare const dataConnectors: () => Promise<DataConnectorInfo[]>;
|
|
972
997
|
declare const savedQueries: () => Promise<SavedQueryInfo[]>;
|
|
973
998
|
declare const query: (name: string, params?: Record<string, unknown>) => Promise<SqlRows>;
|
|
@@ -980,6 +1005,7 @@ declare const llm: {
|
|
|
980
1005
|
stream(input: string | LlmMessage[], opts?: LlmOptions): AsyncIterable<LlmStreamEvent>;
|
|
981
1006
|
};
|
|
982
1007
|
declare const llmProviders: () => Promise<LlmProviderInfo[]>;
|
|
1008
|
+
declare const email: { send(opts: EmailSendOptions): Promise<EmailSendResult> };
|
|
983
1009
|
`;
|
|
984
1010
|
}
|
|
985
1011
|
function reactViteEnvTypes() {
|
|
@@ -1054,6 +1080,14 @@ function message(error: unknown): string {
|
|
|
1054
1080
|
return error instanceof Error ? error.message : String(error);
|
|
1055
1081
|
}
|
|
1056
1082
|
|
|
1083
|
+
function httpStatus(error: unknown): number | null {
|
|
1084
|
+
if (typeof error === "object" && error !== null && "status" in error) {
|
|
1085
|
+
const status = (error as { status?: unknown }).status;
|
|
1086
|
+
return typeof status === "number" ? status : null;
|
|
1087
|
+
}
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1057
1091
|
function truncate(value: string, max: number): string {
|
|
1058
1092
|
return value.length > max ? value.slice(0, max - 1) + "…" : value;
|
|
1059
1093
|
}
|
|
@@ -1355,7 +1389,7 @@ function Hero() {
|
|
|
1355
1389
|
</div>
|
|
1356
1390
|
) : null}
|
|
1357
1391
|
{teammates.slice(0, 4).map((user) => (
|
|
1358
|
-
<div className="avatar" key={user.uuid} title={user.name + (user.
|
|
1392
|
+
<div className="avatar" key={user.uuid} title={user.name + (user.is_admin ? " · admin" : "")}>
|
|
1359
1393
|
{initials(user.name)}
|
|
1360
1394
|
</div>
|
|
1361
1395
|
))}
|
|
@@ -1861,9 +1895,124 @@ function ConnectorsCard() {
|
|
|
1861
1895
|
);
|
|
1862
1896
|
}
|
|
1863
1897
|
|
|
1898
|
+
function emailFailureMessage(error: unknown): string {
|
|
1899
|
+
const status = httpStatus(error);
|
|
1900
|
+
if (status === 403) return "Email is not granted for this app or user yet.";
|
|
1901
|
+
if (status === 429) return "The daily email recipient limit has been reached.";
|
|
1902
|
+
if (status === 503) return "Email is not configured for this Railcode instance.";
|
|
1903
|
+
return message(error);
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
function EmailCard() {
|
|
1907
|
+
const { identity } = useAppStore();
|
|
1908
|
+
const [to, setTo] = useState("");
|
|
1909
|
+
const [subject, setSubject] = useState("Hello from " + APP_SLUG);
|
|
1910
|
+
const [body, setBody] = useState(
|
|
1911
|
+
"This message was sent by the Railcode starter app through the platform email gateway.",
|
|
1912
|
+
);
|
|
1913
|
+
const [busy, setBusy] = useState(false);
|
|
1914
|
+
const [status, setStatus] = useState<{ kind: "sent" | "error"; text: string } | null>(null);
|
|
1915
|
+
|
|
1916
|
+
useEffect(() => {
|
|
1917
|
+
if (!to && identity?.user.email) setTo(identity.user.email);
|
|
1918
|
+
}, [identity, to]);
|
|
1919
|
+
|
|
1920
|
+
async function sendMessage(event: FormEvent<HTMLFormElement>) {
|
|
1921
|
+
event.preventDefault();
|
|
1922
|
+
const recipient = to.trim();
|
|
1923
|
+
const trimmedSubject = subject.trim() || "Hello from " + APP_SLUG;
|
|
1924
|
+
const text = body.trim() || "Sent from a Railcode starter app.";
|
|
1925
|
+
if (!recipient) {
|
|
1926
|
+
setStatus({ kind: "error", text: "Add a recipient email address first." });
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
setBusy(true);
|
|
1931
|
+
setStatus(null);
|
|
1932
|
+
try {
|
|
1933
|
+
const result = await email.send({ to: recipient, subject: trimmedSubject, text });
|
|
1934
|
+
setStatus({
|
|
1935
|
+
kind: "sent",
|
|
1936
|
+
text: "Sent with status " + result.status + " · " + result.requestId,
|
|
1937
|
+
});
|
|
1938
|
+
} catch (error) {
|
|
1939
|
+
setStatus({ kind: "error", text: emailFailureMessage(error) });
|
|
1940
|
+
} finally {
|
|
1941
|
+
setBusy(false);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
return (
|
|
1946
|
+
<article className="card">
|
|
1947
|
+
<div className="card-head">
|
|
1948
|
+
<div>
|
|
1949
|
+
<div className="card-title-row">
|
|
1950
|
+
<Icon name="mail" />
|
|
1951
|
+
<span className="card-title">
|
|
1952
|
+
<Reveal code="await email.send(message)">Email</Reveal>
|
|
1953
|
+
</span>
|
|
1954
|
+
</div>
|
|
1955
|
+
</div>
|
|
1956
|
+
</div>
|
|
1957
|
+
|
|
1958
|
+
<form className="email-form" onSubmit={sendMessage}>
|
|
1959
|
+
<label className="control-field">
|
|
1960
|
+
<span>Recipient</span>
|
|
1961
|
+
<input
|
|
1962
|
+
className="field"
|
|
1963
|
+
type="email"
|
|
1964
|
+
autoComplete="email"
|
|
1965
|
+
value={to}
|
|
1966
|
+
onChange={(event) => setTo(event.target.value)}
|
|
1967
|
+
/>
|
|
1968
|
+
</label>
|
|
1969
|
+
<label className="control-field">
|
|
1970
|
+
<span>Subject</span>
|
|
1971
|
+
<input
|
|
1972
|
+
className="field"
|
|
1973
|
+
type="text"
|
|
1974
|
+
value={subject}
|
|
1975
|
+
onChange={(event) => setSubject(event.target.value)}
|
|
1976
|
+
/>
|
|
1977
|
+
</label>
|
|
1978
|
+
<label className="json-editor">
|
|
1979
|
+
<span>Message</span>
|
|
1980
|
+
<textarea
|
|
1981
|
+
className="field"
|
|
1982
|
+
rows={4}
|
|
1983
|
+
value={body}
|
|
1984
|
+
onChange={(event) => setBody(event.target.value)}
|
|
1985
|
+
/>
|
|
1986
|
+
</label>
|
|
1987
|
+
<ActionTip code={\`await email.send({
|
|
1988
|
+
to,
|
|
1989
|
+
subject,
|
|
1990
|
+
text
|
|
1991
|
+
})\`} className="upload-tip">
|
|
1992
|
+
<button className="btn btn-primary" type="submit" disabled={busy}>
|
|
1993
|
+
{busy ? <span className="spinner" /> : <Icon name="mail" />}
|
|
1994
|
+
{busy ? "Sending..." : "Send test email"}
|
|
1995
|
+
</button>
|
|
1996
|
+
</ActionTip>
|
|
1997
|
+
</form>
|
|
1998
|
+
|
|
1999
|
+
<div className="email-state">
|
|
2000
|
+
<div className="result-status">
|
|
2001
|
+
<span>Delivery</span>
|
|
2002
|
+
<span>{status?.kind === "sent" ? "Sent" : status?.kind === "error" ? "Needs setup" : "Ready"}</span>
|
|
2003
|
+
</div>
|
|
2004
|
+
<p className={status?.kind === "error" ? "email-note is-error" : "email-note"}>
|
|
2005
|
+
{status?.text ||
|
|
2006
|
+
"Email uses the platform sender and requires an email grant or app manifest approval."}
|
|
2007
|
+
</p>
|
|
2008
|
+
</div>
|
|
2009
|
+
</article>
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
1864
2013
|
const AI_SAMPLE_PROMPT = "Summarize what this Railcode template demonstrates in one sentence.";
|
|
1865
2014
|
const AI_SAMPLE_RESPONSE =
|
|
1866
|
-
"This template shows a live Railcode app reading identity, storing notes in KV, uploading files, querying Postgres, invoking a saved query, calling Stripe through a service connector, and generating text — all without handling a single credential in the browser.";
|
|
2015
|
+
"This template shows a live Railcode app reading identity, storing notes in KV, uploading files, querying Postgres, invoking a saved query, calling Stripe through a service connector, sending email, and generating text — all without handling a single credential in the browser.";
|
|
1867
2016
|
|
|
1868
2017
|
function AiCard() {
|
|
1869
2018
|
const [prompt, setPrompt] = useState(AI_SAMPLE_PROMPT);
|
|
@@ -2004,6 +2153,10 @@ function IconSprite() {
|
|
|
2004
2153
|
<symbol id="i-chat" viewBox="0 0 24 24">
|
|
2005
2154
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
2006
2155
|
</symbol>
|
|
2156
|
+
<symbol id="i-mail" viewBox="0 0 24 24">
|
|
2157
|
+
<rect x="3" y="5" width="18" height="14" rx="2" />
|
|
2158
|
+
<path d="m3 7 9 6 9-6" />
|
|
2159
|
+
</symbol>
|
|
2007
2160
|
<symbol id="i-code" viewBox="0 0 24 24">
|
|
2008
2161
|
<polyline points="16 18 22 12 16 6" />
|
|
2009
2162
|
<polyline points="8 6 2 12 8 18" />
|
|
@@ -2070,11 +2223,18 @@ export function App() {
|
|
|
2070
2223
|
|
|
2071
2224
|
<GuideSection
|
|
2072
2225
|
title="They can connect to external services"
|
|
2073
|
-
body="Railcode apps can securely connect to your SaaS services so you can manage refunds,
|
|
2226
|
+
body="Railcode apps can securely connect to your SaaS services so you can manage refunds, sync tickets, enrich records, and anything else you need."
|
|
2074
2227
|
>
|
|
2075
2228
|
<ConnectorsCard />
|
|
2076
2229
|
</GuideSection>
|
|
2077
2230
|
|
|
2231
|
+
<GuideSection
|
|
2232
|
+
title="They can send email"
|
|
2233
|
+
body="Send transactional email through the Railcode gateway without putting provider credentials in browser code."
|
|
2234
|
+
>
|
|
2235
|
+
<EmailCard />
|
|
2236
|
+
</GuideSection>
|
|
2237
|
+
|
|
2078
2238
|
<GuideSection
|
|
2079
2239
|
title="They can use AI"
|
|
2080
2240
|
body="Build AI-native apps that use LLMs to enable powerful workflows."
|
|
@@ -3095,6 +3255,41 @@ th:first-child {
|
|
|
3095
3255
|
}
|
|
3096
3256
|
}
|
|
3097
3257
|
|
|
3258
|
+
/* ---------- email card ---------- */
|
|
3259
|
+
|
|
3260
|
+
.email-form {
|
|
3261
|
+
display: flex;
|
|
3262
|
+
flex-direction: column;
|
|
3263
|
+
gap: var(--space-3);
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
.email-form .field {
|
|
3267
|
+
width: 100%;
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
.email-state {
|
|
3271
|
+
margin-top: var(--space-4);
|
|
3272
|
+
padding: 12px 14px;
|
|
3273
|
+
border: 1px solid var(--line);
|
|
3274
|
+
border-radius: var(--radius-md);
|
|
3275
|
+
background: var(--bg-inset);
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
.email-state .result-status {
|
|
3279
|
+
margin-bottom: 6px;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
.email-note {
|
|
3283
|
+
color: var(--ink-3);
|
|
3284
|
+
font-size: 12.5px;
|
|
3285
|
+
line-height: 1.55;
|
|
3286
|
+
overflow-wrap: anywhere;
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
.email-note.is-error {
|
|
3290
|
+
color: #b42318;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3098
3293
|
/* ---------- reveal + action tooltips ---------- */
|
|
3099
3294
|
|
|
3100
3295
|
.reveal {
|
|
@@ -3534,6 +3729,550 @@ async function commandManifestShow(args) {
|
|
|
3534
3729
|
}
|
|
3535
3730
|
}
|
|
3536
3731
|
// ---------------------------------------------------------------------------
|
|
3732
|
+
// agent — managed agents (`railcode agent ...`)
|
|
3733
|
+
// ---------------------------------------------------------------------------
|
|
3734
|
+
const AGENT_HELP = `railcode agent — managed agents
|
|
3735
|
+
|
|
3736
|
+
Usage:
|
|
3737
|
+
railcode agent list List managed agents in this org
|
|
3738
|
+
railcode agent show <agent> Show one agent
|
|
3739
|
+
railcode agent pull <agent> [--output <path>]
|
|
3740
|
+
Write the agent manifest as JSON
|
|
3741
|
+
railcode agent create --file <path> Create an agent from a JSON manifest
|
|
3742
|
+
railcode agent update <agent> --file <path> Update an agent from a JSON manifest
|
|
3743
|
+
railcode agent delete <agent> [--yes] Delete/archive an agent
|
|
3744
|
+
railcode agent test --file <path> [opts] Run a draft manifest without saving
|
|
3745
|
+
railcode agent run <agent> [opts] Trigger a saved agent and print results
|
|
3746
|
+
railcode agent schedule <show|set|add|update|pause|resume|delete|run-now> <agent>
|
|
3747
|
+
|
|
3748
|
+
Agent options:
|
|
3749
|
+
--json Print the raw API response
|
|
3750
|
+
--file <path> JSON manifest file
|
|
3751
|
+
--output <path> Output path for pull (default stdout)
|
|
3752
|
+
--input '<json>' Invoke/test input JSON (default null)
|
|
3753
|
+
--input-file <path> Read invoke/test input JSON from a file
|
|
3754
|
+
--trace Include the run step trace in human output
|
|
3755
|
+
--yes Confirm delete without a prompt
|
|
3756
|
+
|
|
3757
|
+
Schedule options:
|
|
3758
|
+
--name <name> Schedule name (default: default on create)
|
|
3759
|
+
--cron <expr> Five-field cron expression
|
|
3760
|
+
--schedule <expr> Alias for --cron
|
|
3761
|
+
--timezone <iana> IANA timezone (default: UTC on create)
|
|
3762
|
+
--enabled / --disabled Initial or updated enabled state
|
|
3763
|
+
|
|
3764
|
+
Manifests are JSON files in the same shape the API stores under agent.manifest.
|
|
3765
|
+
Schedules are currently one per agent, so "set" creates the schedule if missing
|
|
3766
|
+
or updates the existing one.
|
|
3767
|
+
`;
|
|
3768
|
+
async function commandAgent(args) {
|
|
3769
|
+
const sub = args.rest[0] ?? "";
|
|
3770
|
+
switch (sub) {
|
|
3771
|
+
case "list":
|
|
3772
|
+
case "ls":
|
|
3773
|
+
await commandAgentList(args);
|
|
3774
|
+
return;
|
|
3775
|
+
case "show":
|
|
3776
|
+
case "get":
|
|
3777
|
+
await commandAgentShow(args);
|
|
3778
|
+
return;
|
|
3779
|
+
case "pull":
|
|
3780
|
+
case "export":
|
|
3781
|
+
await commandAgentPull(args);
|
|
3782
|
+
return;
|
|
3783
|
+
case "create":
|
|
3784
|
+
await commandAgentCreate(args);
|
|
3785
|
+
return;
|
|
3786
|
+
case "update":
|
|
3787
|
+
case "edit":
|
|
3788
|
+
await commandAgentUpdate(args);
|
|
3789
|
+
return;
|
|
3790
|
+
case "delete":
|
|
3791
|
+
case "rm":
|
|
3792
|
+
await commandAgentDelete(args);
|
|
3793
|
+
return;
|
|
3794
|
+
case "test":
|
|
3795
|
+
await commandAgentTest(args);
|
|
3796
|
+
return;
|
|
3797
|
+
case "run":
|
|
3798
|
+
case "invoke":
|
|
3799
|
+
await commandAgentRun(args);
|
|
3800
|
+
return;
|
|
3801
|
+
case "schedule":
|
|
3802
|
+
case "schedules":
|
|
3803
|
+
await commandAgentSchedule(args);
|
|
3804
|
+
return;
|
|
3805
|
+
case "":
|
|
3806
|
+
case "help":
|
|
3807
|
+
console.log(AGENT_HELP);
|
|
3808
|
+
return;
|
|
3809
|
+
default:
|
|
3810
|
+
throw new CliError(`Unknown agent command: ${sub}\n\n${AGENT_HELP}`, 2);
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
async function commandAgentList(args) {
|
|
3814
|
+
assertKnownAgentOptions(args, ["json", "apiUrl"]);
|
|
3815
|
+
if (args.rest.length > 1) {
|
|
3816
|
+
throw new CliError(`railcode agent list takes no arguments (got "${args.rest[1]}").`, 2);
|
|
3817
|
+
}
|
|
3818
|
+
const config = requireLogin(args);
|
|
3819
|
+
const agents = await fetchAgents(config);
|
|
3820
|
+
if (args.options.json) {
|
|
3821
|
+
console.log(JSON.stringify(agents, null, 2));
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
if (agents.length === 0) {
|
|
3825
|
+
console.log("No managed agents in this organization.");
|
|
3826
|
+
return;
|
|
3827
|
+
}
|
|
3828
|
+
console.log(formatTable(["name", "model", "uuid", "updated"], agents.map((agent) => [
|
|
3829
|
+
agent.name,
|
|
3830
|
+
typeof agent.manifest.model === "string" ? agent.manifest.model : "-",
|
|
3831
|
+
agent.uuid,
|
|
3832
|
+
shortDate(agent.updated_at),
|
|
3833
|
+
])));
|
|
3834
|
+
}
|
|
3835
|
+
async function commandAgentShow(args) {
|
|
3836
|
+
assertKnownAgentOptions(args, ["json", "manifest", "apiUrl"]);
|
|
3837
|
+
const ref = agentArg(args, "show");
|
|
3838
|
+
const config = requireLogin(args);
|
|
3839
|
+
const agent = await fetchAgent(config, ref);
|
|
3840
|
+
if (args.options.json) {
|
|
3841
|
+
console.log(JSON.stringify(agent, null, 2));
|
|
3842
|
+
return;
|
|
3843
|
+
}
|
|
3844
|
+
if (args.options.manifest) {
|
|
3845
|
+
console.log(JSON.stringify(agent.manifest, null, 2));
|
|
3846
|
+
return;
|
|
3847
|
+
}
|
|
3848
|
+
printAgent(agent);
|
|
3849
|
+
}
|
|
3850
|
+
async function commandAgentPull(args) {
|
|
3851
|
+
assertKnownAgentOptions(args, ["output", "json", "apiUrl"]);
|
|
3852
|
+
const ref = agentArg(args, "pull");
|
|
3853
|
+
const output = optionString(args, "output");
|
|
3854
|
+
const config = requireLogin(args);
|
|
3855
|
+
const agent = await fetchAgent(config, ref);
|
|
3856
|
+
const manifest = JSON.stringify(agent.manifest, null, 2) + "\n";
|
|
3857
|
+
if (output) {
|
|
3858
|
+
writeFileSync(resolve(process.cwd(), output), manifest, { mode: 0o644 });
|
|
3859
|
+
console.log(`Wrote ${output}`);
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3862
|
+
process.stdout.write(manifest);
|
|
3863
|
+
}
|
|
3864
|
+
async function commandAgentCreate(args) {
|
|
3865
|
+
assertKnownAgentOptions(args, ["file", "json", "apiUrl"]);
|
|
3866
|
+
const manifest = readAgentManifest(args);
|
|
3867
|
+
const config = requireLogin(args);
|
|
3868
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents`, {
|
|
3869
|
+
method: "POST",
|
|
3870
|
+
headers: { "Content-Type": "application/json" },
|
|
3871
|
+
body: JSON.stringify({ manifest }),
|
|
3872
|
+
});
|
|
3873
|
+
if (resp.status === 401)
|
|
3874
|
+
await reLoginOrThrow();
|
|
3875
|
+
const result = (await readJsonOrThrow(resp, "Create agent"));
|
|
3876
|
+
printAgentMutation(result, args.options.json === true, "created");
|
|
3877
|
+
}
|
|
3878
|
+
async function commandAgentUpdate(args) {
|
|
3879
|
+
assertKnownAgentOptions(args, ["file", "json", "apiUrl"]);
|
|
3880
|
+
const ref = agentArg(args, "update");
|
|
3881
|
+
const manifest = readAgentManifest(args);
|
|
3882
|
+
const config = requireLogin(args);
|
|
3883
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}`, {
|
|
3884
|
+
method: "PUT",
|
|
3885
|
+
headers: { "Content-Type": "application/json" },
|
|
3886
|
+
body: JSON.stringify({ manifest }),
|
|
3887
|
+
});
|
|
3888
|
+
if (resp.status === 401)
|
|
3889
|
+
await reLoginOrThrow();
|
|
3890
|
+
const result = (await readJsonOrThrow(resp, `Update agent "${ref}"`));
|
|
3891
|
+
printAgentMutation(result, args.options.json === true, "updated");
|
|
3892
|
+
}
|
|
3893
|
+
async function commandAgentDelete(args) {
|
|
3894
|
+
assertKnownAgentOptions(args, ["yes", "apiUrl"]);
|
|
3895
|
+
const ref = agentArg(args, "delete");
|
|
3896
|
+
if (args.options.yes !== true) {
|
|
3897
|
+
if (!process.stdin.isTTY) {
|
|
3898
|
+
throw new CliError("Pass --yes to delete an agent in a non-interactive session.", 2);
|
|
3899
|
+
}
|
|
3900
|
+
const answer = await prompt(`Delete agent "${ref}"? Its run history is kept. [y/N] `);
|
|
3901
|
+
if (!["y", "yes"].includes(answer.trim().toLowerCase())) {
|
|
3902
|
+
throw new CliError("Delete cancelled.", 1);
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
const config = requireLogin(args);
|
|
3906
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}`, { method: "DELETE" });
|
|
3907
|
+
if (resp.status === 401)
|
|
3908
|
+
await reLoginOrThrow();
|
|
3909
|
+
if (!resp.ok) {
|
|
3910
|
+
throw new CliError(`Delete agent "${ref}" failed (${resp.status}): ${await errorDetail(resp)}`, 1);
|
|
3911
|
+
}
|
|
3912
|
+
console.log(`Agent "${ref}" deleted.`);
|
|
3913
|
+
}
|
|
3914
|
+
async function commandAgentTest(args) {
|
|
3915
|
+
assertKnownAgentOptions(args, ["file", "input", "inputFile", "json", "trace", "apiUrl"]);
|
|
3916
|
+
const manifest = readAgentManifest(args);
|
|
3917
|
+
const input = readJsonInput(args);
|
|
3918
|
+
const config = requireLogin(args);
|
|
3919
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/test`, {
|
|
3920
|
+
method: "POST",
|
|
3921
|
+
headers: { "Content-Type": "application/json" },
|
|
3922
|
+
body: JSON.stringify({ manifest, input }),
|
|
3923
|
+
});
|
|
3924
|
+
if (resp.status === 401)
|
|
3925
|
+
await reLoginOrThrow();
|
|
3926
|
+
const run = (await readJsonOrThrow(resp, "Test agent"));
|
|
3927
|
+
printAgentRun(run, { json: args.options.json === true, trace: args.options.trace === true });
|
|
3928
|
+
}
|
|
3929
|
+
async function commandAgentRun(args) {
|
|
3930
|
+
assertKnownAgentOptions(args, ["input", "inputFile", "json", "trace", "apiUrl"]);
|
|
3931
|
+
const ref = agentArg(args, "run");
|
|
3932
|
+
const input = readJsonInput(args);
|
|
3933
|
+
const config = requireLogin(args);
|
|
3934
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/invoke`, {
|
|
3935
|
+
method: "POST",
|
|
3936
|
+
headers: { "Content-Type": "application/json" },
|
|
3937
|
+
body: JSON.stringify({ input }),
|
|
3938
|
+
});
|
|
3939
|
+
if (resp.status === 401)
|
|
3940
|
+
await reLoginOrThrow();
|
|
3941
|
+
const run = (await readJsonOrThrow(resp, `Run agent "${ref}"`));
|
|
3942
|
+
printAgentRun(run, { json: args.options.json === true, trace: args.options.trace === true });
|
|
3943
|
+
}
|
|
3944
|
+
async function commandAgentSchedule(args) {
|
|
3945
|
+
const sub = args.rest[1] ?? "";
|
|
3946
|
+
switch (sub) {
|
|
3947
|
+
case "show":
|
|
3948
|
+
case "list":
|
|
3949
|
+
case "ls":
|
|
3950
|
+
await commandAgentScheduleShow(args);
|
|
3951
|
+
return;
|
|
3952
|
+
case "set":
|
|
3953
|
+
await commandAgentScheduleSet(args, "upsert");
|
|
3954
|
+
return;
|
|
3955
|
+
case "add":
|
|
3956
|
+
case "create":
|
|
3957
|
+
await commandAgentScheduleSet(args, "create");
|
|
3958
|
+
return;
|
|
3959
|
+
case "update":
|
|
3960
|
+
await commandAgentScheduleSet(args, "update");
|
|
3961
|
+
return;
|
|
3962
|
+
case "pause":
|
|
3963
|
+
await commandAgentScheduleEnabled(args, false);
|
|
3964
|
+
return;
|
|
3965
|
+
case "resume":
|
|
3966
|
+
await commandAgentScheduleEnabled(args, true);
|
|
3967
|
+
return;
|
|
3968
|
+
case "delete":
|
|
3969
|
+
case "rm":
|
|
3970
|
+
await commandAgentScheduleDelete(args);
|
|
3971
|
+
return;
|
|
3972
|
+
case "run-now":
|
|
3973
|
+
case "run":
|
|
3974
|
+
await commandAgentScheduleRunNow(args);
|
|
3975
|
+
return;
|
|
3976
|
+
case "":
|
|
3977
|
+
case "help":
|
|
3978
|
+
console.log(AGENT_HELP);
|
|
3979
|
+
return;
|
|
3980
|
+
default:
|
|
3981
|
+
throw new CliError(`Unknown agent schedule command: ${sub}\n\n${AGENT_HELP}`, 2);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
async function commandAgentScheduleShow(args) {
|
|
3985
|
+
assertKnownAgentOptions(args, ["json", "apiUrl"]);
|
|
3986
|
+
const ref = agentScheduleAgentArg(args, "show");
|
|
3987
|
+
const config = requireLogin(args);
|
|
3988
|
+
const schedules = await fetchAgentSchedules(config, ref);
|
|
3989
|
+
if (args.options.json) {
|
|
3990
|
+
console.log(JSON.stringify(schedules, null, 2));
|
|
3991
|
+
return;
|
|
3992
|
+
}
|
|
3993
|
+
if (schedules.length === 0) {
|
|
3994
|
+
console.log(`Agent "${ref}" has no schedule.`);
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
printSchedule(schedules[0]);
|
|
3998
|
+
}
|
|
3999
|
+
async function commandAgentScheduleSet(args, mode) {
|
|
4000
|
+
assertKnownAgentOptions(args, [
|
|
4001
|
+
"name",
|
|
4002
|
+
"cron",
|
|
4003
|
+
"schedule",
|
|
4004
|
+
"timezone",
|
|
4005
|
+
"enabled",
|
|
4006
|
+
"disabled",
|
|
4007
|
+
"json",
|
|
4008
|
+
"apiUrl",
|
|
4009
|
+
]);
|
|
4010
|
+
const ref = agentScheduleAgentArg(args, mode === "upsert" ? "set" : mode);
|
|
4011
|
+
const config = requireLogin(args);
|
|
4012
|
+
const existing = await fetchAgentSchedules(config, ref);
|
|
4013
|
+
if (mode === "create" && existing.length > 0) {
|
|
4014
|
+
throw new CliError(`Agent "${ref}" already has a schedule. Use \`railcode agent schedule set ${ref}\` to update it.`, 1);
|
|
4015
|
+
}
|
|
4016
|
+
if (mode === "update" && existing.length === 0) {
|
|
4017
|
+
throw new CliError(`Agent "${ref}" has no schedule. Use \`railcode agent schedule set ${ref} --cron "0 9 * * *"\` to create one.`, 1);
|
|
4018
|
+
}
|
|
4019
|
+
const body = schedulePayload(args, existing.length === 0);
|
|
4020
|
+
const path = existing.length === 0
|
|
4021
|
+
? `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules`
|
|
4022
|
+
: `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules/${encodeURIComponent(existing[0].uuid)}`;
|
|
4023
|
+
const resp = await authedFetch(config, path, {
|
|
4024
|
+
method: existing.length === 0 ? "POST" : "PATCH",
|
|
4025
|
+
headers: { "Content-Type": "application/json" },
|
|
4026
|
+
body: JSON.stringify(body),
|
|
4027
|
+
});
|
|
4028
|
+
if (resp.status === 401)
|
|
4029
|
+
await reLoginOrThrow();
|
|
4030
|
+
const schedule = (await readJsonOrThrow(resp, `${existing.length === 0 ? "Create" : "Update"} agent schedule`));
|
|
4031
|
+
if (args.options.json) {
|
|
4032
|
+
console.log(JSON.stringify(schedule, null, 2));
|
|
4033
|
+
return;
|
|
4034
|
+
}
|
|
4035
|
+
console.log(existing.length === 0 ? "Schedule created." : "Schedule updated.");
|
|
4036
|
+
printSchedule(schedule);
|
|
4037
|
+
}
|
|
4038
|
+
async function commandAgentScheduleEnabled(args, enabled) {
|
|
4039
|
+
assertKnownAgentOptions(args, ["json", "apiUrl"]);
|
|
4040
|
+
const ref = agentScheduleAgentArg(args, enabled ? "resume" : "pause");
|
|
4041
|
+
const config = requireLogin(args);
|
|
4042
|
+
const schedule = await requireSingleSchedule(config, ref);
|
|
4043
|
+
const updated = await patchAgentSchedule(config, ref, schedule.uuid, { enabled });
|
|
4044
|
+
if (args.options.json) {
|
|
4045
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
console.log(enabled ? "Schedule resumed." : "Schedule paused.");
|
|
4049
|
+
printSchedule(updated);
|
|
4050
|
+
}
|
|
4051
|
+
async function commandAgentScheduleDelete(args) {
|
|
4052
|
+
assertKnownAgentOptions(args, ["yes", "apiUrl"]);
|
|
4053
|
+
const ref = agentScheduleAgentArg(args, "delete");
|
|
4054
|
+
const config = requireLogin(args);
|
|
4055
|
+
const schedule = await requireSingleSchedule(config, ref);
|
|
4056
|
+
if (args.options.yes !== true) {
|
|
4057
|
+
if (!process.stdin.isTTY) {
|
|
4058
|
+
throw new CliError("Pass --yes to delete a schedule in a non-interactive session.", 2);
|
|
4059
|
+
}
|
|
4060
|
+
const answer = await prompt(`Delete schedule "${schedule.name}" for agent "${ref}"? [y/N] `);
|
|
4061
|
+
if (!["y", "yes"].includes(answer.trim().toLowerCase())) {
|
|
4062
|
+
throw new CliError("Delete cancelled.", 1);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules/${encodeURIComponent(schedule.uuid)}`, { method: "DELETE" });
|
|
4066
|
+
if (resp.status === 401)
|
|
4067
|
+
await reLoginOrThrow();
|
|
4068
|
+
if (!resp.ok) {
|
|
4069
|
+
throw new CliError(`Delete agent schedule failed (${resp.status}): ${await errorDetail(resp)}`, 1);
|
|
4070
|
+
}
|
|
4071
|
+
console.log(`Schedule "${schedule.name}" deleted.`);
|
|
4072
|
+
}
|
|
4073
|
+
async function commandAgentScheduleRunNow(args) {
|
|
4074
|
+
assertKnownAgentOptions(args, ["json", "trace", "apiUrl"]);
|
|
4075
|
+
const ref = agentScheduleAgentArg(args, "run-now");
|
|
4076
|
+
const config = requireLogin(args);
|
|
4077
|
+
const schedule = await requireSingleSchedule(config, ref);
|
|
4078
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules/${encodeURIComponent(schedule.uuid)}/run-now`, { method: "POST" });
|
|
4079
|
+
if (resp.status === 401)
|
|
4080
|
+
await reLoginOrThrow();
|
|
4081
|
+
const run = (await readJsonOrThrow(resp, `Run schedule "${schedule.name}"`));
|
|
4082
|
+
printAgentRun(run, { json: args.options.json === true, trace: args.options.trace === true });
|
|
4083
|
+
}
|
|
4084
|
+
async function fetchAgents(config) {
|
|
4085
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents`);
|
|
4086
|
+
if (resp.status === 401)
|
|
4087
|
+
await reLoginOrThrow();
|
|
4088
|
+
return (await readJsonOrThrow(resp, "List agents"));
|
|
4089
|
+
}
|
|
4090
|
+
async function fetchAgent(config, ref) {
|
|
4091
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}`);
|
|
4092
|
+
if (resp.status === 401)
|
|
4093
|
+
await reLoginOrThrow();
|
|
4094
|
+
return (await readJsonOrThrow(resp, `Fetch agent "${ref}"`));
|
|
4095
|
+
}
|
|
4096
|
+
async function fetchAgentSchedules(config, ref) {
|
|
4097
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules`);
|
|
4098
|
+
if (resp.status === 401)
|
|
4099
|
+
await reLoginOrThrow();
|
|
4100
|
+
return (await readJsonOrThrow(resp, `List schedules for agent "${ref}"`));
|
|
4101
|
+
}
|
|
4102
|
+
async function patchAgentSchedule(config, ref, scheduleUuid, body) {
|
|
4103
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/agents/${encodeURIComponent(ref)}/schedules/${encodeURIComponent(scheduleUuid)}`, {
|
|
4104
|
+
method: "PATCH",
|
|
4105
|
+
headers: { "Content-Type": "application/json" },
|
|
4106
|
+
body: JSON.stringify(body),
|
|
4107
|
+
});
|
|
4108
|
+
if (resp.status === 401)
|
|
4109
|
+
await reLoginOrThrow();
|
|
4110
|
+
return (await readJsonOrThrow(resp, "Update agent schedule"));
|
|
4111
|
+
}
|
|
4112
|
+
async function requireSingleSchedule(config, ref) {
|
|
4113
|
+
const schedules = await fetchAgentSchedules(config, ref);
|
|
4114
|
+
if (schedules.length === 0) {
|
|
4115
|
+
throw new CliError(`Agent "${ref}" has no schedule.`, 1);
|
|
4116
|
+
}
|
|
4117
|
+
return schedules[0];
|
|
4118
|
+
}
|
|
4119
|
+
function readAgentManifest(args) {
|
|
4120
|
+
const path = optionString(args, "file");
|
|
4121
|
+
if (!path)
|
|
4122
|
+
throw new CliError("Pass --file <agent-manifest.json>.", 2);
|
|
4123
|
+
return readJsonObjectFile(path, "Agent manifest");
|
|
4124
|
+
}
|
|
4125
|
+
function readJsonObjectFile(path, label) {
|
|
4126
|
+
const resolved = resolve(process.cwd(), path);
|
|
4127
|
+
if (!existsSync(resolved))
|
|
4128
|
+
throw new CliError(`${label} file not found: ${path}`, 2);
|
|
4129
|
+
let parsed;
|
|
4130
|
+
try {
|
|
4131
|
+
parsed = JSON.parse(readFileSync(resolved, "utf8"));
|
|
4132
|
+
}
|
|
4133
|
+
catch (error) {
|
|
4134
|
+
throw new CliError(`${label} file is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, 2);
|
|
4135
|
+
}
|
|
4136
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4137
|
+
throw new CliError(`${label} file must contain a JSON object.`, 2);
|
|
4138
|
+
}
|
|
4139
|
+
return parsed;
|
|
4140
|
+
}
|
|
4141
|
+
function readJsonInput(args) {
|
|
4142
|
+
const inline = optionString(args, "input");
|
|
4143
|
+
const file = optionString(args, "inputFile");
|
|
4144
|
+
if (inline !== undefined && file !== undefined) {
|
|
4145
|
+
throw new CliError("Pass either --input or --input-file, not both.", 2);
|
|
4146
|
+
}
|
|
4147
|
+
if (inline === undefined && file === undefined)
|
|
4148
|
+
return null;
|
|
4149
|
+
const raw = inline ?? readFileSync(resolve(process.cwd(), file), "utf8");
|
|
4150
|
+
try {
|
|
4151
|
+
return JSON.parse(raw);
|
|
4152
|
+
}
|
|
4153
|
+
catch (error) {
|
|
4154
|
+
throw new CliError(`Agent input is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, 2);
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
function schedulePayload(args, creating) {
|
|
4158
|
+
const body = {};
|
|
4159
|
+
const name = optionString(args, "name");
|
|
4160
|
+
const cron = optionString(args, "cron") ?? optionString(args, "schedule");
|
|
4161
|
+
const timezone = optionString(args, "timezone");
|
|
4162
|
+
if (args.options.enabled === true && args.options.disabled === true) {
|
|
4163
|
+
throw new CliError("Pass either --enabled or --disabled, not both.", 2);
|
|
4164
|
+
}
|
|
4165
|
+
if (name !== undefined)
|
|
4166
|
+
body.name = name;
|
|
4167
|
+
if (cron !== undefined)
|
|
4168
|
+
body.schedule = cron;
|
|
4169
|
+
if (timezone !== undefined)
|
|
4170
|
+
body.timezone = timezone;
|
|
4171
|
+
if (args.options.enabled === true)
|
|
4172
|
+
body.enabled = true;
|
|
4173
|
+
if (args.options.disabled === true)
|
|
4174
|
+
body.enabled = false;
|
|
4175
|
+
if (creating) {
|
|
4176
|
+
if (!body.name)
|
|
4177
|
+
body.name = "default";
|
|
4178
|
+
if (!body.schedule)
|
|
4179
|
+
throw new CliError("Creating an agent schedule requires --cron.", 2);
|
|
4180
|
+
if (!body.timezone)
|
|
4181
|
+
body.timezone = "UTC";
|
|
4182
|
+
if (body.enabled === undefined)
|
|
4183
|
+
body.enabled = true;
|
|
4184
|
+
}
|
|
4185
|
+
else if (Object.keys(body).length === 0) {
|
|
4186
|
+
throw new CliError("Nothing to update. Pass --name, --cron, --timezone, --enabled or --disabled.", 2);
|
|
4187
|
+
}
|
|
4188
|
+
return body;
|
|
4189
|
+
}
|
|
4190
|
+
function agentArg(args, command) {
|
|
4191
|
+
const ref = args.rest[1];
|
|
4192
|
+
if (!ref || args.rest.length > 2) {
|
|
4193
|
+
throw new CliError(`Usage: railcode agent ${command} <agent>`, 2);
|
|
4194
|
+
}
|
|
4195
|
+
return ref;
|
|
4196
|
+
}
|
|
4197
|
+
function agentScheduleAgentArg(args, command) {
|
|
4198
|
+
const ref = args.rest[2];
|
|
4199
|
+
if (!ref || args.rest.length > 3) {
|
|
4200
|
+
throw new CliError(`Usage: railcode agent schedule ${command} <agent>`, 2);
|
|
4201
|
+
}
|
|
4202
|
+
return ref;
|
|
4203
|
+
}
|
|
4204
|
+
function printAgentMutation(result, json, verb) {
|
|
4205
|
+
if (json) {
|
|
4206
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4207
|
+
return;
|
|
4208
|
+
}
|
|
4209
|
+
console.log(`Agent "${result.agent.name}" ${verb}.`);
|
|
4210
|
+
console.log(`UUID: ${result.agent.uuid}`);
|
|
4211
|
+
if (result.warnings.length > 0) {
|
|
4212
|
+
console.log("");
|
|
4213
|
+
console.log("Warnings:");
|
|
4214
|
+
for (const warning of result.warnings)
|
|
4215
|
+
console.log(`- ${warning}`);
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
function printAgent(agent) {
|
|
4219
|
+
console.log(agent.name);
|
|
4220
|
+
console.log(`UUID: ${agent.uuid}`);
|
|
4221
|
+
console.log(`Description: ${agent.description || "-"}`);
|
|
4222
|
+
console.log(`Model: ${typeof agent.manifest.model === "string" ? agent.manifest.model : "-"}`);
|
|
4223
|
+
console.log(`Updated: ${agent.updated_at}`);
|
|
4224
|
+
console.log(`Content hash: ${agent.content_hash}`);
|
|
4225
|
+
}
|
|
4226
|
+
function printAgentRun(run, options) {
|
|
4227
|
+
if (options.json) {
|
|
4228
|
+
console.log(JSON.stringify(run, null, 2));
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
console.log(`Status: ${run.status}`);
|
|
4232
|
+
console.log(`Request: ${run.request_id}`);
|
|
4233
|
+
if (run.error_code || run.error_message) {
|
|
4234
|
+
console.log(`Error: ${[run.error_code, run.error_message].filter(Boolean).join(" - ")}`);
|
|
4235
|
+
}
|
|
4236
|
+
console.log("Output:");
|
|
4237
|
+
console.log(JSON.stringify(run.output_json, null, 2));
|
|
4238
|
+
if (options.trace) {
|
|
4239
|
+
console.log("");
|
|
4240
|
+
if (run.steps.length === 0) {
|
|
4241
|
+
console.log("Trace: no steps");
|
|
4242
|
+
}
|
|
4243
|
+
else {
|
|
4244
|
+
console.log("Trace:");
|
|
4245
|
+
console.log(formatTable(["#", "kind", "status", "tool", "summary"], run.steps.map((step) => [
|
|
4246
|
+
String(step.step_index),
|
|
4247
|
+
step.kind,
|
|
4248
|
+
step.status,
|
|
4249
|
+
step.tool_name ?? "-",
|
|
4250
|
+
step.result_summary ?? step.args_summary ?? step.error_message ?? "-",
|
|
4251
|
+
])));
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
function printSchedule(schedule) {
|
|
4256
|
+
console.log(schedule.name);
|
|
4257
|
+
console.log(`UUID: ${schedule.uuid}`);
|
|
4258
|
+
console.log(`Cron: ${schedule.schedule}`);
|
|
4259
|
+
console.log(`Timezone: ${schedule.timezone}`);
|
|
4260
|
+
console.log(`Enabled: ${schedule.enabled ? "yes" : "no"}`);
|
|
4261
|
+
console.log(`Next fire: ${schedule.next_fire_at}`);
|
|
4262
|
+
console.log(`Last fire: ${schedule.last_fire_at ?? "-"}`);
|
|
4263
|
+
console.log(`Last run: ${schedule.last_run_uuid ?? "-"}`);
|
|
4264
|
+
}
|
|
4265
|
+
function assertKnownAgentOptions(args, allowed) {
|
|
4266
|
+
for (const key of Object.keys(args.options)) {
|
|
4267
|
+
if (!allowed.includes(key)) {
|
|
4268
|
+
throw new CliError(`Unknown option --${camelToKebab(key)} for \`railcode agent\`.\n\n${AGENT_HELP}`, 2);
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
function shortDate(value) {
|
|
4273
|
+
return value.includes("T") ? value.slice(0, 10) : value;
|
|
4274
|
+
}
|
|
4275
|
+
// ---------------------------------------------------------------------------
|
|
3537
4276
|
// db — list data connectors + run read-only SQL (`railcode db list|query`)
|
|
3538
4277
|
//
|
|
3539
4278
|
// Data connectors are org-wide, so these commands use the app-less org data
|
|
@@ -3552,7 +4291,7 @@ List options:
|
|
|
3552
4291
|
|
|
3553
4292
|
Query options:
|
|
3554
4293
|
--connection <name> Connector to query (default: default)
|
|
3555
|
-
--engine <postgres|
|
|
4294
|
+
--engine <postgres|bigquery|turso> Engine override (inferred from --connection otherwise)
|
|
3556
4295
|
--params '<json-array>' Values for $1, $2, … placeholders
|
|
3557
4296
|
--file <path> Read SQL from a file instead of the argument
|
|
3558
4297
|
--json Print the raw { columns, rows, rowcount, truncated } envelope
|
|
@@ -4903,6 +5642,10 @@ async function handleLocalApi(ctx, req, res, url) {
|
|
|
4903
5642
|
sendJson(res, 200, [appUserEnvelope()]);
|
|
4904
5643
|
return;
|
|
4905
5644
|
}
|
|
5645
|
+
if (path === "/roles") {
|
|
5646
|
+
sendJson(res, 200, []);
|
|
5647
|
+
return;
|
|
5648
|
+
}
|
|
4906
5649
|
if (path === "/config/design-system") {
|
|
4907
5650
|
await handleDevDesignSystem(ctx, res);
|
|
4908
5651
|
return;
|
|
@@ -5253,13 +5996,19 @@ async function handleDevDesignSystem(ctx, res) {
|
|
|
5253
5996
|
// ── envelopes + http helpers ──────────────────────────────────────────────────
|
|
5254
5997
|
function meEnvelope(ctx) {
|
|
5255
5998
|
return {
|
|
5256
|
-
user: {
|
|
5999
|
+
user: {
|
|
6000
|
+
uuid: DEV_USER_UUID,
|
|
6001
|
+
name: "Local Dev",
|
|
6002
|
+
email: "dev@localhost",
|
|
6003
|
+
is_admin: true,
|
|
6004
|
+
roles: [],
|
|
6005
|
+
},
|
|
5257
6006
|
app: { uuid: DEV_APP_UUID, slug: ctx.app, name: titleCase(ctx.app) },
|
|
5258
6007
|
org: { uuid: DEV_ORG_UUID, slug: "local", name: "Local" },
|
|
5259
6008
|
};
|
|
5260
6009
|
}
|
|
5261
6010
|
function appUserEnvelope() {
|
|
5262
|
-
return { uuid: DEV_USER_UUID, name: "Local Dev", email: "dev@localhost",
|
|
6011
|
+
return { uuid: DEV_USER_UUID, name: "Local Dev", email: "dev@localhost", is_admin: true };
|
|
5263
6012
|
}
|
|
5264
6013
|
function isLoopbackHost(host) {
|
|
5265
6014
|
if (!host)
|
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
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
padding:2px 6px; border-radius:4px; white-space:nowrap; align-self:start; margin-top:2px; }
|
|
36
36
|
.k-sql { background:#1e3a5f; color:#7cc4ff; } .k-db { background:#3a2752; color:#c79bff; }
|
|
37
37
|
.k-files { background:#13413c; color:#5fd6c4; } .k-llm { background:#4a2b16; color:#f6b26b; }
|
|
38
|
-
.k-me, .k-connections, .k-app-users { background:#2a2f38; color:#9aa4b2; }
|
|
38
|
+
.k-me, .k-connections, .k-app-users, .k-roles { background:#2a2f38; color:#9aa4b2; }
|
|
39
39
|
.body { min-width:0; }
|
|
40
40
|
code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size:11.5px; color:#e6e8eb;
|
|
41
41
|
word-break:break-word; display:block; line-height:1.45; }
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
<button id="collapse">Hide \u203A</button>
|
|
55
55
|
</header>
|
|
56
56
|
<div class="log" id="log"></div>
|
|
57
|
-
<footer>Calls made via <code>postgres()</code>, <code>llm</code>, <code>db</code>, <code>files</code>, <code>me()</code>, <code>appUsers()</code></footer>
|
|
57
|
+
<footer>Calls made via <code>postgres()</code>, <code>llm</code>, <code>db</code>, <code>files</code>, <code>me()</code>, <code>appUsers()</code>, <code>roles()</code></footer>
|
|
58
58
|
</div>`;
|
|
59
59
|
function mount() {
|
|
60
60
|
(document.body || document.documentElement).appendChild(host);
|
|
@@ -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;
|
|
@@ -256,7 +265,7 @@
|
|
|
256
265
|
var data = namespace();
|
|
257
266
|
var postgres = namespace("postgres");
|
|
258
267
|
var bigquery = namespace("bigquery");
|
|
259
|
-
var
|
|
268
|
+
var turso = namespace("turso");
|
|
260
269
|
var dataConnectors = () => track("connections", "dataConnectors()", () => call("GET", "/connections"));
|
|
261
270
|
|
|
262
271
|
// ../sdk/src/util.ts
|
|
@@ -268,9 +277,18 @@
|
|
|
268
277
|
}
|
|
269
278
|
|
|
270
279
|
// ../sdk/src/db.ts
|
|
280
|
+
function scopeParams(s) {
|
|
281
|
+
return { scope: s.scope, role: s.role };
|
|
282
|
+
}
|
|
283
|
+
function scopeLabel(s) {
|
|
284
|
+
if (s.scope === "user") return "db.user";
|
|
285
|
+
if (s.scope === "role") return `db.role(${q(s.role ?? "")})`;
|
|
286
|
+
return "db.shared";
|
|
287
|
+
}
|
|
271
288
|
var Query = class {
|
|
272
|
-
constructor(collection) {
|
|
289
|
+
constructor(collection, scope) {
|
|
273
290
|
this.collection = collection;
|
|
291
|
+
this.scope = scope;
|
|
274
292
|
this._where = [];
|
|
275
293
|
}
|
|
276
294
|
where(field, op, value) {
|
|
@@ -300,11 +318,12 @@
|
|
|
300
318
|
prefix: this._prefix,
|
|
301
319
|
updated_since: this._since,
|
|
302
320
|
updated_before: this._before,
|
|
321
|
+
...scopeParams(this.scope),
|
|
303
322
|
...extra
|
|
304
323
|
};
|
|
305
324
|
}
|
|
306
325
|
label(method) {
|
|
307
|
-
return
|
|
326
|
+
return `${scopeLabel(this.scope)}.collection(${q(this.collection)}).${method}`;
|
|
308
327
|
}
|
|
309
328
|
async fetchPage(page, size) {
|
|
310
329
|
const resp = await call("GET", `/kv/${encPath(this.collection)}`, {
|
|
@@ -331,16 +350,20 @@
|
|
|
331
350
|
}
|
|
332
351
|
};
|
|
333
352
|
var Collection = class {
|
|
334
|
-
constructor(name) {
|
|
353
|
+
constructor(name, scope) {
|
|
335
354
|
this.name = name;
|
|
355
|
+
this.scope = scope;
|
|
336
356
|
}
|
|
337
357
|
label(method) {
|
|
338
|
-
return
|
|
358
|
+
return `${scopeLabel(this.scope)}.collection(${q(this.name)}).${method}`;
|
|
339
359
|
}
|
|
340
360
|
/** The stored value for ``key``, or null if absent. */
|
|
341
361
|
get(key) {
|
|
342
362
|
return track("db", this.label(`get(${q(key)})`), async () => {
|
|
343
|
-
const record = await getOrNull(
|
|
363
|
+
const record = await getOrNull(
|
|
364
|
+
`/kv/${encPath(this.name)}/${encPath(key)}`,
|
|
365
|
+
scopeParams(this.scope)
|
|
366
|
+
);
|
|
344
367
|
return record ? record.value : null;
|
|
345
368
|
});
|
|
346
369
|
}
|
|
@@ -348,7 +371,8 @@
|
|
|
348
371
|
put(key, value) {
|
|
349
372
|
return track("db", this.label(`put(${q(key)}, ${shorten(JSON.stringify(value), 40)})`), async () => {
|
|
350
373
|
const record = await call("PUT", `/kv/${encPath(this.name)}/${encPath(key)}`, {
|
|
351
|
-
body: { value }
|
|
374
|
+
body: { value },
|
|
375
|
+
query: scopeParams(this.scope)
|
|
352
376
|
});
|
|
353
377
|
return record.value;
|
|
354
378
|
});
|
|
@@ -357,18 +381,22 @@
|
|
|
357
381
|
return track(
|
|
358
382
|
"db",
|
|
359
383
|
this.label(`delete(${q(key)})`),
|
|
360
|
-
() => call("DELETE", `/kv/${encPath(this.name)}/${encPath(key)}
|
|
384
|
+
() => call("DELETE", `/kv/${encPath(this.name)}/${encPath(key)}`, {
|
|
385
|
+
query: scopeParams(this.scope)
|
|
386
|
+
})
|
|
361
387
|
);
|
|
362
388
|
}
|
|
363
389
|
/** All records in the collection (first page). */
|
|
364
390
|
list() {
|
|
365
391
|
return track("db", this.label("list()"), async () => {
|
|
366
|
-
const resp = await call("GET", `/kv/${encPath(this.name)}
|
|
392
|
+
const resp = await call("GET", `/kv/${encPath(this.name)}`, {
|
|
393
|
+
query: scopeParams(this.scope)
|
|
394
|
+
});
|
|
367
395
|
return resp.items;
|
|
368
396
|
});
|
|
369
397
|
}
|
|
370
398
|
query() {
|
|
371
|
-
return new Query(this.name);
|
|
399
|
+
return new Query(this.name, this.scope);
|
|
372
400
|
}
|
|
373
401
|
where(field, op, value) {
|
|
374
402
|
return this.query().where(field, op, value);
|
|
@@ -377,8 +405,15 @@
|
|
|
377
405
|
return this.query().prefix(value);
|
|
378
406
|
}
|
|
379
407
|
};
|
|
408
|
+
function namespace2(scope) {
|
|
409
|
+
return { collection: (name) => new Collection(name, scope) };
|
|
410
|
+
}
|
|
411
|
+
var shared = namespace2({});
|
|
380
412
|
var db = {
|
|
381
|
-
|
|
413
|
+
shared,
|
|
414
|
+
user: namespace2({ scope: "user" }),
|
|
415
|
+
role: (roleUuid) => namespace2({ scope: "role", role: roleUuid }),
|
|
416
|
+
collection: shared.collection
|
|
382
417
|
};
|
|
383
418
|
|
|
384
419
|
// ../sdk/src/design-system.ts
|
|
@@ -414,18 +449,41 @@
|
|
|
414
449
|
var email = { send: send2 };
|
|
415
450
|
|
|
416
451
|
// ../sdk/src/files.ts
|
|
452
|
+
var URL_EXPIRY_MARGIN_MS = 15e3;
|
|
453
|
+
var urlCache = /* @__PURE__ */ new Map();
|
|
454
|
+
function scopeParams2(s) {
|
|
455
|
+
return { scope: s.scope, role: s.role };
|
|
456
|
+
}
|
|
457
|
+
function scopeKey(s) {
|
|
458
|
+
if (s.scope === "user") return "user";
|
|
459
|
+
if (s.scope === "role") return `role:${s.role ?? ""}`;
|
|
460
|
+
return "shared";
|
|
461
|
+
}
|
|
462
|
+
function scopeLabel2(s) {
|
|
463
|
+
if (s.scope === "user") return "files.user";
|
|
464
|
+
if (s.scope === "role") return `files.role(${q(s.role ?? "")})`;
|
|
465
|
+
return "files.shared";
|
|
466
|
+
}
|
|
467
|
+
function cacheKey(s, name) {
|
|
468
|
+
return `${scopeKey(s)} ${name}`;
|
|
469
|
+
}
|
|
417
470
|
function contentTypeOf(data2, explicit) {
|
|
418
471
|
if (explicit) return explicit;
|
|
419
472
|
if (data2 instanceof Blob && data2.type) return data2.type;
|
|
420
473
|
return "application/octet-stream";
|
|
421
474
|
}
|
|
422
|
-
function upload(name, data2, contentType) {
|
|
423
|
-
return track("files",
|
|
475
|
+
function upload(scope, name, data2, contentType) {
|
|
476
|
+
return track("files", `${scopeLabel2(scope)}.upload(${q(name)}, <blob>)`, async () => {
|
|
477
|
+
const result = await doUpload(scope, name, data2, contentType);
|
|
478
|
+
urlCache.delete(cacheKey(scope, name));
|
|
479
|
+
return result;
|
|
480
|
+
});
|
|
424
481
|
}
|
|
425
|
-
async function doUpload(name, data2, contentType) {
|
|
482
|
+
async function doUpload(scope, name, data2, contentType) {
|
|
426
483
|
const ct = contentTypeOf(data2, contentType);
|
|
427
484
|
const target2 = await call("POST", "/files", {
|
|
428
|
-
body: { name, content_type: ct }
|
|
485
|
+
body: { name, content_type: ct },
|
|
486
|
+
query: scopeParams2(scope)
|
|
429
487
|
});
|
|
430
488
|
const headers = { "Content-Type": ct, ...target2.headers };
|
|
431
489
|
if (target2.mode === "proxy") {
|
|
@@ -440,21 +498,79 @@
|
|
|
440
498
|
}
|
|
441
499
|
const resp = await fetch(target2.url, { method: target2.method, body: data2, headers });
|
|
442
500
|
if (!resp.ok) throw new Error(`upload failed: ${resp.status}`);
|
|
443
|
-
return call("POST", "/files/finalize", { body: { name } });
|
|
501
|
+
return call("POST", "/files/finalize", { body: { name }, query: scopeParams2(scope) });
|
|
444
502
|
}
|
|
445
|
-
function url(name) {
|
|
446
|
-
return buildUrl(`/files/${encPath(name)}
|
|
503
|
+
function url(scope, name) {
|
|
504
|
+
return buildUrl(`/files/${encPath(name)}`, scopeParams2(scope));
|
|
447
505
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
506
|
+
async function urls(scope, names) {
|
|
507
|
+
return track("files", `${scopeLabel2(scope)}.urls(<${names.length} names>)`, async () => {
|
|
508
|
+
const unique = [...new Set(names)];
|
|
509
|
+
const now = Date.now();
|
|
510
|
+
const items = /* @__PURE__ */ new Map();
|
|
511
|
+
const unresolved = [];
|
|
512
|
+
for (const name of unique) {
|
|
513
|
+
const cached = urlCache.get(cacheKey(scope, name));
|
|
514
|
+
if (cached && cached.expiresAt > now + URL_EXPIRY_MARGIN_MS) items.set(name, cached.item);
|
|
515
|
+
else {
|
|
516
|
+
urlCache.delete(cacheKey(scope, name));
|
|
517
|
+
unresolved.push(name);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
let missing = [];
|
|
521
|
+
if (unresolved.length) {
|
|
522
|
+
const resolved = await call("POST", "/files/urls", {
|
|
523
|
+
body: { names: unresolved },
|
|
524
|
+
query: scopeParams2(scope)
|
|
525
|
+
});
|
|
526
|
+
missing = resolved.missing;
|
|
527
|
+
for (const item of resolved.items) {
|
|
528
|
+
const normalized = { ...item, url: new URL(item.url, location.origin).toString() };
|
|
529
|
+
const expiresAt = item.expires_in === null ? Number.POSITIVE_INFINITY : now + item.expires_in * 1e3;
|
|
530
|
+
urlCache.set(cacheKey(scope, item.name), { item: normalized, expiresAt });
|
|
531
|
+
items.set(item.name, normalized);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return { items: unique.flatMap((name) => items.has(name) ? [items.get(name)] : []), missing };
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
function list(scope) {
|
|
538
|
+
return track("files", `${scopeLabel2(scope)}.list()`, async () => {
|
|
539
|
+
const resp = await call("GET", "/files", { query: scopeParams2(scope) });
|
|
540
|
+
return resp.items;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
function remove(scope, name) {
|
|
544
|
+
return track("files", `${scopeLabel2(scope)}.delete(${q(name)})`, async () => {
|
|
545
|
+
await call("DELETE", `/files/${encPath(name)}`, { query: scopeParams2(scope) });
|
|
546
|
+
urlCache.delete(cacheKey(scope, name));
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
function fileNamespace(scope) {
|
|
550
|
+
return {
|
|
551
|
+
upload: (name, data2, contentType) => upload(scope, name, data2, contentType),
|
|
552
|
+
url: (name) => url(scope, name),
|
|
553
|
+
urls: (names) => urls(scope, names),
|
|
554
|
+
list: () => list(scope),
|
|
555
|
+
delete: (name) => remove(scope, name)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
var shared2 = fileNamespace({});
|
|
559
|
+
var files = {
|
|
560
|
+
shared: shared2,
|
|
561
|
+
user: fileNamespace({ scope: "user" }),
|
|
562
|
+
role: (roleUuid) => fileNamespace({ scope: "role", role: roleUuid }),
|
|
563
|
+
upload: shared2.upload,
|
|
564
|
+
url: shared2.url,
|
|
565
|
+
urls: shared2.urls,
|
|
566
|
+
list: shared2.list,
|
|
567
|
+
delete: shared2.delete
|
|
568
|
+
};
|
|
454
569
|
|
|
455
570
|
// ../sdk/src/identity.ts
|
|
456
571
|
var me = () => track("me", "me()", () => call("GET", "/me"));
|
|
457
572
|
var appUsers = () => track("app-users", "appUsers()", () => call("GET", "/app-users"));
|
|
573
|
+
var roles = () => track("roles", "roles()", () => call("GET", "/roles"));
|
|
458
574
|
|
|
459
575
|
// ../sdk/src/llm.ts
|
|
460
576
|
function label2(method, input, opts = {}) {
|
|
@@ -580,6 +696,7 @@
|
|
|
580
696
|
target.files = files;
|
|
581
697
|
target.me = me;
|
|
582
698
|
target.appUsers = appUsers;
|
|
699
|
+
target.roles = roles;
|
|
583
700
|
target.designSystem = designSystem;
|
|
584
701
|
target.email = email;
|
|
585
702
|
target.llm = llm;
|
|
@@ -587,10 +704,11 @@
|
|
|
587
704
|
target.data = data;
|
|
588
705
|
target.postgres = postgres;
|
|
589
706
|
target.bigquery = bigquery;
|
|
590
|
-
target.
|
|
707
|
+
target.turso = turso;
|
|
591
708
|
target.dataConnectors = dataConnectors;
|
|
592
709
|
target.connector = connector;
|
|
593
710
|
target.serviceConnectors = serviceConnectors;
|
|
594
711
|
target.query = query;
|
|
595
712
|
target.savedQueries = savedQueries;
|
|
713
|
+
target.agents = agents;
|
|
596
714
|
})();
|