Skip to content

Commit 1f15571

Browse files
committed
Add SSH flags to Codex examples and runners
1 parent 6f2e975 commit 1f15571

43 files changed

Lines changed: 826 additions & 221 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ applies to Codex CLI subprocesses and MCP HTTP/OAuth flows.
3333

3434
```bash
3535
./examples/run_all.sh
36+
./examples/run_all.sh --ssh-host example.internal
37+
./examples/run_all.sh --ssh-host builder@example.internal --ssh-port 2222
3638
```
3739

3840
Run the same CLI-backed example set against local Codex OSS + Ollama:
@@ -52,6 +54,27 @@ Run the same CLI-backed example set against local Codex OSS + Ollama:
5254
The runner checks that the requested Ollama model is installed before starting
5355
the examples.
5456

57+
SSH routing is explicit and flag-driven. When you pass `--ssh-host`, the
58+
CLI/app-server examples switch to `execution_surface: :ssh_exec` while keeping
59+
their existing local default when you omit the flag.
60+
61+
Supported SSH flags for CLI/app-server examples:
62+
63+
- `--ssh-host <host>` or `--ssh-host <user>@<host>`
64+
- `--ssh-user <user>`
65+
- `--ssh-port <port>`
66+
- `--ssh-identity-file <path>`
67+
68+
`--ssh-host` is mutually exclusive with `--ollama`, because `--ollama` is the
69+
local OSS route and `--ssh-host` is remote subprocess placement.
70+
71+
`./examples/run_all.sh --ssh-host ...` applies only to examples that actually
72+
run through the Codex CLI execution surface. It does not apply to the direct
73+
Realtime/Voice examples, and it intentionally skips
74+
`examples/live_oauth_login.exs` because that example demonstrates local OAuth
75+
session storage and local browser/device login flow rather than subprocess
76+
placement.
77+
5578
`gpt-oss:20b` remains the default validated Codex/Ollama example model, but
5679
the runner also accepts other installed Ollama models such as `llama3.2`.
5780
Those non-default models may trigger upstream fallback metadata warnings and
@@ -91,6 +114,21 @@ Prereqs:
91114

92115
The `live_*.exs` scripts hit the live Codex CLI (no OPENAI_API_KEY needed if you are authenticated via `codex login`).
93116

