flowai-cli 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowai_cli-0.3.0/PKG-INFO +302 -0
- flowai_cli-0.3.0/README.md +247 -0
- flowai_cli-0.3.0/SECURITY.md +110 -0
- flowai_cli-0.2.0/examples/02_nvidia_deepseek.py → flowai_cli-0.3.0/examples/02_nvidia.py +13 -6
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/examples/04_fastmcp_agent.py +1 -1
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/__init__.py +1 -1
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/agents/standard.py +9 -1
- flowai_cli-0.3.0/flowai/cli/main.py +918 -0
- flowai_cli-0.3.0/flowai/cli/theme.py +135 -0
- flowai_cli-0.3.0/flowai/cli/tui.py +666 -0
- flowai_cli-0.3.0/flowai/cli/ui.py +216 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/config/settings.py +92 -31
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/mcp/client.py +7 -4
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/providers/anthropic.py +2 -1
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/providers/base.py +18 -1
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/providers/gemini.py +13 -7
- flowai_cli-0.3.0/flowai/providers/nvidia.py +161 -0
- flowai_cli-0.3.0/flowai/providers/openai.py +486 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/runtime/session.py +12 -4
- flowai_cli-0.3.0/flowai/security/__init__.py +43 -0
- flowai_cli-0.3.0/flowai/security/keystore.py +238 -0
- flowai_cli-0.3.0/flowai/security/policy.py +264 -0
- flowai_cli-0.3.0/flowai/security/secrets.py +160 -0
- flowai_cli-0.3.0/flowai/security/shield.py +106 -0
- flowai_cli-0.3.0/flowai/server/app.py +171 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/tools/base.py +2 -0
- flowai_cli-0.3.0/flowai/tools/builtin.py +172 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/tools/files.py +7 -1
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/tools/registry.py +48 -10
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/tools/web.py +45 -10
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai.yaml +1 -1
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/pyproject.toml +5 -2
- flowai_cli-0.3.0/tests/conftest.py +9 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_builtin_tools.py +23 -4
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_cli.py +62 -9
- flowai_cli-0.3.0/tests/test_nvidia.py +261 -0
- flowai_cli-0.3.0/tests/test_security.py +261 -0
- flowai_cli-0.3.0/tests/test_server.py +59 -0
- flowai_cli-0.3.0/tests/test_tui.py +176 -0
- flowai_cli-0.2.0/PKG-INFO +0 -223
- flowai_cli-0.2.0/README.md +0 -171
- flowai_cli-0.2.0/flowai/cli/main.py +0 -562
- flowai_cli-0.2.0/flowai/cli/ui.py +0 -98
- flowai_cli-0.2.0/flowai/providers/nvidia.py +0 -57
- flowai_cli-0.2.0/flowai/providers/openai.py +0 -274
- flowai_cli-0.2.0/flowai/server/app.py +0 -133
- flowai_cli-0.2.0/flowai/tools/builtin.py +0 -128
- flowai_cli-0.2.0/tests/test_server.py +0 -38
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/.gitignore +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/LICENSE +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/examples/01_quickstart.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/examples/03_workflow_graph.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/examples/05_debate_and_swarm.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/__main__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/agents/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/agents/base.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/agents/patterns/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/agents/patterns/debate.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/agents/patterns/reflection.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/agents/specialized.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/cli/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/config/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/errors.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/mcp/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/memory/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/memory/base.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/memory/chroma.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/memory/in_memory.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/memory/qdrant.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/observability/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/observability/events.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/observability/opentelemetry.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/observability/tracer.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/plugins/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/plugins/loader.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/providers/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/providers/ollama.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/providers/registry.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/runtime/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/server/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/tools/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/tools/decorator.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/workflow/__init__.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/workflow/checkpoint.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/workflow/engine.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/flowai/workflow/state.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_agents.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_memory.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_observability.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_session.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_streaming.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_tools.py +0 -0
- {flowai_cli-0.2.0 → flowai_cli-0.3.0}/tests/test_workflow.py +0 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: flowai-cli
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: FlowAI: an open-source, extensible Python agent harness with a terminal-first CLI.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Ajayramaswamy2005/FlowAI
|
|
6
|
+
Project-URL: Source, https://github.com/Ajayramaswamy2005/FlowAI
|
|
7
|
+
Project-URL: Issues, https://github.com/Ajayramaswamy2005/FlowAI/issues
|
|
8
|
+
Author: Ajay Rama Swamy Katragaada
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,cli,fastapi,llm,mcp,nvidia,ollama,pydantic,workflow
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
Requires-Dist: keyring>=24.0
|
|
24
|
+
Requires-Dist: prompt-toolkit>=3.0.43
|
|
25
|
+
Requires-Dist: pydantic>=2.7.0
|
|
26
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
27
|
+
Requires-Dist: rich>=13.7
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: chromadb>=0.5.0; extra == 'all'
|
|
30
|
+
Requires-Dist: fastapi>=0.111.0; extra == 'all'
|
|
31
|
+
Requires-Dist: mcp>=1.0.0; extra == 'all'
|
|
32
|
+
Requires-Dist: opentelemetry-api>=1.25.0; extra == 'all'
|
|
33
|
+
Requires-Dist: opentelemetry-sdk>=1.25.0; extra == 'all'
|
|
34
|
+
Requires-Dist: qdrant-client>=1.9.0; extra == 'all'
|
|
35
|
+
Requires-Dist: uvicorn[standard]>=0.30.0; extra == 'all'
|
|
36
|
+
Provides-Extra: chroma
|
|
37
|
+
Requires-Dist: chromadb>=0.5.0; extra == 'chroma'
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: fastapi>=0.111.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: httpx>=0.27.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
44
|
+
Provides-Extra: mcp
|
|
45
|
+
Requires-Dist: mcp>=1.0.0; extra == 'mcp'
|
|
46
|
+
Provides-Extra: observability
|
|
47
|
+
Requires-Dist: opentelemetry-api>=1.25.0; extra == 'observability'
|
|
48
|
+
Requires-Dist: opentelemetry-sdk>=1.25.0; extra == 'observability'
|
|
49
|
+
Provides-Extra: qdrant
|
|
50
|
+
Requires-Dist: qdrant-client>=1.9.0; extra == 'qdrant'
|
|
51
|
+
Provides-Extra: server
|
|
52
|
+
Requires-Dist: fastapi>=0.111.0; extra == 'server'
|
|
53
|
+
Requires-Dist: uvicorn[standard]>=0.30.0; extra == 'server'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
# FlowAI
|
|
57
|
+
|
|
58
|
+
FlowAI is an AI agent you run in your terminal, in the same spirit as Claude
|
|
59
|
+
Code or OpenCode. Bring your own key from Anthropic, OpenAI, NVIDIA, Google
|
|
60
|
+
Gemini or OpenRouter, or run fully local with Ollama or your own NVIDIA NIM.
|
|
61
|
+
The agent can read, search and edit the files in your project, run shell
|
|
62
|
+
commands (after asking you), and search the web, while a security layer keeps
|
|
63
|
+
your API keys out of its reach.
|
|
64
|
+
|
|
65
|
+
It is also a Python library: the same agents, tools, workflows and memory the
|
|
66
|
+
CLI uses are yours to build on.
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install flowai-cli
|
|
72
|
+
pipx install flowai-cli # same, in its own isolated environment
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Python 3.10 or newer. The package is `flowai-cli` (the name `flowai` is taken on PyPI);
|
|
76
|
+
the command it installs is `flowai`, and the import is `import flowai`.
|
|
77
|
+
|
|
78
|
+
## First run
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
flowai
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The first time, FlowAI asks for a provider and an API key, stores the key in
|
|
85
|
+
your operating system's credential store, and sends one tiny request to check
|
|
86
|
+
it works. After that, `flowai` opens a session in the current directory.
|
|
87
|
+
|
|
88
|
+
## The interface
|
|
89
|
+
|
|
90
|
+
A FlowAI session is drawn as a current: every turn flows down one rail.
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
█▀▀ █ █▀█ █ █ █ ▄▀█ █ v0.3.0
|
|
94
|
+
█▀ █▄▄ █▄█ ▀▄▀▄▀ █▀█ █ an agent that moves with your code
|
|
95
|
+
▅▇█▇▅▂▁▁▃▆▇█▇▅▂▁▁▃▆▇█▇▅▂▁▁▃▆██▇▅▂▁▁▃▆██▇▄▂▁▁▃▆
|
|
96
|
+
|
|
97
|
+
model nvidia › nvidia/nemotron-3.5-lightning-30b-a3b
|
|
98
|
+
workspace C:\Users\you\project
|
|
99
|
+
shield armed · key in Windows Credential Manager · approvals ask
|
|
100
|
+
|
|
101
|
+
◆ fix the failing test in tests/test_api.py
|
|
102
|
+
≋ undertow thought for 2.1s · 48 words /think to read it
|
|
103
|
+
◇ read_file tests/test_api.py 0.0s ✓ 120 lines
|
|
104
|
+
◇ bash pytest -q tests/test_api.py 1.9s ✗ exit 1
|
|
105
|
+
|
|
106
|
+
┃ GATE edit_file wants to run risk LOW
|
|
107
|
+
┃ src/api.py
|
|
108
|
+
┃ - return a - b
|
|
109
|
+
┃ + return a + b
|
|
110
|
+
┃ [y] allow once (default) [a] always allow edit_file [n] deny
|
|
111
|
+
● flowai
|
|
112
|
+
│ The bug was a sign error in add(). All 12 tests pass now.
|
|
113
|
+
╰─ 6.3s · 3 tools · 4.1k tok tide ▂ 18%
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- **◆** is your prompt. **≋ undertow** is the model's reasoning, folded to one
|
|
117
|
+
line (`/think` reads it in full, `/think on` shows it live).
|
|
118
|
+
- **◇** is a tool call, with its timing and outcome.
|
|
119
|
+
- The **gate** asks before anything risky runs. Its rail is coloured by risk,
|
|
120
|
+
it shows commands with syntax highlighting and edits as a diff, and it says
|
|
121
|
+
why a call is risky.
|
|
122
|
+
- The answer is rendered markdown. While the model works, a swell of block
|
|
123
|
+
characters moves along the rail.
|
|
124
|
+
- The **tide** gauge shows how full the model's context window is. The strip at
|
|
125
|
+
the bottom of the prompt shows the model, tide, tokens spent and shield state.
|
|
126
|
+
- `/` completes commands. Esc then Enter adds a new line. Prompt history is
|
|
127
|
+
kept, with any pasted key masked.
|
|
128
|
+
|
|
129
|
+
Piped output, CI logs and `--plain` get plain text instead. `NO_COLOR` and
|
|
130
|
+
`FLOWAI_NO_ANIM` are respected.
|
|
131
|
+
|
|
132
|
+
## NVIDIA
|
|
133
|
+
|
|
134
|
+
FlowAI works with every kind of NVIDIA key and endpoint:
|
|
135
|
+
|
|
136
|
+
| You have | Do this |
|
|
137
|
+
|---|---|
|
|
138
|
+
| A key from build.nvidia.com (`nvapi-...`) | `flowai configure`, pick NVIDIA, paste it |
|
|
139
|
+
| The key in an environment variable | Any of `NVIDIA_API_KEY`, `NVAPI_KEY`, `NGC_API_KEY`, `NIM_API_KEY` works |
|
|
140
|
+
| A NIM running on your own machine or cluster | `NVIDIA_BASE_URL=http://localhost:8000/v1` (or `flowai configure --provider nvidia --base-url ...`); no key needed |
|
|
141
|
+
|
|
142
|
+
The catalog's models do not all accept the same requests, and FlowAI adapts to
|
|
143
|
+
each one:
|
|
144
|
+
|
|
145
|
+
- **No tool support.** A model that refuses `tools` is sent the conversation
|
|
146
|
+
without them, and you are told so. The same goes for `stream_options`, the
|
|
147
|
+
`system` role and `max_tokens`. Any chat model on the catalog can be used.
|
|
148
|
+
- **Reasoning models** (Nemotron, DeepSeek, GPT-OSS) stream their thinking
|
|
149
|
+
separately, whether it arrives as `reasoning_content` or inside `<think>` tags.
|
|
150
|
+
- **Model names.** A bare name such as `glm-5.3-flash` is resolved to its
|
|
151
|
+
catalog id (`z-ai/glm-5.3-flash`).
|
|
152
|
+
- **Unavailable models.** Some models are listed but not available to every
|
|
153
|
+
account, and retired ones answer 410. FlowAI then suggests models your key
|
|
154
|
+
can call, and `flowai configure` finds a working one for you.
|
|
155
|
+
- **Rate limits.** The free tier's limits are retried with backoff,
|
|
156
|
+
honouring `Retry-After`.
|
|
157
|
+
- **Key mistakes** are named: a key without the `nvapi-` prefix (an old NGC
|
|
158
|
+
key cannot call the hosted API), or stray whitespace around it.
|
|
159
|
+
|
|
160
|
+
`flowai models nvidia [filter]` lists the catalog. In a session, `/models` does
|
|
161
|
+
the same, and `/model <id>` switches without losing the conversation.
|
|
162
|
+
|
|
163
|
+
## Security
|
|
164
|
+
|
|
165
|
+
An agent that runs commands on your machine is an obvious target. Someone
|
|
166
|
+
would like it to read your API key and send it somewhere, usually by hiding
|
|
167
|
+
instructions in a file or web page the agent reads. FlowAI defends in layers:
|
|
168
|
+
|
|
169
|
+
| Layer | What it does |
|
|
170
|
+
|---|---|
|
|
171
|
+
| Key vault | Keys live in Windows Credential Manager, the macOS Keychain or the Secret Service. Without one, they go in a file only your user can read. Keys are never copied into the environment |
|
|
172
|
+
| Scrubbed children | `bash`, `python_repl` and MCP servers start with every credential-looking variable removed. Python runs in a separate process, so it cannot reach into FlowAI's memory |
|
|
173
|
+
| Redaction | Every key FlowAI knows, plus the shapes of common keys (`nvapi-`, `sk-`, `sk-ant-`, `AIza`, `ghp_`...), is masked in tool output before the model sees it. That includes base64, hex, reversed and URL-encoded forms. Transcripts, prompt history and errors are masked too |
|
|
174
|
+
| Tripwires | Dumping the environment, reading `.env`, SSH keys, the credential store or FlowAI's own config always asks you, even with `--yolo`. With no terminal attached, the answer is no |
|
|
175
|
+
| Exfiltration block | A call that reads secrets and sends data over the network in one go is refused outright, as is any tool call whose arguments contain a key |
|
|
176
|
+
| Network guard | `fetch_url` refuses localhost, private networks and cloud metadata addresses, checking every redirect |
|
|
177
|
+
| Injection fence | Web and MCP output is wrapped as untrusted data, and the system prompt tells the model to treat it that way |
|
|
178
|
+
| Terminal hygiene | Escape sequences are stripped from everything drawn, so a page cannot rewrite your clipboard or hide text |
|
|
179
|
+
| Server | `flowai serve` requires a bearer token, refuses non-local `Host` headers (DNS rebinding), and runs no shell tools unless started with `--allow-shell` |
|
|
180
|
+
|
|
181
|
+
`flowai shield` audits your setup and says what to fix. `flowai shield --fix`
|
|
182
|
+
moves plain-text keys into the keyring and locks the config directory, and
|
|
183
|
+
`flowai logout` deletes a saved key. These are tripwires, not a sandbox: read
|
|
184
|
+
[SECURITY.md](SECURITY.md) for what they do not cover.
|
|
185
|
+
|
|
186
|
+
## Commands
|
|
187
|
+
|
|
188
|
+
| Command | What it does |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `flowai` | Interactive session in the current directory |
|
|
191
|
+
| `flowai "add type hints to utils.py"` | Run one task and exit |
|
|
192
|
+
| `flowai chat --resume` (or `-c`) | Continue the most recent session |
|
|
193
|
+
| `flowai run -q "..."` | One task, print only the final answer (good for scripts) |
|
|
194
|
+
| `echo "..." \| flowai run` | Read the prompt from stdin |
|
|
195
|
+
| `flowai configure` | Choose provider, model and API key |
|
|
196
|
+
| `flowai models [provider] [filter]` | List the models a provider serves |
|
|
197
|
+
| `flowai shield [--fix]` | Audit how your keys are protected |
|
|
198
|
+
| `flowai logout [provider]` | Delete a saved key |
|
|
199
|
+
| `flowai init [dir]` | Create a `flowai.yaml` and a starter `main.py` |
|
|
200
|
+
| `flowai inspect` | Show config status, saved sessions, checkpoints |
|
|
201
|
+
| `flowai tools` | List the built-in tools |
|
|
202
|
+
| `flowai serve` | HTTP API with streaming (`pip install 'flowai-cli[server]'`) |
|
|
203
|
+
|
|
204
|
+
Options for `chat` and `run`: `--provider`, `--model`, `-w/--workspace DIR`,
|
|
205
|
+
`--yolo` (do not ask before shell/python run; secret access still asks),
|
|
206
|
+
`--no-tools`, `--max-iterations N`, `--plain`.
|
|
207
|
+
|
|
208
|
+
Inside a session: `/help`, `/model <id>`, `/models [filter]`,
|
|
209
|
+
`/provider <name> [model]`, `/tools`, `/think [on|off]`, `/cost`, `/shield`,
|
|
210
|
+
`/new`, `/reset`, `/clear`, `/yolo`, `/exit`.
|
|
211
|
+
|
|
212
|
+
## What the agent can do
|
|
213
|
+
|
|
214
|
+
| Tool | Notes |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `read_file`, `list_dir`, `glob_files`, `grep_files` | Read-only; never ask, except for files that hold secrets |
|
|
217
|
+
| `write_file`, `edit_file` | Change files inside the workspace only |
|
|
218
|
+
| `bash` | Asks first. Runs in the workspace, 120 s timeout |
|
|
219
|
+
| `python_repl` | Asks first. Runs in a separate interpreter, 60 s timeout |
|
|
220
|
+
| `search_web`, `fetch_url` | DuckDuckGo search and public page fetch |
|
|
221
|
+
|
|
222
|
+
Every file path is resolved against the workspace (the directory you started
|
|
223
|
+
in, or `-w`) and refused if it points outside.
|
|
224
|
+
|
|
225
|
+
## Configuration
|
|
226
|
+
|
|
227
|
+
Settings are layered, later wins:
|
|
228
|
+
|
|
229
|
+
1. `~/.flowai/config.json`, written by `flowai configure` (keys themselves go
|
|
230
|
+
to the OS keyring)
|
|
231
|
+
2. `flowai.yaml` (or `.yml`, `.toml`, `.json`) in the current directory or any
|
|
232
|
+
parent, so it works from a subdirectory of your project
|
|
233
|
+
3. Environment: `FLOWAI_PROVIDER`, `FLOWAI_MODEL`, and the provider key variables
|
|
234
|
+
|
|
235
|
+
See [flowai.yaml](flowai.yaml) for every option, including `mcp_servers` for
|
|
236
|
+
borrowing tools from Model Context Protocol servers (`pip install 'flowai-cli[mcp]'`).
|
|
237
|
+
Never put a key in `flowai.yaml`; `flowai shield` flags it if you do.
|
|
238
|
+
|
|
239
|
+
`FLOWAI_HOME` moves `~/.flowai`. `FLOWAI_PASS_ENV=GITHUB_TOKEN,...` hands
|
|
240
|
+
named variables to the agent's shell on purpose. `FLOWAI_KEYRING=0` turns
|
|
241
|
+
the keyring off.
|
|
242
|
+
|
|
243
|
+
## Using it as a library
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
import asyncio
|
|
247
|
+
from flowai import Agent, tool
|
|
248
|
+
|
|
249
|
+
@tool
|
|
250
|
+
def compound_interest(principal: float, rate: float, years: int) -> float:
|
|
251
|
+
"""Balance after compounding yearly."""
|
|
252
|
+
return round(principal * (1 + rate) ** years, 2)
|
|
253
|
+
|
|
254
|
+
agent = Agent(
|
|
255
|
+
name="Advisor",
|
|
256
|
+
provider="nvidia", # anthropic, ollama, openai, gemini, openrouter
|
|
257
|
+
model="nvidia/nemotron-3.5-lightning-30b-a3b",
|
|
258
|
+
tools=[compound_interest],
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
print(asyncio.run(agent.run("What will $5,000 be after 10 years at 8%?")))
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`agent.stream(prompt)` yields tokens as they arrive (and `reasoning_delta` for
|
|
265
|
+
reasoning models) and runs tool calls along the way. Tools you register in
|
|
266
|
+
your own code run without an approval prompt (you wrote them). To change that,
|
|
267
|
+
give a `ToolRegistry` an `approver` callback; to add the CLI's protections,
|
|
268
|
+
give it a `shield=Shield()` (`from flowai.security import Shield`).
|
|
269
|
+
|
|
270
|
+
Beyond a single agent the library has:
|
|
271
|
+
|
|
272
|
+
- **Specialised agents**: `PlannerAgent`, `CriticAgent`, `ToolAgent`, `SupervisorAgent`
|
|
273
|
+
- **Patterns**: `ReflectionPattern`, `PlanAndExecutePattern`, `DebatePattern`, `Swarm`
|
|
274
|
+
- **Workflows**: a graph engine with conditional edges, loops, parallel
|
|
275
|
+
branches and checkpointing (`Workflow`, `FileCheckpointer`)
|
|
276
|
+
- **Memory**: `InMemory`, `QdrantMemory` (`flowai-cli[qdrant]`), `ChromaMemory` (`flowai-cli[chroma]`)
|
|
277
|
+
- **Observability**: per-span token counts and estimated cost (`agent.tracer`),
|
|
278
|
+
an event bus, and an OpenTelemetry exporter (`flowai-cli[observability]`)
|
|
279
|
+
- **Plugins**: third-party providers via the `flowai.plugins` entry point
|
|
280
|
+
|
|
281
|
+
Runnable examples are in [examples/](examples/).
|
|
282
|
+
|
|
283
|
+
## Developing FlowAI
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
pip install -e '.[dev]'
|
|
287
|
+
pytest
|
|
288
|
+
ruff check flowai tests
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The tests are offline: providers are faked at the HTTP layer, so no key is
|
|
292
|
+
needed, and they never touch your real keyring.
|
|
293
|
+
|
|
294
|
+
## Releasing to PyPI
|
|
295
|
+
|
|
296
|
+
1. Bump `version` in `pyproject.toml` and `__version__` in `flowai/__init__.py`.
|
|
297
|
+
2. Push a tag `flowai-vX.Y.Z`. The `flowai-release` workflow builds the
|
|
298
|
+
package and publishes it with PyPI Trusted Publishing.
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# FlowAI
|
|
2
|
+
|
|
3
|
+
FlowAI is an AI agent you run in your terminal, in the same spirit as Claude
|
|
4
|
+
Code or OpenCode. Bring your own key from Anthropic, OpenAI, NVIDIA, Google
|
|
5
|
+
Gemini or OpenRouter, or run fully local with Ollama or your own NVIDIA NIM.
|
|
6
|
+
The agent can read, search and edit the files in your project, run shell
|
|
7
|
+
commands (after asking you), and search the web, while a security layer keeps
|
|
8
|
+
your API keys out of its reach.
|
|
9
|
+
|
|
10
|
+
It is also a Python library: the same agents, tools, workflows and memory the
|
|
11
|
+
CLI uses are yours to build on.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install flowai-cli
|
|
17
|
+
pipx install flowai-cli # same, in its own isolated environment
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Python 3.10 or newer. The package is `flowai-cli` (the name `flowai` is taken on PyPI);
|
|
21
|
+
the command it installs is `flowai`, and the import is `import flowai`.
|
|
22
|
+
|
|
23
|
+
## First run
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
flowai
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The first time, FlowAI asks for a provider and an API key, stores the key in
|
|
30
|
+
your operating system's credential store, and sends one tiny request to check
|
|
31
|
+
it works. After that, `flowai` opens a session in the current directory.
|
|
32
|
+
|
|
33
|
+
## The interface
|
|
34
|
+
|
|
35
|
+
A FlowAI session is drawn as a current: every turn flows down one rail.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
█▀▀ █ █▀█ █ █ █ ▄▀█ █ v0.3.0
|
|
39
|
+
█▀ █▄▄ █▄█ ▀▄▀▄▀ █▀█ █ an agent that moves with your code
|
|
40
|
+
▅▇█▇▅▂▁▁▃▆▇█▇▅▂▁▁▃▆▇█▇▅▂▁▁▃▆██▇▅▂▁▁▃▆██▇▄▂▁▁▃▆
|
|
41
|
+
|
|
42
|
+
model nvidia › nvidia/nemotron-3.5-lightning-30b-a3b
|
|
43
|
+
workspace C:\Users\you\project
|
|
44
|
+
shield armed · key in Windows Credential Manager · approvals ask
|
|
45
|
+
|
|
46
|
+
◆ fix the failing test in tests/test_api.py
|
|
47
|
+
≋ undertow thought for 2.1s · 48 words /think to read it
|
|
48
|
+
◇ read_file tests/test_api.py 0.0s ✓ 120 lines
|
|
49
|
+
◇ bash pytest -q tests/test_api.py 1.9s ✗ exit 1
|
|
50
|
+
|
|
51
|
+
┃ GATE edit_file wants to run risk LOW
|
|
52
|
+
┃ src/api.py
|
|
53
|
+
┃ - return a - b
|
|
54
|
+
┃ + return a + b
|
|
55
|
+
┃ [y] allow once (default) [a] always allow edit_file [n] deny
|
|
56
|
+
● flowai
|
|
57
|
+
│ The bug was a sign error in add(). All 12 tests pass now.
|
|
58
|
+
╰─ 6.3s · 3 tools · 4.1k tok tide ▂ 18%
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **◆** is your prompt. **≋ undertow** is the model's reasoning, folded to one
|
|
62
|
+
line (`/think` reads it in full, `/think on` shows it live).
|
|
63
|
+
- **◇** is a tool call, with its timing and outcome.
|
|
64
|
+
- The **gate** asks before anything risky runs. Its rail is coloured by risk,
|
|
65
|
+
it shows commands with syntax highlighting and edits as a diff, and it says
|
|
66
|
+
why a call is risky.
|
|
67
|
+
- The answer is rendered markdown. While the model works, a swell of block
|
|
68
|
+
characters moves along the rail.
|
|
69
|
+
- The **tide** gauge shows how full the model's context window is. The strip at
|
|
70
|
+
the bottom of the prompt shows the model, tide, tokens spent and shield state.
|
|
71
|
+
- `/` completes commands. Esc then Enter adds a new line. Prompt history is
|
|
72
|
+
kept, with any pasted key masked.
|
|
73
|
+
|
|
74
|
+
Piped output, CI logs and `--plain` get plain text instead. `NO_COLOR` and
|
|
75
|
+
`FLOWAI_NO_ANIM` are respected.
|
|
76
|
+
|
|
77
|
+
## NVIDIA
|
|
78
|
+
|
|
79
|
+
FlowAI works with every kind of NVIDIA key and endpoint:
|
|
80
|
+
|
|
81
|
+
| You have | Do this |
|
|
82
|
+
|---|---|
|
|
83
|
+
| A key from build.nvidia.com (`nvapi-...`) | `flowai configure`, pick NVIDIA, paste it |
|
|
84
|
+
| The key in an environment variable | Any of `NVIDIA_API_KEY`, `NVAPI_KEY`, `NGC_API_KEY`, `NIM_API_KEY` works |
|
|
85
|
+
| A NIM running on your own machine or cluster | `NVIDIA_BASE_URL=http://localhost:8000/v1` (or `flowai configure --provider nvidia --base-url ...`); no key needed |
|
|
86
|
+
|
|
87
|
+
The catalog's models do not all accept the same requests, and FlowAI adapts to
|
|
88
|
+
each one:
|
|
89
|
+
|
|
90
|
+
- **No tool support.** A model that refuses `tools` is sent the conversation
|
|
91
|
+
without them, and you are told so. The same goes for `stream_options`, the
|
|
92
|
+
`system` role and `max_tokens`. Any chat model on the catalog can be used.
|
|
93
|
+
- **Reasoning models** (Nemotron, DeepSeek, GPT-OSS) stream their thinking
|
|
94
|
+
separately, whether it arrives as `reasoning_content` or inside `<think>` tags.
|
|
95
|
+
- **Model names.** A bare name such as `glm-5.3-flash` is resolved to its
|
|
96
|
+
catalog id (`z-ai/glm-5.3-flash`).
|
|
97
|
+
- **Unavailable models.** Some models are listed but not available to every
|
|
98
|
+
account, and retired ones answer 410. FlowAI then suggests models your key
|
|
99
|
+
can call, and `flowai configure` finds a working one for you.
|
|
100
|
+
- **Rate limits.** The free tier's limits are retried with backoff,
|
|
101
|
+
honouring `Retry-After`.
|
|
102
|
+
- **Key mistakes** are named: a key without the `nvapi-` prefix (an old NGC
|
|
103
|
+
key cannot call the hosted API), or stray whitespace around it.
|
|
104
|
+
|
|
105
|
+
`flowai models nvidia [filter]` lists the catalog. In a session, `/models` does
|
|
106
|
+
the same, and `/model <id>` switches without losing the conversation.
|
|
107
|
+
|
|
108
|
+
## Security
|
|
109
|
+
|
|
110
|
+
An agent that runs commands on your machine is an obvious target. Someone
|
|
111
|
+
would like it to read your API key and send it somewhere, usually by hiding
|
|
112
|
+
instructions in a file or web page the agent reads. FlowAI defends in layers:
|
|
113
|
+
|
|
114
|
+
| Layer | What it does |
|
|
115
|
+
|---|---|
|
|
116
|
+
| Key vault | Keys live in Windows Credential Manager, the macOS Keychain or the Secret Service. Without one, they go in a file only your user can read. Keys are never copied into the environment |
|
|
117
|
+
| Scrubbed children | `bash`, `python_repl` and MCP servers start with every credential-looking variable removed. Python runs in a separate process, so it cannot reach into FlowAI's memory |
|
|
118
|
+
| Redaction | Every key FlowAI knows, plus the shapes of common keys (`nvapi-`, `sk-`, `sk-ant-`, `AIza`, `ghp_`...), is masked in tool output before the model sees it. That includes base64, hex, reversed and URL-encoded forms. Transcripts, prompt history and errors are masked too |
|
|
119
|
+
| Tripwires | Dumping the environment, reading `.env`, SSH keys, the credential store or FlowAI's own config always asks you, even with `--yolo`. With no terminal attached, the answer is no |
|
|
120
|
+
| Exfiltration block | A call that reads secrets and sends data over the network in one go is refused outright, as is any tool call whose arguments contain a key |
|
|
121
|
+
| Network guard | `fetch_url` refuses localhost, private networks and cloud metadata addresses, checking every redirect |
|
|
122
|
+
| Injection fence | Web and MCP output is wrapped as untrusted data, and the system prompt tells the model to treat it that way |
|
|
123
|
+
| Terminal hygiene | Escape sequences are stripped from everything drawn, so a page cannot rewrite your clipboard or hide text |
|
|
124
|
+
| Server | `flowai serve` requires a bearer token, refuses non-local `Host` headers (DNS rebinding), and runs no shell tools unless started with `--allow-shell` |
|
|
125
|
+
|
|
126
|
+
`flowai shield` audits your setup and says what to fix. `flowai shield --fix`
|
|
127
|
+
moves plain-text keys into the keyring and locks the config directory, and
|
|
128
|
+
`flowai logout` deletes a saved key. These are tripwires, not a sandbox: read
|
|
129
|
+
[SECURITY.md](SECURITY.md) for what they do not cover.
|
|
130
|
+
|
|
131
|
+
## Commands
|
|
132
|
+
|
|
133
|
+
| Command | What it does |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `flowai` | Interactive session in the current directory |
|
|
136
|
+
| `flowai "add type hints to utils.py"` | Run one task and exit |
|
|
137
|
+
| `flowai chat --resume` (or `-c`) | Continue the most recent session |
|
|
138
|
+
| `flowai run -q "..."` | One task, print only the final answer (good for scripts) |
|
|
139
|
+
| `echo "..." \| flowai run` | Read the prompt from stdin |
|
|
140
|
+
| `flowai configure` | Choose provider, model and API key |
|
|
141
|
+
| `flowai models [provider] [filter]` | List the models a provider serves |
|
|
142
|
+
| `flowai shield [--fix]` | Audit how your keys are protected |
|
|
143
|
+
| `flowai logout [provider]` | Delete a saved key |
|
|
144
|
+
| `flowai init [dir]` | Create a `flowai.yaml` and a starter `main.py` |
|
|
145
|
+
| `flowai inspect` | Show config status, saved sessions, checkpoints |
|
|
146
|
+
| `flowai tools` | List the built-in tools |
|
|
147
|
+
| `flowai serve` | HTTP API with streaming (`pip install 'flowai-cli[server]'`) |
|
|
148
|
+
|
|
149
|
+
Options for `chat` and `run`: `--provider`, `--model`, `-w/--workspace DIR`,
|
|
150
|
+
`--yolo` (do not ask before shell/python run; secret access still asks),
|
|
151
|
+
`--no-tools`, `--max-iterations N`, `--plain`.
|
|
152
|
+
|
|
153
|
+
Inside a session: `/help`, `/model <id>`, `/models [filter]`,
|
|
154
|
+
`/provider <name> [model]`, `/tools`, `/think [on|off]`, `/cost`, `/shield`,
|
|
155
|
+
`/new`, `/reset`, `/clear`, `/yolo`, `/exit`.
|
|
156
|
+
|
|
157
|
+
## What the agent can do
|
|
158
|
+
|
|
159
|
+
| Tool | Notes |
|
|
160
|
+
|---|---|
|
|
161
|
+
| `read_file`, `list_dir`, `glob_files`, `grep_files` | Read-only; never ask, except for files that hold secrets |
|
|
162
|
+
| `write_file`, `edit_file` | Change files inside the workspace only |
|
|
163
|
+
| `bash` | Asks first. Runs in the workspace, 120 s timeout |
|
|
164
|
+
| `python_repl` | Asks first. Runs in a separate interpreter, 60 s timeout |
|
|
165
|
+
| `search_web`, `fetch_url` | DuckDuckGo search and public page fetch |
|
|
166
|
+
|
|
167
|
+
Every file path is resolved against the workspace (the directory you started
|
|
168
|
+
in, or `-w`) and refused if it points outside.
|
|
169
|
+
|
|
170
|
+
## Configuration
|
|
171
|
+
|
|
172
|
+
Settings are layered, later wins:
|
|
173
|
+
|
|
174
|
+
1. `~/.flowai/config.json`, written by `flowai configure` (keys themselves go
|
|
175
|
+
to the OS keyring)
|
|
176
|
+
2. `flowai.yaml` (or `.yml`, `.toml`, `.json`) in the current directory or any
|
|
177
|
+
parent, so it works from a subdirectory of your project
|
|
178
|
+
3. Environment: `FLOWAI_PROVIDER`, `FLOWAI_MODEL`, and the provider key variables
|
|
179
|
+
|
|
180
|
+
See [flowai.yaml](flowai.yaml) for every option, including `mcp_servers` for
|
|
181
|
+
borrowing tools from Model Context Protocol servers (`pip install 'flowai-cli[mcp]'`).
|
|
182
|
+
Never put a key in `flowai.yaml`; `flowai shield` flags it if you do.
|
|
183
|
+
|
|
184
|
+
`FLOWAI_HOME` moves `~/.flowai`. `FLOWAI_PASS_ENV=GITHUB_TOKEN,...` hands
|
|
185
|
+
named variables to the agent's shell on purpose. `FLOWAI_KEYRING=0` turns
|
|
186
|
+
the keyring off.
|
|
187
|
+
|
|
188
|
+
## Using it as a library
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
import asyncio
|
|
192
|
+
from flowai import Agent, tool
|
|
193
|
+
|
|
194
|
+
@tool
|
|
195
|
+
def compound_interest(principal: float, rate: float, years: int) -> float:
|
|
196
|
+
"""Balance after compounding yearly."""
|
|
197
|
+
return round(principal * (1 + rate) ** years, 2)
|
|
198
|
+
|
|
199
|
+
agent = Agent(
|
|
200
|
+
name="Advisor",
|
|
201
|
+
provider="nvidia", # anthropic, ollama, openai, gemini, openrouter
|
|
202
|
+
model="nvidia/nemotron-3.5-lightning-30b-a3b",
|
|
203
|
+
tools=[compound_interest],
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
print(asyncio.run(agent.run("What will $5,000 be after 10 years at 8%?")))
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`agent.stream(prompt)` yields tokens as they arrive (and `reasoning_delta` for
|
|
210
|
+
reasoning models) and runs tool calls along the way. Tools you register in
|
|
211
|
+
your own code run without an approval prompt (you wrote them). To change that,
|
|
212
|
+
give a `ToolRegistry` an `approver` callback; to add the CLI's protections,
|
|
213
|
+
give it a `shield=Shield()` (`from flowai.security import Shield`).
|
|
214
|
+
|
|
215
|
+
Beyond a single agent the library has:
|
|
216
|
+
|
|
217
|
+
- **Specialised agents**: `PlannerAgent`, `CriticAgent`, `ToolAgent`, `SupervisorAgent`
|
|
218
|
+
- **Patterns**: `ReflectionPattern`, `PlanAndExecutePattern`, `DebatePattern`, `Swarm`
|
|
219
|
+
- **Workflows**: a graph engine with conditional edges, loops, parallel
|
|
220
|
+
branches and checkpointing (`Workflow`, `FileCheckpointer`)
|
|
221
|
+
- **Memory**: `InMemory`, `QdrantMemory` (`flowai-cli[qdrant]`), `ChromaMemory` (`flowai-cli[chroma]`)
|
|
222
|
+
- **Observability**: per-span token counts and estimated cost (`agent.tracer`),
|
|
223
|
+
an event bus, and an OpenTelemetry exporter (`flowai-cli[observability]`)
|
|
224
|
+
- **Plugins**: third-party providers via the `flowai.plugins` entry point
|
|
225
|
+
|
|
226
|
+
Runnable examples are in [examples/](examples/).
|
|
227
|
+
|
|
228
|
+
## Developing FlowAI
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pip install -e '.[dev]'
|
|
232
|
+
pytest
|
|
233
|
+
ruff check flowai tests
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The tests are offline: providers are faked at the HTTP layer, so no key is
|
|
237
|
+
needed, and they never touch your real keyring.
|
|
238
|
+
|
|
239
|
+
## Releasing to PyPI
|
|
240
|
+
|
|
241
|
+
1. Bump `version` in `pyproject.toml` and `__version__` in `flowai/__init__.py`.
|
|
242
|
+
2. Push a tag `flowai-vX.Y.Z`. The `flowai-release` workflow builds the
|
|
243
|
+
package and publishes it with PyPI Trusted Publishing.
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
FlowAI is an agent that reads files, runs commands and fetches web pages on
|
|
4
|
+
your machine, using your API key. This page says what FlowAI does to protect
|
|
5
|
+
that key and that machine, and where the protection stops.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Please do not open a public issue. Use GitHub's private vulnerability
|
|
10
|
+
reporting on this repository (Security tab, "Report a vulnerability"). Include
|
|
11
|
+
the FlowAI version (`flowai --version`), your OS, and the smallest prompt,
|
|
12
|
+
file or command that shows the problem.
|
|
13
|
+
|
|
14
|
+
## The threat
|
|
15
|
+
|
|
16
|
+
The usual attack is **prompt injection**. An attacker cannot type into your
|
|
17
|
+
session, but they can write text the agent will read: a README in a cloned
|
|
18
|
+
repository, a comment in a web page, an issue body, the output of an MCP tool.
|
|
19
|
+
That text tells the model to read your API key and send it somewhere. Others
|
|
20
|
+
aim for persistence (git hooks, shell startup files) or for internal services
|
|
21
|
+
(`localhost`, your router, cloud metadata at 169.254.169.254).
|
|
22
|
+
|
|
23
|
+
## What FlowAI does
|
|
24
|
+
|
|
25
|
+
**Keys are stored out of reach.**
|
|
26
|
+
- `flowai configure` saves keys in the OS credential store: Windows Credential
|
|
27
|
+
Manager, the macOS Keychain, or the Secret Service on Linux. The config file
|
|
28
|
+
holds only a marker.
|
|
29
|
+
- Without a keyring, the key goes into `~/.flowai/config.json`. The file is
|
|
30
|
+
created with owner-only permissions (mode 0600 on POSIX, a user-only ACL on
|
|
31
|
+
Windows) and written atomically.
|
|
32
|
+
- Keys are never placed in `os.environ`, so nothing FlowAI starts inherits them.
|
|
33
|
+
- Gemini keys travel in a header, not the URL, so they do not show up in
|
|
34
|
+
error messages or proxy logs.
|
|
35
|
+
|
|
36
|
+
**Tools cannot see keys.**
|
|
37
|
+
- `bash`, `python_repl` and MCP servers start with every credential-looking
|
|
38
|
+
environment variable removed (`*_API_KEY`, `*_TOKEN`, `*_SECRET`,
|
|
39
|
+
`*PASSWORD*` and similar). `FLOWAI_PASS_ENV` lets you pass named ones through
|
|
40
|
+
on purpose.
|
|
41
|
+
- `python_repl` runs in a separate interpreter, so it cannot import FlowAI and
|
|
42
|
+
read keys from its memory.
|
|
43
|
+
- `grep_files` never opens files that usually hold credentials (`.env`, `*.pem`,
|
|
44
|
+
`id_rsa`, `credentials.json`...). Reading one with `read_file` asks you first.
|
|
45
|
+
|
|
46
|
+
**The model never learns a key.**
|
|
47
|
+
- Every tool result is redacted before the model sees it. Redaction covers
|
|
48
|
+
each key FlowAI holds, any variable whose name looks like a credential, and
|
|
49
|
+
the shapes of common keys.
|
|
50
|
+
- Encoded copies are caught too: base64 at any alignment, hex, reversed and
|
|
51
|
+
URL-encoded.
|
|
52
|
+
- So even when a model is tricked into reading a key file, it sees
|
|
53
|
+
`[REDACTED:NVIDIA_API_KEY]`, and it has nothing to exfiltrate.
|
|
54
|
+
- Session transcripts, prompt history and error messages are redacted the same way.
|
|
55
|
+
|
|
56
|
+
**Dangerous calls are stopped or confirmed.**
|
|
57
|
+
- A tool call whose arguments contain a known key is refused.
|
|
58
|
+
- A command that reads secrets and sends data over the network in the same
|
|
59
|
+
call is refused (`cat .env | curl -d @- ...`).
|
|
60
|
+
- Commands that dump the environment, read credential files or stores, or
|
|
61
|
+
touch `~/.flowai` are rated critical. They ask you every time, also in
|
|
62
|
+
`--yolo` mode, and "always allow" is not offered for them.
|
|
63
|
+
- Uploads, remote scripts (`curl ... | sh`), destructive commands, privilege
|
|
64
|
+
escalation and persistence (git hooks, cron, shell profiles, the registry)
|
|
65
|
+
are rated high. The approval gate names the risk in plain words.
|
|
66
|
+
- With no terminal attached, nothing that needs approval runs.
|
|
67
|
+
|
|
68
|
+
**The network is fenced.**
|
|
69
|
+
- `fetch_url` only reaches public addresses. It resolves every host and
|
|
70
|
+
re-checks every redirect, refusing loopback, private ranges, link-local and
|
|
71
|
+
metadata addresses, and URLs with embedded credentials.
|
|
72
|
+
- Output from `fetch_url`, `search_web` and MCP tools is wrapped as untrusted
|
|
73
|
+
content. The system prompt tells the model never to follow instructions
|
|
74
|
+
found inside it.
|
|
75
|
+
|
|
76
|
+
**The terminal is protected.** Escape sequences and control characters are
|
|
77
|
+
stripped from model and tool output before they are drawn. A fetched page
|
|
78
|
+
cannot set your clipboard (OSC 52), retitle the window or hide text. Markdown
|
|
79
|
+
links are shown as text and URL, never as disguised hyperlinks.
|
|
80
|
+
|
|
81
|
+
**The HTTP server is closed by default.** `flowai serve`:
|
|
82
|
+
- binds to 127.0.0.1 and requires `Authorization: Bearer <token>` on every
|
|
83
|
+
route except `/health`
|
|
84
|
+
- rejects requests whose `Host` header is not local, which defeats DNS rebinding
|
|
85
|
+
- refuses shell and python tools unless started with `--allow-shell`
|
|
86
|
+
- never runs calls that touch secrets on behalf of an HTTP client
|
|
87
|
+
|
|
88
|
+
## What FlowAI does not do
|
|
89
|
+
|
|
90
|
+
- **It is not a sandbox.** When you approve a shell command, it runs with your
|
|
91
|
+
user's full permissions. The risk rules are pattern-based tripwires: a
|
|
92
|
+
determined attacker can write a command they do not recognise. Read what the
|
|
93
|
+
gate shows you, and keep `--yolo` for throwaway environments.
|
|
94
|
+
- Redaction only knows the keys FlowAI holds and the formats it recognises. A
|
|
95
|
+
password typed into a file the agent reads, in a format it cannot recognise,
|
|
96
|
+
will reach the model.
|
|
97
|
+
- Name resolution is checked before connecting, but a hostile DNS server can
|
|
98
|
+
still answer differently on the second lookup (a rebinding race).
|
|
99
|
+
- MCP servers are programs you chose to run. FlowAI scrubs their environment
|
|
100
|
+
and asks before each of their tool calls, but cannot see what they do inside.
|
|
101
|
+
- A key you export in your shell is visible to any program you run in that
|
|
102
|
+
shell, FlowAI or not. Prefer `flowai configure`.
|
|
103
|
+
|
|
104
|
+
## Checking your setup
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
flowai shield # what protects your keys right now
|
|
108
|
+
flowai shield --fix # move plain-text keys into the keyring, lock permissions
|
|
109
|
+
flowai logout nvidia # delete a saved key
|
|
110
|
+
```
|