@snappedly-tools/shipyard 0.4.2 → 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 +108 -66
- package/dist/{chunk-VDHZO5PP.js → chunk-BYXKYXK6.js} +62 -4
- package/dist/chunk-BYXKYXK6.js.map +1 -0
- package/dist/chunk-DP5MP533.js +925 -0
- package/dist/chunk-DP5MP533.js.map +1 -0
- package/dist/{chunk-2GT3OPO5.js → chunk-KPJFCYX5.js} +145 -6
- package/dist/chunk-KPJFCYX5.js.map +1 -0
- package/dist/index.js +3 -59
- package/dist/index.js.map +1 -1
- package/dist/main.js +1672 -69
- package/dist/main.js.map +1 -1
- package/dist/sandboxes/docker.js +2 -2
- package/package.json +3 -2
- package/dist/chunk-2GT3OPO5.js.map +0 -1
- package/dist/chunk-OCJKONZ7.js +0 -475
- package/dist/chunk-OCJKONZ7.js.map +0 -1
- package/dist/chunk-VDHZO5PP.js.map +0 -1
package/README.md
CHANGED
|
@@ -34,10 +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.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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`.
|
|
41
59
|
|
|
42
60
|
Run it:
|
|
43
61
|
|
|
@@ -53,44 +71,100 @@ image when its definition has not changed:
|
|
|
53
71
|
npx shipyard run --skip-build
|
|
54
72
|
```
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
## 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.
|
|
61
77
|
|
|
62
|
-
|
|
63
|
-
TypeScript:
|
|
78
|
+
## Repository runner for GitHub Issues
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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.
|
|
68
85
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
promptFile: ".shipyard/prompt.md",
|
|
73
|
-
});
|
|
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:
|
|
74
89
|
|
|
75
|
-
|
|
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
|
|
76
96
|
```
|
|
77
97
|
|
|
78
|
-
|
|
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:
|
|
79
129
|
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
promptFile: ".shipyard/prompt.md",
|
|
85
|
-
branchStrategy: { type: "branch", branch: "agent/my-task" },
|
|
86
|
-
maxIterations: 1,
|
|
87
|
-
logging: { type: "stdout" },
|
|
88
|
-
});
|
|
130
|
+
```sh
|
|
131
|
+
npx shipyard runner status
|
|
132
|
+
npx shipyard runner stop
|
|
133
|
+
npx shipyard runner remove
|
|
89
134
|
```
|
|
90
135
|
|
|
91
|
-
|
|
92
|
-
and
|
|
93
|
-
|
|
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.
|
|
94
168
|
|
|
95
169
|
To run directly on the host without isolation:
|
|
96
170
|
|
|
@@ -108,39 +182,6 @@ const result = await run({
|
|
|
108
182
|
No-sandbox mode grants the agent the permissions of the Shipyard process. Use
|
|
109
183
|
it only with trusted repositories and prompts.
|
|
110
184
|
|
|
111
|
-
## Branch behavior
|
|
112
|
-
|
|
113
|
-
Docker keeps the repository and Git metadata inside the sandbox. It starts
|
|
114
|
-
from committed history and syncs changes back. The generated blank
|
|
115
|
-
template omits `branchStrategy`, which defaults to `merge-to-head`: commits
|
|
116
|
-
are transferred to a temporary host branch and merged into your current
|
|
117
|
-
branch. Direct `head` mode is not supported by Docker or Vercel Sandbox.
|
|
118
|
-
|
|
119
|
-
The explicit `branch` strategy keeps commits on the named branch for review.
|
|
120
|
-
Commit input files before either strategy, or use `copyToWorktree` for specific
|
|
121
|
-
untracked or ignored files.
|
|
122
|
-
|
|
123
|
-
## Authentication
|
|
124
|
-
|
|
125
|
-
`shipyard init` generates the environment variables and mounts required by
|
|
126
|
-
the chosen agent. Keep `.shipyard/.env`, agent login files, logs, and recovery
|
|
127
|
-
patches private.
|
|
128
|
-
|
|
129
|
-
Use a subscription first when the selected agent supports it. If subscription
|
|
130
|
-
authentication does not work in your environment, use the API-key fallback:
|
|
131
|
-
|
|
132
|
-
- Codex subscription authentication: choose **Sign in with ChatGPT** during
|
|
133
|
-
interactive init and complete the browser login. The generated configuration
|
|
134
|
-
mounts `~/.codex/auth.json` read-only. For non-interactive init, run
|
|
135
|
-
`codex login` on the host, then pass `--codex-auth chatgpt`.
|
|
136
|
-
- Codex API authentication: choose **OpenAI API key** during interactive init
|
|
137
|
-
and put `OPENAI_API_KEY` in `.shipyard/.env`. For non-interactive init, pass
|
|
138
|
-
`--codex-auth api-key`.
|
|
139
|
-
- Claude Code subscription authentication: run `claude setup-token` on the
|
|
140
|
-
host and put the result in `CLAUDE_CODE_OAUTH_TOKEN` in `.shipyard/.env`.
|
|
141
|
-
- Claude Code API authentication: uncomment `ANTHROPIC_API_KEY` in
|
|
142
|
-
`.shipyard/.env` and put your API key there.
|
|
143
|
-
|
|
144
185
|
## Generated files
|
|
145
186
|
|
|
146
187
|
| Path | Purpose |
|
|
@@ -152,6 +193,7 @@ authentication does not work in your environment, use the API-key fallback:
|
|
|
152
193
|
| `.shipyard/logs/` | Run logs |
|
|
153
194
|
| `.shipyard/worktrees/` | Worktrees for separate-branch runs |
|
|
154
195
|
| `.shipyard/patches/` | Recovery artifacts preserved after some failures |
|
|
196
|
+
| `.shipyard/runner/` | Protected self-hosted runner files and diagnostics |
|
|
155
197
|
|
|
156
198
|
## Workflow templates
|
|
157
199
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Display } from './chunk-
|
|
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-
|
|
113
|
-
//# sourceMappingURL=chunk-
|
|
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"]}
|