kiwi-code 0.0.4__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.
Files changed (33) hide show
  1. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/.gitignore +1 -1
  2. kiwi_code-0.0.6/CLAUDE.md +79 -0
  3. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/Makefile +6 -1
  4. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/PKG-INFO +7 -7
  5. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/README.md +4 -4
  6. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/pyproject.toml +5 -5
  7. kiwi_code-0.0.6/src/kiwi_cli/__init__.py +3 -0
  8. {kiwi_code-0.0.4/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/auth.py +2 -2
  9. {kiwi_code-0.0.4/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/cli.py +1 -1
  10. {kiwi_code-0.0.4/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/client.py +80 -9
  11. {kiwi_code-0.0.4/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/commands.py +20 -0
  12. {kiwi_code-0.0.4/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/config.py +2 -2
  13. {kiwi_code-0.0.4/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/logger.py +1 -1
  14. {kiwi_code-0.0.4/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/runtime_manager.py +48 -2
  15. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_runtime/main.py +204 -22
  16. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/main.py +116 -11
  17. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/screens/actions.py +2 -2
  18. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/screens/autobots.py +2 -2
  19. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/screens/dashboard.py +531 -11
  20. kiwi_code-0.0.6/src/kiwi_tui/screens/file_browser.py +182 -0
  21. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/screens/login.py +3 -2
  22. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/uv.lock +567 -568
  23. kiwi_code-0.0.4/poetry.lock +0 -1570
  24. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/.github/workflows/publish.yml +0 -0
  25. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/.python-version +0 -0
  26. {kiwi_code-0.0.4/src/kiwi_tui → kiwi_code-0.0.6/src/kiwi_cli}/models.py +0 -0
  27. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_runtime/__init__.py +0 -0
  28. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_runtime/__main__.py +0 -0
  29. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/__init__.py +0 -0
  30. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/screens/__init__.py +0 -0
  31. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/screens/runtime_logs.py +0 -0
  32. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/src/kiwi_tui/widgets.py +0 -0
  33. {kiwi_code-0.0.4 → kiwi_code-0.0.6}/test_hello.py +0 -0
@@ -29,4 +29,4 @@ Thumbs.db
29
29
  *.log
30
30
 
31
31
  # Local config & tokens
32
- .autobots-tui/
32
+ .kiwi/
@@ -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,4 +1,4 @@
1
- .PHONY: run dev cli runtime test-hello install sync clean help
1
+ .PHONY: run dev serve cli runtime test-hello install sync clean help
2
2
 
3
3
  # Run the Kiwi TUI application
4
4
  run:
@@ -8,6 +8,10 @@ run:
8
8
  dev:
9
9
  uv run --dev kiwi
10
10
 
11
+ # Serve the Kiwi TUI in the browser via textual serve (e.g. make serve PORT=8566)
12
+ serve:
13
+ uv run textual serve --port $(or $(PORT),8566) -c "python -m kiwi_tui.main"
14
+
11
15
  # Run the Kiwi CLI
12
16
  cli:
13
17
  uv run kiwicli
@@ -53,6 +57,7 @@ help:
53
57
  @echo "Commands:"
54
58
  @echo " run - Run the Kiwi TUI application (kiwi)"
55
59
  @echo " dev - Run TUI in development mode (with live reload)"
60
+ @echo " serve - Serve the TUI in the browser (PORT=8566 make serve)"
56
61
  @echo " cli - Run the Kiwi CLI (kiwicli)"
57
62
  @echo " runtime - Run the Kiwi Runtime agent (kiwi-runtime)"
58
63
  @echo " e.g. make runtime ARGS=\"connect --server app\""
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiwi-code
3
- Version: 0.0.4
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: MIT
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 :: OSI Approved :: MIT 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 `~/.autobots-tui/tokens.json`, so logging in via one works for the other.
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 `~/.autobots-tui/config.json`:
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 `~/.autobots-tui/autobots_tui.log`.
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
- MIT
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 `~/.autobots-tui/tokens.json`, so logging in via one works for the other.
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 `~/.autobots-tui/config.json`:
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 `~/.autobots-tui/autobots_tui.log`.
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
- MIT
208
+ Proprietary. All rights reserved.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kiwi-code"
3
- version = "0.0.4"
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 = "MIT" }
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 :: OSI Approved :: MIT 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 = "kiwi_tui.cli:cli"
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"]
@@ -0,0 +1,3 @@
1
+ """Kiwi CLI - command-line interface and shared infrastructure modules."""
2
+
3
+ __version__ = "0.1.0"
@@ -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 ~/.autobots-tui/tokens.json
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() / ".autobots-tui" / "tokens.json"
21
+ token_path = Path.home() / ".kiwi" / "tokens.json"
22
22
 
23
23
  self.token_path = token_path
24
24
  self._tokens: Optional[AuthTokens] = None
@@ -231,7 +231,7 @@ def whoami():
231
231
  @app.command()
232
232
  def tui():
233
233
  """Launch the interactive TUI."""
234
- from .main import main
234
+ from kiwi_tui.main import main
235
235
  main()
236
236
 
237
237
 
@@ -229,39 +229,110 @@ class AutobotsClientWrapper:
229
229
  logger.error(error_msg)
230
230
  return False, None, error_msg
231
231
 
232
- def run_action_async(self, action_id: str, user_input: str, action_result_id: Optional[str] = None) -> tuple[bool, Optional[str], str]:
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 ~/.autobots-tui/config.json
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() / ".autobots-tui" / "config.json"
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() / ".autobots-tui" / log_file
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 ~/.autobots-tui/runtimes/<key>/:
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() / ".autobots-tui" / "runtimes"
16
+ RUNTIMES_DIR = Path.home() / ".kiwi" / "runtimes"
17
17
 
18
18
 
19
19
  def runtime_key(cwd: str | None = None) -> str:
@@ -58,6 +58,52 @@ def log_path(cwd: str | None = None) -> Path:
58
58
  return runtime_dir(cwd) / "log"
59
59
 
60
60
 
61
+ def token_path(cwd: str | None = None) -> Path:
62
+ """Return the shared token file path for a directory's runtime."""
63
+ return runtime_dir(cwd) / "token"
64
+
65
+
66
+ def refresh_token_path(cwd: str | None = None) -> Path:
67
+ """Return the shared refresh token file path for a directory's runtime."""
68
+ return runtime_dir(cwd) / "refresh_token"
69
+
70
+
71
+ def save_token(token: str, cwd: str | None = None) -> None:
72
+ """Write a refreshed access token so the runtime can pick it up."""
73
+ tp = token_path(cwd)
74
+ tp.write_text(token)
75
+ tp.chmod(0o600)
76
+
77
+
78
+ def save_refresh_token(token: str, cwd: str | None = None) -> None:
79
+ """Write the refresh token so the runtime can refresh on its own."""
80
+ tp = refresh_token_path(cwd)
81
+ tp.write_text(token)
82
+ tp.chmod(0o600)
83
+
84
+
85
+ def read_token(cwd: str | None = None) -> str | None:
86
+ """Read the shared access token (returns None if missing)."""
87
+ tp = token_path(cwd)
88
+ if not tp.exists():
89
+ return None
90
+ try:
91
+ return tp.read_text().strip() or None
92
+ except OSError:
93
+ return None
94
+
95
+
96
+ def read_refresh_token(cwd: str | None = None) -> str | None:
97
+ """Read the shared refresh token (returns None if missing)."""
98
+ tp = refresh_token_path(cwd)
99
+ if not tp.exists():
100
+ return None
101
+ try:
102
+ return tp.read_text().strip() or None
103
+ except OSError:
104
+ return None
105
+
106
+
61
107
  def stop_runtime(cwd: str | None = None) -> bool:
62
108
  """Stop the runtime for a directory. Returns True if a process was stopped."""
63
109
  pid = get_running_pid(cwd)