kiwi-code 0.0.5__tar.gz → 0.0.6__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.
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/.gitignore +1 -1
- kiwi_code-0.0.6/CLAUDE.md +79 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/PKG-INFO +7 -7
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/README.md +4 -4
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/pyproject.toml +5 -5
- kiwi_code-0.0.6/src/kiwi_cli/__init__.py +3 -0
- {kiwi_code-0.0.5/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/auth.py +2 -2
- {kiwi_code-0.0.5/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/cli.py +1 -1
- {kiwi_code-0.0.5/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/client.py +80 -9
- {kiwi_code-0.0.5/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/commands.py +20 -0
- {kiwi_code-0.0.5/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/config.py +2 -2
- {kiwi_code-0.0.5/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/logger.py +1 -1
- {kiwi_code-0.0.5/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/runtime_manager.py +2 -2
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_runtime/main.py +1 -1
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/main.py +35 -5
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/screens/actions.py +2 -2
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/screens/autobots.py +2 -2
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/screens/dashboard.py +531 -11
- kiwi_code-0.0.6/src/kiwi_tui/screens/file_browser.py +182 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/screens/login.py +2 -2
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/uv.lock +1 -1
- kiwi_code-0.0.5/poetry.lock +0 -1570
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/.github/workflows/publish.yml +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/.python-version +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/Makefile +0 -0
- {kiwi_code-0.0.5/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/models.py +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_runtime/__init__.py +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_runtime/__main__.py +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/__init__.py +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/screens/__init__.py +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/screens/runtime_logs.py +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/src/kiwi_tui/widgets.py +0 -0
- {kiwi_code-0.0.5 → kiwi_code-0.0.6}/test_hello.py +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Kiwi Code is a TUI + CLI client for the Kiwi AI platform. It provides a chat interface (Textual) and CLI tools (Typer) for managing AI actions, action graphs, and their runs. A WebSocket-based runtime agent executes shell commands on behalf of the LLM.
|
|
8
|
+
|
|
9
|
+
## Build & Run Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Package manager: uv | Build backend: hatchling | Python: 3.13+
|
|
13
|
+
uv sync # Install/sync dependencies
|
|
14
|
+
uv build # Build package
|
|
15
|
+
|
|
16
|
+
# Run
|
|
17
|
+
make run # Launch TUI (kiwi)
|
|
18
|
+
make dev # TUI with live reload
|
|
19
|
+
make serve # Serve TUI in browser (port 8566)
|
|
20
|
+
make cli # Run CLI (kiwicli)
|
|
21
|
+
make runtime ARGS="..." # Run runtime agent with args
|
|
22
|
+
|
|
23
|
+
# Test
|
|
24
|
+
make test-hello # Test backend API connection
|
|
25
|
+
uv run python test_hello.py # Same, without make
|
|
26
|
+
|
|
27
|
+
# Clean
|
|
28
|
+
make clean # Remove caches and build artifacts
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
There are three entry points defined in pyproject.toml:
|
|
32
|
+
- `kiwi` → `kiwi_tui.main:main` (TUI)
|
|
33
|
+
- `kiwicli` → `kiwi_cli.cli:cli` (CLI)
|
|
34
|
+
- `kiwi-runtime` → `kiwi_runtime.main:main` (WebSocket agent)
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
┌─────────────────────────────────┐
|
|
40
|
+
│ Shared Libraries │
|
|
41
|
+
│ commands.py · client.py │
|
|
42
|
+
│ auth.py · models.py · config.py│
|
|
43
|
+
└──────────┬──────────┬───────────┘
|
|
44
|
+
│ │
|
|
45
|
+
┌──────▼──────┐ └──────────────┐
|
|
46
|
+
│ kiwi (TUI) │ kiwicli (CLI) │
|
|
47
|
+
│ Textual app │ Typer CLI │
|
|
48
|
+
└──────┬──────┘ └──────────────┘
|
|
49
|
+
│ auto-starts
|
|
50
|
+
┌──────▼──────────────────┐
|
|
51
|
+
│ kiwi-runtime │
|
|
52
|
+
│ WebSocket agent │
|
|
53
|
+
│ One per working dir │
|
|
54
|
+
│ Survives TUI restarts │
|
|
55
|
+
└─────────────────────────┘
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Key design decisions:**
|
|
59
|
+
- Shared infrastructure lives in `kiwi_cli`; TUI imports from it via absolute imports (e.g., `from kiwi_cli.auth import TokenManager`)
|
|
60
|
+
- One runtime agent per working directory, managed via `runtime_manager.py`
|
|
61
|
+
- Runtime state stored in `~/.kiwi/runtimes/<key>/` (pid, log, cwd, tokens)
|
|
62
|
+
- Auth tokens stored in `~/.kiwi/tokens.json` with 0600 permissions
|
|
63
|
+
- Config stored in `~/.kiwi/config.json`
|
|
64
|
+
|
|
65
|
+
## Three Source Packages
|
|
66
|
+
|
|
67
|
+
- **`src/kiwi_cli/`** — Typer CLI app and shared infrastructure: auth, client, config, commands, models, runtime_manager, logger. Both `kiwi_tui` and the CLI entry point depend on this package.
|
|
68
|
+
- **`src/kiwi_tui/`** — Textual TUI app, screens, and widgets. Imports shared modules from `kiwi_cli`.
|
|
69
|
+
- **`src/kiwi_runtime/`** — Standalone WebSocket agent (~43KB `main.py`) that connects to Kiwi server and executes shell commands, sandboxed to CWD by default
|
|
70
|
+
|
|
71
|
+
## Server Presets (runtime)
|
|
72
|
+
|
|
73
|
+
- `app` → `https://api.meetkiwi.ai` (production)
|
|
74
|
+
- `dev` → `https://dev.api.myautobots.com` (default for TUI)
|
|
75
|
+
- `local` → `http://localhost:8000`
|
|
76
|
+
|
|
77
|
+
## CI/CD
|
|
78
|
+
|
|
79
|
+
GitHub Actions (`publish.yml`): triggered by manual dispatch or `v*.*.*` tags. Runs smoke tests (`kiwicli --help`, `kiwi-runtime --help`, `kiwicli runtime status`), builds, and publishes to PyPI.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kiwi-code
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: A textual-based terminal user interface application
|
|
5
5
|
Project-URL: Homepage, https://meetkiwi.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/jetoslabs/kiwi-code
|
|
7
7
|
Author-email: Anurag Jha <anurag@meetkiwi.co>
|
|
8
|
-
License:
|
|
8
|
+
License: Proprietary
|
|
9
9
|
Keywords: cli,terminal,textual,tui
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License ::
|
|
12
|
+
Classifier: License :: Other/Proprietary License
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Requires-Python: <4.0,>=3.13
|
|
@@ -77,7 +77,7 @@ Kiwi Code has three entry points, but they are **not three services** — they a
|
|
|
77
77
|
- **`kiwi` automatically spawns `kiwi-runtime`** when you log in. The runtime runs as an independent process — quitting or restarting the TUI does **not** kill it.
|
|
78
78
|
- **One runtime per directory.** Each working directory gets its own runtime, scoped to that directory. Opening the TUI in two different project folders runs two independent runtimes.
|
|
79
79
|
- **`kiwicli` is for headless/scripting use.** It provides the same query commands (actions, runs, graphs) as the TUI, but as standalone CLI commands.
|
|
80
|
-
- **Authentication is shared.** Both use `TokenManager` to read/write tokens from `~/.
|
|
80
|
+
- **Authentication is shared.** Both use `TokenManager` to read/write tokens from `~/.kiwi/tokens.json`, so logging in via one works for the other.
|
|
81
81
|
- **Runtime lifecycle is independent.** The runtime persists across TUI restarts. Use `kiwicli runtime stop` to explicitly stop it.
|
|
82
82
|
|
|
83
83
|
## Installation
|
|
@@ -207,7 +207,7 @@ In **restricted mode** (default), commands are sandboxed to the current working
|
|
|
207
207
|
|
|
208
208
|
## Configuration
|
|
209
209
|
|
|
210
|
-
Stored at `~/.
|
|
210
|
+
Stored at `~/.kiwi/config.json`:
|
|
211
211
|
|
|
212
212
|
```json
|
|
213
213
|
{
|
|
@@ -218,7 +218,7 @@ Stored at `~/.autobots-tui/config.json`:
|
|
|
218
218
|
}
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
-
Logs are written to `~/.
|
|
221
|
+
Logs are written to `~/.kiwi/kiwi_tui.log`.
|
|
222
222
|
|
|
223
223
|
## Development
|
|
224
224
|
|
|
@@ -231,4 +231,4 @@ uv run kiwi
|
|
|
231
231
|
|
|
232
232
|
## License
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
Proprietary. All rights reserved.
|
|
@@ -51,7 +51,7 @@ Kiwi Code has three entry points, but they are **not three services** — they a
|
|
|
51
51
|
- **`kiwi` automatically spawns `kiwi-runtime`** when you log in. The runtime runs as an independent process — quitting or restarting the TUI does **not** kill it.
|
|
52
52
|
- **One runtime per directory.** Each working directory gets its own runtime, scoped to that directory. Opening the TUI in two different project folders runs two independent runtimes.
|
|
53
53
|
- **`kiwicli` is for headless/scripting use.** It provides the same query commands (actions, runs, graphs) as the TUI, but as standalone CLI commands.
|
|
54
|
-
- **Authentication is shared.** Both use `TokenManager` to read/write tokens from `~/.
|
|
54
|
+
- **Authentication is shared.** Both use `TokenManager` to read/write tokens from `~/.kiwi/tokens.json`, so logging in via one works for the other.
|
|
55
55
|
- **Runtime lifecycle is independent.** The runtime persists across TUI restarts. Use `kiwicli runtime stop` to explicitly stop it.
|
|
56
56
|
|
|
57
57
|
## Installation
|
|
@@ -181,7 +181,7 @@ In **restricted mode** (default), commands are sandboxed to the current working
|
|
|
181
181
|
|
|
182
182
|
## Configuration
|
|
183
183
|
|
|
184
|
-
Stored at `~/.
|
|
184
|
+
Stored at `~/.kiwi/config.json`:
|
|
185
185
|
|
|
186
186
|
```json
|
|
187
187
|
{
|
|
@@ -192,7 +192,7 @@ Stored at `~/.autobots-tui/config.json`:
|
|
|
192
192
|
}
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
-
Logs are written to `~/.
|
|
195
|
+
Logs are written to `~/.kiwi/kiwi_tui.log`.
|
|
196
196
|
|
|
197
197
|
## Development
|
|
198
198
|
|
|
@@ -205,4 +205,4 @@ uv run kiwi
|
|
|
205
205
|
|
|
206
206
|
## License
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
Proprietary. All rights reserved.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kiwi-code"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.6"
|
|
4
4
|
description = "A textual-based terminal user interface application"
|
|
5
5
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
6
6
|
requires-python = ">=3.13,<4.0"
|
|
@@ -18,12 +18,12 @@ dependencies = [
|
|
|
18
18
|
authors = [
|
|
19
19
|
{ name = "Anurag Jha", email = "anurag@meetkiwi.co" }
|
|
20
20
|
]
|
|
21
|
-
license = { text = "
|
|
21
|
+
license = { text = "Proprietary" }
|
|
22
22
|
keywords = ["tui", "textual", "terminal", "cli"]
|
|
23
23
|
classifiers = [
|
|
24
24
|
"Development Status :: 3 - Alpha",
|
|
25
25
|
"Intended Audience :: Developers",
|
|
26
|
-
"License ::
|
|
26
|
+
"License :: Other/Proprietary License",
|
|
27
27
|
"Programming Language :: Python :: 3",
|
|
28
28
|
"Programming Language :: Python :: 3.13",
|
|
29
29
|
]
|
|
@@ -34,11 +34,11 @@ Repository = "https://github.com/jetoslabs/kiwi-code"
|
|
|
34
34
|
|
|
35
35
|
[project.scripts]
|
|
36
36
|
kiwi = "kiwi_tui.main:main"
|
|
37
|
-
kiwicli = "
|
|
37
|
+
kiwicli = "kiwi_cli.cli:cli"
|
|
38
38
|
kiwi-runtime = "kiwi_runtime.main:main"
|
|
39
39
|
|
|
40
40
|
[tool.hatch.build.targets.wheel]
|
|
41
|
-
packages = ["src/kiwi_tui", "src/kiwi_runtime"]
|
|
41
|
+
packages = ["src/kiwi_cli", "src/kiwi_tui", "src/kiwi_runtime"]
|
|
42
42
|
|
|
43
43
|
[build-system]
|
|
44
44
|
requires = ["hatchling"]
|
|
@@ -15,10 +15,10 @@ class TokenManager:
|
|
|
15
15
|
"""Initialize token manager.
|
|
16
16
|
|
|
17
17
|
Args:
|
|
18
|
-
token_path: Path to token file, defaults to ~/.
|
|
18
|
+
token_path: Path to token file, defaults to ~/.kiwi/tokens.json
|
|
19
19
|
"""
|
|
20
20
|
if token_path is None:
|
|
21
|
-
token_path = Path.home() / ".
|
|
21
|
+
token_path = Path.home() / ".kiwi" / "tokens.json"
|
|
22
22
|
|
|
23
23
|
self.token_path = token_path
|
|
24
24
|
self._tokens: Optional[AuthTokens] = None
|
|
@@ -229,39 +229,110 @@ class AutobotsClientWrapper:
|
|
|
229
229
|
logger.error(error_msg)
|
|
230
230
|
return False, None, error_msg
|
|
231
231
|
|
|
232
|
-
def
|
|
232
|
+
def upload_files(self, file_paths: list[str]) -> tuple[bool, list[str], str]:
|
|
233
|
+
"""Upload files via /v1/files/ API and return their URLs.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
file_paths: List of local file paths to upload
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Tuple of (success, urls, message)
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
if not isinstance(self.client, AuthenticatedClient):
|
|
243
|
+
return False, [], "Authentication required"
|
|
244
|
+
|
|
245
|
+
import mimetypes
|
|
246
|
+
from pathlib import Path
|
|
247
|
+
from autobots_client.api.files import upload_files_v1_files_post
|
|
248
|
+
from autobots_client.models.body_upload_files_v1_files_post import BodyUploadFilesV1FilesPost
|
|
249
|
+
|
|
250
|
+
# Validate all files exist first
|
|
251
|
+
paths = []
|
|
252
|
+
for fp in file_paths:
|
|
253
|
+
p = Path(fp).expanduser().resolve()
|
|
254
|
+
if not p.exists():
|
|
255
|
+
return False, [], f"File not found: {fp}"
|
|
256
|
+
paths.append(p)
|
|
257
|
+
|
|
258
|
+
# The SDK's BodyUploadFilesV1FilesPost.to_multipart() sends files as
|
|
259
|
+
# text/plain strings, which doesn't work for binary uploads.
|
|
260
|
+
# Use the SDK's endpoint URL but build the multipart manually.
|
|
261
|
+
multipart_files = []
|
|
262
|
+
file_handles = []
|
|
263
|
+
for p in paths:
|
|
264
|
+
mime = mimetypes.guess_type(str(p))[0] or "application/octet-stream"
|
|
265
|
+
fh = open(p, "rb")
|
|
266
|
+
file_handles.append(fh)
|
|
267
|
+
multipart_files.append(("files", (p.name, fh, mime)))
|
|
268
|
+
|
|
269
|
+
http_client = self.client.get_httpx_client()
|
|
270
|
+
response = http_client.request(
|
|
271
|
+
method="POST",
|
|
272
|
+
url="/v1/files/public/",
|
|
273
|
+
files=multipart_files,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Close opened file handles
|
|
277
|
+
for fh in file_handles:
|
|
278
|
+
fh.close()
|
|
279
|
+
|
|
280
|
+
if response.status_code == 200:
|
|
281
|
+
result = response.json()
|
|
282
|
+
urls = [item["url"] for item in result if "url" in item]
|
|
283
|
+
names = [item.get("name", "?") for item in result]
|
|
284
|
+
logger.info(f"Uploaded {len(urls)} file(s): {names}")
|
|
285
|
+
return True, urls, f"Uploaded {len(urls)} file(s)"
|
|
286
|
+
else:
|
|
287
|
+
error_msg = f"Upload failed: HTTP {response.status_code}"
|
|
288
|
+
logger.error(error_msg)
|
|
289
|
+
return False, [], error_msg
|
|
290
|
+
|
|
291
|
+
except Exception as e:
|
|
292
|
+
error_msg = f"Upload error: {e}"
|
|
293
|
+
logger.error(error_msg)
|
|
294
|
+
return False, [], error_msg
|
|
295
|
+
|
|
296
|
+
def run_action_async(self, action_id: str, user_input: str, action_result_id: Optional[str] = None, urls: Optional[list[str]] = None, metadata: Optional[dict] = None) -> tuple[bool, Optional[str], str]:
|
|
233
297
|
"""Run an action asynchronously and return the run ID.
|
|
234
298
|
|
|
235
299
|
Args:
|
|
236
300
|
action_id: ID of the action to run
|
|
237
301
|
user_input: User input text for the action
|
|
238
302
|
action_result_id: Optional ID to continue an existing conversation
|
|
303
|
+
urls: Optional list of file URLs to attach
|
|
304
|
+
metadata: Optional metadata dict to merge into the DataBlock
|
|
239
305
|
|
|
240
306
|
Returns:
|
|
241
307
|
Tuple of (success, run_id, message)
|
|
242
308
|
"""
|
|
243
309
|
try:
|
|
244
310
|
if action_result_id:
|
|
245
|
-
logger.info(f"Running action {action_id} with input: {user_input}, continuing run_id: {action_result_id}")
|
|
311
|
+
logger.info(f"Running action {action_id} with input: {user_input}, urls: {urls}, continuing run_id: {action_result_id}")
|
|
246
312
|
else:
|
|
247
|
-
logger.info(f"Running action {action_id} with input: {user_input}")
|
|
313
|
+
logger.info(f"Running action {action_id} with input: {user_input}, urls: {urls}")
|
|
248
314
|
|
|
249
315
|
if not isinstance(self.client, AuthenticatedClient):
|
|
250
316
|
return False, None, "Authentication required"
|
|
251
317
|
|
|
252
318
|
# Create input body - DataBlock structure
|
|
319
|
+
default_metadata = {
|
|
320
|
+
"process_url_in_text": False,
|
|
321
|
+
"process_attachment_url": True,
|
|
322
|
+
"attachment_requested": 0
|
|
323
|
+
}
|
|
324
|
+
if metadata:
|
|
325
|
+
default_metadata.update(metadata)
|
|
326
|
+
|
|
253
327
|
input_data = BodyAsyncRunActionV1ActionsIdAsyncRunPostInput()
|
|
254
328
|
input_data.additional_properties = {
|
|
255
329
|
"text": user_input,
|
|
256
|
-
"urls": [],
|
|
257
|
-
"metadata":
|
|
258
|
-
"process_url_in_text": False,
|
|
259
|
-
"process_attachment_url": True,
|
|
260
|
-
"attachment_requested": 0
|
|
261
|
-
}
|
|
330
|
+
"urls": urls or [],
|
|
331
|
+
"metadata": default_metadata
|
|
262
332
|
}
|
|
263
333
|
|
|
264
334
|
body = BodyAsyncRunActionV1ActionsIdAsyncRunPost(input_=input_data)
|
|
335
|
+
logger.info(f"Request body: {body.to_dict()}")
|
|
265
336
|
|
|
266
337
|
# Import UNSET for optional parameter
|
|
267
338
|
from autobots_client.types import UNSET
|
|
@@ -296,6 +296,26 @@ Session:
|
|
|
296
296
|
/continue <run_id> Continue an existing run
|
|
297
297
|
/new Start new conversation
|
|
298
298
|
/status Show current action & run
|
|
299
|
+
/cancel Cancel active request
|
|
300
|
+
|
|
301
|
+
Files:
|
|
302
|
+
/upload <path> [...] Upload file(s), attach to next message
|
|
303
|
+
/files Show pending file uploads
|
|
304
|
+
/clear-files Clear pending file uploads
|
|
305
|
+
|
|
306
|
+
Metadata (persistent across runs):
|
|
307
|
+
/metadata Show current metadata
|
|
308
|
+
/metadata set <k> <v> Set a metadata field
|
|
309
|
+
/metadata remove <k> Remove a metadata field
|
|
310
|
+
/metadata clear Clear all metadata
|
|
311
|
+
|
|
312
|
+
Runtime:
|
|
313
|
+
/runtime status Show runtime status
|
|
314
|
+
/runtime start Start runtime
|
|
315
|
+
/runtime stop Stop runtime
|
|
316
|
+
/runtime restart Restart runtime
|
|
317
|
+
/runtime list List all runtimes
|
|
318
|
+
/runtime logs Show runtime logs
|
|
299
319
|
|
|
300
320
|
Query:
|
|
301
321
|
/actions list [--name NAME] [--limit N]
|
|
@@ -14,10 +14,10 @@ class ConfigManager:
|
|
|
14
14
|
"""Initialize config manager.
|
|
15
15
|
|
|
16
16
|
Args:
|
|
17
|
-
config_path: Path to config file, defaults to ~/.
|
|
17
|
+
config_path: Path to config file, defaults to ~/.kiwi/config.json
|
|
18
18
|
"""
|
|
19
19
|
if config_path is None:
|
|
20
|
-
config_path = Path.home() / ".
|
|
20
|
+
config_path = Path.home() / ".kiwi" / "config.json"
|
|
21
21
|
|
|
22
22
|
self.config_path = config_path
|
|
23
23
|
self._config: Optional[AppConfig] = None
|
|
@@ -16,7 +16,7 @@ def setup_logging(log_level: str = "INFO", log_file: str = "kiwi_tui.log") -> No
|
|
|
16
16
|
logger.remove()
|
|
17
17
|
|
|
18
18
|
# Only log to file, not to console (to avoid interference with Textual UI)
|
|
19
|
-
log_path = Path.home() / ".
|
|
19
|
+
log_path = Path.home() / ".kiwi" / log_file
|
|
20
20
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
21
21
|
|
|
22
22
|
logger.add(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Shared helpers for managing per-directory kiwi-runtime processes.
|
|
2
2
|
|
|
3
3
|
Each working directory gets its own runtime, identified by a hash of the
|
|
4
|
-
absolute path. State is stored under ~/.
|
|
4
|
+
absolute path. State is stored under ~/.kiwi/runtimes/<key>/:
|
|
5
5
|
pid — PID of the running process
|
|
6
6
|
log — stdout/stderr of the runtime
|
|
7
7
|
cwd — the working directory this runtime is scoped to
|
|
@@ -13,7 +13,7 @@ import signal
|
|
|
13
13
|
import time
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
-
RUNTIMES_DIR = Path.home() / ".
|
|
16
|
+
RUNTIMES_DIR = Path.home() / ".kiwi" / "runtimes"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def runtime_key(cwd: str | None = None) -> str:
|
|
@@ -978,7 +978,7 @@ def main():
|
|
|
978
978
|
connect_parser.add_argument(
|
|
979
979
|
"--token",
|
|
980
980
|
default=None,
|
|
981
|
-
help="Access token to skip login (used by
|
|
981
|
+
help="Access token to skip login (used by kiwi for shared auth)",
|
|
982
982
|
)
|
|
983
983
|
connect_parser.add_argument(
|
|
984
984
|
"--scope",
|
|
@@ -8,11 +8,11 @@ from textual.app import App
|
|
|
8
8
|
from textual.binding import Binding
|
|
9
9
|
from loguru import logger
|
|
10
10
|
|
|
11
|
-
from .logger import setup_logging
|
|
12
|
-
from .config import ConfigManager
|
|
13
|
-
from .client import AutobotsClientWrapper
|
|
14
|
-
from .auth import TokenManager
|
|
15
|
-
from
|
|
11
|
+
from kiwi_cli.logger import setup_logging
|
|
12
|
+
from kiwi_cli.config import ConfigManager
|
|
13
|
+
from kiwi_cli.client import AutobotsClientWrapper
|
|
14
|
+
from kiwi_cli.auth import TokenManager
|
|
15
|
+
from kiwi_cli import runtime_manager
|
|
16
16
|
from .screens import LoginScreen, DashboardScreen, AutobotsScreen, ActionsScreen, RuntimeLogsScreen
|
|
17
17
|
|
|
18
18
|
|
|
@@ -54,6 +54,7 @@ class AutobotsTUI(App):
|
|
|
54
54
|
Binding("ctrl+c", "quit", "Quit", show=True),
|
|
55
55
|
Binding("ctrl+d", "toggle_dark", "Toggle Dark Mode", show=False),
|
|
56
56
|
Binding("ctrl+r", "runtime_logs", "Runtime Logs", show=True),
|
|
57
|
+
Binding("ctrl+k", "toggle_runtime", "Stop/Start Runtime", show=True),
|
|
57
58
|
Binding("ctrl+l", "logout", "Logout", show=False),
|
|
58
59
|
]
|
|
59
60
|
|
|
@@ -267,9 +268,12 @@ class AutobotsTUI(App):
|
|
|
267
268
|
|
|
268
269
|
def _stop_runtime(self) -> None:
|
|
269
270
|
"""Explicitly stop the kiwi-runtime for the current directory."""
|
|
271
|
+
# Drain any remaining output before closing
|
|
272
|
+
self._drain_kiwi_output()
|
|
270
273
|
runtime_manager.stop_runtime()
|
|
271
274
|
self._kiwi_pid = None
|
|
272
275
|
self._close_log_tail()
|
|
276
|
+
self._kiwi_log_lines.append("--- Runtime disconnected ---")
|
|
273
277
|
|
|
274
278
|
# ------------------------------------------------------------------
|
|
275
279
|
# Periodic token refresh
|
|
@@ -320,6 +324,32 @@ class AutobotsTUI(App):
|
|
|
320
324
|
|
|
321
325
|
# ------------------------------------------------------------------
|
|
322
326
|
|
|
327
|
+
def action_toggle_runtime(self) -> None:
|
|
328
|
+
"""Toggle the kiwi-runtime: stop if running, start if stopped."""
|
|
329
|
+
if self._kiwi_pid:
|
|
330
|
+
self._stop_runtime()
|
|
331
|
+
logger.info("Runtime stopped by user")
|
|
332
|
+
self.notify("Runtime stopped (Ctrl+K to start)", severity="information")
|
|
333
|
+
else:
|
|
334
|
+
self._start_kiwi_session()
|
|
335
|
+
if self._kiwi_pid:
|
|
336
|
+
logger.info("Runtime started by user")
|
|
337
|
+
self.notify("Runtime started", severity="information")
|
|
338
|
+
else:
|
|
339
|
+
self.notify("Failed to start runtime", severity="error")
|
|
340
|
+
|
|
341
|
+
def action_restart_runtime(self) -> None:
|
|
342
|
+
"""Restart the kiwi-runtime for the current directory."""
|
|
343
|
+
if self._kiwi_pid:
|
|
344
|
+
self._stop_runtime()
|
|
345
|
+
logger.info("Runtime stopped for restart")
|
|
346
|
+
self._start_kiwi_session()
|
|
347
|
+
if self._kiwi_pid:
|
|
348
|
+
logger.info("Runtime restarted by user")
|
|
349
|
+
self.notify("Runtime restarted", severity="information")
|
|
350
|
+
else:
|
|
351
|
+
self.notify("Failed to restart runtime", severity="error")
|
|
352
|
+
|
|
323
353
|
def action_runtime_logs(self) -> None:
|
|
324
354
|
"""Show the runtime log stream."""
|
|
325
355
|
self.push_screen("runtime_logs")
|
|
@@ -7,8 +7,8 @@ from textual.containers import Container, Horizontal, Vertical, ScrollableContai
|
|
|
7
7
|
from textual.widgets import Header, Footer, Static, DataTable, Button, Input, Label
|
|
8
8
|
from loguru import logger
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
10
|
+
from kiwi_cli.models import Action, ActionExecution, ActionStatus
|
|
11
|
+
from kiwi_tui.widgets import StatusBadge, ActionButton, InfoPanel
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ActionsScreen(Screen):
|
|
@@ -7,8 +7,8 @@ from textual.containers import Container, Vertical, Horizontal
|
|
|
7
7
|
from textual.widgets import Header, Footer, Static, DataTable, Button
|
|
8
8
|
from loguru import logger
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
10
|
+
from kiwi_cli.models import Autobot, AutobotType
|
|
11
|
+
from kiwi_tui.widgets import StatusBadge, ActionButton
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class AutobotsScreen(Screen):
|