kiwi-code 0.0.434__tar.gz → 0.0.436__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 (59) hide show
  1. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/PKG-INFO +1 -1
  2. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/pyproject.toml +1 -1
  3. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/auth.py +22 -16
  4. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/client.py +28 -6
  5. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/models.py +16 -13
  6. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/main.py +72 -3
  7. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/dashboard.py +34 -1
  8. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/runtime_cleanup.py +5 -0
  9. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_tui_headless.py +0 -49
  10. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/uv.lock +1 -1
  11. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/.github/workflows/publish.yml +0 -0
  12. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/.github/workflows/test.yml +0 -0
  13. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/.gitignore +0 -0
  14. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/.python-version +0 -0
  15. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/CLAUDE.md +0 -0
  16. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/Makefile +0 -0
  17. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/README.md +0 -0
  18. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/__init__.py +0 -0
  19. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/checkpoints.py +0 -0
  20. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/cli.py +0 -0
  21. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/commands.py +0 -0
  22. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/logger.py +0 -0
  23. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/runtime_manager.py +0 -0
  24. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/server.py +0 -0
  25. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_cli/terminal_mode.py +0 -0
  26. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_runtime/__init__.py +0 -0
  27. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_runtime/__main__.py +0 -0
  28. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_runtime/main.py +0 -0
  29. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/__init__.py +0 -0
  30. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/inline_file_picker.py +0 -0
  31. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/random_words.py +0 -0
  32. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/runtime_agent.py +0 -0
  33. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/__init__.py +0 -0
  34. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/attach_content.py +0 -0
  35. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/command_result.py +0 -0
  36. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/file_browser.py +0 -0
  37. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/help.py +0 -0
  38. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/id_picker.py +0 -0
  39. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/login.py +0 -0
  40. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/runtime_logs.py +0 -0
  41. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/screens/slash_picker.py +0 -0
  42. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/slash_commands.py +0 -0
  43. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/status_words.py +0 -0
  44. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/widgets.py +0 -0
  45. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/src/kiwi_tui/worktrees.py +0 -0
  46. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/test_hello.py +0 -0
  47. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/__init__.py +0 -0
  48. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/conftest.py +0 -0
  49. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_checkpoints.py +0 -0
  50. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_cli_help.py +0 -0
  51. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_imports.py +0 -0
  52. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_reexec_kiwi.py +0 -0
  53. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_runtime_log_trimming.py +0 -0
  54. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_slash_commands.py +0 -0
  55. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_terminal_mode.py +0 -0
  56. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_tokens.py +0 -0
  57. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_tui_interactive_runtime.py +0 -0
  58. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_tui_palette.py +0 -0
  59. {kiwi_code-0.0.434 → kiwi_code-0.0.436}/tests/test_worktrees.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiwi-code
3
- Version: 0.0.434
3
+ Version: 0.0.436
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kiwi-code"
3
- version = "0.0.434"
3
+ version = "0.0.436"
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.11,<4.0"
@@ -6,6 +6,9 @@ import time
6
6
  from contextlib import contextmanager
7
7
  from pathlib import Path
8
8
  from typing import Optional
9
+ from loguru import logger
10
+ from .models import AuthTokens
11
+
9
12
  def _lock_path_for(token_path: Path) -> Path:
10
13
  return token_path.with_suffix(".lock")
11
14
 
@@ -51,11 +54,6 @@ def _atomic_write_json(path: Path, data: dict) -> None:
51
54
  if sys.platform != "win32":
52
55
  path.chmod(0o600)
53
56
 
54
- from loguru import logger
55
-
56
- from .models import AuthTokens
57
-
58
-
59
57
 
60
58
  class TokenManager:
61
59
  """Manages authentication tokens with secure storage."""
@@ -102,17 +100,25 @@ class TokenManager:
102
100
  logger.debug("No saved tokens found")
103
101
  return None
104
102
 
