station-kit 1.0.0

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.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli-main.d.ts +2 -0
  3. package/dist/cli-main.d.ts.map +1 -0
  4. package/dist/cli-main.js +58 -0
  5. package/dist/cli-main.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +25 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config/loader.d.ts +3 -0
  11. package/dist/config/loader.d.ts.map +1 -0
  12. package/dist/config/loader.js +29 -0
  13. package/dist/config/loader.js.map +1 -0
  14. package/dist/config/schema.d.ts +36 -0
  15. package/dist/config/schema.d.ts.map +1 -0
  16. package/dist/config/schema.js +40 -0
  17. package/dist/config/schema.js.map +1 -0
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +4 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/server/auth/keys.d.ts +28 -0
  23. package/dist/server/auth/keys.d.ts.map +1 -0
  24. package/dist/server/auth/keys.js +91 -0
  25. package/dist/server/auth/keys.js.map +1 -0
  26. package/dist/server/auth/session.d.ts +9 -0
  27. package/dist/server/auth/session.d.ts.map +1 -0
  28. package/dist/server/auth/session.js +42 -0
  29. package/dist/server/auth/session.js.map +1 -0
  30. package/dist/server/index.d.ts +7 -0
  31. package/dist/server/index.d.ts.map +1 -0
  32. package/dist/server/index.js +253 -0
  33. package/dist/server/index.js.map +1 -0
  34. package/dist/server/log-buffer.d.ts +20 -0
  35. package/dist/server/log-buffer.d.ts.map +1 -0
  36. package/dist/server/log-buffer.js +33 -0
  37. package/dist/server/log-buffer.js.map +1 -0
  38. package/dist/server/log-store.d.ts +11 -0
  39. package/dist/server/log-store.d.ts.map +1 -0
  40. package/dist/server/log-store.js +40 -0
  41. package/dist/server/log-store.js.map +1 -0
  42. package/dist/server/metadata.d.ts +38 -0
  43. package/dist/server/metadata.d.ts.map +1 -0
  44. package/dist/server/metadata.js +130 -0
  45. package/dist/server/metadata.js.map +1 -0
  46. package/dist/server/middleware/auth.d.ts +12 -0
  47. package/dist/server/middleware/auth.d.ts.map +1 -0
  48. package/dist/server/middleware/auth.js +42 -0
  49. package/dist/server/middleware/auth.js.map +1 -0
  50. package/dist/server/middleware/rate-limit.d.ts +15 -0
  51. package/dist/server/middleware/rate-limit.d.ts.map +1 -0
  52. package/dist/server/middleware/rate-limit.js +36 -0
  53. package/dist/server/middleware/rate-limit.js.map +1 -0
  54. package/dist/server/middleware/scope-guard.d.ts +9 -0
  55. package/dist/server/middleware/scope-guard.d.ts.map +1 -0
  56. package/dist/server/middleware/scope-guard.js +17 -0
  57. package/dist/server/middleware/scope-guard.js.map +1 -0
  58. package/dist/server/routes/broadcasts.d.ts +12 -0
  59. package/dist/server/routes/broadcasts.d.ts.map +1 -0
  60. package/dist/server/routes/broadcasts.js +135 -0
  61. package/dist/server/routes/broadcasts.js.map +1 -0
  62. package/dist/server/routes/health.d.ts +9 -0
  63. package/dist/server/routes/health.d.ts.map +1 -0
  64. package/dist/server/routes/health.js +27 -0
  65. package/dist/server/routes/health.js.map +1 -0
  66. package/dist/server/routes/runs.d.ts +12 -0
  67. package/dist/server/routes/runs.d.ts.map +1 -0
  68. package/dist/server/routes/runs.js +122 -0
  69. package/dist/server/routes/runs.js.map +1 -0
  70. package/dist/server/routes/signals.d.ts +10 -0
  71. package/dist/server/routes/signals.d.ts.map +1 -0
  72. package/dist/server/routes/signals.js +120 -0
  73. package/dist/server/routes/signals.js.map +1 -0
  74. package/dist/server/routes/v1/auth.d.ts +7 -0
  75. package/dist/server/routes/v1/auth.d.ts.map +1 -0
  76. package/dist/server/routes/v1/auth.js +28 -0
  77. package/dist/server/routes/v1/auth.js.map +1 -0
  78. package/dist/server/routes/v1/broadcasts.d.ts +10 -0
  79. package/dist/server/routes/v1/broadcasts.d.ts.map +1 -0
  80. package/dist/server/routes/v1/broadcasts.js +68 -0
  81. package/dist/server/routes/v1/broadcasts.js.map +1 -0
  82. package/dist/server/routes/v1/events.d.ts +7 -0
  83. package/dist/server/routes/v1/events.d.ts.map +1 -0
  84. package/dist/server/routes/v1/events.js +57 -0
  85. package/dist/server/routes/v1/events.js.map +1 -0
  86. package/dist/server/routes/v1/health.d.ts +9 -0
  87. package/dist/server/routes/v1/health.d.ts.map +1 -0
  88. package/dist/server/routes/v1/health.js +31 -0
  89. package/dist/server/routes/v1/health.js.map +1 -0
  90. package/dist/server/routes/v1/keys.d.ts +7 -0
  91. package/dist/server/routes/v1/keys.d.ts.map +1 -0
  92. package/dist/server/routes/v1/keys.js +43 -0
  93. package/dist/server/routes/v1/keys.js.map +1 -0
  94. package/dist/server/routes/v1/runs.d.ts +12 -0
  95. package/dist/server/routes/v1/runs.d.ts.map +1 -0
  96. package/dist/server/routes/v1/runs.js +76 -0
  97. package/dist/server/routes/v1/runs.js.map +1 -0
  98. package/dist/server/routes/v1/signals.d.ts +9 -0
  99. package/dist/server/routes/v1/signals.d.ts.map +1 -0
  100. package/dist/server/routes/v1/signals.js +33 -0
  101. package/dist/server/routes/v1/signals.js.map +1 -0
  102. package/dist/server/routes/v1/trigger.d.ts +12 -0
  103. package/dist/server/routes/v1/trigger.d.ts.map +1 -0
  104. package/dist/server/routes/v1/trigger.js +73 -0
  105. package/dist/server/routes/v1/trigger.js.map +1 -0
  106. package/dist/server/sse.d.ts +19 -0
  107. package/dist/server/sse.d.ts.map +1 -0
  108. package/dist/server/sse.js +51 -0
  109. package/dist/server/sse.js.map +1 -0
  110. package/dist/server/subscriber.d.ts +128 -0
  111. package/dist/server/subscriber.d.ts.map +1 -0
  112. package/dist/server/subscriber.js +246 -0
  113. package/dist/server/subscriber.js.map +1 -0
  114. package/dist/server/ws.d.ts +15 -0
  115. package/dist/server/ws.d.ts.map +1 -0
  116. package/dist/server/ws.js +32 -0
  117. package/dist/server/ws.js.map +1 -0
  118. package/next-env.d.ts +6 -0
  119. package/next.config.ts +10 -0
  120. package/package.json +49 -0
  121. package/src/app/broadcasts/[id]/page.tsx +511 -0
  122. package/src/app/broadcasts/page.tsx +158 -0
  123. package/src/app/components/auth-provider.tsx +75 -0
  124. package/src/app/components/breadcrumb-provider.tsx +18 -0
  125. package/src/app/components/dag-view.tsx +380 -0
  126. package/src/app/components/empty-state.tsx +7 -0
  127. package/src/app/components/json-viewer.tsx +153 -0
  128. package/src/app/components/login-page.tsx +78 -0
  129. package/src/app/components/node-detail.tsx +158 -0
  130. package/src/app/components/pulse-dot.tsx +8 -0
  131. package/src/app/components/relative-time.tsx +34 -0
  132. package/src/app/components/run-table.tsx +96 -0
  133. package/src/app/components/schema-form.tsx +121 -0
  134. package/src/app/components/shell.tsx +203 -0
  135. package/src/app/components/status-badge.tsx +10 -0
  136. package/src/app/components/step-timeline.tsx +134 -0
  137. package/src/app/components/theme-provider.tsx +45 -0
  138. package/src/app/components/workflow-node-sidebar.tsx +68 -0
  139. package/src/app/globals.css +1523 -0
  140. package/src/app/hooks/use-api.ts +129 -0
  141. package/src/app/hooks/use-breadcrumb.ts +37 -0
  142. package/src/app/hooks/use-realtime.ts +68 -0
  143. package/src/app/hooks/use-station.tsx +34 -0
  144. package/src/app/layout.tsx +42 -0
  145. package/src/app/page.tsx +275 -0
  146. package/src/app/runs/[id]/page.tsx +277 -0
  147. package/src/app/signals/[name]/page.tsx +250 -0
  148. package/src/app/signals/page.tsx +99 -0
  149. package/src/cli-main.ts +70 -0
  150. package/src/cli.ts +27 -0
  151. package/src/config/loader.ts +33 -0
  152. package/src/config/schema.ts +80 -0
  153. package/src/index.ts +7 -0
  154. package/src/server/auth/keys.ts +112 -0
  155. package/src/server/auth/session.ts +48 -0
  156. package/src/server/index.ts +296 -0
  157. package/src/server/log-buffer.ts +43 -0
  158. package/src/server/log-store.ts +56 -0
  159. package/src/server/metadata.ts +180 -0
  160. package/src/server/middleware/auth.ts +50 -0
  161. package/src/server/middleware/rate-limit.ts +61 -0
  162. package/src/server/middleware/scope-guard.ts +20 -0
  163. package/src/server/routes/broadcasts.ts +160 -0
  164. package/src/server/routes/health.ts +37 -0
  165. package/src/server/routes/runs.ts +149 -0
  166. package/src/server/routes/signals.ts +153 -0
  167. package/src/server/routes/v1/auth.ts +47 -0
  168. package/src/server/routes/v1/broadcasts.ts +84 -0
  169. package/src/server/routes/v1/events.ts +71 -0
  170. package/src/server/routes/v1/health.ts +41 -0
  171. package/src/server/routes/v1/keys.ts +57 -0
  172. package/src/server/routes/v1/runs.ts +97 -0
  173. package/src/server/routes/v1/signals.ts +44 -0
  174. package/src/server/routes/v1/trigger.ts +111 -0
  175. package/src/server/sse.ts +70 -0
  176. package/src/server/subscriber.ts +288 -0
  177. package/src/server/ws.ts +44 -0
  178. package/station.config.example.ts +16 -0
  179. package/tsconfig.json +12 -0
  180. package/tsconfig.next.json +15 -0
  181. package/tsconfig.tsbuildinfo +1 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 porkytheblack
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-main.d.ts","sourceRoot":"","sources":["../src/cli-main.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
1
+ import { spawn } from "node:child_process";
2
+ import { resolve } from "node:path";
3
+ import { loadConfig } from "./config/loader.js";
4
+ import { createStation } from "./server/index.js";
5
+ const cwd = process.cwd();
6
+ const config = await loadConfig(cwd);
7
+ const station = await createStation(config, cwd);
8
+ await station.start();
9
+ // Start Next.js dev server as child process
10
+ const nextPort = config.port + 1;
11
+ const stationRoot = resolve(import.meta.dirname, "..");
12
+ const nextProcess = spawn("npx", ["next", "dev", "--port", String(nextPort), "--hostname", config.host], {
13
+ cwd: stationRoot,
14
+ stdio: ["ignore", "pipe", "pipe"],
15
+ env: {
16
+ ...process.env,
17
+ NEXT_PUBLIC_STATION_API: `http://${config.host}:${config.port}`,
18
+ },
19
+ });
20
+ nextProcess.stdout?.on("data", (chunk) => {
21
+ const msg = chunk.toString().trim();
22
+ if (msg)
23
+ console.log(`[station:ui] ${msg}`);
24
+ });
25
+ nextProcess.stderr?.on("data", (chunk) => {
26
+ const msg = chunk.toString().trim();
27
+ // Filter out noisy Next.js dev warnings
28
+ if (msg && !msg.includes("ExperimentalWarning")) {
29
+ console.error(`[station:ui] ${msg}`);
30
+ }
31
+ });
32
+ console.log(`[station] Dashboard on http://${config.host}:${nextPort}`);
33
+ // Open browser
34
+ if (config.open) {
35
+ const url = `http://${config.host}:${nextPort}`;
36
+ const { execFile } = await import("node:child_process");
37
+ const platform = process.platform;
38
+ await new Promise((res) => setTimeout(() => res(true), 5000));
39
+ if (platform === "darwin") {
40
+ execFile("open", [url]);
41
+ }
42
+ else if (platform === "linux") {
43
+ execFile("xdg-open", [url]);
44
+ }
45
+ else if (platform === "win32") {
46
+ execFile("cmd", ["/c", "start", url]);
47
+ }
48
+ }
49
+ // Graceful shutdown
50
+ const shutdown = async () => {
51
+ console.log("\n[station] Shutting down...");
52
+ nextProcess.kill("SIGTERM");
53
+ await station.stop();
54
+ process.exit(0);
55
+ };
56
+ process.on("SIGINT", shutdown);
57
+ process.on("SIGTERM", shutdown);
58
+ //# sourceMappingURL=cli-main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-main.js","sourceRoot":"","sources":["../src/cli-main.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ // Station CLI launcher — re-executes itself with tsx loader so user .ts files
3
+ // (signals, broadcasts, configs) can be imported with full resolution.
4
+ import { execPath } from "node:process";
5
+ import { spawn } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+ const MARKER = "__STATION_TSX_LOADED";
8
+ if (!process.env[MARKER]) {
9
+ // Re-exec with --import tsx
10
+ const main = fileURLToPath(new URL("./cli-main.js", import.meta.url));
11
+ const child = spawn(execPath, ["--import", "tsx", main], {
12
+ stdio: "inherit",
13
+ env: { ...process.env, [MARKER]: "1" },
14
+ });
15
+ child.on("exit", (code) => process.exit(code ?? 0));
16
+ child.on("error", (err) => {
17
+ console.error("[station] Failed to start:", err.message);
18
+ process.exit(1);
19
+ });
20
+ }
21
+ else {
22
+ // Already loaded with tsx — import main
23
+ await import("./cli-main.js");
24
+ }
25
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,8EAA8E;AAC9E,uEAAuE;AAEvE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,MAAM,GAAG,sBAAsB,CAAC;AAEtC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;IACzB,4BAA4B;IAC5B,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE;QACvD,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE;KACvC,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACxB,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,wCAAwC;IACxC,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type StationConfig } from "./schema.js";
2
+ export declare function loadConfig(cwd: string): Promise<StationConfig>;
3
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAQhE,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAapE"}
@@ -0,0 +1,29 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { resolveConfig } from "./schema.js";
5
+ const CONFIG_NAMES = [
6
+ "station.config.ts",
7
+ "station.config.js",
8
+ "station.config.mjs",
9
+ ];
10
+ export async function loadConfig(cwd) {
11
+ const configPath = findConfigFile(cwd);
12
+ if (!configPath) {
13
+ console.log("[station] No config file found. Using defaults.");
14
+ return resolveConfig({});
15
+ }
16
+ console.log(`[station] Loading ${configPath}`);
17
+ const mod = await import(pathToFileURL(resolve(configPath)).href);
18
+ const raw = mod.default ?? mod;
19
+ return resolveConfig(raw);
20
+ }
21
+ function findConfigFile(cwd) {
22
+ for (const name of CONFIG_NAMES) {
23
+ const candidate = join(cwd, name);
24
+ if (existsSync(candidate))
25
+ return candidate;
26
+ }
27
+ return null;
28
+ }
29
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AAEhE,MAAM,YAAY,GAAG;IACnB,mBAAmB;IACnB,mBAAmB;IACnB,oBAAoB;CACrB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IAEvC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;QAC/D,OAAO,aAAa,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,qBAAqB,UAAU,EAAE,CAAC,CAAC;IAE/C,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;IAC/B,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,cAAc,CAAC,GAAW;IACjC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAClC,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { SignalQueueAdapter } from "station-signal";
2
+ import type { BroadcastQueueAdapter } from "station-broadcast";
3
+ export interface AuthConfig {
4
+ username: string;
5
+ password: string;
6
+ sessionTtlMs?: number;
7
+ }
8
+ export interface RunnerConfig {
9
+ pollIntervalMs: number;
10
+ maxConcurrent: number;
11
+ maxAttempts: number;
12
+ retryBackoffMs: number;
13
+ }
14
+ export interface BroadcastRunnerConfig {
15
+ pollIntervalMs: number;
16
+ }
17
+ export interface StationConfig {
18
+ port: number;
19
+ host: string;
20
+ adapter?: SignalQueueAdapter;
21
+ broadcastAdapter?: BroadcastQueueAdapter;
22
+ signalsDir?: string;
23
+ broadcastsDir?: string;
24
+ runner: RunnerConfig;
25
+ broadcastRunner: BroadcastRunnerConfig;
26
+ runRunners: boolean;
27
+ open: boolean;
28
+ logLevel: "debug" | "info" | "warn" | "error";
29
+ auth?: AuthConfig;
30
+ }
31
+ export type StationUserConfig = Partial<Omit<StationConfig, "runner" | "broadcastRunner">> & {
32
+ runner?: Partial<RunnerConfig>;
33
+ broadcastRunner?: Partial<BroadcastRunnerConfig>;
34
+ };
35
+ export declare function resolveConfig(input: StationUserConfig): StationConfig;
36
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/D,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,eAAe,EAAE,qBAAqB,CAAC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC9C,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,iBAAiB,CAAC,CAAC,GAAG;IAC3F,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAClD,CAAC;AAmBF,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,aAAa,CAsBrE"}
@@ -0,0 +1,40 @@
1
+ const DEFAULTS = {
2
+ port: 4400,
3
+ host: "localhost",
4
+ runner: {
5
+ pollIntervalMs: 1000,
6
+ maxConcurrent: 5,
7
+ maxAttempts: 1,
8
+ retryBackoffMs: 1000,
9
+ },
10
+ broadcastRunner: {
11
+ pollIntervalMs: 1000,
12
+ },
13
+ runRunners: true,
14
+ open: true,
15
+ logLevel: "info",
16
+ };
17
+ export function resolveConfig(input) {
18
+ return {
19
+ port: input.port ?? DEFAULTS.port,
20
+ host: input.host ?? DEFAULTS.host,
21
+ adapter: input.adapter,
22
+ broadcastAdapter: input.broadcastAdapter,
23
+ signalsDir: input.signalsDir,
24
+ broadcastsDir: input.broadcastsDir,
25
+ runner: {
26
+ pollIntervalMs: input.runner?.pollIntervalMs ?? DEFAULTS.runner.pollIntervalMs,
27
+ maxConcurrent: input.runner?.maxConcurrent ?? DEFAULTS.runner.maxConcurrent,
28
+ maxAttempts: input.runner?.maxAttempts ?? DEFAULTS.runner.maxAttempts,
29
+ retryBackoffMs: input.runner?.retryBackoffMs ?? DEFAULTS.runner.retryBackoffMs,
30
+ },
31
+ broadcastRunner: {
32
+ pollIntervalMs: input.broadcastRunner?.pollIntervalMs ?? DEFAULTS.broadcastRunner.pollIntervalMs,
33
+ },
34
+ runRunners: input.runRunners ?? DEFAULTS.runRunners,
35
+ open: input.open ?? DEFAULTS.open,
36
+ logLevel: input.logLevel ?? DEFAULTS.logLevel,
37
+ auth: input.auth,
38
+ };
39
+ }
40
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAwCA,MAAM,QAAQ,GAAkB;IAC9B,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE;QACN,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,CAAC;QAChB,WAAW,EAAE,CAAC;QACd,cAAc,EAAE,IAAI;KACrB;IACD,eAAe,EAAE;QACf,cAAc,EAAE,IAAI;KACrB;IACD,UAAU,EAAE,IAAI;IAChB,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,MAAM;CACjB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,KAAwB;IACpD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI;QACjC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI;QACjC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,MAAM,EAAE;YACN,cAAc,EAAE,KAAK,CAAC,MAAM,EAAE,cAAc,IAAI,QAAQ,CAAC,MAAM,CAAC,cAAc;YAC9E,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,IAAI,QAAQ,CAAC,MAAM,CAAC,aAAa;YAC3E,WAAW,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,QAAQ,CAAC,MAAM,CAAC,WAAW;YACrE,cAAc,EAAE,KAAK,CAAC,MAAM,EAAE,cAAc,IAAI,QAAQ,CAAC,MAAM,CAAC,cAAc;SAC/E;QACD,eAAe,EAAE;YACf,cAAc,EAAE,KAAK,CAAC,eAAe,EAAE,cAAc,IAAI,QAAQ,CAAC,eAAe,CAAC,cAAc;SACjG;QACD,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,QAAQ,CAAC,UAAU;QACnD,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI;QACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ;QAC7C,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { StationUserConfig } from "./config/schema.js";
2
+ export declare function defineConfig(config: StationUserConfig): StationUserConfig;
3
+ export type { StationConfig, StationUserConfig, AuthConfig } from "./config/schema.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,CAEzE;AAED,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export function defineConfig(config) {
2
+ return config;
3
+ }
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,MAAyB;IACpD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,28 @@
1
+ export interface ApiKey {
2
+ id: string;
3
+ name: string;
4
+ keyHash: string;
5
+ keyPrefix: string;
6
+ scopes: string[];
7
+ createdAt: string;
8
+ lastUsed: string | null;
9
+ expiresAt: string | null;
10
+ revoked: boolean;
11
+ }
12
+ export declare class KeyStore {
13
+ private db;
14
+ constructor(dbPath: string);
15
+ /** Generate a new API key. Returns the full key (only shown once) and the stored record. */
16
+ create(name: string, scopes?: string[]): {
17
+ key: string;
18
+ record: ApiKey;
19
+ };
20
+ /** Verify an API key. Returns the key record if valid, null otherwise. */
21
+ verify(rawKey: string): ApiKey | null;
22
+ /** List all keys (without hashes). */
23
+ list(): Omit<ApiKey, "keyHash">[];
24
+ /** Revoke a key by ID. */
25
+ revoke(id: string): boolean;
26
+ close(): void;
27
+ }
28
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/server/auth/keys.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,EAAE,MAAM;IAkB1B,4FAA4F;IAC5F,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,EAAwB,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAkB7F,0EAA0E;IAC1E,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA2BrC,sCAAsC;IACtC,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE;IAkBjC,0BAA0B;IAC1B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAK3B,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,91 @@
1
+ import crypto from "node:crypto";
2
+ import Database from "better-sqlite3";
3
+ export class KeyStore {
4
+ db;
5
+ constructor(dbPath) {
6
+ this.db = new Database(dbPath);
7
+ this.db.pragma("journal_mode = WAL");
8
+ this.db.exec(`
9
+ CREATE TABLE IF NOT EXISTS api_keys (
10
+ id TEXT PRIMARY KEY,
11
+ name TEXT NOT NULL,
12
+ key_hash TEXT NOT NULL UNIQUE,
13
+ key_prefix TEXT NOT NULL,
14
+ scopes TEXT NOT NULL DEFAULT '[]',
15
+ created_at TEXT NOT NULL,
16
+ last_used TEXT,
17
+ expires_at TEXT,
18
+ revoked INTEGER NOT NULL DEFAULT 0
19
+ )
20
+ `);
21
+ }
22
+ /** Generate a new API key. Returns the full key (only shown once) and the stored record. */
23
+ create(name, scopes = ["trigger", "read"]) {
24
+ const id = crypto.randomUUID();
25
+ const rawKey = `sk_live_${crypto.randomBytes(16).toString("hex")}`;
26
+ const keyHash = crypto.createHash("sha256").update(rawKey).digest("hex");
27
+ const keyPrefix = rawKey.slice(0, 12);
28
+ const createdAt = new Date().toISOString();
29
+ this.db.prepare(`
30
+ INSERT INTO api_keys (id, name, key_hash, key_prefix, scopes, created_at)
31
+ VALUES (?, ?, ?, ?, ?, ?)
32
+ `).run(id, name, keyHash, keyPrefix, JSON.stringify(scopes), createdAt);
33
+ return {
34
+ key: rawKey,
35
+ record: { id, name, keyHash, keyPrefix, scopes, createdAt, lastUsed: null, expiresAt: null, revoked: false },
36
+ };
37
+ }
38
+ /** Verify an API key. Returns the key record if valid, null otherwise. */
39
+ verify(rawKey) {
40
+ const keyHash = crypto.createHash("sha256").update(rawKey).digest("hex");
41
+ const row = this.db.prepare(`
42
+ SELECT id, name, key_hash, key_prefix, scopes, created_at, last_used, expires_at, revoked
43
+ FROM api_keys WHERE key_hash = ?
44
+ `).get(keyHash);
45
+ if (!row)
46
+ return null;
47
+ if (row.revoked)
48
+ return null;
49
+ if (row.expires_at && new Date(row.expires_at) < new Date())
50
+ return null;
51
+ // Update last_used
52
+ this.db.prepare("UPDATE api_keys SET last_used = ? WHERE id = ?").run(new Date().toISOString(), row.id);
53
+ return {
54
+ id: row.id,
55
+ name: row.name,
56
+ keyHash: row.key_hash,
57
+ keyPrefix: row.key_prefix,
58
+ scopes: JSON.parse(row.scopes),
59
+ createdAt: row.created_at,
60
+ lastUsed: row.last_used,
61
+ expiresAt: row.expires_at,
62
+ revoked: Boolean(row.revoked),
63
+ };
64
+ }
65
+ /** List all keys (without hashes). */
66
+ list() {
67
+ const rows = this.db.prepare(`
68
+ SELECT id, name, key_prefix, scopes, created_at, last_used, expires_at, revoked
69
+ FROM api_keys ORDER BY created_at DESC
70
+ `).all();
71
+ return rows.map((row) => ({
72
+ id: row.id,
73
+ name: row.name,
74
+ keyPrefix: row.key_prefix,
75
+ scopes: JSON.parse(row.scopes),
76
+ createdAt: row.created_at,
77
+ lastUsed: row.last_used,
78
+ expiresAt: row.expires_at,
79
+ revoked: Boolean(row.revoked),
80
+ }));
81
+ }
82
+ /** Revoke a key by ID. */
83
+ revoke(id) {
84
+ const result = this.db.prepare("UPDATE api_keys SET revoked = 1 WHERE id = ?").run(id);
85
+ return result.changes > 0;
86
+ }
87
+ close() {
88
+ this.db.close();
89
+ }
90
+ }
91
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/server/auth/keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface SessionConfig {
2
+ username: string;
3
+ password: string;
4
+ sessionTtlMs?: number;
5
+ }
6
+ export declare function createSessionToken(config: SessionConfig): string;
7
+ export declare function verifySessionToken(token: string, config: SessionConfig): boolean;
8
+ export declare function verifyCredentials(username: string, password: string, config: SessionConfig): boolean;
9
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/server/auth/session.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AASD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAKhE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAchF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAOpG"}
@@ -0,0 +1,42 @@
1
+ import crypto from "node:crypto";
2
+ const SESSION_TTL_MS = 86_400_000; // 24 hours
3
+ /** HMAC-sign a token. The secret is derived from the password. */
4
+ function sign(payload, secret) {
5
+ const hmac = crypto.createHmac("sha256", secret);
6
+ hmac.update(payload);
7
+ return hmac.digest("hex");
8
+ }
9
+ export function createSessionToken(config) {
10
+ const exp = Date.now() + (config.sessionTtlMs ?? SESSION_TTL_MS);
11
+ const payload = `${config.username}:${exp}`;
12
+ const signature = sign(payload, config.password);
13
+ return Buffer.from(`${payload}:${signature}`).toString("base64url");
14
+ }
15
+ export function verifySessionToken(token, config) {
16
+ try {
17
+ const decoded = Buffer.from(token, "base64url").toString();
18
+ const parts = decoded.split(":");
19
+ if (parts.length !== 3)
20
+ return false;
21
+ const [username, expStr, sig] = parts;
22
+ const exp = parseInt(expStr, 10);
23
+ if (isNaN(exp) || Date.now() > exp)
24
+ return false;
25
+ if (username !== config.username)
26
+ return false;
27
+ const expected = sign(`${username}:${expStr}`, config.password);
28
+ return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ export function verifyCredentials(username, password, config) {
35
+ // Use timing-safe comparison to prevent timing attacks
36
+ const userMatch = username.length === config.username.length &&
37
+ crypto.timingSafeEqual(Buffer.from(username), Buffer.from(config.username));
38
+ const passMatch = password.length === config.password.length &&
39
+ crypto.timingSafeEqual(Buffer.from(password), Buffer.from(config.password));
40
+ return userMatch && passMatch;
41
+ }
42
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/server/auth/session.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,WAAW;AAQ9C,kEAAkE;AAClE,SAAS,IAAI,CAAC,OAAe,EAAE,MAAc;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrB,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAqB;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,QAAQ,IAAI,GAAG,EAAE,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,MAAqB;IACrE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;QACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG;YAAE,OAAO,KAAK,CAAC;QACjD,IAAI,QAAQ,KAAK,MAAM,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,QAAQ,IAAI,MAAM,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChE,OAAO,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,QAAgB,EAAE,MAAqB;IACzF,uDAAuD;IACvD,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM;QAC1D,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM;QAC1D,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9E,OAAO,SAAS,IAAI,SAAS,CAAC;AAChC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { StationConfig } from "../config/schema.js";
2
+ export interface StationInstance {
3
+ start(): Promise<void>;
4
+ stop(): Promise<void>;
5
+ }
6
+ export declare function createStation(config: StationConfig, cwd: string): Promise<StationInstance>;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAwBzD,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA+PhG"}