@elie-laloum/outpost 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +3 -3
  3. package/dist/adapters/agents/codex-adapter.js +2 -0
  4. package/dist/adapters/agents/codex-adapter.js.map +1 -1
  5. package/dist/adapters/agents/codex-provider.d.ts +2 -0
  6. package/dist/adapters/agents/codex-provider.js +32 -0
  7. package/dist/adapters/agents/codex-provider.js.map +1 -0
  8. package/dist/adapters/agents/codex-request.js +2 -1
  9. package/dist/adapters/agents/codex-request.js.map +1 -1
  10. package/dist/adapters/agents/gemini-request.js +2 -0
  11. package/dist/adapters/agents/gemini-request.js.map +1 -1
  12. package/dist/adapters/agents/settings.types.d.ts +5 -0
  13. package/dist/adapters/agents/settings.types.js.map +1 -1
  14. package/dist/cli/commands.js +55 -18
  15. package/dist/cli/commands.js.map +1 -1
  16. package/dist/cli/init-authentication.constants.d.ts +13 -0
  17. package/dist/cli/init-authentication.constants.js +10 -0
  18. package/dist/cli/init-authentication.constants.js.map +1 -0
  19. package/dist/cli/init-authentication.d.ts +4 -0
  20. package/dist/cli/init-authentication.js +43 -0
  21. package/dist/cli/init-authentication.js.map +1 -0
  22. package/dist/cli/init-command.js +74 -15
  23. package/dist/cli/init-command.js.map +1 -1
  24. package/dist/cli/main.constants.d.ts +102 -2
  25. package/dist/cli/main.constants.js +148 -46
  26. package/dist/cli/main.constants.js.map +1 -1
  27. package/dist/cli/scaffold-files.js +4 -3
  28. package/dist/cli/scaffold-files.js.map +1 -1
  29. package/dist/cli/scaffold-validation.js +19 -0
  30. package/dist/cli/scaffold-validation.js.map +1 -1
  31. package/dist/cli/scaffold.constants.d.ts +0 -5
  32. package/dist/cli/scaffold.constants.js +0 -5
  33. package/dist/cli/scaffold.constants.js.map +1 -1
  34. package/dist/cli/scaffold.js +13 -0
  35. package/dist/cli/scaffold.js.map +1 -1
  36. package/dist/cli/scaffold.types.d.ts +3 -0
  37. package/dist/cli/scaffold.types.js.map +1 -1
  38. package/dist/cli/starters.js +9 -2
  39. package/dist/cli/starters.js.map +1 -1
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/providers/agents.d.ts +1 -1
  43. package/dist/providers/agents.js.map +1 -1
  44. package/dist/providers/daytona-command.constants.d.ts +2 -0
  45. package/dist/providers/daytona-command.constants.js +13 -0
  46. package/dist/providers/daytona-command.constants.js.map +1 -0
  47. package/dist/providers/daytona-command.js +9 -3
  48. package/dist/providers/daytona-command.js.map +1 -1
  49. package/dist/providers/daytona-output.d.ts +4 -0
  50. package/dist/providers/daytona-output.js +37 -0
  51. package/dist/providers/daytona-output.js.map +1 -0
  52. package/dist/providers/daytona-terminal.js +3 -1
  53. package/dist/providers/daytona-terminal.js.map +1 -1
  54. package/dist/providers/vercel-command.js +2 -2
  55. package/dist/providers/vercel-command.js.map +1 -1
  56. package/package.json +6 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.0
4
+
5
+ - Replace global CLI parsing and manual help with Commander subcommands and command-specific options; use Clack selection prompts for interactive initialization. Previously ignored options belonging to other commands are now rejected. Headless and JSON output remain plain.
6
+ - Guide initialization through package manager and authentication choices. Check a requested package manager before writing files when `--install` is used. Build Docker/Podman images automatically; use `--no-build` to generate files without building. Existing generated workflows are unchanged.
7
+ - Generate explicit API-key, Claude subscription-token and Codex account-login setup. Validate required model credentials before allocation; prepare Codex API login through stdin, or copy an explicitly selected account credential seed into the private sandbox home. Host keychains are not exported.
8
+ - Add `CodexModelProvider` with a custom Responses API URL and API-key environment variable; expose `--base-url`, `--api-key-env` and `--model` in initialization. Chat Completions-only endpoints are not supported. Verify the native Codex connection against a local simulated Responses endpoint in container CI.
9
+ - Preserve Vercel allocation variables on every command, with command-level overrides. Regress generated Claude token forwarding and document that switching providers must preserve explicit model variables.
10
+ - Add manually enabled authenticated Claude/Codex cloud campaigns with sanitized reports. Missing credentials remain skipped; adding this fixture does not establish a successful live account campaign. Scheduled runs do not make model calls.
11
+ - Preserve Daytona’s session shell and real exit status for non-interactive commands; accept an already removed PTY during cancellation cleanup. Regress both defects found by live testing. Validate Claude OAuth workflows on Docker, Vercel and Daytona, plus Daytona terminal input, resize, exit, cancellation and reuse.
12
+ - Preserve exact Daytona command output through encoded streaming, fixing synchronization of Git paths delimited by NUL bytes. Verify unterminated output, Unicode, exit status, cancellation and warm reuse.
13
+ - Make unattended Gemini `yolo` requests trust the selected workspace with `--skip-trust`; preserve other approval modes and document the trust boundary. Clarify generated Codex subscription login for local and cloud providers.
14
+ - Validate authenticated file-editing workflows with Codex subscription login and Gemini API keys on Docker, Vercel and Daytona, a custom Responses endpoint on Docker and Vercel, and OpenRouter Responses on Daytona. The same unmodified adapter completes the OpenRouter model/tool/synchronization workflow; the other custom endpoint still resets HTTPS connections from Daytona before authentication. Codex API-key authentication was not exercised in this campaign.
15
+ - Refocus the bilingual roadmap on near-term reliability and medium-term directions with explicit validation criteria.
16
+
3
17
  ## 4.0.0
4
18
 
5
19
  This major release extends public workflow status unions. Exhaustive consumers must handle task statuses `paused` and `rejected`, and workflow result status `paused`. Research features remain explicitly opt-in with their documented limitations.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Outpost is a TypeScript library for running coding agents in reusable sandboxes, managing their Git workspaces and composing typed workflows. Claude Code, Codex and Gemini CLI adapters work with Docker, Podman, Vercel, Daytona or explicit host execution. Gemini supports fresh sessions only; native conversation capture, resume and fork are available for Claude Code and Codex.
6
6
 
