@snappedly-tools/shipyard 0.4.1 → 0.4.4-staging.35699051936

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 CHANGED
@@ -34,9 +34,28 @@ cp .shipyard/.env.example .shipyard/.env
34
34
  ```
35
35
 
36
36
  `init` asks which agent, sandbox provider, and starter template to use. Its
37
- issue-based templates use GitHub Issues. Start with the `blank` template. Put
38
- the requested credentials in
39
- `.shipyard/.env`, then write one concrete task in `.shipyard/prompt.md`.
37
+ issue-based templates use GitHub Issues. Init always attempts to create or
38
+ update the lowercase `shipyard` label, and built-in issue workflows select only
39
+ open issues carrying that label. When you select Codex, init also asks whether
40
+ to sign in with ChatGPT or use an API key. Start with the `blank` template. Put
41
+ any requested credentials in `.shipyard/.env`, then write one concrete task in
42
+ `.shipyard/prompt.md`.
43
+
44
+ Use a subscription first when the selected agent supports it. If subscription
45
+ authentication does not work in your environment, use the API-key fallback:
46
+
47
+ - Codex subscription authentication: choose **Sign in with ChatGPT** during
48
+ interactive init and complete the browser login. The generated configuration
49
+ mounts `~/.codex/auth.json` read-only. For non-interactive init, run
50
+ `codex login` on the host, then pass `--codex-auth chatgpt`.
51
+ - Codex API authentication: choose **OpenAI API key** during interactive init
52
+ and put `OPENAI_API_KEY` in `.shipyard/.env`. For non-interactive init, pass
53
+ `--codex-auth api-key`.
54
+ - Claude Code subscription authentication: run `claude setup-token` on the
55
+ host and put the result in `CLAUDE_CODE_OAUTH_TOKEN` in `.shipyard/.env`.
56
+ - Claude Code API authentication: uncomment `ANTHROPIC_API_KEY` in
57
+ `.shipyard/.env` and put your API key there.
58
+ - GitHub Issues authentication: set `GH_TOKEN` and `GH_REPO` in `.shipyard/.env`.
40
59
 
41
60
  Run it:
42
61
 
@@ -52,44 +71,100 @@ image when its definition has not changed:
52
71
  npx shipyard run --skip-build
53
72
  ```
54
73
 
55
- Inspect the generated branch strategy and start from a clean working tree. A
56
- completion marker means the agent stopped; inspect its commits and run the
57
- repository's checks before keeping the result.
58
-
59
- ## JavaScript API
74
+ Shipyard supports Codex and Claude Code agents, with Docker, Vercel Sandbox,
75
+ and no-sandbox providers. `shipyard init` scaffolds Docker; configure Vercel
76
+ Sandbox or no-sandbox mode through the JavaScript interface.
60
77
 
61
- The generated `.shipyard/main.ts` or `.shipyard/main.mts` is ordinary
62
- TypeScript:
78
+ ## Repository runner for GitHub Issues
63
79
 
