Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ jobs:
bash tests/user-env.test.sh
bash tests/run-t3.test.sh
bash tests/persist-credentials.test.sh
for f in docker/setup/app.js docker/setup/client/*.js docker/setup/design/ui.js docker/router/*.mjs docker/runtime/*.mjs; do
sh -n docker/setup/credentials/askpass.sh
shellcheck -s sh -S warning docker/setup/credentials/askpass.sh
for f in docker/setup/app.js docker/setup/client/*.js docker/setup/credentials.mjs docker/setup/credentials/*.mjs docker/setup/design/ui.js docker/router/*.mjs docker/runtime/*.mjs; do
node --check "$f"
done
node --test tests/*.test.mjs
Expand Down
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ and Chromium; set `CHROME_PATH` if needed. Screenshots go to
`/tmp/t3-opencode-auth-ui` (`T3_AUTH_UI_OUTPUT` overrides it). These audits
never read or change your real OpenCode credentials.

For Server credentials changes, run `node --test tests/credentials*.test.mjs`
and `node scripts/credentials-ui-audit.mjs`. The UI audit uses a real setup
server in a disposable home, synthetic credentials and intercepted connection
tests. It covers add/edit/delete, masked values, failed reads, concurrent edits
and repeated saves in light/dark, phone, tablet, desktop and an actual embedded
frame. Screenshots go to `/tmp/t3-credentials-ui` (`T3_CREDENTIALS_UI_OUTPUT`
overrides it). It never connects to a real SSH server or external API.
The image smoke suite also checks both installed helpers and a local API
round trip with a disposable saved token on each variant and architecture.

T3 functionality specific to this container and Setup belongs in the local
source patch set under `docker/runtime/patches`. Maintain the patched distribution
here, including Stable/Nightly update support. See [Maintaining native T3
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ RUN "$T3_INFRA_NODE" /usr/local/share/t3-client/patch.mjs
# shell and anyone typing it. Nothing in the image resolves it by name: the
# entrypoint, the setup service and the helpers all use T3_INFRA_LAUNCHER.
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/lib/t3code/run-t3.sh /usr/local/bin/t3-* \
/opt/t3-setup/credentials/askpass.sh \
&& ln -sfn t3-admin /usr/local/bin/t3

# The agent names, for `docker exec <container> claude` and friends: links to
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ instead. Details: [Reaching the server from a phone](docs/remote-access.md).
| [Upgrading T3 Code](docs/t3code-updates.md) | Stable and Nightly, auto-update, rollback |
| [Toolchains](docs/toolchains.md) | Go, Rust and friends, any mise tool, per-project versions |
| [Source control](docs/source-control.md) | gh, glab, fj, tea and az sign-in |
| [Server credentials](docs/server-credentials.md) | Saved SSH passwords and API keys, used by name without putting values in chat |
| [Publishing a port](docs/ports.md) | Public URLs for dev servers |
| [Giving agents a browser](docs/agent-browser.md) | T3's server browser and the browser MCP servers |
| [Context management](docs/context-management.md) | Global `AGENTS.md` and the shared skill library |
Expand Down
10 changes: 10 additions & 0 deletions docker/bin/t3-api
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
set -euo pipefail
inherited_path="$PATH"
[ "$EUID" -ne 0 ] || PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
if [ "$EUID" -eq 0 ] && command -v gosu >/dev/null 2>&1 && id t3 >/dev/null 2>&1; then
exec env PATH="$inherited_path" "$(command -v gosu)" t3 "$0" "$@"
fi
# shellcheck source=/dev/null
[ ! -r /etc/profile.d/t3-user-env.sh ] || . /etc/profile.d/t3-user-env.sh
exec "${T3_INFRA_NODE:-/usr/local/bin/node}" "${T3_CREDENTIAL_CLI:-/opt/t3-setup/credentials/cli.mjs}" api "$@"
10 changes: 10 additions & 0 deletions docker/bin/t3-ssh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
set -euo pipefail
inherited_path="$PATH"
[ "$EUID" -ne 0 ] || PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
if [ "$EUID" -eq 0 ] && command -v gosu >/dev/null 2>&1 && id t3 >/dev/null 2>&1; then
exec env PATH="$inherited_path" "$(command -v gosu)" t3 "$0" "$@"
fi
# shellcheck source=/dev/null
[ ! -r /etc/profile.d/t3-user-env.sh ] || . /etc/profile.d/t3-user-env.sh
exec "${T3_INFRA_NODE:-/usr/local/bin/node}" "${T3_CREDENTIAL_CLI:-/opt/t3-setup/credentials/cli.mjs}" ssh "$@"
4 changes: 4 additions & 0 deletions docker/setup/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
const skillsUI = T3SkillsUI.create({ api, getStatus: () => state.status, render: () => render(),
readMarkdown: (file) => readMarkdownFile(file, 64 * 1024), downloadMarkdown: (...args) => downloadMarkdown(...args) });
const mcpUI = T3McpUI.create({ api, getStatus: () => state.status, render: () => render() });
const credentialsUI = T3CredentialsUI.create({ api, render: () => render() });
const filesUI = T3FilesUI.create({ api, base: BASE, getStatus: () => state.status, getRoute: () => state.route, render: () => render(), go: route => go(route) });

/** Whether this browser came in on its T3 Code session rather than the key, so Lock would end nothing. */
Expand Down Expand Up @@ -232,6 +233,7 @@
checkHere();
if (state.route === 'instructions' && state.contextTab === 'skills') skillsUI.activate();
if (state.route === 'mcp') mcpUI.activate();
if (state.route === 'credentials') credentialsUI.activate();
if (state.route === 'files') filesUI.activate();
if (state.route === 'instructions' && state.contextTab === 'instructions') void loadInstructionsPreview();
} else if (!locked) {
Expand Down Expand Up @@ -1873,6 +1875,7 @@
files: () => html`${filesUI.phoneHead()}${pageHead('files')}${filesUI.content()}`,
overview: overviewPage, access: accessPage, agents: agentsPage, instructions: instructionsPage,
mcp: () => html`${mcpUI.phoneHead()}${pageHead('mcp', { sub: 'Servers your agents can start or connect to. Define one once, then add it to the agents you choose.' })}${mcpUI.content()}`,
credentials: () => html`${pageHead('credentials')}${credentialsUI.content()}`,
toolchains: toolchainsPage, sourcecontrol: sourceControlPage, ports: portsPage,
updates: updatesPage, storage: storagePage, server: serverPage, more: morePage,
};
Expand Down Expand Up @@ -2025,6 +2028,7 @@
if (route === 'instructions' && state.contextTab === 'skills' && state.status) skillsUI.activate(true);
if (route === 'instructions' && state.contextTab === 'instructions' && state.status) void loadInstructionsPreview(true);
if (route === 'mcp' && state.status) mcpUI.activate(true);
if (route === 'credentials' && state.status) credentialsUI.activate(true);
if (route === 'files' && state.status) filesUI.activate();
};
const navigateFromHash = async focus => {
Expand Down
Loading
Loading