railcode 0.1.23 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,7 +32,7 @@ railcode roles <list|create|update|delete|add-member|remove-member|grants|grant|
32
32
  Org roles + the granular grants table (admin)
33
33
  railcode apps <list|show|access|set-access|transfer|delete> ...
34
34
  Apps + access policy (owner/admin)
35
- railcode analytics <app> [--range 7d|30d|90d] Per-app pageview analytics
35
+ railcode analytics <app> [--range 1d|7d|30d|90d] Per-app pageview analytics
36
36
  railcode logs <connector|service-connector|llm|email|agent> [filters]
37
37
  Org observability logs (admin)
38
38
  ```
@@ -83,12 +83,16 @@ export function parseAuthType(value) {
83
83
  }
84
84
  return v;
85
85
  }
86
+ // A null content_type is a pre-default row: the proxy still sends application/json with
87
+ // any body, so show the effective value rather than a bare "-".
88
+ export const DEFAULT_CONTENT_TYPE = "application/json";
86
89
  export function serviceConnectorAdminTable(list) {
87
- return formatTable(["name", "base_url", "auth", "cred", "methods", "enabled", "native"], list.map((c) => [
90
+ return formatTable(["name", "base_url", "auth", "cred", "content_type", "methods", "enabled", "native"], list.map((c) => [
88
91
  c.name,
89
92
  c.base_url,
90
93
  c.auth_type,
91
94
  c.has_credential ? "yes" : "no",
95
+ c.content_type || DEFAULT_CONTENT_TYPE,
92
96
  (c.allowed_methods ?? []).join(", ") || "all",
93
97
  c.enabled ? "yes" : "no",
94
98
  c.native_key ?? "-",
@@ -141,11 +145,30 @@ export function buildServiceConnectorCreateBody(input) {
141
145
  body.auth_config = input.authConfig;
142
146
  if (input.description !== undefined)
143
147
  body.description = input.description;
148
+ // Omitted → the server stores application/json. Only send the field when the caller
149
+ // actually chose a type (e.g. a form-encoded API).
150
+ if (input.contentType !== undefined)
151
+ body.content_type = parseContentType(input.contentType);
144
152
  if (input.allowedMethods && input.allowedMethods.length > 0) {
145
153
  body.allowed_methods = input.allowedMethods;
146
154
  }
147
155
  return body;
148
156
  }
157
+ // `--content-type <type>`: a single header value, so no CR/LF and nothing blank (an
158
+ // empty value would read as "unset" server-side — just leave the flag off for that).
159
+ export function parseContentType(value) {
160
+ const text = value.trim();
161
+ if (!text) {
162
+ throw new ConnectionAdminError("--content-type requires a value (e.g. application/x-www-form-urlencoded); omit it for application/json.");
163
+ }
164
+ if (/[\r\n]/.test(text)) {
165
+ throw new ConnectionAdminError("--content-type must not contain newlines.");
166
+ }
167
+ if (text.length > 255) {
168
+ throw new ConnectionAdminError("--content-type must be 255 characters or fewer.");
169
+ }
170
+ return text;
171
+ }
149
172
  export function parseMethodList(value) {
150
173
  if (value === undefined || value.trim() === "")
151
174
  return [];
@@ -49,7 +49,10 @@ export function connectorOpenapiOutput(docs) {
49
49
  // API docs (link or inline), and the OpenAPI spec (link or inline) when present.
50
50
  export function renderConnectorDocs(docs) {
51
51
  const methods = (docs.allowed_methods ?? []).join(", ") || "(all)";
52
- const sections = [`${docs.name} (${docs.auth_type}) — methods: ${methods}`];
52
+ const identity = [`${docs.name} (${docs.auth_type}) — methods: ${methods}`];
53
+ if (docs.content_type)
54
+ identity.push(`body Content-Type: ${docs.content_type}`);
55
+ const sections = [identity.join("\n")];
53
56
  if (docs.description)
54
57
  sections.push(docs.description);
55
58
  const hasAny = docs.usage_instructions ||
package/dist/index.js CHANGED
@@ -54,7 +54,7 @@ Usage:
54
54
  railcode roles <list|create|update|delete|add-member|remove-member|grants|grant|revoke|materialize|effective|catalog> ...
55
55
  Manage org roles + the granular grants table (admin)
56
56
  railcode apps <list|show|access|set-access|transfer|delete> ... Manage apps + access policy
57
- railcode analytics <app> [--range 7d|30d|90d] Per-app pageview analytics (admin/owner)
57
+ railcode analytics <app> [--range 1d|7d|30d|90d] Per-app pageview analytics (admin/owner)
58
58
  railcode logs <connector|service-connector|llm|email|agent> [filters]
59
59
  Read org observability logs (admin)
60
60
  railcode --version
@@ -4740,15 +4740,22 @@ Admin (service-connector:manage):
4740
4740
  railcode connector enable <key> --credentials '{...}'
4741
4741
  One-click enable/rotate a native preset
4742
4742
  railcode connector create --name <n> --base-url <url> [--auth-type <t>]
4743
- [--auth-config '{...}'] [--methods GET,POST] [--description <d>] [--disabled]
4743
+ [--auth-config '{...}'] [--methods GET,POST] [--description <d>]
4744
+ [--content-type <type>] [--disabled]
4744
4745
  Create/replace a custom connector
4745
4746
  railcode connector delete <name|uuid> Delete a service connector
4746
4747
 
4747
4748
  Aliases: connector = connectors; list = ls, connectors; docs = doc; fetch = request.
4748
4749
 
4750
+ Create options:
4751
+ --content-type <type> Content-Type sent with request bodies
4752
+ (default application/json; e.g.
4753
+ application/x-www-form-urlencoded for a form API)
4754
+
4749
4755
  List options:
4750
4756
  --admin Manage view: base_url, credential + enabled
4751
- state, native key (service-connector:manage)
4757
+ state, content type, native key
4758
+ (service-connector:manage)
4752
4759
  --json Print the raw connector array
4753
4760
 
4754
4761
  Docs options:
@@ -4846,6 +4853,7 @@ async function commandConnectorCreate(args) {
4846
4853
  "authConfig",
4847
4854
  "authConfigFile",
4848
4855
  "description",
4856
+ "contentType",
4849
4857
  "methods",
4850
4858
  "disabled",
4851
4859
  "apiUrl",
@@ -4860,6 +4868,7 @@ async function commandConnectorCreate(args) {
4860
4868
  authType,
4861
4869
  authConfig,
4862
4870
  description: optionString(args, "description"),
4871
+ contentType: optionString(args, "contentType"),
4863
4872
  allowedMethods: parseMethodList(optionString(args, "methods")),
4864
4873
  enabled: args.options.disabled !== true,
4865
4874
  });
@@ -5692,7 +5701,7 @@ async function commandAppsDelete(args) {
5692
5701
  const ANALYTICS_HELP = `railcode analytics — per-app pageview analytics (admin/owner)