64
- ```ts
65
- import { CODEX_MODELS, codex, run } from "@snappedly-tools/shipyard";
66
- import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
80
+ Shipyard can keep one repository ready for labelled GitHub Issues on a
81
+ user-managed Mac. This deployment currently supports GitHub.com repositories
82
+ on Apple Silicon macOS only. It uses GitHub's official self-hosted Actions
83
+ runner as a wake-up transport; the foreground Shipyard controller owns the
84
+ actual work outside the Actions job.
67
85
 
68
- const result = await run({
69
- agent: codex(CODEX_MODELS.routine),
70
- sandbox: docker(),
71
- promptFile: ".shipyard/prompt.md",
72
- });
86
+ Prerequisites are an initialized repository, Docker Desktop, `gh` authenticated
87
+ to GitHub.com with repository-administration access, and the credentials needed
88
+ by the selected agent in `.shipyard/.env`. Install from the repository root:
73
89
 
74
- console.log(result.commits);
90
+ ```sh
91
+ npx shipyard runner install
92
+ git add .github/workflows/shipyard-wake.yml .shipyard/.gitignore
93
+ git commit -m "Add Shipyard wake workflow"
94
+ git push
95
+ npx shipyard runner start
75
96
  ```
76
97
 
77
- Use a dedicated branch when the scaffold has already been committed:
98
+ `--registration-token` can supply the one-time registration credential, but it
99
+ does not replace administrative `gh` access: Shipyard still uses that access to
100
+ verify that the repository has no conflicting `shipyard` runner.
101
+
102
+ Interactive `shipyard init` also offers installation after it creates a valid
103
+ scaffold, defaulting to No. Non-interactive init skips installation unless
104
+ `--install-runner true` is passed. Installation creates the workflow locally;
105
+ it never commits or pushes it. `runner start` requires the exact generated
106
+ workflow on the repository's default branch.
107
+
108
+ The activation label is the exact, lowercase label `shipyard`. Init attempts to
109
+ create it, and start creates it if it is missing. Adding that label or manually
110
+ dispatching the **Shipyard wake-up** workflow wakes the controller. Labels with
111
+ different casing do not match. Issue edits and comments do not wake it.
112
+
113
+ The controller first checks the current backlog, so restarting it recovers work
114
+ labelled while the Mac was off. It invokes exactly `npx shipyard run`, then
115
+ compares the eligible issue-number set. An empty set returns it to idle; a
116
+ changed nonempty set starts another finite invocation; an unchanged set records
117
+ no progress and returns it to idle instead of looping. A later label event,
118
+ manual dispatch, or restart retries that backlog. Wake-ups delivered while
119
+ Shipyard is busy are coalesced.
120
+
121
+ The Actions job only reports whether its wake-up reached the controller. It
122
+ does not report the later agent outcome and does not own the agent's runtime.
123
+ The controller stays available only while `runner start` remains open in its
124
+ foreground terminal. Ctrl-C, closing that terminal, or `runner stop` stops it
125
+ and cancels active work; it does not start at login. A nonzero Shipyard exit or
126
+ infrastructure failure records the error and takes the controller offline.
127
+
128
+ Use these commands from the same repository root:
78
129
 
79
- ```ts
80
- const result = await run({
81
- agent: codex(CODEX_MODELS.routine),
82
- sandbox: docker(),
83
- promptFile: ".shipyard/prompt.md",
84
- branchStrategy: { type: "branch", branch: "agent/my-task" },
85
- maxIterations: 1,
86
- logging: { type: "stdout" },
87
- });
130
+ ```sh
131
+ npx shipyard runner status
132
+ npx shipyard runner stop
133
+ npx shipyard runner remove
88
134
  ```
89
135
 