105
- try:
106
- with open(self.token_path, "r") as f:
107
- data = json.load(f)
108
-
109
- self._tokens = AuthTokens(**data)
110
- logger.info("Authentication tokens loaded")
111
- return self._tokens
112
- except Exception as e:
113
- logger.error(f"Failed to load tokens: {e}")
114
- return None
115
-
103
+ last_err: Exception | None = None
104
+ for attempt in range(5):
105
+ try:
106
+ with open(self.token_path, "r", encoding="utf-8") as f:
107
+ data = json.load(f)
108
+ self._tokens = AuthTokens(**data)
109
+ logger.info("Authentication tokens loaded")
110
+ return self._tokens
111
+ except Exception as e:
112
+ last_err = e
113
+ if attempt < 4:
114
+ time.sleep(0.05)
115
+ continue
116
+ logger.error(f"Failed to load tokens: {e}")
117
+ return None
118
+ if last_err:
119
+ logger.error(f"Failed to load tokens: {last_err}")
120
+ return None
121
+
116
122
  def clear_tokens(self) -> None:
117
123
  """Clear stored tokens under the same lock used for refresh/write flows."""
118
124
  with self.file_lock():
@@ -71,7 +71,7 @@ class AutobotsClientWrapper:
71
71
  self.access_token = access_token
72
72
  self.client = AuthenticatedClient(
73
73
  base_url=self.base_url,
74
- token=access_token,
74
+ token=self.access_token,
75
75
  raise_on_unexpected_status=False,
76
76
  )
77
77
  logger.info("Client updated with new access token")
@@ -598,6 +598,17 @@ class AutobotsClientWrapper:
598
598
  logger.error(f"Error processing SSE data: {e}")
599
599
  return False
600
600
 
601
+ pending_blank = False
602
+
603
+ def _is_field_line(s: str) -> bool:
604
+ return (
605
+ s.startswith("data:")
606
+ or s.startswith(":")
607
+ or s.startswith("event:")
608
+ or s.startswith("id:")
609
+ or s.startswith("retry:")
610
+ )
611
+
601
612
  async for chunk in response.aiter_bytes():
602
613
  text_chunk = decoder.decode(chunk, final=False)
603
614
  buffer += text_chunk
@@ -606,14 +617,26 @@ class AutobotsClientWrapper:
606
617
  line, buffer = buffer.split("\n", 1)
607
618
  line = line.rstrip("\r")
608
619
 
609
- # SSE event boundary (blank line)
620
+ if pending_blank:
621
+ if line == "":
622
+ pending_blank = False
623
+ if _flush_event():
624
+ return
625
+ continue
626
+ if _is_field_line(line):
627
+ pending_blank = False
628
+ if _flush_event():
629
+ return
630
+ else:
631
+ event_data_lines.append("")
632
+ pending_blank = False
633
+
610
634
  if line == "":
611
- if _flush_event():
612
- return
635
+ if event_data_lines:
636
+ pending_blank = True
613
637
  continue
614
638
 
615
639
  if line.startswith(":"):
616
- # keep-alive comments
617
640
  continue
618
641
 
619
642
  if line.startswith("data:"):
@@ -626,7 +649,6 @@ class AutobotsClientWrapper:
626
649
  if event_data_lines:
627
650
  event_data_lines.append(line)
628
651
 
629
- # Flush any remaining bytes in decoder + any pending event data.
630
652
  decoder.decode(b"", final=True)
631
653
  _flush_event()
632
654
  except Exception as e:
@@ -65,14 +65,7 @@ class AuthTokens(BaseModel):
65
65
  token_type: str = "Bearer"
66
66
  expires_at: Optional[datetime] = None
67
67
 
68
- def is_expired(self) -> bool:
69
- """Check if access token is expired.
70
-
71
- Notes:
72
- - Prefer `expires_at` when present.
73
- - If `expires_at` is missing (common when the backend doesn't return expires_in),
74
- fall back to the JWT `exp` claim (if the access token is a JWT).
75
- """
68
+ def effective_expires_at(self) -> datetime | None:
76
69
  def _jwt_exp(token: str) -> datetime | None:
77
70
  try:
78
71
  parts = token.split(".")
