airweave-cli 0.1.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.
- airweave_cli-0.1.0/LICENSE +21 -0
- airweave_cli-0.1.0/PKG-INFO +194 -0
- airweave_cli-0.1.0/README.md +167 -0
- airweave_cli-0.1.0/airweave_cli/__init__.py +1 -0
- airweave_cli-0.1.0/airweave_cli/commands/__init__.py +0 -0
- airweave_cli-0.1.0/airweave_cli/commands/auth.py +105 -0
- airweave_cli-0.1.0/airweave_cli/commands/collections.py +116 -0
- airweave_cli-0.1.0/airweave_cli/commands/search.py +87 -0
- airweave_cli-0.1.0/airweave_cli/commands/sources.py +96 -0
- airweave_cli-0.1.0/airweave_cli/config.py +105 -0
- airweave_cli-0.1.0/airweave_cli/main.py +45 -0
- airweave_cli-0.1.0/pyproject.toml +57 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Airweave
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: airweave-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The Airweave CLI for developers and AI agents.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: airweave,cli,search,ai,agents
|
|
7
|
+
Author: Airweave
|
|
8
|
+
Author-email: hello@airweave.ai
|
|
9
|
+
Requires-Python: >=3.9,<4.0
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
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 :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Dist: airweave-sdk (>=0.9)
|
|
23
|
+
Requires-Dist: rich (>=13,<14)
|
|
24
|
+
Requires-Dist: typer[all] (>=0.12,<0.13)
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Airweave CLI
|
|
28
|
+
|
|
29
|
+
[](https://pypi.python.org/pypi/airweave-cli)
|
|
30
|
+
|
|
31
|
+
A command-line interface for [Airweave](https://airweave.ai) — search across your connected sources from any terminal. Built for developers and AI agents.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
pip install airweave-cli
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This installs the `airweave` binary.
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
# Set your API key
|
|
45
|
+
export AIRWEAVE_API_KEY="your-api-key"
|
|
46
|
+
|
|
47
|
+
# Search a collection
|
|
48
|
+
airweave search "quarterly revenue figures" --collection finance-data
|
|
49
|
+
|
|
50
|
+
# List collections
|
|
51
|
+
airweave collections list
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Authentication
|
|
55
|
+
|
|
56
|
+
The CLI resolves credentials in this order:
|
|
57
|
+
|
|
58
|
+
1. `AIRWEAVE_API_KEY` environment variable
|
|
59
|
+
2. `~/.airweave/config.json` (saved via `airweave auth login`)
|
|
60
|
+
|
|
61
|
+
For interactive setup:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
airweave auth login
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This prompts for your API key, validates it, and saves it locally.
|
|
68
|
+
|
|
69
|
+
### Self-hosted
|
|
70
|
+
|
|
71
|
+
Point the CLI at your own Airweave instance:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
export AIRWEAVE_BASE_URL="https://airweave.internal.corp.com"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or set it during `airweave auth login`.
|
|
78
|
+
|
|
79
|
+
## Commands
|
|
80
|
+
|
|
81
|
+
### Search
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
airweave search "<query>" --collection <id> --top-k 5 --format json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Flag | Short | Default | Description |
|
|
88
|
+
|------|-------|---------|-------------|
|
|
89
|
+
| `--collection` | `-c` | `$AIRWEAVE_COLLECTION` | Collection readable ID |
|
|
90
|
+
| `--top-k` | `-k` | `10` | Number of results |
|
|
91
|
+
| `--format` | `-f` | `json` | Output format: `json` or `text` |
|
|
92
|
+
|
|
93
|
+
JSON output (default) writes pure JSON to stdout — nothing else. Pipe it:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
airweave search "query" -c my-collection | jq '.results[0].md_content'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Auth
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
airweave auth login # interactive setup
|
|
103
|
+
airweave auth status # show current auth state
|
|
104
|
+
airweave auth logout # clear saved credentials
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Collections
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
airweave collections list # list all
|
|
111
|
+
airweave collections create --name "My Data" # create
|
|
112
|
+
airweave collections get my-data-x7k9m # get details
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Sources
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
airweave sources list --collection my-data # list source connections
|
|
119
|
+
airweave sources sync <source-connection-id> # trigger sync
|
|
120
|
+
airweave sources sync <id> --force # full re-sync
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Agent Usage
|
|
124
|
+
|
|
125
|
+
The CLI is designed for non-interactive use by AI agents. Every command:
|
|
126
|
+
|
|
127
|
+
- Outputs clean JSON to stdout (default)
|
|
128
|
+
- Sends all errors to stderr
|
|
129
|
+
- Uses correct exit codes (0 = success, 1 = error)
|
|
130
|
+
|
|
131
|
+
### Environment variables
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
export AIRWEAVE_API_KEY="sk-..."
|
|
135
|
+
export AIRWEAVE_COLLECTION="my-knowledge-base"
|
|
136
|
+
export AIRWEAVE_BASE_URL="https://api.airweave.ai" # optional
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Piping
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
# Get the top result's content
|
|
143
|
+
airweave search "how to reset password" | jq -r '.results[0].md_content'
|
|
144
|
+
|
|
145
|
+
# Get the AI-generated answer
|
|
146
|
+
airweave search "what is our refund policy" | jq -r '.completion'
|
|
147
|
+
|
|
148
|
+
# List collection IDs
|
|
149
|
+
airweave collections list | jq -r '.[].readable_id'
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### CI / Cloud Functions
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
# GitHub Actions example
|
|
156
|
+
- name: Search Airweave
|
|
157
|
+
env:
|
|
158
|
+
AIRWEAVE_API_KEY: ${{ secrets.AIRWEAVE_API_KEY }}
|
|
159
|
+
AIRWEAVE_COLLECTION: docs
|
|
160
|
+
run: |
|
|
161
|
+
result=$(airweave search "deployment guide")
|
|
162
|
+
echo "$result" | jq '.results[:3]'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
No SDK imports, no MCP server, no config files — just an env var and a shell.
|
|
166
|
+
|
|
167
|
+
## Configuration
|
|
168
|
+
|
|
169
|
+
Config file location: `~/.airweave/config.json`
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"api_key": "sk-...",
|
|
174
|
+
"base_url": "https://api.airweave.ai",
|
|
175
|
+
"collection": "my-default-collection"
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Resolution order for all settings:
|
|
180
|
+
|
|
181
|
+
1. CLI flag (e.g. `--collection`)
|
|
182
|
+
2. Environment variable (e.g. `AIRWEAVE_COLLECTION`)
|
|
183
|
+
3. Config file
|
|
184
|
+
4. Default / error
|
|
185
|
+
|
|
186
|
+
## Contributing
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
git clone https://github.com/airweave-ai/cli.git
|
|
190
|
+
cd cli
|
|
191
|
+
poetry install
|
|
192
|
+
poetry run airweave --help
|
|
193
|
+
```
|
|
194
|
+
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Airweave CLI
|
|
2
|
+
|
|
3
|
+
[](https://pypi.python.org/pypi/airweave-cli)
|
|
4
|
+
|
|
5
|
+
A command-line interface for [Airweave](https://airweave.ai) — search across your connected sources from any terminal. Built for developers and AI agents.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install airweave-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This installs the `airweave` binary.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
# Set your API key
|
|
19
|
+
export AIRWEAVE_API_KEY="your-api-key"
|
|
20
|
+
|
|
21
|
+
# Search a collection
|
|
22
|
+
airweave search "quarterly revenue figures" --collection finance-data
|
|
23
|
+
|
|
24
|
+
# List collections
|
|
25
|
+
airweave collections list
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Authentication
|
|
29
|
+
|
|
30
|
+
The CLI resolves credentials in this order:
|
|
31
|
+
|
|
32
|
+
1. `AIRWEAVE_API_KEY` environment variable
|
|
33
|
+
2. `~/.airweave/config.json` (saved via `airweave auth login`)
|
|
34
|
+
|
|
35
|
+
For interactive setup:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
airweave auth login
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This prompts for your API key, validates it, and saves it locally.
|
|
42
|
+
|
|
43
|
+
### Self-hosted
|
|
44
|
+
|
|
45
|
+
Point the CLI at your own Airweave instance:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
export AIRWEAVE_BASE_URL="https://airweave.internal.corp.com"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or set it during `airweave auth login`.
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
### Search
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
airweave search "<query>" --collection <id> --top-k 5 --format json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Flag | Short | Default | Description |
|
|
62
|
+
|------|-------|---------|-------------|
|
|
63
|
+
| `--collection` | `-c` | `$AIRWEAVE_COLLECTION` | Collection readable ID |
|
|
64
|
+
| `--top-k` | `-k` | `10` | Number of results |
|
|
65
|
+
| `--format` | `-f` | `json` | Output format: `json` or `text` |
|
|
66
|
+
|
|
67
|
+
JSON output (default) writes pure JSON to stdout — nothing else. Pipe it:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
airweave search "query" -c my-collection | jq '.results[0].md_content'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Auth
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
airweave auth login # interactive setup
|
|
77
|
+
airweave auth status # show current auth state
|
|
78
|
+
airweave auth logout # clear saved credentials
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Collections
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
airweave collections list # list all
|
|
85
|
+
airweave collections create --name "My Data" # create
|
|
86
|
+
airweave collections get my-data-x7k9m # get details
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Sources
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
airweave sources list --collection my-data # list source connections
|
|
93
|
+
airweave sources sync <source-connection-id> # trigger sync
|
|
94
|
+
airweave sources sync <id> --force # full re-sync
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Agent Usage
|
|
98
|
+
|
|
99
|
+
The CLI is designed for non-interactive use by AI agents. Every command:
|
|
100
|
+
|
|
101
|
+
- Outputs clean JSON to stdout (default)
|
|
102
|
+
- Sends all errors to stderr
|
|
103
|
+
- Uses correct exit codes (0 = success, 1 = error)
|
|
104
|
+
|
|
105
|
+
### Environment variables
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
export AIRWEAVE_API_KEY="sk-..."
|
|
109
|
+
export AIRWEAVE_COLLECTION="my-knowledge-base"
|
|
110
|
+
export AIRWEAVE_BASE_URL="https://api.airweave.ai" # optional
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Piping
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
# Get the top result's content
|
|
117
|
+
airweave search "how to reset password" | jq -r '.results[0].md_content'
|
|
118
|
+
|
|
119
|
+
# Get the AI-generated answer
|
|
120
|
+
airweave search "what is our refund policy" | jq -r '.completion'
|
|
121
|
+
|
|
122
|
+
# List collection IDs
|
|
123
|
+
airweave collections list | jq -r '.[].readable_id'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### CI / Cloud Functions
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
# GitHub Actions example
|
|
130
|
+
- name: Search Airweave
|
|
131
|
+
env:
|
|
132
|
+
AIRWEAVE_API_KEY: ${{ secrets.AIRWEAVE_API_KEY }}
|
|
133
|
+
AIRWEAVE_COLLECTION: docs
|
|
134
|
+
run: |
|
|
135
|
+
result=$(airweave search "deployment guide")
|
|
136
|
+
echo "$result" | jq '.results[:3]'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
No SDK imports, no MCP server, no config files — just an env var and a shell.
|
|
140
|
+
|
|
141
|
+
## Configuration
|
|
142
|
+
|
|
143
|
+
Config file location: `~/.airweave/config.json`
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"api_key": "sk-...",
|
|
148
|
+
"base_url": "https://api.airweave.ai",
|
|
149
|
+
"collection": "my-default-collection"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Resolution order for all settings:
|
|
154
|
+
|
|
155
|
+
1. CLI flag (e.g. `--collection`)
|
|
156
|
+
2. Environment variable (e.g. `AIRWEAVE_COLLECTION`)
|
|
157
|
+
3. Config file
|
|
158
|
+
4. Default / error
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
git clone https://github.com/airweave-ai/cli.git
|
|
164
|
+
cd cli
|
|
165
|
+
poetry install
|
|
166
|
+
poetry run airweave --help
|
|
167
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from airweave_cli.config import (
|
|
11
|
+
clear_config,
|
|
12
|
+
load_config,
|
|
13
|
+
save_config,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(name="auth", help="Manage authentication.", no_args_is_help=True)
|
|
17
|
+
|
|
18
|
+
stderr = Console(stderr=True)
|
|
19
|
+
stdout = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class OutputFormat(str, Enum):
|
|
23
|
+
json = "json"
|
|
24
|
+
text = "text"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
def login() -> None:
|
|
29
|
+
"""Interactive login: save your API key to ~/.airweave/config.json."""
|
|
30
|
+
api_key = typer.prompt("API key", hide_input=True)
|
|
31
|
+
base_url = typer.prompt(
|
|
32
|
+
"Base URL",
|
|
33
|
+
default="https://api.airweave.ai",
|
|
34
|
+
show_default=True,
|
|
35
|
+
)
|
|
36
|
+
collection = typer.prompt(
|
|
37
|
+
"Default collection (readable_id)",
|
|
38
|
+
default="",
|
|
39
|
+
show_default=False,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Validate the key
|
|
43
|
+
try:
|
|
44
|
+
from airweave import AirweaveSDK
|
|
45
|
+
|
|
46
|
+
client = AirweaveSDK(api_key=api_key, base_url=base_url)
|
|
47
|
+
client.collections.list(limit=1)
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
stderr.print(f"[red]Authentication failed:[/red] {exc}")
|
|
50
|
+
raise typer.Exit(code=1)
|
|
51
|
+
|
|
52
|
+
cfg = load_config()
|
|
53
|
+
cfg["api_key"] = api_key
|
|
54
|
+
if base_url and base_url != "https://api.airweave.ai":
|
|
55
|
+
cfg["base_url"] = base_url
|
|
56
|
+
if collection:
|
|
57
|
+
cfg["collection"] = collection
|
|
58
|
+
save_config(cfg)
|
|
59
|
+
|
|
60
|
+
stderr.print("[green]Logged in.[/green] Config saved to ~/.airweave/config.json")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.command()
|
|
64
|
+
def status(
|
|
65
|
+
format: OutputFormat = typer.Option(
|
|
66
|
+
OutputFormat.text, "--format", "-f", help="Output format."
|
|
67
|
+
),
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Show current authentication state."""
|
|
70
|
+
env_key = os.environ.get("AIRWEAVE_API_KEY")
|
|
71
|
+
cfg = load_config()
|
|
72
|
+
|
|
73
|
+
info = {
|
|
74
|
+
"api_key_source": "env" if env_key else ("config" if cfg.get("api_key") else "none"),
|
|
75
|
+
"api_key_set": bool(env_key or cfg.get("api_key")),
|
|
76
|
+
"base_url": os.environ.get("AIRWEAVE_BASE_URL")
|
|
77
|
+
or cfg.get("base_url", "https://api.airweave.ai"),
|
|
78
|
+
"base_url_source": "env"
|
|
79
|
+
if os.environ.get("AIRWEAVE_BASE_URL")
|
|
80
|
+
else ("config" if cfg.get("base_url") else "default"),
|
|
81
|
+
"collection": os.environ.get("AIRWEAVE_COLLECTION") or cfg.get("collection"),
|
|
82
|
+
"collection_source": "env"
|
|
83
|
+
if os.environ.get("AIRWEAVE_COLLECTION")
|
|
84
|
+
else ("config" if cfg.get("collection") else "none"),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if format == OutputFormat.json:
|
|
88
|
+
typer.echo(json.dumps(info, indent=2))
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
stdout.print()
|
|
92
|
+
key_display = "****" if info["api_key_set"] else "[red]not set[/red]"
|
|
93
|
+
stdout.print(f" API key: {key_display} [dim]({info['api_key_source']})[/dim]")
|
|
94
|
+
stdout.print(f" Base URL: {info['base_url']} [dim]({info['base_url_source']})[/dim]")
|
|
95
|
+
coll_display = info["collection"] or "[red]not set[/red]"
|
|
96
|
+
coll_source = f" [dim]({info['collection_source']})[/dim]" if info["collection"] else ""
|
|
97
|
+
stdout.print(f" Collection: {coll_display}{coll_source}")
|
|
98
|
+
stdout.print()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@app.command()
|
|
102
|
+
def logout() -> None:
|
|
103
|
+
"""Clear saved credentials from ~/.airweave/config.json."""
|
|
104
|
+
clear_config()
|
|
105
|
+
stderr.print("Logged out. Config cleared.")
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from airweave_cli.config import get_client, serialize
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(name="collections", help="Manage collections.", no_args_is_help=True)
|
|
14
|
+
|
|
15
|
+
stderr = Console(stderr=True)
|
|
16
|
+
stdout = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OutputFormat(str, Enum):
|
|
20
|
+
json = "json"
|
|
21
|
+
text = "text"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("list")
|
|
25
|
+
def list_collections(
|
|
26
|
+
format: OutputFormat = typer.Option(
|
|
27
|
+
OutputFormat.json, "--format", "-f", help="Output format."
|
|
28
|
+
),
|
|
29
|
+
) -> None:
|
|
30
|
+
"""List all collections."""
|
|
31
|
+
client = get_client()
|
|
32
|
+
try:
|
|
33
|
+
collections = client.collections.list()
|
|
34
|
+
except Exception as exc:
|
|
35
|
+
stderr.print(f"[red]Error:[/red] {exc}")
|
|
36
|
+
raise typer.Exit(code=1)
|
|
37
|
+
|
|
38
|
+
if format == OutputFormat.json:
|
|
39
|
+
typer.echo(json.dumps(serialize(collections), indent=2, default=str))
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
if not collections:
|
|
43
|
+
stderr.print("[yellow]No collections found.[/yellow]")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
table = Table(title="Collections")
|
|
47
|
+
table.add_column("Name", style="bold")
|
|
48
|
+
table.add_column("Readable ID")
|
|
49
|
+
table.add_column("Status")
|
|
50
|
+
table.add_column("Created")
|
|
51
|
+
|
|
52
|
+
for c in collections:
|
|
53
|
+
table.add_row(
|
|
54
|
+
c.name,
|
|
55
|
+
c.readable_id,
|
|
56
|
+
str(c.status) if c.status else "-",
|
|
57
|
+
c.created_at.strftime("%Y-%m-%d %H:%M") if c.created_at else "-",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
stdout.print(table)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.command()
|
|
64
|
+
def create(
|
|
65
|
+
name: str = typer.Option(..., "--name", "-n", help="Collection name."),
|
|
66
|
+
readable_id: Optional[str] = typer.Option(
|
|
67
|
+
None, "--readable-id", "-r", help="Custom readable ID (auto-generated if omitted)."
|
|
68
|
+
),
|
|
69
|
+
format: OutputFormat = typer.Option(
|
|
70
|
+
OutputFormat.json, "--format", "-f", help="Output format."
|
|
71
|
+
),
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Create a new collection."""
|
|
74
|
+
client = get_client()
|
|
75
|
+
kwargs = {"name": name}
|
|
76
|
+
if readable_id:
|
|
77
|
+
kwargs["readable_id"] = readable_id
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
collection = client.collections.create(**kwargs)
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
stderr.print(f"[red]Error:[/red] {exc}")
|
|
83
|
+
raise typer.Exit(code=1)
|
|
84
|
+
|
|
85
|
+
if format == OutputFormat.json:
|
|
86
|
+
typer.echo(json.dumps(serialize(collection), indent=2, default=str))
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
stdout.print(f"[green]Created:[/green] {collection.name} ({collection.readable_id})")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command()
|
|
93
|
+
def get(
|
|
94
|
+
readable_id: str = typer.Argument(..., help="Collection readable_id."),
|
|
95
|
+
format: OutputFormat = typer.Option(
|
|
96
|
+
OutputFormat.json, "--format", "-f", help="Output format."
|
|
97
|
+
),
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Get details of a collection."""
|
|
100
|
+
client = get_client()
|
|
101
|
+
try:
|
|
102
|
+
collection = client.collections.get(readable_id)
|
|
103
|
+
except Exception as exc:
|
|
104
|
+
stderr.print(f"[red]Error:[/red] {exc}")
|
|
105
|
+
raise typer.Exit(code=1)
|
|
106
|
+
|
|
107
|
+
if format == OutputFormat.json:
|
|
108
|
+
typer.echo(json.dumps(serialize(collection), indent=2, default=str))
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
stdout.print(f"[bold]{collection.name}[/bold] ({collection.readable_id})")
|
|
112
|
+
stdout.print(f" ID: {collection.id}")
|
|
113
|
+
stdout.print(f" Status: {collection.status}")
|
|
114
|
+
stdout.print(f" Vectors: {collection.vector_size}d ({collection.embedding_model_name})")
|
|
115
|
+
stdout.print(f" Created: {collection.created_at}")
|
|
116
|
+
stdout.print(f" Modified: {collection.modified_at}")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.markdown import Markdown
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
from airweave_cli.config import get_client, resolve_collection, serialize
|
|
14
|
+
|
|
15
|
+
stderr = Console(stderr=True)
|
|
16
|
+
stdout = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OutputFormat(str, Enum):
|
|
20
|
+
json = "json"
|
|
21
|
+
text = "text"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def search(
|
|
25
|
+
query: str = typer.Argument(..., help="Search query."),
|
|
26
|
+
collection: Optional[str] = typer.Option(
|
|
27
|
+
None, "--collection", "-c", help="Collection readable_id."
|
|
28
|
+
),
|
|
29
|
+
top_k: int = typer.Option(10, "--top-k", "-k", help="Number of results to return."),
|
|
30
|
+
format: OutputFormat = typer.Option(
|
|
31
|
+
OutputFormat.json, "--format", "-f", help="Output format."
|
|
32
|
+
),
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Search a collection. This is the core command.
|
|
35
|
+
|
|
36
|
+
Pipe JSON output directly: airweave search "query" | jq '.results[0]'
|
|
37
|
+
"""
|
|
38
|
+
coll = resolve_collection(collection)
|
|
39
|
+
client = get_client()
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
from airweave import SearchRequest
|
|
43
|
+
|
|
44
|
+
response = client.collections.search(
|
|
45
|
+
coll,
|
|
46
|
+
request=SearchRequest(query=query, limit=top_k),
|
|
47
|
+
)
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
stderr.print(f"[red]Error:[/red] {exc}")
|
|
50
|
+
raise typer.Exit(code=1)
|
|
51
|
+
|
|
52
|
+
if format == OutputFormat.json:
|
|
53
|
+
typer.echo(json.dumps(serialize(response), indent=2, default=str))
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
# Rich text output
|
|
57
|
+
results = response.results
|
|
58
|
+
if not results:
|
|
59
|
+
stderr.print("[yellow]No results found.[/yellow]")
|
|
60
|
+
raise typer.Exit(code=0)
|
|
61
|
+
|
|
62
|
+
if response.completion:
|
|
63
|
+
stdout.print(Panel(Markdown(response.completion), title="Answer", border_style="green"))
|
|
64
|
+
stdout.print()
|
|
65
|
+
|
|
66
|
+
for i, result in enumerate(results, 1):
|
|
67
|
+
meta = result.get("system_metadata") or {}
|
|
68
|
+
source = meta.get("source_name") or result.get("source_name", "unknown")
|
|
69
|
+
score = result.get("score")
|
|
70
|
+
name = result.get("name", "")
|
|
71
|
+
content = result.get("textual_representation") or result.get("md_content", "")
|
|
72
|
+
source_fields = result.get("source_fields") or {}
|
|
73
|
+
url = source_fields.get("web_url") or source_fields.get("url") or result.get("url")
|
|
74
|
+
|
|
75
|
+
title_parts = [f"[bold]#{i}[/bold] {source}"]
|
|
76
|
+
if score is not None:
|
|
77
|
+
title_parts.append(f"[dim]score={score:.3f}[/dim]")
|
|
78
|
+
title = Text.from_markup(" ".join(title_parts))
|
|
79
|
+
|
|
80
|
+
snippet = content[:500] + ("..." if len(content) > 500 else "") if content else ""
|
|
81
|
+
body = Markdown(snippet) if snippet else Text.from_markup("[dim]no content[/dim]")
|
|
82
|
+
|
|
83
|
+
subtitle = url or None
|
|
84
|
+
if name and not snippet:
|
|
85
|
+
body = Text(name)
|
|
86
|
+
|
|
87
|
+
stdout.print(Panel(body, title=title, subtitle=subtitle, border_style="blue"))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from airweave_cli.config import get_client, resolve_collection, serialize
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(name="sources", help="Manage source connections.", no_args_is_help=True)
|
|
14
|
+
|
|
15
|
+
stderr = Console(stderr=True)
|
|
16
|
+
stdout = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OutputFormat(str, Enum):
|
|
20
|
+
json = "json"
|
|
21
|
+
text = "text"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("list")
|
|
25
|
+
def list_sources(
|
|
26
|
+
collection: Optional[str] = typer.Option(
|
|
27
|
+
None, "--collection", "-c", help="Collection readable_id."
|
|
28
|
+
),
|
|
29
|
+
format: OutputFormat = typer.Option(
|
|
30
|
+
OutputFormat.json, "--format", "-f", help="Output format."
|
|
31
|
+
),
|
|
32
|
+
) -> None:
|
|
33
|
+
"""List source connections for a collection."""
|
|
34
|
+
coll = resolve_collection(collection)
|
|
35
|
+
client = get_client()
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
sources = client.source_connections.list(collection=coll)
|
|
39
|
+
except Exception as exc:
|
|
40
|
+
stderr.print(f"[red]Error:[/red] {exc}")
|
|
41
|
+
raise typer.Exit(code=1)
|
|
42
|
+
|
|
43
|
+
if format == OutputFormat.json:
|
|
44
|
+
typer.echo(json.dumps(serialize(sources), indent=2, default=str))
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
if not sources:
|
|
48
|
+
stderr.print("[yellow]No source connections found.[/yellow]")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
table = Table(title="Source Connections")
|
|
52
|
+
table.add_column("Name", style="bold")
|
|
53
|
+
table.add_column("Source")
|
|
54
|
+
table.add_column("ID")
|
|
55
|
+
table.add_column("Status")
|
|
56
|
+
table.add_column("Entities")
|
|
57
|
+
|
|
58
|
+
for s in sources:
|
|
59
|
+
table.add_row(
|
|
60
|
+
s.name,
|
|
61
|
+
s.short_name,
|
|
62
|
+
s.id,
|
|
63
|
+
str(s.status) if s.status else "-",
|
|
64
|
+
str(s.entity_count) if s.entity_count is not None else "-",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
stdout.print(table)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.command()
|
|
71
|
+
def sync(
|
|
72
|
+
source_connection_id: str = typer.Argument(..., help="Source connection ID (UUID)."),
|
|
73
|
+
force: bool = typer.Option(
|
|
74
|
+
False, "--force", help="Force a full re-sync instead of incremental."
|
|
75
|
+
),
|
|
76
|
+
format: OutputFormat = typer.Option(
|
|
77
|
+
OutputFormat.json, "--format", "-f", help="Output format."
|
|
78
|
+
),
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Trigger a sync for a source connection."""
|
|
81
|
+
client = get_client()
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
job = client.source_connections.run(
|
|
85
|
+
source_connection_id,
|
|
86
|
+
force_full_sync=force if force else None,
|
|
87
|
+
)
|
|
88
|
+
except Exception as exc:
|
|
89
|
+
stderr.print(f"[red]Error:[/red] {exc}")
|
|
90
|
+
raise typer.Exit(code=1)
|
|
91
|
+
|
|
92
|
+
if format == OutputFormat.json:
|
|
93
|
+
typer.echo(json.dumps(serialize(job), indent=2, default=str))
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
stdout.print(f"[green]Sync started.[/green] Job ID: {job.id} Status: {job.status}")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
CONFIG_DIR = Path.home() / ".airweave"
|
|
11
|
+
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
12
|
+
|
|
13
|
+
DEFAULT_BASE_URL = "https://api.airweave.ai"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_config() -> Dict[str, Any]:
|
|
17
|
+
if not CONFIG_PATH.exists():
|
|
18
|
+
return {}
|
|
19
|
+
try:
|
|
20
|
+
return json.loads(CONFIG_PATH.read_text())
|
|
21
|
+
except (json.JSONDecodeError, OSError):
|
|
22
|
+
return {}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def save_config(data: Dict[str, Any]) -> None:
|
|
26
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
CONFIG_DIR.chmod(0o700)
|
|
28
|
+
CONFIG_PATH.write_text(json.dumps(data, indent=2) + "\n")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def clear_config() -> None:
|
|
32
|
+
if CONFIG_PATH.exists():
|
|
33
|
+
CONFIG_PATH.unlink()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _fail(message: str) -> None:
|
|
37
|
+
typer.echo(message, err=True)
|
|
38
|
+
raise typer.Exit(code=1)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def resolve_api_key() -> str:
|
|
42
|
+
key = os.environ.get("AIRWEAVE_API_KEY")
|
|
43
|
+
if key:
|
|
44
|
+
return key
|
|
45
|
+
|
|
46
|
+
cfg = load_config()
|
|
47
|
+
key = cfg.get("api_key")
|
|
48
|
+
if key:
|
|
49
|
+
return key
|
|
50
|
+
|
|
51
|
+
_fail(
|
|
52
|
+
"No API key found. Set AIRWEAVE_API_KEY or run: airweave auth login"
|
|
53
|
+
)
|
|
54
|
+
return "" # unreachable, keeps type checkers happy
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def resolve_base_url() -> str:
|
|
58
|
+
url = os.environ.get("AIRWEAVE_BASE_URL")
|
|
59
|
+
if url:
|
|
60
|
+
return url
|
|
61
|
+
|
|
62
|
+
cfg = load_config()
|
|
63
|
+
url = cfg.get("base_url")
|
|
64
|
+
if url:
|
|
65
|
+
return url
|
|
66
|
+
|
|
67
|
+
return DEFAULT_BASE_URL
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def resolve_collection(flag: Optional[str] = None) -> str:
|
|
71
|
+
if flag:
|
|
72
|
+
return flag
|
|
73
|
+
|
|
74
|
+
coll = os.environ.get("AIRWEAVE_COLLECTION")
|
|
75
|
+
if coll:
|
|
76
|
+
return coll
|
|
77
|
+
|
|
78
|
+
cfg = load_config()
|
|
79
|
+
coll = cfg.get("collection")
|
|
80
|
+
if coll:
|
|
81
|
+
return coll
|
|
82
|
+
|
|
83
|
+
_fail(
|
|
84
|
+
"No collection specified. Use --collection, set AIRWEAVE_COLLECTION, "
|
|
85
|
+
"or run: airweave auth login"
|
|
86
|
+
)
|
|
87
|
+
return ""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_client():
|
|
91
|
+
from airweave import AirweaveSDK
|
|
92
|
+
|
|
93
|
+
return AirweaveSDK(
|
|
94
|
+
api_key=resolve_api_key(),
|
|
95
|
+
base_url=resolve_base_url(),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def serialize(obj: Any) -> Any:
|
|
100
|
+
"""Convert an SDK model (or list of models) to a JSON-serializable dict."""
|
|
101
|
+
if isinstance(obj, list):
|
|
102
|
+
return [serialize(item) for item in obj]
|
|
103
|
+
if hasattr(obj, "dict"):
|
|
104
|
+
return obj.dict()
|
|
105
|
+
return obj
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from airweave_cli import __version__
|
|
6
|
+
from airweave_cli.commands.auth import app as auth_app
|
|
7
|
+
from airweave_cli.commands.collections import app as collections_app
|
|
8
|
+
from airweave_cli.commands.search import search
|
|
9
|
+
from airweave_cli.commands.sources import app as sources_app
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="airweave",
|
|
13
|
+
help="Airweave CLI — search across your connected sources.",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _version_callback(value: bool) -> None:
|
|
19
|
+
if value:
|
|
20
|
+
typer.echo(f"airweave-cli {__version__}")
|
|
21
|
+
raise typer.Exit()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.callback()
|
|
25
|
+
def main(
|
|
26
|
+
version: bool = typer.Option(
|
|
27
|
+
False,
|
|
28
|
+
"--version",
|
|
29
|
+
"-v",
|
|
30
|
+
help="Show version and exit.",
|
|
31
|
+
callback=_version_callback,
|
|
32
|
+
is_eager=True,
|
|
33
|
+
),
|
|
34
|
+
) -> None:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
app.add_typer(auth_app, name="auth")
|
|
39
|
+
app.add_typer(collections_app, name="collections")
|
|
40
|
+
app.add_typer(sources_app, name="sources")
|
|
41
|
+
app.command()(search)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
app()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "airweave-cli"
|
|
3
|
+
|
|
4
|
+
[tool.poetry]
|
|
5
|
+
name = "airweave-cli"
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
description = "The Airweave CLI for developers and AI agents."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
authors = ["Airweave <hello@airweave.ai>"]
|
|
11
|
+
keywords = ["airweave", "cli", "search", "ai", "agents"]
|
|
12
|
+
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.9",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
packages = [
|
|
27
|
+
{ include = "airweave_cli" }
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Repository = "https://github.com/airweave-ai/cli"
|
|
32
|
+
|
|
33
|
+
[tool.poetry.dependencies]
|
|
34
|
+
python = "^3.9"
|
|
35
|
+
typer = {version = "^0.12", extras = ["all"]}
|
|
36
|
+
rich = "^13"
|
|
37
|
+
airweave-sdk = ">=0.9"
|
|
38
|
+
|
|
39
|
+
[tool.poetry.group.dev.dependencies]
|
|
40
|
+
pytest = "^8.0"
|
|
41
|
+
ruff = ">=0.11"
|
|
42
|
+
|
|
43
|
+
[tool.poetry.scripts]
|
|
44
|
+
airweave = "airweave_cli.main:app"
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = ["E", "F", "I"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|
|
54
|
+
|
|
55
|
+
[build-system]
|
|
56
|
+
requires = ["poetry-core"]
|
|
57
|
+
build-backend = "poetry.core.masonry.api"
|