utim-cli 1.0.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.
utim_cli/backup.py ADDED
@@ -0,0 +1,101 @@
1
+ import os
2
+ import shutil
3
+ import json
4
+ import sqlite3
5
+ from utim_cli.logger import log_info, log_error, log_warning
6
+
7
+ BACKUP_DIR = ".utim_backup"
8
+ UTIM_DIR = ".utim"
9
+
10
+ def is_json_valid(path: str) -> bool:
11
+ """Check if JSON file exists and is parseable."""
12
+ if not os.path.exists(path):
13
+ return True
14
+ try:
15
+ with open(path, "r", encoding="utf-8") as f:
16
+ json.load(f)
17
+ return True
18
+ except Exception:
19
+ return False
20
+
21
+ def is_sqlite_valid(path: str) -> bool:
22
+ """Check if SQLite DB is valid via PRAGMA integrity_check."""
23
+ if not os.path.exists(path):
24
+ return True
25
+ try:
26
+ conn = sqlite3.connect(path)
27
+ c = conn.cursor()
28
+ c.execute("PRAGMA integrity_check(1)")
29
+ res = c.fetchone()
30
+ conn.close()
31
+ return res is not None and res[0] == "ok"
32
+ except Exception:
33
+ return False
34
+
35
+ def is_file_valid(path: str) -> bool:
36
+ """Determine if a file is not corrupted based on its extension."""
37
+ if path.endswith(".json"):
38
+ return is_json_valid(path)
39
+ elif path.endswith(".db"):
40
+ return is_sqlite_valid(path)
41
+ return True
42
+
43
+ def backup_state():
44
+ """Backup active configuration and database files to .utim_backup/."""
45
+ try:
46
+ if not os.path.exists(UTIM_DIR):
47
+ return
48
+
49
+ os.makedirs(BACKUP_DIR, exist_ok=True)
50
+
51
+ files_to_backup = [
52
+ "config.json", "mcp.json", "utim_local.db",
53
+ "memory.json", "session_state.json"
54
+ ]
55
+ for f in files_to_backup:
56
+ src = os.path.join(UTIM_DIR, f)
57
+ dst = os.path.join(BACKUP_DIR, f)
58
+ if os.path.exists(src):
59
+ # Verify file is not corrupted before backing it up
60
+ if not is_file_valid(src):
61
+ log_warning("backup", f"Skipping backup of corrupted file {f}")
62
+ continue
63
+
64
+ shutil.copy2(src, dst)
65
+
66
+ log_info("backup", "State backup completed successfully.")
67
+ except Exception as e:
68
+ log_error("backup", f"State backup failed: {e}", e)
69
+
70
+ def restore_state() -> bool:
71
+ """Restore configuration and database files from backup if corrupt or missing."""
72
+ try:
73
+ if not os.path.exists(BACKUP_DIR):
74
+ return False
75
+
76
+ os.makedirs(UTIM_DIR, exist_ok=True)
77
+ restored = False
78
+
79
+ files_to_restore = [
80
+ "config.json", "mcp.json", "utim_local.db",
81
+ "memory.json", "session_state.json"
82
+ ]
83
+ for f in files_to_restore:
84
+ src = os.path.join(BACKUP_DIR, f)
85
+ dst = os.path.join(UTIM_DIR, f)
86
+
87
+ # Check if destination is missing or corrupted
88
+ needs_restore = not os.path.exists(dst) or not is_file_valid(dst)
89
+
90
+ if needs_restore and os.path.exists(src) and is_file_valid(src):
91
+ shutil.copy2(src, dst)
92
+ restored = True
93
+ log_info("backup", f"Restored {f} from backup.")
94
+
95
+ if restored:
96
+ log_info("backup", "State backup restored successfully.")
97
+ return restored
98
+ except Exception as e:
99
+ log_error("backup", f"State restoration failed: {e}", e)
100
+ return False
101
+
utim_cli/billing.py ADDED
@@ -0,0 +1,40 @@
1
+ import json
2
+ import os
3
+
4
+ class CreditManager:
5
+ def __init__(self, config_path=None):
6
+ if config_path is None:
7
+ config_path = os.path.join(".utim_tmp", "utim_state.json")
8
+ self.config_path = config_path
9
+ self.costs = {
10
+ "gemini": 1, # 1 credit per 1000 tokens
11
+ "claude": 15, # 15 credits per 1000 tokens (more expensive)
12
+ "codex": 10 # 10 credits per 1000 tokens
13
+ }
14
+ self.state = self._load_state()
15
+
16
+ def _load_state(self):
17
+ if os.path.exists(self.config_path):
18
+ with open(self.config_path, "r") as f:
19
+ return json.load(f)
20
+ return {"credits": 0} # Start with 0 free credits
21
+
22
+ def save_state(self):
23
+ dir_name = os.path.dirname(self.config_path)
24
+ if dir_name:
25
+ os.makedirs(dir_name, exist_ok=True)
26
+ with open(self.config_path, "w") as f:
27
+ json.dump(self.state, f, indent=4)
28
+
29
+ def has_credits(self, agent_name, estimate_tokens=1000):
30
+ cost = (estimate_tokens / 1000) * self.costs.get(agent_name, 1)
31
+ return self.state["credits"] >= cost
32
+
33
+ def deduct_credits(self, agent_name, actual_tokens):
34
+ cost = (actual_tokens / 1000) * self.costs.get(agent_name, 1)
35
+ self.state["credits"] -= cost
36
+ self.save_state()
37
+ return cost
38
+
39
+ def get_balance(self):
40
+ return self.state["credits"]