kashcloud 0.2.0__py3-none-any.whl

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.
kashcloud/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """KashCloud CLI -- AI terminal + agent infrastructure."""
2
+
3
+ __version__ = "0.2.0"
kashcloud/cli.py ADDED
@@ -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()
@@ -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,7 @@
1
+ kashcloud/__init__.py,sha256=M-tVuZKABtLXtt7k93BDTdFs8pbGiWtr7-RHmBK3HmI,82
2
+ kashcloud/cli.py,sha256=LtGC2hpldHPj8qcTqe2MPX6SXt_Z8VEkAKxPno3Jhwo,18710
3
+ kashcloud-0.2.0.dist-info/METADATA,sha256=GfZh0g2RKRnHn_kN14sg2jwPpwYxPcAAnMDW5DMslKs,3114
4
+ kashcloud-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
+ kashcloud-0.2.0.dist-info/entry_points.txt,sha256=-GBBtEBxowaPTt02vbAyiwHa93QQQgk6Gqv5L3r538w,49
6
+ kashcloud-0.2.0.dist-info/licenses/LICENSE,sha256=2m84Sc_hCwLY3KI0jlMo45dV2FqIgmmOMvqSIKf8eOk,1066
7
+ kashcloud-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kashcloud = kashcloud.cli:main
@@ -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.