Skip to content
Closed
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
67 changes: 50 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,36 @@

Python SDK/CLI for Truffle devices.

## Agent setup

Give this to Codex, Claude Code, or another coding agent from the workspace
where you want to use Truffile. Replace the placeholder with what you want to
do with your Truffle:

> Set up the latest Truffile in this workspace, then **<what you want to do with your Truffle>**. Follow the
> [installation guide](https://docs.truffle.net/sdk/installation). Run
> `truffile load all --json` and `truffile doctor --json`, then follow only the copied skills relevant to the task.
> Continue through all work that does not need me. Ask only for Symphony onboarding or my User ID,
> approval on the physical Truffle,
> credentials, or confirmation before deployment or a destructive action.

How the pieces fit:

- Symphony onboards the Truffle and supplies the User ID used for pairing.
- Truffile gives a coding agent a local CLI and SDK for the device.
- `truffile chat` talks to the on-device agent, including its tasks and apps.
- `truffile infer` calls the raw on-device model and can test MCP servers.
- The user still approves pairing on the device and authorizes deployments or
destructive actions.

## What It Does

- discovers and connects to your Truffle (`scan`, `connect`, `disconnect`)
- copies bundled agent resources into your workspace (`load`)
- diagnoses the local-to-device path (`doctor`)
- validates and deploys apps from `truffile.yaml` (`validate`, `deploy`)
- manages installed apps (`list apps`, `delete`)
- talks to inference directly (`models`, `chat`)
- talks to the on-device agent (`chat`) or raw inference service (`infer`)

## Start making your Own Apps

Expand Down Expand Up @@ -49,19 +72,29 @@ In practice:
## Core Commands

```bash
truffile scan
truffile connect <device>
truffile create [app_name]
truffile load all
truffile validate [app_dir]
truffile deploy [app_dir]
truffile deploy --dry-run [app_dir]
truffile --version
truffile load all --json
truffile doctor --json
truffile scan --json --non-interactive
truffile connect <device> --user-id <user-id> --json --non-interactive
truffile create <app-name> --path ./apps --json --non-interactive
truffile validate ./apps/<app-name> --json
truffile deploy ./apps/<app-name> --dry-run --json --non-interactive
truffile deploy ./apps/<app-name> --json --non-interactive
truffile list apps --json
truffile delete <app-name>
truffile models
truffile chat
truffile delete <app-name> --dry-run --json --non-interactive
truffile delete <app-name> --yes --json --non-interactive
truffile models --json
truffile chat --quiet --json "your request"
truffile infer --quiet --json "your prompt"
```

For a first connection, onboard the device in
[Symphony](https://docs.truffle.net/client/overview), copy the User ID from
Symphony Settings, run the `scan` and `connect` commands above, then approve
the new session on the Truffle. Installation and local validation do not
require a connected device.

`truffile create` scaffolds a hybrid app starter with:
- `truffile.yaml` (foreground + background process config)
- copy-file steps for generated `*_foreground.py` and `*_background.py`
Expand Down Expand Up @@ -130,12 +163,12 @@ Refresh vendored protos from firmware repo:
The supported app development loop is CLI-first:

```bash
truffile create my-app --path ./apps
truffile validate ./apps/my-app
truffile deploy --dry-run ./apps/my-app
truffile deploy ./apps/my-app
truffile create my-app --path ./apps --json --non-interactive
truffile validate ./apps/my-app --json
truffile deploy ./apps/my-app --dry-run --json --non-interactive
truffile deploy ./apps/my-app --json --non-interactive
```

After deploy, use `truffile chat` to attach the app to a task and exercise its
tools with the on-device agent. Use `truffile delete` to remove test apps from
the connected device.
tools with the on-device agent. Preview removal with `truffile delete <app>
--dry-run --json --non-interactive`; only add `--yes` after the user confirms.
48 changes: 48 additions & 0 deletions tests/test_agent_guidance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from pathlib import Path


ROOT = Path(__file__).parents[1]
README = (ROOT / "README.md").read_text()
CLI_SKILL = (ROOT / "truffile/skills/truffile-cli/SKILL.md").read_text()
CHAT_SKILL = (ROOT / "truffile/skills/truffile-chat/SKILL.md").read_text()
APP_SKILL = (ROOT / "truffile/skills/truffle-app-creator/SKILL.md").read_text()


def compact(text: str) -> str:
return " ".join(text.split())


def test_setup_prompt_is_goal_first_and_names_human_boundaries():
prompt = compact(README)
assert "then **<what you want to do with your Truffle>**" in prompt
assert "Continue through all work that does not need me" in prompt
assert "follow only the copied skills relevant to the task" in prompt
assert "Symphony onboarding or my User ID" in prompt
assert "approval on the physical" in prompt


def test_cli_skill_prefers_machine_contract_and_safe_deletion():
assert "truffile doctor --json" in CLI_SKILL
assert "truffile scan --json --non-interactive" in CLI_SKILL
assert '--user-id "$TRUFFLE_USER_ID"' in CLI_SKILL
assert "delete my-app --dry-run --json --non-interactive" in CLI_SKILL
assert "delete my-app --yes --json --non-interactive" in CLI_SKILL
assert "Settings >\nAbout" not in CLI_SKILL
assert "### Running inside a Truffle app container" in CLI_SKILL


def test_chat_skill_documents_bounded_compact_output():
assert "--max-output-bytes" in CHAT_SKILL
assert "--include-thinking" in CHAT_SKILL
assert "--include-tools" in CHAT_SKILL
assert "interrupt a known task" in CHAT_SKILL
assert "task_not_waiting" in CHAT_SKILL
assert "pending_user_response" in CHAT_SKILL
assert "Reserved:" not in CHAT_SKILL


def test_app_creator_keeps_working_before_device_pairing():
assert "finish the local app, tests, validation, and dry-run" in compact(APP_SKILL)
assert "truffile create my-app --path ./apps --json --non-interactive" in APP_SKILL
assert "Ask the user which approach they prefer" not in APP_SKILL
assert "Ask the user if they want to add skills" not in APP_SKILL
55 changes: 39 additions & 16 deletions truffile/skills/truffile-chat/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,35 @@ In one-shot mode:
error diagnostics, and (with `--show-thinking`) the agent's thinking
summaries. Pass `--quiet` to silence stderr entirely.
- Exit code is `0` on success, `1` on error, `130` on Ctrl+C.
- JSON is compact by default. Thinking summaries and tool names are omitted
unless requested, and response content is bounded by `--max-output-bytes`.

`--json` payload shape:

```json
{
"schema_version": "1",
"status": "ok",
"task_id": "uuid-of-the-task",
"title": "Generated task title",
"device": "truffle-1234",
"content": "the agent's final response text",
"thinking": ["summary 1", "summary 2"] | null,
"tool_calls": ["tool_a", "tool_b"] | null,
"content_bytes": 39,
"returned_bytes": 39,
"truncated": false,
"task_status": "ready",
"run_state": "TASK_RUN_STATE_READY",
"pending_user_response": false,
"attached_apps": ["Slack", "Gmail"] | null
"attached_apps": ["Slack", "Gmail"]
}
```

`pending_user_response: true` means the agent is waiting for a follow-up
message — call `truffile chat --task-id <id> "answer"` to continue.
If the task is no longer waiting, the same command returns `task_not_waiting`;
omit `--task-id` to start a new task.
Add `--include-thinking`, `--include-tools`, or `--full` only when that detail
is needed.

## Full flag reference

Expand All @@ -86,8 +97,8 @@ message — call `truffile chat --task-id <id> "answer"` to continue.
### Task targeting
| Flag | What it does |
|---|---|
| `--task-id ID` | Resume a specific task. With a prompt: send follow-up. Without: peek state |
| `--resume-last` | Resume the most recent task on the device |
| `--task-id ID` | Read a task, or answer it with a prompt only while it is waiting |
| `--resume-last` | Target the most recent task; prompted follow-up still requires it to be waiting |
| `--resume` | (REPL only) Open the interactive task picker |

### App attachment
Expand All @@ -106,8 +117,12 @@ message — call `truffile chat --task-id <id> "answer"` to continue.
|---|---|
| `--json` | Emit a structured JSON object on stdout |
| `--show-thinking` | Include the agent's thinking summaries on stderr |
| `--include-thinking` | Include thinking summaries in JSON |
| `--include-tools` | Include tool names in JSON |
| `--full` | Include both thinking summaries and tool names in JSON |
| `--max-output-bytes N` | Bound returned UTF-8 content bytes (default 65536) |
| `--quiet`, `-q` | Suppress all stderr decoration |
| `--timeout SECONDS` | Reserved: max seconds to wait for the task to settle |
| `--timeout SECONDS` | Stop waiting and interrupt a known task after this limit |

## Cookbook

Expand Down Expand Up @@ -163,10 +178,12 @@ truffile chat --list-tasks 10 --quiet

As JSON:
```bash
truffile chat --list-tasks 5 --json --quiet | jq '.tasks[] | .title'
truffile chat --list-tasks 5 --json --quiet \
| jq '.tasks[] | {task_id, title, status, pending_user_response}'
```

### Resume the most recent task and add a follow-up
Use this only when the task is waiting for a user response:
```bash
truffile chat --quiet --resume-last "tell me more about the second item"
```
Expand All @@ -182,6 +199,11 @@ truffile chat --quiet --task-id 0c83cc07-4ee5-410f-8c0a-be8152644f24 \
truffile chat --quiet --json --task-id <id> | jq .content
```

Use `--full` only for explicit debugging:
```bash
truffile chat --quiet --json --full --task-id <id>
```

### Show what the agent was thinking on stderr
```bash
truffile chat --show-thinking "explain why my last deploy failed"
Expand Down Expand Up @@ -246,16 +268,17 @@ truffile chat --quiet --app "$APP_NAME" "..."
all work the same way they do on a LAN client. See the `truffile-cli`
skill's "Running inside a Truffle app container" section for the full
contract.
- **No device connected (LAN) → first-run onboarding fires.** The CLI will
ask for a truffle number and user id, then run `truffile connect`
automatically. This still requires the user to **physically tap "approve"
on the Truffle device screen**. After that, all subsequent commands work
without re-prompting.
- **No device connected in machine mode → structured failure.** Run
`truffile doctor --json`, then onboard in Symphony and connect with the
non-interactive command in the `truffile-cli` skill. Interactive mode can
still walk a human through the same flow. Pairing requires the user to
physically approve the session on the Truffle.
- **App matching is fuzzy.** `slack` matches `Slack`. If two app names overlap,
the first match wins — use the uuid for absolute precision.
- **`--task-id` without a prompt does NOT send a message.** It just opens the
task and dumps current state. Combine with `--json` to inspect, or with a
prompt to send a follow-up.
task and dumps current state. A prompt answers the task only while
`pending_user_response` is true. Completed tasks return `task_not_waiting`;
omit `--task-id` to start a new task.
- **`--resume-last` picks the most recently updated task,** which is usually
the one the user was just looking at — but not always.
- **`--quiet` suppresses error explanations on stderr too.** Drop it during
Expand All @@ -264,5 +287,5 @@ truffile chat --quiet --app "$APP_NAME" "..."
`--task-id` or `--resume-last` always starts a fresh task — old conversation
context is not carried over.
- **Tool calls are surfaced on stderr** in plain mode. In `--json` mode they
appear in the `tool_calls` array. With `--quiet`, you only see them via the
JSON payload.
are omitted by default; add `--include-tools` or `--full` when you need the
`tool_calls` array.
Loading