skein-js 0.9.0 → 0.10.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.
- package/README.md +42 -15
- package/dist/index.js +129 -41
- package/package.json +13 -6
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ one-word change (`langgraph dev` → `skein dev`).
|
|
|
17
17
|
- [Commands](#commands)
|
|
18
18
|
- [`skein dev` flags](#skein-dev-flags)
|
|
19
19
|
- [Self-hosted, no lock-in](#self-hosted-no-lock-in)
|
|
20
|
+
- [Deploy anywhere](#deploy-anywhere)
|
|
20
21
|
- [When the managed platform may fit you better](#when-the-managed-platform-may-fit-you-better)
|
|
21
22
|
- [Learn more](#learn-more)
|
|
22
23
|
- [License](#license)
|
|
@@ -52,12 +53,13 @@ LangGraph Studio — any Agent Protocol client works with only a URL change. See
|
|
|
52
53
|
|
|
53
54
|
## Commands
|
|
54
55
|
|
|
55
|
-
| Command | What it does
|
|
56
|
-
| ------------------ |
|
|
57
|
-
| `skein dev` | In-process dev server, hot reload, `.skein/` state, no Docker.
|
|
58
|
-
| `skein up` | Self-hosted stack via Docker Compose (app + Postgres + Redis).
|
|
59
|
-
| `skein build` | Build a deployable Docker image from the config.
|
|
60
|
-
| `skein dockerfile` | Emit a standalone Dockerfile (stdout by default).
|
|
56
|
+
| Command | What it does | LangGraph CLI equivalent | Key flags |
|
|
57
|
+
| ------------------ | ------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------ |
|
|
58
|
+
| `skein dev` | In-process dev server, hot reload, `.skein/` state, no Docker. | `langgraph dev` | see [`skein dev` flags](#skein-dev-flags) |
|
|
59
|
+
| `skein up` | Self-hosted stack via Docker Compose (app + Postgres + Redis). | `langgraph up` | `-p, --port` (8123) · `--host` (0.0.0.0) · `-n, --npmrc <path>` |
|
|
60
|
+
| `skein build` | Build a deployable Docker image from the config. | `langgraph build` | `-t, --tag` (defaults to the project dir name) · `-n, --npmrc <path>` |
|
|
61
|
+
| `skein dockerfile` | Emit a standalone Dockerfile (stdout by default). | `langgraph dockerfile` | `-o, --output <path>` |
|
|
62
|
+
| `skein start` | Serve a pre-built `.skein/build` artifact (the image's entrypoint). | — | `-p, --port` (8123) · `--host` · `--store` · `--queue` · `--concurrency` |
|
|
61
63
|
|
|
62
64
|
All commands take `-c, --config <path>` (default `langgraph.json`).
|
|
63
65
|
|
|
@@ -67,15 +69,22 @@ any layer. Public-registry builds don't need it.
|
|
|
67
69
|
|
|
68
70
|
## `skein dev` flags
|
|
69
71
|
|
|
70
|
-
| Flag
|
|
71
|
-
|
|
|
72
|
-
| `-p, --port`
|
|
73
|
-
| `--host`
|
|
74
|
-
| `--store <driver>`
|
|
75
|
-
| `--queue <driver>`
|
|
76
|
-
| `--
|
|
77
|
-
|
|
|
78
|
-
|
|
|
72
|
+
| Flag | Values | Default | Notes |
|
|
73
|
+
| ------------------------- | -------------------- | ----------- | -------------------------------------------------------------- |
|
|
74
|
+
| `-p, --port` | number | `2024` | Port to listen on. |
|
|
75
|
+
| `--host` | host | `127.0.0.1` | Interface to bind. |
|
|
76
|
+
| `--store <driver>` | `memory`, `postgres` | `memory` | `postgres` reads `POSTGRES_URI`; also selects `PostgresSaver`. |
|
|
77
|
+
| `--queue <driver>` | `memory`, `redis` | `memory` | `redis` reads `REDIS_URI` (BullMQ queue + Redis Streams bus). |
|
|
78
|
+
| `--concurrency` | number | `10` | Queued runs the background worker executes at once. |
|
|
79
|
+
| `-n, --n-jobs-per-worker` | number | `10` | LangGraph-compatible alias for `--concurrency`. |
|
|
80
|
+
| `--no-persist` | — | persists | Don't snapshot dev state to `.skein/` across restarts. |
|
|
81
|
+
| `--no-reload` | — | reloads | Disable hot reload on source change. |
|
|
82
|
+
| `-v, --verbose` | — | off | Log per-run activity (tool calls, interrupts, timing). |
|
|
83
|
+
|
|
84
|
+
`--store`, `--queue`, `--concurrency`, and `-n, --n-jobs-per-worker` are accepted by `skein start`
|
|
85
|
+
too. Concurrency also reads `SKEIN_RUN_CONCURRENCY` (or `N_JOBS_PER_WORKER`) when no flag is passed —
|
|
86
|
+
the path that reaches a container. See
|
|
87
|
+
[runs-and-redis.md](../../docs/runs-and-redis.md#run-concurrency).
|
|
79
88
|
|
|
80
89
|
`skein dev --store postgres --queue redis` is a capability the LangGraph CLI does **not** offer: it
|
|
81
90
|
lets you develop against **production-shaped** storage (durable Postgres checkpoints, pgvector
|
|
@@ -89,6 +98,23 @@ run your LangGraph.js graphs behind the standard Agent Protocol on **your own in
|
|
|
89
98
|
license key, no per-deployment fee, and no vendor lock-in. `skein up` brings up a Docker Compose
|
|
90
99
|
stack (app + your Postgres + your Redis) that you own end to end.
|
|
91
100
|
|
|
101
|
+
## Deploy anywhere
|
|
102
|
+
|
|
103
|
+
`skein build` produces an ordinary Docker image, so your LangGraph.js graphs run **anywhere you can
|
|
104
|
+
run a container** — Google Cloud Run, Railway, Fly.io, Render, AWS App Runner or ECS Fargate,
|
|
105
|
+
Kubernetes, or your own VPS.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
skein build -t my-agent # → a deployable Docker image
|
|
109
|
+
docker run -p 8123:8123 \
|
|
110
|
+
-e POSTGRES_URI="postgresql://…" \
|
|
111
|
+
-e REDIS_URI="redis://…" my-agent
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The image binds `$PORT` when a platform injects one (8123 when nothing does), runs as a non-root
|
|
115
|
+
user, serves a `/ok` health probe, and drains in-flight runs on `SIGTERM`. Step-by-step guides per
|
|
116
|
+
platform, and the knobs that are the same everywhere: [deploy anywhere](../../docs/deploy.md).
|
|
117
|
+
|
|
92
118
|
## When the managed platform may fit you better
|
|
93
119
|
|
|
94
120
|
skein-js gives you the code and full ownership, not a support contract. If you're an established
|
|
@@ -100,6 +126,7 @@ on top of the open LangGraph runtime.
|
|
|
100
126
|
|
|
101
127
|
## Learn more
|
|
102
128
|
|
|
129
|
+
- [Deploy anywhere](../../docs/deploy.md) — Cloud Run, Railway, Fly.io, Render, AWS, Kubernetes, VPS
|
|
103
130
|
- [LangGraph CLI compatibility](../../docs/langgraph-cli-compat.md) — commands + `langgraph.json` fields
|
|
104
131
|
- [skein-js overview](../../docs/index.md) · [Reuse-first architecture](../../docs/reuse.md) · [Roadmap](../../docs/roadmap.md)
|
|
105
132
|
- [skein-js root README](../../README.md)
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
readLanggraphDevState
|
|
15
15
|
} from "@skein-js/express";
|
|
16
16
|
import { buildRuntime } from "@skein-js/runtime";
|
|
17
|
+
import { resolveRunConcurrency, resolveShutdownGraceMs } from "@skein-js/server-kit";
|
|
17
18
|
|
|
18
19
|
// src/colors.ts
|
|
19
20
|
var colorEnabled = process.stdout.isTTY === true && process.env.NO_COLOR === void 0;
|
|
@@ -27,7 +28,7 @@ var dim = paint(2);
|
|
|
27
28
|
|
|
28
29
|
// src/banner.ts
|
|
29
30
|
function printBanner(info, logger2) {
|
|
30
|
-
const { host, port, graphIds, authPath,
|
|
31
|
+
const { host, port, graphIds, authPath, runConcurrency } = info;
|
|
31
32
|
const base = `http://${host}:${port}`;
|
|
32
33
|
console.log();
|
|
33
34
|
console.log(`${bold(green("skein"))} ${dim("\xB7 Agent Protocol dev server")}`);
|
|
@@ -37,7 +38,9 @@ function printBanner(info, logger2) {
|
|
|
37
38
|
console.log();
|
|
38
39
|
for (const id of graphIds) logger2.info(`Registering graph with id '${id}'`);
|
|
39
40
|
if (authPath) logger2.info(`Loading auth from ${authPath}`);
|
|
40
|
-
logger2.info(
|
|
41
|
+
logger2.info(
|
|
42
|
+
runConcurrency === 1 ? "Starting 1 worker" : `Starting 1 worker, up to ${runConcurrency} concurrent runs`
|
|
43
|
+
);
|
|
41
44
|
logger2.info(`Server running at ${base}`);
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -136,6 +139,8 @@ async function applyProjectEnv(config, configDir) {
|
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
// src/serve-env.ts
|
|
142
|
+
var DEFAULT_DEV_PORT = 2024;
|
|
143
|
+
var DEFAULT_CONTAINER_PORT = 8123;
|
|
139
144
|
function envPort(fallback) {
|
|
140
145
|
const raw = process.env.PORT;
|
|
141
146
|
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
@@ -154,6 +159,32 @@ function describeBindError(error, port) {
|
|
|
154
159
|
return `failed to start server: ${error instanceof Error ? error.message : String(error)}`;
|
|
155
160
|
}
|
|
156
161
|
|
|
162
|
+
// src/shutdown.ts
|
|
163
|
+
import { DEFAULT_SHUTDOWN_GRACE_MS } from "@skein-js/server-kit";
|
|
164
|
+
var FORCE_EXIT_BUFFER_MS = 3e3;
|
|
165
|
+
function forceExitDelayMs(graceMs = DEFAULT_SHUTDOWN_GRACE_MS) {
|
|
166
|
+
return graceMs + FORCE_EXIT_BUFFER_MS;
|
|
167
|
+
}
|
|
168
|
+
var describe = (error) => error instanceof Error ? error.message : String(error);
|
|
169
|
+
function createShutdownHandler(options) {
|
|
170
|
+
const { forceExitMs, close, onShutdownStart, exit = (code) => process.exit(code) } = options;
|
|
171
|
+
let shuttingDown = false;
|
|
172
|
+
return () => {
|
|
173
|
+
if (shuttingDown) return;
|
|
174
|
+
shuttingDown = true;
|
|
175
|
+
try {
|
|
176
|
+
onShutdownStart?.();
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(`skein: pre-shutdown step failed: ${describe(error)}`);
|
|
179
|
+
}
|
|
180
|
+
const forceExit = setTimeout(() => exit(0), forceExitMs);
|
|
181
|
+
forceExit.unref();
|
|
182
|
+
void close().catch((error) => {
|
|
183
|
+
console.error(`skein: shutdown did not complete cleanly: ${describe(error)}`);
|
|
184
|
+
}).finally(() => exit(0));
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
157
188
|
// src/vite-graph-loader.ts
|
|
158
189
|
import { createServer as createNetServer } from "net";
|
|
159
190
|
function findFreePort() {
|
|
@@ -208,12 +239,12 @@ async function createViteGraphLoader(root, ignored = []) {
|
|
|
208
239
|
// src/dev-command.ts
|
|
209
240
|
var RELOAD_DEBOUNCE_MS = 120;
|
|
210
241
|
var AUTOSAVE_MS = 2e3;
|
|
211
|
-
var FORCE_EXIT_MS = 5e3;
|
|
212
242
|
var devLogger = createDevLogger();
|
|
213
243
|
async function runDev(options) {
|
|
214
244
|
const configPath = path3.resolve(process.cwd(), options.config);
|
|
215
245
|
const { config, configDir } = await loadConfig({ configPath });
|
|
216
246
|
await applyProjectEnv(config, configDir);
|
|
247
|
+
const shutdownGraceMs = resolveShutdownGraceMs();
|
|
217
248
|
const stateDir = path3.join(configDir, STATE_DIR);
|
|
218
249
|
const stateFile = devStateFile(configDir);
|
|
219
250
|
const loader = await createViteGraphLoader(configDir, [`${STATE_DIR}/**`, `**/${STATE_DIR}/**`]);
|
|
@@ -225,6 +256,7 @@ async function runDev(options) {
|
|
|
225
256
|
});
|
|
226
257
|
const port = options.portExplicit ? options.port : envPort(options.port);
|
|
227
258
|
const host = options.hostExplicit ? options.host : envHost(options.host);
|
|
259
|
+
const runConcurrency = resolveRunConcurrency(options.concurrency ?? options.nJobsPerWorker);
|
|
228
260
|
const canPersist = options.persist && runtime.snapshotState !== void 0;
|
|
229
261
|
if (options.persist && runtime.snapshotState === void 0) {
|
|
230
262
|
console.log(
|
|
@@ -263,7 +295,8 @@ async function runDev(options) {
|
|
|
263
295
|
deps: runtime.deps,
|
|
264
296
|
cors: runtime.cors,
|
|
265
297
|
warm: true,
|
|
266
|
-
logger: devLogger
|
|
298
|
+
logger: devLogger,
|
|
299
|
+
worker: { maxConcurrency: runConcurrency, shutdownGraceMs }
|
|
267
300
|
});
|
|
268
301
|
await server.listen(port, host);
|
|
269
302
|
} catch (error) {
|
|
@@ -278,7 +311,7 @@ async function runDev(options) {
|
|
|
278
311
|
port,
|
|
279
312
|
graphIds: runtime.deps.graphs.ids,
|
|
280
313
|
authPath: config.auth?.path,
|
|
281
|
-
|
|
314
|
+
runConcurrency
|
|
282
315
|
},
|
|
283
316
|
devLogger
|
|
284
317
|
);
|
|
@@ -341,18 +374,25 @@ async function runDev(options) {
|
|
|
341
374
|
pending = setTimeout(() => void reload(), RELOAD_DEBOUNCE_MS);
|
|
342
375
|
});
|
|
343
376
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
377
|
+
const shutdown = createShutdownHandler({
|
|
378
|
+
forceExitMs: forceExitDelayMs(shutdownGraceMs),
|
|
379
|
+
// Stop autosaving and flush one last snapshot synchronously, before anything can race it.
|
|
380
|
+
onShutdownStart: () => {
|
|
381
|
+
if (autosave) clearInterval(autosave);
|
|
382
|
+
saveState();
|
|
383
|
+
},
|
|
384
|
+
// Strictly ordered, never concurrent. `server.close()` stops the worker, which needs the store
|
|
385
|
+
// and queue still alive to settle in-flight runs terminally — and needs the vite loader alive
|
|
386
|
+
// too, since a draining graph may still `await import(...)` a module it hasn't loaded yet.
|
|
387
|
+
// Tearing either down alongside the drain fails the runs it was meant to save.
|
|
388
|
+
close: async () => {
|
|
389
|
+
try {
|
|
390
|
+
await server.close();
|
|
391
|
+
} finally {
|
|
392
|
+
await Promise.allSettled([loader.close(), runtime.dispose()]);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
356
396
|
process.on("SIGINT", shutdown);
|
|
357
397
|
process.on("SIGTERM", shutdown);
|
|
358
398
|
}
|
|
@@ -587,8 +627,9 @@ services:
|
|
|
587
627
|
ports:
|
|
588
628
|
- ${ports}
|
|
589
629
|
environment:
|
|
590
|
-
# The container CMD passes no --port; it binds $PORT (as a hosting platform would inject)
|
|
591
|
-
#
|
|
630
|
+
# The container CMD passes no --port; it binds $PORT (as a hosting platform would inject), and
|
|
631
|
+
# falls back to this same port anyway. Set explicitly so the mapping above stays correct even
|
|
632
|
+
# if a host port is published that differs from the port the container listens on.
|
|
592
633
|
PORT: "${options.containerPort}"
|
|
593
634
|
POSTGRES_URI: postgresql://postgres:postgres@postgres:5432/skein
|
|
594
635
|
REDIS_URI: redis://redis:6379
|
|
@@ -598,6 +639,11 @@ services:
|
|
|
598
639
|
redis:
|
|
599
640
|
condition: service_healthy
|
|
600
641
|
restart: unless-stopped
|
|
642
|
+
# Longer than Docker's 10s default so the app can finish its shutdown: it drains in-flight runs
|
|
643
|
+
# for SKEIN_SHUTDOWN_GRACE_MS, then aborts the stragglers so they settle to a terminal status.
|
|
644
|
+
# 10s leaves no headroom the moment that window is raised, and a SIGKILL mid-abort is exactly
|
|
645
|
+
# what leaves a run stuck in a non-terminal state.
|
|
646
|
+
stop_grace_period: 30s
|
|
601
647
|
|
|
602
648
|
postgres:
|
|
603
649
|
image: pgvector/pgvector:pg16
|
|
@@ -683,17 +729,28 @@ function generateDockerfile(options) {
|
|
|
683
729
|
// The bundle ships sourcemaps; enable them so stack traces map back to the original TypeScript.
|
|
684
730
|
`ENV NODE_OPTIONS=--enable-source-maps`,
|
|
685
731
|
`EXPOSE ${port}`,
|
|
686
|
-
// Liveness probe against skein's `/ok` endpoint, targeting the same port the server binds.
|
|
732
|
+
// Liveness probe against skein's `/ok` endpoint, targeting the same port the server binds. PORT
|
|
733
|
+
// is resolved exactly as `envPort` resolves it — integer, in range, blank counts as unset —
|
|
734
|
+
// rather than by truthiness, so a malformed value (`PORT=8080a`, `PORT=0`) has the probe and the
|
|
735
|
+
// server agree on the fallback instead of probing a URL the server never bound.
|
|
687
736
|
`HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \\`,
|
|
688
|
-
` CMD node -e "fetch('http://127.0.0.1:'+
|
|
737
|
+
` CMD node -e "const r=Number(process.env.PORT);const p=Number.isInteger(r)&&r>=0&&r<=65535?r:${port};fetch('http://127.0.0.1:'+p+'/ok').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"`,
|
|
689
738
|
// Run unprivileged. The bundle writes nothing to disk, and /app is world-readable, so no chown.
|
|
690
739
|
`USER node`,
|
|
691
740
|
...dockerfileLines,
|
|
692
741
|
``,
|
|
693
742
|
`# Serve the compiled artifact against Postgres + Redis (POSTGRES_URI / REDIS_URI from the env).`,
|
|
694
|
-
`# No --port: the server binds $PORT when the platform injects one (Railway/Fly/Render)
|
|
695
|
-
`#
|
|
696
|
-
|
|
743
|
+
`# No --port: the server binds $PORT when the platform injects one (Railway/Fly/Render/Cloud Run),`,
|
|
744
|
+
`# else \`skein start\`'s default \u2014 the port EXPOSEd above, so a bare \`docker run -p ${port}:${port}\``,
|
|
745
|
+
`# works too.`,
|
|
746
|
+
`#`,
|
|
747
|
+
`# Invoked as \`node <entry>\` rather than \`npx skein\` so that **node itself is PID 1**. Under npx,`,
|
|
748
|
+
`# PID 1 is npm, which forwards SIGTERM to a \`sh -c\` child and exits immediately \u2014 the server is`,
|
|
749
|
+
`# killed mid-shutdown and in-flight runs are left stranded in a non-terminal status. Exec form`,
|
|
750
|
+
`# (no shell) keeps the signal path direct, so SIGTERM reaches skein's graceful-shutdown handler.`,
|
|
751
|
+
// Absolute, so a `dockerfile_lines` entry that changes WORKDIR (spliced in just above) can't
|
|
752
|
+
// leave the CMD resolving against the wrong directory.
|
|
753
|
+
`CMD ["node", "/app/node_modules/skein-js/dist/index.js", "start", "--store", "postgres", "--queue", "redis", "--host", "0.0.0.0"]`
|
|
697
754
|
];
|
|
698
755
|
return `${lines.join("\n")}
|
|
699
756
|
`;
|
|
@@ -704,7 +761,7 @@ function skeinCliVersion() {
|
|
|
704
761
|
return createRequire2(import.meta.url)("../package.json").version;
|
|
705
762
|
}
|
|
706
763
|
var ARTIFACT_SUBDIR = path5.join(".skein", "build");
|
|
707
|
-
var CONTAINER_PORT =
|
|
764
|
+
var CONTAINER_PORT = DEFAULT_CONTAINER_PORT;
|
|
708
765
|
var DOCKERFILE_NAME = "Dockerfile";
|
|
709
766
|
var COMPOSE_NAME = "compose.yaml";
|
|
710
767
|
var DOCKERIGNORE_NAME = ".dockerignore";
|
|
@@ -925,7 +982,7 @@ import { createExpressServer as createExpressServer2 } from "@skein-js/express";
|
|
|
925
982
|
import {
|
|
926
983
|
buildRuntime as buildRuntime3
|
|
927
984
|
} from "@skein-js/runtime";
|
|
928
|
-
|
|
985
|
+
import { resolveRunConcurrency as resolveRunConcurrency2, resolveShutdownGraceMs as resolveShutdownGraceMs2 } from "@skein-js/server-kit";
|
|
929
986
|
var logger = createDevLogger();
|
|
930
987
|
function readBakedSchemas(configDir) {
|
|
931
988
|
const schemasFile = path7.join(configDir, "schemas.json");
|
|
@@ -938,14 +995,21 @@ function readBakedSchemas(configDir) {
|
|
|
938
995
|
}
|
|
939
996
|
async function runStart(options) {
|
|
940
997
|
const configPath = path7.resolve(process.cwd(), options.config);
|
|
998
|
+
let handleSignal = () => process.exit(0);
|
|
999
|
+
process.on("SIGINT", () => handleSignal());
|
|
1000
|
+
process.on("SIGTERM", () => handleSignal());
|
|
941
1001
|
let schemas;
|
|
942
1002
|
let configDir;
|
|
943
1003
|
let authPath;
|
|
1004
|
+
let runConcurrency;
|
|
1005
|
+
let shutdownGraceMs;
|
|
944
1006
|
try {
|
|
945
1007
|
const loaded = await loadConfig5({ configPath });
|
|
946
1008
|
configDir = loaded.configDir;
|
|
947
1009
|
authPath = loaded.config.auth?.path;
|
|
948
1010
|
await applyProjectEnv(loaded.config, configDir);
|
|
1011
|
+
runConcurrency = resolveRunConcurrency2(options.concurrency ?? options.nJobsPerWorker);
|
|
1012
|
+
shutdownGraceMs = resolveShutdownGraceMs2();
|
|
949
1013
|
schemas = readBakedSchemas(configDir);
|
|
950
1014
|
} catch (error) {
|
|
951
1015
|
console.error(`skein: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -974,7 +1038,8 @@ async function runStart(options) {
|
|
|
974
1038
|
deps: runtime.deps,
|
|
975
1039
|
cors: runtime.cors,
|
|
976
1040
|
warm: true,
|
|
977
|
-
logger
|
|
1041
|
+
logger,
|
|
1042
|
+
worker: { maxConcurrency: runConcurrency, shutdownGraceMs }
|
|
978
1043
|
});
|
|
979
1044
|
await server.listen(port, host);
|
|
980
1045
|
} catch (error) {
|
|
@@ -983,17 +1048,17 @@ async function runStart(options) {
|
|
|
983
1048
|
process.exitCode = 1;
|
|
984
1049
|
return;
|
|
985
1050
|
}
|
|
986
|
-
printBanner({ host, port, graphIds: runtime.deps.graphs.ids, authPath,
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1051
|
+
printBanner({ host, port, graphIds: runtime.deps.graphs.ids, authPath, runConcurrency }, logger);
|
|
1052
|
+
handleSignal = createShutdownHandler({
|
|
1053
|
+
forceExitMs: forceExitDelayMs(shutdownGraceMs),
|
|
1054
|
+
close: async () => {
|
|
1055
|
+
try {
|
|
1056
|
+
await server.close();
|
|
1057
|
+
} finally {
|
|
1058
|
+
await runtime.dispose();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
997
1062
|
}
|
|
998
1063
|
|
|
999
1064
|
// src/index.ts
|
|
@@ -1006,6 +1071,13 @@ function parsePort(value) {
|
|
|
1006
1071
|
}
|
|
1007
1072
|
return port;
|
|
1008
1073
|
}
|
|
1074
|
+
function parseConcurrency(value) {
|
|
1075
|
+
const concurrency = Number(value);
|
|
1076
|
+
if (!Number.isInteger(concurrency) || concurrency <= 0) {
|
|
1077
|
+
throw new InvalidArgumentError("Concurrency must be a positive integer.");
|
|
1078
|
+
}
|
|
1079
|
+
return concurrency;
|
|
1080
|
+
}
|
|
1009
1081
|
function parseChoice(choices) {
|
|
1010
1082
|
return (value) => {
|
|
1011
1083
|
if (!choices.includes(value)) {
|
|
@@ -1019,21 +1091,37 @@ var parseQueue = parseChoice(["memory", "redis"]);
|
|
|
1019
1091
|
var program = new Command().name("skein").description(
|
|
1020
1092
|
"Agent Protocol server for LangGraph.js \u2014 a drop-in replacement for the LangGraph CLI."
|
|
1021
1093
|
).version(version, "-v, --version");
|
|
1022
|
-
program.command("dev").description("Run the in-process dev server with hot reload (no Docker).").option("-c, --config <path>", "Path to langgraph.json", "langgraph.json").option("-p, --port <port>", "Port to bind", parsePort,
|
|
1094
|
+
program.command("dev").description("Run the in-process dev server with hot reload (no Docker).").option("-c, --config <path>", "Path to langgraph.json", "langgraph.json").option("-p, --port <port>", "Port to bind", parsePort, DEFAULT_DEV_PORT).option("--host <host>", "Host to bind", "127.0.0.1").option("--no-reload", "Disable hot reload").option("--no-persist", "Don't persist dev state to .skein/ between restarts").option("--store <driver>", "Store driver: memory | postgres", parseStore, "memory").option("--queue <driver>", "Queue driver: memory | redis", parseQueue, "memory").option(
|
|
1095
|
+
"--concurrency <count>",
|
|
1096
|
+
"Queued runs the background worker executes at once (default 10; env SKEIN_RUN_CONCURRENCY)",
|
|
1097
|
+
parseConcurrency
|
|
1098
|
+
).option(
|
|
1099
|
+
"-n, --n-jobs-per-worker <count>",
|
|
1100
|
+
"Alias for --concurrency (LangGraph CLI compatibility)",
|
|
1101
|
+
parseConcurrency
|
|
1102
|
+
).option("-v, --verbose", "Log per-run activity: start/finish, tool calls, and interrupts").action(
|
|
1023
1103
|
(options, command) => runDev({
|
|
1024
1104
|
...options,
|
|
1025
1105
|
portExplicit: command.getOptionValueSource("port") === "cli",
|
|
1026
1106
|
hostExplicit: command.getOptionValueSource("host") === "cli"
|
|
1027
1107
|
})
|
|
1028
1108
|
);
|
|
1029
|
-
program.command("start").description("Serve a pre-built .skein/build artifact (the production image entrypoint).").option("-c, --config <path>", "Path to the artifact's langgraph.json", "langgraph.json").option("-p, --port <port>", "Port to bind", parsePort,
|
|
1109
|
+
program.command("start").description("Serve a pre-built .skein/build artifact (the production image entrypoint).").option("-c, --config <path>", "Path to the artifact's langgraph.json", "langgraph.json").option("-p, --port <port>", "Port to bind", parsePort, DEFAULT_CONTAINER_PORT).option("--host <host>", "Host to bind", "127.0.0.1").option("--store <driver>", "Store driver: memory | postgres", parseStore, "memory").option("--queue <driver>", "Queue driver: memory | redis", parseQueue, "memory").option(
|
|
1110
|
+
"--concurrency <count>",
|
|
1111
|
+
"Queued runs the background worker executes at once (default 10; env SKEIN_RUN_CONCURRENCY)",
|
|
1112
|
+
parseConcurrency
|
|
1113
|
+
).option(
|
|
1114
|
+
"-n, --n-jobs-per-worker <count>",
|
|
1115
|
+
"Alias for --concurrency (LangGraph CLI compatibility)",
|
|
1116
|
+
parseConcurrency
|
|
1117
|
+
).option("-v, --verbose", "Log per-run activity: start/finish, tool calls, and interrupts").action(
|
|
1030
1118
|
(options, command) => runStart({
|
|
1031
1119
|
...options,
|
|
1032
1120
|
portExplicit: command.getOptionValueSource("port") === "cli",
|
|
1033
1121
|
hostExplicit: command.getOptionValueSource("host") === "cli"
|
|
1034
1122
|
})
|
|
1035
1123
|
);
|
|
1036
|
-
program.command("up").description("Bring up the production stack (Docker Compose: app + Postgres + Redis).").option("-c, --config <path>", "Path to langgraph.json", "langgraph.json").option("-p, --port <port>", "Port to expose", parsePort,
|
|
1124
|
+
program.command("up").description("Bring up the production stack (Docker Compose: app + Postgres + Redis).").option("-c, --config <path>", "Path to langgraph.json", "langgraph.json").option("-p, --port <port>", "Port to expose", parsePort, DEFAULT_CONTAINER_PORT).option("--host <host>", "Host to bind", "0.0.0.0").option(
|
|
1037
1125
|
"-n, --npmrc <path>",
|
|
1038
1126
|
"Path to an .npmrc for authenticating private-registry installs (wired in as a build secret)"
|
|
1039
1127
|
).action((options) => runUp(options));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skein-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "The skein-js CLI — a drop-in replacement for the LangGraph CLI (dev/up/build/dockerfile).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Maina Wycliffe <wmmaina7@gmail.com>",
|
|
@@ -16,10 +16,16 @@
|
|
|
16
16
|
"keywords": [
|
|
17
17
|
"typescript",
|
|
18
18
|
"langgraph",
|
|
19
|
+
"langgraph-js",
|
|
19
20
|
"cli",
|
|
20
21
|
"agent-protocol",
|
|
21
22
|
"langgraph-cli",
|
|
22
|
-
"self-hosted"
|
|
23
|
+
"self-hosted",
|
|
24
|
+
"ai-agents",
|
|
25
|
+
"deploy",
|
|
26
|
+
"docker",
|
|
27
|
+
"cloud-run",
|
|
28
|
+
"kubernetes"
|
|
23
29
|
],
|
|
24
30
|
"type": "module",
|
|
25
31
|
"main": "./dist/index.js",
|
|
@@ -47,10 +53,11 @@
|
|
|
47
53
|
"@commander-js/extra-typings": "~13.1.0",
|
|
48
54
|
"@langchain/langgraph-checkpoint-postgres": "^1.0.4",
|
|
49
55
|
"commander": "~13.1.0",
|
|
50
|
-
"@skein-js/core": "0.
|
|
51
|
-
"@skein-js/config": "0.
|
|
52
|
-
"@skein-js/express": "0.
|
|
53
|
-
"@skein-js/
|
|
56
|
+
"@skein-js/core": "0.10.0",
|
|
57
|
+
"@skein-js/config": "0.10.0",
|
|
58
|
+
"@skein-js/express": "0.10.0",
|
|
59
|
+
"@skein-js/server-kit": "0.10.0",
|
|
60
|
+
"@skein-js/runtime": "0.10.0"
|
|
54
61
|
},
|
|
55
62
|
"optionalDependencies": {
|
|
56
63
|
"vite": "^8.1.0"
|