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.
Files changed (3) hide show
  1. package/README.md +42 -15
  2. package/dist/index.js +129 -41
  3. 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 | LangGraph CLI equivalent | Key flags |
56
- | ------------------ | -------------------------------------------------------------- | ------------------------ | --------------------------------------------------------------------- |
57
- | `skein dev` | In-process dev server, hot reload, `.skein/` state, no Docker. | `langgraph dev` | see [`skein dev` flags](#skein-dev-flags) |
58
- | `skein up` | Self-hosted stack via Docker Compose (app + Postgres + Redis). | `langgraph up` | `-p, --port` (8123) · `--host` (0.0.0.0) · `-n, --npmrc <path>` |
59
- | `skein build` | Build a deployable Docker image from the config. | `langgraph build` | `-t, --tag` (defaults to the project dir name) · `-n, --npmrc <path>` |
60
- | `skein dockerfile` | Emit a standalone Dockerfile (stdout by default). | `langgraph dockerfile` | `-o, --output <path>` |
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 | Values | Default | Notes |
71
- | ------------------ | -------------------- | ----------- | -------------------------------------------------------------- |
72
- | `-p, --port` | number | `2024` | Port to listen on. |
73
- | `--host` | host | `127.0.0.1` | Interface to bind. |
74
- | `--store <driver>` | `memory`, `postgres` | `memory` | `postgres` reads `POSTGRES_URI`; also selects `PostgresSaver`. |
75
- | `--queue <driver>` | `memory`, `redis` | `memory` | `redis` reads `REDIS_URI` (BullMQ queue + Redis Streams bus). |
76
- | `--no-persist` | | persists | Don't snapshot dev state to `.skein/` across restarts. |
77
- | `--no-reload` | | reloads | Disable hot reload on source change. |
78
- | `-v, --verbose` | — | off | Log per-run activity (tool calls, interrupts, timing). |
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, workerCount } = info;
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(`Starting ${workerCount} worker${workerCount === 1 ? "" : "s"}`);
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
- workerCount: 1
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
- let shuttingDown = false;
345
- const shutdown = () => {
346
- if (shuttingDown) return;
347
- shuttingDown = true;
348
- if (autosave) clearInterval(autosave);
349
- const forceExit = setTimeout(() => process.exit(0), FORCE_EXIT_MS);
350
- forceExit.unref();
351
- saveState();
352
- void Promise.allSettled([server.close(), loader.close(), runtime.dispose()]).then(
353
- () => process.exit(0)
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
- # Set it here so the app listens on the port the mapping above publishes.
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:'+(process.env.PORT||${port})+'/ok').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(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), else its`,
695
- `# default. Exec form keeps node as PID 1 so SIGTERM reaches skein's graceful-shutdown handler.`,
696
- `CMD ["npx", "skein", "start", "--store", "postgres", "--queue", "redis", "--host", "0.0.0.0"]`
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 = 8123;
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
- var FORCE_EXIT_MS2 = 5e3;
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, workerCount: 1 }, logger);
987
- let shuttingDown = false;
988
- const shutdown = () => {
989
- if (shuttingDown) return;
990
- shuttingDown = true;
991
- const forceExit = setTimeout(() => process.exit(0), FORCE_EXIT_MS2);
992
- forceExit.unref();
993
- void Promise.allSettled([server.close(), runtime.dispose()]).then(() => process.exit(0));
994
- };
995
- process.on("SIGINT", shutdown);
996
- process.on("SIGTERM", shutdown);
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, 2024).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("-v, --verbose", "Log per-run activity: start/finish, tool calls, and interrupts").action(
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, 2024).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("-v, --verbose", "Log per-run activity: start/finish, tool calls, and interrupts").action(
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, 8123).option("--host <host>", "Host to bind", "0.0.0.0").option(
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.9.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.9.0",
51
- "@skein-js/config": "0.9.0",
52
- "@skein-js/express": "0.9.0",
53
- "@skein-js/runtime": "0.9.0"
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"