90
- Shipyard supports Codex and Claude Code agents, with Docker, Vercel Sandbox,
91
- and no-sandbox providers. `shipyard init` scaffolds Docker; configure Vercel
92
- Sandbox or no-sandbox mode through the JavaScript interface.
136
+ Status reports local process state, GitHub connectivity, repository identity,
137
+ and the last outcome without showing credentials. Runner binaries, credentials,
138
+ state, and diagnostics are protected and ignored under `.shipyard/runner/`;
139
+ normal removal unregisters the runner and deletes those files while preserving
140
+ the Shipyard config, workflow, issues, logs, and worktrees. If GitHub
141
+ unregistration is unavailable, retry after restoring access. As a last resort,
142
+ `runner remove --force` deletes local files and prints the GitHub registration
143
+ that must be removed manually.
144
+
145
+ There is no Mac sleep/wake detector. GitHub can discard a queued self-hosted job
146
+ after 24 hours, so after a long sleep use manual workflow dispatch or restart
147
+ the controller. GitHub removes self-hosted runner registrations after 14 days
148
+ offline; the next start automatically re-registers with the current
149
+ administrative `gh` login. A Mac rename does not rename an existing
150
+ registration. Remove and reinstall it explicitly.
151
+
152
+ GitHub does not charge Actions minutes for the self-hosted wake job. The brief
153
+ exact-case gate runs on `ubuntu-latest` and may consume hosted Actions minutes
154
+ for private repositories. Model costs apply only when a preflight finds eligible
155
+ issues and starts Shipyard; the host owner still pays for hardware, electricity,
156
+ network, Docker, and model usage. GitHub warns that self-hosted runners on public repositories can be
157
+ exposed to untrusted repository activity. Use this deployment only where label
158
+ authority, workflows, collaborators, and agent credentials have an acceptable
159
+ trust boundary. See GitHub's
160
+ [self-hosted runner security guidance](https://docs.github.com/en/actions/reference/security/secure-use#hardening-for-self-hosted-runners).
161
+
162
+ For setup failures, run `runner status`, inspect the foreground terminal and
163
+ `.shipyard/runner/.shipyard-last-failure.json`, then verify Docker, GitHub
164
+ authentication with `gh auth status --hostname github.com`, `.shipyard/.env`,
165
+ the lowercase label, and the published workflow. The
166
+ [Apple Silicon validation runbook](docs/runbooks/repository-runner-macos-validation.md)
167
+ covers the complete lifecycle.
93
168
 
94
169
  To run directly on the host without isolation:
95
170
 
@@ -107,37 +182,6 @@ const result = await run({
107
182
  No-sandbox mode grants the agent the permissions of the Shipyard process. Use
108
183
  it only with trusted repositories and prompts.
109
184
 
110
- ## Branch behavior
111
-
112
- Docker keeps the repository and Git metadata inside the sandbox. It starts
113
- from committed history and syncs changes back. The generated blank
114
- template omits `branchStrategy`, which defaults to `merge-to-head`: commits
115
- are transferred to a temporary host branch and merged into your current
116
- branch. Direct `head` mode is not supported by Docker or Vercel Sandbox.
117
-
118
- The explicit `branch` strategy keeps commits on the named branch for review.
119
- Commit input files before either strategy, or use `copyToWorktree` for specific
120
- untracked or ignored files.
121
-
122
- ## Authentication
123
-
124
- `shipyard init` generates the environment variables and mounts required by
125
- the chosen agent. Keep `.shipyard/.env`, agent login files, logs, and recovery
126
- patches private.
127
-
128
- Use a subscription first when the selected agent supports it. If subscription
129
- authentication does not work in your environment, use the API-key fallback:
130
-
131
- - Codex subscription authentication: run `codex login` on the host, then pass
132
- `--codex-auth chatgpt` to `shipyard init`. The generated configuration mounts
133
- `~/.codex/auth.json` read-only.
134
- - Codex API authentication: pass `--codex-auth api-key` and put
135
- `OPENAI_API_KEY` in `.shipyard/.env`.
136
- - Claude Code subscription authentication: run `claude setup-token` on the
137
- host and put the result in `CLAUDE_CODE_OAUTH_TOKEN` in `.shipyard/.env`.
138
- - Claude Code API authentication: uncomment `ANTHROPIC_API_KEY` in
139
- `.shipyard/.env` and put your API key there.
140
-
141
185
  ## Generated files
142
186
 
143
187
  | Path | Purpose |
@@ -149,6 +193,7 @@ authentication does not work in your environment, use the API-key fallback:
149
193
  | `.shipyard/logs/` | Run logs |
150
194
  | `.shipyard/worktrees/` | Worktrees for separate-branch runs |
151
195
  | `.shipyard/patches/` | Recovery artifacts preserved after some failures |
196
+ | `.shipyard/runner/` | Protected self-hosted runner files and diagnostics |
152
197
 
153
198
  ## Workflow templates
154
199
 
@@ -1,5 +1,7 @@
1
- import { Display } from './chunk-2GT3OPO5.js';
1
+ import { CONFIG_DIR, ConfigDirError, assertNoSymlinkComponents, Display } from './chunk-KPJFCYX5.js';
2
2
  import { Effect } from 'effect';
3
+ import { FileSystem } from '@effect/platform';
4
+ import { join } from 'path';
3
5
 
4
6
  var formatErrorMessage = (error) => {
5
7
  switch (error._tag) {
@@ -107,7 +109,63 @@ var CODEX_MODELS = {
107
109
  effort: readEffort("strong")
108
110
  }
109
111
  };
112
+ var parseEnvFile = (filePath) => Effect.gen(function* () {
113
+ const fs = yield* FileSystem.FileSystem;
114
+ const symlink = yield* fs.readLink(filePath).pipe(
115
+ Effect.map(() => true),
116
+ Effect.catchAll(() => Effect.succeed(false))
117
+ );
118
+ if (symlink) return {};
119
+ const content = yield* fs.readFileString(filePath).pipe(Effect.catchAll(() => Effect.succeed(null)));
120
+ if (content === null) return {};
121
+ const vars = {};
122
+ for (const line of content.split("\n")) {
123
+ const trimmed = line.trim();
124
+ if (!trimmed || trimmed.startsWith("#")) continue;
125
+ const eqIndex = trimmed.indexOf("=");
126
+ if (eqIndex === -1) continue;
127
+ const key = trimmed.slice(0, eqIndex).trim();
128
+ let value = trimmed.slice(eqIndex + 1).trim();
129
+ const isDoubleQuoted = value.length >= 2 && value[0] === '"' && value[value.length - 1] === '"';
130
+ const isSingleQuoted = value.length >= 2 && value[0] === "'" && value[value.length - 1] === "'";
131
+ if (isDoubleQuoted || isSingleQuoted) {
132
+ value = value.slice(1, -1);
133
+ }
134
+ if (isDoubleQuoted) {
135
+ value = value.replace(/\\([nrt\\])/g, (_, ch) => {
136
+ const escapes = {
137
+ n: "\n",
138
+ r: "\r",
139
+ t: " ",
140
+ "\\": "\\"
141
+ };
142
+ return escapes[ch] ?? ch;
143
+ });
144
+ }
145
+ vars[key] = value;
146
+ }
147
+ return vars;
148
+ });
149
+ var resolveEnv = (repoDir) => Effect.gen(function* () {
150
+ const configDir = join(repoDir, CONFIG_DIR);
151
+ const envPath = join(configDir, ".env");
152
+ yield* Effect.tryPromise({
153
+ try: () => assertNoSymlinkComponents(repoDir, envPath, "environment file"),
154
+ catch: (e) => new ConfigDirError({
155
+ message: `Refusing symlinked environment path: ${e instanceof Error ? e.message : String(e)}`
156
+ })
157
+ });
158
+ const shipyardEnv = yield* parseEnvFile(envPath);
159
+ const result = {};
160
+ for (const key of Object.keys(shipyardEnv)) {
161
+ const value = shipyardEnv[key] || process.env[key];
162
+ if (value) {
163
+ result[key] = value;
164
+ }
165
+ }
166
+ return result;
167
+ });
110
168
 
111
- export { CODEX_MODELS, CODEX_REASONING_EFFORTS, formatErrorMessage, withFriendlyErrors };
112
- //# sourceMappingURL=chunk-VDHZO5PP.js.map
113
- //# sourceMappingURL=chunk-VDHZO5PP.js.map
169
+ export { CODEX_MODELS, CODEX_REASONING_EFFORTS, formatErrorMessage, resolveEnv, withFriendlyErrors };
170
+ //# sourceMappingURL=chunk-BYXKYXK6.js.map
171
+ //# sourceMappingURL=chunk-BYXKYXK6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ErrorHandler.ts","../src/modelConfig.ts","../src/EnvResolver.ts"],"names":["Effect"],"mappings":"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","file":"chunk-BYXKYXK6.js","sourcesContent":["import { Effect } from \"effect\";\nimport { Display } from \"./Display.js\";\nimport type { SandboxError } from \"./errors.js\";\n\n/**\n * Formats a tagged SandboxError into a user-friendly message with\n * context-specific hints about what went wrong and how to recover.\n */\nexport const formatErrorMessage = (error: SandboxError): string => {\n switch (error._tag) {\n case \"ExecError\":\n return `Command failed in sandbox (${error.command}): ${error.message}`;\n case \"ExecHostError\":\n return `Command failed on host (${error.command}): ${error.message}`;\n case \"CopyError\":\n return `File copy failed: ${error.message}`;\n case \"DockerError\":\n return `Docker operation failed: ${error.message}. Is Docker running?`;\n case \"SyncError\":\n return `Git sync failed: ${error.message}`;\n case \"WorktreeError\":\n return `Git worktree operation failed: ${error.message}`;\n case \"PromptError\":\n return `Failed to resolve prompt: ${error.message}`;\n case \"AgentError\":\n return `Agent invocation failed: ${error.message}`;\n case \"ConfigDirError\":\n return `${error.message}`;\n case \"InitError\":\n return `${error.message}`;\n case \"AgentIdleTimeoutError\":\n case \"WorktreeTimeoutError\":\n case \"ContainerStartTimeoutError\":\n case \"CopyToWorktreeTimeoutError\":\n case \"CopyToWorktreeError\":\n case \"SyncInTimeoutError\":\n case \"HookTimeoutError\":\n case \"GitSetupTimeoutError\":\n case \"PromptExpansionTimeoutError\":\n case \"CommitCollectionTimeoutError\":\n case \"MergeToHostTimeoutError\":\n case \"SessionCaptureError\":\n case \"CwdError\":\n return error.message;\n }\n};\n\nconst showErrorAndExit = (error: SandboxError) =>\n Effect.gen(function* () {\n const d = yield* Display;\n yield* d.status(formatErrorMessage(error), \"error\");\n return yield* Effect.sync(() => process.exit(1) as never);\n });\n\n/**\n * Wraps an effect so that any SandboxError is caught, displayed via the\n * Display service as an error-severity status message, then exits the process\n * with code 1. Non-SandboxError errors pass through unchanged.\n */\nexport const withFriendlyErrors = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.Effect<A, Exclude<E, SandboxError>, R | Display> =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Effect.catchTags(effect as Effect.Effect<A, SandboxError, R>, {\n ExecError: showErrorAndExit,\n ExecHostError: showErrorAndExit,\n CopyError: showErrorAndExit,\n DockerError: showErrorAndExit,\n SyncError: showErrorAndExit,\n WorktreeError: showErrorAndExit,\n PromptError: showErrorAndExit,\n AgentError: showErrorAndExit,\n ConfigDirError: showErrorAndExit,\n InitError: showErrorAndExit,\n AgentIdleTimeoutError: showErrorAndExit,\n WorktreeTimeoutError: showErrorAndExit,\n ContainerStartTimeoutError: showErrorAndExit,\n CopyToWorktreeTimeoutError: showErrorAndExit,\n CopyToWorktreeError: showErrorAndExit,\n SyncInTimeoutError: showErrorAndExit,\n HookTimeoutError: showErrorAndExit,\n GitSetupTimeoutError: showErrorAndExit,\n PromptExpansionTimeoutError: showErrorAndExit,\n CommitCollectionTimeoutError: showErrorAndExit,\n MergeToHostTimeoutError: showErrorAndExit,\n SessionCaptureError: showErrorAndExit,\n CwdError: showErrorAndExit,\n }) as Effect.Effect<A, Exclude<E, SandboxError>, R | Display>;\n","/** Reasoning efforts accepted by the Codex CLI. */\nexport const CODEX_REASONING_EFFORTS = [\n \"low\",\n \"medium\",\n \"high\",\n \"xhigh\",\n \"max\",\n] as const;\n\nexport type CodexReasoningEffort = (typeof CODEX_REASONING_EFFORTS)[number];\n\n/** A model identifier plus the reasoning policy used for that model role. */\nexport interface CodexModelConfig {\n readonly model: string;\n readonly effort: CodexReasoningEffort;\n}\n\n/**\n * Default values for Shipyard's built-in Codex roles.\n *\n * Runtime environment variables override these defaults:\n * SHIPYARD_CODEX_ROUTINE_MODEL\n * SHIPYARD_CODEX_ROUTINE_REASONING_EFFORT\n * SHIPYARD_CODEX_STRONG_MODEL\n * SHIPYARD_CODEX_STRONG_REASONING_EFFORT\n *\n * Keep the defaults here so runtime providers, generated templates, and the\n * repository workflows all use the same role definitions.\n */\nconst DEFAULT_CODEX_MODELS = {\n routine: { model: \"gpt-5.6-luna\", effort: \"max\" },\n strong: { model: \"gpt-5.6-sol\", effort: \"max\" },\n} as const satisfies Record<string, CodexModelConfig>;\n\nconst readModel = (role: keyof typeof DEFAULT_CODEX_MODELS): string =>\n process.env[`SHIPYARD_CODEX_${role.toUpperCase()}_MODEL`]?.trim() ||\n DEFAULT_CODEX_MODELS[role].model;\n\nconst readEffort = (\n role: keyof typeof DEFAULT_CODEX_MODELS,\n): CodexReasoningEffort => {\n const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;\n const value = process.env[envName]?.trim();\n if (!value) return DEFAULT_CODEX_MODELS[role].effort;\n if ((CODEX_REASONING_EFFORTS as readonly string[]).includes(value)) {\n return value as CodexReasoningEffort;\n }\n throw new Error(\n `${envName} must be one of ${CODEX_REASONING_EFFORTS.join(\", \")}; received \"${value}\"`,\n );\n};\n\nexport const CODEX_MODELS = {\n routine: {\n model: readModel(\"routine\"),\n effort: readEffort(\"routine\"),\n },\n strong: {\n model: readModel(\"strong\"),\n effort: readEffort(\"strong\"),\n },\n} as const satisfies Record<string, CodexModelConfig>;\n","import { FileSystem } from \"@effect/platform\";\nimport { Effect } from \"effect\";\nimport { join } from \"node:path\";\n\nimport { ConfigDirError } from \"./errors.js\";\nimport { assertNoSymlinkComponents } from \"./pathSecurity.js\";\nimport { CONFIG_DIR } from \"./runtimeNames.js\";\n\nconst parseEnvFile = (\n filePath: string,\n): Effect.Effect<Record<string, string>, never, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const symlink = yield* fs.readLink(filePath).pipe(\n Effect.map(() => true),\n Effect.catchAll(() => Effect.succeed(false)),\n );\n // A repository-controlled symlink could redirect this read to a host\n // secret and then expose matching keys to the agent.\n if (symlink) return {};\n const content = yield* fs\n .readFileString(filePath)\n .pipe(Effect.catchAll(() => Effect.succeed(null)));\n if (content === null) return {};\n const vars: Record<string, string> = {};\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n const isDoubleQuoted =\n value.length >= 2 &&\n value[0] === '\"' &&\n value[value.length - 1] === '\"';\n const isSingleQuoted =\n value.length >= 2 &&\n value[0] === \"'\" &&\n value[value.length - 1] === \"'\";\n if (isDoubleQuoted || isSingleQuoted) {\n value = value.slice(1, -1);\n }\n if (isDoubleQuoted) {\n value = value.replace(/\\\\([nrt\\\\])/g, (_, ch: string) => {\n const escapes: Record<string, string> = {\n n: \"\\n\",\n r: \"\\r\",\n t: \"\\t\",\n \"\\\\\": \"\\\\\",\n };\n return escapes[ch] ?? ch;\n });\n }\n vars[key] = value;\n }\n return vars;\n });\n\n/**\n * Resolve all env vars from .env files with process.env fallback.\n *\n * Precedence: .shipyard/.env > process.env\n * Only keys declared in .shipyard/.env are resolved from process.env.\n * Repo root .env is not part of the resolution chain.\n */\nexport const resolveEnv = (\n repoDir: string,\n): Effect.Effect<\n Record<string, string>,\n ConfigDirError,\n FileSystem.FileSystem\n> =>\n Effect.gen(function* () {\n const configDir = join(repoDir, CONFIG_DIR);\n const envPath = join(configDir, \".env\");\n yield* Effect.tryPromise({\n try: () =>\n assertNoSymlinkComponents(repoDir, envPath, \"environment file\"),\n catch: (e) =>\n new ConfigDirError({\n message: `Refusing symlinked environment path: ${e instanceof Error ? e.message : String(e)}`,\n }),\n });\n const shipyardEnv = yield* parseEnvFile(envPath);\n\n const result: Record<string, string> = {};\n for (const key of Object.keys(shipyardEnv)) {\n // A blank placeholder falls back to process.env, so callers can source\n // credentials from a host login for one invocation, e.g.\n // `GH_TOKEN=\"$(gh auth token)\" npx shipyard run`. Only keys declared in\n // .shipyard/.env are eligible for this fallback.\n const value = shipyardEnv[key] || process.env[key];\n if (value) {\n result[key] = value;\n }\n }\n\n return result;\n });\n"]}