chore: sync skills from openai/plugins
This commit is contained in:
1 parent
571a645c44
commit
49183cac0a
18 files changed
+1917
-244
No files matched your search
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf of
|
||||
any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don\'t include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+147
-166
@@ -1,174 +1,155 @@
|
||||
---
|
||||
name: agents-sdk
|
||||
description: Build, run, deploy, and evaluate OpenAI Agents SDK apps from Codex. Use when the user asks to create or adapt an Agents SDK app, build from a prompt or Codex thread, prepare a runnable agent prototype, add a focused eval harness, or deploy locally through the Agents SDK Deployment Manager.
|
||||
description: Build AI agents on Cloudflare Workers using the Agents SDK. Load when creating stateful agents, durable workflows, real-time WebSocket apps, scheduled tasks, MCP servers, or chat applications. Covers Agent class, state management, callable RPC, Workflows integration, and React hooks. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.
|
||||
---
|
||||
|
||||
# Agents SDK
|
||||
# Cloudflare Agents SDK
|
||||
|
||||
Use this skill to turn an idea, repo, or prior Codex thread into a small runnable Agents SDK app. Verify it locally, then deploy it through the local Deployment Manager when the user wants a running service. Prefer Python unless the user asks for TypeScript.
|
||||
Your knowledge of the Agents SDK may be outdated. **Prefer retrieval over pre-training** for any Agents SDK task.
|
||||
|
||||
## Retrieval Sources
|
||||
|
||||
Fetch current docs from `https://github.com/cloudflare/agents/tree/main/docs` before implementing.
|
||||
|
||||
| Topic | Doc | Use for |
|
||||
|-------|-----|---------|
|
||||
| Getting started | `docs/getting-started.md` | First agent, project setup |
|
||||
| State | `docs/state.md` | `setState`, `validateStateChange`, persistence |
|
||||
| Routing | `docs/routing.md` | URL patterns, `routeAgentRequest`, `basePath` |
|
||||
| Callable methods | `docs/callable-methods.md` | `@callable`, RPC, streaming, timeouts |
|
||||
| Scheduling | `docs/scheduling.md` | `schedule()`, `scheduleEvery()`, cron |
|
||||
| Workflows | `docs/workflows.md` | `AgentWorkflow`, durable multi-step tasks |
|
||||
| HTTP/WebSockets | `docs/http-websockets.md` | Lifecycle hooks, hibernation |
|
||||
| Email | `docs/email.md` | Email routing, secure reply resolver |
|
||||
| MCP client | `docs/mcp-client.md` | Connecting to MCP servers |
|
||||
| MCP server | `docs/mcp-servers.md` | Building MCP servers with `McpAgent` |
|
||||
| Client SDK | `docs/client-sdk.md` | `useAgent`, `useAgentChat`, React hooks |
|
||||
| Human-in-the-loop | `docs/human-in-the-loop.md` | Approval flows, pausing workflows |
|
||||
| Resumable streaming | `docs/resumable-streaming.md` | Stream recovery on disconnect |
|
||||
|
||||
Cloudflare docs: https://developers.cloudflare.com/agents/
|
||||
|
||||
## Capabilities
|
||||
|
||||
The Agents SDK provides:
|
||||
|
||||
- **Persistent state** - SQLite-backed, auto-synced to clients
|
||||
- **Callable RPC** - `@callable()` methods invoked over WebSocket
|
||||
- **Scheduling** - One-time, recurring (`scheduleEvery`), and cron tasks
|
||||
- **Workflows** - Durable multi-step background processing via `AgentWorkflow`
|
||||
- **MCP integration** - Connect to MCP servers or build your own with `McpAgent`
|
||||
- **Email handling** - Receive and reply to emails with secure routing
|
||||
- **Streaming chat** - `AIChatAgent` with resumable streams
|
||||
- **React hooks** - `useAgent`, `useAgentChat` for client apps
|
||||
|
||||
## FIRST: Verify Installation
|
||||
|
||||
```bash
|
||||
npm ls agents # Should show agents package
|
||||
```
|
||||
|
||||
If not installed:
|
||||
```bash
|
||||
npm install agents
|
||||
```
|
||||
|
||||
## Wrangler Configuration
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"durable_objects": {
|
||||
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
|
||||
},
|
||||
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }]
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Class
|
||||
|
||||
```typescript
|
||||
import { Agent, routeAgentRequest, callable } from "agents";
|
||||
|
||||
type State = { count: number };
|
||||
|
||||
export class Counter extends Agent<Env, State> {
|
||||
initialState = { count: 0 };
|
||||
|
||||
// Validation hook - runs before state persists (sync, throwing rejects the update)
|
||||
validateStateChange(nextState: State, source: Connection | "server") {
|
||||
if (nextState.count < 0) throw new Error("Count cannot be negative");
|
||||
}
|
||||
|
||||
// Notification hook - runs after state persists (async, non-blocking)
|
||||
onStateUpdate(state: State, source: Connection | "server") {
|
||||
console.log("State updated:", state);
|
||||
}
|
||||
|
||||
@callable()
|
||||
increment() {
|
||||
this.setState({ count: this.state.count + 1 });
|
||||
return this.state.count;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
fetch: (req, env) => routeAgentRequest(req, env) ?? new Response("Not found", { status: 404 })
|
||||
};
|
||||
```
|
||||
|
||||
## Routing
|
||||
|
||||
Requests route to `/agents/{agent-name}/{instance-name}`:
|
||||
|
||||
| Class | URL |
|
||||
|-------|-----|
|
||||
| `Counter` | `/agents/counter/user-123` |
|
||||
| `ChatRoom` | `/agents/chat-room/lobby` |
|
||||
|
||||
Client: `useAgent({ agent: "Counter", name: "user-123" })`
|
||||
|
||||
## Core APIs
|
||||
|
||||
| Task | API |
|
||||
|------|-----|
|
||||
| Read state | `this.state.count` |
|
||||
| Write state | `this.setState({ count: 1 })` |
|
||||
| SQL query | `` this.sql`SELECT * FROM users WHERE id = ${id}` `` |
|
||||
| Schedule (delay) | `await this.schedule(60, "task", payload)` |
|
||||
| Schedule (cron) | `await this.schedule("0 * * * *", "task", payload)` |
|
||||
| Schedule (interval) | `await this.scheduleEvery(30, "poll")` |
|
||||
| RPC method | `@callable() myMethod() { ... }` |
|
||||
| Streaming RPC | `@callable({ streaming: true }) stream(res) { ... }` |
|
||||
| Start workflow | `await this.runWorkflow("ProcessingWorkflow", params)` |
|
||||
|
||||
## React Client
|
||||
|
||||
```tsx
|
||||
import { useAgent } from "agents/react";
|
||||
|
||||
function App() {
|
||||
const [state, setLocalState] = useState({ count: 0 });
|
||||
|
||||
const agent = useAgent({
|
||||
agent: "Counter",
|
||||
name: "my-instance",
|
||||
onStateUpdate: (newState) => setLocalState(newState),
|
||||
onIdentity: (name, agentType) => console.log(`Connected to ${name}`)
|
||||
});
|
||||
|
||||
return (
|
||||
<button onClick={() => agent.setState({ count: state.count + 1 })}>
|
||||
Count: {state.count}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- Agents guide: https://developers.openai.com/api/docs/guides/agents
|
||||
- Sandbox Agents guide: https://developers.openai.com/api/docs/guides/agents/sandboxes
|
||||
- Python SDK: https://github.com/openai/openai-agents-python
|
||||
- TypeScript SDK: https://github.com/openai/openai-agents-js
|
||||
- Deployment Manager: https://github.com/openai/openai-cookbook/tree/main/examples/agents_sdk/deployment_manager
|
||||
- Agent evals guide: https://developers.openai.com/api/docs/guides/agent-evals
|
||||
|
||||
## Rules
|
||||
|
||||
- Read the Agents guide before creating or changing an Agents SDK implementation.
|
||||
- Read the Sandbox Agents guide before choosing `SandboxAgent`, workspace manifests, shell/file access, skills, or sandbox backend behavior.
|
||||
- If a docs MCP/tool is unavailable, read the official docs URLs directly instead of skipping the docs gate.
|
||||
- Follow current docs for SDK semantics and the target repo for packaging, naming, and command conventions.
|
||||
- Keep generated app files, eval files, and deployment-generated files separate in the final summary.
|
||||
|
||||
## Intake
|
||||
|
||||
Classify the request before editing:
|
||||
|
||||
- New app from prompt or idea: build the smallest runnable Agents SDK app that proves the workflow.
|
||||
- Existing app or demo: inspect the repo and add the smallest Agents SDK layer needed to make the workflow agentic.
|
||||
- Prior Codex work: turn thread IDs, session links, rollout JSONL paths, or pasted summaries into a short build brief before writing code.
|
||||
- Evals request: add a focused local eval harness against the real agent path.
|
||||
- Deployment-only request: deploy the existing app without rebuilding unless deployment reveals a small required fix.
|
||||
|
||||
## API Access
|
||||
|
||||
Agents SDK apps need `OPENAI_API_KEY` to run. Before building, running, or testing an app that calls the OpenAI API, use the `openai-platform-api-key` skill in this plugin as the credential gate. Follow that skill's confirmation flow and never print, summarize, or commit secret values.
|
||||
|
||||
## Build Workflow
|
||||
|
||||
1. Inspect the target repo.
|
||||
Read `README.md`, dependency files, app entrypoints, existing examples, and any domain-specific `skills/` or policy files.
|
||||
|
||||
2. Define the app contract.
|
||||
Capture the agent goal, input shape, expected output, tools, state, approval gates, and the local command that proves the workflow.
|
||||
|
||||
3. Set up dependencies using the repo's existing package manager.
|
||||
For Python projects, prefer `uv`; add `openai-agents` when the project owns dependencies.
|
||||
|
||||
4. Start with one agent.
|
||||
Use a single `Agent` with clear static `instructions` and `Runner.run` until the workflow proves it needs specialists, handoffs, structured outputs, or sandbox execution.
|
||||
|
||||
5. Add tools deliberately.
|
||||
Use `@function_tool` for deterministic local actions such as lookups, calculations, file transforms, API calls, or validation. Keep side effects narrow and schemas explicit.
|
||||
|
||||
6. Add sandbox only for workspace tasks.
|
||||
Use `SandboxAgent` when the agent must inspect files, run shell commands, use workspace skills, or create artifacts in an isolated environment. Keep ordinary business workflows on normal `Agent` plus tools.
|
||||
|
||||
7. Make it runnable.
|
||||
Provide a local smoke command, sample input, and expected observable output. If there is a UI, wire it to the agent path and verify the core workflow, not just rendering.
|
||||
|
||||
For HTTP apps that may be deployed, make `uv run python main.py` start the web service when `PORT` is present, keep CLI-only smoke behavior behind explicit arguments or the no-`PORT` path, and expose `/health` for readiness.
|
||||
|
||||
For every new prototype or substantial app build, prefer:
|
||||
|
||||
```text
|
||||
<project>/
|
||||
agent.py # Agent definition, tools, and run helper
|
||||
main.py # API/server/CLI entrypoint if needed
|
||||
pyproject.toml # includes openai-agents if the project owns deps
|
||||
docs/
|
||||
prompt.md # runtime prompt or instructions used by the app agent
|
||||
agent-interactions.png
|
||||
agent-sequence.png
|
||||
data/ # small sample inputs or fixtures
|
||||
skills/<domain>/ # optional domain instructions or reusable policy
|
||||
README.md # local run instructions if the project already uses READMEs
|
||||
```
|
||||
|
||||
Generate diagrams directly as PNG files. Do not create SVG diagram sources or rely on browser screenshots of SVGs unless the user explicitly asks for editable vector sources. For one-off diagram generation, prefer a small script under `scripts/generate_diagrams.py` and run extra drawing dependencies with `uv run --no-project --with ...` so the app dependency file stays focused.
|
||||
|
||||
## Build From Codex
|
||||
|
||||
When the source is prior Codex work, create a compact brief before building:
|
||||
|
||||
- confirmed facts from the source threads;
|
||||
- inferences and open questions;
|
||||
- app goal, agent behavior, tools, state, UI, approvals, sandbox needs, and deployment assumptions;
|
||||
- a standalone build prompt that can drive the implementation.
|
||||
|
||||
Prefer the newest user direction when threads conflict. Keep secrets out of the brief and mention missing environment variable names only.
|
||||
|
||||
If the user already asked to build after planning, continue from the brief into the build workflow. Otherwise ask for approval before implementing.
|
||||
|
||||
## Eval Workflow
|
||||
|
||||
Add evals when requested. Default to a local harness that exercises the real agent workflow rather than a mock or contract-only path.
|
||||
|
||||
Before creating or changing evals, read the Agents guide and Agent evals guide. Read trace grading, evals, and graders docs before generating platform eval configs, grader JSON, or dataset upload scripts.
|
||||
|
||||
Prefer an `evals/` folder unless the repo already has a stronger convention:
|
||||
|
||||
```text
|
||||
<project>/
|
||||
evals/
|
||||
README.md
|
||||
cases.jsonl
|
||||
graders.py
|
||||
run_local.py
|
||||
results/
|
||||
.gitignore
|
||||
```
|
||||
|
||||
Design a small case matrix around meaningful behavior: happy path, missing evidence, escalation boundary, required or forbidden tool calls, approval gates, state updates, and regressions from observed bugs. Grade behavior such as structured output, tool calls, handoffs, guardrails, trace IDs, event logs, state changes, and approval behavior instead of volatile IDs or exact prose unless wording is contractual.
|
||||
|
||||
`evals/run_local.py` should load cases, add the app root to `sys.path`, run each case through the app's real agent path, isolate or reset state, require needed environment variable names up front, write `evals/results/latest.json`, and exit non-zero on failures.
|
||||
|
||||
## Deploy Workflow
|
||||
|
||||
Use the Deployment Manager from `openai-cookbook` for local deployments. Default to `local-docker` unless the user or app requires a different local target.
|
||||
|
||||
1. Check deployable app signals:
|
||||
- an app/orchestrator entrypoint, usually `main.py`;
|
||||
- dependency metadata, preferably `pyproject.toml`;
|
||||
- `openai-agents` in the app dependencies;
|
||||
- `PORT` support for local app startup, with `uv run python main.py` starting the HTTP service when `PORT` is set;
|
||||
- `/health` readiness endpoint;
|
||||
- optional `SANDBOX_BACKEND` support for sandbox-backed apps;
|
||||
- optional `docs/prompt.md`, `docs/agent-interactions.png`, and `docs/agent-sequence.png` for manager app details.
|
||||
|
||||
2. Resolve the manager directory.
|
||||
Prefer `DEPLOYMENT_MANAGER_ROOT` when set. Otherwise use `$HOME/code/openai-cookbook/examples/agents_sdk/deployment_manager`. If the cookbook checkout is missing, clone `https://github.com/openai/openai-cookbook`. If it exists, update it with `git pull --ff-only` unless the user asked to avoid updating local checkouts. Stop and report local changes or diverged history instead of forcing the checkout. Verify `$MANAGER_DIR/Makefile` exists before deploying.
|
||||
|
||||
3. Deploy through the manager:
|
||||
|
||||
```bash
|
||||
make -C "$MANAGER_DIR" deploy PROJECT_PATH=<absolute-app-path>
|
||||
```
|
||||
|
||||
Useful options:
|
||||
|
||||
```bash
|
||||
make -C "$MANAGER_DIR" deploy PROJECT_PATH=/path/to/app APP_PORT=8421
|
||||
make -C "$MANAGER_DIR" deploy PROJECT_PATH=/path/to/app TARGET=local-process
|
||||
make -C "$MANAGER_DIR" deploy PROJECT_PATH=/path/to/app SANDBOX_BACKEND=docker
|
||||
make -C "$MANAGER_DIR" start
|
||||
make -C "$MANAGER_DIR" health
|
||||
```
|
||||
|
||||
4. Let the manager own extraction and deployment records.
|
||||
The helper imports the project, creates or reuses a matching deployment, starts it, and prints JSON with `manager_url`, `deployment`, and `app_url`. For `local-docker`, it may generate or reuse an app-level `Dockerfile`. If that changes the app worktree, report it and do not revert user files.
|
||||
|
||||
5. Verify the result.
|
||||
Check manager health, the app `/health` readiness endpoint, and deployment sessions/containers when available.
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:8732/api/health
|
||||
curl -fsS <app-url>/health
|
||||
curl -fsS http://127.0.0.1:8732/api/deployments/<deployment-id>/sessions
|
||||
curl -fsS http://127.0.0.1:8732/api/deployments/<deployment-id>/containers
|
||||
```
|
||||
|
||||
Run `git -C <app-path> status --short` when the app path is inside a git checkout so generated Dockerfiles or other local edits are visible.
|
||||
|
||||
## Done Criteria
|
||||
|
||||
Before handing back:
|
||||
|
||||
- the app has a clear local run command and smoke result, or a clear blocker;
|
||||
- deployment was attempted when requested and the manager/app URLs are reported;
|
||||
- any missing credentials, model access, port conflicts, Docker issues, or layout warnings are explicit;
|
||||
- generated app files, eval files, and deployment-generated files are separated in the summary.
|
||||
- **[references/workflows.md](references/workflows.md)** - Durable Workflows integration
|
||||
- **[references/callable.md](references/callable.md)** - RPC methods, streaming, timeouts
|
||||
- **[references/state-scheduling.md](references/state-scheduling.md)** - State persistence, scheduling
|
||||
- **[references/streaming-chat.md](references/streaming-chat.md)** - AIChatAgent, resumable streams
|
||||
- **[references/mcp.md](references/mcp.md)** - MCP server integration
|
||||
- **[references/email.md](references/email.md)** - Email routing and handling
|
||||
- **[references/codemode.md](references/codemode.md)** - Code Mode (experimental)
|
||||
@@ -1,3 +1,6 @@
|
||||
interface:
|
||||
display_name: "Agents SDK"
|
||||
short_description: "Build and deploy OpenAI Agents SDK apps"
|
||||
short_description: "Build stateful agents on Cloudflare Workers"
|
||||
icon_small: "./assets/cloudflare-small.svg"
|
||||
icon_large: "./assets/cloudflare.png"
|
||||
default_prompt: "Use the Cloudflare Agents SDK to design or implement this agent on Workers with the right state, routing, and workflow patterns."
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path fill="currentColor" d="M16.5088 16.8447c.1475-.5068.0908-.9707-.1553-1.3154-.2246-.3164-.6045-.499-1.0615-.5205l-8.6592-.1123a.1559.1559 0 0 1-.1333-.0713c-.0283-.042-.0351-.0986-.021-.1553.0278-.084.1123-.1484.2036-.1562l8.7359-.1123c1.0351-.0489 2.1601-.8868 2.5537-1.9136l.499-1.3013c.0215-.0561.0293-.1128.0147-.168-.5625-2.5463-2.835-4.4453-5.5499-4.4453-2.5039 0-4.6284 1.6177-5.3876 3.8614-.4927-.3658-1.1187-.5625-1.794-.499-1.2026.119-2.1665 1.083-2.2861 2.2856-.0283.31-.0069.6128.0635.894C1.5683 13.171 0 14.7754 0 16.752c0 .1748.0142.3515.0352.5273.0141.083.0844.1475.1689.1475h15.9814c.0909 0 .1758-.0645.2032-.1553l.12-.4268zm2.7568-5.5634c-.0771 0-.1611 0-.2383.0112-.0566 0-.1054.0415-.127.0976l-.3378 1.1744c-.1475.5068-.0918.9707.1543 1.3164.2256.3164.6055.498 1.0625.5195l1.8437.1133c.0557 0 .1055.0263.1329.0703.0283.043.0351.1074.0214.1562-.0283.084-.1132.1485-.204.1553l-1.921.1123c-1.041.0488-2.1582.8867-2.5527 1.914l-.1406.3585c-.0283.0713.0215.1416.0986.1416h6.5977c.0771 0 .1474-.0489.169-.126.1122-.4082.1757-.837.1757-1.2803 0-2.6025-2.125-4.727-4.7344-4.727"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,92 @@
|
||||
# Callable Methods
|
||||
|
||||
Fetch `docs/callable-methods.md` from `https://github.com/cloudflare/agents/tree/main/docs` for complete documentation.
|
||||
|
||||
## Overview
|
||||
|
||||
`@callable()` exposes agent methods to clients via WebSocket RPC.
|
||||
|
||||
```typescript
|
||||
import { Agent, callable } from "agents";
|
||||
|
||||
export class MyAgent extends Agent<Env, State> {
|
||||
@callable()
|
||||
async greet(name: string): Promise<string> {
|
||||
return `Hello, ${name}!`;
|
||||
}
|
||||
|
||||
@callable()
|
||||
async processData(data: unknown): Promise<Result> {
|
||||
// Long-running work
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Client Usage
|
||||
|
||||
```typescript
|
||||
// Basic call
|
||||
const greeting = await agent.call("greet", ["World"]);
|
||||
|
||||
// With timeout
|
||||
const result = await agent.call("processData", [data], {
|
||||
timeout: 5000 // 5 second timeout
|
||||
});
|
||||
```
|
||||
|
||||
## Streaming Responses
|
||||
|
||||
```typescript
|
||||
import { Agent, callable, StreamingResponse } from "agents";
|
||||
|
||||
export class MyAgent extends Agent<Env, State> {
|
||||
@callable({ streaming: true })
|
||||
async streamResults(stream: StreamingResponse, query: string) {
|
||||
for await (const item of fetchResults(query)) {
|
||||
stream.send(JSON.stringify(item));
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
|
||||
@callable({ streaming: true })
|
||||
async streamWithError(stream: StreamingResponse) {
|
||||
try {
|
||||
// ... work
|
||||
} catch (error) {
|
||||
stream.error(error.message); // Signal error to client
|
||||
return;
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Client with streaming:
|
||||
|
||||
```typescript
|
||||
await agent.call("streamResults", ["search term"], {
|
||||
stream: {
|
||||
onChunk: (data) => console.log("Chunk:", data),
|
||||
onDone: () => console.log("Complete"),
|
||||
onError: (error) => console.error("Error:", error)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Introspection
|
||||
|
||||
```typescript
|
||||
// Get list of callable methods on an agent
|
||||
const methods = await agent.call("getCallableMethods", []);
|
||||
// Returns: ["greet", "processData", "streamResults", ...]
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
| Scenario | Use |
|
||||
|----------|-----|
|
||||
| Browser/mobile calling agent | `@callable()` |
|
||||
| External service calling agent | `@callable()` |
|
||||
| Worker calling agent (same codebase) | DO RPC directly |
|
||||
| Agent calling another agent | `getAgentByName()` + DO RPC |
|
||||
@@ -0,0 +1,207 @@
|
||||
# Code Mode (Experimental)
|
||||
|
||||
Code Mode generates executable JavaScript instead of making individual tool calls. This significantly reduces token usage and enables complex multi-tool workflows.
|
||||
|
||||
## Why Code Mode?
|
||||
|
||||
Traditional tool calling:
|
||||
- One tool call per LLM request
|
||||
- Multiple round-trips for chained operations
|
||||
- High token usage for complex workflows
|
||||
|
||||
Code Mode:
|
||||
- LLM generates code that orchestrates multiple tools
|
||||
- Single execution for complex workflows
|
||||
- Self-debugging and error recovery
|
||||
- Ideal for MCP server orchestration
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Wrangler Config
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"name": "my-agent-worker",
|
||||
"compatibility_flags": ["experimental", "enable_ctx_exports"],
|
||||
"durable_objects": {
|
||||
// "class_name" must match your Agent class name exactly
|
||||
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
|
||||
},
|
||||
"migrations": [
|
||||
// Required: list all Agent classes for SQLite storage
|
||||
{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"binding": "globalOutbound",
|
||||
// "service" must match "name" above
|
||||
"service": "my-agent-worker",
|
||||
"entrypoint": "globalOutbound"
|
||||
},
|
||||
{
|
||||
"binding": "CodeModeProxy",
|
||||
"service": "my-agent-worker",
|
||||
"entrypoint": "CodeModeProxy"
|
||||
}
|
||||
],
|
||||
"worker_loaders": [{ "binding": "LOADER" }]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Export Required Classes
|
||||
|
||||
```typescript
|
||||
// Export the proxy for tool execution (required for codemode)
|
||||
export { CodeModeProxy } from "@cloudflare/codemode/ai";
|
||||
|
||||
// Define outbound fetch handler for security filtering
|
||||
export const globalOutbound = {
|
||||
fetch: async (input: string | URL | RequestInfo, init?: RequestInit) => {
|
||||
const url = new URL(
|
||||
typeof input === "string"
|
||||
? input
|
||||
: typeof input === "object" && "url" in input
|
||||
? input.url
|
||||
: input.toString()
|
||||
);
|
||||
// Block certain domains if needed
|
||||
if (url.hostname === "blocked.example.com") {
|
||||
return new Response("Not allowed", { status: 403 });
|
||||
}
|
||||
return fetch(input, init);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install @cloudflare/codemode ai @ai-sdk/openai zod
|
||||
```
|
||||
|
||||
### 4. Use Code Mode in Agent
|
||||
|
||||
```typescript
|
||||
import { Agent } from "agents";
|
||||
import { experimental_codemode as codemode } from "@cloudflare/codemode/ai";
|
||||
import { streamText, tool, convertToModelMessages } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { env } from "cloudflare:workers";
|
||||
import { z } from "zod";
|
||||
|
||||
const tools = {
|
||||
getWeather: tool({
|
||||
description: "Get weather for a location",
|
||||
parameters: z.object({ location: z.string() }),
|
||||
execute: async ({ location }) => `Weather: ${location} 72°F`
|
||||
}),
|
||||
sendEmail: tool({
|
||||
description: "Send an email",
|
||||
parameters: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
|
||||
execute: async ({ to, subject, body }) => `Email sent to ${to}`
|
||||
})
|
||||
};
|
||||
|
||||
export class MyAgent extends Agent<Env, State> {
|
||||
tools = {};
|
||||
|
||||
// Method called by codemode proxy
|
||||
callTool(functionName: string, args: unknown[]) {
|
||||
return this.tools[functionName]?.execute?.(args, {
|
||||
abortSignal: new AbortController().signal,
|
||||
toolCallId: "codemode",
|
||||
messages: []
|
||||
});
|
||||
}
|
||||
|
||||
async onChatMessage() {
|
||||
this.tools = { ...tools, ...this.mcp.getAITools() };
|
||||
|
||||
const { prompt, tools: wrappedTools } = await codemode({
|
||||
prompt: "You are a helpful assistant...",
|
||||
tools: this.tools,
|
||||
globalOutbound: env.globalOutbound,
|
||||
loader: env.LOADER,
|
||||
proxy: this.ctx.exports.CodeModeProxy({
|
||||
props: {
|
||||
binding: "MyAgent", // Class name
|
||||
name: this.name, // Instance name
|
||||
callback: "callTool" // Method to call
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const result = streamText({
|
||||
system: prompt,
|
||||
model: openai("gpt-4o"),
|
||||
messages: await convertToModelMessages(this.state.messages),
|
||||
tools: wrappedTools // Use wrapped tools, not original
|
||||
});
|
||||
|
||||
// ... handle stream
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Generated Code Example
|
||||
|
||||
When user asks "Check the weather in NYC and email me the forecast", codemode generates:
|
||||
|
||||
```javascript
|
||||
async function executeTask() {
|
||||
const weather = await codemode.getWeather({ location: "NYC" });
|
||||
|
||||
await codemode.sendEmail({
|
||||
to: "user@example.com",
|
||||
subject: "NYC Weather Forecast",
|
||||
body: `Current weather: ${weather}`
|
||||
});
|
||||
|
||||
return { success: true, weather };
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Server Orchestration
|
||||
|
||||
Code Mode excels at orchestrating multiple MCP servers:
|
||||
|
||||
```javascript
|
||||
async function executeTask() {
|
||||
// Query file system MCP
|
||||
const files = await codemode.listFiles({ path: "/projects" });
|
||||
|
||||
// Query database MCP
|
||||
const status = await codemode.queryDatabase({
|
||||
query: "SELECT * FROM projects WHERE name = ?",
|
||||
params: [files[0].name]
|
||||
});
|
||||
|
||||
// Conditional logic based on results
|
||||
if (status.length === 0) {
|
||||
await codemode.createTask({
|
||||
title: `Review: ${files[0].name}`,
|
||||
priority: "high"
|
||||
});
|
||||
}
|
||||
|
||||
return { files, status };
|
||||
}
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
| Scenario | Use Code Mode? |
|
||||
|----------|---------------|
|
||||
| Single tool call | No |
|
||||
| Chained tool calls | Yes |
|
||||
| Conditional logic across tools | Yes |
|
||||
| MCP multi-server workflows | Yes |
|
||||
| Token budget constrained | Yes |
|
||||
| Simple Q&A chat | No |
|
||||
|
||||
## Limitations
|
||||
|
||||
- Experimental - API may change
|
||||
- Requires Cloudflare Workers
|
||||
- JavaScript execution only (Python planned)
|
||||
- Requires additional wrangler config
|
||||
@@ -0,0 +1,146 @@
|
||||
# Email Handling
|
||||
|
||||
Fetch `docs/email.md` from `https://github.com/cloudflare/agents/tree/main/docs` for complete documentation.
|
||||
|
||||
## Overview
|
||||
|
||||
Agents receive and reply to emails via Cloudflare Email Routing.
|
||||
|
||||
## Wrangler Configuration
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"durable_objects": {
|
||||
"bindings": [{ "name": "EmailAgent", "class_name": "EmailAgent" }]
|
||||
},
|
||||
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["EmailAgent"] }],
|
||||
"send_email": [
|
||||
{ "name": "SEB", "destination_address": "reply@yourdomain.com" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Basic Email Handler
|
||||
|
||||
```typescript
|
||||
import { Agent } from "agents";
|
||||
import { type AgentEmail } from "agents/email";
|
||||
import PostalMime from "postal-mime";
|
||||
|
||||
export class EmailAgent extends Agent<Env, State> {
|
||||
async onEmail(email: AgentEmail) {
|
||||
const raw = await email.getRaw();
|
||||
const parsed = await PostalMime.parse(raw);
|
||||
|
||||
console.log("From:", email.from);
|
||||
console.log("Subject:", parsed.subject);
|
||||
|
||||
await this.replyToEmail(email, {
|
||||
fromName: "My Agent",
|
||||
subject: `Re: ${parsed.subject}`,
|
||||
body: "Thanks for your email!"
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Routing Emails
|
||||
|
||||
```typescript
|
||||
import { routeAgentRequest, routeAgentEmail } from "agents";
|
||||
import { createAddressBasedEmailResolver } from "agents/email";
|
||||
|
||||
export default {
|
||||
async email(message, env) {
|
||||
await routeAgentEmail(message, env, {
|
||||
resolver: createAddressBasedEmailResolver("EmailAgent")
|
||||
});
|
||||
},
|
||||
|
||||
async fetch(request, env) {
|
||||
return routeAgentRequest(request, env) ?? new Response("Not found", { status: 404 });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Resolvers
|
||||
|
||||
### Address-Based (Inbound Mail)
|
||||
|
||||
Routes based on recipient address:
|
||||
|
||||
```typescript
|
||||
import { createAddressBasedEmailResolver } from "agents/email";
|
||||
|
||||
const resolver = createAddressBasedEmailResolver("EmailAgent");
|
||||
// support@example.com → EmailAgent, instance "support"
|
||||
// NotificationAgent+user123@example.com → NotificationAgent, instance "user123"
|
||||
```
|
||||
|
||||
### Secure Reply (Reply Flows)
|
||||
|
||||
Verifies replies are authentic using HMAC-SHA256 signatures:
|
||||
|
||||
```typescript
|
||||
import { createSecureReplyEmailResolver } from "agents/email";
|
||||
|
||||
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
|
||||
maxAge: 7 * 24 * 60 * 60, // 7 days (default: 30 days)
|
||||
onInvalidSignature: (email, reason) => {
|
||||
console.warn(`Invalid signature from ${email.from}: ${reason}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Sign outbound emails to enable secure reply routing:
|
||||
|
||||
```typescript
|
||||
await this.replyToEmail(email, {
|
||||
fromName: "My Agent",
|
||||
body: "Thanks!",
|
||||
secret: this.env.EMAIL_SECRET // Signs headers for secure reply routing
|
||||
});
|
||||
```
|
||||
|
||||
### Catch-All (Single Instance)
|
||||
|
||||
Routes all emails to one agent instance:
|
||||
|
||||
```typescript
|
||||
import { createCatchAllEmailResolver } from "agents/email";
|
||||
|
||||
const resolver = createCatchAllEmailResolver("EmailAgent", "default");
|
||||
```
|
||||
|
||||
### Combining Resolvers
|
||||
|
||||
```typescript
|
||||
async email(message, env) {
|
||||
const secureReply = createSecureReplyEmailResolver(env.EMAIL_SECRET);
|
||||
const addressBased = createAddressBasedEmailResolver("EmailAgent");
|
||||
|
||||
await routeAgentEmail(message, env, {
|
||||
resolver: async (email, env) => {
|
||||
// Try secure reply first
|
||||
const result = await secureReply(email, env);
|
||||
if (result) return result;
|
||||
// Fall back to address-based
|
||||
return addressBased(email, env);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Utilities
|
||||
|
||||
```typescript
|
||||
import { isAutoReplyEmail } from "agents/email";
|
||||
|
||||
async onEmail(email: AgentEmail) {
|
||||
if (isAutoReplyEmail(email.headers)) {
|
||||
// Skip auto-replies (vacation, out-of-office, etc.)
|
||||
return;
|
||||
}
|
||||
// Process email...
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,154 @@
|
||||
# MCP Server Integration
|
||||
|
||||
Fetch `docs/mcp-client.md` and `docs/mcp-servers.md` from `https://github.com/cloudflare/agents/tree/main/docs` for complete documentation.
|
||||
|
||||
Agents include a multi-server MCP client for connecting to external MCP servers.
|
||||
|
||||
## Add an MCP Server
|
||||
|
||||
```typescript
|
||||
import { Agent, callable } from "agents";
|
||||
|
||||
export class MyAgent extends Agent<Env, State> {
|
||||
@callable()
|
||||
async addServer(name: string, url: string) {
|
||||
// Options-based API (recommended)
|
||||
const result = await this.addMcpServer(name, url, {
|
||||
callbackHost: "https://my-worker.workers.dev",
|
||||
transport: { headers: { Authorization: "Bearer ..." } }
|
||||
});
|
||||
|
||||
if (result.state === "authenticating") {
|
||||
// OAuth required - redirect user to result.authUrl
|
||||
return { needsAuth: true, authUrl: result.authUrl };
|
||||
}
|
||||
|
||||
return { ready: true, id: result.id };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Use MCP Tools
|
||||
|
||||
```typescript
|
||||
async onChatMessage() {
|
||||
// Get AI-compatible tools from all connected MCP servers
|
||||
const mcpTools = this.mcp.getAITools();
|
||||
|
||||
const allTools = {
|
||||
...localTools,
|
||||
...mcpTools
|
||||
};
|
||||
|
||||
const result = streamText({
|
||||
model: openai("gpt-4o"),
|
||||
messages: await convertToModelMessages(this.messages),
|
||||
tools: allTools
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## List MCP Resources
|
||||
|
||||
```typescript
|
||||
// List all registered servers
|
||||
const servers = this.mcp.listServers();
|
||||
|
||||
// List tools from all servers
|
||||
const tools = this.mcp.listTools();
|
||||
|
||||
// List resources
|
||||
const resources = this.mcp.listResources();
|
||||
|
||||
// List prompts
|
||||
const prompts = this.mcp.listPrompts();
|
||||
```
|
||||
|
||||
## Remove Server
|
||||
|
||||
```typescript
|
||||
await this.removeMcpServer(serverId);
|
||||
```
|
||||
|
||||
## Building an MCP Server
|
||||
|
||||
Use `McpAgent` from the SDK to create an MCP server.
|
||||
|
||||
**Install dependencies:**
|
||||
```bash
|
||||
npm install @modelcontextprotocol/sdk zod
|
||||
```
|
||||
|
||||
**Wrangler config:**
|
||||
```jsonc
|
||||
{
|
||||
"durable_objects": {
|
||||
"bindings": [{ "name": "MyMCP", "class_name": "MyMCP" }]
|
||||
},
|
||||
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyMCP"] }]
|
||||
}
|
||||
```
|
||||
|
||||
**Server implementation:**
|
||||
```typescript
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { McpAgent } from "agents/mcp";
|
||||
import { z } from "zod";
|
||||
|
||||
type State = { counter: number };
|
||||
|
||||
export class MyMCP extends McpAgent<Env, State, {}> {
|
||||
server = new McpServer({
|
||||
name: "MyMCPServer",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
initialState = { counter: 0 };
|
||||
|
||||
async init() {
|
||||
// Register a resource
|
||||
this.server.resource("counter", "mcp://resource/counter", (uri) => ({
|
||||
contents: [{ text: String(this.state.counter), uri: uri.href }]
|
||||
}));
|
||||
|
||||
// Register a tool
|
||||
this.server.registerTool(
|
||||
"increment",
|
||||
{
|
||||
description: "Increment the counter",
|
||||
inputSchema: { amount: z.number().default(1) }
|
||||
},
|
||||
async ({ amount }) => {
|
||||
this.setState({ counter: this.state.counter + amount });
|
||||
return {
|
||||
content: [{ text: `Counter: ${this.state.counter}`, type: "text" }]
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Serve MCP Server
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// SSE transport (legacy)
|
||||
if (url.pathname.startsWith("/sse")) {
|
||||
return MyMCP.serveSSE("/sse", { binding: "MyMCP" }).fetch(request, env, ctx);
|
||||
}
|
||||
|
||||
// Streamable HTTP transport (recommended)
|
||||
if (url.pathname.startsWith("/mcp")) {
|
||||
return MyMCP.serve("/mcp", { binding: "MyMCP" }).fetch(request, env, ctx);
|
||||
}
|
||||
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,164 @@
|
||||
# State & Scheduling
|
||||
|
||||
Fetch `docs/state.md` and `docs/scheduling.md` from `https://github.com/cloudflare/agents/tree/main/docs` for complete documentation.
|
||||
|
||||
## State Management
|
||||
|
||||
State persists to SQLite and broadcasts to connected clients automatically.
|
||||
|
||||
### Define Typed State
|
||||
|
||||
```typescript
|
||||
type State = {
|
||||
count: number;
|
||||
items: string[];
|
||||
};
|
||||
|
||||
export class MyAgent extends Agent<Env, State> {
|
||||
initialState: State = { count: 0, items: [] };
|
||||
}
|
||||
```
|
||||
|
||||
### Read and Update
|
||||
|
||||
```typescript
|
||||
// Read (lazy-loaded from SQLite)
|
||||
const count = this.state.count;
|
||||
|
||||
// Write (sync, persists, broadcasts)
|
||||
this.setState({ count: this.state.count + 1 });
|
||||
```
|
||||
|
||||
### Validation Hook
|
||||
|
||||
`validateStateChange()` runs synchronously before state persists. Throw to reject the update.
|
||||
|
||||
```typescript
|
||||
validateStateChange(nextState: State, source: Connection | "server") {
|
||||
if (nextState.count < 0) {
|
||||
throw new Error("Count cannot be negative");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Execution Order
|
||||
|
||||
1. `validateStateChange(nextState, source)` - sync, gating
|
||||
2. State persisted to SQLite
|
||||
3. State broadcast to connected clients
|
||||
4. `onStateUpdate(nextState, source)` - async via `ctx.waitUntil`, non-gating
|
||||
|
||||
### Client-Side Sync (React)
|
||||
|
||||
```tsx
|
||||
import { useAgent } from "agents/react";
|
||||
|
||||
function App() {
|
||||
const [state, setLocalState] = useState<State>({ count: 0 });
|
||||
|
||||
const agent = useAgent<State>({
|
||||
agent: "MyAgent",
|
||||
name: "instance-1",
|
||||
onStateUpdate: (newState) => setLocalState(newState)
|
||||
});
|
||||
|
||||
return <button onClick={() => agent.setState({ count: state.count + 1 })}>
|
||||
Count: {state.count}
|
||||
</button>;
|
||||
}
|
||||
```
|
||||
|
||||
## SQL API
|
||||
|
||||
Direct SQLite access for custom queries:
|
||||
|
||||
```typescript
|
||||
// Create table
|
||||
this.sql`
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
created_at INTEGER DEFAULT (unixepoch())
|
||||
)
|
||||
`;
|
||||
|
||||
// Insert
|
||||
this.sql`INSERT INTO items (id, name) VALUES (${id}, ${name})`;
|
||||
|
||||
// Query with types
|
||||
const items = this.sql<{ id: string; name: string }>`
|
||||
SELECT * FROM items WHERE name LIKE ${`%${search}%`}
|
||||
`;
|
||||
```
|
||||
|
||||
## Scheduling
|
||||
|
||||
### Schedule Types
|
||||
|
||||
| Mode | Syntax | Use Case |
|
||||
|------|--------|----------|
|
||||
| Delay | `this.schedule(60, ...)` | Run in 60 seconds |
|
||||
| Date | `this.schedule(new Date(...), ...)` | Run at specific time |
|
||||
| Cron | `this.schedule("0 8 * * *", ...)` | Recurring schedule |
|
||||
| Interval | `this.scheduleEvery(30, ...)` | Fixed interval (every 30s) |
|
||||
|
||||
### Examples
|
||||
|
||||
```typescript
|
||||
// Delay (seconds)
|
||||
await this.schedule(60, "checkStatus", { id: "abc123" });
|
||||
|
||||
// Specific date
|
||||
await this.schedule(new Date("2025-12-25T00:00:00Z"), "sendGreeting", { to: "user" });
|
||||
|
||||
// Cron (recurring)
|
||||
await this.schedule("0 9 * * 1-5", "weekdayReport", {});
|
||||
|
||||
// Fixed interval (every 30 seconds, overlap prevention built-in)
|
||||
await this.scheduleEvery(30, "pollUpdates");
|
||||
await this.scheduleEvery(300, "syncData", { source: "api" });
|
||||
```
|
||||
|
||||
### Handler
|
||||
|
||||
```typescript
|
||||
async sendGreeting(payload: { to: string }, schedule: Schedule) {
|
||||
console.log(`Sending greeting to ${payload.to}`);
|
||||
// Cron schedules auto-reschedule; one-time schedules are deleted
|
||||
}
|
||||
```
|
||||
|
||||
### Manage Schedules
|
||||
|
||||
```typescript
|
||||
const schedules = this.getSchedules();
|
||||
const crons = this.getSchedules({ type: "cron" });
|
||||
await this.cancelSchedule(schedule.id);
|
||||
```
|
||||
|
||||
## Lifecycle Callbacks
|
||||
|
||||
```typescript
|
||||
export class MyAgent extends Agent<Env, State> {
|
||||
async onStart() {
|
||||
// Agent started or woke from hibernation
|
||||
}
|
||||
|
||||
onConnect(conn: Connection, ctx: ConnectionContext) {
|
||||
// WebSocket connected
|
||||
}
|
||||
|
||||
onMessage(conn: Connection, message: WSMessage) {
|
||||
// WebSocket message (non-RPC)
|
||||
}
|
||||
|
||||
onStateUpdate(state: State, source: Connection | "server") {
|
||||
// State changed (async, non-blocking)
|
||||
}
|
||||
|
||||
onError(error: unknown) {
|
||||
// Error handler
|
||||
throw error; // Re-throw to propagate
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,178 @@
|
||||
# Streaming Chat with AIChatAgent
|
||||
|
||||
Fetch `docs/resumable-streaming.md` and `docs/client-sdk.md` from `https://github.com/cloudflare/agents/tree/main/docs` for complete documentation.
|
||||
|
||||
`AIChatAgent` provides streaming chat with automatic message persistence and resumable streams.
|
||||
|
||||
## Basic Chat Agent
|
||||
|
||||
```typescript
|
||||
import { AIChatAgent } from "@cloudflare/ai-chat";
|
||||
import { streamText, convertToModelMessages } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
|
||||
export class Chat extends AIChatAgent<Env> {
|
||||
async onChatMessage(onFinish) {
|
||||
const result = streamText({
|
||||
model: openai("gpt-4o"),
|
||||
messages: await convertToModelMessages(this.messages),
|
||||
onFinish
|
||||
});
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## With Custom System Prompt
|
||||
|
||||
```typescript
|
||||
export class Chat extends AIChatAgent<Env> {
|
||||
async onChatMessage(onFinish) {
|
||||
const result = streamText({
|
||||
model: openai("gpt-4o"),
|
||||
system: "You are a helpful assistant specializing in...",
|
||||
messages: await convertToModelMessages(this.messages),
|
||||
onFinish
|
||||
});
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## With Tools
|
||||
|
||||
```typescript
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
const tools = {
|
||||
getWeather: tool({
|
||||
description: "Get weather for a location",
|
||||
parameters: z.object({ location: z.string() }),
|
||||
execute: async ({ location }) => `Weather in ${location}: 72°F, sunny`
|
||||
})
|
||||
};
|
||||
|
||||
export class Chat extends AIChatAgent<Env> {
|
||||
async onChatMessage(onFinish) {
|
||||
const result = streamText({
|
||||
model: openai("gpt-4o"),
|
||||
messages: await convertToModelMessages(this.messages),
|
||||
tools,
|
||||
onFinish
|
||||
});
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom UI Message Stream
|
||||
|
||||
For more control, use `createUIMessageStream`:
|
||||
|
||||
```typescript
|
||||
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
|
||||
|
||||
export class Chat extends AIChatAgent<Env> {
|
||||
async onChatMessage(onFinish) {
|
||||
const stream = createUIMessageStream({
|
||||
execute: async ({ writer }) => {
|
||||
const result = streamText({
|
||||
model: openai("gpt-4o"),
|
||||
messages: await convertToModelMessages(this.messages),
|
||||
onFinish
|
||||
});
|
||||
writer.merge(result.toUIMessageStream());
|
||||
}
|
||||
});
|
||||
return createUIMessageStreamResponse({ stream });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resumable Streaming
|
||||
|
||||
Streams automatically resume if client disconnects and reconnects:
|
||||
|
||||
1. Chunks buffered to SQLite during streaming
|
||||
2. On reconnect, buffered chunks sent immediately
|
||||
3. Live streaming continues from where it left off
|
||||
|
||||
**Enabled by default.** To disable:
|
||||
|
||||
```tsx
|
||||
const { messages } = useAgentChat({ agent, resume: false });
|
||||
```
|
||||
|
||||
## React Client
|
||||
|
||||
```tsx
|
||||
import { useAgent } from "agents/react";
|
||||
import { useAgentChat } from "@cloudflare/ai-chat/react";
|
||||
|
||||
function ChatUI() {
|
||||
const agent = useAgent({
|
||||
agent: "Chat",
|
||||
name: "my-chat-session"
|
||||
});
|
||||
|
||||
const {
|
||||
messages,
|
||||
input,
|
||||
handleInputChange,
|
||||
handleSubmit,
|
||||
status
|
||||
} = useAgentChat({ agent });
|
||||
|
||||
return (
|
||||
<div>
|
||||
{messages.map((m) => (
|
||||
<div key={m.id}>
|
||||
<strong>{m.role}:</strong> {m.content}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
disabled={status === "streaming"}
|
||||
/>
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Streaming RPC Methods
|
||||
|
||||
For non-chat streaming, use `@callable({ streaming: true })`:
|
||||
|
||||
```typescript
|
||||
import { Agent, callable, StreamingResponse } from "agents";
|
||||
|
||||
export class MyAgent extends Agent<Env> {
|
||||
@callable({ streaming: true })
|
||||
async streamData(stream: StreamingResponse, query: string) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
stream.send(`Result ${i}: ${query}`);
|
||||
await sleep(100);
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Client receives streamed messages via WebSocket RPC.
|
||||
|
||||
## Status Values
|
||||
|
||||
`useAgentChat` status:
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| `ready` | Idle, ready for input |
|
||||
| `streaming` | Response streaming |
|
||||
| `submitted` | Request sent, waiting |
|
||||
| `error` | Error occurred |
|
||||
@@ -0,0 +1,132 @@
|
||||
# Workflows Integration
|
||||
|
||||
Fetch `docs/workflows.md` from `https://github.com/cloudflare/agents/tree/main/docs` for complete documentation.
|
||||
|
||||
## Overview
|
||||
|
||||
Agents handle real-time communication; Workflows handle durable execution. Together they enable:
|
||||
|
||||
- Long-running background tasks with automatic retries
|
||||
- Human-in-the-loop approval flows
|
||||
- Multi-step pipelines that survive failures
|
||||
|
||||
| Use Case | Recommendation |
|
||||
|----------|----------------|
|
||||
| Chat/messaging | Agent only |
|
||||
| Quick API calls (<30s) | Agent only |
|
||||
| Background processing (<30s) | Agent `queue()` |
|
||||
| Long-running tasks (>30s) | Agent + Workflow |
|
||||
| Human approval flows | Agent + Workflow |
|
||||
|
||||
## AgentWorkflow Base Class
|
||||
|
||||
```typescript
|
||||
import { AgentWorkflow } from "agents/workflows";
|
||||
import type { AgentWorkflowEvent, AgentWorkflowStep } from "agents/workflows";
|
||||
|
||||
type TaskParams = { taskId: string; data: string };
|
||||
|
||||
export class ProcessingWorkflow extends AgentWorkflow<MyAgent, TaskParams> {
|
||||
async run(event: AgentWorkflowEvent<TaskParams>, step: AgentWorkflowStep) {
|
||||
const params = event.payload;
|
||||
|
||||
// Durable step - retries on failure
|
||||
const result = await step.do("process", async () => {
|
||||
return processData(params.data);
|
||||
});
|
||||
|
||||
// Non-durable: progress reporting
|
||||
await this.reportProgress({ step: "process", percent: 0.5 });
|
||||
|
||||
// Non-durable: broadcast to connected clients
|
||||
this.broadcastToClients({ type: "update", taskId: params.taskId });
|
||||
|
||||
// Durable: merge state via step
|
||||
await step.mergeAgentState({ lastProcessed: params.taskId });
|
||||
|
||||
// Durable: report completion
|
||||
await step.reportComplete(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Wrangler Configuration
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"workflows": [
|
||||
{ "name": "processing-workflow", "binding": "PROCESSING_WORKFLOW", "class_name": "ProcessingWorkflow" }
|
||||
],
|
||||
"durable_objects": {
|
||||
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
|
||||
},
|
||||
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }]
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Methods for Workflows
|
||||
|
||||
```typescript
|
||||
// Start a workflow
|
||||
const instance = await this.runWorkflow("ProcessingWorkflow", { taskId: "123", data: "..." });
|
||||
|
||||
// Send event to waiting workflow
|
||||
await this.sendWorkflowEvent("ProcessingWorkflow", workflowId, { type: "approve" });
|
||||
|
||||
// Query workflows
|
||||
const workflow = await this.getWorkflow(workflowId);
|
||||
const workflows = await this.getWorkflows({ status: "running" });
|
||||
|
||||
// Control workflows
|
||||
await this.approveWorkflow(workflowId);
|
||||
await this.rejectWorkflow(workflowId);
|
||||
await this.terminateWorkflow(workflowId);
|
||||
await this.pauseWorkflow(workflowId);
|
||||
await this.resumeWorkflow(workflowId);
|
||||
|
||||
// Delete workflows
|
||||
await this.deleteWorkflow(workflowId);
|
||||
await this.deleteWorkflows({ status: "complete", before: new Date(...) });
|
||||
```
|
||||
|
||||
## Lifecycle Callbacks
|
||||
|
||||
```typescript
|
||||
export class MyAgent extends Agent<Env, State> {
|
||||
async onWorkflowProgress(workflowName: string, workflowId: string, progress: unknown) {
|
||||
// Workflow reported progress via this.reportProgress()
|
||||
this.broadcast({ type: "progress", workflowId, progress });
|
||||
}
|
||||
|
||||
async onWorkflowComplete(workflowName: string, workflowId: string, result?: unknown) {
|
||||
// Workflow finished successfully
|
||||
}
|
||||
|
||||
async onWorkflowError(workflowName: string, workflowId: string, error: Error) {
|
||||
// Workflow failed
|
||||
}
|
||||
|
||||
async onWorkflowEvent(workflowName: string, workflowId: string, event: unknown) {
|
||||
// Workflow received an event via sendWorkflowEvent()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Human-in-the-Loop
|
||||
|
||||
```typescript
|
||||
// In workflow: wait for approval
|
||||
const approved = await step.waitForEvent<{ approved: boolean }>("approval", {
|
||||
timeout: "7d"
|
||||
});
|
||||
|
||||
if (!approved.approved) {
|
||||
throw new Error("Rejected");
|
||||
}
|
||||
|
||||
// From agent: approve or reject
|
||||
await this.approveWorkflow(workflowId); // Sends { approved: true }
|
||||
await this.rejectWorkflow(workflowId); // Sends { approved: false }
|
||||
```
|
||||
@@ -6,5 +6,5 @@ interface:
|
||||
dependencies:
|
||||
tools:
|
||||
- type: "mcp"
|
||||
value: "chronograph-gp"
|
||||
value: "chronograph-lp"
|
||||
description: "Optional Chronograph MCP server for live company metadata, investments, and metric retrieval. Not required when working from an uploaded Excel model."
|
||||
+155
-43
@@ -1,60 +1,172 @@
|
||||
---
|
||||
name: circleci-cli
|
||||
description: Operate and troubleshoot CircleCI using the CircleCI CLI. Use when users ask to authenticate CLI access, inspect pipeline/workflow/job status, validate configuration locally, rerun pipelines/jobs, trigger pipelines, or gather actionable diagnostics from CLI outputs.
|
||||
name: hf-cli
|
||||
description: "Hugging Face Hub CLI (`hf`) for downloading, uploading, and managing repositories, models, datasets, and Spaces on the Hugging Face Hub. Replaces now deprecated `huggingface-cli` command."
|
||||
---
|
||||
|
||||
# CircleCI CLI
|
||||
Install: `curl -LsSf https://hf.co/cli/install.sh | bash -s`.
|
||||
|
||||
## Overview
|
||||
The Hugging Face Hub CLI tool `hf` is available. IMPORTANT: The `hf` command replaces the deprecated `huggingface-cli` command.
|
||||
|
||||
Use this skill when the fastest path is CircleCI CLI-driven operations rather than editing config first. Prioritize safe, read-first diagnostics, then run targeted mutating commands only after confirming scope.
|
||||
Use `hf --help` to view available functions. Note that auth commands are now all under `hf auth` e.g. `hf auth whoami`.
|
||||
|
||||
## Inputs To Gather
|
||||
## Commands
|
||||
|
||||
- Repository path and target branch
|
||||
- CircleCI project slug (if needed)
|
||||
- Whether objective is inspect, rerun, trigger, or validate
|
||||
- Required token/auth state and org permissions
|
||||
- `hf download REPO_ID` — Download files from the Hub. `[--type CHOICE --revision TEXT --include TEXT --exclude TEXT --cache-dir TEXT --local-dir TEXT --force-download --dry-run --quiet --max-workers INTEGER]`
|
||||
- `hf env` — Print information about the environment.
|
||||
- `hf sync` — Sync files between local directory and a bucket. `[--delete --ignore-times --ignore-sizes --plan TEXT --apply TEXT --dry-run --include TEXT --exclude TEXT --filter-from TEXT --existing --ignore-existing --verbose --quiet]`
|
||||
- `hf upload REPO_ID` — Upload a file or a folder to the Hub. Recommended for single-commit uploads. `[--type CHOICE --revision TEXT --private --include TEXT --exclude TEXT --delete TEXT --commit-message TEXT --commit-description TEXT --create-pr --every FLOAT --quiet]`
|
||||
- `hf upload-large-folder REPO_ID LOCAL_PATH` — Upload a large folder to the Hub. Recommended for resumable uploads. `[--type CHOICE --revision TEXT --private --include TEXT --exclude TEXT --num-workers INTEGER --no-report --no-bars]`
|
||||
- `hf version` — Print information about the hf version.
|
||||
|
||||
## Workflow
|
||||
### `hf auth` — Manage authentication (login, logout, etc.).
|
||||
|
||||
1. Verify CLI and auth state.
|
||||
- Confirm `circleci` is installed and version is available.
|
||||
- Confirm token/auth before issuing remote CircleCI commands.
|
||||
2. Run read-only diagnostics first.
|
||||
- Inspect available pipeline/project/trigger state and capture concrete identifiers.
|
||||
- Extract first failing scope and step details from supported command output before rerun/trigger actions.
|
||||
3. Validate config locally when relevant.
|
||||
- Run config validation/processing commands before committing risky edits.
|
||||
4. Run targeted mutation commands.
|
||||
- Rerun only required workflow/job scope.
|
||||
- Trigger pipelines with explicit parameters and branch context.
|
||||
5. Report results and next action.
|
||||
- Provide exact command results, remaining blockers, and safest follow-up.
|
||||
- `hf auth list` — List all stored access tokens.
|
||||
- `hf auth login` — Login using a token from huggingface.co/settings/tokens. `[--add-to-git-credential --force]`
|
||||
- `hf auth logout` — Logout from a specific token. `[--token-name TEXT]`
|
||||
- `hf auth switch` — Switch between access tokens. `[--token-name TEXT --add-to-git-credential]`
|
||||
- `hf auth whoami` — Find out which huggingface.co account you are logged in as. `[--format CHOICE]`
|
||||
|
||||
## Guardrails
|
||||
### `hf buckets` — Commands to interact with buckets.
|
||||
|
||||
- Prefer read-only commands before rerun/trigger/cancel operations.
|
||||
- Confirm organization/project scope before mutating pipeline state.
|
||||
- Never print raw secret values from environment variables or tokens.
|
||||
- If permissions fail, report exact auth/scope gap and safest remediation.
|
||||
- Respect installed CLI capabilities and avoid inventing commands.
|
||||
- Do not use `circleci api`, `circleci workflow`, or other unavailable legacy commands unless `circleci help` confirms they exist.
|
||||
- `hf buckets cp SRC` — Copy a single file to or from a bucket. `[--quiet]`
|
||||
- `hf buckets create BUCKET_ID` — Create a new bucket. `[--private --exist-ok --quiet]`
|
||||
- `hf buckets delete BUCKET_ID` — Delete a bucket. `[--yes --missing-ok --quiet]`
|
||||
- `hf buckets info BUCKET_ID` — Get info about a bucket. `[--quiet]`
|
||||
- `hf buckets list` — List buckets or files in a bucket. `[--human-readable --tree --recursive --format CHOICE --quiet]`
|
||||
- `hf buckets move FROM_ID TO_ID` — Move (rename) a bucket to a new name or namespace.
|
||||
- `hf buckets remove ARGUMENT` — Remove files from a bucket. `[--recursive --yes --dry-run --include TEXT --exclude TEXT --quiet]`
|
||||
- `hf buckets sync` — Sync files between local directory and a bucket. `[--delete --ignore-times --ignore-sizes --plan TEXT --apply TEXT --dry-run --include TEXT --exclude TEXT --filter-from TEXT --existing --ignore-existing --verbose --quiet]`
|
||||
|
||||
## Installed CLI Compatibility
|
||||
### `hf cache` — Manage local cache directory.
|
||||
|
||||
For newer `circleci` builds that expose domain subcommands (for example `pipeline`, `project`, `trigger`) but not `api`:
|
||||
- `hf cache list` — List cached repositories or revisions. `[--cache-dir TEXT --revisions --filter TEXT --format CHOICE --quiet --sort CHOICE --limit INTEGER]`
|
||||
- `hf cache prune` — Remove detached revisions from the cache. `[--cache-dir TEXT --yes --dry-run]`
|
||||
- `hf cache rm TARGETS` — Remove cached repositories or revisions. `[--cache-dir TEXT --yes --dry-run]`
|
||||
- `hf cache verify REPO_ID` — Verify checksums for a single repo revision from cache or a local directory. `[--type CHOICE --revision TEXT --cache-dir TEXT --local-dir TEXT --fail-on-missing-files --fail-on-extra-files]`
|
||||
|
||||
- Verify available commands first with `circleci help`.
|
||||
- Use only discovered subcommands from help output.
|
||||
- Prefer `circleci pipeline list|create|run` and `circleci trigger ...` for pipeline operations.
|
||||
- For cloud job logs, use supported platform tools (CircleCI app/UI or connected CircleCI MCP tooling) if the CLI does not expose a logs command.
|
||||
### `hf collections` — Interact with collections on the Hub.
|
||||
|
||||
## Output Contract
|
||||
- `hf collections add-item COLLECTION_SLUG ITEM_ID ITEM_TYPE` — Add an item to a collection. `[--note TEXT --exists-ok]`
|
||||
- `hf collections create TITLE` — Create a new collection on the Hub. `[--namespace TEXT --description TEXT --private --exists-ok]`
|
||||
- `hf collections delete COLLECTION_SLUG` — Delete a collection from the Hub. `[--missing-ok]`
|
||||
- `hf collections delete-item COLLECTION_SLUG ITEM_OBJECT_ID` — Delete an item from a collection. `[--missing-ok]`
|
||||
- `hf collections info COLLECTION_SLUG` — Get info about a collection on the Hub. Output is in JSON format.
|
||||
- `hf collections list` — List collections on the Hub. `[--owner TEXT --item TEXT --sort CHOICE --limit INTEGER --format CHOICE --quiet]`
|
||||
- `hf collections update COLLECTION_SLUG` — Update a collection's metadata on the Hub. `[--title TEXT --description TEXT --position INTEGER --private --theme TEXT]`
|
||||
- `hf collections update-item COLLECTION_SLUG ITEM_OBJECT_ID` — Update an item in a collection. `[--note TEXT --position INTEGER]`
|
||||
|
||||
Provide:
|
||||
### `hf datasets` — Interact with datasets on the Hub.
|
||||
|
||||
1. Commands run and purpose.
|
||||
2. Key outputs (pipeline/workflow/job ids, status, failing step).
|
||||
3. Actions taken (rerun/trigger/validate) and why.
|
||||
4. Remaining blockers and next recommended CLI command.
|
||||
- `hf datasets info DATASET_ID` — Get info about a dataset on the Hub. Output is in JSON format. `[--revision TEXT --expand TEXT]`
|
||||
- `hf datasets list` — List datasets on the Hub. `[--search TEXT --author TEXT --filter TEXT --sort CHOICE --limit INTEGER --expand TEXT --format CHOICE --quiet]`
|
||||
- `hf datasets parquet DATASET_ID` — List parquet file URLs available for a dataset. `[--subset TEXT --split TEXT --format CHOICE --quiet]`
|
||||
- `hf datasets sql SQL` — Execute a raw SQL query with DuckDB against dataset parquet URLs. `[--format CHOICE]`
|
||||
|
||||
### `hf discussions` — Manage discussions and pull requests on the Hub.
|
||||
|
||||
- `hf discussions close REPO_ID NUM` — Close a discussion or pull request. `[--comment TEXT --yes --type CHOICE]`
|
||||
- `hf discussions comment REPO_ID NUM` — Comment on a discussion or pull request. `[--body TEXT --body-file PATH --type CHOICE]`
|
||||
- `hf discussions create REPO_ID --title TEXT` — Create a new discussion or pull request on a repo. `[--body TEXT --body-file PATH --pull-request --type CHOICE]`
|
||||
- `hf discussions diff REPO_ID NUM` — Show the diff of a pull request. `[--type CHOICE]`
|
||||
- `hf discussions info REPO_ID NUM` — Get info about a discussion or pull request. `[--comments --diff --no-color --type CHOICE --format CHOICE]`
|
||||
- `hf discussions list REPO_ID` — List discussions and pull requests on a repo. `[--status CHOICE --kind CHOICE --author TEXT --limit INTEGER --type CHOICE --format CHOICE --quiet]`
|
||||
- `hf discussions merge REPO_ID NUM` — Merge a pull request. `[--comment TEXT --yes --type CHOICE]`
|
||||
- `hf discussions rename REPO_ID NUM NEW_TITLE` — Rename a discussion or pull request. `[--type CHOICE]`
|
||||
- `hf discussions reopen REPO_ID NUM` — Reopen a closed discussion or pull request. `[--comment TEXT --yes --type CHOICE]`
|
||||
|
||||
### `hf endpoints` — Manage Hugging Face Inference Endpoints.
|
||||
|
||||
- `hf endpoints catalog deploy --repo TEXT` — Deploy an Inference Endpoint from the Model Catalog. `[--name TEXT --accelerator TEXT --namespace TEXT]`
|
||||
- `hf endpoints catalog list` — List available Catalog models.
|
||||
- `hf endpoints delete NAME` — Delete an Inference Endpoint permanently. `[--namespace TEXT --yes]`
|
||||
- `hf endpoints deploy NAME --repo TEXT --framework TEXT --accelerator TEXT --instance-size TEXT --instance-type TEXT --region TEXT --vendor TEXT` — Deploy an Inference Endpoint from a Hub repository. `[--namespace TEXT --task TEXT --min-replica INTEGER --max-replica INTEGER --scale-to-zero-timeout INTEGER --scaling-metric CHOICE --scaling-threshold FLOAT]`
|
||||
- `hf endpoints describe NAME` — Get information about an existing endpoint. `[--namespace TEXT]`
|
||||
- `hf endpoints list` — Lists all Inference Endpoints for the given namespace. `[--namespace TEXT --format CHOICE --quiet]`
|
||||
- `hf endpoints pause NAME` — Pause an Inference Endpoint. `[--namespace TEXT]`
|
||||
- `hf endpoints resume NAME` — Resume an Inference Endpoint. `[--namespace TEXT --fail-if-already-running]`
|
||||
- `hf endpoints scale-to-zero NAME` — Scale an Inference Endpoint to zero. `[--namespace TEXT]`
|
||||
- `hf endpoints update NAME` — Update an existing endpoint. `[--namespace TEXT --repo TEXT --accelerator TEXT --instance-size TEXT --instance-type TEXT --framework TEXT --revision TEXT --task TEXT --min-replica INTEGER --max-replica INTEGER --scale-to-zero-timeout INTEGER --scaling-metric CHOICE --scaling-threshold FLOAT]`
|
||||
|
||||
### `hf extensions` — Manage hf CLI extensions.
|
||||
|
||||
- `hf extensions exec NAME` — Execute an installed extension.
|
||||
- `hf extensions install REPO_ID` — Install an extension from a public GitHub repository. `[--force]`
|
||||
- `hf extensions list` — List installed extension commands. `[--format CHOICE --quiet]`
|
||||
- `hf extensions remove NAME` — Remove an installed extension.
|
||||
- `hf extensions search` — Search extensions available on GitHub (tagged with 'hf-extension' topic). `[--format CHOICE --quiet]`
|
||||
|
||||
### `hf jobs` — Run and manage Jobs on the Hub.
|
||||
|
||||
- `hf jobs cancel JOB_ID` — Cancel a Job `[--namespace TEXT]`
|
||||
- `hf jobs hardware` — List available hardware options for Jobs
|
||||
- `hf jobs inspect JOB_IDS` — Display detailed information on one or more Jobs `[--namespace TEXT]`
|
||||
- `hf jobs logs JOB_ID` — Fetch the logs of a Job. `[--follow --tail INTEGER --namespace TEXT]`
|
||||
- `hf jobs ps` — List Jobs. `[--all --namespace TEXT --filter TEXT --format TEXT --quiet]`
|
||||
- `hf jobs run IMAGE COMMAND` — Run a Job. `[--env TEXT --secrets TEXT --label TEXT --env-file TEXT --secrets-file TEXT --flavor CHOICE --timeout TEXT --detach --namespace TEXT]`
|
||||
- `hf jobs scheduled delete SCHEDULED_JOB_ID` — Delete a scheduled Job. `[--namespace TEXT]`
|
||||
- `hf jobs scheduled inspect SCHEDULED_JOB_IDS` — Display detailed information on one or more scheduled Jobs `[--namespace TEXT]`
|
||||
- `hf jobs scheduled ps` — List scheduled Jobs `[--all --namespace TEXT --filter TEXT --format TEXT --quiet]`
|
||||
- `hf jobs scheduled resume SCHEDULED_JOB_ID` — Resume (unpause) a scheduled Job. `[--namespace TEXT]`
|
||||
- `hf jobs scheduled run SCHEDULE IMAGE COMMAND` — Schedule a Job. `[--suspend --concurrency --env TEXT --secrets TEXT --label TEXT --env-file TEXT --secrets-file TEXT --flavor CHOICE --timeout TEXT --namespace TEXT]`
|
||||
- `hf jobs scheduled suspend SCHEDULED_JOB_ID` — Suspend (pause) a scheduled Job. `[--namespace TEXT]`
|
||||
- `hf jobs scheduled uv run SCHEDULE SCRIPT` — Run a UV script (local file or URL) on HF infrastructure `[--suspend --concurrency --image TEXT --flavor CHOICE --env TEXT --secrets TEXT --label TEXT --env-file TEXT --secrets-file TEXT --timeout TEXT --namespace TEXT --with TEXT --python TEXT]`
|
||||
- `hf jobs stats` — Fetch the resource usage statistics and metrics of Jobs `[--namespace TEXT]`
|
||||
- `hf jobs uv run SCRIPT` — Run a UV script (local file or URL) on HF infrastructure `[--image TEXT --flavor CHOICE --env TEXT --secrets TEXT --label TEXT --env-file TEXT --secrets-file TEXT --timeout TEXT --detach --namespace TEXT --with TEXT --python TEXT]`
|
||||
|
||||
### `hf models` — Interact with models on the Hub.
|
||||
|
||||
- `hf models info MODEL_ID` — Get info about a model on the Hub. Output is in JSON format. `[--revision TEXT --expand TEXT]`
|
||||
- `hf models list` — List models on the Hub. `[--search TEXT --author TEXT --filter TEXT --num-parameters TEXT --sort CHOICE --limit INTEGER --expand TEXT --format CHOICE --quiet]`
|
||||
|
||||
### `hf papers` — Interact with papers on the Hub.
|
||||
|
||||
- `hf papers list` — List daily papers on the Hub. `[--date TEXT --sort CHOICE --limit INTEGER --format CHOICE --quiet]`
|
||||
|
||||
### `hf repos` — Manage repos on the Hub.
|
||||
|
||||
- `hf repos branch create REPO_ID BRANCH` — Create a new branch for a repo on the Hub. `[--revision TEXT --type CHOICE --exist-ok]`
|
||||
- `hf repos branch delete REPO_ID BRANCH` — Delete a branch from a repo on the Hub. `[--type CHOICE]`
|
||||
- `hf repos create REPO_ID` — Create a new repo on the Hub. `[--type CHOICE --space-sdk TEXT --private --exist-ok --resource-group-id TEXT]`
|
||||
- `hf repos delete REPO_ID` — Delete a repo from the Hub. This is an irreversible operation. `[--type CHOICE --missing-ok]`
|
||||
- `hf repos delete-files REPO_ID PATTERNS` — Delete files from a repo on the Hub. `[--type CHOICE --revision TEXT --commit-message TEXT --commit-description TEXT --create-pr]`
|
||||
- `hf repos duplicate FROM_ID` — Duplicate a repo on the Hub (model, dataset, or Space). `[--type CHOICE --private --exist-ok]`
|
||||
- `hf repos move FROM_ID TO_ID` — Move a repository from a namespace to another namespace. `[--type CHOICE]`
|
||||
- `hf repos settings REPO_ID` — Update the settings of a repository. `[--gated CHOICE --private --type CHOICE]`
|
||||
- `hf repos tag create REPO_ID TAG` — Create a tag for a repo. `[--message TEXT --revision TEXT --type CHOICE]`
|
||||
- `hf repos tag delete REPO_ID TAG` — Delete a tag for a repo. `[--yes --type CHOICE]`
|
||||
- `hf repos tag list REPO_ID` — List tags for a repo. `[--type CHOICE]`
|
||||
|
||||
### `hf skills` — Manage skills for AI assistants.
|
||||
|
||||
- `hf skills add` — Download a skill and install it for an AI assistant. `[--claude --codex --cursor --opencode --global --dest PATH --force]`
|
||||
- `hf skills preview` — Print the generated SKILL.md to stdout.
|
||||
|
||||
### `hf spaces` — Interact with spaces on the Hub.
|
||||
|
||||
- `hf spaces dev-mode SPACE_ID` — Enable or disable dev mode on a Space. `[--stop]`
|
||||
- `hf spaces hot-reload SPACE_ID` — Hot-reload any Python file of a Space without a full rebuild + restart. `[--local-file TEXT --skip-checks --skip-summary]`
|
||||
- `hf spaces info SPACE_ID` — Get info about a space on the Hub. Output is in JSON format. `[--revision TEXT --expand TEXT]`
|
||||
- `hf spaces list` — List spaces on the Hub. `[--search TEXT --author TEXT --filter TEXT --sort CHOICE --limit INTEGER --expand TEXT --format CHOICE --quiet]`
|
||||
|
||||
### `hf webhooks` — Manage webhooks on the Hub.
|
||||
|
||||
- `hf webhooks create --watch TEXT` — Create a new webhook. `[--url TEXT --job-id TEXT --domain CHOICE --secret TEXT]`
|
||||
- `hf webhooks delete WEBHOOK_ID` — Delete a webhook permanently. `[--yes]`
|
||||
- `hf webhooks disable WEBHOOK_ID` — Disable an active webhook.
|
||||
- `hf webhooks enable WEBHOOK_ID` — Enable a disabled webhook.
|
||||
- `hf webhooks info WEBHOOK_ID` — Show full details for a single webhook as JSON.
|
||||
- `hf webhooks list` — List all webhooks for the current user. `[--format CHOICE --quiet]`
|
||||
- `hf webhooks update WEBHOOK_ID` — Update an existing webhook. Only provided options are changed. `[--url TEXT --watch TEXT --domain CHOICE --secret TEXT]`
|
||||
|
||||
## Common options
|
||||
|
||||
- `--format` — Output format: `--format json` (or `--json`) or `--format table` (default).
|
||||
- `-q / --quiet` — Minimal output.
|
||||
- `--revision` — Git revision id which can be a branch name, a tag, or a commit hash.
|
||||
- `--token` — Use a User Access Token. Prefer setting `HF_TOKEN` env var instead of passing `--token`.
|
||||
- `--type` — The type of repository (model, dataset, or space).
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `hf <command> --help` for full options, descriptions, usage, and real-world examples
|
||||
- Authenticate with `HF_TOKEN` env var (recommended) or with `--token`
|
||||
@@ -1,3 +1,3 @@
|
||||
interface:
|
||||
display_name: "CircleCI CLI"
|
||||
short_description: "Operate and troubleshoot CircleCI with the CLI"
|
||||
display_name: "HF CLI"
|
||||
short_description: "Manage Hub repos, models, datasets, and Spaces with the Hugging Face CLI"
|
||||
+165
-25
@@ -1,35 +1,175 @@
|
||||
---
|
||||
name: setup
|
||||
description: Verify Daloopa MCP connection and show available skills
|
||||
name: mixpanel-headless-setup
|
||||
description: This skill installs mixpanel_headless, pandas, numpy, matplotlib, seaborn, networkx, anytree, scipy (and pyarrow on Python 3.11+), then verifies Mixpanel credentials. It should be invoked when setting up a new environment for Mixpanel data analysis, when dependencies are missing, or when configuring service account or OAuth credentials for the first time.
|
||||
disable-model-invocation: false
|
||||
allowed-tools: Bash
|
||||
---
|
||||
|
||||
Walk the user through verifying their Daloopa setup for Codex or ChatGPT. Be conversational and helpful.
|
||||
# mixpanel-headless — Setup
|
||||
|
||||
## Step 1: Verify Runtime
|
||||
Confirm the user is working in Codex, ChatGPT, or another OpenAI environment that can use skills. Explain that this skill checks whether the Daloopa MCP tools are available.
|
||||
Install dependencies and verify credentials for CodeMode analytics.
|
||||
|
||||
## Step 2: Verify MCP Connection
|
||||
This plugin connects to two Daloopa MCP servers:
|
||||
- **daloopa** (`mcp.daloopa.com/server/mcp`) - Financial data (fundamentals, KPIs, SEC filings)
|
||||
- **daloopa-docs** (`docs.daloopa.com/mcp`) - Daloopa knowledgebase (API docs, how-tos, usage help)
|
||||
## Run Setup
|
||||
|
||||
Run a quick test by calling `discover_companies` with a well-known ticker like "AAPL" to confirm the data MCP server is connected and responding. Show the user the result.
|
||||
Before running bundled scripts, set `SKILL_DIR` to the absolute path of this
|
||||
`skills/setup` directory.
|
||||
|
||||
If this fails:
|
||||
- Check that `.mcp.json` is present and configured for the Daloopa MCP servers.
|
||||
- In Codex, reinstall or reload the plugin after changing MCP configuration.
|
||||
- In ChatGPT, verify that the Daloopa MCP connector or equivalent tool access is enabled.
|
||||
- If the server returns `401` or `Reauthentication required`, restart the Daloopa OAuth/login flow in the current environment.
|
||||
- On first use, OAuth may open a browser window for Daloopa login.
|
||||
```bash
|
||||
bash $SKILL_DIR/scripts/setup.sh
|
||||
```
|
||||
|
||||
## Step 3: Quick Tour
|
||||
Tell the user about the available analysis skills. Use natural-language examples such as:
|
||||
- "Create a tearsheet for AAPL."
|
||||
- "Review MSFT earnings and guidance."
|
||||
- "Build a DCF valuation for NVDA."
|
||||
- "Create an industry comp sheet for AAPL and peers."
|
||||
This will:
|
||||
1. Verify Python 3.10+ is available
|
||||
2. Install `mixpanel_headless`, `pandas`, `numpy`, `matplotlib`, `seaborn`, `networkx>=3.0`, `anytree>=2.8.0`, `scipy`, and `pyarrow>=17.0` on Python 3.11+ (tries uv, pip in order)
|
||||
3. Verify all packages import successfully (including pyarrow on 3.11+, networkx, anytree, and scipy)
|
||||
4. Check for configured Mixpanel credentials (single schema — Account → Project → Workspace)
|
||||
|
||||
Each reporting skill saves generated files to the `reports/` directory when file access is available.
|
||||
## Check Credentials
|
||||
|
||||
## Step 4: Note on Enhanced Features
|
||||
For file-heavy workflows such as Word research notes, Excel models, and pitch decks, use the local document, spreadsheet, or presentation generation workflow available in the current OpenAI environment. If a file cannot be generated in the current environment, provide the complete structured content and explain the limitation.
|
||||
After installation, check the active session:
|
||||
|
||||
```bash
|
||||
python3 $SKILL_DIR/../mixpanelyst/scripts/auth_manager.py session
|
||||
```
|
||||
|
||||
Parse the JSON `state` field:
|
||||
- **`ok`** — credentials configured. Show `account.name` → project `project.id` and proceed to verification.
|
||||
- **`needs_account`** — no account configured. Read `next` for onboarding suggestions and follow "If Credentials Are Missing" below.
|
||||
- **`needs_project`** — account configured but no project pinned. Suggest `mp project list` then `mp project use <id>`.
|
||||
- **`error`** — show `error.message`. If `error.actionable` is true, the message names a concrete next command.
|
||||
|
||||
## If Credentials Are Missing
|
||||
|
||||
If no credentials are configured, guide the user to one of these methods:
|
||||
|
||||
### Recommended: `mp login`
|
||||
|
||||
The frictionless one-shot path. Tell the user to run:
|
||||
|
||||
```bash
|
||||
mp login
|
||||
```
|
||||
|
||||
`mp login` runs the right auth flow for the environment, derives the
|
||||
account name from `/me`, and pins a default project. For laptops with a
|
||||
usable browser, this opens the PKCE flow; for environments with
|
||||
`MP_USERNAME` + `MP_SECRET` set, it skips the browser and uses the
|
||||
service-account path; for `MP_OAUTH_TOKEN` set, it uses the static
|
||||
bearer.
|
||||
|
||||
Region behavior:
|
||||
- `service_account` and `oauth_token` paths probe `us → eu → in` when
|
||||
`--region` is omitted.
|
||||
- `oauth_browser` (the bare-`mp login` default) defaults to `us`. EU and
|
||||
India browser users must pass `--region eu` or `--region in`.
|
||||
|
||||
Useful flags: `--name NAME`, `--region us|eu|in`, `--project ID`,
|
||||
`--service-account`, `--token-env VAR`, `--no-browser`, `--secret-stdin`.
|
||||
|
||||
### Alternative: Guided Setup (explicit account add)
|
||||
|
||||
Use the `mixpanel-auth` skill's account-add workflow for a
|
||||
step-by-step walkthrough. The workflow never prompts for secrets in
|
||||
conversation — it instructs the user to run `mp account add ...`
|
||||
themselves so the secret is read with hidden input. Use this path when
|
||||
the user wants explicit control over the account name, region, and type
|
||||
at registration time.
|
||||
|
||||
### Alternative: Service-Account Environment Variables (temporary)
|
||||
|
||||
For quick testing, set all four variables in the shell — the resolver
|
||||
picks them up directly without account registration:
|
||||
|
||||
```bash
|
||||
export MP_USERNAME="service-account-username"
|
||||
export MP_SECRET="service-account-secret"
|
||||
export MP_PROJECT_ID="12345"
|
||||
export MP_REGION="us" # or "eu", "in"
|
||||
```
|
||||
|
||||
### Alternative: Raw OAuth Bearer Token (best for agents / CI)
|
||||
|
||||
If the user has an OAuth 2.0 access token from another source, they can use
|
||||
it directly without the PKCE browser flow:
|
||||
|
||||
```bash
|
||||
export MP_OAUTH_TOKEN="<bearer-token>"
|
||||
export MP_PROJECT_ID="12345"
|
||||
export MP_REGION="us" # or "eu", "in"
|
||||
```
|
||||
|
||||
This is the recommended mode for non-interactive contexts. The full
|
||||
service-account env-var set (`MP_USERNAME` + `MP_SECRET` + `MP_PROJECT_ID`
|
||||
+ `MP_REGION`) takes precedence when both sets are complete.
|
||||
|
||||
## Remote Environment
|
||||
|
||||
If running inside a remote or sandboxed agent environment, credentials work differently:
|
||||
|
||||
- **OAuth login and interactive account setup are NOT available** (no browser, no host terminal access)
|
||||
- Credentials must be configured on the **host machine** before starting the remote session
|
||||
|
||||
### If No Credentials Found in the Remote Session
|
||||
|
||||
Tell the user:
|
||||
|
||||
> No Mixpanel credentials found in this remote session.
|
||||
>
|
||||
> On your **host machine** (outside the remote session), run:
|
||||
> ```
|
||||
> mp account export-bridge --to ~/.claude/mixpanel/auth.json
|
||||
> ```
|
||||
> This writes a v2 bridge file embedding your account record (and any
|
||||
> oauth_browser tokens) so the remote session can read your credentials
|
||||
> at startup.
|
||||
>
|
||||
> Then **start a new remote session** — credentials will be available automatically.
|
||||
|
||||
Do NOT suggest the account-login or account-add interactive workflows — these won't work inside remote sessions without browser or terminal access.
|
||||
|
||||
### If Bridge File Found But Token Expired
|
||||
|
||||
The library will auto-refresh the OAuth token via the on-disk refresh
|
||||
token (no browser needed). If refresh fails:
|
||||
|
||||
> Your OAuth session has expired and could not be refreshed.
|
||||
> On your host machine, run:
|
||||
> ```
|
||||
> mp login --name personal # re-authenticate (or `mp account login personal`)
|
||||
> mp account export-bridge --to ~/.claude/mixpanel/auth.json
|
||||
> ```
|
||||
> Then start a new remote session.
|
||||
|
||||
## Verify Everything Works
|
||||
|
||||
```bash
|
||||
python3 $SKILL_DIR/../mixpanelyst/scripts/auth_manager.py account test
|
||||
```
|
||||
|
||||
The subcommand never raises — read `result.ok` to determine outcome.
|
||||
- `result.ok: true` → setup is complete; the user can ask analytics questions.
|
||||
- `result.ok: false` → suggest the `mixpanel-auth` account-test workflow for detailed diagnostics.
|
||||
|
||||
## Post-Setup: Explore Your Data
|
||||
|
||||
Once authenticated, these slash commands help orient the user:
|
||||
|
||||
- `mixpanel-auth` project-list workflow — discover all accessible projects via `/me`
|
||||
- `mixpanel-auth` session workflow — see active account / project / workspace
|
||||
- `mixpanel-auth` project-use workflow — switch to a different project
|
||||
- `mixpanel-auth` target-add workflow — save a named cursor position
|
||||
|
||||
The user can also construct a Workspace targeting a specific account / project /
|
||||
workspace directly:
|
||||
|
||||
```python
|
||||
import mixpanel_headless as mp
|
||||
|
||||
ws = mp.Workspace() # default session
|
||||
ws = mp.Workspace(account="team") # named account
|
||||
ws = mp.Workspace(project="67890") # explicit project (active account)
|
||||
ws = mp.Workspace(account="team", project="67890") # both axes
|
||||
ws.use(project="98765").events() # in-session switch (no re-auth)
|
||||
```
|
||||
|
||||
_The mixpanelyst skill auto-triggers on analytics questions. For the analytical frameworks that guide investigations, see [analytical-frameworks.md](../mixpanelyst/references/analytical-frameworks.md). For the complete Python API, see [python-api.md](../mixpanelyst/references/python-api.md)._
|
||||
@@ -1,6 +0,0 @@
|
||||
interface:
|
||||
display_name: Setup
|
||||
short_description: Verify Daloopa MCP connection and show available skills
|
||||
default_prompt: Verify my Daloopa MCP connection.
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
Executable
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install mixpanel_headless and pandas for CodeMode analytics
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== mixpanel-headless — CodeMode Setup ==="
|
||||
echo ""
|
||||
|
||||
# Find Python 3.10+
|
||||
python_cmd=""
|
||||
for cmd in python3 python; do
|
||||
if command -v "$cmd" &>/dev/null; then
|
||||
major=$("$cmd" -c "import sys; print(sys.version_info.major)" 2>/dev/null || echo 0)
|
||||
minor=$("$cmd" -c "import sys; print(sys.version_info.minor)" 2>/dev/null || echo 0)
|
||||
if [ "$major" -gt 3 ] || ([ "$major" -eq 3 ] && [ "$minor" -ge 10 ]); then
|
||||
version=$("$cmd" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')")
|
||||
python_cmd="$cmd"
|
||||
echo "✓ Python $version ($cmd)"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$python_cmd" ]; then
|
||||
echo "✗ Python 3.10+ required but not found."
|
||||
echo " Install from https://python.org or via your package manager."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install packages
|
||||
MIXPANEL_HEADLESS_PKG="mixpanel-headless"
|
||||
DEPS=(pandas numpy matplotlib seaborn 'networkx>=3.0' 'anytree>=2.8.0' scipy)
|
||||
|
||||
# pyarrow is only needed on Python 3.11+ (for pandas 3.x Arrow-backed dtypes)
|
||||
if [ "$minor" -ge 11 ]; then
|
||||
DEPS+=('pyarrow>=17.0')
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Installing mixpanel-headless (import name: mixpanel_headless) and dependencies..."
|
||||
if command -v uv &>/dev/null; then
|
||||
echo " (using uv)"
|
||||
uv pip install --python "$python_cmd" "$MIXPANEL_HEADLESS_PKG" "${DEPS[@]}" || { echo " ⚠ Virtualenv install failed, trying system install..."; uv pip install --system --python "$python_cmd" "$MIXPANEL_HEADLESS_PKG" "${DEPS[@]}"; }
|
||||
elif "$python_cmd" -m pip --version &>/dev/null; then
|
||||
echo " (using pip via $python_cmd)"
|
||||
"$python_cmd" -m pip install "$MIXPANEL_HEADLESS_PKG" "${DEPS[@]}"
|
||||
else
|
||||
echo "✗ No package manager found. Install pip or uv."
|
||||
echo " Recommended: https://docs.astral.sh/uv/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify imports
|
||||
echo ""
|
||||
echo "Verifying installation..."
|
||||
"$python_cmd" -c "
|
||||
import sys
|
||||
import mixpanel_headless as mp
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
import seaborn as sns
|
||||
import networkx as nx
|
||||
import anytree
|
||||
import scipy
|
||||
print(f'✓ mixpanel_headless installed')
|
||||
print(f'✓ pandas {pd.__version__}')
|
||||
if sys.version_info >= (3, 11):
|
||||
import pyarrow as pa
|
||||
print(f'✓ pyarrow {pa.__version__}')
|
||||
print(f'✓ numpy {np.__version__}')
|
||||
print(f'✓ matplotlib {matplotlib.__version__}')
|
||||
print(f'✓ seaborn {sns.__version__}')
|
||||
print(f'✓ networkx {nx.__version__}')
|
||||
print(f'✓ anytree {anytree.__version__}')
|
||||
print(f'✓ scipy {scipy.__version__}')
|
||||
" || { echo "✗ Import verification failed"; exit 1; }
|
||||
|
||||
# Check credentials
|
||||
echo ""
|
||||
echo "Checking Mixpanel credentials..."
|
||||
"$python_cmd" -c "
|
||||
import os, sys
|
||||
|
||||
# 1) Service-account env quad
|
||||
sa_quad = ['MP_USERNAME', 'MP_SECRET', 'MP_PROJECT_ID', 'MP_REGION']
|
||||
sa_set = [v for v in sa_quad if os.environ.get(v)]
|
||||
if len(sa_set) == len(sa_quad):
|
||||
print('✓ Service-account env quad is fully set (MP_USERNAME + MP_SECRET + MP_PROJECT_ID + MP_REGION)')
|
||||
sys.exit(0)
|
||||
elif sa_set:
|
||||
missing = [v for v in sa_quad if not os.environ.get(v)]
|
||||
print(f'⚠ Partial service-account env config — missing: {\", \".join(missing)}')
|
||||
|
||||
# 2) OAuth-token env triple
|
||||
oauth_triple = ['MP_OAUTH_TOKEN', 'MP_PROJECT_ID', 'MP_REGION']
|
||||
oauth_set = [v for v in oauth_triple if os.environ.get(v)]
|
||||
if len(oauth_set) == len(oauth_triple):
|
||||
print('✓ OAuth-token env triple is fully set (MP_OAUTH_TOKEN + MP_PROJECT_ID + MP_REGION)')
|
||||
sys.exit(0)
|
||||
|
||||
# 3) Persisted accounts in ~/.mp/config.toml
|
||||
try:
|
||||
import mixpanel_headless as mp
|
||||
accounts = mp.accounts.list()
|
||||
if accounts:
|
||||
active = next((a for a in accounts if a.is_active), None)
|
||||
names = ', '.join(a.name for a in accounts)
|
||||
print(f'✓ {len(accounts)} account(s) in ~/.mp/config.toml: {names}')
|
||||
if active:
|
||||
print(f' Active: {active.name} ({active.type}, {active.region})')
|
||||
else:
|
||||
print('⚠ No active account selected. Run: mp account use <name>')
|
||||
else:
|
||||
print('⚠ No accounts configured yet.')
|
||||
print(' Recommended: mp login # one-shot frictionless login')
|
||||
print(' Service account: mp account add team --type service_account --username sa_xxx --project 12345 --region us')
|
||||
except Exception as e:
|
||||
print(f'⚠ Could not read ~/.mp/config.toml: {e}')
|
||||
print(' Run `mp login`, set env vars (service-account quad or OAuth triple), or run mp account add ...')
|
||||
"
|
||||
|
||||
# Remote-session detection: check for bridge file
|
||||
if [ -d "/sessions" ] || [ -n "${CODEX_CLOUD:-}" ]; then
|
||||
echo ""
|
||||
echo "Remote environment detected."
|
||||
BRIDGE_FOUND=""
|
||||
for f in "$HOME/.claude/mixpanel/auth.json"; do
|
||||
if [ -f "$f" ]; then
|
||||
echo "✓ Auth bridge file found: $f"
|
||||
"$python_cmd" -c "
|
||||
import json, sys
|
||||
try:
|
||||
with open(sys.argv[1]) as fh:
|
||||
bridge = json.load(fh)
|
||||
if bridge.get('version') != 2:
|
||||
print(f' ⚠ Unexpected bridge version: {bridge.get(\"version\")} (expected 2)')
|
||||
account = bridge.get('account', {})
|
||||
print(f' Account: {account.get(\"name\", \"?\")} ({account.get(\"type\", \"?\")}, {account.get(\"region\", \"?\")})')
|
||||
project = bridge.get('project') or account.get('default_project')
|
||||
if project:
|
||||
print(f' Project: {project}')
|
||||
if bridge.get('workspace'):
|
||||
print(f' Workspace: {bridge[\"workspace\"]}')
|
||||
headers = bridge.get('headers') or {}
|
||||
if headers:
|
||||
print(f' Custom headers: {len(headers)} entr{\"y\" if len(headers) == 1 else \"ies\"} ✓')
|
||||
tokens = bridge.get('tokens')
|
||||
if tokens and tokens.get('expires_at'):
|
||||
print(f' Token expires: {tokens[\"expires_at\"]}')
|
||||
except Exception as e:
|
||||
print(f' Error reading bridge file: {e}')
|
||||
" "$f"
|
||||
BRIDGE_FOUND=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$BRIDGE_FOUND" ]; then
|
||||
echo "⚠ No auth bridge file found."
|
||||
echo " On your HOST machine, run:"
|
||||
echo " mp account export-bridge --to ~/.claude/mixpanel/auth.json"
|
||||
echo " Then start a new remote session."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Setup complete ==="
|
||||
Reference in new issue
Block a user