Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
a0f8bfc
feat(codex): honor CODEX_SANDBOX_MODE/CODEX_APPROVAL_POLICY + add tes…
christian-mcdermott Jul 27, 2026
b1b5c60
fix(codex): derive default agent-name prefix from the chosen provider…
christian-mcdermott Jul 28, 2026
c483547
fix(codex): persist Switch creds on disk for providers without repo-a…
christian-mcdermott Jul 28, 2026
d9f8f04
feat(codex): register Codex as its own gateway known-agent (CHOO-1436)
christian-mcdermott Jul 28, 2026
96e8eae
refactor(codex): address PR self-review findings (CHOO-1436)
christian-mcdermott Jul 29, 2026
116f288
fix(agents): write per-agent Switch credentials for every provider (C…
christian-mcdermott Jul 29, 2026
cbf798e
fix(agents): require an explicit gateway known-agent type (CHOO-1436)
christian-mcdermott Jul 29, 2026
a049f89
fix(agents): keep relative Switch settings paths POSIX (CHOO-1436)
christian-mcdermott Jul 29, 2026
6ab047e
fix(agents): stop warning when a credentials file is simply absent (C…
christian-mcdermott Jul 29, 2026
83f4698
fix(agents): inject the Switch identity on the remote launch path (CH…
christian-mcdermott Jul 29, 2026
29c2c3c
fix(agents): version the storage-migration marker (CHOO-1436)
christian-mcdermott Jul 29, 2026
3ed3344
refactor(agents): collapse to one per-agent credential writer (CHOO-1…
christian-mcdermott Jul 29, 2026
8e5e047
fix(codex): fail loud on a corrupt hooks file, drop the stale flag co…
christian-mcdermott Jul 29, 2026
01c800b
feat(codex): session-control parity for reset/compact/interrupt (CHOO…
christian-mcdermott Jul 29, 2026
a813da4
test(agents): cover the agent_type registration contract (CHOO-1436)
christian-mcdermott Jul 29, 2026
d0d5e03
fix(codex): interrupt before /compact, which Codex also drops mid-tur…
christian-mcdermott Jul 30, 2026
54cb3ca
test(agents): observe the files, not a spy on a deleted hook (CHOO-1436)
christian-mcdermott Jul 30, 2026
9b12639
refactor(agents): make the creds slug a pure read of the session (CHO…
christian-mcdermott Jul 30, 2026
953b0bf
perf(agents): scope the migration re-run to what its generation can f…
christian-mcdermott Jul 30, 2026
1de30de
feat(codex): connect Codex to Switch through its own connector plugin…
christian-mcdermott Jul 29, 2026
c8e77a6
fix(codex): track the agent's room from connect_to_room (CHOO-1436)
christian-mcdermott Jul 29, 2026
eb0eb05
fix(codex): post the real hook payload from the generated command (CH…
christian-mcdermott Jul 29, 2026
5b49ef0
fix(codex): give remote sessions the Switch MCP server (CHOO-1436)
christian-mcdermott Jul 29, 2026
3936ecd
refactor(codex): declare what argv MCP registration actually supports…
christian-mcdermott Jul 29, 2026
f5dc094
fix(agent-hooks): read room ids from an MCP tool result of any shape …
christian-mcdermott Jul 29, 2026
d821613
fix(switch-setup): drop the dead remote version probe, cover the code…
christian-mcdermott Jul 29, 2026
1512648
docs: cover both agent connectors and backport the skill fixes (CHOO-…
christian-mcdermott Jul 29, 2026
36d569f
feat(mcp): expose cancel_task, the one task tool agents were told to …
christian-mcdermott Jul 29, 2026
e75a62e
test(codex): a probe for the two hook behaviours we inferred (CHOO-1436)
christian-mcdermott Jul 29, 2026
ab87a3c
test(codex): run the probe, pin what Codex actually sends (CHOO-1436)
christian-mcdermott Jul 29, 2026
512db06
fix(codex): remote auto-sessions could never launch (CHOO-1436)
christian-mcdermott Jul 30, 2026
3ab1841
fix(codex): correct the argv-override semantics and stop overstating …
christian-mcdermott Jul 30, 2026
9ac7397
fix(codex): run switchdash's hooks for every session, not just auto-a…
christian-mcdermott Jul 30, 2026
875862d
fix(agents): remove the per-agent token on delete for every provider …
christian-mcdermott Jul 30, 2026
b379c03
fix(agents): move an agent's files when it is renamed (CHOO-1436)
christian-mcdermott Jul 30, 2026
79c597e
fix(codex): stop reporting a stale connector as up to date (CHOO-1436)
christian-mcdermott Jul 30, 2026
1f73c48
docs(connectors): fail the Claude skill's upload curl loudly too (CHO…
christian-mcdermott Jul 30, 2026
4773178
docs(connectors): bump switch-connector to 0.3.1 for the skill accura…
christian-mcdermott Jul 30, 2026
48a0273
chore(core): sync uv.lock to switch-core 0.10.0 after rebase onto mai…
christian-mcdermott Jul 30, 2026
5ab0d30
refactor(codex): fix the sandbox flags and mirror them in the registr…
christian-mcdermott Jul 31, 2026
d1e88ec
fix(agents): reject an agent name already taken in the location (CHOO…
christian-mcdermott Jul 31, 2026
8fe737f
fix(fs): make PluginFs.delete fail loud on anything but a missing fil…
christian-mcdermott Jul 31, 2026
32374fb
fix(switch-setup): repair the marketplace before a remove-then-add up…
christian-mcdermott Jul 31, 2026
8da1e85
fix(switch-setup): disclose when a connector update cannot be detecte…
christian-mcdermott Jul 31, 2026
41ddb8f
fix(codex): refuse to launch when the MCP server name is already take…
christian-mcdermott Jul 31, 2026
aac82fa
fix(agent-hooks): require both ids to recognise a connect_to_room res…
christian-mcdermott Jul 31, 2026
b287d3c
fix(plugins): keep relative PluginFs paths POSIX (CHOO-1436)
christian-mcdermott Jul 31, 2026
56eb45f
fix(mcp): correct tool docstrings that misdescribe their own contract…
christian-mcdermott Jul 31, 2026
7d45962
docs(connectors): document the role-authoring tools and guard skill d…
christian-mcdermott Jul 31, 2026
d1849c9
style(switchdash): drop change narration and first person from commen…
christian-mcdermott Jul 31, 2026
b85c82f
docs(telemetry): document what Switch Console collects and why it is …
amaudruz Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "switch-plugins",
"description": "Switch platform plugins for Claude Code",
"description": "Switch platform connector plugins for Claude Code and Codex",
"owner": {
"name": "SandboxAQ"
},
Expand All @@ -11,6 +11,12 @@
"description": "Connect Claude Code to a Switch platform instance as a participating agent",
"category": "integrations",
"source": "./connectors/claude-code-plugin"
},
{
"name": "switch-connector-codex",
"description": "Connect Codex to a Switch platform instance as a participating agent",
"category": "integrations",
"source": "./connectors/codex-plugin"
}
]
}
42 changes: 30 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,36 @@ just test -k "test_name" # run specific test
- Session management: API endpoints use middleware-provided sessions; background work creates sessions explicitly
- All participants in rooms are Matrix clients (matrix-nio) connecting to Tuwunel

## Claude Code connector plugin

The `switch-connector` Claude Code plugin lives in `connectors/claude-code-plugin/`
(skill in `skills/switch/SKILL.md`, MCP server, hooks, channel). When you change
how agents interact with Switch — new/changed MCP tools, in-room commands,
room workflow, or anything an agent-facing client needs to know:

- **Update the skill** (`connectors/claude-code-plugin/skills/switch/SKILL.md`)
so the documented workflow matches the actual behavior.
- **Bump the plugin version** in
`connectors/claude-code-plugin/.claude-plugin/plugin.json` so installs pick
up the change.
## Connector plugins

There are **two** connector plugins under `connectors/`, one per agent host, and
each ships its own copy of the Switch room-workflow skill at
`skills/switch/SKILL.md`:

- `connectors/claude-code-plugin/` — manifest `.claude-plugin/plugin.json`.
Ships the skill plus an MCP config (`.mcp.json`), hooks, and a local channel
process.
- `connectors/codex-plugin/` — manifest `.codex-plugin/plugin.json`. Ships
**only** the skill. Codex does not expand `${VAR}` in a plugin-bundled
`.mcp.json` and has no `${CLAUDE_PLUGIN_ROOT}` equivalent, so switchdash
registers the Switch MCP server on argv when it launches the session instead.

When you change how agents interact with Switch — new/changed MCP tools, in-room
commands, room workflow, or anything an agent-facing client needs to know:

- **Update both skills.** A room-workflow change must land in
`connectors/claude-code-plugin/skills/switch/SKILL.md` *and*
`connectors/codex-plugin/skills/switch/SKILL.md` so the documented workflow
matches actual behavior on both hosts.
- **Bump both plugin versions** —
`connectors/claude-code-plugin/.claude-plugin/plugin.json` and
`connectors/codex-plugin/.codex-plugin/plugin.json` — so installs pick up the
change.
- **Diff the two skills after editing.** They are deliberately not identical
(host-specific wording for tool namespacing, event delivery and task
notifications, attachments, and MCP registration), so diff them to confirm
every remaining difference is intentional rather than a fix that only landed
on one side.

## Code Style

Expand Down
57 changes: 51 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,19 @@ Once it's up, open:
| Mattermost (chat with agents) | <http://localhost:8065> | `user` / `user1234` |

**First run — connect your own agent.** Switch ships no bundled agents; the
point is to plug in yours. The quickest path is the **Claude Code connector**
in [`connectors/claude-code-plugin/`](connectors/claude-code-plugin/):
point is to plug in yours. The quickest path is a bundled connector — the
**Claude Code connector** in
[`connectors/claude-code-plugin/`](connectors/claude-code-plugin/), or the
**Codex connector** in [`connectors/codex-plugin/`](connectors/codex-plugin/):

1. In the gateway, create a room (and note its id).
2. Install and configure the connector so Claude Code registers as a Switch
agent and joins the room — the plugin's `configure` skill walks you through
registering with this Switch instance and writing the credentials.
2. Install and configure the connector so your agent registers as a Switch
agent and joins the room. For Claude Code, the plugin's `configure` skill
walks you through registering with this Switch instance and writing the
credentials. The Codex plugin ships the room-workflow skill only — its
Switch MCP server is registered by the [switchdash desktop app](dash/) when
it launches the session, so set the agent up there; see
[`connectors/codex-plugin/README.md`](connectors/codex-plugin/README.md).
3. Talk to the agent from Mattermost, and watch the interaction in the gateway.

Stop the stack with `just standalone-down`, or `just standalone-reset` to also
Expand Down Expand Up @@ -172,7 +178,7 @@ The operator dashboard frontend lives in [`gateway/`](gateway/)
| `core/tests/` | Test suite, mirroring the `switch_core/` module structure |
| `gateway/` | Operator dashboard frontend (Node/Vite) |
| `dash/` | The switchdash desktop app |
| `connectors/` | Agent connectors (`claude-code-plugin`) |
| `connectors/` | Agent connectors (`claude-code-plugin`, `codex-plugin`) |
| `deploy/` | Deployment assets — Docker Compose stacks (`local/`) and shared resources |
| `justfile` | Repo-root task runner (drives all three code trees) |

Expand All @@ -182,6 +188,45 @@ Tests live in `core/tests/switch_core/` and mirror the module structure. The
suite uses pytest with pytest-asyncio; store tests run against a real
PostgreSQL instance (not mocks, not SQLite). Run them with `just test`.

## Telemetry

Switch Console collects anonymous usage analytics to help us understand how the
app is used and improve it. Telemetry is **opt-in** — you are asked during first
run, and nothing is sent without your explicit agreement. Switch Core (the server) sends no telemetry at all.

What we collect:

| Data | Example | Purpose |
| --- | --- | --- |
| Event name | `session_started`, `room_created` | Understand which features are used |
| App version | `0.9.14` | Track adoption of new releases |
| Release channel | `stable` | Separate pre-release from released usage |
| Operating system | `darwin`, `23.6.0` | Prioritise platform support |
| Agent provider | `claude`, `codex` | Understand which agents people run |
| Outcome and error code | `failure`, `docker_daemon_down` | Prioritise bug fixes |
| Counts and flags | `agent_count: 3`, `has_initial_prompt: true` | Size features without seeing content |
| Anonymous client ID | `3f2a9c41-…` (random UUID) | Count unique installations |

Every field is drawn from a fixed vocabulary of enumerated values, numbers and
booleans — free text cannot be transmitted.

**What we never collect:** source code, prompts, file paths, working
directories, repository or project names, room or agent names, server URLs or
hostnames, usernames, emails, API keys or credentials, model outputs, search
queries, error messages or stack traces, IP addresses, or any personally
identifiable information.

Events are sent to a relay we operate (`telemetry.flintai.dev`), which forwards
them to our analytics providers; no vendor credentials ship in the app.

**Opting out:** turn off *Send anonymous usage data* in Settings → Telemetry.
Sending stops immediately — the setting is checked before every event — and you
can change it back at any time. Declining at first run leaves telemetry off.

For the complete field-by-field list of every event, how collection is enforced,
where the data goes and why it cannot be traced to a person, see
[`docs/TELEMETRY.md`](docs/TELEMETRY.md).

## License

Agent Switch is licensed under the **Apache License 2.0 with the Commons Clause**
Expand Down
2 changes: 1 addition & 1 deletion connectors/claude-code-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "switch-connector",
"version": "0.3.0",
"version": "0.3.2",
"description": "Connect Claude Code to a Switch platform instance as a participating agent"
}
49 changes: 38 additions & 11 deletions connectors/claude-code-plugin/skills/switch/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: switch
description: REQUIRED before calling ANY `mcp__plugin_switch-connector_switch__*` tool (list_rooms, connect_to_room, read_context, post_message, send_targeted_message, list_participants, delegate_task, accept_task, update_task, finalise_task, cancel_task, list_tasks, create_room, invite_agent_to_room, list_all_rooms, get_room_detail, list_bridges, list_reference_types, create_reference, attach_reference_to_room, link_rooms, unlink_rooms, list_room_groups, create_room_group, get_room_group_detail, list_agents, get_agent_detail, update_agent_detail). Load this skill the moment the user mentions Switch, a Switch room, joining/connecting to a room, listing rooms, posting in a room, creating a room, creating a room group, creating a reference, linking rooms, inspecting or updating an agent, or interacting with other Switch agents — BEFORE you call any tool. The skill explains the room workflow, interaction modes, the task-protocol lifecycle, the moderation tools (room creation, invites, references, links), and the rules you must follow to participate correctly.
description: REQUIRED before calling ANY `mcp__plugin_switch-connector_switch__*` tool (list_rooms, connect_to_room, read_context, post_message, send_targeted_message, list_participants, list_roles, get_role_detail, assume_role, release_role, define_role, edit_role, delete_role, delegate_task, accept_task, update_task, finalise_task, cancel_task, list_tasks, create_room, invite_agent_to_room, list_all_rooms, get_room_detail, list_bridges, list_reference_types, create_reference, attach_reference_to_room, link_rooms, unlink_rooms, list_room_groups, create_room_group, get_room_group_detail, list_agents, get_agent_detail, update_agent_detail). Load this skill the moment the user mentions Switch, a Switch room, joining/connecting to a room, listing rooms, posting in a room, creating a room, creating a room group, creating a reference, linking rooms, inspecting or updating an agent, or interacting with other Switch agents — BEFORE you call any tool. The skill explains the room workflow, interaction modes, the task-protocol lifecycle, the moderation tools (room creation, invites, references, links), and the rules you must follow to participate correctly.
---

# Switch Room Workflow
Expand Down Expand Up @@ -34,9 +34,11 @@ message bus.
(freshest last). Top-level messages are roots with an empty `replies`
list. Every message carries an `id` — use it as `thread_id` to reply into
that thread (see "Threads" below).
5. **Check participants** — call `list_participants` to see who else is in
the room, the room role each currently holds (if any), their `agent_type`,
and their task capabilities.
5. **Check participants** — call `list_participants` for the current roster
(`id`, `name`, `type`, `status`, `alias`). Each participant's
`agent_type`, task capabilities and room role come from the
`participants` array in the `connect_to_room` payload, or from
`get_room_detail`.
6. **Act** — see the interaction modes below.

## Interaction modes
Expand Down Expand Up @@ -115,7 +117,7 @@ bridge as a real platform file upload (Slack, Mattermost).
name is bolded in the file's comment instead.
- **No channel tool available?** (e.g. a switchdash-managed session where the
channel process is not running): upload directly to the bridge API —
`curl -X POST "$SWITCH_API_ENDPOINT/agents/$SWITCH_AGENT_ID/rooms/<room_id>/media"
`curl -fsS -X POST "$SWITCH_API_ENDPOINT/agents/$SWITCH_AGENT_ID/rooms/<room_id>/media"
-H "Authorization: Bearer $SWITCH_API_TOKEN" -F "files=@/path/to/report.md"
-F "caption=..."` (optional `-F "thread_id=..."`; repeat `-F "files=@..."`
for several files in one message). Returns the posted `event_id`.
Expand Down Expand Up @@ -292,9 +294,12 @@ applies.
- **`update_agent_detail`** — change an agent's editable settings.
**Owner-only**: you may only update an agent whose owner matches your own
owner. `options` is a PARTIAL map of known-agent options merged over the
current ones (for a claude-code agent: `repo_dir` (working directory),
`channels_enabled`, `notify_user`, `subagent_name`) — only the keys you
pass change. `parent_agent_id` sets the agent's parent (validated against
current ones — the keys differ per known-agent type. For `codex`:
`repo_dir` (working directory), `notify_user`, `auto_session`. For
`claude-code`: those plus `channels_enabled` and `subagent_name`. Only the
keys you pass change, and a key the type does not define is ignored rather
than rejected — so check the returned detail rather than assuming a write
landed. `parent_agent_id` sets the agent's parent (validated against
self-parenting and cycles); `clear_parent=true` detaches it to top-level.
- **`list_reference_types`** — discover the Reference sub-types this
instance supports, including the per-type `value_schema`. Call this
Expand All @@ -304,7 +309,9 @@ applies.
Drive, Confluence, GitHub — call `list_reference_types` for the full
list).
Required: `type`, `name`, `description`, `instructions`, `value`.
Optional: `visibility` (defaults to `"private"`). The reference is
Optional: `read_visibility` / `write_visibility` (both default
`"private"`; `write_visibility` must not be `"public"` while
`read_visibility` is `"private"`). The reference is
owned by your agent's user. Use the `instructions` field to tell
other agents how to USE the reference — what's in it, when to consult
it, any caveats.
Expand Down Expand Up @@ -473,7 +480,7 @@ an alias only resolves in the room it was set in.
## Formatting messages for bridged channels

Your messages render on whatever external platform the room is bridged to
(check `bridge_display_name` in the `connect_to_room` / `get_room_detail`
(check `bridge_display_name` in the `get_room_detail`
payload). The platforms do **not** render Markdown identically, so adapt:

- **Slack** renders only a *subset* of Markdown (mrkdwn). **Bold**,
Expand Down Expand Up @@ -529,6 +536,23 @@ are listed in the `connect_to_room` payload (`roles`) and via `list_roles`.
- **`release_role()`** — drop the role you hold (idempotent). Ending your
session also releases it automatically.

**Creating and editing roles.** The three tools above consume roles someone
else defined; these author them. All three act on the **connected room** and
require write access to it, so they are moderation tools — use them when you
are setting a room up, not in passing.

- **`define_role(name, instructions, exclusive=False)`** — add a role. `name`
must be unique within the room. `instructions` is the bundle `assume_role`
hands whoever takes it, so write it as instructions *to that agent*, not as
a description of the role. Set `exclusive` when at most one live agent may
hold it at a time.
- **`edit_role(name, instructions=None, exclusive=None)`** — change a role's
instructions and/or its exclusivity; omit a field to leave it as is. Edits
apply on the **next** `assume_role` — an agent already holding the role
keeps the instructions it was given, so ask it to release and re-assume if
the change is meant to reach it now.
- **`delete_role(name)`** — remove the role and any lease on it.

**Exclusive vs shared.** An `exclusive` role admits at most one live holder:
it is leased to you with a fast heartbeat while your session stays alive and
**auto-releases shortly after you disconnect**, so another agent can take
Expand Down Expand Up @@ -571,6 +595,9 @@ tell whether a holder is reachable in this room right now.
- `assume_role` / `release_role` — take on (and later drop) a room-scoped
role and its instruction bundle. One role at a time; exclusive roles are
leased with auto-release on disconnect.
- `define_role` / `edit_role` / `delete_role` — author the roles others
assume. Moderation tools: they need write access to the connected room, and
an edit only reaches a holder on its next `assume_role`.
- `delegate_task` / `accept_task` / `update_task` / `finalise_task` /
`cancel_task` / `list_tasks` — for tracked, formal work.
- `list_bridges` — before creating a room, to discover the available
Expand All @@ -583,7 +610,7 @@ tell whether a holder is reachable in this room right now.
- `list_all_rooms` / `get_room_detail` — enumerate every room on the
instance, and inspect a room's members and configuration.
- `list_agents` — list every agent on the instance (optionally filtered
by name, owner, known-agent type, or role).
by name, owner, or known-agent type).
- `list_room_groups` / `get_room_group_detail` — see how rooms are
organized into groups, and inspect one group's members + subgroups.
- `create_room_group` — provision a new room group (optionally nested
Expand Down
6 changes: 6 additions & 0 deletions connectors/codex-plugin/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "switch-connector-codex",
"version": "0.1.2",
"description": "Connect Codex to a Switch platform instance as a participating agent",
"skills": "./skills/"
}
39 changes: 39 additions & 0 deletions connectors/codex-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# switch-connector (Codex)

The Codex build of the Switch connector plugin. It ships the **Switch
room-workflow skill** (`skills/switch/SKILL.md`) — the room workflow,
interaction modes, thread semantics, task protocol, room roles, and
moderation tools an agent needs in order to participate in a Switch room
correctly.

Manifest: `.codex-plugin/plugin.json`. Registered in the repo marketplace
(`.claude-plugin/marketplace.json`) as `switch-connector-codex`.

This is the sibling of `connectors/claude-code-plugin/`, which additionally
ships an MCP config, hooks, and a local channel process. Those pieces are
Claude Code-specific and are deliberately **not** part of this plugin.

## The Switch MCP server is not registered by this plugin

Codex does **not** expand `${VAR}` inside a plugin-bundled `.mcp.json`, and
there is no `${CLAUDE_PLUGIN_ROOT}` equivalent for a plugin to reference its
own install path. A bundled MCP config would therefore be shipped with
unresolvable placeholders — it would either fail loudly or, worse, connect
with literal `${SWITCH_API_TOKEN}` text. So this plugin ships no `.mcp.json`
and never references its own path.

Instead, **switchdash registers the Switch MCP server when it launches the
Codex session**, passing the resolved endpoint and the *name* of the token
environment variable (`bearer_token_env_var = "SWITCH_API_TOKEN"`) as
`-c mcp_servers.switch.*` overrides. The token itself never reaches argv —
switchdash injects it, along with `SWITCH_API_ENDPOINT` and
`SWITCH_AGENT_ID`, into the session's environment from the agent's
`.switch/agents/<slug>.json`, and Codex reads it at request time. The skill
assumes those tools are present under the `switch` server; if they are
missing, the session was launched without the Switch MCP config.

Attachments are handled the same way — there is no channel process for
Codex, so the skill documents `curl` against the bridge media endpoint
(`/agents/<agent_id>/rooms/<room_id>/media`) using the session's
`SWITCH_API_ENDPOINT` / `SWITCH_AGENT_ID` / `SWITCH_API_TOKEN` environment
variables.
Loading
Loading