diff --git a/README.md b/README.md index 728369d..2c94f56 100644 --- a/README.md +++ b/README.md @@ -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 ****. 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 @@ -49,19 +72,29 @@ In practice: ## Core Commands ```bash -truffile scan -truffile connect -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 --user-id --json --non-interactive +truffile create --path ./apps --json --non-interactive +truffile validate ./apps/ --json +truffile deploy ./apps/ --dry-run --json --non-interactive +truffile deploy ./apps/ --json --non-interactive truffile list apps --json -truffile delete -truffile models -truffile chat +truffile delete --dry-run --json --non-interactive +truffile delete --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` @@ -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 +--dry-run --json --non-interactive`; only add `--yes` after the user confirms. diff --git a/tests/test_agent_guidance.py b/tests/test_agent_guidance.py new file mode 100644 index 0000000..7745df0 --- /dev/null +++ b/tests/test_agent_guidance.py @@ -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 ****" 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 diff --git a/truffile/skills/truffile-chat/SKILL.md b/truffile/skills/truffile-chat/SKILL.md index 7c30b5c..50fdc48 100644 --- a/truffile/skills/truffile-chat/SKILL.md +++ b/truffile/skills/truffile-chat/SKILL.md @@ -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 "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 @@ -86,8 +97,8 @@ message — call `truffile chat --task-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 @@ -106,8 +117,12 @@ message — call `truffile chat --task-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 @@ -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" ``` @@ -182,6 +199,11 @@ truffile chat --quiet --task-id 0c83cc07-4ee5-410f-8c0a-be8152644f24 \ truffile chat --quiet --json --task-id | jq .content ``` +Use `--full` only for explicit debugging: +```bash +truffile chat --quiet --json --full --task-id +``` + ### Show what the agent was thinking on stderr ```bash truffile chat --show-thinking "explain why my last deploy failed" @@ -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 @@ -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. diff --git a/truffile/skills/truffile-cli/SKILL.md b/truffile/skills/truffile-cli/SKILL.md index 37fd505..e17c4a9 100644 --- a/truffile/skills/truffile-cli/SKILL.md +++ b/truffile/skills/truffile-cli/SKILL.md @@ -11,40 +11,65 @@ description: | Use this for Truffle device and app lifecycle work. +## Agent workflow + +1. Preserve the user's original goal; setup is a prerequisite, not the result. +2. Run `truffile --version`, `truffile load all --json`, and + `truffile doctor --json` before guessing about state. +3. Prefer `--json --non-interactive` for commands that support them. Treat + stdout as machine output and report the command's `code`, `message`, and + any `next_action` when it fails. +4. Continue through local create and validation even when no device is paired. +5. Pause only for a human boundary: Symphony onboarding, User ID, physical + device approval, credentials, deployment approval, or destructive action + confirmation. + ## Connection Assumptions +### Running inside a Truffle app container + When running inside a Truffle app or agent container, assume the runtime has already provided the session token in env. `truffile connect` should report that the device is already connected. Do not ask the user for tokens. +### Connecting from a computer + For normal laptop use, the user must first onboard their Truffle through the Symphony desktop client: https://docs.truffle.net/client/overview -After onboarding, ask the user for the User ID from Symphony **Settings > -About** if they have not already provided it. `truffile connect` uses that User -ID and then the user must approve the new session on the Truffle device. -Stored credentials are reused after the first approval. +After onboarding, ask the user for the User ID from Symphony **Settings** if +they have not already provided it. `truffile connect` uses that User ID and +then the user must approve the new session on the Truffle device. Stored +credentials are reused after the first approval. Useful checks: ```bash -truffile connect truffle-1234 -truffile list devices -truffile list apps +truffile doctor --json +truffile scan --json --non-interactive +truffile connect truffle-1234 --user-id "$TRUFFLE_USER_ID" \ + --json --non-interactive +truffile list devices --json +truffile list apps --json ``` +`connect` can return discovery, authentication, or approval failures as +structured JSON. Pairing always requires the user to approve the new session +on the physical Truffle. Do not ask them to paste a session token. + ## Core Commands ### Discover and Connect ```bash -truffile scan -truffile connect truffle-1234 --user-id "$TRUFFLE_USER_ID" -truffile list devices -truffile disconnect truffle-1234 -truffile disconnect all +truffile scan --json --non-interactive +truffile connect truffle-1234 --user-id "$TRUFFLE_USER_ID" \ + --json --non-interactive +truffile list devices --json +truffile disconnect truffle-1234 --json +truffile disconnect all --json ``` Inside a container, `connect` is effectively a no-op success because the @@ -53,9 +78,9 @@ session comes from env. ### Create and Validate an App ```bash -truffile load all -truffile create my-app --path ./apps -truffile validate ./apps/my-app +truffile load all --json +truffile create my-app --path ./apps --json --non-interactive +truffile validate ./apps/my-app --json ``` `truffile load all` copies bundled skills and example apps into @@ -68,27 +93,27 @@ Run validation before deploy. Validation is local and does not need a device. ### Deploy a Local App Directory ```bash -truffile deploy ./apps/my-app -truffile deploy ./apps/my-app --dry-run +truffile deploy ./apps/my-app --dry-run --json --non-interactive +truffile deploy ./apps/my-app --json --non-interactive truffile deploy ./apps/my-app --json --non-interactive --replace truffile deploy ./apps/my-app --interactive ``` -Use `--dry-run` before a risky deploy. Use `--interactive` only for debugging -inside the build container before finalizing. +Run `--dry-run` first. Deploy only when the user asked for deployment, and use +`--replace` only when they approved replacing the installed bundle. Use +`--interactive` only for debugging inside the build container. ### Delete Installed Apps ```bash truffile list apps --json -truffile delete my-app -truffile delete 1 -truffile delete 1 2 3 -truffile delete all +truffile delete my-app --dry-run --json --non-interactive +truffile delete my-app --yes --json --non-interactive ``` Prefer deleting by app name, slug, or uuid. Numeric indices still work, but -they are less safe for agent workflows because app ordering can change. +they are less safe for agent workflows because app ordering can change. Never +add `--yes` until the user has confirmed the exact apps shown by `--dry-run`. ### Obsidian @@ -124,6 +149,9 @@ ToolSpec( ## Output Habits -- Use `--json` when available for scripting. +- Use `--json` when available for scripting. Successful payloads contain + `schema_version` and `status: "ok"`; failures contain `status: "error"` plus + stable `code`, `message`, and `retryable` fields. They include `next_action` + when a concrete recovery command or human action is known. - Keep stdout clean when feeding another command. - Relay important command results to the user; they do not see shell output. diff --git a/truffile/skills/truffile-infer/SKILL.md b/truffile/skills/truffile-infer/SKILL.md index c8a686c..07e9407 100644 --- a/truffile/skills/truffile-infer/SKILL.md +++ b/truffile/skills/truffile-infer/SKILL.md @@ -56,6 +56,12 @@ In one-shot mode: This means you can always do `truffile infer --quiet "..."` and get a clean, parseable response on stdout with no preamble. +Before inference, use `truffile doctor --json` to distinguish connection, +authentication, and IF2 service failures. In `--json` mode, failures keep a +stable `code`, `message`, and `retryable`; they include `next_action` when a +concrete recovery step is known. HTTP 503 responses remain visible as +service-unavailable errors. + ## Full flag reference ### Conversation @@ -248,8 +254,8 @@ truffile infer --quiet --prompt-file ./prompt.txt --max-tokens 1024 `--mcp`, image attachments, and `--json` 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) → exit 1.** Run `truffile list devices` to - verify a Truffle is connected before invoking infer in scripts. +- **No device connected (LAN) → exit 1.** Run `truffile doctor --json` to + identify whether discovery, pairing, authentication, or IF2 is missing. - **`--mcp` URLs must start with `http://` or `https://`.** Streamable HTTP only. - **`--tool-args` must be a JSON object** (not an array, not a bare value). - **Default behavior is non-streaming in one-shot mode.** Pass `--stream` if diff --git a/truffile/skills/truffle-app-creator/SKILL.md b/truffile/skills/truffle-app-creator/SKILL.md index 46eda9e..0920b1c 100644 --- a/truffile/skills/truffle-app-creator/SKILL.md +++ b/truffile/skills/truffle-app-creator/SKILL.md @@ -23,7 +23,8 @@ Activate when the user wants to: ### Step 1: Understand what the user wants -Interview the user: +Start from the user's stated goal and infer safe defaults. Ask only for missing +answers that would materially change behavior, auth, data access, or scope: - what service or API do they want to connect? - what should the agent be able to DO with it? (foreground tools) - what should happen automatically in the background? (background monitoring) @@ -31,7 +32,7 @@ Interview the user: Explain the difference between foreground and background clearly: - foreground: tools the agent calls during a conversation (search, send, check) -- background: scheduled jobs that submit context to the background agent running on their Truffle that decides whether submiited context is worth posting or taking an action on or not +- background: scheduled jobs that submit context to the background agent running on their Truffle, which decides whether submitted context is worth posting or acting on - they run in separate containers and cannot talk to each other directly - use app variables to share state between them @@ -42,12 +43,14 @@ Before building from scratch, search for: - open source libraries or SDKs for the API - existing Truffle apps that do something similar -Use web search to find options. Present them to the user with pros/cons: +Use web search to find options and compare: - if an MCP server exists: can we proxy it or wrap it? - if an SDK exists: can we build tools on top of it? - if nothing exists: we build from scratch using the API docs -Ask the user which approach they prefer. +Choose the smallest well-supported approach when the tradeoff is reversible. +Ask the user only when the alternatives materially change product behavior, +credentials, data exposure, or implementation scope. ### Step 3: Decide the app architecture @@ -62,6 +65,10 @@ Reference: see references/auth-patterns.md for the decision tree. ### Step 4: Scaffold the app Create the directory structure, the command truffile create does this, read the docs first: +```bash +truffile create my-app --path ./apps --json --non-interactive +``` + ``` my-app/ ├── truffile.yaml @@ -128,8 +135,8 @@ Subclass BackgroundWorkerApp with the 4 required methods. Follow the rules: Don't wait until the end. Write tests as you implement: ```bash -truffile validate ./my-app # yaml + source checks -python -m pytest ./my-app/tests/ -v # unit tests +truffile validate ./apps/my-app --json # yaml + source checks +python -m pytest ./apps/my-app/tests/ -v # unit tests ``` Use FakeHttpTransport and AppHarness. See references/testing-guide.md. @@ -137,9 +144,9 @@ Use FakeHttpTransport and AppHarness. See references/testing-guide.md. ### Step 9: Validate and iterate Run validation: -- `truffile validate ./my-app` — check manifest, file references, and Python syntax -- `truffile deploy --dry-run ./my-app` — inspect the deploy plan without changing the device -- `python -m pytest ./my-app/tests/` — run tests +- `truffile validate ./apps/my-app --json` — check manifest, file references, and Python syntax +- `truffile deploy ./apps/my-app --dry-run --json --non-interactive` — inspect the deploy plan without changing the device +- `python -m pytest ./apps/my-app/tests/` — run tests Fix any issues. Repeat until everything passes. @@ -147,18 +154,23 @@ Fix any issues. Repeat until everything passes. If the user has a device connected: ```bash -truffile deploy ./my-app +truffile deploy ./apps/my-app --json --non-interactive ``` +Deploy only when the user requested it. If no device is connected, finish the +local app, tests, validation, and dry-run; then give the exact connection +blocker instead of abandoning the build. + Then test with the agent: ```bash -truffile chat -you> [test the app's tools] +truffile chat --quiet --json --app my-app "test the app's tools" ``` -### Step 11: Create skills (optional) +### Step 11: Create skills when useful -Ask the user if they want to add skills — workflow guides that teach the agent how to use the app's tools effectively. Create SKILL.md files under skills/foreground/ and skills/background/. +Add workflow skills under `skills/foreground/` or `skills/background/` when +the app needs repeatable usage guidance. Otherwise mention them as an optional +follow-up in the handoff instead of stopping implementation to ask. ## Error handling @@ -174,8 +186,8 @@ Common issues during app creation: | Script | What it does | |--------|-------------| | `scripts/fetch_docs.py` | pull docs from docs.truffle.net | -| `truffile validate ` | check truffile.yaml, referenced files, and Python syntax | -| `truffile deploy --dry-run ` | inspect the deploy plan without changing the device | +| `truffile validate --json` | check truffile.yaml, referenced files, and Python syntax | +| `truffile deploy --dry-run --json --non-interactive` | inspect the deploy plan without changing the device | ## Reference docs