5693
5702
 
5694
5703
  Usage:
5695
- railcode analytics <app> [--range 7d|30d|90d] Views, uniques, daily, top paths/users
5704
+ railcode analytics <app> [--range 1d|7d|30d|90d] Views, uniques, daily, top paths/users
5696
5705
 
5697
5706
  <app> is an app slug or UUID. Default range 30d.
5698
5707
 
@@ -177,7 +177,7 @@ export function logTable(stream, rows) {
177
177
  return formatTable(stream.columns, rows.map((r) => stream.map(r)));
178
178
  }
179
179
  // ── Analytics (per-app pageviews) ───────────────────────────────────────────
180
- export const ANALYTICS_RANGES = ["7d", "30d", "90d"];
180
+ export const ANALYTICS_RANGES = ["1d", "7d", "30d", "90d"];
181
181
  export function parseRange(value) {
182
182
  if (value === undefined)
183
183
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "railcode",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Developer CLI for the multi-tenant Railcode platform: log in, scaffold, and deploy static apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/static/sdk.js CHANGED
@@ -82,6 +82,7 @@
82
82
  localStorage.setItem("__sdk_open", v ? "1" : "0");
83
83
  root.getElementById("panel").style.display = v ? "flex" : "none";
84
84
  applyWidth();
85
+ window.dispatchEvent(new CustomEvent("railcode:inspector", { detail: { open: v } }));
85
86
  }
86
87
  var escMap = { "&": "&amp;", "<": "&lt;", ">": "&gt;" };
87
88
  var escHtml = (s) => String(s == null ? "" : s).replace(/[&<>]/g, (c) => escMap[c]);
@@ -106,7 +107,9 @@
106
107
  if (entries.length > 100) entries.shift();
107
108
  render();
108
109
  },
109
- update: render
110
+ update: render,
111
+ toggle: () => setOpen(!isOpen),
112
+ isOpen: () => isOpen
110
113
  };
111
114
 
112
115
  // ../sdk/src/config.ts
@@ -219,14 +222,84 @@
219
222
  }();
220
223
  }
221
224
 