@@ -93,16 +86,26 @@ class AuthTokens(BaseModel):
93
86
  jwt_expires_at = _jwt_exp(self.access_token)
94
87
  effective_expires_at = self.expires_at or jwt_expires_at
95
88
  if self.expires_at and jwt_expires_at:
96
- # Be conservative if they disagree.
97
89
  effective_expires_at = min(self.expires_at, jwt_expires_at)
90
+ return effective_expires_at
98
91
 
99
- if not effective_expires_at:
100
- # No expiry info; assume valid (server will decide).
92
+ def is_expiring_within(self, seconds: int) -> bool:
93
+ exp = self.effective_expires_at()
94
+ if not exp:
95
+ return False
96
+ try:
97
+ return datetime.now() >= (exp - timedelta(seconds=max(0, int(seconds))))
98
+ except Exception:
101
99
  return False
102
100
 
101
+ def is_expired(self) -> bool:
102
+ """Check if access token is expired (with a small safety buffer)."""
103
+ exp = self.effective_expires_at()
104
+ if not exp:
105
+ # No expiry info; assume valid (server will decide).
106
+ return False
103
107
  # Add 60 second buffer before expiry
104
- return datetime.now() >= (effective_expires_at - timedelta(seconds=60))
105
-
108
+ return datetime.now() >= (exp - timedelta(seconds=60))
106
109
 
107
110
  class LoginCredentials(BaseModel):
108
111
  """Login credentials."""
@@ -602,6 +602,11 @@ class AutobotsTUI(App):
602
602
  self._token_refresh_timer = self.set_interval(
603
603
  interval_min * 60, self._refresh_token_if_needed
604
604
  )
605
+ try:
606
+ self._refresh_token_if_needed(force=False)
607
+ except Exception:
608
+ pass
609
+
605
610
  # Write current tokens to shared files so runtime can read them
606
611
  # if self._kiwi_token:
607
612
  # runtime_manager.save_token(self._kiwi_token)
@@ -609,6 +614,38 @@ class AutobotsTUI(App):
609
614
  # if tokens and tokens.refresh_token:
610
615
  # runtime_manager.save_refresh_token(tokens.refresh_token)
611
616
 
617
+ def _sync_autobots_client_from_disk(self) -> bool:
618
+ try:
619
+ with self.token_manager.file_lock():
620
+ tokens = self.token_manager.load_tokens()
621
+ except Exception:
622
+ tokens = None
623
+ access = getattr(tokens, "access_token", None) if tokens else None
624
+ try:
625
+ access_str = str(access)
626
+ log_full = os.environ.get("KIWI_LOG_TOKENS") == "1"
627
+ if log_full:
628
+ logger.debug(f"Loaded access token from disk: {access_str}")
629
+ else:
630
+ redacted = access_str
631
+ if len(access_str) > 24:
632
+ redacted = f"{access_str[:12]}...{access_str[-6:]}"
633
+ logger.debug(
634
+ f"Loaded access token from disk: {redacted} (len={len(access_str)})"
635
+ )
636
+ except Exception:
637
+ # Best-effort only; never block requests on logging.
638
+ pass
639
+
640
+ if not access:
641
+ return False
642
+ try:
643
+ self.autobots_client.update_token(str(access))
644
+ return True
645
+ except Exception:
646
+ return False
647
+
648
+
612
649
  def _refresh_token_if_needed(self, force: bool = False) -> bool:
