@snappedly-tools/shipyard 0.8.0 → 0.9.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.
- package/README.md +24 -14
- package/dist/MountConfig-K5ILnfht.d.ts +26 -0
- package/dist/{MountConfig-BHnKnA4h.d.ts → SandboxProvider-oUAwYlWm.d.ts} +1 -26
- package/dist/{chunk-L6PX5QTU.js → chunk-57EEKW3R.js} +57 -55
- package/dist/chunk-57EEKW3R.js.map +1 -0
- package/dist/{chunk-JI3HDDMS.js → chunk-HXSZM52J.js} +3 -3
- package/dist/{chunk-JI3HDDMS.js.map → chunk-HXSZM52J.js.map} +1 -1
- package/dist/{chunk-44I2BL6E.js → chunk-JZUBT4WG.js} +20 -4
- package/dist/chunk-JZUBT4WG.js.map +1 -0
- package/dist/chunk-NQRFVKCU.js +1840 -0
- package/dist/chunk-NQRFVKCU.js.map +1 -0
- package/dist/chunk-Z5C4LHVP.js +137 -0
- package/dist/chunk-Z5C4LHVP.js.map +1 -0
- package/dist/createSandbox-DmbnWAZv.d.ts +739 -0
- package/dist/index.d.ts +7 -2534
- package/dist/index.js +216 -7269
- package/dist/index.js.map +1 -1
- package/dist/integrations/github.d.ts +52 -0
- package/dist/integrations/github.js +1049 -0
- package/dist/integrations/github.js.map +1 -0
- package/dist/integrations/releases.d.ts +136 -0
- package/dist/integrations/releases.js +500 -0
- package/dist/integrations/releases.js.map +1 -0
- package/dist/main.js +641 -383
- package/dist/main.js.map +1 -1
- package/dist/publication-BPoy_M9M.d.ts +1200 -0
- package/dist/sandboxes/docker.d.ts +2 -1
- package/dist/sandboxes/docker.js +2 -2
- package/dist/templates/parallel-planner/main.mts +11 -7
- package/dist/templates/parallel-planner/setup.sh +1 -0
- package/dist/templates/parallel-planner-with-review/main.mts +11 -7
- package/dist/templates/parallel-planner-with-review/setup.sh +1 -0
- package/dist/templates/sequential-reviewer/main.mts +7 -3
- package/dist/templates/sequential-reviewer/setup.sh +1 -0
- package/dist/templates/shared/setup.sh +1 -0
- package/dist/templates/simple-loop/main.mts +7 -3
- package/dist/templates/simple-loop/setup.sh +1 -0
- package/dist/workflow/coordinator/migrations/002_workflow_phase_records.sql +11 -0
- package/dist/workflow/coordinator/migrations/003_phase_record_schema_version.sql +6 -0
- package/dist/workflow.d.ts +472 -0
- package/dist/workflow.js +4345 -0
- package/dist/workflow.js.map +1 -0
- package/package.json +13 -1
- package/dist/chunk-44I2BL6E.js.map +0 -1
- package/dist/chunk-L6PX5QTU.js.map +0 -1
package/dist/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { CODEX_MODELS, purgeRunLogs, withFriendlyErrors, DEFAULT_LOG_RETENTION_DAYS, resolveEnv } from './chunk-
|
|
3
|
-
import { buildImage,
|
|
4
|
-
import { SANDBOX_REPO_DIR, Display, InitError, ACTIVATION_LABEL, PRODUCT_NAME, CONFIG_DIR, CLI_NAME, ClackDisplay, ConfigDirError, ExecHostError, LOCKS_DIR,
|
|
2
|
+
import { CODEX_MODELS, purgeRunLogs, withFriendlyErrors, DEFAULT_LOG_RETENTION_DAYS, resolveEnv } from './chunk-HXSZM52J.js';
|
|
3
|
+
import { buildImage, RunnerLifecycleError, removeRepositoryRunner, removeImage, defaultImageName, validateExistingRepositoryRunner, RUNNER_INSTALL_METADATA, RUNNER_CONTROLLER_LOCK, lockOwnsProcess, recoverRepositoryRunner, REPOSITORY_RUNNER_OWNER_ENV, removeOwnedRepositoryRunnerContainers, RUNNER_WORK_DIR } from './chunk-JZUBT4WG.js';
|
|
4
|
+
import { SANDBOX_REPO_DIR, Display, InitError, ACTIVATION_LABEL, PRODUCT_NAME, CONFIG_DIR, CLI_NAME, RUNNER_DIR, ClackDisplay, ConfigDirError, ExecHostError, LOCKS_DIR, assertProtectedDirectoryIdentities, repositoryRunnerEnvironment } from './chunk-57EEKW3R.js';
|
|
5
5
|
import { NodeContext, NodeRuntime, NodeFileSystem } from '@effect/platform-node';
|
|
6
6
|
import { Effect, Layer, Option, Exit, Cause } from 'effect';
|
|
7
7
|
import { Options, Command } from '@effect/cli';
|
|
@@ -14,7 +14,7 @@ import { FileSystem } from '@effect/platform';
|
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
15
|
import { hostname, homedir } from 'os';
|
|
16
16
|
import { createHash } from 'crypto';
|
|
17
|
-
import { chmod, lstat, realpath,
|
|
17
|
+
import { chmod, rm, lstat, realpath, mkdir, writeFile, readFile, access } from 'fs/promises';
|
|
18
18
|
import 'module';
|
|
19
19
|
|
|
20
20
|
var configDirExists = (repoDir) => Effect.gen(function* () {
|
|
@@ -101,6 +101,12 @@ var LOCKFILES = [
|
|
|
101
101
|
["yarn.lock", "yarn"],
|
|
102
102
|
["package-lock.json", "npm"]
|
|
103
103
|
];
|
|
104
|
+
var DEPENDENCY_COMMANDS = {
|
|
105
|
+
npm: { add: "install", remove: "uninstall" },
|
|
106
|
+
pnpm: { add: "add", remove: "remove" },
|
|
107
|
+
yarn: { add: "add", remove: "remove" },
|
|
108
|
+
bun: { add: "add", remove: "remove" }
|
|
109
|
+
};
|
|
104
110
|
var detectPackageManager = (repoDir) => Effect.gen(function* () {
|
|
105
111
|
const fs = yield* FileSystem.FileSystem;
|
|
106
112
|
const pkgPath = join(repoDir, "package.json");
|
|
@@ -124,18 +130,8 @@ var detectPackageManager = (repoDir) => Effect.gen(function* () {
|
|
|
124
130
|
}
|
|
125
131
|
return "npm";
|
|
126
132
|
});
|
|
127
|
-
var addDependencyCommand = (packageManager, pkg) => {
|
|
128
|
-
|
|
129
|
-
case "pnpm":
|
|
130
|
-
return `pnpm add ${pkg}`;
|
|
131
|
-
case "yarn":
|
|
132
|
-
return `yarn add ${pkg}`;
|
|
133
|
-
case "bun":
|
|
134
|
-
return `bun add ${pkg}`;
|
|
135
|
-
case "npm":
|
|
136
|
-
return `npm install ${pkg}`;
|
|
137
|
-
}
|
|
138
|
-
};
|
|
133
|
+
var addDependencyCommand = (packageManager, pkg) => `${packageManager} ${DEPENDENCY_COMMANDS[packageManager].add} ${pkg}`;
|
|
134
|
+
var removeDependencyCommand = (packageManager, pkg) => `${packageManager} ${DEPENDENCY_COMMANDS[packageManager].remove} ${pkg}`;
|
|
139
135
|
var hostHasDependency = (repoDir, pkg) => Effect.gen(function* () {
|
|
140
136
|
const fs = yield* FileSystem.FileSystem;
|
|
141
137
|
const pkgPath = join(repoDir, "package.json");
|
|
@@ -271,11 +267,9 @@ var ISSUE_TRACKER_REGISTRY = [
|
|
|
271
267
|
# Required repository permissions: Contents, Issues, and Pull requests (Read and write); Metadata (Read)
|
|
272
268
|
# Or leave blank and run: GH_TOKEN="$(gh auth token)" npx shipyard run
|
|
273
269
|
GH_TOKEN=
|
|
274
|
-
|
|
275
|
-
GH_REPO=`
|
|
270
|
+
`
|
|
276
271
|
}
|
|
277
272
|
];
|
|
278
|
-
var listIssueTrackers = () => ISSUE_TRACKER_REGISTRY;
|
|
279
273
|
var getIssueTracker = (name) => ISSUE_TRACKER_REGISTRY.find((b) => b.name === name);
|
|
280
274
|
var getAgent = (name) => AGENT_REGISTRY.find((a) => a.name === name);
|
|
281
275
|
var SANDBOX_PROVIDER_REGISTRY = [
|
|
@@ -295,7 +289,7 @@ function getNextStepsLines() {
|
|
|
295
289
|
"2. If using a model subscription, sign in. For Codex:",
|
|
296
290
|
` codex --config 'cli_auth_credentials_store="file"' login`,
|
|
297
291
|
" test -f ~/.codex/auth.json",
|
|
298
|
-
`3.
|
|
292
|
+
`3. If the runner installed successfully, start it with \`npx ${CLI_NAME} runner start\`; otherwise run \`npx ${CLI_NAME} run\`.`
|
|
299
293
|
];
|
|
300
294
|
}
|
|
301
295
|
function getTemplatesDir() {
|
|
@@ -358,8 +352,8 @@ var rewriteMainTs = (configDir, agent, model, modelExplicit, sandboxProvider, ma
|
|
|
358
352
|
}
|
|
359
353
|
if (agent.name === "codex" && codexAuth === "chatgpt") {
|
|
360
354
|
content = content.replace(
|
|
361
|
-
|
|
362
|
-
`
|
|
355
|
+
"const sandboxAuthOptions = {};",
|
|
356
|
+
`const sandboxAuthOptions = ${CODEX_CHATGPT_AUTH_OPTIONS};`
|
|
363
357
|
);
|
|
364
358
|
}
|
|
365
359
|
content = content.replace(/\bdocker\b/g, sandboxProvider.name);
|
|
@@ -564,11 +558,6 @@ var ensureCodexChatGptAuth = (options) => {
|
|
|
564
558
|
});
|
|
565
559
|
}
|
|
566
560
|
};
|
|
567
|
-
var runEffectPromise = async (effect) => {
|
|
568
|
-
const exit = await Effect.runPromiseExit(effect);
|
|
569
|
-
if (Exit.isFailure(exit)) throw Cause.squash(exit.cause);
|
|
570
|
-
return exit.value;
|
|
571
|
-
};
|
|
572
561
|
var REPOSITORY_RUNNER_WORKFLOW_PATH = ".github/workflows/shipyard-wake.yml";
|
|
573
562
|
var REPOSITORY_RUNNER_WAKE_EXECUTABLE = "shipyard-wake";
|
|
574
563
|
var REPOSITORY_RUNNER_WORKFLOW = `name: Shipyard wake-up
|
|
@@ -1274,31 +1263,58 @@ var installRepositoryRunner = async (options, adapters = defaultAdapters) => {
|
|
|
1274
1263
|
};
|
|
1275
1264
|
|
|
1276
1265
|
// src/InitRepositoryRunner.ts
|
|
1277
|
-
var initializeRepositoryRunner = async (options) => {
|
|
1278
|
-
let shouldInstall = options.requested;
|
|
1279
|
-
if (shouldInstall === void 0) {
|
|
1280
|
-
shouldInstall = options.interactive ? await options.confirm({
|
|
1281
|
-
message: "Install a foreground repository runner for labelled GitHub issues?",
|
|
1282
|
-
initialValue: true
|
|
1283
|
-
}) : false;
|
|
1284
|
-
}
|
|
1285
|
-
if (!shouldInstall) return { status: "declined" };
|
|
1286
|
-
try {
|
|
1287
|
-
return { status: "installed", result: await options.install() };
|
|
1288
|
-
} catch (error) {
|
|
1289
|
-
return {
|
|
1290
|
-
status: "failed",
|
|
1291
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1292
|
-
};
|
|
1293
|
-
}
|
|
1294
|
-
};
|
|
1295
1266
|
var repositoryRunnerNextSteps = () => [
|
|
1296
|
-
`
|
|
1267
|
+
`Ensure ${REPOSITORY_RUNNER_WORKFLOW_PATH} is committed and pushed to the repository's default branch`,
|
|
1297
1268
|
`Keep the foreground controller open with \`npx ${CLI_NAME} runner start\``,
|
|
1298
1269
|
`Inspect it from another terminal with \`npx ${CLI_NAME} runner status\``,
|
|
1299
1270
|
`Stop it with \`npx ${CLI_NAME} runner stop\``,
|
|
1300
1271
|
`Unregister and delete its protected files with \`npx ${CLI_NAME} runner remove\``
|
|
1301
1272
|
];
|
|
1273
|
+
var errorText = (error) => {
|
|
1274
|
+
if (error !== null && typeof error === "object" && "stderr" in error) {
|
|
1275
|
+
const stderr = error.stderr;
|
|
1276
|
+
if (typeof stderr === "string" || Buffer.isBuffer(stderr)) {
|
|
1277
|
+
const text = stderr.toString().trim();
|
|
1278
|
+
if (text) return text;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return error instanceof Error ? error.message : String(error);
|
|
1282
|
+
};
|
|
1283
|
+
var commitAndPushInitSetup = (repoDir) => {
|
|
1284
|
+
const branch = execFileSync("git", ["branch", "--show-current"], {
|
|
1285
|
+
cwd: repoDir,
|
|
1286
|
+
encoding: "utf8"
|
|
1287
|
+
}).trim();
|
|
1288
|
+
if (!branch) {
|
|
1289
|
+
throw new Error("Cannot push the Shipyard setup from a detached HEAD.");
|
|
1290
|
+
}
|
|
1291
|
+
const paths = [CONFIG_DIR];
|
|
1292
|
+
if (existsSync(join(repoDir, REPOSITORY_RUNNER_WORKFLOW_PATH))) {
|
|
1293
|
+
paths.push(REPOSITORY_RUNNER_WORKFLOW_PATH);
|
|
1294
|
+
}
|
|
1295
|
+
execFileSync("git", ["add", "--", ...paths], {
|
|
1296
|
+
cwd: repoDir,
|
|
1297
|
+
stdio: "ignore"
|
|
1298
|
+
});
|
|
1299
|
+
execFileSync(
|
|
1300
|
+
"git",
|
|
1301
|
+
["commit", "--only", "-m", "Add Shipyard setup", "--", ...paths],
|
|
1302
|
+
{ cwd: repoDir, stdio: "pipe" }
|
|
1303
|
+
);
|
|
1304
|
+
const commit = execFileSync("git", ["rev-parse", "--short", "HEAD"], {
|
|
1305
|
+
cwd: repoDir,
|
|
1306
|
+
encoding: "utf8"
|
|
1307
|
+
}).trim();
|
|
1308
|
+
try {
|
|
1309
|
+
execFileSync("git", ["push", "origin", `HEAD:refs/heads/${branch}`], {
|
|
1310
|
+
cwd: repoDir,
|
|
1311
|
+
stdio: "pipe"
|
|
1312
|
+
});
|
|
1313
|
+
return { branch, commit };
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
return { branch, commit, pushError: errorText(error) };
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1302
1318
|
var execFileAsync2 = promisify(execFile);
|
|
1303
1319
|
var CONTROLLER_STATE = ".shipyard-state.json";
|
|
1304
1320
|
var LAST_FAILURE = ".shipyard-last-failure.json";
|
|
@@ -1657,7 +1673,6 @@ var stopChildBounded = async (child, pause) => {
|
|
|
1657
1673
|
);
|
|
1658
1674
|
}
|
|
1659
1675
|
};
|
|
1660
|
-
var lockOwnsProcess = async (lock, adapters) => lock !== void 0 && Number.isInteger(lock.pid) && typeof lock.processStartedAt === "string" && lock.processStartedAt.length > 0 && adapters.isProcessRunning(lock.pid) && await adapters.processIdentity(lock.pid) === lock.processStartedAt;
|
|
1661
1676
|
var bestEffort = async (operation) => {
|
|
1662
1677
|
try {
|
|
1663
1678
|
await operation();
|
|
@@ -2199,6 +2214,11 @@ var stopRepositoryRunner = async (options, adapters = defaultAdapters2) => {
|
|
|
2199
2214
|
}
|
|
2200
2215
|
return { pid: lock.pid };
|
|
2201
2216
|
};
|
|
2217
|
+
var runEffectPromise = async (effect) => {
|
|
2218
|
+
const exit = await Effect.runPromiseExit(effect);
|
|
2219
|
+
if (Exit.isFailure(exit)) throw Cause.squash(exit.cause);
|
|
2220
|
+
return exit.value;
|
|
2221
|
+
};
|
|
2202
2222
|
|
|
2203
2223
|
// src/RepositoryRunnerCommands.ts
|
|
2204
2224
|
var registrationTokenOption = Options.text("registration-token").pipe(
|
|
@@ -2367,7 +2387,78 @@ var runnerCommand = Command.make(
|
|
|
2367
2387
|
purgeRunnerCommand
|
|
2368
2388
|
])
|
|
2369
2389
|
);
|
|
2370
|
-
var VERSION = "0.
|
|
2390
|
+
var VERSION = "0.9.0" ;
|
|
2391
|
+
var SHIPYARD_PACKAGE_NAME = "@snappedly-tools/shipyard";
|
|
2392
|
+
var isMissingPath = (error) => error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
2393
|
+
var lstatIfPresent = async (path) => {
|
|
2394
|
+
try {
|
|
2395
|
+
return await lstat(path);
|
|
2396
|
+
} catch (error) {
|
|
2397
|
+
if (isMissingPath(error)) return void 0;
|
|
2398
|
+
throw error;
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
var inspectShipyardConfigDirectory = async (repoDir) => {
|
|
2402
|
+
const configDir = join(repoDir, CONFIG_DIR);
|
|
2403
|
+
const info = await lstatIfPresent(configDir);
|
|
2404
|
+
if (!info) return false;
|
|
2405
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
2406
|
+
throw new Error(
|
|
2407
|
+
`Refusing to uninstall because ${configDir} is not a real directory.`
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2410
|
+
return true;
|
|
2411
|
+
};
|
|
2412
|
+
var removeWakeWorkflow = async (repoDir) => {
|
|
2413
|
+
const githubDir = join(repoDir, ".github");
|
|
2414
|
+
const workflowsDir = join(githubDir, "workflows");
|
|
2415
|
+
const workflowPath = join(repoDir, REPOSITORY_RUNNER_WORKFLOW_PATH);
|
|
2416
|
+
const githubInfo = await lstatIfPresent(githubDir);
|
|
2417
|
+
if (!githubInfo) return { removed: false, preserved: false };
|
|
2418
|
+
if (!githubInfo.isDirectory() || githubInfo.isSymbolicLink()) {
|
|
2419
|
+
return { removed: false, preserved: githubInfo.isSymbolicLink() };
|
|
2420
|
+
}
|
|
2421
|
+
const workflowsInfo = await lstatIfPresent(workflowsDir);
|
|
2422
|
+
if (!workflowsInfo) return { removed: false, preserved: false };
|
|
2423
|
+
if (!workflowsInfo.isDirectory() || workflowsInfo.isSymbolicLink()) {
|
|
2424
|
+
return {
|
|
2425
|
+
removed: false,
|
|
2426
|
+
preserved: workflowsInfo.isSymbolicLink()
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
const workflowInfo = await lstatIfPresent(workflowPath);
|
|
2430
|
+
if (!workflowInfo) return { removed: false, preserved: false };
|
|
2431
|
+
if (!workflowInfo.isFile() || workflowInfo.isSymbolicLink()) {
|
|
2432
|
+
return { removed: false, preserved: true };
|
|
2433
|
+
}
|
|
2434
|
+
const workflow = await readFile(workflowPath, "utf8");
|
|
2435
|
+
if (workflow !== REPOSITORY_RUNNER_WORKFLOW) {
|
|
2436
|
+
return { removed: false, preserved: true };
|
|
2437
|
+
}
|
|
2438
|
+
await rm(workflowPath);
|
|
2439
|
+
return { removed: true, preserved: false };
|
|
2440
|
+
};
|
|
2441
|
+
var removeShipyardRepositoryFiles = async (options) => {
|
|
2442
|
+
const configDirExists2 = await inspectShipyardConfigDirectory(options.repoDir);
|
|
2443
|
+
if (configDirExists2) {
|
|
2444
|
+
const configDir = join(options.repoDir, CONFIG_DIR);
|
|
2445
|
+
const runnerDir = join(configDir, RUNNER_DIR);
|
|
2446
|
+
if (await lstatIfPresent(runnerDir)) {
|
|
2447
|
+
throw new Error(
|
|
2448
|
+
`Remove the repository runner at ${runnerDir} before removing Shipyard configuration.`
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
const workflow = await removeWakeWorkflow(options.repoDir);
|
|
2453
|
+
if (configDirExists2) {
|
|
2454
|
+
await rm(join(options.repoDir, CONFIG_DIR), { recursive: true });
|
|
2455
|
+
}
|
|
2456
|
+
return {
|
|
2457
|
+
workflowRemoved: workflow.removed,
|
|
2458
|
+
workflowPreserved: workflow.preserved,
|
|
2459
|
+
configDirectoryRemoved: configDirExists2
|
|
2460
|
+
};
|
|
2461
|
+
};
|
|
2371
2462
|
|
|
2372
2463
|
// src/cli.ts
|
|
2373
2464
|
var imageNameOption = Options.text("image-name").pipe(
|
|
@@ -2383,7 +2474,6 @@ var defaultUidBuildArgs = () => {
|
|
|
2383
2474
|
if (gid !== void 0) args.AGENT_GID = String(gid);
|
|
2384
2475
|
return args;
|
|
2385
2476
|
};
|
|
2386
|
-
var requireConfigDir = (cwd) => requireCanonicalConfigDir(cwd);
|
|
2387
2477
|
var purgeRunLogsBestEffort = (display, repoDir) => Effect.tryPromise({
|
|
2388
2478
|
try: () => purgeRunLogs({
|
|
2389
2479
|
repoDir,
|
|
@@ -2503,7 +2593,7 @@ var runCommand2 = Command.make(
|
|
|
2503
2593
|
({ imageName: imageNameFlag, sandbox, skipBuild, entrypoint }) => Effect.gen(function* () {
|
|
2504
2594
|
const d = yield* Display;
|
|
2505
2595
|
const cwd = process.cwd();
|
|
2506
|
-
const configDir = yield*
|
|
2596
|
+
const configDir = yield* requireCanonicalConfigDir(cwd);
|
|
2507
2597
|
yield* purgeRunLogsBestEffort(d, cwd);
|
|
2508
2598
|
const resolvedEntrypoint = resolveRunEntrypoint(
|
|
2509
2599
|
cwd,
|
|
@@ -2562,14 +2652,6 @@ var sandboxOption = Options.text("sandbox").pipe(
|
|
|
2562
2652
|
Options.withDescription("Sandbox provider to use (docker)"),
|
|
2563
2653
|
Options.optional
|
|
2564
2654
|
);
|
|
2565
|
-
var issueTrackerOption = Options.text("issue-tracker").pipe(
|
|
2566
|
-
Options.withDescription("Issue tracker to use (github-issues)"),
|
|
2567
|
-
Options.optional
|
|
2568
|
-
);
|
|
2569
|
-
var buildImageOption = Options.choice("build-image", ["true", "false"]).pipe(
|
|
2570
|
-
Options.withDescription("Whether to build the sandbox image now"),
|
|
2571
|
-
Options.optional
|
|
2572
|
-
);
|
|
2573
2655
|
var installTemplateDepsOption = Options.choice("install-template-deps", [
|
|
2574
2656
|
"true",
|
|
2575
2657
|
"false"
|
|
@@ -2579,14 +2661,18 @@ var installTemplateDepsOption = Options.choice("install-template-deps", [
|
|
|
2579
2661
|
),
|
|
2580
2662
|
Options.optional
|
|
2581
2663
|
);
|
|
2582
|
-
var
|
|
2583
|
-
"true",
|
|
2584
|
-
"false"
|
|
2585
|
-
]).pipe(
|
|
2664
|
+
var commitSetupOption = Options.choice("commit-setup", ["true", "false"]).pipe(
|
|
2586
2665
|
Options.withDescription(
|
|
2587
|
-
"Whether to
|
|
2588
|
-
)
|
|
2589
|
-
|
|
2666
|
+
"Whether to commit and push .shipyard/ and any generated runner workflow"
|
|
2667
|
+
)
|
|
2668
|
+
).pipe(Options.optional);
|
|
2669
|
+
var uninstallYesOption = Options.boolean("yes").pipe(
|
|
2670
|
+
Options.withDescription("confirm uninstall without an interactive prompt")
|
|
2671
|
+
);
|
|
2672
|
+
var uninstallForceOption = Options.boolean("force").pipe(
|
|
2673
|
+
Options.withDescription(
|
|
2674
|
+
"remove local runner files if GitHub unregistration fails"
|
|
2675
|
+
)
|
|
2590
2676
|
);
|
|
2591
2677
|
var choiceToTriBool = (opt) => opt._tag === "Some" ? Option.some(opt.value === "true") : Option.none();
|
|
2592
2678
|
var initCommand = Command.make(
|
|
@@ -2598,10 +2684,8 @@ var initCommand = Command.make(
|
|
|
2598
2684
|
codexAuth: codexAuthOption,
|
|
2599
2685
|
model: initModelOption,
|
|
2600
2686
|
sandbox: sandboxOption,
|
|
2601
|
-
issueTracker: issueTrackerOption,
|
|
2602
|
-
buildImage: buildImageOption,
|
|
2603
2687
|
installTemplateDeps: installTemplateDepsOption,
|
|
2604
|
-
|
|
2688
|
+
commitSetup: commitSetupOption
|
|
2605
2689
|
},
|
|
2606
2690
|
({
|
|
2607
2691
|
imageName: imageNameFlag,
|
|
@@ -2610,10 +2694,8 @@ var initCommand = Command.make(
|
|
|
2610
2694
|
codexAuth: codexAuthFlag,
|
|
2611
2695
|
model: modelFlag,
|
|
2612
2696
|
sandbox: sandboxFlag,
|
|
2613
|
-
issueTracker: issueTrackerFlag,
|
|
2614
|
-
buildImage: buildImageFlag,
|
|
2615
2697
|
installTemplateDeps: installTemplateDepsFlag,
|
|
2616
|
-
|
|
2698
|
+
commitSetup: commitSetupFlag
|
|
2617
2699
|
}) => Effect.gen(function* () {
|
|
2618
2700
|
const d = yield* Display;
|
|
2619
2701
|
const cwd = process.cwd();
|
|
@@ -2641,355 +2723,530 @@ var initCommand = Command.make(
|
|
|
2641
2723
|
);
|
|
2642
2724
|
}
|
|
2643
2725
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
const
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2726
|
+
yield* d.progress(
|
|
2727
|
+
"Initializing Shipyard",
|
|
2728
|
+
(report) => Effect.gen(function* () {
|
|
2729
|
+
const progressAt = (current, message) => report({ current, total: 100, message });
|
|
2730
|
+
const progressRange = (start, end, update) => {
|
|
2731
|
+
const total = Math.max(1, update.total);
|
|
2732
|
+
const fraction = Math.min(1, Math.max(0, update.current / total));
|
|
2733
|
+
progressAt(
|
|
2734
|
+
Math.round(start + (end - start) * fraction),
|
|
2735
|
+
update.message
|
|
2736
|
+
);
|
|
2737
|
+
};
|
|
2738
|
+
const whilePrompt = async (prompt) => {
|
|
2739
|
+
report.pause();
|
|
2740
|
+
try {
|
|
2741
|
+
return await prompt();
|
|
2742
|
+
} finally {
|
|
2743
|
+
report.resume();
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2746
|
+
const statusWithProgress = (message, severity) => Effect.acquireUseRelease(
|
|
2747
|
+
Effect.sync(() => report.pause()),
|
|
2748
|
+
() => d.status(message, severity),
|
|
2749
|
+
() => Effect.sync(() => report.resume())
|
|
2652
2750
|
);
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
const buildImageChoice = choiceToTriBool(buildImageFlag);
|
|
2656
|
-
const installTemplateDepsChoice = choiceToTriBool(
|
|
2657
|
-
installTemplateDepsFlag
|
|
2658
|
-
);
|
|
2659
|
-
const installRunnerChoice = choiceToTriBool(installRunnerFlag);
|
|
2660
|
-
const isInteractive = process.stdin.isTTY === true;
|
|
2661
|
-
const failIfNonInteractive = (flag) => Effect.fail(
|
|
2662
|
-
new InitError({
|
|
2663
|
-
message: `${flag} is required in non-interactive mode (no TTY detected).`
|
|
2664
|
-
})
|
|
2665
|
-
);
|
|
2666
|
-
const resolveConfirmFlag = (params) => Effect.gen(function* () {
|
|
2667
|
-
if (params.choice._tag === "Some") return params.choice.value;
|
|
2668
|
-
if (!isInteractive) {
|
|
2669
|
-
yield* failIfNonInteractive(params.flag);
|
|
2670
|
-
}
|
|
2671
|
-
const confirmed = yield* Effect.promise(
|
|
2672
|
-
() => clack2.confirm({
|
|
2673
|
-
message: params.promptMessage,
|
|
2674
|
-
initialValue: true
|
|
2675
|
-
})
|
|
2676
|
-
);
|
|
2677
|
-
if (clack2.isCancel(confirmed)) {
|
|
2678
|
-
yield* Effect.fail(
|
|
2679
|
-
new InitError({ message: params.cancelMessage })
|
|
2751
|
+
const installTemplateDepsChoice = choiceToTriBool(
|
|
2752
|
+
installTemplateDepsFlag
|
|
2680
2753
|
);
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
const agents = listAgents();
|
|
2685
|
-
let selectedAgent;
|
|
2686
|
-
if (agentFlag._tag === "Some") {
|
|
2687
|
-
const entry = getAgent(agentFlag.value);
|
|
2688
|
-
if (!entry) {
|
|
2689
|
-
const names = agents.map((a) => a.name).join(", ");
|
|
2690
|
-
yield* Effect.fail(
|
|
2754
|
+
const commitSetupChoice = choiceToTriBool(commitSetupFlag);
|
|
2755
|
+
const isInteractive = process.stdin.isTTY === true;
|
|
2756
|
+
const failIfNonInteractive = (flag) => Effect.fail(
|
|
2691
2757
|
new InitError({
|
|
2692
|
-
message:
|
|
2758
|
+
message: `${flag} is required in non-interactive mode (no TTY detected).`
|
|
2693
2759
|
})
|
|
2694
2760
|
);
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
);
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
{
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2761
|
+
const resolveConfirmFlag = (params) => Effect.gen(function* () {
|
|
2762
|
+
if (params.choice._tag === "Some") return params.choice.value;
|
|
2763
|
+
if (!isInteractive) {
|
|
2764
|
+
yield* failIfNonInteractive(params.flag);
|
|
2765
|
+
}
|
|
2766
|
+
const confirmed = yield* Effect.promise(
|
|
2767
|
+
() => whilePrompt(
|
|
2768
|
+
() => clack2.confirm({
|
|
2769
|
+
message: params.promptMessage,
|
|
2770
|
+
initialValue: true
|
|
2771
|
+
})
|
|
2772
|
+
)
|
|
2773
|
+
);
|
|
2774
|
+
if (clack2.isCancel(confirmed)) {
|
|
2775
|
+
yield* Effect.fail(
|
|
2776
|
+
new InitError({ message: params.cancelMessage })
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2779
|
+
return confirmed === true;
|
|
2780
|
+
});
|
|
2781
|
+
const agents = listAgents();
|
|
2782
|
+
let selectedAgent;
|
|
2783
|
+
if (agentFlag._tag === "Some") {
|
|
2784
|
+
const entry = getAgent(agentFlag.value);
|
|
2785
|
+
if (!entry) {
|
|
2786
|
+
const names = agents.map((a) => a.name).join(", ");
|
|
2787
|
+
yield* Effect.fail(
|
|
2788
|
+
new InitError({
|
|
2789
|
+
message: `Unknown agent "${agentFlag.value}". Available: ${names}`
|
|
2790
|
+
})
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
selectedAgent = entry;
|
|
2794
|
+
} else {
|
|
2795
|
+
if (!isInteractive) {
|
|
2796
|
+
yield* failIfNonInteractive("--agent");
|
|
2797
|
+
}
|
|
2798
|
+
const selected = yield* Effect.promise(
|
|
2799
|
+
() => whilePrompt(
|
|
2800
|
+
() => clack2.select({
|
|
2801
|
+
message: "Select an agent:",
|
|
2802
|
+
initialValue: DEFAULT_AGENT_NAME,
|
|
2803
|
+
options: agents.map((a) => ({
|
|
2804
|
+
value: a.name,
|
|
2805
|
+
label: a.label,
|
|
2806
|
+
hint: `Default model: ${a.defaultModel}`
|
|
2807
|
+
}))
|
|
2808
|
+
})
|
|
2809
|
+
)
|
|
2810
|
+
);
|
|
2811
|
+
if (clack2.isCancel(selected)) {
|
|
2812
|
+
yield* Effect.fail(
|
|
2813
|
+
new InitError({ message: "Agent selection cancelled." })
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
selectedAgent = getAgent(selected);
|
|
2817
|
+
}
|
|
2818
|
+
progressAt(6, `Selected ${selectedAgent.label}`);
|
|
2819
|
+
const selectedCodexAuth = yield* Effect.tryPromise({
|
|
2820
|
+
try: () => resolveCodexAuthMode({
|
|
2821
|
+
agentName: selectedAgent.name,
|
|
2822
|
+
requested: codexAuthFlag._tag === "Some" ? codexAuthFlag.value : void 0,
|
|
2823
|
+
interactive: isInteractive,
|
|
2824
|
+
select: async () => {
|
|
2825
|
+
const selected = await whilePrompt(
|
|
2826
|
+
() => clack2.select({
|
|
2827
|
+
message: "How will you authenticate Codex?",
|
|
2828
|
+
initialValue: "chatgpt",
|
|
2829
|
+
options: [
|
|
2830
|
+
{
|
|
2831
|
+
value: "chatgpt",
|
|
2832
|
+
label: "Sign in with ChatGPT",
|
|
2833
|
+
hint: "Use your ChatGPT subscription"
|
|
2834
|
+
},
|
|
2835
|
+
{
|
|
2836
|
+
value: "api-key",
|
|
2837
|
+
label: "OpenAI API key",
|
|
2838
|
+
hint: "Use API billing"
|
|
2839
|
+
}
|
|
2840
|
+
]
|
|
2841
|
+
})
|
|
2842
|
+
);
|
|
2843
|
+
return clack2.isCancel(selected) ? void 0 : selected;
|
|
2844
|
+
}
|
|
2845
|
+
}),
|
|
2846
|
+
catch: (error) => error instanceof InitError ? error : new InitError({
|
|
2847
|
+
message: `Codex authentication selection failed: ${String(error)}`
|
|
2848
|
+
})
|
|
2849
|
+
});
|
|
2850
|
+
progressAt(12, "Authentication method selected");
|
|
2851
|
+
const selectedModel = modelFlag._tag === "Some" ? modelFlag.value : selectedAgent.defaultModel;
|
|
2852
|
+
if (selectedCodexAuth === "chatgpt") {
|
|
2853
|
+
yield* Effect.try({
|
|
2854
|
+
try: () => {
|
|
2855
|
+
report.pause();
|
|
2856
|
+
try {
|
|
2857
|
+
ensureCodexChatGptAuth({
|
|
2858
|
+
cwd,
|
|
2859
|
+
interactive: isInteractive
|
|
2860
|
+
});
|
|
2861
|
+
} finally {
|
|
2862
|
+
report.resume();
|
|
2738
2863
|
}
|
|
2739
|
-
|
|
2864
|
+
},
|
|
2865
|
+
catch: (error) => error instanceof InitError ? error : new InitError({
|
|
2866
|
+
message: `Codex authentication setup failed: ${String(error)}`
|
|
2867
|
+
})
|
|
2740
2868
|
});
|
|
2741
|
-
return clack2.isCancel(selected) ? void 0 : selected;
|
|
2742
2869
|
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
} else {
|
|
2766
|
-
if (!isInteractive) {
|
|
2767
|
-
yield* failIfNonInteractive("--issue-tracker");
|
|
2768
|
-
}
|
|
2769
|
-
const selected = yield* Effect.promise(
|
|
2770
|
-
() => clack2.select({
|
|
2771
|
-
message: "Select an issue tracker:",
|
|
2772
|
-
initialValue: "github-issues",
|
|
2773
|
-
options: issueTrackers.map((b) => ({
|
|
2774
|
-
value: b.name,
|
|
2775
|
-
label: b.label
|
|
2776
|
-
}))
|
|
2777
|
-
})
|
|
2778
|
-
);
|
|
2779
|
-
if (clack2.isCancel(selected)) {
|
|
2780
|
-
yield* Effect.fail(
|
|
2781
|
-
new InitError({
|
|
2782
|
-
message: "Issue tracker selection cancelled."
|
|
2783
|
-
})
|
|
2784
|
-
);
|
|
2785
|
-
}
|
|
2786
|
-
selectedIssueTracker = getIssueTracker(selected);
|
|
2787
|
-
}
|
|
2788
|
-
let selectedTemplate;
|
|
2789
|
-
if (template._tag === "Some") {
|
|
2790
|
-
selectedTemplate = template.value;
|
|
2791
|
-
} else {
|
|
2792
|
-
if (!isInteractive) {
|
|
2793
|
-
yield* failIfNonInteractive("--template");
|
|
2794
|
-
}
|
|
2795
|
-
const selected = yield* Effect.promise(
|
|
2796
|
-
() => clack2.select({
|
|
2797
|
-
message: "Select a template:",
|
|
2798
|
-
initialValue: "simple-loop",
|
|
2799
|
-
options: templates.map((tmpl) => ({
|
|
2800
|
-
value: tmpl.name,
|
|
2801
|
-
label: tmpl.name,
|
|
2802
|
-
hint: tmpl.description
|
|
2803
|
-
}))
|
|
2804
|
-
})
|
|
2805
|
-
);
|
|
2806
|
-
if (clack2.isCancel(selected)) {
|
|
2807
|
-
yield* Effect.fail(
|
|
2808
|
-
new InitError({ message: "Template selection cancelled." })
|
|
2809
|
-
);
|
|
2810
|
-
}
|
|
2811
|
-
selectedTemplate = selected;
|
|
2812
|
-
}
|
|
2813
|
-
if (selectedIssueTracker.name === "github-issues") {
|
|
2814
|
-
const failedLabels = [];
|
|
2815
|
-
for (const [name, description, color] of [
|
|
2816
|
-
[ACTIVATION_LABEL, `Issues for ${PRODUCT_NAME} to work on`, "F9A825"],
|
|
2817
|
-
["bug", "Something is broken", "D73A4A"],
|
|
2818
|
-
["enhancement", "New feature or improvement", "A2EEEF"],
|
|
2819
|
-
["needs-triage", "Maintainer evaluation needed", "FBCA04"],
|
|
2820
|
-
["needs-info", "Waiting for reporter information", "D4C5F9"],
|
|
2821
|
-
["ready-for-agent", "Ready for agent implementation", "0E8A16"],
|
|
2822
|
-
["ready-for-human", "Requires human implementation", "1D76DB"],
|
|
2823
|
-
["wontfix", "Will not be actioned", "FFFFFF"],
|
|
2824
|
-
["shipyard:blocked", "Shipyard work needs intervention", "B60205"],
|
|
2825
|
-
["shipyard:pending", "Shipyard is working on this ticket", "1D76DB"],
|
|
2826
|
-
[
|
|
2827
|
-
"shipyard:complete",
|
|
2828
|
-
"Shipyard work ready for human review",
|
|
2829
|
-
"0E8A16"
|
|
2830
|
-
],
|
|
2831
|
-
[
|
|
2832
|
-
"shipyard:outstanding-tasks",
|
|
2833
|
-
"Spec has uncompleted tickets",
|
|
2834
|
-
"FBCA04"
|
|
2835
|
-
]
|
|
2836
|
-
]) {
|
|
2837
|
-
try {
|
|
2838
|
-
execSync(
|
|
2839
|
-
`gh label create "${name}" --description "${description}" --color "${color}" --force`,
|
|
2840
|
-
{ cwd, stdio: "ignore" }
|
|
2870
|
+
progressAt(18, "Authentication setup complete");
|
|
2871
|
+
const selectedSandboxProvider = getSandboxProvider("docker");
|
|
2872
|
+
const selectedIssueTracker = getIssueTracker("github-issues");
|
|
2873
|
+
let selectedTemplate;
|
|
2874
|
+
if (template._tag === "Some") {
|
|
2875
|
+
selectedTemplate = template.value;
|
|
2876
|
+
} else {
|
|
2877
|
+
if (!isInteractive) {
|
|
2878
|
+
yield* failIfNonInteractive("--template");
|
|
2879
|
+
}
|
|
2880
|
+
const selected = yield* Effect.promise(
|
|
2881
|
+
() => whilePrompt(
|
|
2882
|
+
() => clack2.select({
|
|
2883
|
+
message: "Select a template:",
|
|
2884
|
+
initialValue: "simple-loop",
|
|
2885
|
+
options: templates.map((tmpl) => ({
|
|
2886
|
+
value: tmpl.name,
|
|
2887
|
+
label: tmpl.name,
|
|
2888
|
+
hint: tmpl.description
|
|
2889
|
+
}))
|
|
2890
|
+
})
|
|
2891
|
+
)
|
|
2841
2892
|
);
|
|
2842
|
-
|
|
2843
|
-
|
|
2893
|
+
if (clack2.isCancel(selected)) {
|
|
2894
|
+
yield* Effect.fail(
|
|
2895
|
+
new InitError({ message: "Template selection cancelled." })
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
selectedTemplate = selected;
|
|
2844
2899
|
}
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2900
|
+
progressAt(24, `Selected ${selectedTemplate} template`);
|
|
2901
|
+
const failedLabels = [];
|
|
2902
|
+
const labels = [
|
|
2903
|
+
[
|
|
2904
|
+
ACTIVATION_LABEL,
|
|
2905
|
+
`Issues for ${PRODUCT_NAME} to work on`,
|
|
2906
|
+
"F9A825"
|
|
2907
|
+
],
|
|
2908
|
+
["bug", "Something is broken", "D73A4A"],
|
|
2909
|
+
["enhancement", "New feature or improvement", "A2EEEF"],
|
|
2910
|
+
["needs-triage", "Maintainer evaluation needed", "FBCA04"],
|
|
2911
|
+
["needs-info", "Waiting for reporter information", "D4C5F9"],
|
|
2912
|
+
["ready-for-agent", "Ready for agent implementation", "0E8A16"],
|
|
2913
|
+
["ready-for-human", "Requires human implementation", "1D76DB"],
|
|
2914
|
+
["wontfix", "Will not be actioned", "FFFFFF"],
|
|
2915
|
+
["shipyard:blocked", "Shipyard work needs intervention", "B60205"],
|
|
2916
|
+
[
|
|
2917
|
+
"shipyard:pending",
|
|
2918
|
+
"Shipyard is working on this ticket",
|
|
2919
|
+
"1D76DB"
|
|
2920
|
+
],
|
|
2921
|
+
[
|
|
2922
|
+
"shipyard:complete",
|
|
2923
|
+
"Shipyard work ready for human review",
|
|
2924
|
+
"0E8A16"
|
|
2925
|
+
],
|
|
2926
|
+
[
|
|
2927
|
+
"shipyard:outstanding-tasks",
|
|
2928
|
+
"Spec has uncompleted tickets",
|
|
2929
|
+
"FBCA04"
|
|
2930
|
+
]
|
|
2931
|
+
];
|
|
2932
|
+
for (const [index, [name, description, color]] of labels.entries()) {
|
|
2933
|
+
try {
|
|
2934
|
+
execSync(
|
|
2935
|
+
`gh label create "${name}" --description "${description}" --color "${color}" --force`,
|
|
2936
|
+
{ cwd, stdio: "ignore" }
|
|
2937
|
+
);
|
|
2938
|
+
} catch {
|
|
2939
|
+
failedLabels.push(name);
|
|
2940
|
+
}
|
|
2941
|
+
progressAt(
|
|
2942
|
+
24 + Math.round((index + 1) / labels.length * 10),
|
|
2943
|
+
"Provisioning GitHub labels"
|
|
2944
|
+
);
|
|
2852
2945
|
}
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2946
|
+
if (failedLabels.length) {
|
|
2947
|
+
let connected = Boolean(process.env.GH_REPO);
|
|
2948
|
+
try {
|
|
2949
|
+
execSync("git remote get-url origin", { cwd, stdio: "ignore" });
|
|
2950
|
+
connected = true;
|
|
2951
|
+
} catch {
|
|
2952
|
+
}
|
|
2953
|
+
const message = `Could not create GitHub labels: ${failedLabels.join(", ")}. Check GitHub access and rerun init.`;
|
|
2954
|
+
if (connected) yield* Effect.fail(new InitError({ message }));
|
|
2955
|
+
report.pause();
|
|
2956
|
+
try {
|
|
2957
|
+
console.warn(message);
|
|
2958
|
+
} finally {
|
|
2959
|
+
report.resume();
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
progressAt(34, `Scaffolding ${CONFIG_DIR}/ config directory`);
|
|
2963
|
+
yield* scaffold(cwd, {
|
|
2964
|
+
agent: selectedAgent,
|
|
2965
|
+
model: selectedModel,
|
|
2966
|
+
modelExplicit: modelFlag._tag === "Some",
|
|
2967
|
+
templateName: selectedTemplate,
|
|
2968
|
+
issueTracker: selectedIssueTracker,
|
|
2969
|
+
sandboxProvider: selectedSandboxProvider,
|
|
2970
|
+
codexAuth: selectedCodexAuth,
|
|
2971
|
+
onProgress: (update) => progressRange(34, 56, update)
|
|
2972
|
+
}).pipe(
|
|
2973
|
+
Effect.mapError(
|
|
2974
|
+
(e) => new InitError({
|
|
2975
|
+
message: `${e instanceof Error ? e.message : e}`
|
|
2976
|
+
})
|
|
2977
|
+
)
|
|
2978
|
+
);
|
|
2979
|
+
progressAt(56, "Shipyard configuration generated");
|
|
2980
|
+
const packageManager = yield* detectPackageManager(cwd);
|
|
2981
|
+
progressAt(58, `Detected ${packageManager}`);
|
|
2982
|
+
if (getTemplateDependencies(selectedTemplate).includes("zod")) {
|
|
2983
|
+
const alreadyInstalled = yield* hostHasDependency(cwd, "zod");
|
|
2984
|
+
if (!alreadyInstalled) {
|
|
2985
|
+
const installCmd = addDependencyCommand(packageManager, "zod");
|
|
2986
|
+
const shouldInstall = yield* resolveConfirmFlag({
|
|
2987
|
+
choice: installTemplateDepsChoice,
|
|
2988
|
+
flag: "--install-template-deps",
|
|
2989
|
+
promptMessage: `The ${selectedTemplate} template needs a schema validator. Install zod now (\`${installCmd}\`)?`,
|
|
2990
|
+
cancelMessage: "Install-template-deps selection cancelled."
|
|
2991
|
+
});
|
|
2992
|
+
progressAt(
|
|
2993
|
+
60,
|
|
2994
|
+
shouldInstall ? "Installing template dependency" : "Skipping zod installation"
|
|
2995
|
+
);
|
|
2996
|
+
if (shouldInstall) {
|
|
2997
|
+
progressAt(62, `Installing zod with ${packageManager}`);
|
|
2998
|
+
const installed = yield* Effect.sync(() => {
|
|
2999
|
+
try {
|
|
3000
|
+
execSync(installCmd, { cwd, stdio: "ignore" });
|
|
3001
|
+
return true;
|
|
3002
|
+
} catch {
|
|
3003
|
+
return false;
|
|
3004
|
+
}
|
|
3005
|
+
});
|
|
3006
|
+
progressAt(64, "Template dependency setup complete");
|
|
3007
|
+
yield* installed ? statusWithProgress(
|
|
3008
|
+
`Installed zod with ${packageManager}.`,
|
|
3009
|
+
"success"
|
|
3010
|
+
) : statusWithProgress(
|
|
3011
|
+
`Couldn't install zod automatically. Run \`${installCmd}\` before running the agent.`,
|
|
3012
|
+
"warn"
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
progressAt(66, "Template dependencies ready");
|
|
3018
|
+
const providerLabel = selectedSandboxProvider.label;
|
|
3019
|
+
const containerfileDir = join(cwd, CONFIG_DIR);
|
|
3020
|
+
progressAt(68, `Building ${providerLabel} image '${imageName}'`);
|
|
3021
|
+
yield* buildImage(imageName, containerfileDir, {
|
|
3022
|
+
buildArgs: defaultUidBuildArgs()
|
|
3023
|
+
});
|
|
3024
|
+
progressAt(78, "Docker image built");
|
|
3025
|
+
yield* statusWithProgress("Image built successfully.", "success");
|
|
3026
|
+
progressAt(78, "Installing repository runner");
|
|
3027
|
+
const runner = yield* Effect.tryPromise({
|
|
3028
|
+
try: () => installRepositoryRunnerWithReplacement({
|
|
3029
|
+
repoDir: cwd,
|
|
3030
|
+
interactive: isInteractive,
|
|
3031
|
+
confirmReplacement: async (conflict) => {
|
|
3032
|
+
const confirmed = await whilePrompt(
|
|
3033
|
+
() => clack2.confirm({
|
|
3034
|
+
message: conflict.confirmationMessage,
|
|
3035
|
+
initialValue: false
|
|
3036
|
+
})
|
|
3037
|
+
);
|
|
3038
|
+
return !clack2.isCancel(confirmed) && confirmed === true;
|
|
3039
|
+
},
|
|
3040
|
+
install: () => installRepositoryRunner({
|
|
3041
|
+
repoDir: cwd,
|
|
3042
|
+
onProgress: (update) => progressRange(78, 94, update)
|
|
3043
|
+
})
|
|
3044
|
+
}),
|
|
3045
|
+
catch: (error) => error instanceof InitError ? error : new InitError({
|
|
3046
|
+
message: `Repository runner installation failed: ${error instanceof Error ? error.message : String(error)}. Init is incomplete. Fix the issue and retry with \`npx ${CLI_NAME} runner install\`.`
|
|
2873
3047
|
})
|
|
2874
|
-
)
|
|
2875
|
-
)
|
|
2876
|
-
);
|
|
2877
|
-
const packageManager = yield* detectPackageManager(cwd);
|
|
2878
|
-
if (getTemplateDependencies(selectedTemplate).includes("zod")) {
|
|
2879
|
-
const alreadyInstalled = yield* hostHasDependency(cwd, "zod");
|
|
2880
|
-
if (!alreadyInstalled) {
|
|
2881
|
-
const installCmd = addDependencyCommand(packageManager, "zod");
|
|
2882
|
-
const shouldInstall = yield* resolveConfirmFlag({
|
|
2883
|
-
choice: installTemplateDepsChoice,
|
|
2884
|
-
flag: "--install-template-deps",
|
|
2885
|
-
promptMessage: `The ${selectedTemplate} template needs a schema validator. Install zod now (\`${installCmd}\`)?`,
|
|
2886
|
-
cancelMessage: "Install-template-deps selection cancelled."
|
|
2887
3048
|
});
|
|
2888
|
-
|
|
2889
|
-
|
|
3049
|
+
progressAt(94, "Repository runner installed");
|
|
3050
|
+
yield* statusWithProgress(
|
|
3051
|
+
`Installed ${runner.name} for ${runner.repository}.`,
|
|
3052
|
+
"success"
|
|
3053
|
+
);
|
|
3054
|
+
let shouldCommitSetup = commitSetupChoice._tag === "Some" && commitSetupChoice.value;
|
|
3055
|
+
if (commitSetupChoice._tag === "None" && isInteractive) {
|
|
3056
|
+
const confirmed = yield* Effect.promise(
|
|
3057
|
+
() => whilePrompt(
|
|
3058
|
+
() => clack2.confirm({
|
|
3059
|
+
message: "Commit .shipyard/ and any generated runner workflow, then push this branch to origin? Sandboxes need the setup committed. The push also sends any local commits not already on origin.",
|
|
3060
|
+
initialValue: true
|
|
3061
|
+
})
|
|
3062
|
+
)
|
|
3063
|
+
);
|
|
3064
|
+
if (clack2.isCancel(confirmed)) {
|
|
3065
|
+
yield* Effect.fail(
|
|
3066
|
+
new InitError({
|
|
3067
|
+
message: "Shipyard setup commit cancelled."
|
|
3068
|
+
})
|
|
3069
|
+
);
|
|
3070
|
+
}
|
|
3071
|
+
shouldCommitSetup = confirmed === true;
|
|
3072
|
+
}
|
|
3073
|
+
progressAt(
|
|
3074
|
+
96,
|
|
3075
|
+
shouldCommitSetup ? "Committing and pushing Shipyard setup" : "Setup commit skipped"
|
|
3076
|
+
);
|
|
3077
|
+
if (shouldCommitSetup) {
|
|
3078
|
+
const publishResult = yield* Effect.sync(() => {
|
|
2890
3079
|
try {
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
3080
|
+
return {
|
|
3081
|
+
status: "committed",
|
|
3082
|
+
result: commitAndPushInitSetup(cwd)
|
|
3083
|
+
};
|
|
3084
|
+
} catch (error) {
|
|
3085
|
+
return {
|
|
3086
|
+
status: "failed",
|
|
3087
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3088
|
+
};
|
|
2895
3089
|
}
|
|
2896
3090
|
});
|
|
2897
|
-
|
|
2898
|
-
|
|
3091
|
+
progressAt(99, "Shipyard setup commit finished");
|
|
3092
|
+
if (publishResult.status === "failed") {
|
|
3093
|
+
yield* statusWithProgress(
|
|
3094
|
+
`Could not commit the Shipyard setup: ${publishResult.message}`,
|
|
3095
|
+
"warn"
|
|
3096
|
+
);
|
|
3097
|
+
yield* statusWithProgress(
|
|
3098
|
+
`Commit \`${CONFIG_DIR}/\` before running Shipyard.`,
|
|
3099
|
+
"warn"
|
|
3100
|
+
);
|
|
3101
|
+
} else if (publishResult.result.pushError) {
|
|
3102
|
+
yield* statusWithProgress(
|
|
3103
|
+
`Committed ${publishResult.result.commit}, but could not push to origin/${publishResult.result.branch}: ${publishResult.result.pushError}`,
|
|
3104
|
+
"warn"
|
|
3105
|
+
);
|
|
3106
|
+
yield* statusWithProgress(
|
|
3107
|
+
`Push it later with \`git push origin ${publishResult.result.branch}\`.`,
|
|
3108
|
+
"warn"
|
|
3109
|
+
);
|
|
3110
|
+
} else {
|
|
3111
|
+
yield* statusWithProgress(
|
|
3112
|
+
`Committed ${publishResult.result.commit} and pushed origin/${publishResult.result.branch}.`,
|
|
3113
|
+
"success"
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
} else {
|
|
3117
|
+
yield* statusWithProgress(
|
|
3118
|
+
`Commit \`${CONFIG_DIR}/\` before running Shipyard. Sandboxes do not receive uncommitted setup files.`,
|
|
3119
|
+
"warn"
|
|
3120
|
+
);
|
|
3121
|
+
yield* statusWithProgress(
|
|
3122
|
+
"Push the runner wake workflow to the repository's default branch before starting the runner.",
|
|
2899
3123
|
"warn"
|
|
2900
3124
|
);
|
|
2901
3125
|
}
|
|
2902
|
-
|
|
3126
|
+
progressAt(100, "Initialization complete");
|
|
3127
|
+
})
|
|
3128
|
+
);
|
|
3129
|
+
yield* d.text("Repository runner next steps:");
|
|
3130
|
+
for (const [index, line] of repositoryRunnerNextSteps().entries()) {
|
|
3131
|
+
yield* d.text(styleText("dim", `${index + 1}. ${line}`));
|
|
3132
|
+
}
|
|
3133
|
+
yield* d.status("Init complete!", "success");
|
|
3134
|
+
const nextSteps = getNextStepsLines();
|
|
3135
|
+
for (const [i, line] of nextSteps.entries()) {
|
|
3136
|
+
yield* d.text(i === 0 ? line : styleText("dim", line));
|
|
2903
3137
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
3138
|
+
})
|
|
3139
|
+
);
|
|
3140
|
+
var uninstallCommand = Command.make(
|
|
3141
|
+
"uninstall",
|
|
3142
|
+
{ yes: uninstallYesOption, force: uninstallForceOption },
|
|
3143
|
+
({ yes, force }) => Effect.gen(function* () {
|
|
3144
|
+
const d = yield* Display;
|
|
3145
|
+
const repoDir = process.cwd();
|
|
3146
|
+
const configDirExists2 = yield* Effect.tryPromise({
|
|
3147
|
+
try: () => inspectShipyardConfigDirectory(repoDir),
|
|
3148
|
+
catch: (error) => new InitError({
|
|
3149
|
+
message: `Could not inspect Shipyard configuration: ${error instanceof Error ? error.message : String(error)}`
|
|
3150
|
+
})
|
|
2910
3151
|
});
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
3152
|
+
const runnerDir = join(repoDir, CONFIG_DIR, RUNNER_DIR);
|
|
3153
|
+
const runnerInstalled = configDirExists2 && existsSync(runnerDir);
|
|
3154
|
+
const workflowExists = existsSync(
|
|
3155
|
+
join(repoDir, REPOSITORY_RUNNER_WORKFLOW_PATH)
|
|
3156
|
+
);
|
|
3157
|
+
const packageInstalled = yield* hostHasDependency(
|
|
3158
|
+
repoDir,
|
|
3159
|
+
SHIPYARD_PACKAGE_NAME
|
|
3160
|
+
);
|
|
3161
|
+
if (!configDirExists2 && !workflowExists && !runnerInstalled && !packageInstalled) {
|
|
2921
3162
|
yield* d.status(
|
|
2922
|
-
|
|
3163
|
+
"No Shipyard installation found in this repository.",
|
|
2923
3164
|
"info"
|
|
2924
3165
|
);
|
|
3166
|
+
return;
|
|
2925
3167
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
confirm: async ({ message, initialValue }) => {
|
|
2931
|
-
const confirmed = await clack2.confirm({ message, initialValue });
|
|
2932
|
-
if (clack2.isCancel(confirmed)) {
|
|
2933
|
-
throw new InitError({
|
|
2934
|
-
message: "Repository-runner installation selection cancelled."
|
|
2935
|
-
});
|
|
2936
|
-
}
|
|
2937
|
-
return confirmed === true;
|
|
2938
|
-
},
|
|
2939
|
-
install: () => installRepositoryRunnerWithReplacement({
|
|
2940
|
-
repoDir: cwd,
|
|
2941
|
-
interactive: isInteractive,
|
|
2942
|
-
confirmReplacement: async (conflict) => {
|
|
2943
|
-
const confirmed = await clack2.confirm({
|
|
2944
|
-
message: conflict.confirmationMessage,
|
|
2945
|
-
initialValue: false
|
|
2946
|
-
});
|
|
2947
|
-
return !clack2.isCancel(confirmed) && confirmed === true;
|
|
2948
|
-
},
|
|
2949
|
-
install: () => runEffectPromise(
|
|
2950
|
-
d.progress(
|
|
2951
|
-
"Installing repository runner",
|
|
2952
|
-
(report) => Effect.tryPromise({
|
|
2953
|
-
try: () => installRepositoryRunner({
|
|
2954
|
-
repoDir: cwd,
|
|
2955
|
-
onProgress: report
|
|
2956
|
-
}),
|
|
2957
|
-
catch: (error) => error instanceof RunnerInstallError ? error : new InitError({
|
|
2958
|
-
message: `Repository runner installation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2959
|
-
})
|
|
2960
|
-
})
|
|
2961
|
-
)
|
|
2962
|
-
)
|
|
3168
|
+
if (!yes && process.stdin.isTTY !== true) {
|
|
3169
|
+
return yield* Effect.fail(
|
|
3170
|
+
new InitError({
|
|
3171
|
+
message: "Shipyard uninstall needs confirmation. Run it in a terminal or pass --yes."
|
|
2963
3172
|
})
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
3173
|
+
);
|
|
3174
|
+
}
|
|
3175
|
+
if (!yes) {
|
|
3176
|
+
const actions = [
|
|
3177
|
+
runnerInstalled ? "unregister and remove the repository runner" : null,
|
|
3178
|
+
configDirExists2 ? `remove all of ${CONFIG_DIR}/, including .env and runtime data` : null,
|
|
3179
|
+
workflowExists ? "remove the generated runner wake workflow" : null,
|
|
3180
|
+
packageInstalled ? `remove ${SHIPYARD_PACKAGE_NAME} from package.json` : null
|
|
3181
|
+
].filter((action) => action !== null);
|
|
3182
|
+
const confirmation = yield* Effect.tryPromise({
|
|
3183
|
+
try: () => clack2.confirm({
|
|
3184
|
+
message: `Uninstall Shipyard from ${repoDir}? This will ${actions.join(", ")}. It leaves GitHub issues and labels unchanged.`,
|
|
3185
|
+
initialValue: false
|
|
3186
|
+
}),
|
|
3187
|
+
catch: (error) => new InitError({
|
|
3188
|
+
message: `Could not confirm Shipyard uninstall: ${error instanceof Error ? error.message : String(error)}`
|
|
3189
|
+
})
|
|
3190
|
+
});
|
|
3191
|
+
if (clack2.isCancel(confirmation) || confirmation !== true) {
|
|
3192
|
+
yield* d.status("Shipyard uninstall cancelled.", "info");
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
if (runnerInstalled) {
|
|
3197
|
+
const result = yield* Effect.tryPromise({
|
|
3198
|
+
try: () => removeRepositoryRunner({ repoDir, force }),
|
|
3199
|
+
catch: (error) => error instanceof RunnerLifecycleError ? new InitError({ message: error.message }) : new InitError({
|
|
3200
|
+
message: `Repository runner removal failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3201
|
+
})
|
|
3202
|
+
});
|
|
3203
|
+
yield* d.status("Repository runner removed.", "success");
|
|
3204
|
+
if (result.manualCleanup) {
|
|
3205
|
+
yield* d.status(result.manualCleanup, "warn");
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
const files = yield* Effect.tryPromise({
|
|
3209
|
+
try: () => removeShipyardRepositoryFiles({ repoDir }),
|
|
3210
|
+
catch: (error) => new InitError({
|
|
3211
|
+
message: `Could not remove Shipyard repository files: ${error instanceof Error ? error.message : String(error)}`
|
|
2967
3212
|
})
|
|
2968
3213
|
});
|
|
2969
|
-
if (
|
|
3214
|
+
if (files.configDirectoryRemoved) {
|
|
3215
|
+
yield* d.status(`Removed all of ${CONFIG_DIR}/.`, "success");
|
|
3216
|
+
}
|
|
3217
|
+
if (files.workflowRemoved) {
|
|
2970
3218
|
yield* d.status(
|
|
2971
|
-
`
|
|
3219
|
+
`Removed ${REPOSITORY_RUNNER_WORKFLOW_PATH}.`,
|
|
2972
3220
|
"success"
|
|
2973
3221
|
);
|
|
2974
|
-
yield* d.text("Repository runner next steps:");
|
|
2975
|
-
for (const [index, line] of repositoryRunnerNextSteps().entries()) {
|
|
2976
|
-
yield* d.text(styleText("dim", `${index + 1}. ${line}`));
|
|
2977
|
-
}
|
|
2978
|
-
} else if (runnerInit.status === "failed") {
|
|
2979
3222
|
yield* d.status(
|
|
2980
|
-
|
|
2981
|
-
"
|
|
3223
|
+
"Commit and push the workflow deletion to disable it on GitHub.",
|
|
3224
|
+
"info"
|
|
2982
3225
|
);
|
|
3226
|
+
} else if (files.workflowPreserved) {
|
|
2983
3227
|
yield* d.status(
|
|
2984
|
-
`
|
|
3228
|
+
`Preserved ${REPOSITORY_RUNNER_WORKFLOW_PATH} because it is customized or linked.`,
|
|
2985
3229
|
"warn"
|
|
2986
3230
|
);
|
|
2987
3231
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3232
|
+
if (packageInstalled) {
|
|
3233
|
+
const packageManager = yield* detectPackageManager(repoDir);
|
|
3234
|
+
const command = removeDependencyCommand(
|
|
3235
|
+
packageManager,
|
|
3236
|
+
SHIPYARD_PACKAGE_NAME
|
|
3237
|
+
);
|
|
3238
|
+
yield* Effect.try({
|
|
3239
|
+
try: () => execSync(command, { cwd: repoDir, stdio: "inherit" }),
|
|
3240
|
+
catch: (error) => new InitError({
|
|
3241
|
+
message: `Could not remove ${SHIPYARD_PACKAGE_NAME}. Rerun the uninstall after resolving the package-manager error: ${error instanceof Error ? error.message : String(error)}`
|
|
3242
|
+
})
|
|
3243
|
+
});
|
|
3244
|
+
yield* d.status(
|
|
3245
|
+
`Removed ${SHIPYARD_PACKAGE_NAME} with ${packageManager}.`,
|
|
3246
|
+
"success"
|
|
3247
|
+
);
|
|
2992
3248
|
}
|
|
3249
|
+
yield* d.status("Shipyard uninstalled from this repository.", "success");
|
|
2993
3250
|
})
|
|
2994
3251
|
);
|
|
2995
3252
|
var dockerfileOption = Options.file("dockerfile").pipe(
|
|
@@ -3007,7 +3264,7 @@ var buildImageCommand = Command.make(
|
|
|
3007
3264
|
({ imageName: imageNameFlag, dockerfile }) => Effect.gen(function* () {
|
|
3008
3265
|
const d = yield* Display;
|
|
3009
3266
|
const cwd = process.cwd();
|
|
3010
|
-
yield*
|
|
3267
|
+
yield* requireCanonicalConfigDir(cwd);
|
|
3011
3268
|
const imageName = resolveImageName(imageNameFlag, cwd);
|
|
3012
3269
|
const dockerfileDir = join(cwd, CONFIG_DIR);
|
|
3013
3270
|
const dockerfilePath = dockerfile._tag === "Some" ? dockerfile.value : void 0;
|
|
@@ -3060,6 +3317,7 @@ var rootCommand = Command.make(
|
|
|
3060
3317
|
var shipyard = rootCommand.pipe(
|
|
3061
3318
|
Command.withSubcommands([
|
|
3062
3319
|
initCommand,
|
|
3320
|
+
uninstallCommand,
|
|
3063
3321
|
runCommand2,
|
|
3064
3322
|
dockerCommand,
|
|
3065
3323
|
runnerCommand
|