7
- Version 4.0.0 adds durable orchestration, recovery and transfer improvements, Gemini CLI and native Daytona terminals. Research prototypes remain opt-in; see the [changelog](CHANGELOG.md#400) for public contract changes and the [roadmap](https://elie-laloum.github.io/outpost/project/roadmap/) for capabilities, limits and remaining work.
7
+ Version 4.1.0 improves CLI setup, explicit agent authentication and custom OpenAI Responses providers. Docker/Podman images build automatically during `init`; use `--no-build` to opt out. See the [changelog](CHANGELOG.md#410) for behavior changes and the [roadmap](https://elie-laloum.github.io/outpost/project/roadmap/) for upcoming priorities.
8
8
 
9
9
  ## Get started
10
10
 
@@ -13,7 +13,7 @@ Requires Node.js **24+**, Git, a target repository with a commit, and the creden
13
13
  ```sh
14
14
  mkdir workflow1
15
15
  cd workflow1
16
- npx @elie-laloum/outpost init --yes --repository /path1/repository --install --build
16
+ npx @elie-laloum/outpost init --yes --repository /path1/repository --install
17
17
  ```
18
18
 
19
19
  Copy `.env.example` to `.env` and declare `OPENAI_API_KEY`. An empty declaration inherits the matching process variable. Then run the generated script:
@@ -24,7 +24,7 @@ node run.ts "Add validation, run tests and commit the change"
24
24
 
25
25
  `init` creates `package.json`, `run.ts`, `brief.md`, `.env.example`, `.gitignore` and a container recipe directly in the workflow directory. Existing package manifests are preserved; explicit CommonJS projects get `run.mts` for compatibility. The script resolves its brief, environment and relative repository paths from its own directory. See [multi-repository workflows](https://elie-laloum.github.io/outpost/workflows/sandbox-tasks/) to orchestrate several repositories.
26
26
 
27
- For Codex API-key authentication, add the login hook from [Connect Codex](https://elie-laloum.github.io/outpost/agents/connect-codex/) before dispatching.
27
+ Generated Codex API-key workflows prepare login inside the sandbox. For account login and credential storage, see [Connect Codex](https://elie-laloum.github.io/outpost/agents/connect-codex/) before dispatching.
28
28
 
29
29
  ## Use the library
30
30
 
@@ -1,6 +1,8 @@
1
+ import { codexProvider } from "./codex-provider.js";
1
2
  import { codexEvents } from "./codex-events.js";
2
3
  import { codexRequest } from "./codex-request.js";
3
4
  export function codex(settings = {}) {
5
+ codexProvider(settings);
4
6
  return Object.freeze({
5
7
  name: "codex",
6
8
  conversations: "codex",
@@ -1 +1 @@
1
- {"version":3,"file":"codex-adapter.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,UAAU,KAAK,CAAC,WAA0B,EAAE;IAChD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,OAAO;QACb,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,QAAQ,CAAC,iBAAiB,IAAI,IAAI;QAC3C,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;QACnD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC;QACjD,MAAM,EAAE,WAAW;KACG,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["import type { AgentAdapter } from \"../../domain/agent.types.ts\";\nimport { codexEvents } from \"./codex-events.ts\";\nimport { codexRequest } from \"./codex-request.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codex(settings: CodexSettings = {}): AgentAdapter {\n return Object.freeze({\n name: \"codex\",\n conversations: \"codex\",\n resumable: true,\n capture: settings.saveConversations ?? true,\n variables: Object.freeze({ ...settings.variables }),\n request: (input) => codexRequest(settings, input),\n events: codexEvents,\n } satisfies AgentAdapter);\n}\n"]}
1
+ {"version":3,"file":"codex-adapter.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,UAAU,KAAK,CAAC,WAA0B,EAAE;IAChD,aAAa,CAAC,QAAQ,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,OAAO;QACb,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,QAAQ,CAAC,iBAAiB,IAAI,IAAI;QAC3C,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;QACnD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC;QACjD,MAAM,EAAE,WAAW;KACG,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["import type { AgentAdapter } from \"../../domain/agent.types.ts\";\nimport { codexProvider } from \"./codex-provider.ts\";\nimport { codexEvents } from \"./codex-events.ts\";\nimport { codexRequest } from \"./codex-request.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codex(settings: CodexSettings = {}): AgentAdapter {\n codexProvider(settings);\n return Object.freeze({\n name: \"codex\",\n conversations: \"codex\",\n resumable: true,\n capture: settings.saveConversations ?? true,\n variables: Object.freeze({ ...settings.variables }),\n request: (input) => codexRequest(settings, input),\n events: codexEvents,\n } satisfies AgentAdapter);\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { CodexSettings } from "./settings.types.ts";
2
+ export declare function codexProvider(settings: CodexSettings): readonly string[];
@@ -0,0 +1,32 @@
1
+ import { invariant } from "../../domain/errors.js";
2
+ export function codexProvider(settings) {
3
+ const provider = settings.modelProvider;
4
+ if (!provider)
5
+ return [];
6
+ invariant(settings.model?.trim(), "A custom model provider requires a model name");
7
+ let url;
8
+ try {
9
+ url = new URL(provider.baseUrl);
10
+ }
11
+ catch {
12
+ throw new Error("Model provider baseUrl must be an absolute HTTP(S) URL");
13
+ }
14
+ invariant(["http:", "https:"].includes(url.protocol) &&
15
+ !url.username &&
16
+ !url.password &&
17
+ !url.search &&
18
+ !url.hash, "Model provider baseUrl must use HTTP(S) without embedded credentials, query or fragment");
19
+ const environment = provider.apiKeyEnvironment ?? "OPENAI_API_KEY";
20
+ invariant(environment === false || /^[A-Za-z_][A-Za-z0-9_]*$/.test(environment), "Invalid model provider API-key environment variable");
21
+ const configuration = [
22
+ 'model_provider="outpost_compatible"',
23
+ 'model_providers.outpost_compatible.name="OpenAI compatible"',
24
+ `model_providers.outpost_compatible.base_url=${JSON.stringify(provider.baseUrl)}`,
25
+ 'model_providers.outpost_compatible.wire_api="responses"',
26
+ "model_providers.outpost_compatible.requires_openai_auth=false",
27
+ ];
28
+ if (environment !== false)
29
+ configuration.push(`model_providers.outpost_compatible.env_key=${JSON.stringify(environment)}`);
30
+ return configuration.flatMap((value) => ["-c", value]);
31
+ }
32
+ //# sourceMappingURL=codex-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-provider.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,MAAM,UAAU,aAAa,CAAC,QAAuB;IACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC;IACxC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,SAAS,CACP,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,EACtB,+CAA+C,CAChD,CAAC;IACF,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,SAAS,CACP,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;QACxC,CAAC,GAAG,CAAC,QAAQ;QACb,CAAC,GAAG,CAAC,QAAQ;QACb,CAAC,GAAG,CAAC,MAAM;QACX,CAAC,GAAG,CAAC,IAAI,EACX,yFAAyF,CAC1F,CAAC;IACF,MAAM,WAAW,GAAG,QAAQ,CAAC,iBAAiB,IAAI,gBAAgB,CAAC;IACnE,SAAS,CACP,WAAW,KAAK,KAAK,IAAI,0BAA0B,CAAC,IAAI,CAAC,WAAW,CAAC,EACrE,qDAAqD,CACtD,CAAC;IACF,MAAM,aAAa,GAAG;QACpB,qCAAqC;QACrC,6DAA6D;QAC7D,+CAA+C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjF,yDAAyD;QACzD,+DAA+D;KAChE,CAAC;IACF,IAAI,WAAW,KAAK,KAAK;QACvB,aAAa,CAAC,IAAI,CAChB,8CAA8C,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAC5E,CAAC;IACJ,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AACzD,CAAC","sourcesContent":["import { invariant } from \"../../domain/errors.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codexProvider(settings: CodexSettings): readonly string[] {\n const provider = settings.modelProvider;\n if (!provider) return [];\n invariant(\n settings.model?.trim(),\n \"A custom model provider requires a model name\",\n );\n let url: URL;\n try {\n url = new URL(provider.baseUrl);\n } catch {\n throw new Error(\"Model provider baseUrl must be an absolute HTTP(S) URL\");\n }\n invariant(\n [\"http:\", \"https:\"].includes(url.protocol) &&\n !url.username &&\n !url.password &&\n !url.search &&\n !url.hash,\n \"Model provider baseUrl must use HTTP(S) without embedded credentials, query or fragment\",\n );\n const environment = provider.apiKeyEnvironment ?? \"OPENAI_API_KEY\";\n invariant(\n environment === false || /^[A-Za-z_][A-Za-z0-9_]*$/.test(environment),\n \"Invalid model provider API-key environment variable\",\n );\n const configuration = [\n 'model_provider=\"outpost_compatible\"',\n 'model_providers.outpost_compatible.name=\"OpenAI compatible\"',\n `model_providers.outpost_compatible.base_url=${JSON.stringify(provider.baseUrl)}`,\n 'model_providers.outpost_compatible.wire_api=\"responses\"',\n \"model_providers.outpost_compatible.requires_openai_auth=false\",\n ];\n if (environment !== false)\n configuration.push(\n `model_providers.outpost_compatible.env_key=${JSON.stringify(environment)}`,\n );\n return configuration.flatMap((value) => [\"-c\", value]);\n}\n"]}
@@ -1,7 +1,8 @@
1
+ import { codexProvider } from "./codex-provider.js";
1
2
  import { validateContinuation } from "./continuation.js";
2
3
  export function codexRequest(settings, input) {
3
4
  validateContinuation(input);
4
- const args = [];
5
+ const args = [...codexProvider(settings)];
5
6
  if (settings.approvalReviewer === "auto_review")
6
7
  args.push("-a", "on-request", "-s", "danger-full-access", "-c", 'approvals_reviewer="auto_review"');
7
8
  if (settings.approvalReviewer !== "auto_review" && !input.interactive)
@@ -1 +1 @@
1
- {"version":3,"file":"codex-request.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,MAAM,UAAU,YAAY,CAC1B,QAAuB,EACvB,KAAiB;IAEjB,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,QAAQ,CAAC,gBAAgB,KAAK,aAAa;QAC7C,IAAI,CAAC,IAAI,CACP,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,oBAAoB,EACpB,IAAI,EACJ,kCAAkC,CACnC,CAAC;IACJ,IAAI,QAAQ,CAAC,gBAAgB,KAAK,aAAa,IAAI,CAAC,KAAK,CAAC,WAAW;QACnE,IAAI,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAC1D,IAAI,QAAQ,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,QAAQ,CAAC,SAAS;QACpB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,2BAA2B,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;IACpE,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,KAAK,CAAC,YAAY;QACpB,IAAI,CAAC,IAAI,CACP,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAC3C,KAAK,CAAC,YAAY,CAAC,EAAE,CACtB,CAAC;IACJ,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO;QACL,UAAU,EAAE,OAAO;QACnB,SAAS,EAAE,IAAI;QACf,GAAG,CAAC,KAAK,CAAC,WAAW;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInput } from \"../../domain/agent.types.ts\";\nimport type { Command } from \"../../domain/command.types.ts\";\nimport { validateContinuation } from \"./continuation.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codexRequest(\n settings: CodexSettings,\n input: AgentInput,\n): Command {\n validateContinuation(input);\n const args: string[] = [];\n if (settings.approvalReviewer === \"auto_review\")\n args.push(\n \"-a\",\n \"on-request\",\n \"-s\",\n \"danger-full-access\",\n \"-c\",\n 'approvals_reviewer=\"auto_review\"',\n );\n if (settings.approvalReviewer !== \"auto_review\" && !input.interactive)\n args.push(\"--dangerously-bypass-approvals-and-sandbox\");\n if (settings.model) args.push(\"--model\", settings.model);\n if (settings.reasoning)\n args.push(\"-c\", `model_reasoning_effort=\"${settings.reasoning}\"`);\n if (!input.interactive) args.push(\"exec\");\n if (input.continuation)\n args.push(\n input.continuation.fork ? \"fork\" : \"resume\",\n input.continuation.id,\n );\n if (!input.interactive) args.push(\"--json\", \"-\");\n if (input.interactive && input.text !== undefined)\n args.push(\"--\", input.text);\n return {\n executable: \"codex\",\n arguments: args,\n ...(input.interactive\n ? { interactive: true }\n : { stdin: input.text ?? \"\" }),\n };\n}\n"]}
1
+ {"version":3,"file":"codex-request.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,MAAM,UAAU,YAAY,CAC1B,QAAuB,EACvB,KAAiB;IAEjB,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAa,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpD,IAAI,QAAQ,CAAC,gBAAgB,KAAK,aAAa;QAC7C,IAAI,CAAC,IAAI,CACP,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,oBAAoB,EACpB,IAAI,EACJ,kCAAkC,CACnC,CAAC;IACJ,IAAI,QAAQ,CAAC,gBAAgB,KAAK,aAAa,IAAI,CAAC,KAAK,CAAC,WAAW;QACnE,IAAI,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAC1D,IAAI,QAAQ,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,QAAQ,CAAC,SAAS;QACpB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,2BAA2B,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;IACpE,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,KAAK,CAAC,YAAY;QACpB,IAAI,CAAC,IAAI,CACP,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAC3C,KAAK,CAAC,YAAY,CAAC,EAAE,CACtB,CAAC;IACJ,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO;QACL,UAAU,EAAE,OAAO;QACnB,SAAS,EAAE,IAAI;QACf,GAAG,CAAC,KAAK,CAAC,WAAW;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInput } from \"../../domain/agent.types.ts\";\nimport type { Command } from \"../../domain/command.types.ts\";\nimport { codexProvider } from \"./codex-provider.ts\";\nimport { validateContinuation } from \"./continuation.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codexRequest(\n settings: CodexSettings,\n input: AgentInput,\n): Command {\n validateContinuation(input);\n const args: string[] = [...codexProvider(settings)];\n if (settings.approvalReviewer === \"auto_review\")\n args.push(\n \"-a\",\n \"on-request\",\n \"-s\",\n \"danger-full-access\",\n \"-c\",\n 'approvals_reviewer=\"auto_review\"',\n );\n if (settings.approvalReviewer !== \"auto_review\" && !input.interactive)\n args.push(\"--dangerously-bypass-approvals-and-sandbox\");\n if (settings.model) args.push(\"--model\", settings.model);\n if (settings.reasoning)\n args.push(\"-c\", `model_reasoning_effort=\"${settings.reasoning}\"`);\n if (!input.interactive) args.push(\"exec\");\n if (input.continuation)\n args.push(\n input.continuation.fork ? \"fork\" : \"resume\",\n input.continuation.id,\n );\n if (!input.interactive) args.push(\"--json\", \"-\");\n if (input.interactive && input.text !== undefined)\n args.push(\"--\", input.text);\n return {\n executable: \"codex\",\n arguments: args,\n ...(input.interactive\n ? { interactive: true }\n : { stdin: input.text ?? \"\" }),\n };\n}\n"]}
@@ -6,6 +6,8 @@ export function geminiRequest(settings, input) {
6
6
  args.push("--model", settings.model);
7
7
  const approvalMode = settings.approvalMode ?? (input.interactive ? "default" : "yolo");
8
8
  args.push("--approval-mode", approvalMode);
9
+ if (!input.interactive && approvalMode === "yolo")
10
+ args.push("--skip-trust");
9
11
  if (!input.interactive)
10
12
  args.push("--output-format", "stream-json");
11
13
  if (input.interactive && input.text !== undefined)
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-request.js","sourceRoot":"","sources":["../../../src/adapters/agents/gemini-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,MAAM,UAAU,aAAa,CAC3B,QAAwB,EACxB,KAAiB;IAEjB,SAAS,CACP,CAAC,KAAK,CAAC,YAAY,EACnB,yDAAyD,CAC1D,CAAC;IACF,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,QAAQ,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,MAAM,YAAY,GAChB,QAAQ,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACpE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IACpE,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC/C,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO;QACL,UAAU,EAAE,QAAQ;QACpB,SAAS,EAAE,IAAI;QACf,GAAG,CAAC,KAAK,CAAC,WAAW;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInput } from \"../../domain/agent.types.ts\";\nimport type { Command } from \"../../domain/command.types.ts\";\nimport { invariant } from \"../../domain/errors.ts\";\nimport type { GeminiSettings } from \"./gemini.types.ts\";\n\nexport function geminiRequest(\n settings: GeminiSettings,\n input: AgentInput,\n): Command {\n invariant(\n !input.continuation,\n \"Gemini does not support continuation or fork in Outpost\",\n );\n const args: string[] = [];\n if (settings.model) args.push(\"--model\", settings.model);\n const approvalMode =\n settings.approvalMode ?? (input.interactive ? \"default\" : \"yolo\");\n args.push(\"--approval-mode\", approvalMode);\n if (!input.interactive) args.push(\"--output-format\", \"stream-json\");\n if (input.interactive && input.text !== undefined)\n args.push(\"--prompt-interactive\", input.text);\n return {\n executable: \"gemini\",\n arguments: args,\n ...(input.interactive\n ? { interactive: true }\n : { stdin: input.text ?? \"\" }),\n };\n}\n"]}
1
+ {"version":3,"file":"gemini-request.js","sourceRoot":"","sources":["../../../src/adapters/agents/gemini-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,MAAM,UAAU,aAAa,CAC3B,QAAwB,EACxB,KAAiB;IAEjB,SAAS,CACP,CAAC,KAAK,CAAC,YAAY,EACnB,yDAAyD,CAC1D,CAAC;IACF,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,QAAQ,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,MAAM,YAAY,GAChB,QAAQ,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACpE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,YAAY,KAAK,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7E,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IACpE,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC/C,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO;QACL,UAAU,EAAE,QAAQ;QACpB,SAAS,EAAE,IAAI;QACf,GAAG,CAAC,KAAK,CAAC,WAAW;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInput } from \"../../domain/agent.types.ts\";\nimport type { Command } from \"../../domain/command.types.ts\";\nimport { invariant } from \"../../domain/errors.ts\";\nimport type { GeminiSettings } from \"./gemini.types.ts\";\n\nexport function geminiRequest(\n settings: GeminiSettings,\n input: AgentInput,\n): Command {\n invariant(\n !input.continuation,\n \"Gemini does not support continuation or fork in Outpost\",\n );\n const args: string[] = [];\n if (settings.model) args.push(\"--model\", settings.model);\n const approvalMode =\n settings.approvalMode ?? (input.interactive ? \"default\" : \"yolo\");\n args.push(\"--approval-mode\", approvalMode);\n if (!input.interactive && approvalMode === \"yolo\") args.push(\"--skip-trust\");\n if (!input.interactive) args.push(\"--output-format\", \"stream-json\");\n if (input.interactive && input.text !== undefined)\n args.push(\"--prompt-interactive\", input.text);\n return {\n executable: \"gemini\",\n arguments: args,\n ...(input.interactive\n ? { interactive: true }\n : { stdin: input.text ?? \"\" }),\n };\n}\n"]}
@@ -8,7 +8,12 @@ export interface ClaudeSettings extends CommonAgentSettings {
8
8
  readonly reasoning?: "low" | "medium" | "high" | "xhigh" | "max";
9
9
  readonly permissions?: "default" | "acceptEdits" | "plan" | "auto" | "dontAsk" | "bypassPermissions";
10
10
  }
11
+ export interface CodexModelProvider {
12
+ readonly baseUrl: string;
13
+ readonly apiKeyEnvironment?: string | false;
14
+ }
11
15
  export interface CodexSettings extends CommonAgentSettings {
16
+ readonly modelProvider?: CodexModelProvider;
12
17
  readonly reasoning?: "low" | "medium" | "high" | "xhigh";
13
18
  readonly approvalReviewer?: "user" | "auto_review";
14
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"settings.types.js","sourceRoot":"","sources":["../../../src/adapters/agents/settings.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Variables } from \"../../domain/command.types.ts\";\n\nexport interface CommonAgentSettings {\n readonly model?: string;\n readonly variables?: Variables;\n readonly saveConversations?: boolean;\n}\n\nexport interface ClaudeSettings extends CommonAgentSettings {\n readonly reasoning?: \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n readonly permissions?:\n | \"default\"\n | \"acceptEdits\"\n | \"plan\"\n | \"auto\"\n | \"dontAsk\"\n | \"bypassPermissions\";\n}\n\nexport interface CodexSettings extends CommonAgentSettings {\n readonly reasoning?: \"low\" | \"medium\" | \"high\" | \"xhigh\";\n readonly approvalReviewer?: \"user\" | \"auto_review\";\n}\n"]}
1
+ {"version":3,"file":"settings.types.js","sourceRoot":"","sources":["../../../src/adapters/agents/settings.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Variables } from \"../../domain/command.types.ts\";\n\nexport interface CommonAgentSettings {\n readonly model?: string;\n readonly variables?: Variables;\n readonly saveConversations?: boolean;\n}\n\nexport interface ClaudeSettings extends CommonAgentSettings {\n readonly reasoning?: \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n readonly permissions?:\n | \"default\"\n | \"acceptEdits\"\n | \"plan\"\n | \"auto\"\n | \"dontAsk\"\n | \"bypassPermissions\";\n}\n\nexport interface CodexModelProvider {\n readonly baseUrl: string;\n readonly apiKeyEnvironment?: string | false;\n}\n\nexport interface CodexSettings extends CommonAgentSettings {\n readonly modelProvider?: CodexModelProvider;\n readonly reasoning?: \"low\" | \"medium\" | \"high\" | \"xhigh\";\n readonly approvalReviewer?: \"user\" | \"auto_review\";\n}\n"]}
@@ -1,28 +1,65 @@
1
- import { parseArgs } from "node:util";
1
+ import { Command, CommanderError, Option } from "commander";
2
2
  import { recoveryCommand } from "./recovery-command.js";
3
3
  import { doctorCommand } from "./doctor-command.js";
4
4
  import { imageCommand } from "./image-command.js";
5
5
  import { initializeCommand } from "./init-command.js";
6
- import { cliOptions, help } from "./main.constants.js";
6
+ import { cliOptions, commandOptions } from "./main.constants.js";
7
7
  export async function runCli(args = process.argv.slice(2)) {
8
- const invocation = parseArgs({
9
- args,
10
- allowPositionals: true,
11
- options: cliOptions,
12
- });
13
- if (invocation.values.help || !invocation.positionals.length) {
14
- process.stdout.write(help);
15
- return;
16
- }
17
- const commands = {
18
- doctor: doctorCommand,
19
- recovery: recoveryCommand,
8
+ const program = new Command()
9
+ .name("outpost")
10
+ .description("Outpost — sandboxed coding agents and workflows")
11
+ .exitOverride()
12
+ .configureOutput({ writeErr: () => { } });
13
+ const handlers = {
20
14
  init: initializeCommand,
15
+ doctor: doctorCommand,
21
16
  image: imageCommand,
17
+ recovery: recoveryCommand,
22
18
  };
23
- const name = invocation.positionals[0];
24
- if (!Object.hasOwn(commands, name))
25
- throw new Error("Unknown command. Run outpost --help.");
26
- await commands[name](invocation);
19
+ for (const [name, definition] of Object.entries(commandOptions)) {
20
+ const path = name.split(" ");
21
+ let command = program;
22
+ for (const part of path) {
23
+ command =
24
+ command.commands.find((item) => item.name() === part) ??
25
+ command.command(part);
26
+ }
27
+ command.description(definition.description);
28
+ for (const key of definition.options) {
29
+ const configuration = cliOptions[key];
30
+ const short = "short" in configuration ? `-${configuration.short}, ` : "";
31
+ command.addOption(new Option(`${short}--${key}${configuration.type === "string" ? " <value>" : ""}`, configuration.description));
32
+ }
33
+ if (name === "init")
34
+ command.addOption(new Option("--no-build", "Skip automatic container image build").default(undefined));
35
+ command.action(async () => {
36
+ const values = {};
37
+ const parsed = command.opts();
38
+ for (const option of command.options) {
39
+ const value = parsed[option.attributeName()];
40
+ if (typeof value === "string" || typeof value === "boolean")
41
+ values[option.long.replace(/^--(?:no-)?/, "")] = value;
42
+ }
43
+ await handlers[path[0]]({
44
+ values: values,
45
+ positionals: path,
46
+ });
47
+ });
48
+ }
49
+ for (const command of program.commands.filter((item) => item.commands.length)) {
50
+ command.allowExcessArguments().action(() => {
51
+ throw new Error(`Unknown command. Run outpost ${command.name()} --help.`);
52
+ });
53
+ }
54
+ try {
55
+ await program.parseAsync(args.length ? [...args] : ["--help"], {
56
+ from: "user",
57
+ });
58
+ }
59
+ catch (error) {
60
+ if (error instanceof CommanderError && error.exitCode === 0)
61
+ return;
62
+ throw error;
63
+ }
27
64
  }
28
65
  //# sourceMappingURL=commands.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAGvD,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,OAA0B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAE/C,MAAM,UAAU,GAAG,SAAS,CAAC;QAC3B,IAAI;QACJ,gBAAgB,EAAE,IAAI;QACtB,OAAO,EAAE,UAAU;KACpB,CAAC,CAAC;IACH,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;QAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,MAAM,QAAQ,GAAyC;QACrD,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,eAAe;QACzB,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,YAAY;KACpB,CAAC;IACF,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,MAAM,QAAQ,CAAC,IAAI,CAAE,CAAC,UAAU,CAAC,CAAC;AACpC,CAAC","sourcesContent":["import { parseArgs } from \"node:util\";\nimport { recoveryCommand } from \"./recovery-command.ts\";\nimport { doctorCommand } from \"./doctor-command.ts\";\nimport { imageCommand } from \"./image-command.ts\";\nimport { initializeCommand } from \"./init-command.ts\";\nimport { cliOptions, help } from \"./main.constants.ts\";\nimport type { CliCommand } from \"./main.types.ts\";\n\nexport async function runCli(\n args: readonly string[] = process.argv.slice(2),\n): Promise<void> {\n const invocation = parseArgs({\n args,\n allowPositionals: true,\n options: cliOptions,\n });\n if (invocation.values.help || !invocation.positionals.length) {\n process.stdout.write(help);\n return;\n }\n const commands: Readonly<Record<string, CliCommand>> = {\n doctor: doctorCommand,\n recovery: recoveryCommand,\n init: initializeCommand,\n image: imageCommand,\n };\n const name = invocation.positionals[0]!;\n if (!Object.hasOwn(commands, name))\n throw new Error(\"Unknown command. Run outpost --help.\");\n await commands[name]!(invocation);\n}\n"]}
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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","sourcesContent":["import { Command, CommanderError, Option } from \"commander\";\nimport { recoveryCommand } from \"./recovery-command.ts\";\nimport { doctorCommand } from \"./doctor-command.ts\";\nimport { imageCommand } from \"./image-command.ts\";\nimport { initializeCommand } from \"./init-command.ts\";\nimport { cliOptions, commandOptions } from \"./main.constants.ts\";\nimport type { CliCommand, CliValues } from \"./main.types.ts\";\n\nexport async function runCli(\n args: readonly string[] = process.argv.slice(2),\n): Promise<void> {\n const program = new Command()\n .name(\"outpost\")\n .description(\"Outpost — sandboxed coding agents and workflows\")\n .exitOverride()\n .configureOutput({ writeErr: () => {} });\n const handlers: Readonly<Record<string, CliCommand>> = {\n init: initializeCommand,\n doctor: doctorCommand,\n image: imageCommand,\n recovery: recoveryCommand,\n };\n for (const [name, definition] of Object.entries(commandOptions)) {\n const path = name.split(\" \");\n let command = program;\n for (const part of path) {\n command =\n command.commands.find((item) => item.name() === part) ??\n command.command(part);\n }\n command.description(definition.description);\n for (const key of definition.options) {\n const configuration = cliOptions[key];\n const short = \"short\" in configuration ? `-${configuration.short}, ` : \"\";\n command.addOption(\n new Option(\n `${short}--${key}${configuration.type === \"string\" ? \" <value>\" : \"\"}`,\n configuration.description,\n ),\n );\n }\n if (name === \"init\")\n command.addOption(\n new Option(\n \"--no-build\",\n \"Skip automatic container image build\",\n ).default(undefined),\n );\n command.action(async () => {\n const values: Record<string, string | boolean> = {};\n const parsed = command.opts<Record<string, unknown>>();\n for (const option of command.options) {\n const value = parsed[option.attributeName()];\n if (typeof value === \"string\" || typeof value === \"boolean\")\n values[option.long!.replace(/^--(?:no-)?/, \"\")] = value;\n }\n await handlers[path[0]!]!({\n values: values as CliValues,\n positionals: path,\n });\n });\n }\n for (const command of program.commands.filter(\n (item) => item.commands.length,\n )) {\n command.allowExcessArguments().action(() => {\n throw new Error(`Unknown command. Run outpost ${command.name()} --help.`);\n });\n }\n try {\n await program.parseAsync(args.length ? [...args] : [\"--help\"], {\n from: \"user\",\n });\n } catch (error) {\n if (error instanceof CommanderError && error.exitCode === 0) return;\n throw error;\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ export declare const authenticationRecipes: {
2
+ readonly codex: {
3
+ readonly environment: "OPENAI_API_KEY=\n";
4
+ readonly login: "const {spawnSync}=require(\"node:child_process\"); const {existsSync}=require(\"node:fs\"); const {join}=require(\"node:path\"); const {homedir}=require(\"node:os\"); const installed=join(homedir(),\".outpost-tools\",\"bin\",\"codex\"); const result=spawnSync(existsSync(installed)?installed:\"codex\",[\"login\",\"--with-api-key\"],{stdio:\"inherit\",shell:process.platform===\"win32\"}); process.exit(result.status ?? 1);";
5
+ readonly seed: "const {mkdirSync,writeFileSync,readFileSync,chmodSync}=require(\"node:fs\"); const {join}=require(\"node:path\"); const {homedir}=require(\"node:os\"); const directory=join(homedir(),\".codex\"); mkdirSync(directory,{recursive:true,mode:0o700}); const target=join(directory,\"auth.json\"); writeFileSync(target,readFileSync(0),{mode:0o600}); chmodSync(target,0o600);";
6
+ };
7
+ readonly claude: {
8
+ readonly environment: "ANTHROPIC_API_KEY=\n";
9
+ };
10
+ readonly gemini: {
11
+ readonly environment: "GEMINI_API_KEY=\n";
12
+ };
13
+ };
@@ -0,0 +1,10 @@
1
+ export const authenticationRecipes = {
2
+ codex: {
3
+ environment: "OPENAI_API_KEY=\n",
4
+ login: 'const {spawnSync}=require("node:child_process"); const {existsSync}=require("node:fs"); const {join}=require("node:path"); const {homedir}=require("node:os"); const installed=join(homedir(),".outpost-tools","bin","codex"); const result=spawnSync(existsSync(installed)?installed:"codex",["login","--with-api-key"],{stdio:"inherit",shell:process.platform==="win32"}); process.exit(result.status ?? 1);',
5
+ seed: 'const {mkdirSync,writeFileSync,readFileSync,chmodSync}=require("node:fs"); const {join}=require("node:path"); const {homedir}=require("node:os"); const directory=join(homedir(),".codex"); mkdirSync(directory,{recursive:true,mode:0o700}); const target=join(directory,"auth.json"); writeFileSync(target,readFileSync(0),{mode:0o600}); chmodSync(target,0o600);',
6
+ },
7
+ claude: { environment: "ANTHROPIC_API_KEY=\n" },
8
+ gemini: { environment: "GEMINI_API_KEY=\n" },
9
+ };
10
+ //# sourceMappingURL=init-authentication.constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-authentication.constants.js","sourceRoot":"","sources":["../../src/cli/init-authentication.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,KAAK,EAAE;QACL,WAAW,EAAE,mBAAmB;QAChC,KAAK,EACH,iZAAiZ;QACnZ,IAAI,EAAE,sWAAsW;KAC7W;IACD,MAAM,EAAE,EAAE,WAAW,EAAE,sBAAsB,EAAE;IAC/C,MAAM,EAAE,EAAE,WAAW,EAAE,mBAAmB,EAAE;CACpC,CAAC","sourcesContent":["export const authenticationRecipes = {\n codex: {\n environment: \"OPENAI_API_KEY=\\n\",\n login:\n 'const {spawnSync}=require(\"node:child_process\"); const {existsSync}=require(\"node:fs\"); const {join}=require(\"node:path\"); const {homedir}=require(\"node:os\"); const installed=join(homedir(),\".outpost-tools\",\"bin\",\"codex\"); const result=spawnSync(existsSync(installed)?installed:\"codex\",[\"login\",\"--with-api-key\"],{stdio:\"inherit\",shell:process.platform===\"win32\"}); process.exit(result.status ?? 1);',\n seed: 'const {mkdirSync,writeFileSync,readFileSync,chmodSync}=require(\"node:fs\"); const {join}=require(\"node:path\"); const {homedir}=require(\"node:os\"); const directory=join(homedir(),\".codex\"); mkdirSync(directory,{recursive:true,mode:0o700}); const target=join(directory,\"auth.json\"); writeFileSync(target,readFileSync(0),{mode:0o600}); chmodSync(target,0o600);',\n },\n claude: { environment: \"ANTHROPIC_API_KEY=\\n\" },\n gemini: { environment: \"GEMINI_API_KEY=\\n\" },\n} as const;\n"]}
@@ -0,0 +1,4 @@
1
+ import type { InitOptions } from "./scaffold.types.ts";
2
+ export declare function authenticationEnvironment(options: InitOptions): string;
3
+ export declare function authenticationInstructions(options: InitOptions): string;
4
+ export declare function authenticationSource(options: InitOptions): string;
@@ -0,0 +1,43 @@
1
+ import { authenticationRecipes } from "./init-authentication.constants.js";
2
+ export function authenticationEnvironment(options) {
3
+ if (options.baseUrl)
4
+ return `${options.apiKeyEnvironment ?? "OPENAI_API_KEY"}=\n`;
5
+ if (options.authentication === "login")
6
+ return "";
7
+ if (options.authentication === "oauth-token")
8
+ return "CLAUDE_CODE_OAUTH_TOKEN=\n";
9
+ return authenticationRecipes[options.agent ?? "codex"].environment;
10
+ }
11
+ export function authenticationInstructions(options) {
12
+ if (options.authentication === "login")
13
+ return "Run codex login on the host. For isolated providers, use file credential storage; the generated script copies auth.json into the private sandbox home.";
14
+ if (options.authentication === "oauth-token")
15
+ return "Run claude setup-token on the host, then set CLAUDE_CODE_OAUTH_TOKEN in the workflow .env or parent environment. This uses your Claude subscription.";
16
+ return ("Set " +
17
+ authenticationEnvironment(options).trim().replace("=", "") +
18
+ " in the workflow .env or parent environment. API usage is billed separately from subscriptions.");
19
+ }
20
+ export function authenticationSource(options) {
21
+ const agent = options.agent ?? "codex";
22
+ if (options.authentication === "login") {
23
+ if (options.provider === "local")
24
+ return "const authentication = {};";
25
+ return `const seed = await readFile(resolve(process.env.CODEX_HOME || resolve(homedir(), ".codex"), "auth.json"), "utf8");
26
+ try { JSON.parse(seed); } catch { throw new Error("Invalid Codex authentication file. Run codex login again with file credential storage."); }
27
+ const authentication = { sandboxReady: [{ executable: "node", arguments: ["-e", ${JSON.stringify(authenticationRecipes.codex.seed)}], stdin: seed }] };`;
28
+ }
29
+ const key = authenticationEnvironment(options).trim().replace("=", "");
30
+ const conflict = agent === "claude"
31
+ ? key === "ANTHROPIC_API_KEY"
32
+ ? "CLAUDE_CODE_OAUTH_TOKEN"
33
+ : "ANTHROPIC_API_KEY"
34
+ : undefined;
35
+ const check = `if (!variables[${JSON.stringify(key)}]) throw new Error(${JSON.stringify(`Missing ${key}. Configure the workflow .env or parent environment before starting a sandbox.`)});`;
36
+ const guard = conflict
37
+ ? `\nif (variables[${JSON.stringify(conflict)}]) throw new Error("Conflicting Claude authentication methods. Keep only the selected credential in the workflow environment.");`
38
+ : "";
39
+ if (agent !== "codex" || options.baseUrl)
40
+ return `${check}${guard}\nconst authentication = {};`;
41
+ return `${check}\nconst authentication = { sandboxReady: [{ executable: "node", arguments: ["-e", ${JSON.stringify(authenticationRecipes.codex.login)}], stdin: variables.OPENAI_API_KEY }] };`;
42
+ }
43
+ //# sourceMappingURL=init-authentication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-authentication.js","sourceRoot":"","sources":["../../src/cli/init-authentication.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAE3E,MAAM,UAAU,yBAAyB,CAAC,OAAoB;IAC5D,IAAI,OAAO,CAAC,OAAO;QACjB,OAAO,GAAG,OAAO,CAAC,iBAAiB,IAAI,gBAAgB,KAAK,CAAC;IAC/D,IAAI,OAAO,CAAC,cAAc,KAAK,OAAO;QAAE,OAAO,EAAE,CAAC;IAClD,IAAI,OAAO,CAAC,cAAc,KAAK,aAAa;QAC1C,OAAO,4BAA4B,CAAC;IACtC,OAAO,qBAAqB,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC,WAAW,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAoB;IAC7D,IAAI,OAAO,CAAC,cAAc,KAAK,OAAO;QACpC,OAAO,wJAAwJ,CAAC;IAClK,IAAI,OAAO,CAAC,cAAc,KAAK,aAAa;QAC1C,OAAO,sJAAsJ,CAAC;IAChK,OAAO,CACL,MAAM;QACN,yBAAyB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QAC1D,iGAAiG,CAClG,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAoB;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC;IACvC,IAAI,OAAO,CAAC,cAAc,KAAK,OAAO,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;YAAE,OAAO,4BAA4B,CAAC;QACtE,OAAO;;kFAEuE,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC;IACvJ,CAAC;IACD,MAAM,GAAG,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACvE,MAAM,QAAQ,GACZ,KAAK,KAAK,QAAQ;QAChB,CAAC,CAAC,GAAG,KAAK,mBAAmB;YAC3B,CAAC,CAAC,yBAAyB;YAC3B,CAAC,CAAC,mBAAmB;QACvB,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,KAAK,GAAG,kBAAkB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,sBAAsB,IAAI,CAAC,SAAS,CAAC,WAAW,GAAG,gFAAgF,CAAC,IAAI,CAAC;IAC5L,MAAM,KAAK,GAAG,QAAQ;QACpB,CAAC,CAAC,mBAAmB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,kIAAkI;QAC/K,CAAC,CAAC,EAAE,CAAC;IACP,IAAI,KAAK,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO;QACtC,OAAO,GAAG,KAAK,GAAG,KAAK,8BAA8B,CAAC;IACxD,OAAO,GAAG,KAAK,qFAAqF,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,KAAK,CAAC,KAAK,CAAC,0CAA0C,CAAC;AAClM,CAAC","sourcesContent":["import type { InitOptions } from \"./scaffold.types.ts\";\nimport { authenticationRecipes } from \"./init-authentication.constants.ts\";\n\nexport function authenticationEnvironment(options: InitOptions): string {\n if (options.baseUrl)\n return `${options.apiKeyEnvironment ?? \"OPENAI_API_KEY\"}=\\n`;\n if (options.authentication === \"login\") return \"\";\n if (options.authentication === \"oauth-token\")\n return \"CLAUDE_CODE_OAUTH_TOKEN=\\n\";\n return authenticationRecipes[options.agent ?? \"codex\"].environment;\n}\n\nexport function authenticationInstructions(options: InitOptions): string {\n if (options.authentication === \"login\")\n return \"Run codex login on the host. For isolated providers, use file credential storage; the generated script copies auth.json into the private sandbox home.\";\n if (options.authentication === \"oauth-token\")\n return \"Run claude setup-token on the host, then set CLAUDE_CODE_OAUTH_TOKEN in the workflow .env or parent environment. This uses your Claude subscription.\";\n return (\n \"Set \" +\n authenticationEnvironment(options).trim().replace(\"=\", \"\") +\n \" in the workflow .env or parent environment. API usage is billed separately from subscriptions.\"\n );\n}\n\nexport function authenticationSource(options: InitOptions): string {\n const agent = options.agent ?? \"codex\";\n if (options.authentication === \"login\") {\n if (options.provider === \"local\") return \"const authentication = {};\";\n return `const seed = await readFile(resolve(process.env.CODEX_HOME || resolve(homedir(), \".codex\"), \"auth.json\"), \"utf8\");\ntry { JSON.parse(seed); } catch { throw new Error(\"Invalid Codex authentication file. Run codex login again with file credential storage.\"); }\nconst authentication = { sandboxReady: [{ executable: \"node\", arguments: [\"-e\", ${JSON.stringify(authenticationRecipes.codex.seed)}], stdin: seed }] };`;\n }\n const key = authenticationEnvironment(options).trim().replace(\"=\", \"\");\n const conflict =\n agent === \"claude\"\n ? key === \"ANTHROPIC_API_KEY\"\n ? \"CLAUDE_CODE_OAUTH_TOKEN\"\n : \"ANTHROPIC_API_KEY\"\n : undefined;\n const check = `if (!variables[${JSON.stringify(key)}]) throw new Error(${JSON.stringify(`Missing ${key}. Configure the workflow .env or parent environment before starting a sandbox.`)});`;\n const guard = conflict\n ? `\\nif (variables[${JSON.stringify(conflict)}]) throw new Error(\"Conflicting Claude authentication methods. Keep only the selected credential in the workflow environment.\");`\n : \"\";\n if (agent !== \"codex\" || options.baseUrl)\n return `${check}${guard}\\nconst authentication = {};`;\n return `${check}\\nconst authentication = { sandboxReady: [{ executable: \"node\", arguments: [\"-e\", ${JSON.stringify(authenticationRecipes.codex.login)}], stdin: variables.OPENAI_API_KEY }] };`;\n}\n"]}
@@ -1,30 +1,89 @@
1
+ import { invariant } from "../domain/errors.js";
1
2
  import { resolve } from "node:path";
2
- import { createInterface } from "node:readline/promises";
3
- import { initializationQuestions } from "./main.constants.js";
3
+ import { cancel, intro, isCancel, outro, select, spinner, } from "@clack/prompts";
4
+ import { projectSettings } from "./project-settings.js";
5
+ import { authenticationInstructions } from "./init-authentication.js";
6
+ import { supportedManagers } from "./scaffold.constants.js";
7
+ import { initializationQuestions, authenticationChoices, } from "./main.constants.js";
4
8
  import { initialize } from "./scaffold.js";
5
9
  export async function initializeCommand({ values, }) {
6
10
  const options = Object.fromEntries(Object.entries(values).filter(([key]) => !["help", "yes"].includes(key)));
11
+ if (values["base-url"] !== undefined)
12
+ options.baseUrl = values["base-url"];
13
+ if (values["api-key-env"] !== undefined)
14
+ options.apiKeyEnvironment = values["api-key-env"];
15
+ delete options["base-url"];
16
+ delete options["api-key-env"];
7
17
  if (!values.yes &&
8
18
  !process.stdin.isTTY &&
9
19
  (!values.agent || !values.provider))
10
20
  throw new Error("Headless initialization requires --yes for defaults, or --agent and --provider.");
11
21
  if (!values.yes && process.stdin.isTTY) {
12
- const terminal = createInterface({
13
- input: process.stdin,
14
- output: process.stdout,
15
- });
16
- try {
17
- for (const [key, label, fallback] of initializationQuestions) {
18
- options[key] ??=
19
- (await terminal.question(`${label} [${fallback}]: `)).trim() ||
20
- fallback;
22
+ intro("Create an Outpost workflow");
23
+ for (const question of initializationQuestions) {
24
+ if (options[question.key] !== undefined)
25
+ continue;
26
+ const value = await select({
27
+ message: question.message,
28
+ options: question.choices.map((value) => ({ value, label: value })),
29
+ });
30
+ if (isCancel(value)) {
31
+ cancel("Initialization cancelled.");
32
+ process.exitCode = 130;
33
+ return;
21
34
  }
35
+ options[question.key] = value;
22
36
  }
23
- finally {
24
- terminal.close();
37
+ const settings = await projectSettings(resolve(values.directory ?? process.cwd()), options);
38
+ const choices = Object.entries(authenticationChoices).find(([name]) => name === options.agent)?.[1];
39
+ invariant(choices, "Choose codex, claude or gemini");
40
+ const questions = [
41
+ {
42
+ key: "manager",
43
+ message: "Package manager",
44
+ options: supportedManagers.map((value) => ({ value, label: value })),
45
+ initialValue: settings.manager,
46
+ },
47
+ {
48
+ key: "authentication",
49
+ message: "Agent authentication",
50
+ options: [...choices],
51
+ initialValue: "api-key",
52
+ },
53
+ ];
54
+ for (const question of questions) {
55
+ if (options[question.key] !== undefined)
56
+ continue;
57
+ const value = await select(question);
58
+ if (isCancel(value)) {
59
+ cancel("Initialization cancelled.");
60
+ process.exitCode = 130;
61
+ return;
62
+ }
63
+ options[question.key] = value;
25
64
  }
26
65
  }
27
- const result = await initialize(options);
28
- process.stdout.write(`Initialized ${result.files.length} files in ${resolve(values.directory ?? process.cwd())}.\nRun: ${result.run}\n`);
66
+ options.build ??= ["docker", "podman"].includes(String(options.provider ?? "docker"));
67
+ const progress = !values.yes && process.stdin.isTTY ? spinner() : undefined;
68
+ progress?.start(options.build
69
+ ? "Creating project and building its container image"
70
+ : "Creating project");
71
+ let result;
72
+ try {
73
+ result = await initialize(options);
74
+ }
75
+ catch (error) {
76
+ progress?.stop("Initialization failed");
77
+ throw error;
78
+ }
79
+ progress?.stop("Project ready");
80
+ const installation = options.install
81
+ ? ""
82
+ : "Install project dependencies with your selected package manager before running.\n";
83
+ const message = `Initialized ${result.files.length} files in ${resolve(values.directory ?? process.cwd())}.\n${authenticationInstructions(options)}\n${installation}Run: ${result.run}`;
84
+ if (!values.yes && process.stdin.isTTY)
85
+ outro(message);
86
+ else
87
+ process.stdout.write(`${message}\n`);
29
88
  }
30
89
  //# sourceMappingURL=init-command.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"init-command.js","sourceRoot":"","sources":["../../src/cli/init-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EACtC,MAAM,GACQ;IACd,MAAM,OAAO,GAA4B,MAAM,CAAC,WAAW,CACzD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CACzE,CAAC;IACF,IACE,CAAC,MAAM,CAAC,GAAG;QACX,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;QACpB,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QAEnC,MAAM,IAAI,KAAK,CACb,iFAAiF,CAClF,CAAC;IACJ,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,eAAe,CAAC;YAC/B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,IAAI,CAAC;YACH,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,uBAAuB,EAAE,CAAC;gBAC7D,OAAO,CAAC,GAAG,CAAC;oBACV,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,GAAG,KAAK,KAAK,QAAQ,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;wBAC5D,QAAQ,CAAC;YACb,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAsB,CAAC,CAAC;IACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,eAAe,MAAM,CAAC,KAAK,CAAC,MAAM,aAAa,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,MAAM,CAAC,GAAG,IAAI,CACnH,CAAC;AACJ,CAAC","sourcesContent":["import { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport { initializationQuestions } from \"./main.constants.ts\";\nimport type { CliInvocation } from \"./main.types.ts\";\nimport { initialize } from \"./scaffold.ts\";\nimport type { InitOptions } from \"./scaffold.types.ts\";\n\nexport async function initializeCommand({\n values,\n}: CliInvocation): Promise<void> {\n const options: Record<string, unknown> = Object.fromEntries(\n Object.entries(values).filter(([key]) => ![\"help\", \"yes\"].includes(key)),\n );\n if (\n !values.yes &&\n !process.stdin.isTTY &&\n (!values.agent || !values.provider)\n )\n throw new Error(\n \"Headless initialization requires --yes for defaults, or --agent and --provider.\",\n );\n if (!values.yes && process.stdin.isTTY) {\n const terminal = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n try {\n for (const [key, label, fallback] of initializationQuestions) {\n options[key] ??=\n (await terminal.question(`${label} [${fallback}]: `)).trim() ||\n fallback;\n }\n } finally {\n terminal.close();\n }\n }\n const result = await initialize(options as InitOptions);\n process.stdout.write(\n `Initialized ${result.files.length} files in ${resolve(values.directory ?? process.cwd())}.\\nRun: ${result.run}\\n`,\n );\n}\n"]}
1
+ {"version":3,"file":"init-command.js","sourceRoot":"","sources":["../../src/cli/init-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,MAAM,EACN,KAAK,EACL,QAAQ,EACR,KAAK,EACL,MAAM,EACN,OAAO,GACR,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EACtC,MAAM,GACQ;IACd,MAAM,OAAO,GAA4B,MAAM,CAAC,WAAW,CACzD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CACzE,CAAC;IACF,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAC3E,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,SAAS;QACrC,OAAO,CAAC,iBAAiB,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IACpD,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9B,IACE,CAAC,MAAM,CAAC,GAAG;QACX,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;QACpB,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QAEnC,MAAM,IAAI,KAAK,CACb,iFAAiF,CAClF,CAAC;IACJ,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACvC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QACpC,KAAK,MAAM,QAAQ,IAAI,uBAAuB,EAAE,CAAC;YAC/C,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,SAAS;gBAAE,SAAS;YAClD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;gBACzB,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;aACpE,CAAC,CAAC;YACH,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpB,MAAM,CAAC,2BAA2B,CAAC,CAAC;gBACpC,OAAO,CAAC,QAAQ,GAAG,GAAG,CAAC;gBACvB,OAAO;YACT,CAAC;YACD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAChC,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,eAAe,CACpC,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,EAC1C,OAAsB,CACvB,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,IAAI,CACxD,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,KAAK,CACnC,EAAE,CAAC,CAAC,CAAC,CAAC;QACP,SAAS,CAAC,OAAO,EAAE,gCAAgC,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG;YAChB;gBACE,GAAG,EAAE,SAAS;gBACd,OAAO,EAAE,iBAAiB;gBAC1B,OAAO,EAAE,iBAAiB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;gBACpE,YAAY,EAAE,QAAQ,CAAC,OAAO;aAC/B;YACD;gBACE,GAAG,EAAE,gBAAgB;gBACrB,OAAO,EAAE,sBAAsB;gBAC/B,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;gBACrB,YAAY,EAAE,SAAS;aACxB;SACF,CAAC;QACF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,SAAS;gBAAE,SAAS;YAClD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;YACrC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpB,MAAM,CAAC,2BAA2B,CAAC,CAAC;gBACpC,OAAO,CAAC,QAAQ,GAAG,GAAG,CAAC;gBACvB,OAAO;YACT,CAAC;YACD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAChC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,KAAK,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAC7C,MAAM,CAAC,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,CACrC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5E,QAAQ,EAAE,KAAK,CACb,OAAO,CAAC,KAAK;QACX,CAAC,CAAC,mDAAmD;QACrD,CAAC,CAAC,kBAAkB,CACvB,CAAC;IACF,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,UAAU,CAAC,OAAsB,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACxC,MAAM,KAAK,CAAC;IACd,CAAC;IACD,QAAQ,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IAChC,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO;QAClC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,mFAAmF,CAAC;IACxF,MAAM,OAAO,GAAG,eAAe,MAAM,CAAC,KAAK,CAAC,MAAM,aAAa,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,0BAA0B,CAAC,OAAsB,CAAC,KAAK,YAAY,QAAQ,MAAM,CAAC,GAAG,EAAE,CAAC;IACvM,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,OAAO,CAAC,CAAC;;QAClD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["import { invariant } from \"../domain/errors.ts\";\nimport { resolve } from \"node:path\";\nimport {\n cancel,\n intro,\n isCancel,\n outro,\n select,\n spinner,\n} from \"@clack/prompts\";\nimport { projectSettings } from \"./project-settings.ts\";\nimport { authenticationInstructions } from \"./init-authentication.ts\";\nimport { supportedManagers } from \"./scaffold.constants.ts\";\nimport {\n initializationQuestions,\n authenticationChoices,\n} from \"./main.constants.ts\";\nimport type { CliInvocation } from \"./main.types.ts\";\nimport { initialize } from \"./scaffold.ts\";\nimport type { InitOptions } from \"./scaffold.types.ts\";\n\nexport async function initializeCommand({\n values,\n}: CliInvocation): Promise<void> {\n const options: Record<string, unknown> = Object.fromEntries(\n Object.entries(values).filter(([key]) => ![\"help\", \"yes\"].includes(key)),\n );\n if (values[\"base-url\"] !== undefined) options.baseUrl = values[\"base-url\"];\n if (values[\"api-key-env\"] !== undefined)\n options.apiKeyEnvironment = values[\"api-key-env\"];\n delete options[\"base-url\"];\n delete options[\"api-key-env\"];\n if (\n !values.yes &&\n !process.stdin.isTTY &&\n (!values.agent || !values.provider)\n )\n throw new Error(\n \"Headless initialization requires --yes for defaults, or --agent and --provider.\",\n );\n if (!values.yes && process.stdin.isTTY) {\n intro(\"Create an Outpost workflow\");\n for (const question of initializationQuestions) {\n if (options[question.key] !== undefined) continue;\n const value = await select({\n message: question.message,\n options: question.choices.map((value) => ({ value, label: value })),\n });\n if (isCancel(value)) {\n cancel(\"Initialization cancelled.\");\n process.exitCode = 130;\n return;\n }\n options[question.key] = value;\n }\n const settings = await projectSettings(\n resolve(values.directory ?? process.cwd()),\n options as InitOptions,\n );\n const choices = Object.entries(authenticationChoices).find(\n ([name]) => name === options.agent,\n )?.[1];\n invariant(choices, \"Choose codex, claude or gemini\");\n const questions = [\n {\n key: \"manager\",\n message: \"Package manager\",\n options: supportedManagers.map((value) => ({ value, label: value })),\n initialValue: settings.manager,\n },\n {\n key: \"authentication\",\n message: \"Agent authentication\",\n options: [...choices],\n initialValue: \"api-key\",\n },\n ];\n for (const question of questions) {\n if (options[question.key] !== undefined) continue;\n const value = await select(question);\n if (isCancel(value)) {\n cancel(\"Initialization cancelled.\");\n process.exitCode = 130;\n return;\n }\n options[question.key] = value;\n }\n }\n options.build ??= [\"docker\", \"podman\"].includes(\n String(options.provider ?? \"docker\"),\n );\n const progress = !values.yes && process.stdin.isTTY ? spinner() : undefined;\n progress?.start(\n options.build\n ? \"Creating project and building its container image\"\n : \"Creating project\",\n );\n let result;\n try {\n result = await initialize(options as InitOptions);\n } catch (error) {\n progress?.stop(\"Initialization failed\");\n throw error;\n }\n progress?.stop(\"Project ready\");\n const installation = options.install\n ? \"\"\n : \"Install project dependencies with your selected package manager before running.\\n\";\n const message = `Initialized ${result.files.length} files in ${resolve(values.directory ?? process.cwd())}.\\n${authenticationInstructions(options as InitOptions)}\\n${installation}Run: ${result.run}`;\n if (!values.yes && process.stdin.isTTY) outro(message);\n else process.stdout.write(`${message}\\n`);\n}\n"]}