613
650
  """Synchronize auth state from disk and refresh when needed.
614
651
 
@@ -623,7 +660,28 @@ class AutobotsTUI(App):
623
660
  logger.debug("No tokens to refresh")
624
661
  return False
625
662
 
626
- if not force and not tokens.is_expired():
663
+ expired = False
664
+ try:
665
+ expired = bool(tokens.is_expired())
666
+ except Exception:
667
+ expired = False
668
+
669
+ threshold_sec = 60
670
+ try:
671
+ threshold_sec = max(60, int(self.config.refresh_interval) * 60 + 120)
672
+ except Exception:
673
+ threshold_sec = 360
674
+
675
+ expiring_soon = False
676
+ try:
677
+ fn = getattr(tokens, "is_expiring_within", None)
678
+ expiring_soon = bool(fn and fn(threshold_sec))
679
+ except Exception:
680
+ expiring_soon = False
681
+
682
+ needs_refresh = bool(force or expired or expiring_soon)
683
+
684
+ if not needs_refresh:
627
685
  logger.debug("Token on disk is valid; synchronizing in-memory auth state")
628
686
  if getattr(tokens, "access_token", None):
629
687
  self.autobots_client.update_token(tokens.access_token)
@@ -631,8 +689,19 @@ class AutobotsTUI(App):
631
689
  return True
632
690
 
633
691
  if not getattr(tokens, "refresh_token", None):
634
- logger.warning("No refresh token available; cannot refresh access token")
635
- return False
692
+ if force or expired:
693
+ logger.warning("No refresh token available; cannot refresh access token")
694
+ return False
695
+ logger.warning(
696
+ f"Access token expires within {threshold_sec}s but no refresh token; continuing with existing token"
697
+ )
698
+ if getattr(tokens, "access_token", None):
699
+ self.autobots_client.update_token(tokens.access_token)
700
+ self._kiwi_token = tokens.access_token
701
+ return True
702
+
703
+ if expiring_soon and not expired and not force:
704
+ logger.info(f"Access token expires within {threshold_sec}s; refreshing early")
636
705
 
637
706
  if getattr(tokens, "access_token", None):
638
707
  self.autobots_client.update_token(tokens.access_token)
@@ -627,6 +627,11 @@ class DashboardScreen(Screen):
627
627
  from autobots_client.api.actions import get_action_v1_actions_id_get
628
628
 
629
629
  requested_action_id = self.current_action_id
630
+ try:
631
+ self.app._sync_autobots_client_from_disk()
632
+ except Exception:
633
+ pass
634
+
630
635
  api_client = getattr(getattr(self.app, "autobots_client", None), "client", None)
631
636
  if api_client is None:
632
637
  return
@@ -855,6 +860,10 @@ class DashboardScreen(Screen):
855
860
  so the Textual UI stays responsive and can render the loading indicator.
856
861
  """
857
862
  try:
863
+ try:
864
+ self.app._sync_autobots_client_from_disk()
865
+ except Exception:
866
+ pass
858
867
  parts = command.strip().split()
859
868
  if not parts:
860
869
  return
@@ -1821,6 +1830,11 @@ class DashboardScreen(Screen):
1821
1830
  self.add_message("Error: Client not initialized", "error")
1822
1831
  return None
1823
1832
 
1833
+ try:
1834
+ self.app._sync_autobots_client_from_disk()
1835
+ except Exception:
1836
+ pass
1837
+
1824
1838
  api_client = self.app.autobots_client.client
1825
1839
  if not isinstance(api_client, AuthenticatedClient):
1826
1840
  self.add_message("Error: Not authenticated", "error")
@@ -1834,6 +1848,11 @@ class DashboardScreen(Screen):
1834
1848
  def _get_api_client_for_command(self, title: str):
1835
1849
  """Get API client for slash commands without writing errors into chat."""
1836
1850
  from autobots_client import AuthenticatedClient
1851
+ try:
1852
+ self.app._sync_autobots_client_from_disk()
1853
+ except Exception:
1854
+ pass
1855
+
1837
1856
 
1838
1857
  if not hasattr(self.app, 'autobots_client'):
1839
1858
  self._show_command_result(title, "Error: Client not initialized", is_error=True)
@@ -3214,7 +3233,11 @@ class DashboardScreen(Screen):
3214
3233
  pass
3215
3234
 
3216
3235
  async def _run_action_worker(self, user_input: str) -> None:
3217
- """Async worker: send the action request then stream results."""
3236
+ try:
3237
+ self.app._sync_autobots_client_from_disk()
3238
+ except Exception:
3239
+ pass
3240
+
3218
3241
  client = self.app.autobots_client
3219
3242
 
3220
3243
  checkpoint_run_dir: Path | None = None
