kashcloud 0.2.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.
@@ -0,0 +1,12 @@
1
+ node_modules/
2
+ .next/
3
+ .venv/
4
+ __pycache__/
5
+ *.pyc
6
+ .env
7
+ .env.local
8
+ .DS_Store
9
+ *.log
10
+ /frontend/.env
11
+ /backend/.env
12
+ /kashcloud/.env
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 KashCloud
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,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: kashcloud
3
+ Version: 0.2.0
4
+ Summary: KashCloud CLI -- AI terminal + agent infrastructure. Like Claude Code, but yours.
5
+ Project-URL: Homepage, https://kashagent.com/developers
6
+ Project-URL: Documentation, https://kashagent.com/docs
7
+ Project-URL: Repository, https://github.com/pacoai-afk/kashagent
8
+ Project-URL: Issues, https://github.com/pacoai-afk/kashagent/issues
9
+ Author-email: KashCloud <hello@kashagent.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,cli,cloud,deploy,llm,terminal
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Build Tools
24
+ Classifier: Topic :: Software Development :: Libraries
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: httpx>=0.28.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # KashCloud CLI
30
+
31
+ AI terminal + agent infrastructure. Like Claude Code, but yours.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install kashcloud
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```bash
42
+ # Authenticate
43
+ kashcloud login
44
+
45
+ # Interactive AI terminal (Claude Code-style)
46
+ kashcloud
47
+
48
+ # One-shot message
49
+ kashcloud chat "explain this codebase"
50
+
51
+ # Deploy a persistent AI agent
52
+ kashcloud deploy agent.py
53
+
54
+ # Manage agents
55
+ kashcloud list
56
+ kashcloud logs <agent_id>
57
+ kashcloud stop <agent_id>
58
+ kashcloud restart <agent_id>
59
+ ```
60
+
61
+ ## Interactive Terminal
62
+
63
+ Running `kashcloud` with no arguments launches an interactive AI terminal powered by Claude. It can:
64
+
65
+ - Read and write files in your project
66
+ - Run shell commands
67
+ - Explain and refactor code
68
+ - Deploy agents to KashCloud
69
+
70
+ ### Terminal Commands
71
+
72
+ | Command | Description |
73
+ |---------|-------------|
74
+ | `/help` | Show all commands |
75
+ | `/read FILE` | Read a file |
76
+ | `/run CMD` | Run a shell command |
77
+ | `/deploy FILE` | Deploy an agent |
78
+ | `/agents` | List your agents |
79
+ | `/clear` | Clear conversation |
80
+ | `/exit` | Quit |
81
+
82
+ Or just type naturally: *"read main.py and explain it"*, *"fix the bug on line 42"*, *"create a REST API for user management"*.
83
+
84
+ ## Agent Deployment
85
+
86
+ Deploy persistent Python agents that run 24/7 on KashCloud:
87
+
88
+ ```bash
89
+ # Basic deploy
90
+ kashcloud deploy my_agent.py
91
+
92
+ # With options
93
+ kashcloud deploy my_agent.py --name "price-tracker" --model claude-sonnet --memory 512
94
+ ```
95
+
96
+ Agents get:
97
+ - Persistent memory
98
+ - Automatic restarts
99
+ - Log streaming
100
+ - Model routing (cheap/mid/premium)
101
+
102
+ ## Configuration
103
+
104
+ Config is stored at `~/.kashcloud/config.json`. You can also use environment variables:
105
+
106
+ - `KASHCLOUD_API_KEY` -- Your API key
107
+ - `KASHCLOUD_API_URL` -- Custom API endpoint
108
+
109
+ ## Links
110
+
111
+ - Website: [kashagent.com](https://kashagent.com)
112
+ - Developer docs: [kashagent.com/docs](https://kashagent.com/docs)
113
+ - Dashboard: [kashagent.com/dashboard](https://kashagent.com/dashboard)
114
+
115
+ ## License
116
+
117
+ MIT
@@ -0,0 +1,89 @@
1
+ # KashCloud CLI
2
+
3
+ AI terminal + agent infrastructure. Like Claude Code, but yours.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install kashcloud
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Authenticate
15
+ kashcloud login
16
+
17
+ # Interactive AI terminal (Claude Code-style)
18
+ kashcloud
19
+
20
+ # One-shot message
21
+ kashcloud chat "explain this codebase"
22
+
23
+ # Deploy a persistent AI agent
24
+ kashcloud deploy agent.py
25
+
26
+ # Manage agents
27
+ kashcloud list
28
+ kashcloud logs <agent_id>
29
+ kashcloud stop <agent_id>
30
+ kashcloud restart <agent_id>
31
+ ```
32
+
33
+ ## Interactive Terminal
34
+
35
+ Running `kashcloud` with no arguments launches an interactive AI terminal powered by Claude. It can:
36
+
37
+ - Read and write files in your project
38
+ - Run shell commands
39
+ - Explain and refactor code
40
+ - Deploy agents to KashCloud
41
+
42
+ ### Terminal Commands
43
+
44
+ | Command | Description |
45
+ |---------|-------------|
46
+ | `/help` | Show all commands |
47
+ | `/read FILE` | Read a file |
48
+ | `/run CMD` | Run a shell command |
49
+ | `/deploy FILE` | Deploy an agent |
50
+ | `/agents` | List your agents |
51
+ | `/clear` | Clear conversation |
52
+ | `/exit` | Quit |
53
+
54
+ Or just type naturally: *"read main.py and explain it"*, *"fix the bug on line 42"*, *"create a REST API for user management"*.
55
+
56
+ ## Agent Deployment
57
+
58
+ Deploy persistent Python agents that run 24/7 on KashCloud:
59
+
60
+ ```bash
61
+ # Basic deploy
62
+ kashcloud deploy my_agent.py
63
+
64
+ # With options
65
+ kashcloud deploy my_agent.py --name "price-tracker" --model claude-sonnet --memory 512
66
+ ```
67
+
68
+ Agents get:
69
+ - Persistent memory
70
+ - Automatic restarts
71
+ - Log streaming
72
+ - Model routing (cheap/mid/premium)
73
+
74
+ ## Configuration
75
+
76
+ Config is stored at `~/.kashcloud/config.json`. You can also use environment variables:
77
+
78
+ - `KASHCLOUD_API_KEY` -- Your API key
79
+ - `KASHCLOUD_API_URL` -- Custom API endpoint
80
+
81
+ ## Links
82
+
83
+ - Website: [kashagent.com](https://kashagent.com)
84
+ - Developer docs: [kashagent.com/docs](https://kashagent.com/docs)
85
+ - Dashboard: [kashagent.com/dashboard](https://kashagent.com/dashboard)
86
+
87
+ ## License
88
+
89
+ MIT
File without changes
@@ -0,0 +1,528 @@
1
+ #!/usr/bin/env python3
2
+ """KashCloud CLI — Claude Code-style interactive AI terminal.
3
+
4
+ Usage:
5
+ kashcloud Interactive AI terminal (like Claude Code)
6
+ kashcloud chat "your message" One-shot message
7
+ kashcloud deploy agent.py Deploy a Python agent
8
+ kashcloud list List all your agents
9
+ kashcloud logs <agent_id> View agent logs
10
+ kashcloud stop <agent_id> Stop an agent
11
+ kashcloud restart <agent_id> Restart an agent
12
+ kashcloud login Authenticate with API key
13
+ kashcloud status <agent_id> Check agent status
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import os
19
+ import re
20
+ import subprocess
21
+ import sys
22
+ import textwrap
23
+ from pathlib import Path
24
+
25
+ try:
26
+ import httpx
27
+ except ImportError:
28
+ print("Missing dependency. Run: pip install httpx")
29
+ sys.exit(1)
30
+
31
+ # --- Config ---
32
+ CONFIG_PATH = os.path.expanduser("~/.kashcloud/config.json")
33
+ DEFAULT_API_URL = "https://backend-production-bca0.up.railway.app"
34
+ VERSION = "0.2.0"
35
+
36
+ EMERALD = "\033[38;2;16;185;129m"
37
+ CYAN = "\033[38;2;100;220;255m"
38
+ DIM = "\033[2m"
39
+ BOLD = "\033[1m"
40
+ RESET = "\033[0m"
41
+ RED = "\033[31m"
42
+ YELLOW = "\033[33m"
43
+
44
+
45
+ def get_config() -> dict:
46
+ if os.path.exists(CONFIG_PATH):
47
+ with open(CONFIG_PATH) as f:
48
+ return json.load(f)
49
+ return {}
50
+
51
+
52
+ def save_config(config: dict):
53
+ os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
54
+ with open(CONFIG_PATH, "w") as f:
55
+ json.dump(config, f, indent=2)
56
+
57
+
58
+ def get_api_url() -> str:
59
+ return get_config().get("api_url", os.getenv("KASHCLOUD_API_URL", DEFAULT_API_URL))
60
+
61
+
62
+ def get_api_key() -> str:
63
+ key = get_config().get("api_key", os.getenv("KASHCLOUD_API_KEY", ""))
64
+ if not key:
65
+ print(f"{RED}Not authenticated.{RESET} Run: {EMERALD}kashcloud login{RESET}")
66
+ sys.exit(1)
67
+ return key
68
+
69
+
70
+ def headers() -> dict:
71
+ return {"Authorization": f"Bearer {get_api_key()}", "Content-Type": "application/json"}
72
+
73
+
74
+ # --- Interactive REPL (Claude Code style) ---
75
+
76
+ def print_banner():
77
+ print(f"""
78
+ {EMERALD}╔══════════════════════════════════════════════════════════╗
79
+ ║ {BOLD}KashCloud{RESET}{EMERALD} v{VERSION} ║
80
+ ║ Interactive AI Terminal — powered by Claude ║
81
+ ╚══════════════════════════════════════════════════════════╝{RESET}
82
+
83
+ {DIM} Model: Claude Sonnet · Memory: Persistent · Tools: Enabled
84
+ Type a message, or use commands:
85
+ /help Show commands
86
+ /read FILE Read a file
87
+ /run CMD Run a shell command
88
+ /deploy F Deploy an agent
89
+ /agents List agents
90
+ /clear Clear history
91
+ /exit Quit
92
+ {RESET}""")
93
+
94
+
95
+ def call_ai(messages: list[dict], api_url: str, api_key: str) -> str:
96
+ """Call the KashCloud chat completions API."""
97
+ try:
98
+ resp = httpx.post(
99
+ f"{api_url}/v1/chat/completions",
100
+ headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
101
+ json={
102
+ "model": "anthropic/claude-sonnet-4",
103
+ "max_tokens": 4096,
104
+ "messages": messages,
105
+ },
106
+ timeout=120.0,
107
+ )
108
+ if resp.status_code == 200:
109
+ data = resp.json()
110
+ return data["choices"][0]["message"]["content"]
111
+ elif resp.status_code == 429:
112
+ return f"{RED}Rate limited. Wait a moment and try again.{RESET}"
113
+ else:
114
+ return f"{RED}API error ({resp.status_code}): {resp.text[:200]}{RESET}"
115
+ except httpx.TimeoutException:
116
+ return f"{RED}Request timed out. Try again.{RESET}"
117
+ except Exception as e:
118
+ return f"{RED}Connection error: {e}{RESET}"
119
+
120
+
121
+ def process_tool_calls(response: str) -> str:
122
+ """Execute any tool calls in the AI response."""
123
+ import re as _re
124
+
125
+ # Execute <run> commands
126
+ for match in _re.finditer(r'<run>(.*?)</run>', response, _re.DOTALL):
127
+ cmd = match.group(1).strip()
128
+ print(f"\n{DIM} Running: {cmd}{RESET}")
129
+ try:
130
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30, cwd=os.getcwd())
131
+ output = result.stdout + (f"\nSTDERR: {result.stderr}" if result.stderr else "")
132
+ response = response.replace(match.group(0), f"\n```\n{output.strip()}\n```\n")
133
+ except subprocess.TimeoutExpired:
134
+ response = response.replace(match.group(0), "\n```\nCommand timed out (30s limit)\n```\n")
135
+
136
+ # Execute <read> file reads
137
+ for match in _re.finditer(r'<read>(.*?)</read>', response, _re.DOTALL):
138
+ path = match.group(1).strip()
139
+ print(f"\n{DIM} Reading: {path}{RESET}")
140
+ try:
141
+ with open(os.path.expanduser(path)) as f:
142
+ content = f.read()
143
+ if len(content) > 5000:
144
+ content = content[:5000] + f"\n... ({len(content)} chars total, truncated)"
145
+ response = response.replace(match.group(0), f"\n```\n{content}\n```\n")
146
+ except Exception as e:
147
+ response = response.replace(match.group(0), f"\n```\nError reading {path}: {e}\n```\n")
148
+
149
+ # Execute <write> file writes
150
+ for match in _re.finditer(r'<write path="([^"]+)">(.*?)</write>', response, _re.DOTALL):
151
+ path = match.group(1).strip()
152
+ content = match.group(2)
153
+ print(f"\n{DIM} Writing: {path}{RESET}")
154
+ try:
155
+ os.makedirs(os.path.dirname(os.path.expanduser(path)) or ".", exist_ok=True)
156
+ with open(os.path.expanduser(path), "w") as f:
157
+ f.write(content)
158
+ response = response.replace(match.group(0), f"\n{EMERALD}Wrote {len(content)} chars to {path}{RESET}\n")
159
+ except Exception as e:
160
+ response = response.replace(match.group(0), f"\n{RED}Error writing {path}: {e}{RESET}\n")
161
+
162
+ # Clean any remaining XML tags
163
+ response = _re.sub(r'</?(?:run|read|write)[^>]*>', '', response)
164
+ return response
165
+
166
+
167
+ def interactive_repl():
168
+ """Claude Code-style interactive terminal."""
169
+ api_url = get_api_url()
170
+ api_key = get_api_key()
171
+
172
+ print_banner()
173
+
174
+ cwd = os.getcwd()
175
+ system_prompt = f"""You are Kash, an AI coding assistant running in the KashCloud terminal. You are like Claude Code.
176
+
177
+ CURRENT DIRECTORY: {cwd}
178
+ PLATFORM: {sys.platform}
179
+
180
+ YOU HAVE REAL TOOLS. Use them by embedding XML tags in your response:
181
+
182
+ 1. RUN SHELL COMMANDS:
183
+ <run>ls -la</run>
184
+ <run>git status</run>
185
+ <run>python3 script.py</run>
186
+
187
+ 2. READ FILES:
188
+ <read>path/to/file.py</read>
189
+
190
+ 3. WRITE/CREATE FILES:
191
+ <write path="path/to/file.py">
192
+ file content here
193
+ </write>
194
+
195
+ RULES:
196
+ - When asked to look at code, READ the file first
197
+ - When asked to modify code, READ it first, then WRITE the updated version
198
+ - When asked to run something, use <run>
199
+ - Be concise and direct — you're in a terminal, not a chat window
200
+ - Show code changes as diffs or full file writes
201
+ - If you need to see the project structure, run: <run>find . -type f -name "*.py" | head -20</run>
202
+ - Never reveal API keys, tokens, or secrets
203
+ - You can chain multiple tool calls in one response"""
204
+
205
+ messages = [{"role": "system", "content": system_prompt}]
206
+
207
+ while True:
208
+ try:
209
+ # Prompt
210
+ user_input = input(f"\n{EMERALD}kash >{RESET} ").strip()
211
+ except (KeyboardInterrupt, EOFError):
212
+ print(f"\n{DIM}Goodbye.{RESET}")
213
+ break
214
+
215
+ if not user_input:
216
+ continue
217
+
218
+ # Commands
219
+ if user_input.startswith("/"):
220
+ cmd = user_input.split()[0].lower()
221
+ arg = user_input[len(cmd):].strip()
222
+
223
+ if cmd in ("/exit", "/quit", "/q"):
224
+ print(f"{DIM}Goodbye.{RESET}")
225
+ break
226
+ elif cmd == "/help":
227
+ print(f"""
228
+ {BOLD}Commands:{RESET}
229
+ /read FILE Read a file
230
+ /run CMD Run a shell command
231
+ /write F C Write content to file
232
+ /deploy FILE Deploy agent
233
+ /agents List your agents
234
+ /model MODEL Switch model
235
+ /clear Clear conversation
236
+ /exit Quit
237
+
238
+ {BOLD}Or just type naturally:{RESET}
239
+ "read the main.py file and explain it"
240
+ "create a script that scrapes product prices"
241
+ "what files are in this directory?"
242
+ "fix the bug on line 42 of server.py"
243
+ """)
244
+ continue
245
+ elif cmd == "/clear":
246
+ messages = [messages[0]] # Keep system prompt
247
+ print(f"{DIM}Conversation cleared.{RESET}")
248
+ continue
249
+ elif cmd == "/read" and arg:
250
+ try:
251
+ with open(os.path.expanduser(arg)) as f:
252
+ content = f.read()
253
+ print(f"\n{DIM}--- {arg} ---{RESET}")
254
+ print(content[:3000])
255
+ if len(content) > 3000:
256
+ print(f"{DIM}... ({len(content)} chars total){RESET}")
257
+ except Exception as e:
258
+ print(f"{RED}Error: {e}{RESET}")
259
+ continue
260
+ elif cmd == "/run" and arg:
261
+ try:
262
+ result = subprocess.run(arg, shell=True, capture_output=True, text=True, timeout=30)
263
+ print(result.stdout)
264
+ if result.stderr:
265
+ print(f"{YELLOW}{result.stderr}{RESET}")
266
+ except Exception as e:
267
+ print(f"{RED}Error: {e}{RESET}")
268
+ continue
269
+ elif cmd == "/agents":
270
+ cmd_list(argparse.Namespace())
271
+ continue
272
+ elif cmd == "/deploy" and arg:
273
+ cmd_deploy(argparse.Namespace(file=arg, name=None, model=None, memory=None))
274
+ continue
275
+ else:
276
+ print(f"{DIM}Unknown command. Type /help{RESET}")
277
+ continue
278
+
279
+ # Send to AI
280
+ messages.append({"role": "user", "content": user_input})
281
+
282
+ print(f"\n{DIM}Thinking...{RESET}", end="", flush=True)
283
+ response = call_ai(messages, api_url, api_key)
284
+ print("\r" + " " * 20 + "\r", end="") # Clear "Thinking..."
285
+
286
+ # Process tool calls
287
+ response = process_tool_calls(response)
288
+
289
+ # Display response
290
+ print(f"\n{CYAN}kash:{RESET} {response}")
291
+
292
+ messages.append({"role": "assistant", "content": response})
293
+
294
+ # Keep conversation manageable
295
+ if len(messages) > 40:
296
+ messages = [messages[0]] + messages[-30:]
297
+
298
+
299
+ # --- Standard CLI commands ---
300
+
301
+ def cmd_login(args):
302
+ print(f"{BOLD}KashCloud Login{RESET}\n")
303
+ api_key = input(" API key (from dashboard): ").strip()
304
+ if not api_key.startswith("sk_"):
305
+ print(f"\n{RED}Invalid key format. Should start with sk_{RESET}")
306
+ sys.exit(1)
307
+
308
+ api_url = getattr(args, "api_url", None)
309
+ config = get_config()
310
+ config["api_key"] = api_key
311
+ if api_url:
312
+ config["api_url"] = api_url
313
+ else:
314
+ config["api_url"] = DEFAULT_API_URL
315
+ save_config(config)
316
+
317
+ # Verify the key
318
+ try:
319
+ resp = httpx.get(f"{config['api_url']}/v1/usage", headers={"Authorization": f"Bearer {api_key}"}, timeout=10)
320
+ if resp.status_code == 200:
321
+ print(f"\n{EMERALD} Authenticated successfully.{RESET}")
322
+ print(f" Run {BOLD}kashcloud{RESET} to start the interactive terminal.\n")
323
+ else:
324
+ print(f"\n{YELLOW} Key saved but could not verify (server returned {resp.status_code}).{RESET}")
325
+ except Exception:
326
+ print(f"\n{YELLOW} Key saved. Could not reach server to verify.{RESET}")
327
+
328
+
329
+ def cmd_deploy(args):
330
+ file_path = args.file
331
+ if not os.path.exists(file_path):
332
+ print(f"{RED}Error: {file_path} not found{RESET}")
333
+ sys.exit(1)
334
+
335
+ name = args.name or os.path.basename(file_path).replace(".py", "")
336
+ filename = os.path.basename(file_path)
337
+
338
+ print(f"\n{DIM} Deploying {filename} as '{name}'...{RESET}")
339
+
340
+ req_path = os.path.join(os.path.dirname(file_path) or ".", "requirements.txt")
341
+ files = {"file": (filename, open(file_path, "rb"), "text/x-python")}
342
+ if os.path.exists(req_path):
343
+ files["requirements"] = ("requirements.txt", open(req_path, "rb"), "text/plain")
344
+
345
+ data = {"name": name}
346
+ if getattr(args, "model", None):
347
+ data["model"] = args.model
348
+ if getattr(args, "memory", None):
349
+ data["memory_mb"] = str(args.memory)
350
+
351
+ try:
352
+ resp = httpx.post(
353
+ f"{get_api_url()}/v1/agents/deploy",
354
+ headers={"Authorization": f"Bearer {get_api_key()}"},
355
+ files=files, data=data, timeout=120.0,
356
+ )
357
+ if resp.status_code == 200:
358
+ d = resp.json()
359
+ print(f"\n{EMERALD} Agent deployed!{RESET}")
360
+ print(f" ID: {d['agent_id']}")
361
+ print(f" Status: {d['status']}")
362
+ print(f"\n Logs: kashcloud logs {d['agent_id']}")
363
+ print(f" Stop: kashcloud stop {d['agent_id']}")
364
+ else:
365
+ print(f"\n{RED} Deploy failed: {resp.text[:200]}{RESET}")
366
+ except Exception as e:
367
+ print(f"{RED} Error: {e}{RESET}")
368
+
369
+
370
+ def cmd_status(args):
371
+ try:
372
+ resp = httpx.get(f"{get_api_url()}/v1/agents/{args.agent_id}/status", headers=headers())
373
+ if resp.status_code == 200:
374
+ d = resp.json()
375
+ status_color = EMERALD if d.get("status") == "running" else RED if d.get("status") == "crashed" else DIM
376
+ print(f"\n Agent: {d.get('name', args.agent_id)}")
377
+ print(f" ID: {d.get('agent_id', args.agent_id)}")
378
+ print(f" Status: {status_color}{d.get('status', 'unknown')}{RESET}")
379
+ print(f" Model: {d.get('model', 'default')}")
380
+ else:
381
+ print(f"{RED}Error: {resp.text}{RESET}")
382
+ except Exception as e:
383
+ print(f"{RED}Error: {e}{RESET}")
384
+
385
+
386
+ def cmd_logs(args):
387
+ try:
388
+ resp = httpx.get(
389
+ f"{get_api_url()}/v1/agents/{args.agent_id}/logs",
390
+ headers=headers(), params={"tail": getattr(args, "tail", 100)},
391
+ )
392
+ if resp.status_code == 200:
393
+ logs = resp.json().get("logs", [])
394
+ if not logs:
395
+ print(f"{DIM} No logs yet.{RESET}")
396
+ else:
397
+ for log in logs:
398
+ ts = log.get("created_at", "")
399
+ stream = log.get("stream", "stdout")
400
+ content = log.get("content", "")
401
+ color = RED if stream == "stderr" else ""
402
+ print(f"{DIM}[{ts}]{RESET} {color}{content}{RESET}")
403
+ else:
404
+ print(f"{RED}Error: {resp.text}{RESET}")
405
+ except Exception as e:
406
+ print(f"{RED}Error: {e}{RESET}")
407
+
408
+
409
+ def cmd_stop(args):
410
+ try:
411
+ resp = httpx.post(f"{get_api_url()}/v1/agents/{args.agent_id}/stop", headers=headers())
412
+ if resp.status_code == 200:
413
+ print(f"{EMERALD} Agent {args.agent_id} stopped.{RESET}")
414
+ else:
415
+ print(f"{RED}Error: {resp.text}{RESET}")
416
+ except Exception as e:
417
+ print(f"{RED}Error: {e}{RESET}")
418
+
419
+
420
+ def cmd_restart(args):
421
+ try:
422
+ resp = httpx.post(f"{get_api_url()}/v1/agents/{args.agent_id}/restart", headers=headers())
423
+ if resp.status_code == 200:
424
+ print(f"{EMERALD} Agent {args.agent_id} restarted.{RESET}")
425
+ else:
426
+ print(f"{RED}Error: {resp.text}{RESET}")
427
+ except Exception as e:
428
+ print(f"{RED}Error: {e}{RESET}")
429
+
430
+
431
+ def cmd_list(args):
432
+ try:
433
+ resp = httpx.get(f"{get_api_url()}/v1/agents", headers=headers())
434
+ if resp.status_code == 200:
435
+ agents = resp.json().get("agents", [])
436
+ if not agents:
437
+ print(f"\n{DIM} No agents deployed.{RESET}")
438
+ print(f" Run: {EMERALD}kashcloud deploy <file.py>{RESET}\n")
439
+ return
440
+ print(f"\n {'ID':<20} {'Name':<20} {'Status':<12} {'Model':<30}")
441
+ print(f" {'-'*82}")
442
+ for a in agents:
443
+ sc = EMERALD if a["status"] == "running" else RED if a["status"] == "crashed" else DIM
444
+ print(f" {a['id']:<20} {a['name']:<20} {sc}{a['status']:<12}{RESET} {a.get('model', ''):<30}")
445
+ print()
446
+ else:
447
+ print(f"{RED}Error: {resp.text}{RESET}")
448
+ except Exception as e:
449
+ print(f"{RED}Error: {e}{RESET}")
450
+
451
+
452
+ def cmd_chat(args):
453
+ """One-shot chat message."""
454
+ api_url = get_api_url()
455
+ api_key = get_api_key()
456
+ message = " ".join(args.message)
457
+ if not message:
458
+ print(f"{RED}No message provided.{RESET}")
459
+ sys.exit(1)
460
+
461
+ messages = [
462
+ {"role": "system", "content": f"You are Kash, an AI assistant. Current dir: {os.getcwd()}. Be concise."},
463
+ {"role": "user", "content": message},
464
+ ]
465
+ response = call_ai(messages, api_url, api_key)
466
+ response = process_tool_calls(response)
467
+ print(response)
468
+
469
+
470
+ def main():
471
+ parser = argparse.ArgumentParser(
472
+ prog="kashcloud",
473
+ description="KashCloud — AI terminal + agent infrastructure.",
474
+ )
475
+ sub = parser.add_subparsers(dest="command")
476
+
477
+ # login
478
+ p_login = sub.add_parser("login", help="Authenticate with API key")
479
+ p_login.add_argument("--api-url", help="Custom API URL")
480
+
481
+ # chat (one-shot)
482
+ p_chat = sub.add_parser("chat", help="Send a one-shot message")
483
+ p_chat.add_argument("message", nargs="+", help="Your message")
484
+
485
+ # deploy
486
+ p_deploy = sub.add_parser("deploy", help="Deploy an agent")
487
+ p_deploy.add_argument("file", help="Python file to deploy")
488
+ p_deploy.add_argument("--name", "-n", help="Agent name")
489
+ p_deploy.add_argument("--model", "-m", help="AI model to use")
490
+ p_deploy.add_argument("--memory", type=int, help="Memory limit in MB")
491
+
492
+ # status
493
+ p_status = sub.add_parser("status", help="Agent status")
494
+ p_status.add_argument("agent_id")
495
+
496
+ # logs
497
+ p_logs = sub.add_parser("logs", help="View logs")
498
+ p_logs.add_argument("agent_id")
499
+ p_logs.add_argument("--tail", "-t", type=int, default=100)
500
+
501
+ # stop
502
+ p_stop = sub.add_parser("stop", help="Stop agent")
503
+ p_stop.add_argument("agent_id")
504
+
505
+ # restart
506
+ p_restart = sub.add_parser("restart", help="Restart agent")
507
+ p_restart.add_argument("agent_id")
508
+
509
+ # list
510
+ sub.add_parser("list", help="List agents")
511
+
512
+ args = parser.parse_args()
513
+
514
+ # No command = interactive REPL
515
+ if not args.command:
516
+ interactive_repl()
517
+ return
518
+
519
+ cmds = {
520
+ "login": cmd_login, "chat": cmd_chat, "deploy": cmd_deploy,
521
+ "status": cmd_status, "logs": cmd_logs, "stop": cmd_stop,
522
+ "restart": cmd_restart, "list": cmd_list,
523
+ }
524
+ cmds[args.command](args)
525
+
526
+
527
+ if __name__ == "__main__":
528
+ main()
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kashcloud"
7
+ version = "0.2.0"
8
+ description = "KashCloud CLI -- AI terminal + agent infrastructure. Like Claude Code, but yours."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "KashCloud", email = "hello@kashagent.com" },
14
+ ]
15
+ keywords = [
16
+ "ai",
17
+ "agents",
18
+ "cli",
19
+ "cloud",
20
+ "deploy",
21
+ "llm",
22
+ "terminal",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Environment :: Console",
27
+ "Intended Audience :: Developers",
28
+ "License :: OSI Approved :: MIT License",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Topic :: Software Development :: Libraries",
36
+ "Topic :: Software Development :: Build Tools",
37
+ ]
38
+ dependencies = [
39
+ "httpx>=0.28.0",
40
+ ]
41
+
42
+ [project.scripts]
43
+ kashcloud = "kashcloud.cli:main"
44
+
45
+ [project.urls]
46
+ Homepage = "https://kashagent.com/developers"
47
+ Documentation = "https://kashagent.com/docs"
48
+ Repository = "https://github.com/pacoai-afk/kashagent"
49
+ Issues = "https://github.com/pacoai-afk/kashagent/issues"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["src/kashcloud"]
@@ -0,0 +1,22 @@
1
+ from setuptools import setup
2
+
3
+ setup(
4
+ name="kashcloud",
5
+ version="0.2.0",
6
+ description="KashCloud — AI terminal + agent infrastructure. Like Claude Code, but yours.",
7
+ long_description="Interactive AI terminal powered by Claude. Deploy persistent AI agents with one command. Model routing, memory, billing, scaling — all included.",
8
+ author="KashCloud",
9
+ author_email="hello@kashagent.com",
10
+ url="https://kashagent.com/developers",
11
+ py_modules=["main"],
12
+ install_requires=["httpx>=0.28.0"],
13
+ entry_points={"console_scripts": ["kashcloud=main:main"]},
14
+ python_requires=">=3.10",
15
+ classifiers=[
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Topic :: Software Development :: Libraries",
20
+ "Programming Language :: Python :: 3",
21
+ ],
22
+ )
@@ -0,0 +1,3 @@
1
+ """KashCloud CLI -- AI terminal + agent infrastructure."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,532 @@
1
+ #!/usr/bin/env python3
2
+ """KashCloud CLI -- Claude Code-style interactive AI terminal.
3
+
4
+ Usage:
5
+ kashcloud Interactive AI terminal (like Claude Code)
6
+ kashcloud chat "your message" One-shot message
7
+ kashcloud deploy agent.py Deploy a Python agent
8
+ kashcloud list List all your agents
9
+ kashcloud logs <agent_id> View agent logs
10
+ kashcloud stop <agent_id> Stop an agent
11
+ kashcloud restart <agent_id> Restart an agent
12
+ kashcloud login Authenticate with API key
13
+ kashcloud status <agent_id> Check agent status
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import os
19
+ import re
20
+ import subprocess
21
+ import sys
22
+ import textwrap
23
+ from pathlib import Path
24
+
25
+ try:
26
+ import httpx
27
+ except ImportError:
28
+ print("Missing dependency. Run: pip install httpx")
29
+ sys.exit(1)
30
+
31
+ from kashcloud import __version__
32
+
33
+ # --- Config ---
34
+ CONFIG_PATH = os.path.expanduser("~/.kashcloud/config.json")
35
+ DEFAULT_API_URL = "https://backend-production-bca0.up.railway.app"
36
+ VERSION = __version__
37
+
38
+ EMERALD = "\033[38;2;16;185;129m"
39
+ CYAN = "\033[38;2;100;220;255m"
40
+ DIM = "\033[2m"
41
+ BOLD = "\033[1m"
42
+ RESET = "\033[0m"
43
+ RED = "\033[31m"
44
+ YELLOW = "\033[33m"
45
+
46
+
47
+ def get_config() -> dict:
48
+ if os.path.exists(CONFIG_PATH):
49
+ with open(CONFIG_PATH) as f:
50
+ return json.load(f)
51
+ return {}
52
+
53
+
54
+ def save_config(config: dict):
55
+ os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
56
+ with open(CONFIG_PATH, "w") as f:
57
+ json.dump(config, f, indent=2)
58
+
59
+
60
+ def get_api_url() -> str:
61
+ return get_config().get("api_url", os.getenv("KASHCLOUD_API_URL", DEFAULT_API_URL))
62
+
63
+
64
+ def get_api_key() -> str:
65
+ key = get_config().get("api_key", os.getenv("KASHCLOUD_API_KEY", ""))
66
+ if not key:
67
+ print(f"{RED}Not authenticated.{RESET} Run: {EMERALD}kashcloud login{RESET}")
68
+ sys.exit(1)
69
+ return key
70
+
71
+
72
+ def headers() -> dict:
73
+ return {"Authorization": f"Bearer {get_api_key()}", "Content-Type": "application/json"}
74
+
75
+
76
+ # --- Interactive REPL (Claude Code style) ---
77
+
78
+ def print_banner():
79
+ print(f"""
80
+ {EMERALD}+----------------------------------------------------------+
81
+ | {BOLD}KashCloud{RESET}{EMERALD} v{VERSION} |
82
+ | Interactive AI Terminal -- powered by Claude |
83
+ +----------------------------------------------------------+{RESET}
84
+
85
+ {DIM} Model: Claude Sonnet - Memory: Persistent - Tools: Enabled
86
+ Type a message, or use commands:
87
+ /help Show commands
88
+ /read FILE Read a file
89
+ /run CMD Run a shell command
90
+ /deploy F Deploy an agent
91
+ /agents List agents
92
+ /clear Clear history
93
+ /exit Quit
94
+ {RESET}""")
95
+
96
+
97
+ def call_ai(messages: list[dict], api_url: str, api_key: str) -> str:
98
+ """Call the KashCloud chat completions API."""
99
+ try:
100
+ resp = httpx.post(
101
+ f"{api_url}/v1/chat/completions",
102
+ headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
103
+ json={
104
+ "model": "anthropic/claude-sonnet-4",
105
+ "max_tokens": 4096,
106
+ "messages": messages,
107
+ },
108
+ timeout=120.0,
109
+ )
110
+ if resp.status_code == 200:
111
+ data = resp.json()
112
+ return data["choices"][0]["message"]["content"]
113
+ elif resp.status_code == 429:
114
+ return f"{RED}Rate limited. Wait a moment and try again.{RESET}"
115
+ else:
116
+ return f"{RED}API error ({resp.status_code}): {resp.text[:200]}{RESET}"
117
+ except httpx.TimeoutException:
118
+ return f"{RED}Request timed out. Try again.{RESET}"
119
+ except Exception as e:
120
+ return f"{RED}Connection error: {e}{RESET}"
121
+
122
+
123
+ def process_tool_calls(response: str) -> str:
124
+ """Execute any tool calls in the AI response."""
125
+ import re as _re
126
+
127
+ # Execute <run> commands
128
+ for match in _re.finditer(r'<run>(.*?)</run>', response, _re.DOTALL):
129
+ cmd = match.group(1).strip()
130
+ print(f"\n{DIM} Running: {cmd}{RESET}")
131
+ try:
132
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30, cwd=os.getcwd())
133
+ output = result.stdout + (f"\nSTDERR: {result.stderr}" if result.stderr else "")
134
+ response = response.replace(match.group(0), f"\n```\n{output.strip()}\n```\n")
135
+ except subprocess.TimeoutExpired:
136
+ response = response.replace(match.group(0), "\n```\nCommand timed out (30s limit)\n```\n")
137
+
138
+ # Execute <read> file reads
139
+ for match in _re.finditer(r'<read>(.*?)</read>', response, _re.DOTALL):
140
+ path = match.group(1).strip()
141
+ print(f"\n{DIM} Reading: {path}{RESET}")
142
+ try:
143
+ with open(os.path.expanduser(path)) as f:
144
+ content = f.read()
145
+ if len(content) > 5000:
146
+ content = content[:5000] + f"\n... ({len(content)} chars total, truncated)"
147
+ response = response.replace(match.group(0), f"\n```\n{content}\n```\n")
148
+ except Exception as e:
149
+ response = response.replace(match.group(0), f"\n```\nError reading {path}: {e}\n```\n")
150
+
151
+ # Execute <write> file writes
152
+ for match in _re.finditer(r'<write path="([^"]+)">(.*?)</write>', response, _re.DOTALL):
153
+ path = match.group(1).strip()
154
+ content = match.group(2)
155
+ print(f"\n{DIM} Writing: {path}{RESET}")
156
+ try:
157
+ os.makedirs(os.path.dirname(os.path.expanduser(path)) or ".", exist_ok=True)
158
+ with open(os.path.expanduser(path), "w") as f:
159
+ f.write(content)
160
+ response = response.replace(match.group(0), f"\n{EMERALD}Wrote {len(content)} chars to {path}{RESET}\n")
161
+ except Exception as e:
162
+ response = response.replace(match.group(0), f"\n{RED}Error writing {path}: {e}{RESET}\n")
163
+
164
+ # Clean any remaining XML tags
165
+ response = _re.sub(r'</?(?:run|read|write)[^>]*>', '', response)
166
+ return response
167
+
168
+
169
+ def interactive_repl():
170
+ """Claude Code-style interactive terminal."""
171
+ api_url = get_api_url()
172
+ api_key = get_api_key()
173
+
174
+ print_banner()
175
+
176
+ cwd = os.getcwd()
177
+ system_prompt = f"""You are Kash, an AI coding assistant running in the KashCloud terminal. You are like Claude Code.
178
+
179
+ CURRENT DIRECTORY: {cwd}
180
+ PLATFORM: {sys.platform}
181
+
182
+ YOU HAVE REAL TOOLS. Use them by embedding XML tags in your response:
183
+
184
+ 1. RUN SHELL COMMANDS:
185
+ <run>ls -la</run>
186
+ <run>git status</run>
187
+ <run>python3 script.py</run>
188
+
189
+ 2. READ FILES:
190
+ <read>path/to/file.py</read>
191
+
192
+ 3. WRITE/CREATE FILES:
193
+ <write path="path/to/file.py">
194
+ file content here
195
+ </write>
196
+
197
+ RULES:
198
+ - When asked to look at code, READ the file first
199
+ - When asked to modify code, READ it first, then WRITE the updated version
200
+ - When asked to run something, use <run>
201
+ - Be concise and direct -- you're in a terminal, not a chat window
202
+ - Show code changes as diffs or full file writes
203
+ - If you need to see the project structure, run: <run>find . -type f -name "*.py" | head -20</run>
204
+ - Never reveal API keys, tokens, or secrets
205
+ - You can chain multiple tool calls in one response"""
206
+
207
+ messages = [{"role": "system", "content": system_prompt}]
208
+
209
+ while True:
210
+ try:
211
+ # Prompt
212
+ user_input = input(f"\n{EMERALD}kash >{RESET} ").strip()
213
+ except (KeyboardInterrupt, EOFError):
214
+ print(f"\n{DIM}Goodbye.{RESET}")
215
+ break
216
+
217
+ if not user_input:
218
+ continue
219
+
220
+ # Commands
221
+ if user_input.startswith("/"):
222
+ cmd = user_input.split()[0].lower()
223
+ arg = user_input[len(cmd):].strip()
224
+
225
+ if cmd in ("/exit", "/quit", "/q"):
226
+ print(f"{DIM}Goodbye.{RESET}")
227
+ break
228
+ elif cmd == "/help":
229
+ print(f"""
230
+ {BOLD}Commands:{RESET}
231
+ /read FILE Read a file
232
+ /run CMD Run a shell command
233
+ /write F C Write content to file
234
+ /deploy FILE Deploy agent
235
+ /agents List your agents
236
+ /model MODEL Switch model
237
+ /clear Clear conversation
238
+ /exit Quit
239
+
240
+ {BOLD}Or just type naturally:{RESET}
241
+ "read the main.py file and explain it"
242
+ "create a script that scrapes product prices"
243
+ "what files are in this directory?"
244
+ "fix the bug on line 42 of server.py"
245
+ """)
246
+ continue
247
+ elif cmd == "/clear":
248
+ messages = [messages[0]] # Keep system prompt
249
+ print(f"{DIM}Conversation cleared.{RESET}")
250
+ continue
251
+ elif cmd == "/read" and arg:
252
+ try:
253
+ with open(os.path.expanduser(arg)) as f:
254
+ content = f.read()
255
+ print(f"\n{DIM}--- {arg} ---{RESET}")
256
+ print(content[:3000])
257
+ if len(content) > 3000:
258
+ print(f"{DIM}... ({len(content)} chars total){RESET}")
259
+ except Exception as e:
260
+ print(f"{RED}Error: {e}{RESET}")
261
+ continue
262
+ elif cmd == "/run" and arg:
263
+ try:
264
+ result = subprocess.run(arg, shell=True, capture_output=True, text=True, timeout=30)
265
+ print(result.stdout)
266
+ if result.stderr:
267
+ print(f"{YELLOW}{result.stderr}{RESET}")
268
+ except Exception as e:
269
+ print(f"{RED}Error: {e}{RESET}")
270
+ continue
271
+ elif cmd == "/agents":
272
+ cmd_list(argparse.Namespace())
273
+ continue
274
+ elif cmd == "/deploy" and arg:
275
+ cmd_deploy(argparse.Namespace(file=arg, name=None, model=None, memory=None))
276
+ continue
277
+ else:
278
+ print(f"{DIM}Unknown command. Type /help{RESET}")
279
+ continue
280
+
281
+ # Send to AI
282
+ messages.append({"role": "user", "content": user_input})
283
+
284
+ print(f"\n{DIM}Thinking...{RESET}", end="", flush=True)
285
+ response = call_ai(messages, api_url, api_key)
286
+ print("\r" + " " * 20 + "\r", end="") # Clear "Thinking..."
287
+
288
+ # Process tool calls
289
+ response = process_tool_calls(response)
290
+
291
+ # Display response
292
+ print(f"\n{CYAN}kash:{RESET} {response}")
293
+
294
+ messages.append({"role": "assistant", "content": response})
295
+
296
+ # Keep conversation manageable
297
+ if len(messages) > 40:
298
+ messages = [messages[0]] + messages[-30:]
299
+
300
+
301
+ # --- Standard CLI commands ---
302
+
303
+ def cmd_login(args):
304
+ print(f"{BOLD}KashCloud Login{RESET}\n")
305
+ api_key = input(" API key (from dashboard): ").strip()
306
+ if not api_key.startswith("sk_"):
307
+ print(f"\n{RED}Invalid key format. Should start with sk_{RESET}")
308
+ sys.exit(1)
309
+
310
+ api_url = getattr(args, "api_url", None)
311
+ config = get_config()
312
+ config["api_key"] = api_key
313
+ if api_url:
314
+ config["api_url"] = api_url
315
+ else:
316
+ config["api_url"] = DEFAULT_API_URL
317
+ save_config(config)
318
+
319
+ # Verify the key
320
+ try:
321
+ resp = httpx.get(f"{config['api_url']}/v1/usage", headers={"Authorization": f"Bearer {api_key}"}, timeout=10)
322
+ if resp.status_code == 200:
323
+ print(f"\n{EMERALD} Authenticated successfully.{RESET}")
324
+ print(f" Run {BOLD}kashcloud{RESET} to start the interactive terminal.\n")
325
+ else:
326
+ print(f"\n{YELLOW} Key saved but could not verify (server returned {resp.status_code}).{RESET}")
327
+ except Exception:
328
+ print(f"\n{YELLOW} Key saved. Could not reach server to verify.{RESET}")
329
+
330
+
331
+ def cmd_deploy(args):
332
+ file_path = args.file
333
+ if not os.path.exists(file_path):
334
+ print(f"{RED}Error: {file_path} not found{RESET}")
335
+ sys.exit(1)
336
+
337
+ name = args.name or os.path.basename(file_path).replace(".py", "")
338
+ filename = os.path.basename(file_path)
339
+
340
+ print(f"\n{DIM} Deploying {filename} as '{name}'...{RESET}")
341
+
342
+ req_path = os.path.join(os.path.dirname(file_path) or ".", "requirements.txt")
343
+ files = {"file": (filename, open(file_path, "rb"), "text/x-python")}
344
+ if os.path.exists(req_path):
345
+ files["requirements"] = ("requirements.txt", open(req_path, "rb"), "text/plain")
346
+
347
+ data = {"name": name}
348
+ if getattr(args, "model", None):
349
+ data["model"] = args.model
350
+ if getattr(args, "memory", None):
351
+ data["memory_mb"] = str(args.memory)
352
+
353
+ try:
354
+ resp = httpx.post(
355
+ f"{get_api_url()}/v1/agents/deploy",
356
+ headers={"Authorization": f"Bearer {get_api_key()}"},
357
+ files=files, data=data, timeout=120.0,
358
+ )
359
+ if resp.status_code == 200:
360
+ d = resp.json()
361
+ print(f"\n{EMERALD} Agent deployed!{RESET}")
362
+ print(f" ID: {d['agent_id']}")
363
+ print(f" Status: {d['status']}")
364
+ print(f"\n Logs: kashcloud logs {d['agent_id']}")
365
+ print(f" Stop: kashcloud stop {d['agent_id']}")
366
+ else:
367
+ print(f"\n{RED} Deploy failed: {resp.text[:200]}{RESET}")
368
+ except Exception as e:
369
+ print(f"{RED} Error: {e}{RESET}")
370
+
371
+
372
+ def cmd_status(args):
373
+ try:
374
+ resp = httpx.get(f"{get_api_url()}/v1/agents/{args.agent_id}/status", headers=headers())
375
+ if resp.status_code == 200:
376
+ d = resp.json()
377
+ status_color = EMERALD if d.get("status") == "running" else RED if d.get("status") == "crashed" else DIM
378
+ print(f"\n Agent: {d.get('name', args.agent_id)}")
379
+ print(f" ID: {d.get('agent_id', args.agent_id)}")
380
+ print(f" Status: {status_color}{d.get('status', 'unknown')}{RESET}")
381
+ print(f" Model: {d.get('model', 'default')}")
382
+ else:
383
+ print(f"{RED}Error: {resp.text}{RESET}")
384
+ except Exception as e:
385
+ print(f"{RED}Error: {e}{RESET}")
386
+
387
+
388
+ def cmd_logs(args):
389
+ try:
390
+ resp = httpx.get(
391
+ f"{get_api_url()}/v1/agents/{args.agent_id}/logs",
392
+ headers=headers(), params={"tail": getattr(args, "tail", 100)},
393
+ )
394
+ if resp.status_code == 200:
395
+ logs = resp.json().get("logs", [])
396
+ if not logs:
397
+ print(f"{DIM} No logs yet.{RESET}")
398
+ else:
399
+ for log in logs:
400
+ ts = log.get("created_at", "")
401
+ stream = log.get("stream", "stdout")
402
+ content = log.get("content", "")
403
+ color = RED if stream == "stderr" else ""
404
+ print(f"{DIM}[{ts}]{RESET} {color}{content}{RESET}")
405
+ else:
406
+ print(f"{RED}Error: {resp.text}{RESET}")
407
+ except Exception as e:
408
+ print(f"{RED}Error: {e}{RESET}")
409
+
410
+
411
+ def cmd_stop(args):
412
+ try:
413
+ resp = httpx.post(f"{get_api_url()}/v1/agents/{args.agent_id}/stop", headers=headers())
414
+ if resp.status_code == 200:
415
+ print(f"{EMERALD} Agent {args.agent_id} stopped.{RESET}")
416
+ else:
417
+ print(f"{RED}Error: {resp.text}{RESET}")
418
+ except Exception as e:
419
+ print(f"{RED}Error: {e}{RESET}")
420
+
421
+
422
+ def cmd_restart(args):
423
+ try:
424
+ resp = httpx.post(f"{get_api_url()}/v1/agents/{args.agent_id}/restart", headers=headers())
425
+ if resp.status_code == 200:
426
+ print(f"{EMERALD} Agent {args.agent_id} restarted.{RESET}")
427
+ else:
428
+ print(f"{RED}Error: {resp.text}{RESET}")
429
+ except Exception as e:
430
+ print(f"{RED}Error: {e}{RESET}")
431
+
432
+
433
+ def cmd_list(args):
434
+ try:
435
+ resp = httpx.get(f"{get_api_url()}/v1/agents", headers=headers())
436
+ if resp.status_code == 200:
437
+ agents = resp.json().get("agents", [])
438
+ if not agents:
439
+ print(f"\n{DIM} No agents deployed.{RESET}")
440
+ print(f" Run: {EMERALD}kashcloud deploy <file.py>{RESET}\n")
441
+ return
442
+ print(f"\n {'ID':<20} {'Name':<20} {'Status':<12} {'Model':<30}")
443
+ print(f" {'-'*82}")
444
+ for a in agents:
445
+ sc = EMERALD if a["status"] == "running" else RED if a["status"] == "crashed" else DIM
446
+ print(f" {a['id']:<20} {a['name']:<20} {sc}{a['status']:<12}{RESET} {a.get('model', ''):<30}")
447
+ print()
448
+ else:
449
+ print(f"{RED}Error: {resp.text}{RESET}")
450
+ except Exception as e:
451
+ print(f"{RED}Error: {e}{RESET}")
452
+
453
+
454
+ def cmd_chat(args):
455
+ """One-shot chat message."""
456
+ api_url = get_api_url()
457
+ api_key = get_api_key()
458
+ message = " ".join(args.message)
459
+ if not message:
460
+ print(f"{RED}No message provided.{RESET}")
461
+ sys.exit(1)
462
+
463
+ messages = [
464
+ {"role": "system", "content": f"You are Kash, an AI assistant. Current dir: {os.getcwd()}. Be concise."},
465
+ {"role": "user", "content": message},
466
+ ]
467
+ response = call_ai(messages, api_url, api_key)
468
+ response = process_tool_calls(response)
469
+ print(response)
470
+
471
+
472
+ def main():
473
+ parser = argparse.ArgumentParser(
474
+ prog="kashcloud",
475
+ description="KashCloud -- AI terminal + agent infrastructure.",
476
+ )
477
+ parser.add_argument("--version", "-V", action="version", version=f"kashcloud {VERSION}")
478
+
479
+ sub = parser.add_subparsers(dest="command")
480
+
481
+ # login
482
+ p_login = sub.add_parser("login", help="Authenticate with API key")
483
+ p_login.add_argument("--api-url", help="Custom API URL")
484
+
485
+ # chat (one-shot)
486
+ p_chat = sub.add_parser("chat", help="Send a one-shot message")
487
+ p_chat.add_argument("message", nargs="+", help="Your message")
488
+
489
+ # deploy
490
+ p_deploy = sub.add_parser("deploy", help="Deploy an agent")
491
+ p_deploy.add_argument("file", help="Python file to deploy")
492
+ p_deploy.add_argument("--name", "-n", help="Agent name")
493
+ p_deploy.add_argument("--model", "-m", help="AI model to use")
494
+ p_deploy.add_argument("--memory", type=int, help="Memory limit in MB")
495
+
496
+ # status
497
+ p_status = sub.add_parser("status", help="Agent status")
498
+ p_status.add_argument("agent_id")
499
+
500
+ # logs
501
+ p_logs = sub.add_parser("logs", help="View logs")
502
+ p_logs.add_argument("agent_id")
503
+ p_logs.add_argument("--tail", "-t", type=int, default=100)
504
+
505
+ # stop
506
+ p_stop = sub.add_parser("stop", help="Stop agent")
507
+ p_stop.add_argument("agent_id")
508
+
509
+ # restart
510
+ p_restart = sub.add_parser("restart", help="Restart agent")
511
+ p_restart.add_argument("agent_id")
512
+
513
+ # list
514
+ sub.add_parser("list", help="List agents")
515
+
516
+ args = parser.parse_args()
517
+
518
+ # No command = interactive REPL
519
+ if not args.command:
520
+ interactive_repl()
521
+ return
522
+
523
+ cmds = {
524
+ "login": cmd_login, "chat": cmd_chat, "deploy": cmd_deploy,
525
+ "status": cmd_status, "logs": cmd_logs, "stop": cmd_stop,
526
+ "restart": cmd_restart, "list": cmd_list,
527
+ }
528
+ cmds[args.command](args)
529
+
530
+
531
+ if __name__ == "__main__":
532
+ main()