117+
Default local usage stays unchanged:
118+
119+
```bash
120+
mix run examples/live_cli_demo.exs "What is the capital of France?"
121+
mix run examples/live_app_server_basic.exs "Reply with exactly ok and nothing else."
122+
```
123+
124+
SSH usage for CLI/app-server examples is explicit:
125+
126+
```bash
127+
mix run examples/live_cli_demo.exs -- --ssh-host example.internal "What is the capital of France?"
128+
mix run examples/live_app_server_basic.exs -- --ssh-host builder@example.internal --ssh-port 2222 "Reply with exactly ok and nothing else."
129+
mix run examples/live_cli_session.exs -- --ssh-host example.internal "Summarize this repository in three bullets."
130+
```
131+
94132
- `examples/live_cli_demo.exs` — minimal Q&A against the live CLI
95133
- `examples/live_cli_passthrough.exs` — direct wrappers for `completion`, `features`, `login status`, and arbitrary raw `codex` argv
96134
- `examples/live_cli_session.exs` — PTY-backed root `codex` prompt mode via `Codex.CLI.interactive/2`
@@ -123,6 +161,11 @@ The `live_*.exs` scripts hit the live Codex CLI (no OPENAI_API_KEY needed if you
123161
- `examples/live_options_config_overrides.exs` — options-level global config overrides, precedence, runtime validation, and reserved-provider notes
124162
- `examples/live_parity_and_status.exs` — quick pointers to parity docs/fixtures and CLI availability
125163

164+
`examples/live_oauth_login.exs` remains local-only for its primary flow. The
165+
OAuth session storage, browser launch, and device-code UX are local host
166+
concerns, not `execution_surface` concerns, so `--ssh-host` is not documented
167+
for that script.
168+
126169
## Realtime Voice Examples (OpenAI Agents SDK)
127170

128171
These examples use the OpenAI Realtime API directly (not via Codex CLI). They demonstrate real-time bidirectional voice interactions:

examples/agent_runner_multi_turn.exs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
#!/usr/bin/env mix run
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
defmodule Examples.AgentRunner do
410
@moduledoc false
511

612
def run do
7-
{:ok, thread} = Codex.start_thread(%{})
13+
{:ok, thread} = Codex.start_thread(Support.codex_options!())
814

915
{:ok, result} =
1016
Codex.AgentRunner.run(thread, "Plan a short checklist",

examples/basic_usage.exs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
#!/usr/bin/env mix run
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
alias Codex.Items
410

511
defmodule Examples.BasicUsage do
612
@moduledoc false
713

814
def ask_question do
9-
with {:ok, thread} <- Codex.start_thread(%{}),
15+
with {:ok, thread} <- Codex.start_thread(Support.codex_options!()),
1016
{:ok, result} <- Codex.Thread.run(thread, "What is a GenServer in Elixir?") do
1117
IO.puts("Final response:\n#{render_response(result.final_response)}\n")
1218
IO.puts("Token usage: #{format_usage(result.usage)}")
@@ -16,7 +22,7 @@ defmodule Examples.BasicUsage do
1622
def list_completed_items do
1723
prompt = "Explain how Elixir processes work, and give me a simple example"
1824

19-
with {:ok, thread} <- Codex.start_thread(%{}),
25+
with {:ok, thread} <- Codex.start_thread(Support.codex_options!()),
2026
{:ok, result} <- Codex.Thread.run(thread, prompt) do
2127
Enum.each(result.events, &print_event/1)
2228
end

examples/concurrency_and_collaboration.exs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
#!/usr/bin/env mix run
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
alias Codex.Items
410

511
defmodule Examples.Concurrency do
@@ -13,7 +19,7 @@ defmodule Examples.Concurrency do
1319
tasks =
1420
Enum.map(files, fn file ->
1521
Task.Supervisor.async_nolink(supervisor, fn ->
16-
{:ok, thread} = Codex.start_thread(%{})
22+
{:ok, thread} = Codex.start_thread(Support.codex_options!())
1723

1824
prompt =
1925
"Give 2 quick risk notes you'd flag for a module like #{file} based only on its path/name. " <>
@@ -40,7 +46,7 @@ defmodule Examples.Concurrency do
4046
items
4147
|> Enum.map(fn item ->
4248
Task.Supervisor.async_nolink(supervisor, fn ->
43-
{:ok, thread} = Codex.start_thread(%{})
49+
{:ok, thread} = Codex.start_thread(Support.codex_options!())
4450
prompt = "Process #{item} and summarise it succinctly (1-2 sentences)."
4551

4652
case Codex.Thread.run(thread, prompt, %{timeout_ms: timeout_ms, max_turns: 1}) do
@@ -51,7 +57,7 @@ defmodule Examples.Concurrency do
5157
end)
5258
|> await_many_with_progress(timeout_ms: 60_000, tick_ms: 5_000, label: "map-reduce")
5359

54-
{:ok, thread} = Codex.start_thread(%{})
60+
{:ok, thread} = Codex.start_thread(Support.codex_options!())
5561

5662
prompt = """
5763
Summarise these analyses into a concise checklist:
@@ -65,7 +71,7 @@ defmodule Examples.Concurrency do
6571

6672
def collaboration(file) do
6773
timeout_ms = 20_000
68-
{:ok, analyzer} = Codex.start_thread(%{})
74+
{:ok, analyzer} = Codex.start_thread(Support.codex_options!())
6975

7076
{:ok, analysis} =
7177
Codex.Thread.run(
@@ -75,7 +81,7 @@ defmodule Examples.Concurrency do
7581
%{timeout_ms: timeout_ms, max_turns: 1}
7682
)
7783

78-
{:ok, security} = Codex.start_thread(%{})
84+
{:ok, security} = Codex.start_thread(Support.codex_options!())
7985

8086
{:ok, security_review} =
8187
Codex.Thread.run(
@@ -88,7 +94,7 @@ defmodule Examples.Concurrency do
8894
%{timeout_ms: timeout_ms, max_turns: 1}
8995
)
9096

91-
{:ok, performance} = Codex.start_thread(%{})
97+
{:ok, performance} = Codex.start_thread(Support.codex_options!())
9298

9399
{:ok, perf_review} =
94100
Codex.Thread.run(
@@ -101,7 +107,7 @@ defmodule Examples.Concurrency do
101107
%{timeout_ms: timeout_ms, max_turns: 1}
102108
)
103109

104-
{:ok, synthesizer} = Codex.start_thread(%{})
110+
{:ok, synthesizer} = Codex.start_thread(Support.codex_options!())
105111

106112
prompt = """
107113
Synthesize these reviews into actionable recommendations:

examples/conversation_and_resume.exs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
#!/usr/bin/env mix run
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
alias Codex.ExamplesSupport
410
alias Codex.Items
511

612
defmodule Examples.Conversation do
713
@moduledoc false
814

915
def multi_turn do
10-
{:ok, codex_opts} =
11-
Codex.Options.new(%{model: ExamplesSupport.example_model()})
16+
codex_opts =
17+
Support.codex_options!(%{model: ExamplesSupport.example_model()})
1218

1319
{:ok, thread} = Codex.start_thread(codex_opts)
1420

@@ -36,8 +42,8 @@ defmodule Examples.Conversation do
3642
end
3743

3844
def resume_existing(thread_id) do
39-
{:ok, codex_opts} =
40-
Codex.Options.new(%{model: ExamplesSupport.example_model()})
45+
codex_opts =
46+
Support.codex_options!(%{model: ExamplesSupport.example_model()})
4147

4248
{:ok, thread} = Codex.resume_thread(thread_id, codex_opts)
4349

@@ -48,8 +54,8 @@ defmodule Examples.Conversation do
4854
end
4955

5056
def save_and_resume_demo do
51-
{:ok, codex_opts} =
52-
Codex.Options.new(%{model: ExamplesSupport.example_model()})
57+
codex_opts =
58+
Support.codex_options!(%{model: ExamplesSupport.example_model()})
5359

5460
{:ok, thread} = Codex.start_thread(codex_opts)
5561
{:ok, result1} = Codex.Thread.run(thread, "Remember the number 42 for me.")

examples/live_app_server_approvals.exs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
Mix.Task.run("app.start")
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
alias Codex.AppServer.ApprovalDecision
410
alias Codex.Auth
511
alias Codex.Config.LayerStack
@@ -43,17 +49,13 @@ defmodule CodexExamples.LiveAppServerApprovals do
4349
values -> Enum.join(values, " ")
4450
end
4551

46-
with {:ok, codex_path} <- fetch_codex_path(),
47-
:ok <- ensure_app_server_supported(codex_path),
52+
with {:ok, codex_opts} <- Support.codex_options(%{}, missing_cli: :skip),
53+
:ok <- Support.ensure_app_server_supported(codex_opts),
4854
{:ok, fixture} <- build_demo_fixture() do
4955
try do
5056
permissions_prompt = permissions_prompt(fixture)
5157

52-
with {:ok, codex_opts} <-
53-
Codex.Options.new(%{
54-
codex_path_override: codex_path
55-
}),
56-
{:ok, conn, experimental_api?, init_fallback_reason} <-
58+
with {:ok, conn, experimental_api?, init_fallback_reason} <-
5759
connect_for_approvals_demo(codex_opts, fixture) do
5860
try do
5961
parent = self()

examples/live_app_server_basic.exs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
Mix.Task.run("app.start")
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
alias Codex.Items.AgentMessage
410

511
defmodule CodexExamples.LiveAppServerBasic do
@@ -24,13 +30,9 @@ defmodule CodexExamples.LiveAppServerBasic do
2430
values -> Enum.join(values, " ")
2531
end
2632

27-
with {:ok, codex_path} <- fetch_codex_path(),
28-
:ok <- ensure_app_server_supported(codex_path),
29-
:ok <- ensure_auth_available(),
30-
{:ok, codex_opts} <-
31-
Codex.Options.new(%{
32-
codex_path_override: codex_path
33-
}),
33+
with {:ok, codex_opts} <- Support.codex_options(%{}, missing_cli: :skip),
34+
:ok <- Support.ensure_auth_available(),
35+
:ok <- Support.ensure_app_server_supported(codex_opts),
3436
{:ok, conn} <- Codex.AppServer.connect(codex_opts, init_timeout_ms: 30_000) do
3537
try do
3638
personality = :friendly

examples/live_app_server_filesystem.exs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
Mix.Task.run("app.start")
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
defmodule CodexExamples.LiveAppServerFilesystem do
410
@moduledoc false
511

@@ -19,13 +25,9 @@ defmodule CodexExamples.LiveAppServerFilesystem do
1925
end
2026

2127
defp run do
22-
with {:ok, codex_path} <- fetch_codex_path(),
23-
:ok <- ensure_app_server_supported(codex_path),
24-
:ok <- ensure_auth_available(),
25-
{:ok, codex_opts} <-
26-
Codex.Options.new(%{
27-
codex_path_override: codex_path
28-
}),
28+
with {:ok, codex_opts} <- Support.codex_options(%{}, missing_cli: :skip),
29+
:ok <- Support.ensure_auth_available(),
30+
:ok <- Support.ensure_app_server_supported(codex_opts),
2931
{:ok, conn} <- Codex.AppServer.connect(codex_opts, init_timeout_ms: 30_000) do
3032
root =
3133
Path.join(

examples/live_app_server_mcp.exs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
Mix.Task.run("app.start")
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
defmodule CodexExamples.LiveAppServerMcp do
410
@moduledoc false
511

612
def main(_argv) do
7-
codex_path = fetch_codex_path!()
8-
ensure_app_server_supported!(codex_path)
9-
10-
{:ok, codex_opts} =
11-
Codex.Options.new(%{
12-
codex_path_override: codex_path
13-
})
13+
codex_opts = Support.codex_options!()
14+
:ok = Support.ensure_app_server_supported(codex_opts)
1415

1516
{:ok, conn} = Codex.AppServer.connect(codex_opts, init_timeout_ms: 30_000)
1617

examples/live_app_server_plugins.exs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
Mix.Task.run("app.start")
22

3+
Code.require_file(Path.expand("support/example_helper.exs", __DIR__))
4+
5+
alias CodexExamples.Support
6+
7+
Support.init!()
8+
39
defmodule CodexExamples.LiveAppServerPlugins do
410
@moduledoc false
511

@@ -19,15 +25,11 @@ defmodule CodexExamples.LiveAppServerPlugins do
1925
end
2026

2127
defp run do
22-
with {:ok, codex_path} <- fetch_codex_path(),
23-
:ok <- ensure_app_server_supported(codex_path),
28+
with {:ok, codex_opts} <- Support.codex_options(%{}, missing_cli: :skip),
29+
:ok <- Support.ensure_app_server_supported(codex_opts),
2430
{:ok, fixture} <- build_demo_plugin_fixture() do
2531
try do
26-
with {:ok, codex_opts} <-
27-
Codex.Options.new(%{
28-
codex_path_override: codex_path
29-
}),
30-
{:ok, conn} <-
32+
with {:ok, conn} <-
3133
Codex.AppServer.connect(codex_opts,
3234
cwd: fixture.repo_root,
3335
process_env: isolated_process_env(fixture),

0 commit comments

Comments
 (0)