@@ -3402,6 +3425,11 @@ class DashboardScreen(Screen):
3402
3425
 
3403
3426
  async def _cache_run_name_worker(self, run_id: str) -> None:
3404
3427
  """Best-effort: cache the run's name in ~/.kiwi/runtimes so the quit prompt can show it."""
3428
+ try:
3429
+ self.app._sync_autobots_client_from_disk()
3430
+ except Exception:
3431
+ pass
3432
+
3405
3433
  try:
3406
3434
  wrapper = self.app.autobots_client
3407
3435
  except Exception:
@@ -3449,6 +3477,11 @@ class DashboardScreen(Screen):
3449
3477
  Runs SSE streaming and result-polling concurrently. Whichever
3450
3478
  detects a terminal state first (success *or* error) wins.
3451
3479
  """
3480
+ try:
3481
+ self.app._sync_autobots_client_from_disk()
3482
+ except Exception:
3483
+ pass
3484
+
3452
3485
  client = self.app.autobots_client
3453
3486
  got_final_result = False
3454
3487
 
@@ -244,6 +244,11 @@ class RuntimeCleanupScreen(ModalScreen[list[int]]):
244
244
  if not by_run:
245
245
  return
246
246
 
247
+ try:
248
+ self.app._sync_autobots_client_from_disk()
249
+ except Exception:
250
+ pass
251
+
247
252
  wrapper = getattr(self.app, "autobots_client", None)
248
253
  if not wrapper:
249
254
  return
@@ -852,55 +852,6 @@ async def test_tui_streaming_requests_autofollow_when_viewport_is_near_bottom(
852
852
  )
853
853
  await pilot.pause()
854
854
  assert calls == [{"after_refresh": True}]
855
- @pytest.mark.asyncio
856
- async def test_tui_streaming_does_not_force_scroll_when_user_reads_older_messages(
857
- isolated_home: Path,
858
- ) -> None:
859
- """Streaming updates should not yank the viewport back to the bottom."""
860
- tokens_path = isolated_home / ".kiwi" / "tokens.json"
861
- tokens_path.parent.mkdir(parents=True, exist_ok=True)
862
- tokens_path.write_text(
863
- json.dumps(
864
- {
865
- "access_token": "test-access-token",
866
- "refresh_token": "test-refresh-token",
867
- "token_type": "Bearer",
868
- "expires_at": None,
869
- }
870
- ),
871
- encoding="utf-8",
872
- )
873
-
874
- from kiwi_tui.main import AutobotsTUI
875
-
876
- app = AutobotsTUI()
877
- async with app.run_test(size=(80, 24)) as pilot:
878
- await pilot.pause()
879
- assert type(app.screen).__name__ == "DashboardScreen"
880
- screen = app.screen
881
-
882
- calls: list[dict] = []
883
- monkeypatch = pytest.MonkeyPatch()
884
- monkeypatch.setattr(screen, "_messages_is_near_bottom", lambda *args, **kwargs: False)
885
- monkeypatch.setattr(
886
- screen,
887
- "_scroll_messages_to_end",
888
- lambda *args, **kwargs: calls.append(dict(kwargs)),
889
- )
890
- try:
891
- widget = screen.update_streaming_message({"blocks": [{"text": "stream start"}]})
892
- await pilot.pause()
893
- assert widget is not None
894
- assert calls == []
895
-
896
- screen.update_streaming_message(
897
- {"blocks": [{"text": "\n".join(f"stream line {i}" for i in range(60))}]},
898
- widget,
899
- )
900
- await pilot.pause()
901
- assert calls == []
902
- finally:
903
- monkeypatch.undo()
904
855
 
905
856
  @pytest.mark.asyncio
906
857
  async def test_tui_quits_cleanly(isolated_home: Path) -> None:
@@ -397,7 +397,7 @@ wheels = [
397
397
 
398
398
  [[package]]
399
399
  name = "kiwi-code"
400
- version = "0.0.434"
400
+ version = "0.0.436"
401
401
  source = { editable = "." }
402
402
  dependencies = [
403
403
  { name = "autobots-client" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes