@boardwalk-labs/cli 0.1.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 (73) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +110 -0
  3. package/bin/boardwalk.js +4 -0
  4. package/dist/artifact.d.ts +53 -0
  5. package/dist/artifact.js +267 -0
  6. package/dist/artifact.js.map +1 -0
  7. package/dist/auth/discovery.d.ts +8 -0
  8. package/dist/auth/discovery.js +43 -0
  9. package/dist/auth/discovery.js.map +1 -0
  10. package/dist/auth/login.d.ts +13 -0
  11. package/dist/auth/login.js +74 -0
  12. package/dist/auth/login.js.map +1 -0
  13. package/dist/auth/pkce.d.ts +63 -0
  14. package/dist/auth/pkce.js +197 -0
  15. package/dist/auth/pkce.js.map +1 -0
  16. package/dist/auth/resolve.d.ts +12 -0
  17. package/dist/auth/resolve.js +51 -0
  18. package/dist/auth/resolve.js.map +1 -0
  19. package/dist/bundle.d.ts +35 -0
  20. package/dist/bundle.js +176 -0
  21. package/dist/bundle.js.map +1 -0
  22. package/dist/client.d.ts +65 -0
  23. package/dist/client.js +193 -0
  24. package/dist/client.js.map +1 -0
  25. package/dist/commands/cancel.d.ts +14 -0
  26. package/dist/commands/cancel.js +53 -0
  27. package/dist/commands/cancel.js.map +1 -0
  28. package/dist/commands/check.d.ts +7 -0
  29. package/dist/commands/check.js +34 -0
  30. package/dist/commands/check.js.map +1 -0
  31. package/dist/commands/deploy.d.ts +15 -0
  32. package/dist/commands/deploy.js +56 -0
  33. package/dist/commands/deploy.js.map +1 -0
  34. package/dist/commands/dev.d.ts +14 -0
  35. package/dist/commands/dev.js +128 -0
  36. package/dist/commands/dev.js.map +1 -0
  37. package/dist/commands/init.d.ts +12 -0
  38. package/dist/commands/init.js +171 -0
  39. package/dist/commands/init.js.map +1 -0
  40. package/dist/commands/run.d.ts +32 -0
  41. package/dist/commands/run.js +105 -0
  42. package/dist/commands/run.js.map +1 -0
  43. package/dist/commands/session.d.ts +13 -0
  44. package/dist/commands/session.js +58 -0
  45. package/dist/commands/session.js.map +1 -0
  46. package/dist/config.d.ts +14 -0
  47. package/dist/config.js +64 -0
  48. package/dist/config.js.map +1 -0
  49. package/dist/credentials.d.ts +23 -0
  50. package/dist/credentials.js +69 -0
  51. package/dist/credentials.js.map +1 -0
  52. package/dist/deployment.d.ts +44 -0
  53. package/dist/deployment.js +100 -0
  54. package/dist/deployment.js.map +1 -0
  55. package/dist/dev/host.d.ts +16 -0
  56. package/dist/dev/host.js +76 -0
  57. package/dist/dev/host.js.map +1 -0
  58. package/dist/errors.d.ts +8 -0
  59. package/dist/errors.js +18 -0
  60. package/dist/errors.js.map +1 -0
  61. package/dist/index.d.ts +1 -0
  62. package/dist/index.js +147 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/manifest.d.ts +12 -0
  65. package/dist/manifest.js +64 -0
  66. package/dist/manifest.js.map +1 -0
  67. package/dist/project.d.ts +15 -0
  68. package/dist/project.js +79 -0
  69. package/dist/project.js.map +1 -0
  70. package/dist/render/renderer.d.ts +18 -0
  71. package/dist/render/renderer.js +108 -0
  72. package/dist/render/renderer.js.map +1 -0
  73. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Boardwalk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @boardwalk-labs/cli
2
+
3
+ The `boardwalk` command — author, validate, run locally, and deploy [Boardwalk](https://boardwalk.sh) workflows.
4
+
5
+ ```
6
+ boardwalk init my-workflow # scaffold a project from a template
7
+ boardwalk dev ./index.ts # run it NOW, locally — no account needed
8
+ boardwalk check ./index.ts # validate locally (no auth/network)
9
+ boardwalk login # browser OAuth (PKCE) → stores a session
10
+ boardwalk deploy ./index.ts --org my-team # ship it to the Boardwalk platform
11
+ boardwalk run ./index.ts --org my-team --input '{"who":"world"}' # deploy + trigger a real run
12
+ boardwalk cancel <runId>
13
+ boardwalk logout / whoami
14
+ ```
15
+
16
+ ## The author loop
17
+
18
+ - **`init [dir]`** — scaffold a workflow project: program file, `package.json`, `.env.example`,
19
+ `.gitignore`. The default template runs green under `dev` immediately.
20
+ - **`dev <file|dir>`** — run the workflow right here, right now. Derives + validates the manifest
21
+ (precise errors before anything runs), bundles the program, executes it in-process, and streams
22
+ the run-event log. Secrets resolve from `.env` (or `--env <path>`); values never print.
23
+ Exit code is the run's verdict: `0` completed, `1` failed, `130` cancelled (Ctrl-C).
24
+ - **`check <file|dir>`** — everything `dev` validates, without running: full manifest-schema
25
+ validation (the same schema every engine enforces) + an esbuild compile proving every import
26
+ resolves.
27
+
28
+ ### Choosing what to watch
29
+
30
+ Every engine emits the same typed event stream, and every event belongs to one channel:
31
+ `lifecycle`, `phase`, `output`, `log`, `agent`. The flags mean the same thing everywhere:
32
+
33
+ ```
34
+ boardwalk dev ./index.ts # default: lifecycle + phase + output (quiet, readable)
35
+ boardwalk dev ./index.ts --verbose # everything: agent turns, tool calls, captured logs
36
+ boardwalk dev ./index.ts --stream output | jq # just the result — pipe-friendly
37
+ boardwalk dev ./index.ts --stream phase,log
38
+ ```
39
+
40
+ > `dev` today executes the program primitives (secrets, sleeps, phases, output, artifacts)
41
+ > in-process; `agent()` and `workflows.call()` need an engine and fail with a clear pointer.
42
+ > The embedded local engine (`@boardwalk-labs/engine`) makes them work in `dev` — it's next on the
43
+ > [roadmap](./SPEC.md).
44
+
45
+ ## Deploying
46
+
47
+ - **`deploy <file|dir> --org <slug>`** — create/update the workflow (idempotent by `meta.name`).
48
+ `--dry-run` prints the plan only.
49
+ - **`run <file|dir> --org <slug>`** — deploy the current source, trigger a **real run on the
50
+ platform**, and wait for it to finish. `--no-wait` triggers and exits.
51
+
52
+ `deploy` builds the program into a content-addressed artifact: esbuild bundles your entry (deps
53
+ pinned at deploy, `@boardwalk-labs/workflow` stays external), package assets (markdown skills, prompt
54
+ templates) ride along at their relative paths, and the lot is packed into a deterministic tarball,
55
+ uploaded via a presigned URL, and verified server-side. The server re-derives the manifest from
56
+ your `meta` — the CLI never sends a hand-built manifest.
57
+
58
+ ### Project link (`.boardwalk/project.json`)
59
+
60
+ The first `deploy`/`run` in a directory writes `.boardwalk/project.json` (gitignored, Vercel-style)
61
+ with `{ orgSlug, workflowId }`. After that the workflow is identified by that stored **id**, so
62
+ `--org` is optional and renaming `meta.name` or the entry file updates the same workflow instead of
63
+ forking a new one. On a fresh clone, pass `--org` once to re-link (it adopts an existing same-name
64
+ workflow if present, else creates one).
65
+
66
+ ## Authentication
67
+
68
+ Resolved in this precedence:
69
+
70
+ 1. `--token <bearer>` flag (one-off / scripting)
71
+ 2. `BOARDWALK_API_KEY` env (CI / headless — a `bwk_…` API key)
72
+ 3. the stored session from `boardwalk login` — either a browser **OAuth/PKCE** session
73
+ (auto-refreshed when expired) **or** an API key persisted via `boardwalk login --token <key>`
74
+
75
+ `boardwalk login` speaks standard OAuth 2.0 Authorization-Code + PKCE against the deployment's own
76
+ issuer: it fetches `/.well-known/oauth-authorization-server` to discover the endpoints, starts a
77
+ localhost callback server, opens your browser, exchanges the code, and stores the session in
78
+ `<config>/credentials.json` (mode 0600). The CLI session is **scoped, least-privilege** — it can
79
+ deploy and trigger/read runs, but cannot mint API keys, manage billing/members, or read secrets.
80
+
81
+ `init`, `dev`, and `check` need no account at all.
82
+
83
+ ## Configuration (env)
84
+
85
+ Point the CLI at any deployment — the Boardwalk platform (default), or a **self-hosted** install on your
86
+ own domain — without rebuilding:
87
+
88
+ | Variable | Default | Purpose |
89
+ | --------------------------- | -------------------------- | ---------------------------------------------- |
90
+ | `BOARDWALK_API_DOMAIN` | `api.boardwalk.sh` | API host → `https://<domain>` (self-host knob) |
91
+ | `BOARDWALK_API_URL` | — | Full API URL override (local http / ports) |
92
+ | `BOARDWALK_ISSUER_URL` | `https://api.boardwalk.sh` | OAuth issuer origin for `login` (discovery) |
93
+ | `BOARDWALK_OAUTH_CLIENT_ID` | `boardwalk-cli` | OAuth client id (built-in default) |
94
+ | `BOARDWALK_OAUTH_PORT` | `53682` | Loopback redirect port |
95
+ | `BOARDWALK_API_KEY` | — | API key for non-interactive auth |
96
+ | `BOARDWALK_CONFIG_DIR` | XDG config dir | Where credentials are stored |
97
+
98
+ ## Develop
99
+
100
+ ```
101
+ pnpm install
102
+ pnpm test
103
+ pnpm lint
104
+ pnpm build # → dist/, run via ./bin/boardwalk.js
105
+ pnpm boardwalk -- dev ./index.ts # run from source via tsx
106
+ ```
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Thin shim → the compiled CLI entrypoint. Kept JS (not TS) so the published `bin` runs without a
3
+ // build step on the consumer's machine; `dist/index.js` is produced by `pnpm build`.
4
+ import "../dist/index.js";
@@ -0,0 +1,53 @@
1
+ /** The entry module the runner imports after extraction. The whole local graph bundles into it. */
2
+ export declare const ENTRY_OUTPUT = "index.mjs";
3
+ /** The author's ORIGINAL entry source, stored verbatim under the `.bw-src/` tree so a dashboard's
4
+ * code view shows what the user wrote (blank lines + comments intact) and quick-edit round-trips
5
+ * real source — NOT the blank-line-stripped esbuild bundle. The runner never reads `.bw-src/`; it
6
+ * runs {@link ENTRY_OUTPUT}. (A bundled package ships only its entry source here; its local
7
+ * modules are inlined into {@link ENTRY_OUTPUT}.) */
8
+ export declare const SOURCE_FILE = ".bw-src/index.ts";
9
+ /** `sdk_version` for a program that pins no SDK version — defer to whatever the runner ships. */
10
+ export declare const UNPINNED_SDK = "*";
11
+ /** One non-code file shipped in the artifact, at its package-relative POSIX path. */
12
+ export interface ArtifactAsset {
13
+ /** Path inside the artifact (POSIX, package-relative), e.g. `skills/review.md`. */
14
+ relPath: string;
15
+ /** Absolute path on disk to read the bytes from. */
16
+ absPath: string;
17
+ }
18
+ /** The built artifact + the metadata the deploy finalize call records on the version row. */
19
+ export interface BuiltArtifact {
20
+ /** The `.tgz` bytes — the exact object uploaded to storage. */
21
+ tarball: Uint8Array;
22
+ /** sha256 hex of {@link tarball} — content-address + integrity digest. */
23
+ digest: string;
24
+ /** Byte length of {@link tarball}. */
25
+ size: number;
26
+ /** Entry module within the extracted tree ({@link ENTRY_OUTPUT}). */
27
+ entry: string;
28
+ /** Resolved `@boardwalk-labs/workflow` version the program was built against, or {@link UNPINNED_SDK}. */
29
+ sdkVersion: string;
30
+ /** sha256 of the project's lockfile (reproducibility anchor), or null when there is none. */
31
+ lockfileDigest: string | null;
32
+ /** The bundled entry source — lets the CLI extract `meta`/`name` without cracking the tarball. */
33
+ entrySource: string;
34
+ /** Sorted POSIX relative paths of the bundled assets (for `--check` output + validation). */
35
+ assetPaths: string[];
36
+ }
37
+ /** Build the deploy artifact for a file or package directory. */
38
+ export declare function buildArtifact(target: string): Promise<BuiltArtifact>;
39
+ /**
40
+ * Collect the package's runtime assets. With an explicit `boardwalk.assets` list in package.json,
41
+ * exactly those paths are shipped (a file, or a directory included recursively). Otherwise the
42
+ * default is npm-pack-style: every file under the package root EXCEPT source the bundle already
43
+ * inlines, build/config files, dotfiles, and excluded dirs (`node_modules`, `.git`, …). Returns a
44
+ * deterministic, path-sorted list with POSIX relative paths.
45
+ */
46
+ export declare function collectAssets(pkgDir: string): ArtifactAsset[];
47
+ /**
48
+ * Resolve the `@boardwalk-labs/workflow` version the program is built against (for runner SDK-layer
49
+ * compat): prefer the actually-installed version, then the declared dependency range, else UNPINNED.
50
+ */
51
+ export declare function resolveSdkVersion(pkgDir: string | null): string;
52
+ /** sha256 of the project's lockfile (first of pnpm/npm/yarn/bun found), or null when none. */
53
+ export declare function lockfileDigest(pkgDir: string): string | null;
@@ -0,0 +1,267 @@
1
+ // Build a deploy ARTIFACT — the frozen, content-addressed program the runner executes.
2
+ //
3
+ // The packaging model: a workflow is built into a JS artifact AT DEPLOY (never at runtime).
4
+ // `buildArtifact` esbuild-bundles the entry into one `index.mjs` (+ external sourcemap,
5
+ // `@boardwalk-labs/workflow` left external — the host layer), collects the package's non-code assets
6
+ // (markdown skills, prompt templates) at their relative paths, and packs the lot into a
7
+ // DETERMINISTIC `tar.gz`. The artifact is content-addressed by the sha256 of its bytes: the same
8
+ // bytes the CLI uploads are the bytes the runner downloads + verifies, so integrity holds
9
+ // regardless of determinism; determinism just lets identical programs dedup.
10
+ //
11
+ // Pure logic + filesystem reads; no program execution, no network.
12
+ import { createHash } from "node:crypto";
13
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { dirname, join, posix, relative, resolve, sep } from "node:path";
16
+ import { create as tarCreate } from "tar";
17
+ import { bundleWorkflowWithMap, isPackageDir, resolveEntry } from "./bundle.js";
18
+ import { CliError } from "./errors.js";
19
+ const SDK_PACKAGE = "@boardwalk-labs/workflow";
20
+ /** The entry module the runner imports after extraction. The whole local graph bundles into it. */
21
+ export const ENTRY_OUTPUT = "index.mjs";
22
+ /** The author's ORIGINAL entry source, stored verbatim under the `.bw-src/` tree so a dashboard's
23
+ * code view shows what the user wrote (blank lines + comments intact) and quick-edit round-trips
24
+ * real source — NOT the blank-line-stripped esbuild bundle. The runner never reads `.bw-src/`; it
25
+ * runs {@link ENTRY_OUTPUT}. (A bundled package ships only its entry source here; its local
26
+ * modules are inlined into {@link ENTRY_OUTPUT}.) */
27
+ export const SOURCE_FILE = ".bw-src/index.ts";
28
+ /** `sdk_version` for a program that pins no SDK version — defer to whatever the runner ships. */
29
+ export const UNPINNED_SDK = "*";
30
+ const LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock"];
31
+ // Source the esbuild bundle already inlines (or emits) — never shipped raw as a runtime asset.
32
+ const SOURCE_EXT = new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".map"]);
33
+ // Build/config files that are not runtime assets.
34
+ const EXCLUDE_FILES = new Set([
35
+ "package.json",
36
+ "tsconfig.json",
37
+ "tsconfig.build.json",
38
+ ".npmrc",
39
+ ".npmignore",
40
+ ".gitignore",
41
+ ".eslintrc",
42
+ ".eslintrc.json",
43
+ ".prettierrc",
44
+ ...LOCKFILES,
45
+ ]);
46
+ const EXCLUDE_DIRS = new Set([
47
+ "node_modules",
48
+ ".git",
49
+ ".boardwalk",
50
+ "dist",
51
+ "build",
52
+ ".turbo",
53
+ ".cache",
54
+ ]);
55
+ /** Build the deploy artifact for a file or package directory. */
56
+ export async function buildArtifact(target) {
57
+ const entry = resolveEntry(target);
58
+ const { code, map } = await bundleWorkflowWithMap(entry);
59
+ // The author's original entry source — stored verbatim for display/quick-edit (the built `index.mjs`
60
+ // has its blank lines + comments stripped by esbuild).
61
+ const originalSource = readFileSync(entry, "utf8");
62
+ const pkgDir = isPackageDir(target) ? resolve(target) : null;
63
+ const assets = pkgDir === null ? [] : collectAssets(pkgDir);
64
+ const sdkVersion = resolveSdkVersion(pkgDir);
65
+ const lockDigest = pkgDir === null ? null : lockfileDigest(pkgDir);
66
+ // Stage every file at its target relative path, then pack the staging dir deterministically.
67
+ const staging = mkdtempSync(join(tmpdir(), "bw-artifact-"));
68
+ try {
69
+ writeStaged(staging, ENTRY_OUTPUT, Buffer.from(code, "utf8"));
70
+ writeStaged(staging, `${ENTRY_OUTPUT}.map`, Buffer.from(map, "utf8"));
71
+ writeStaged(staging, SOURCE_FILE, Buffer.from(originalSource, "utf8"));
72
+ for (const asset of assets)
73
+ writeStaged(staging, asset.relPath, readFileSync(asset.absPath));
74
+ const relPaths = [
75
+ ENTRY_OUTPUT,
76
+ `${ENTRY_OUTPUT}.map`,
77
+ SOURCE_FILE,
78
+ ...assets.map((a) => a.relPath),
79
+ ].sort();
80
+ const tarball = await packDir(staging, relPaths);
81
+ return {
82
+ tarball,
83
+ digest: sha256Hex(tarball),
84
+ size: tarball.length,
85
+ entry: ENTRY_OUTPUT,
86
+ sdkVersion,
87
+ lockfileDigest: lockDigest,
88
+ entrySource: code,
89
+ assetPaths: assets.map((a) => a.relPath).sort(),
90
+ };
91
+ }
92
+ finally {
93
+ rmSync(staging, { recursive: true, force: true });
94
+ }
95
+ }
96
+ /**
97
+ * Collect the package's runtime assets. With an explicit `boardwalk.assets` list in package.json,
98
+ * exactly those paths are shipped (a file, or a directory included recursively). Otherwise the
99
+ * default is npm-pack-style: every file under the package root EXCEPT source the bundle already
100
+ * inlines, build/config files, dotfiles, and excluded dirs (`node_modules`, `.git`, …). Returns a
101
+ * deterministic, path-sorted list with POSIX relative paths.
102
+ */
103
+ export function collectAssets(pkgDir) {
104
+ const explicit = readAssetGlobs(pkgDir);
105
+ const out = [];
106
+ const seen = new Set();
107
+ const add = (absPath) => {
108
+ const rel = toPosix(relative(pkgDir, absPath));
109
+ if (rel.length === 0 || rel.startsWith("..") || seen.has(rel))
110
+ return;
111
+ seen.add(rel);
112
+ out.push({ relPath: rel, absPath });
113
+ };
114
+ if (explicit !== null) {
115
+ for (const entry of explicit) {
116
+ const abs = resolve(pkgDir, entry);
117
+ if (!existsSync(abs)) {
118
+ throw new CliError(`boardwalk.assets entry not found: ${entry}`);
119
+ }
120
+ if (statSync(abs).isDirectory())
121
+ walk(abs, () => true, add);
122
+ else
123
+ add(abs);
124
+ }
125
+ }
126
+ else {
127
+ walk(pkgDir, defaultAssetFilter, add);
128
+ }
129
+ out.sort((a, b) => (a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0));
130
+ return out;
131
+ }
132
+ /** Default include rule: keep non-source, non-config, non-dot files; prune excluded dirs. */
133
+ function defaultAssetFilter(isDir, name) {
134
+ if (name.startsWith("."))
135
+ return false; // dotfiles + dotdirs
136
+ if (isDir)
137
+ return !EXCLUDE_DIRS.has(name);
138
+ if (EXCLUDE_FILES.has(name))
139
+ return false;
140
+ const dot = name.lastIndexOf(".");
141
+ const ext = dot === -1 ? "" : name.slice(dot).toLowerCase();
142
+ return !SOURCE_EXT.has(ext);
143
+ }
144
+ /** Recursively walk `dir`, calling `onFile(abs)` for each file the `accept` predicate keeps. */
145
+ function walk(dir, accept, onFile) {
146
+ for (const ent of readdirSync(dir, { withFileTypes: true })) {
147
+ const abs = join(dir, ent.name);
148
+ const isDir = ent.isDirectory();
149
+ if (!accept(isDir, ent.name))
150
+ continue;
151
+ if (isDir)
152
+ walk(abs, accept, onFile);
153
+ else if (ent.isFile())
154
+ onFile(abs);
155
+ }
156
+ }
157
+ /** Read `boardwalk.assets: string[]` from package.json, or null when unset/invalid. */
158
+ function readAssetGlobs(pkgDir) {
159
+ const pkgPath = join(pkgDir, "package.json");
160
+ if (!existsSync(pkgPath))
161
+ return null;
162
+ try {
163
+ const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
164
+ const boardwalk = typeof parsed === "object" && parsed !== null
165
+ ? parsed.boardwalk
166
+ : undefined;
167
+ const assets = typeof boardwalk === "object" && boardwalk !== null
168
+ ? boardwalk.assets
169
+ : undefined;
170
+ if (Array.isArray(assets) && assets.every((a) => typeof a === "string")) {
171
+ return assets;
172
+ }
173
+ }
174
+ catch {
175
+ // Unreadable package.json → fall back to the default asset rule.
176
+ }
177
+ return null;
178
+ }
179
+ /**
180
+ * Resolve the `@boardwalk-labs/workflow` version the program is built against (for runner SDK-layer
181
+ * compat): prefer the actually-installed version, then the declared dependency range, else UNPINNED.
182
+ */
183
+ export function resolveSdkVersion(pkgDir) {
184
+ if (pkgDir === null)
185
+ return UNPINNED_SDK;
186
+ const installed = join(pkgDir, "node_modules", SDK_PACKAGE, "package.json");
187
+ if (existsSync(installed)) {
188
+ const v = readJsonField(installed, "version");
189
+ if (v !== null)
190
+ return v;
191
+ }
192
+ const pkgPath = join(pkgDir, "package.json");
193
+ if (existsSync(pkgPath)) {
194
+ for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
195
+ const range = readDepRange(pkgPath, field, SDK_PACKAGE);
196
+ if (range !== null)
197
+ return range;
198
+ }
199
+ }
200
+ return UNPINNED_SDK;
201
+ }
202
+ /** sha256 of the project's lockfile (first of pnpm/npm/yarn/bun found), or null when none. */
203
+ export function lockfileDigest(pkgDir) {
204
+ for (const name of LOCKFILES) {
205
+ const p = join(pkgDir, name);
206
+ if (existsSync(p))
207
+ return sha256Hex(readFileSync(p));
208
+ }
209
+ return null;
210
+ }
211
+ // ----- internals -----
212
+ /** Deterministic tar.gz of `cwd`'s listed files: portable (no uid/gid/mtime), stable order. */
213
+ async function packDir(cwd, files) {
214
+ const chunks = [];
215
+ await new Promise((res, rej) => {
216
+ const stream = tarCreate({ cwd, gzip: true, portable: true, noMtime: true }, files);
217
+ stream.on("data", (c) => chunks.push(c));
218
+ stream.on("end", () => {
219
+ res();
220
+ });
221
+ stream.on("error", rej);
222
+ });
223
+ return new Uint8Array(Buffer.concat(chunks));
224
+ }
225
+ function writeStaged(root, relPath, bytes) {
226
+ const abs = join(root, relPath.split(posix.sep).join(sep));
227
+ mkdirSync(dirname(abs), { recursive: true });
228
+ writeFileSync(abs, bytes);
229
+ }
230
+ function sha256Hex(bytes) {
231
+ return createHash("sha256").update(bytes).digest("hex");
232
+ }
233
+ function toPosix(p) {
234
+ return p.split(sep).join(posix.sep);
235
+ }
236
+ function readJsonField(jsonPath, field) {
237
+ try {
238
+ const parsed = JSON.parse(readFileSync(jsonPath, "utf8"));
239
+ if (typeof parsed === "object" && parsed !== null) {
240
+ const v = parsed[field];
241
+ if (typeof v === "string" && v.length > 0)
242
+ return v;
243
+ }
244
+ }
245
+ catch {
246
+ // ignore
247
+ }
248
+ return null;
249
+ }
250
+ function readDepRange(pkgPath, field, dep) {
251
+ try {
252
+ const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
253
+ const deps = typeof parsed === "object" && parsed !== null
254
+ ? parsed[field]
255
+ : undefined;
256
+ if (typeof deps === "object" && deps !== null) {
257
+ const range = deps[dep];
258
+ if (typeof range === "string" && range.length > 0)
259
+ return range;
260
+ }
261
+ }
262
+ catch {
263
+ // ignore
264
+ }
265
+ return null;
266
+ }
267
+ //# sourceMappingURL=artifact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact.js","sourceRoot":"","sources":["../src/artifact.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { FetchLike } from "./pkce.js";
2
+ export interface OAuthDiscovery {
3
+ authorizationEndpoint: string;
4
+ tokenEndpoint: string;
5
+ }
6
+ /** Fetch + parse the issuer's OAuth Authorization Server Metadata. Throws `CliError` (actionable) on
7
+ * an unreachable endpoint, a non-2xx, a non-JSON body, or a document missing the two endpoints. */
8
+ export declare function discoverOAuth(issuerUrl: string, fetchImpl?: FetchLike): Promise<OAuthDiscovery>;
@@ -0,0 +1,43 @@
1
+ // OAuth discovery — one `BOARDWALK_ISSUER_URL` resolves BOTH the authorize page and the token
2
+ // endpoint, which live on different hosts in Boardwalk's self-hosted OAuth server (the themed consent
3
+ // page on the web app, the token exchange on the api-server). The CLI fetches the RFC 8414 metadata
4
+ // document and reads `authorization_endpoint` + `token_endpoint` from it.
5
+ import { CliError } from "../errors.js";
6
+ const DISCOVERY_PATH = "/.well-known/oauth-authorization-server";
7
+ const DISCOVERY_TIMEOUT_MS = 15_000;
8
+ /** Fetch + parse the issuer's OAuth Authorization Server Metadata. Throws `CliError` (actionable) on
9
+ * an unreachable endpoint, a non-2xx, a non-JSON body, or a document missing the two endpoints. */
10
+ export async function discoverOAuth(issuerUrl, fetchImpl = fetch) {
11
+ const url = `${issuerUrl.replace(/\/+$/, "")}${DISCOVERY_PATH}`;
12
+ let res;
13
+ try {
14
+ res = await fetchImpl(url, {
15
+ headers: { Accept: "application/json" },
16
+ signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),
17
+ });
18
+ }
19
+ catch (err) {
20
+ throw new CliError(`Could not reach the OAuth discovery endpoint (${url}).`, err instanceof Error ? err.message : undefined);
21
+ }
22
+ if (!res.ok) {
23
+ throw new CliError(`OAuth discovery failed (${String(res.status)}) at ${url}.`, "Check BOARDWALK_ISSUER_URL (or BOARDWALK_API_DOMAIN) points at a Boardwalk deployment.");
24
+ }
25
+ let body;
26
+ try {
27
+ body = await res.json();
28
+ }
29
+ catch {
30
+ throw new CliError("OAuth discovery returned a non-JSON body.");
31
+ }
32
+ const b = typeof body === "object" && body !== null ? body : {};
33
+ const authorizationEndpoint = b.authorization_endpoint;
34
+ const tokenEndpoint = b.token_endpoint;
35
+ if (typeof authorizationEndpoint !== "string" || authorizationEndpoint.length === 0) {
36
+ throw new CliError("OAuth discovery document is missing an authorization_endpoint.");
37
+ }
38
+ if (typeof tokenEndpoint !== "string" || tokenEndpoint.length === 0) {
39
+ throw new CliError("OAuth discovery document is missing a token_endpoint.");
40
+ }
41
+ return { authorizationEndpoint, tokenEndpoint };
42
+ }
43
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/auth/discovery.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,sGAAsG;AACtG,oGAAoG;AACpG,0EAA0E;AAE1E,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,MAAM,cAAc,GAAG,yCAAyC,CAAC;AACjE,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAOpC;oGACoG;AACpG,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,YAAuB,KAAK;IAE5B,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC;IAEhE,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YACzB,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;YACvC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,oBAAoB,CAAC;SAClD,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,QAAQ,CAChB,iDAAiD,GAAG,IAAI,EACxD,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAC/C,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,QAAQ,CAChB,2BAA2B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,GAAG,EAC3D,wFAAwF,CACzF,CAAC;IACJ,CAAC;IAED,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,2CAA2C,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,CAAC,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAE,IAAgC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7F,MAAM,qBAAqB,GAAG,CAAC,CAAC,sBAAsB,CAAC;IACvD,MAAM,aAAa,GAAG,CAAC,CAAC,cAAc,CAAC;IACvC,IAAI,OAAO,qBAAqB,KAAK,QAAQ,IAAI,qBAAqB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,QAAQ,CAAC,gEAAgE,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,QAAQ,CAAC,uDAAuD,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { CliConfig } from "../config.js";
2
+ import type { CredentialStore, StoredSession } from "../credentials.js";
3
+ import { type FetchLike } from "./pkce.js";
4
+ export interface PerformLoginDeps {
5
+ config: CliConfig;
6
+ store: CredentialStore;
7
+ /** Opens a URL in the user's browser. Injected for tests; defaults to the `open` package. */
8
+ openBrowser?: (url: string) => Promise<void>;
9
+ fetchImpl?: FetchLike;
10
+ log?: (line: string) => void;
11
+ scope?: string;
12
+ }
13
+ export declare function performLogin(deps: PerformLoginDeps): Promise<StoredSession>;
@@ -0,0 +1,74 @@
1
+ // performLogin — orchestrates the browser PKCE handshake and persists the session.
2
+ //
3
+ // Flow: start the loopback callback server → open the issuer's /oauth/authorize in the browser →
4
+ // catch the redirect (validating state) → exchange the code at /oauth/token → store the session.
5
+ // Requires a configured OAuth application (public client id) whose redirect allowlist includes the
6
+ // loopback URI.
7
+ import { CliError } from "../errors.js";
8
+ import { buildAuthorizeUrl, exchangeCode, generatePkcePair, randomState, startLoopback, } from "./pkce.js";
9
+ import { discoverOAuth } from "./discovery.js";
10
+ /**
11
+ * openid+profile+email so the issued JWT carries the claims the backend JIT-provisions users from;
12
+ * `offline_access` so the IdP returns a refresh token (OIDC providers only mint one when it's
13
+ * requested) — without it every expired session would force a full re-login instead of a silent
14
+ * `resolveToken` refresh.
15
+ */
16
+ const DEFAULT_SCOPE = "openid profile email offline_access";
17
+ export async function performLogin(deps) {
18
+ const clientId = deps.config.oauthClientId;
19
+ if (clientId === null) {
20
+ throw new CliError("No OAuth client id configured for `boardwalk login`.", "Set BOARDWALK_OAUTH_CLIENT_ID to the OAuth application's public client id " +
21
+ `(its redirect allowlist must include http://127.0.0.1:${String(deps.config.loopbackPort)}/callback).`);
22
+ }
23
+ const endpoints = await discoverOAuth(deps.config.issuerUrl, deps.fetchImpl);
24
+ const { verifier, challenge } = generatePkcePair();
25
+ const state = randomState();
26
+ const log = deps.log ??
27
+ ((line) => {
28
+ console.log(line);
29
+ });
30
+ const loopback = await startLoopback(deps.config.loopbackPort);
31
+ try {
32
+ const url = buildAuthorizeUrl({
33
+ authorizeEndpoint: endpoints.authorizationEndpoint,
34
+ clientId,
35
+ redirectUri: loopback.redirectUri,
36
+ codeChallenge: challenge,
37
+ state,
38
+ scope: deps.scope ?? DEFAULT_SCOPE,
39
+ });
40
+ log("Opening your browser to sign in to Boardwalk…");
41
+ log(`If it doesn't open automatically, visit:\n ${url}`);
42
+ const openBrowser = deps.openBrowser ?? defaultOpenBrowser;
43
+ await openBrowser(url).catch(() => {
44
+ // Non-fatal: the user can paste the URL printed above.
45
+ });
46
+ const code = await loopback.awaitCode(state);
47
+ const token = await exchangeCode({
48
+ tokenEndpoint: endpoints.tokenEndpoint,
49
+ clientId,
50
+ code,
51
+ codeVerifier: verifier,
52
+ redirectUri: loopback.redirectUri,
53
+ fetchImpl: deps.fetchImpl,
54
+ });
55
+ const session = {
56
+ accessToken: token.accessToken,
57
+ refreshToken: token.refreshToken,
58
+ expiresAt: token.expiresAt,
59
+ clientId,
60
+ tokenEndpoint: endpoints.tokenEndpoint,
61
+ scope: token.scope,
62
+ };
63
+ deps.store.putSession(session);
64
+ return session;
65
+ }
66
+ finally {
67
+ loopback.close();
68
+ }
69
+ }
70
+ async function defaultOpenBrowser(url) {
71
+ const open = (await import("open")).default;
72
+ await open(url);
73
+ }
74
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}