225
+ // ../sdk/src/identity.ts
226
+ var me = () => track("me", "me()", () => call("GET", "/me"));
227
+ var appUsers = () => track("app-users", "appUsers()", () => call("GET", "/app-users"));
228
+ var roles = () => track("roles", "roles()", () => call("GET", "/roles"));
229
+
230
+ // ../sdk/src/debug-fab.ts
231
+ function mount2() {
232
+ const host2 = document.createElement("div");
233
+ host2.id = "__sdk_debug_fab";
234
+ const root2 = host2.attachShadow({ mode: "open" });
235
+ root2.innerHTML = `
236
+ <style>
237
+ :host { all: initial; }
238
+ button {
239
+ box-sizing: border-box; position: fixed; right: 16px; bottom: 16px;
240
+ width: 36px; height: 36px; border-radius: 50%; display: flex;
241
+ align-items: center; justify-content: center; cursor: pointer; padding: 0;
242
+ background: #14171c; color: #d6d9de; border: 1px solid #2c333d;
243
+ box-shadow: 0 2px 10px rgba(0,0,0,.35); z-index: 2147483001;
244
+ }
245
+ button:hover { background: #1c2128; color: #fff; }
246
+ svg { width: 16px; height: 16px; }
247
+ </style>
248
+ <button title="SDK activity (Ctrl+\`)" aria-label="Toggle SDK activity">
249
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
250
+ stroke-linecap="round" stroke-linejoin="round">
251
+ <path d="M12 19h8"></path>
252
+ <path d="m4 17 6-6-6-6"></path>
253
+ </svg>
254
+ </button>`;
255
+ root2.querySelector("button").onclick = () => Inspector.toggle();
256
+ (document.body || document.documentElement).appendChild(host2);
257
+ const sync = () => {
258
+ host2.style.display = Inspector.isOpen() ? "none" : "";
259
+ };
260
+ sync();
261
+ window.addEventListener("railcode:inspector", sync);
262
+ }
263
+ me().then((who) => {
264
+ if (who.user.is_admin) mount2();
265
+ }).catch(() => {
266
+ });
267
+
222
268
  // ../sdk/src/agents.ts
223
- async function doInvoke(name, input) {
269
+ var TERMINAL = ["success", "failed", "cancelled", "limit_exceeded"];
270
+ var POLL_MIN_MS = 400;
271
+ var POLL_MAX_MS = 2e3;
272
+ var POLL_BACKOFF = 1.4;
273
+ function isFinished(run) {
274
+ return TERMINAL.includes(run.status);
275
+ }
276
+ async function doStart(name, input) {
224
277
  return call("POST", `/agents/${encodeURIComponent(name)}`, { body: { input } });
225
278
  }
279
+ async function doGet(requestId) {
280
+ return call("GET", `/agents/runs/${encodeURIComponent(requestId)}`);
281
+ }
282
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
283
+ async function doInvoke(name, input) {
284
+ let run = await doStart(name, input);
285
+ let wait = POLL_MIN_MS;
286
+ while (!isFinished(run)) {
287
+ await sleep(wait);
288
+ wait = Math.min(wait * POLL_BACKOFF, POLL_MAX_MS);
289
+ run = await doGet(run.request_id);
290
+ }
291
+ return run;
292
+ }
293
+ function start(name, input) {
294
+ return track("agents", `agents.start(${q(name)})`, () => doStart(name, input));
295
+ }
296
+ function get(requestId) {
297
+ return doGet(requestId);
298
+ }
226
299
  function invoke(name, input) {
227
300
  return track("agents", `agents.invoke(${q(name)})`, () => doInvoke(name, input));
228
301
  }
229
- var agents = { invoke };
302
+ var agents = { invoke, start, get };
230
303
 
231
304
  // ../sdk/src/databases.ts
232
305
  var QUERY_LABEL_MAX = 80;
@@ -567,11 +640,6 @@
567
640
  delete: shared2.delete
568
641
  };
569
642
 
570
- // ../sdk/src/identity.ts
571
- var me = () => track("me", "me()", () => call("GET", "/me"));
572
- var appUsers = () => track("app-users", "appUsers()", () => call("GET", "/app-users"));
573
- var roles = () => track("roles", "roles()", () => call("GET", "/roles"));
574
-
575
643
  // ../sdk/src/llm.ts
576
644
  function label2(method, input, opts = {}) {
577
645
  const preview = typeof input === "string" ? shorten(input.replace(/\s+/g, " ").trim(), 70) : `${input.length} message${input.length === 1 ? "" : "s"}`;