skein-js 0.4.0 → 0.6.3
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 +10 -6
- package/dist/index.js +434 -114
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -52,15 +52,19 @@ LangGraph Studio — any Agent Protocol client works with only a URL change. See
|
|
|
52
52
|
|
|
53
53
|
## Commands
|
|
54
54
|
|
|
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) |
|
|
59
|
-
| `skein build` | Build a deployable Docker image from the config. | `langgraph build` | `-t, --tag` (defaults to the project dir name) |
|
|
60
|
-
| `skein dockerfile` | Emit a standalone Dockerfile (stdout by default). | `langgraph dockerfile` | `-o, --output <path>`
|
|
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>` |
|
|
61
61
|
|
|
62
62
|
All commands take `-c, --config <path>` (default `langgraph.json`).
|
|
63
63
|
|
|
64
|
+
`--npmrc <path>` (on `build`/`up`) mounts an `.npmrc` as a BuildKit secret so the image's dependency
|
|
65
|
+
install can authenticate against a **private/authenticated npm registry** without baking a token into
|
|
66
|
+
any layer. Public-registry builds don't need it.
|
|
67
|
+
|
|
64
68
|
## `skein dev` flags
|
|
65
69
|
|
|
66
70
|
| Flag | Values | Default | Notes |
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { createRequire } from "module";
|
|
4
|
+
import { createRequire as createRequire3 } from "module";
|
|
5
5
|
import { Command, InvalidArgumentError } from "@commander-js/extra-typings";
|
|
6
6
|
|
|
7
7
|
// src/dev-command.ts
|
|
@@ -26,7 +26,7 @@ var bold = paint(1);
|
|
|
26
26
|
var dim = paint(2);
|
|
27
27
|
|
|
28
28
|
// src/banner.ts
|
|
29
|
-
function printBanner(info,
|
|
29
|
+
function printBanner(info, logger2) {
|
|
30
30
|
const { host, port, graphIds, authPath, workerCount } = info;
|
|
31
31
|
const base = `http://${host}:${port}`;
|
|
32
32
|
console.log();
|
|
@@ -35,10 +35,10 @@ function printBanner(info, logger) {
|
|
|
35
35
|
console.log(`${dim("API ")} ${cyan(base)}`);
|
|
36
36
|
console.log(`${dim("Docs")} ${cyan(`${base}/docs`)}`);
|
|
37
37
|
console.log();
|
|
38
|
-
for (const id of graphIds)
|
|
39
|
-
if (authPath)
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
for (const id of graphIds) logger2.info(`Registering graph with id '${id}'`);
|
|
39
|
+
if (authPath) logger2.info(`Loading auth from ${authPath}`);
|
|
40
|
+
logger2.info(`Starting ${workerCount} worker${workerCount === 1 ? "" : "s"}`);
|
|
41
|
+
logger2.info(`Server running at ${base}`);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// src/dev-logger.ts
|
|
@@ -135,13 +135,27 @@ async function applyProjectEnv(config, configDir) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// src/serve-env.ts
|
|
139
|
+
function envPort(fallback) {
|
|
140
|
+
const raw = process.env.PORT;
|
|
141
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
142
|
+
const port = Number(raw);
|
|
143
|
+
return Number.isInteger(port) && port >= 0 && port <= 65535 ? port : fallback;
|
|
144
|
+
}
|
|
145
|
+
function envHost(fallback) {
|
|
146
|
+
const host = process.env.HOST;
|
|
147
|
+
return host !== void 0 && host.trim() !== "" ? host : fallback;
|
|
148
|
+
}
|
|
149
|
+
function describeBindError(error, port) {
|
|
150
|
+
const code = error.code;
|
|
151
|
+
if (code === "EADDRINUSE") {
|
|
152
|
+
return `port ${port} is already in use. Stop the other process or pass --port.`;
|
|
153
|
+
}
|
|
154
|
+
return `failed to start server: ${error instanceof Error ? error.message : String(error)}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
138
157
|
// src/vite-graph-loader.ts
|
|
139
158
|
import { createServer as createNetServer } from "net";
|
|
140
|
-
import {
|
|
141
|
-
createServer,
|
|
142
|
-
createServerModuleRunner,
|
|
143
|
-
searchForWorkspaceRoot
|
|
144
|
-
} from "vite";
|
|
145
159
|
function findFreePort() {
|
|
146
160
|
return new Promise((resolve, reject) => {
|
|
147
161
|
const probe = createNetServer();
|
|
@@ -154,6 +168,7 @@ function findFreePort() {
|
|
|
154
168
|
});
|
|
155
169
|
}
|
|
156
170
|
async function createViteGraphLoader(root, ignored = []) {
|
|
171
|
+
const { createServer, createServerModuleRunner, searchForWorkspaceRoot } = await import("vite");
|
|
157
172
|
const workspaceRoot = searchForWorkspaceRoot(root);
|
|
158
173
|
const server = await createServer({
|
|
159
174
|
root,
|
|
@@ -195,16 +210,6 @@ var RELOAD_DEBOUNCE_MS = 120;
|
|
|
195
210
|
var AUTOSAVE_MS = 2e3;
|
|
196
211
|
var FORCE_EXIT_MS = 5e3;
|
|
197
212
|
var devLogger = createDevLogger();
|
|
198
|
-
function envPort(fallback) {
|
|
199
|
-
const raw = process.env.PORT;
|
|
200
|
-
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
201
|
-
const port = Number(raw);
|
|
202
|
-
return Number.isInteger(port) && port >= 0 && port <= 65535 ? port : fallback;
|
|
203
|
-
}
|
|
204
|
-
function envHost(fallback) {
|
|
205
|
-
const host = process.env.HOST;
|
|
206
|
-
return host !== void 0 && host.trim() !== "" ? host : fallback;
|
|
207
|
-
}
|
|
208
213
|
async function runDev(options) {
|
|
209
214
|
const configPath = path3.resolve(process.cwd(), options.config);
|
|
210
215
|
const { config, configDir } = await loadConfig({ configPath });
|
|
@@ -263,10 +268,7 @@ async function runDev(options) {
|
|
|
263
268
|
await server.listen(port, host);
|
|
264
269
|
} catch (error) {
|
|
265
270
|
await Promise.allSettled([loader.close(), runtime.dispose()]);
|
|
266
|
-
|
|
267
|
-
console.error(
|
|
268
|
-
code === "EADDRINUSE" ? `skein: port ${port} is already in use. Stop the other process or pass --port.` : `skein: failed to start dev server: ${error instanceof Error ? error.message : String(error)}`
|
|
269
|
-
);
|
|
271
|
+
console.error(`skein: ${describeBindError(error, port)}`);
|
|
270
272
|
process.exitCode = 1;
|
|
271
273
|
return;
|
|
272
274
|
}
|
|
@@ -357,9 +359,206 @@ async function runDev(options) {
|
|
|
357
359
|
|
|
358
360
|
// src/docker/commands.ts
|
|
359
361
|
import { spawn, spawnSync } from "child_process";
|
|
360
|
-
import { existsSync as existsSync4,
|
|
362
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
363
|
+
import { createRequire as createRequire2 } from "module";
|
|
361
364
|
import path5 from "path";
|
|
362
|
-
import { loadConfig as
|
|
365
|
+
import { loadConfig as loadConfig3 } from "@skein-js/config";
|
|
366
|
+
|
|
367
|
+
// src/bundle/bundle-project.ts
|
|
368
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
369
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
370
|
+
import { createRequire, isBuiltin } from "module";
|
|
371
|
+
import path4 from "path";
|
|
372
|
+
import { loadConfig as loadConfig2, parseGraphSpec } from "@skein-js/config";
|
|
373
|
+
import { embedRuntimePackage, isCustomFunctionPath } from "@skein-js/runtime";
|
|
374
|
+
|
|
375
|
+
// src/bundle/precompute-schemas.ts
|
|
376
|
+
async function precomputeSchemas(graphs) {
|
|
377
|
+
const entries = await Promise.all(
|
|
378
|
+
graphs.ids.map(async (id) => [id, await graphs.schemas(id)])
|
|
379
|
+
);
|
|
380
|
+
return Object.fromEntries(entries);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/bundle/write-manifest.ts
|
|
384
|
+
function buildProductionConfig(source, rewrites) {
|
|
385
|
+
const config = structuredClone(source);
|
|
386
|
+
config.graphs = { ...rewrites.graphs };
|
|
387
|
+
if (rewrites.auth && config.auth) {
|
|
388
|
+
config.auth = { ...config.auth, path: rewrites.auth };
|
|
389
|
+
}
|
|
390
|
+
if (rewrites.embed && config.store?.index) {
|
|
391
|
+
config.store = {
|
|
392
|
+
...config.store,
|
|
393
|
+
index: { ...config.store.index, embed: rewrites.embed }
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (typeof config.env === "string") delete config.env;
|
|
397
|
+
return config;
|
|
398
|
+
}
|
|
399
|
+
function buildArtifactPackageJson(appName, dependencies) {
|
|
400
|
+
const sorted = Object.fromEntries(
|
|
401
|
+
Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b))
|
|
402
|
+
);
|
|
403
|
+
const pkg = {
|
|
404
|
+
name: `${appName}-skein-artifact`,
|
|
405
|
+
version: "0.0.0",
|
|
406
|
+
private: true,
|
|
407
|
+
type: "module",
|
|
408
|
+
dependencies: sorted
|
|
409
|
+
};
|
|
410
|
+
return `${JSON.stringify(pkg, null, 2)}
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/bundle/bundle-project.ts
|
|
415
|
+
var SKEIN_RUNTIME_PEERS = ["@langchain/langgraph", "@langchain/langgraph-checkpoint-postgres"];
|
|
416
|
+
function packageNameOf(specifier) {
|
|
417
|
+
const parts = specifier.split("/");
|
|
418
|
+
return specifier.startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0] ?? specifier;
|
|
419
|
+
}
|
|
420
|
+
function safeName(graphId) {
|
|
421
|
+
return graphId.replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
422
|
+
}
|
|
423
|
+
function uniqueSafeName(graphId, used) {
|
|
424
|
+
const base = safeName(graphId);
|
|
425
|
+
let candidate = base;
|
|
426
|
+
let suffix = 2;
|
|
427
|
+
while (used.has(candidate)) candidate = `${base}-${suffix++}`;
|
|
428
|
+
used.add(candidate);
|
|
429
|
+
return candidate;
|
|
430
|
+
}
|
|
431
|
+
function nodeMajor(nodeVersion) {
|
|
432
|
+
return nodeVersion?.trim().match(/\d+/)?.[0] ?? "20";
|
|
433
|
+
}
|
|
434
|
+
function readVersion(pkgJsonPath) {
|
|
435
|
+
try {
|
|
436
|
+
const parsed = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
|
|
437
|
+
return parsed.version;
|
|
438
|
+
} catch {
|
|
439
|
+
return void 0;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function resolveInstalledVersion(pkg, fromDir) {
|
|
443
|
+
const require3 = createRequire(path4.join(fromDir, "__skein_resolver__.js"));
|
|
444
|
+
try {
|
|
445
|
+
const version2 = readVersion(require3.resolve(`${pkg}/package.json`));
|
|
446
|
+
if (version2) return version2;
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
let dir = path4.dirname(require3.resolve(pkg));
|
|
451
|
+
while (dir !== path4.dirname(dir)) {
|
|
452
|
+
const pkgJson = path4.join(dir, "package.json");
|
|
453
|
+
if (existsSync3(pkgJson)) {
|
|
454
|
+
const parsed = JSON.parse(readFileSync3(pkgJson, "utf8"));
|
|
455
|
+
if (parsed.name === pkg && parsed.version) return parsed.version;
|
|
456
|
+
}
|
|
457
|
+
dir = path4.dirname(dir);
|
|
458
|
+
}
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
throw new Error(
|
|
462
|
+
`skein build: could not resolve an installed version of "${pkg}". Install it in your project (it is imported by a graph/auth/embed module and must ship in the production image).`
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
function externalizeNodeModules(externals) {
|
|
466
|
+
return {
|
|
467
|
+
name: "skein-externalize-node-modules",
|
|
468
|
+
enforce: "post",
|
|
469
|
+
async resolveId(source, importer, options) {
|
|
470
|
+
if (source.startsWith("\0") || source.startsWith("/") || source.startsWith(".")) return null;
|
|
471
|
+
if (isBuiltin(source) || source.startsWith("node:")) return { id: source, external: true };
|
|
472
|
+
const resolved = await this.resolve(source, importer, { ...options, skipSelf: true });
|
|
473
|
+
if (!resolved || resolved.id.includes("node_modules")) {
|
|
474
|
+
externals.add(packageNameOf(source));
|
|
475
|
+
return { id: source, external: true };
|
|
476
|
+
}
|
|
477
|
+
return resolved;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
async function bundleProject(options) {
|
|
482
|
+
const { configPath, outDir, nodeVersion, skeinVersion } = options;
|
|
483
|
+
const { build, searchForWorkspaceRoot } = await import("vite");
|
|
484
|
+
const { config, configDir, graphs } = await loadConfig2({ configPath });
|
|
485
|
+
const workspaceRoot = searchForWorkspaceRoot(configDir);
|
|
486
|
+
const input = {};
|
|
487
|
+
const rewrites = { graphs: {} };
|
|
488
|
+
const usedGraphNames = /* @__PURE__ */ new Set();
|
|
489
|
+
for (const graphId of graphs.ids) {
|
|
490
|
+
const spec = graphs.spec(graphId);
|
|
491
|
+
const entryKey = `graphs/${uniqueSafeName(graphId, usedGraphNames)}`;
|
|
492
|
+
input[entryKey] = spec.sourceFile;
|
|
493
|
+
rewrites.graphs[graphId] = `./${entryKey}.js:${spec.exportSymbol}`;
|
|
494
|
+
}
|
|
495
|
+
if (config.auth?.path) {
|
|
496
|
+
const spec = parseGraphSpec(config.auth.path, configDir);
|
|
497
|
+
input["auth"] = spec.sourceFile;
|
|
498
|
+
rewrites.auth = `./auth.js:${spec.exportSymbol}`;
|
|
499
|
+
}
|
|
500
|
+
if (config.store?.index?.embed && isCustomFunctionPath(config.store.index.embed)) {
|
|
501
|
+
const spec = parseGraphSpec(config.store.index.embed, configDir);
|
|
502
|
+
input["embed"] = spec.sourceFile;
|
|
503
|
+
rewrites.embed = `./embed.js:${spec.exportSymbol}`;
|
|
504
|
+
}
|
|
505
|
+
const externals = /* @__PURE__ */ new Set();
|
|
506
|
+
const [, schemas] = await Promise.all([
|
|
507
|
+
build({
|
|
508
|
+
root: workspaceRoot,
|
|
509
|
+
configFile: false,
|
|
510
|
+
logLevel: "warn",
|
|
511
|
+
resolve: { tsconfigPaths: true },
|
|
512
|
+
plugins: [externalizeNodeModules(externals)],
|
|
513
|
+
build: {
|
|
514
|
+
ssr: true,
|
|
515
|
+
outDir,
|
|
516
|
+
emptyOutDir: true,
|
|
517
|
+
target: `node${nodeMajor(nodeVersion)}`,
|
|
518
|
+
minify: true,
|
|
519
|
+
sourcemap: true,
|
|
520
|
+
rollupOptions: {
|
|
521
|
+
input,
|
|
522
|
+
output: {
|
|
523
|
+
format: "es",
|
|
524
|
+
entryFileNames: "[name].js",
|
|
525
|
+
chunkFileNames: "chunks/[name]-[hash].js"
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}),
|
|
530
|
+
precomputeSchemas(graphs)
|
|
531
|
+
]);
|
|
532
|
+
const dependencies = {};
|
|
533
|
+
for (const pkg of externals) dependencies[pkg] = resolveInstalledVersion(pkg, workspaceRoot);
|
|
534
|
+
dependencies["skein-js"] = skeinVersion;
|
|
535
|
+
for (const peer of SKEIN_RUNTIME_PEERS) {
|
|
536
|
+
dependencies[peer] = resolveInstalledVersion(peer, workspaceRoot);
|
|
537
|
+
}
|
|
538
|
+
const embedPkg = config.store?.index?.embed && embedRuntimePackage(config.store.index.embed);
|
|
539
|
+
if (embedPkg) dependencies[embedPkg] = resolveInstalledVersion(embedPkg, workspaceRoot);
|
|
540
|
+
for (const dep of config.dependencies ?? []) {
|
|
541
|
+
if (dep.startsWith(".") || dep.startsWith("/")) continue;
|
|
542
|
+
const pkg = packageNameOf(dep);
|
|
543
|
+
dependencies[pkg] = resolveInstalledVersion(pkg, workspaceRoot);
|
|
544
|
+
}
|
|
545
|
+
await mkdir(outDir, { recursive: true });
|
|
546
|
+
const productionConfig = buildProductionConfig(config, rewrites);
|
|
547
|
+
await Promise.all([
|
|
548
|
+
writeFile(
|
|
549
|
+
path4.join(outDir, "langgraph.json"),
|
|
550
|
+
`${JSON.stringify(productionConfig, null, 2)}
|
|
551
|
+
`
|
|
552
|
+
),
|
|
553
|
+
writeFile(path4.join(outDir, "schemas.json"), `${JSON.stringify(schemas, null, 2)}
|
|
554
|
+
`),
|
|
555
|
+
writeFile(
|
|
556
|
+
path4.join(outDir, "package.json"),
|
|
557
|
+
buildArtifactPackageJson(path4.basename(configDir), dependencies)
|
|
558
|
+
)
|
|
559
|
+
]);
|
|
560
|
+
return { outDir, graphIds: graphs.ids, externals: dependencies };
|
|
561
|
+
}
|
|
363
562
|
|
|
364
563
|
// src/docker/compose.ts
|
|
365
564
|
function portMapping(options) {
|
|
@@ -369,10 +568,19 @@ function portMapping(options) {
|
|
|
369
568
|
}
|
|
370
569
|
function generateCompose(options) {
|
|
371
570
|
const ports = portMapping(options);
|
|
571
|
+
const appBuild = options.npmrcPath ? `build:
|
|
572
|
+
context: .
|
|
573
|
+
secrets:
|
|
574
|
+
- npmrc` : `build: .`;
|
|
575
|
+
const secretsBlock = options.npmrcPath ? `
|
|
576
|
+
secrets:
|
|
577
|
+
npmrc:
|
|
578
|
+
file: ${JSON.stringify(options.npmrcPath)}
|
|
579
|
+
` : "";
|
|
372
580
|
return `# Generated by skein \u2014 regenerated by \`skein up\`. Edit with care.
|
|
373
581
|
services:
|
|
374
582
|
app:
|
|
375
|
-
|
|
583
|
+
${appBuild}
|
|
376
584
|
# Reap zombies + forward signals with an init process (PID 1), so graph-spawned children don't
|
|
377
585
|
# accumulate; the app itself still handles SIGTERM for graceful shutdown.
|
|
378
586
|
init: true
|
|
@@ -417,41 +625,14 @@ services:
|
|
|
417
625
|
|
|
418
626
|
volumes:
|
|
419
627
|
skein-pgdata:
|
|
420
|
-
`;
|
|
628
|
+
${secretsBlock}`;
|
|
421
629
|
}
|
|
422
630
|
|
|
423
631
|
// src/docker/dockerfile.ts
|
|
424
|
-
import { existsSync as existsSync3 } from "fs";
|
|
425
|
-
import path4 from "path";
|
|
426
632
|
function baseImage(nodeVersion) {
|
|
427
633
|
const major = nodeVersion?.trim().match(/^\d+/)?.[0] ?? "20";
|
|
428
634
|
return `node:${major}-slim`;
|
|
429
635
|
}
|
|
430
|
-
function detectPackageManager(projectDir) {
|
|
431
|
-
if (existsSync3(path4.join(projectDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
432
|
-
if (existsSync3(path4.join(projectDir, "yarn.lock"))) return "yarn";
|
|
433
|
-
return "npm";
|
|
434
|
-
}
|
|
435
|
-
function installSteps(manager) {
|
|
436
|
-
switch (manager) {
|
|
437
|
-
case "pnpm":
|
|
438
|
-
return {
|
|
439
|
-
copyLock: "COPY package.json pnpm-lock.yaml* ./",
|
|
440
|
-
install: "RUN --mount=type=cache,target=/root/.local/share/pnpm/store corepack enable && pnpm install --frozen-lockfile"
|
|
441
|
-
};
|
|
442
|
-
case "yarn":
|
|
443
|
-
return {
|
|
444
|
-
copyLock: "COPY package.json yarn.lock* ./",
|
|
445
|
-
install: "RUN --mount=type=cache,target=/usr/local/share/.cache/yarn corepack enable && yarn install --frozen-lockfile"
|
|
446
|
-
};
|
|
447
|
-
case "npm":
|
|
448
|
-
return {
|
|
449
|
-
copyLock: "COPY package.json package-lock.json* ./",
|
|
450
|
-
// `npm ci` needs a lockfile; fall back to `npm install` when there isn't one.
|
|
451
|
-
install: "RUN --mount=type=cache,target=/root/.npm npm ci || npm install"
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
636
|
function generateDockerignore() {
|
|
456
637
|
return [
|
|
457
638
|
"# Generated by skein \u2014 regenerated by `skein build` / `skein up`.",
|
|
@@ -459,7 +640,9 @@ function generateDockerignore() {
|
|
|
459
640
|
"node_modules",
|
|
460
641
|
".env",
|
|
461
642
|
".env.*",
|
|
462
|
-
|
|
643
|
+
// Never bake a registry credential into a layer — it's supplied via a BuildKit secret at install.
|
|
644
|
+
".npmrc",
|
|
645
|
+
".npmrc.*",
|
|
463
646
|
"Dockerfile",
|
|
464
647
|
"compose.yaml",
|
|
465
648
|
".dockerignore",
|
|
@@ -468,65 +651,88 @@ function generateDockerignore() {
|
|
|
468
651
|
].join("\n");
|
|
469
652
|
}
|
|
470
653
|
function generateDockerfile(options) {
|
|
471
|
-
const { nodeVersion, dockerfileLines = [], port
|
|
472
|
-
const { copyLock, install } = installSteps(packageManager);
|
|
654
|
+
const { nodeVersion, dockerfileLines = [], port } = options;
|
|
473
655
|
const lines = [
|
|
474
|
-
// The syntax directive must be the first line to be honored — it enables the RUN cache
|
|
656
|
+
// The syntax directive must be the first line to be honored — it enables the RUN cache mount.
|
|
475
657
|
`# syntax=docker/dockerfile:1`,
|
|
476
658
|
`# Generated by skein \u2014 do not edit by hand (regenerated by \`skein build\` / \`skein up\`).`,
|
|
659
|
+
// The build context must be a skein artifact dir (bundled JS + pinned package.json + schemas.json),
|
|
660
|
+
// which `skein build`/`up` create under `.skein/build`. Building this Dockerfile against a raw
|
|
661
|
+
// project dir will fail — run `skein build` instead of `docker build` by hand.
|
|
662
|
+
`# Build context: a \`.skein/build\` artifact produced by \`skein build\` (not the project root).`,
|
|
477
663
|
`FROM ${baseImage(nodeVersion)}`,
|
|
478
|
-
// Hand /app to the unprivileged 'node' user (uid 1000, shipped by the base image) so the server
|
|
479
|
-
// never runs as root. corepack/npm need root to install, so install as root, then chown the
|
|
480
|
-
// whole tree — including the root-created node_modules, where vite writes its cache at runtime.
|
|
481
664
|
`WORKDIR /app`,
|
|
482
665
|
``,
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
666
|
+
// Install prod deps first for layer caching: the artifact's pinned package.json changes rarely,
|
|
667
|
+
// so this layer is reused across rebuilds while only the bundle layer below re-runs. The BuildKit
|
|
668
|
+
// cache mount keeps the npm cache out of the committed layer. The `npmrc` secret mount lets private
|
|
669
|
+
// scoped deps authenticate against a private/authenticated registry without baking a token into any
|
|
670
|
+
// layer; it is optional (BuildKit secrets are not required by default), so a public-registry build
|
|
671
|
+
// that passes no secret is unaffected. Supply it via `skein build --npmrc <path>` or, for the
|
|
672
|
+
// standalone Dockerfile, `docker build --secret id=npmrc,src=<path>`.
|
|
673
|
+
`# Install the artifact's pinned production dependencies (exact versions \u2014 deterministic).`,
|
|
674
|
+
`COPY package.json ./`,
|
|
675
|
+
`RUN --mount=type=cache,target=/root/.npm \\`,
|
|
676
|
+
` --mount=type=secret,id=npmrc,target=/app/.npmrc \\`,
|
|
677
|
+
` npm install --omit=dev --omit=optional --no-audit --no-fund`,
|
|
486
678
|
``,
|
|
487
|
-
`# Copy the
|
|
679
|
+
`# Copy the pre-built artifact (bundled JS graphs, production langgraph.json, baked schemas).`,
|
|
488
680
|
`COPY . .`,
|
|
489
681
|
``,
|
|
490
|
-
`# Give the runtime user ownership of the app + installed node_modules so vite can write its`,
|
|
491
|
-
`# on-disk cache under node_modules/.vite without EACCES.`,
|
|
492
|
-
`RUN chown -R node:node /app`,
|
|
493
|
-
``,
|
|
494
|
-
// Set NODE_ENV only after install so devDependencies (the vite/tsx toolchain `skein dev` loads
|
|
495
|
-
// graphs with) are installed; setting it before install would prune them and break graph loading.
|
|
496
682
|
`ENV NODE_ENV=production`,
|
|
683
|
+
// The bundle ships sourcemaps; enable them so stack traces map back to the original TypeScript.
|
|
684
|
+
`ENV NODE_OPTIONS=--enable-source-maps`,
|
|
497
685
|
`EXPOSE ${port}`,
|
|
686
|
+
// Liveness probe against skein's `/ok` endpoint, targeting the same port the server binds.
|
|
687
|
+
`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))"`,
|
|
689
|
+
// Run unprivileged. The bundle writes nothing to disk, and /app is world-readable, so no chown.
|
|
498
690
|
`USER node`,
|
|
499
691
|
...dockerfileLines,
|
|
500
692
|
``,
|
|
501
|
-
`# Serve the
|
|
693
|
+
`# Serve the compiled artifact against Postgres + Redis (POSTGRES_URI / REDIS_URI from the env).`,
|
|
502
694
|
`# No --port: the server binds $PORT when the platform injects one (Railway/Fly/Render), else its`,
|
|
503
695
|
`# default. Exec form keeps node as PID 1 so SIGTERM reaches skein's graceful-shutdown handler.`,
|
|
504
|
-
`CMD ["npx", "skein", "
|
|
696
|
+
`CMD ["npx", "skein", "start", "--store", "postgres", "--queue", "redis", "--host", "0.0.0.0"]`
|
|
505
697
|
];
|
|
506
698
|
return `${lines.join("\n")}
|
|
507
699
|
`;
|
|
508
700
|
}
|
|
509
701
|
|
|
510
702
|
// src/docker/commands.ts
|
|
703
|
+
function skeinCliVersion() {
|
|
704
|
+
return createRequire2(import.meta.url)("../package.json").version;
|
|
705
|
+
}
|
|
706
|
+
var ARTIFACT_SUBDIR = path5.join(".skein", "build");
|
|
511
707
|
var CONTAINER_PORT = 8123;
|
|
512
708
|
var DOCKERFILE_NAME = "Dockerfile";
|
|
513
709
|
var COMPOSE_NAME = "compose.yaml";
|
|
514
710
|
var DOCKERIGNORE_NAME = ".dockerignore";
|
|
515
|
-
var GENERATED_MARKER = "# Generated by skein";
|
|
516
|
-
function isSkeinGenerated(contents) {
|
|
517
|
-
return contents.split("\n", 3).some((line2) => line2.startsWith(GENERATED_MARKER));
|
|
518
|
-
}
|
|
519
711
|
async function loadConfigContext(configOption) {
|
|
520
712
|
const configPath = path5.resolve(process.cwd(), configOption);
|
|
521
|
-
const { config, configDir } = await
|
|
522
|
-
return { config, configDir };
|
|
713
|
+
const { config, configDir } = await loadConfig3({ configPath });
|
|
714
|
+
return { config, configDir, configPath };
|
|
715
|
+
}
|
|
716
|
+
async function prepareArtifact(context, extraAssets = []) {
|
|
717
|
+
const outDir = path5.join(context.configDir, ARTIFACT_SUBDIR);
|
|
718
|
+
console.log("skein: bundling graphs into a production artifact\u2026");
|
|
719
|
+
await bundleProject({
|
|
720
|
+
configPath: context.configPath,
|
|
721
|
+
outDir,
|
|
722
|
+
nodeVersion: context.config.node_version,
|
|
723
|
+
skeinVersion: skeinCliVersion()
|
|
724
|
+
});
|
|
725
|
+
writeFileSync2(path5.join(outDir, DOCKERFILE_NAME), renderDockerfile(context, CONTAINER_PORT));
|
|
726
|
+
writeFileSync2(path5.join(outDir, DOCKERIGNORE_NAME), generateDockerignore());
|
|
727
|
+
for (const asset of extraAssets) writeFileSync2(path5.join(outDir, asset.name), asset.contents);
|
|
728
|
+
console.log(`skein: wrote artifact to ${path5.relative(process.cwd(), outDir) || "."}.`);
|
|
729
|
+
return outDir;
|
|
523
730
|
}
|
|
524
|
-
function renderDockerfile({ config
|
|
731
|
+
function renderDockerfile({ config }, port) {
|
|
525
732
|
return generateDockerfile({
|
|
526
733
|
nodeVersion: config.node_version,
|
|
527
734
|
dockerfileLines: config.dockerfile_lines,
|
|
528
|
-
port
|
|
529
|
-
packageManager: detectPackageManager(configDir)
|
|
735
|
+
port
|
|
530
736
|
});
|
|
531
737
|
}
|
|
532
738
|
function defaultImageTag(configDir) {
|
|
@@ -534,6 +740,16 @@ function defaultImageTag(configDir) {
|
|
|
534
740
|
const trimmed = base.replace(/^[-.]+|[-.]+$/g, "");
|
|
535
741
|
return trimmed.length > 0 ? trimmed : "skein-app";
|
|
536
742
|
}
|
|
743
|
+
function resolveNpmrcSecret(npmrc) {
|
|
744
|
+
if (npmrc === void 0) return void 0;
|
|
745
|
+
const resolved = path5.resolve(process.cwd(), npmrc);
|
|
746
|
+
if (!existsSync4(resolved)) {
|
|
747
|
+
console.error(`skein: --npmrc file not found: ${resolved}`);
|
|
748
|
+
process.exitCode = 1;
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
return resolved;
|
|
752
|
+
}
|
|
537
753
|
function dockerAvailable() {
|
|
538
754
|
const probe = spawnSync("docker", ["--version"], { stdio: "ignore" });
|
|
539
755
|
return probe.status === 0;
|
|
@@ -563,19 +779,12 @@ function wasInterrupted(result) {
|
|
|
563
779
|
function describeExit(result) {
|
|
564
780
|
return result.signal !== null ? `killed by ${result.signal}` : `exit ${result.code}`;
|
|
565
781
|
}
|
|
566
|
-
function writeGeneratedFile(file, contents) {
|
|
567
|
-
const name = path5.basename(file);
|
|
568
|
-
if (existsSync4(file) && !isSkeinGenerated(readFileSync3(file, "utf8"))) {
|
|
569
|
-
console.log(`skein: ${name} exists and wasn't generated by skein \u2014 using it as-is.`);
|
|
570
|
-
return "kept-user";
|
|
571
|
-
}
|
|
572
|
-
writeFileSync2(file, contents);
|
|
573
|
-
console.log(`skein: wrote ${name}.`);
|
|
574
|
-
return "wrote";
|
|
575
|
-
}
|
|
576
782
|
async function runDockerfile(options) {
|
|
577
783
|
const context = await loadConfigContext(options.config);
|
|
578
784
|
const contents = renderDockerfile(context, CONTAINER_PORT);
|
|
785
|
+
console.error(
|
|
786
|
+
"skein: this Dockerfile builds against a `.skein/build` artifact (run `skein build`), not the project root."
|
|
787
|
+
);
|
|
579
788
|
if (options.output !== void 0) {
|
|
580
789
|
const target = path5.resolve(process.cwd(), options.output);
|
|
581
790
|
writeFileSync2(target, contents);
|
|
@@ -586,16 +795,24 @@ async function runDockerfile(options) {
|
|
|
586
795
|
}
|
|
587
796
|
async function runBuild(options) {
|
|
588
797
|
const context = await loadConfigContext(options.config);
|
|
798
|
+
const npmrcPath = resolveNpmrcSecret(options.npmrc);
|
|
799
|
+
if (npmrcPath === null) return;
|
|
589
800
|
if (!requireDocker()) return;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
801
|
+
let artifactDir;
|
|
802
|
+
try {
|
|
803
|
+
artifactDir = await prepareArtifact(context);
|
|
804
|
+
} catch (error) {
|
|
805
|
+
console.error(`skein: ${error instanceof Error ? error.message : String(error)}`);
|
|
806
|
+
process.exitCode = 1;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
593
809
|
const tag = options.tag ?? defaultImageTag(context.configDir);
|
|
810
|
+
const secretArgs = npmrcPath !== void 0 ? ["--secret", `id=npmrc,src=${npmrcPath}`] : [];
|
|
594
811
|
console.log(`skein: building image "${tag}"\u2026`);
|
|
595
812
|
const result = await runToCompletion(
|
|
596
813
|
"docker",
|
|
597
|
-
["build", "-t", tag, "-f",
|
|
598
|
-
|
|
814
|
+
["build", "-t", tag, "-f", path5.join(artifactDir, DOCKERFILE_NAME), ...secretArgs, "."],
|
|
815
|
+
artifactDir
|
|
599
816
|
);
|
|
600
817
|
if (!succeeded(result)) {
|
|
601
818
|
console.error(`skein: docker build failed (${describeExit(result)}).`);
|
|
@@ -606,23 +823,34 @@ async function runBuild(options) {
|
|
|
606
823
|
}
|
|
607
824
|
async function runUp(options) {
|
|
608
825
|
const context = await loadConfigContext(options.config);
|
|
826
|
+
const npmrcPath = resolveNpmrcSecret(options.npmrc);
|
|
827
|
+
if (npmrcPath === null) return;
|
|
609
828
|
if (!requireDocker()) return;
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
829
|
+
let artifactDir;
|
|
830
|
+
try {
|
|
831
|
+
artifactDir = await prepareArtifact(context, [
|
|
832
|
+
{
|
|
833
|
+
name: COMPOSE_NAME,
|
|
834
|
+
contents: generateCompose({
|
|
835
|
+
hostPort: options.port,
|
|
836
|
+
host: options.host,
|
|
837
|
+
containerPort: CONTAINER_PORT,
|
|
838
|
+
npmrcPath
|
|
839
|
+
})
|
|
840
|
+
}
|
|
841
|
+
]);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error(`skein: ${error instanceof Error ? error.message : String(error)}`);
|
|
844
|
+
process.exitCode = 1;
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
619
847
|
console.log(
|
|
620
848
|
`skein: bringing up the stack (app + Postgres + Redis) on http://${options.host}:${options.port}\u2026`
|
|
621
849
|
);
|
|
622
850
|
const result = await runToCompletion(
|
|
623
851
|
"docker",
|
|
624
852
|
["compose", "-f", COMPOSE_NAME, "up", "--build"],
|
|
625
|
-
|
|
853
|
+
artifactDir
|
|
626
854
|
);
|
|
627
855
|
if (!succeeded(result) && !wasInterrupted(result)) {
|
|
628
856
|
console.error(`skein: docker compose up failed (${describeExit(result)}).`);
|
|
@@ -633,7 +861,7 @@ async function runUp(options) {
|
|
|
633
861
|
// src/import-command.ts
|
|
634
862
|
import { existsSync as existsSync5 } from "fs";
|
|
635
863
|
import path6 from "path";
|
|
636
|
-
import { loadConfig as
|
|
864
|
+
import { loadConfig as loadConfig4 } from "@skein-js/config";
|
|
637
865
|
import {
|
|
638
866
|
describeSnapshot as describeSnapshot2,
|
|
639
867
|
loadSnapshotIntoStore,
|
|
@@ -645,7 +873,7 @@ function summarize(counts) {
|
|
|
645
873
|
}
|
|
646
874
|
async function runImportLanggraph(options) {
|
|
647
875
|
const configPath = path6.resolve(process.cwd(), options.config);
|
|
648
|
-
const { config, configDir } = await
|
|
876
|
+
const { config, configDir } = await loadConfig4({ configPath });
|
|
649
877
|
const sourceDir = options.from ? path6.resolve(process.cwd(), options.from) : path6.join(configDir, LANGGRAPH_DIR);
|
|
650
878
|
const snapshot = await readLanggraphDevState2(sourceDir);
|
|
651
879
|
if (snapshot === null) {
|
|
@@ -689,8 +917,87 @@ async function runImportLanggraph(options) {
|
|
|
689
917
|
console.log(`skein: imported ${summarize(counts)} into Postgres.`);
|
|
690
918
|
}
|
|
691
919
|
|
|
920
|
+
// src/start-command.ts
|
|
921
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
922
|
+
import path7 from "path";
|
|
923
|
+
import { loadConfig as loadConfig5 } from "@skein-js/config";
|
|
924
|
+
import { createExpressServer as createExpressServer2 } from "@skein-js/express";
|
|
925
|
+
import {
|
|
926
|
+
buildRuntime as buildRuntime3
|
|
927
|
+
} from "@skein-js/runtime";
|
|
928
|
+
var FORCE_EXIT_MS2 = 5e3;
|
|
929
|
+
var logger = createDevLogger();
|
|
930
|
+
function readBakedSchemas(configDir) {
|
|
931
|
+
const schemasFile = path7.join(configDir, "schemas.json");
|
|
932
|
+
if (!existsSync6(schemasFile)) {
|
|
933
|
+
throw new Error(
|
|
934
|
+
`no schemas.json next to the config \u2014 \`skein start\` expects a built artifact. Run \`skein build\` (or \`skein up\`), or use \`skein dev\` for a source project.`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
return JSON.parse(readFileSync4(schemasFile, "utf8"));
|
|
938
|
+
}
|
|
939
|
+
async function runStart(options) {
|
|
940
|
+
const configPath = path7.resolve(process.cwd(), options.config);
|
|
941
|
+
let schemas;
|
|
942
|
+
let configDir;
|
|
943
|
+
let authPath;
|
|
944
|
+
try {
|
|
945
|
+
const loaded = await loadConfig5({ configPath });
|
|
946
|
+
configDir = loaded.configDir;
|
|
947
|
+
authPath = loaded.config.auth?.path;
|
|
948
|
+
await applyProjectEnv(loaded.config, configDir);
|
|
949
|
+
schemas = readBakedSchemas(configDir);
|
|
950
|
+
} catch (error) {
|
|
951
|
+
console.error(`skein: ${error instanceof Error ? error.message : String(error)}`);
|
|
952
|
+
process.exitCode = 1;
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
let runtime;
|
|
956
|
+
try {
|
|
957
|
+
runtime = await buildRuntime3({
|
|
958
|
+
configPath,
|
|
959
|
+
store: options.store,
|
|
960
|
+
queue: options.queue,
|
|
961
|
+
schemas
|
|
962
|
+
});
|
|
963
|
+
} catch (error) {
|
|
964
|
+
console.error(`skein: ${error instanceof Error ? error.message : String(error)}`);
|
|
965
|
+
process.exitCode = 1;
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (options.verbose) runtime.deps.logRunActivity = true;
|
|
969
|
+
const port = options.portExplicit ? options.port : envPort(options.port);
|
|
970
|
+
const host = options.hostExplicit ? options.host : envHost(options.host);
|
|
971
|
+
let server;
|
|
972
|
+
try {
|
|
973
|
+
server = await createExpressServer2({
|
|
974
|
+
deps: runtime.deps,
|
|
975
|
+
cors: runtime.cors,
|
|
976
|
+
warm: true,
|
|
977
|
+
logger
|
|
978
|
+
});
|
|
979
|
+
await server.listen(port, host);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
await runtime.dispose();
|
|
982
|
+
console.error(`skein: ${describeBindError(error, port)}`);
|
|
983
|
+
process.exitCode = 1;
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
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);
|
|
997
|
+
}
|
|
998
|
+
|
|
692
999
|
// src/index.ts
|
|
693
|
-
var require2 =
|
|
1000
|
+
var require2 = createRequire3(import.meta.url);
|
|
694
1001
|
var { version } = require2("../package.json");
|
|
695
1002
|
function parsePort(value) {
|
|
696
1003
|
const port = Number(value);
|
|
@@ -719,8 +1026,21 @@ program.command("dev").description("Run the in-process dev server with hot reloa
|
|
|
719
1026
|
hostExplicit: command.getOptionValueSource("host") === "cli"
|
|
720
1027
|
})
|
|
721
1028
|
);
|
|
722
|
-
program.command("
|
|
723
|
-
|
|
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(
|
|
1030
|
+
(options, command) => runStart({
|
|
1031
|
+
...options,
|
|
1032
|
+
portExplicit: command.getOptionValueSource("port") === "cli",
|
|
1033
|
+
hostExplicit: command.getOptionValueSource("host") === "cli"
|
|
1034
|
+
})
|
|
1035
|
+
);
|
|
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(
|
|
1037
|
+
"-n, --npmrc <path>",
|
|
1038
|
+
"Path to an .npmrc for authenticating private-registry installs (wired in as a build secret)"
|
|
1039
|
+
).action((options) => runUp(options));
|
|
1040
|
+
program.command("build").description("Build a deployable Docker image from the config.").option("-c, --config <path>", "Path to langgraph.json", "langgraph.json").option("-t, --tag <tag>", "Image tag (defaults to the project directory name)").option(
|
|
1041
|
+
"-n, --npmrc <path>",
|
|
1042
|
+
"Path to an .npmrc for authenticating private-registry installs (passed to docker build as a BuildKit secret)"
|
|
1043
|
+
).action((options) => runBuild(options));
|
|
724
1044
|
program.command("dockerfile").description("Emit a standalone Dockerfile from the config.").option("-c, --config <path>", "Path to langgraph.json", "langgraph.json").option("-o, --output <path>", "Write the Dockerfile here instead of stdout").action((options) => runDockerfile(options));
|
|
725
1045
|
program.command("import-langgraph").description("Import an existing LangGraph in-memory dev state (.langgraph_api/) into skein.").option("-c, --config <path>", "Path to langgraph.json", "langgraph.json").option(
|
|
726
1046
|
"--store <driver>",
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skein-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.3",
|
|
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>",
|
|
7
|
-
"homepage": "https://github.com/
|
|
7
|
+
"homepage": "https://github.com/skein-js/skein-js/tree/main/packages/cli#readme",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/skein-js/skein-js.git",
|
|
11
11
|
"directory": "packages/cli"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/skein-js/skein-js/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"typescript",
|
|
@@ -47,11 +47,13 @@
|
|
|
47
47
|
"@commander-js/extra-typings": "~13.1.0",
|
|
48
48
|
"@langchain/langgraph-checkpoint-postgres": "^1.0.4",
|
|
49
49
|
"commander": "~13.1.0",
|
|
50
|
-
"
|
|
51
|
-
"@skein-js/core": "0.
|
|
52
|
-
"@skein-js/express": "0.
|
|
53
|
-
"@skein-js/
|
|
54
|
-
|
|
50
|
+
"@skein-js/config": "0.6.3",
|
|
51
|
+
"@skein-js/core": "0.6.3",
|
|
52
|
+
"@skein-js/express": "0.6.3",
|
|
53
|
+
"@skein-js/runtime": "0.6.3"
|
|
54
|
+
},
|
|
55
|
+
"optionalDependencies": {
|
|
56
|
+
"vite": "^8.1.0"
|
|
55
57
|
},
|
|
56
58
|
"peerDependencies": {
|
|
57
59
|
"@langchain/langgraph": "^1.4.0"
|