epsimo-agent 0.1.0
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.
- package/LICENSE +21 -0
- package/SKILL.md +85 -0
- package/assets/example_asset.txt +24 -0
- package/epsimo/__init__.py +3 -0
- package/epsimo/__main__.py +4 -0
- package/epsimo/auth.py +143 -0
- package/epsimo/cli.py +586 -0
- package/epsimo/client.py +53 -0
- package/epsimo/resources/assistants.py +47 -0
- package/epsimo/resources/credits.py +16 -0
- package/epsimo/resources/db.py +31 -0
- package/epsimo/resources/files.py +39 -0
- package/epsimo/resources/projects.py +30 -0
- package/epsimo/resources/threads.py +83 -0
- package/epsimo/templates/components/AuthModal/AuthModal.module.css +39 -0
- package/epsimo/templates/components/AuthModal/AuthModal.tsx +138 -0
- package/epsimo/templates/components/BuyCredits/BuyCreditsModal.module.css +96 -0
- package/epsimo/templates/components/BuyCredits/BuyCreditsModal.tsx +132 -0
- package/epsimo/templates/components/BuyCredits/CreditsDisplay.tsx +101 -0
- package/epsimo/templates/components/ThreadChat/ThreadChat.module.css +551 -0
- package/epsimo/templates/components/ThreadChat/ThreadChat.tsx +862 -0
- package/epsimo/templates/components/ThreadChat/components/ToolRenderers.module.css +509 -0
- package/epsimo/templates/components/ThreadChat/components/ToolRenderers.tsx +322 -0
- package/epsimo/templates/next-mvp/app/globals.css.tmpl +20 -0
- package/epsimo/templates/next-mvp/app/layout.tsx.tmpl +22 -0
- package/epsimo/templates/next-mvp/app/page.module.css.tmpl +84 -0
- package/epsimo/templates/next-mvp/app/page.tsx.tmpl +43 -0
- package/epsimo/templates/next-mvp/epsimo.yaml.tmpl +12 -0
- package/epsimo/templates/next-mvp/package.json.tmpl +26 -0
- package/epsimo/tools/library.yaml +51 -0
- package/package.json +27 -0
- package/references/api_reference.md +34 -0
- package/references/virtual_db_guide.md +57 -0
- package/requirements.txt +2 -0
- package/scripts/assistant.py +165 -0
- package/scripts/auth.py +195 -0
- package/scripts/credits.py +107 -0
- package/scripts/debug_run.py +41 -0
- package/scripts/example.py +19 -0
- package/scripts/files.py +73 -0
- package/scripts/find_thread.py +55 -0
- package/scripts/project.py +60 -0
- package/scripts/run.py +75 -0
- package/scripts/test_all_skills.py +387 -0
- package/scripts/test_sdk.py +83 -0
- package/scripts/test_streaming.py +167 -0
- package/scripts/test_vdb.py +65 -0
- package/scripts/thread.py +77 -0
- package/scripts/verify_skill.py +87 -0
package/epsimo/cli.py
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import yaml
|
|
6
|
+
from .client import EpsimoClient
|
|
7
|
+
from .auth import login_interactive, get_token
|
|
8
|
+
|
|
9
|
+
def cmd_whoami(args):
|
|
10
|
+
"""Show current user info."""
|
|
11
|
+
print("👤 Fetching user info...")
|
|
12
|
+
try:
|
|
13
|
+
token = get_token()
|
|
14
|
+
if not token:
|
|
15
|
+
print("❌ Not logged in. Use 'epsimo auth'.")
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
client = EpsimoClient(api_key=token)
|
|
19
|
+
# Using a guessed endpoint from auth scripts (thread-info is a proxy for profile info sometimes)
|
|
20
|
+
# Or better check if there is a profile endpoint?
|
|
21
|
+
# api.d.ts doesn't explicitly show /auth/whoami, but /auth/thread-info is available
|
|
22
|
+
info = client.request("GET", "/auth/thread-info")
|
|
23
|
+
print(f"Logged in as: {info.get('email', 'Unknown User')}")
|
|
24
|
+
print(f"Threads Used: {info.get('thread_counter')}/{info.get('thread_max')}")
|
|
25
|
+
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print(f"❌ Failed to fetch user info: {e}")
|
|
28
|
+
|
|
29
|
+
def cmd_balance(args):
|
|
30
|
+
"""Check the current thread and credit balance."""
|
|
31
|
+
print("💳 Checking balance...")
|
|
32
|
+
try:
|
|
33
|
+
token = get_token()
|
|
34
|
+
client = EpsimoClient(api_key=token)
|
|
35
|
+
data = client.credits.get_balance()
|
|
36
|
+
|
|
37
|
+
thread_count = data.get("thread_counter", 0)
|
|
38
|
+
thread_max = data.get("thread_max", 0)
|
|
39
|
+
remaining = thread_max - thread_count
|
|
40
|
+
|
|
41
|
+
print("\n=== Thread Balance ===")
|
|
42
|
+
print(f"Threads Used: {thread_count}")
|
|
43
|
+
print(f"Total Allowance: {thread_max}")
|
|
44
|
+
print(f"Threads Remaining: {remaining}")
|
|
45
|
+
print("======================\n")
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
print(f"❌ Failed to check balance: {e}")
|
|
49
|
+
|
|
50
|
+
def cmd_buy(args):
|
|
51
|
+
"""Create a checkout session to buy credits."""
|
|
52
|
+
print(f"🛒 Preparing purchase of {args.quantity} threads...")
|
|
53
|
+
|
|
54
|
+
quantity = args.quantity
|
|
55
|
+
total_amount = args.amount
|
|
56
|
+
|
|
57
|
+
# Estimation logic if amount not provided
|
|
58
|
+
if total_amount is None:
|
|
59
|
+
if quantity >= 1000:
|
|
60
|
+
price_per_unit = 0.08
|
|
61
|
+
elif quantity >= 500:
|
|
62
|
+
price_per_unit = 0.09
|
|
63
|
+
else:
|
|
64
|
+
price_per_unit = 0.10
|
|
65
|
+
total_amount = round(quantity * price_per_unit, 2)
|
|
66
|
+
print(f"ℹ️ Estimated cost: {total_amount} EUR")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
token = get_token()
|
|
70
|
+
client = EpsimoClient(api_key=token)
|
|
71
|
+
data = client.credits.create_checkout_session(quantity, total_amount)
|
|
72
|
+
|
|
73
|
+
checkout_url = data.get("url")
|
|
74
|
+
if checkout_url:
|
|
75
|
+
print("\n✅ Checkout session created successfully!")
|
|
76
|
+
print(f"Please visit this URL to complete your purchase:\n\n{checkout_url}\n")
|
|
77
|
+
else:
|
|
78
|
+
print("❌ No checkout URL returned from server.")
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"❌ Failed to create checkout session: {e}")
|
|
82
|
+
|
|
83
|
+
def cmd_auth(args):
|
|
84
|
+
"""Handle authentication."""
|
|
85
|
+
print("🔐 Authenticating Epsimo CLI...")
|
|
86
|
+
token = get_token()
|
|
87
|
+
if token:
|
|
88
|
+
print("ℹ️ Already logged in. Re-authenticating...")
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
login_interactive()
|
|
92
|
+
token = get_token()
|
|
93
|
+
if token:
|
|
94
|
+
print(f"✅ Successfully logged in!")
|
|
95
|
+
else:
|
|
96
|
+
print("❌ Login failed (no token found).")
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(f"❌ Error during auth: {e}")
|
|
99
|
+
|
|
100
|
+
def cmd_projects(args):
|
|
101
|
+
"""List projects."""
|
|
102
|
+
if not args.json:
|
|
103
|
+
print("📁 Fetching projects...")
|
|
104
|
+
try:
|
|
105
|
+
token = get_token()
|
|
106
|
+
client = EpsimoClient(api_key=token)
|
|
107
|
+
projects = client.projects.list()
|
|
108
|
+
|
|
109
|
+
if args.json:
|
|
110
|
+
print(json.dumps(projects))
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
if not projects:
|
|
114
|
+
print("No projects found.")
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
print(f"{'ID':<40} | {'Name':<20}")
|
|
118
|
+
print("-" * 65)
|
|
119
|
+
for p in projects:
|
|
120
|
+
print(f"{p['project_id']:<40} | {p['name']:<20}")
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
if not args.json:
|
|
124
|
+
print(f"❌ Failed to fetch projects: {e}")
|
|
125
|
+
else:
|
|
126
|
+
print(json.dumps({"error": str(e)}))
|
|
127
|
+
|
|
128
|
+
def cmd_assistants(args):
|
|
129
|
+
"""List assistants in a project."""
|
|
130
|
+
if not args.json:
|
|
131
|
+
print(f"🤖 Fetching assistants for project {args.project_id}...")
|
|
132
|
+
try:
|
|
133
|
+
token = get_token()
|
|
134
|
+
client = EpsimoClient(api_key=token)
|
|
135
|
+
assistants = client.assistants.list(args.project_id)
|
|
136
|
+
|
|
137
|
+
if args.json:
|
|
138
|
+
print(json.dumps(assistants))
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
if not assistants:
|
|
142
|
+
print("No assistants found.")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
print(f"{'ID':<40} | {'Name':<20}")
|
|
146
|
+
print("-" * 65)
|
|
147
|
+
for a in assistants:
|
|
148
|
+
print(f"{a['assistant_id']:<40} | {a['name']:<20}")
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
if not args.json:
|
|
152
|
+
print(f"❌ Failed to fetch assistants: {e}")
|
|
153
|
+
else:
|
|
154
|
+
print(json.dumps({"error": str(e)}))
|
|
155
|
+
|
|
156
|
+
def cmd_threads(args):
|
|
157
|
+
"""List threads in a project."""
|
|
158
|
+
print(f"🧵 Fetching threads for project {args.project_id}...")
|
|
159
|
+
try:
|
|
160
|
+
token = get_token()
|
|
161
|
+
client = EpsimoClient(api_key=token)
|
|
162
|
+
threads = client.threads.list(args.project_id)
|
|
163
|
+
|
|
164
|
+
if not threads:
|
|
165
|
+
print("No threads found.")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
print(f"{'ID':<40} | {'Name':<20}")
|
|
169
|
+
print("-" * 65)
|
|
170
|
+
for t in threads:
|
|
171
|
+
print(f"{t['thread_id']:<40} | {t['name']:<20}")
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
print(f"❌ Failed to fetch threads: {e}")
|
|
175
|
+
|
|
176
|
+
def cmd_init(args):
|
|
177
|
+
"""Initialize a new Epsimo project in the current directory."""
|
|
178
|
+
print("🚀 Initializing Epsimo project...")
|
|
179
|
+
|
|
180
|
+
if os.path.exists("epsimo.yaml"):
|
|
181
|
+
print("⚠️ epsimo.yaml already exists in this directory.")
|
|
182
|
+
choice = input("Do you want to overwrite it? (y/n): ")
|
|
183
|
+
if choice.lower() != 'y':
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# 1. Auth & Client
|
|
187
|
+
try:
|
|
188
|
+
token = get_token()
|
|
189
|
+
if not token:
|
|
190
|
+
print("🔐 Authentication required.")
|
|
191
|
+
login_interactive()
|
|
192
|
+
token = get_token()
|
|
193
|
+
|
|
194
|
+
client = EpsimoClient(api_key=token)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(f"❌ Auth failed: {e}")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# 2. Project Selection/Creation
|
|
200
|
+
project_name = args.name or os.path.basename(os.getcwd())
|
|
201
|
+
print(f"📁 Project Name: {project_name}")
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
print("Creating project on Epsimo platform...")
|
|
205
|
+
project = client.projects.create(name=project_name)
|
|
206
|
+
project_id = project["project_id"]
|
|
207
|
+
print(f"✅ Project Created: {project_id}")
|
|
208
|
+
except Exception as e:
|
|
209
|
+
print(f"❌ Failed to create project: {e}")
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
# 3. Generate epsimo.yaml
|
|
213
|
+
config = {
|
|
214
|
+
"project_id": project_id,
|
|
215
|
+
"name": project_name,
|
|
216
|
+
"assistants": [
|
|
217
|
+
{
|
|
218
|
+
"name": "default-assistant",
|
|
219
|
+
"model": "gpt-4o",
|
|
220
|
+
"instructions": "You are a helpful AI assistant created via the Epsimo CLI.",
|
|
221
|
+
"tools": [
|
|
222
|
+
{"type": "retrieval"}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
with open("epsimo.yaml", "w") as f:
|
|
230
|
+
yaml.dump(config, f, sort_keys=False)
|
|
231
|
+
print("✅ Created epsimo.yaml")
|
|
232
|
+
print("\nNext steps:")
|
|
233
|
+
print("1. Edit epsimo.yaml to configure your assistants.")
|
|
234
|
+
print(f"2. Run 'epsimo deploy' to sync changes (coming soon).")
|
|
235
|
+
print(f"3. Chat with your assistant: 'epsimo run --project-id {project_id} --assistant-id <ID>'")
|
|
236
|
+
except Exception as e:
|
|
237
|
+
print(f"❌ Failed to write epsimo.yaml: {e}")
|
|
238
|
+
|
|
239
|
+
def cmd_deploy(args):
|
|
240
|
+
"""Deploy configuration from epsimo.yaml to the platform."""
|
|
241
|
+
print("🚀 Deploying configuration...")
|
|
242
|
+
|
|
243
|
+
if not os.path.exists("epsimo.yaml"):
|
|
244
|
+
print("❌ epsimo.yaml not found. Run 'epsimo init' first.")
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
# 1. Load config
|
|
248
|
+
try:
|
|
249
|
+
with open("epsimo.yaml", "r") as f:
|
|
250
|
+
config = yaml.safe_load(f)
|
|
251
|
+
except Exception as e:
|
|
252
|
+
print(f"❌ Failed to load epsimo.yaml: {e}")
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
project_id = config.get("project_id")
|
|
256
|
+
if not project_id:
|
|
257
|
+
print("❌ project_id missing in epsimo.yaml")
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
# 2. Auth & Client
|
|
261
|
+
try:
|
|
262
|
+
token = get_token()
|
|
263
|
+
client = EpsimoClient(api_key=token)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
print(f"❌ Auth failed: {e}")
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
# 3. Process Assistants
|
|
269
|
+
assistants_config = config.get("assistants", [])
|
|
270
|
+
print(f"📦 Found {len(assistants_config)} assistants in config.")
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
# Fetch current assistants to match by name
|
|
274
|
+
current_assistants = client.assistants.list(project_id)
|
|
275
|
+
asst_map = {a["name"]: a for a in current_assistants}
|
|
276
|
+
|
|
277
|
+
for asst_cfg in assistants_config:
|
|
278
|
+
name = asst_cfg.get("name")
|
|
279
|
+
if not name: continue
|
|
280
|
+
|
|
281
|
+
model = asst_cfg.get("model", "gpt-4o")
|
|
282
|
+
instructions = asst_cfg.get("instructions", "")
|
|
283
|
+
tools = asst_cfg.get("tools", [])
|
|
284
|
+
|
|
285
|
+
if name in asst_map:
|
|
286
|
+
asst_id = asst_map[name]["assistant_id"]
|
|
287
|
+
print(f"🔄 Updating assistant: {name} ({asst_id})...")
|
|
288
|
+
client.assistants.update(project_id, asst_id, {
|
|
289
|
+
"name": name,
|
|
290
|
+
"config": {
|
|
291
|
+
"configurable": {
|
|
292
|
+
"type": "agent",
|
|
293
|
+
"type==agent/model": model,
|
|
294
|
+
"type==agent/system_message": instructions,
|
|
295
|
+
"type==agent/tools": tools
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
else:
|
|
300
|
+
print(f"✨ Creating assistant: {name}...")
|
|
301
|
+
client.assistants.create(
|
|
302
|
+
project_id=project_id,
|
|
303
|
+
name=name,
|
|
304
|
+
model=model,
|
|
305
|
+
instructions=instructions,
|
|
306
|
+
tools=tools
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
print("✅ Deployment complete!")
|
|
310
|
+
|
|
311
|
+
except Exception as e:
|
|
312
|
+
print(f"❌ Deployment failed: {e}")
|
|
313
|
+
|
|
314
|
+
def cmd_db(args):
|
|
315
|
+
"""Query the structured state (virtual database) of a thread."""
|
|
316
|
+
print(f"📊 Querying Virtual Database for thread {args.thread_id}...")
|
|
317
|
+
try:
|
|
318
|
+
token = get_token()
|
|
319
|
+
client = EpsimoClient(api_key=token)
|
|
320
|
+
state = client.threads.get_state(args.project_id, args.thread_id)
|
|
321
|
+
|
|
322
|
+
# The state typically has 'values' which is our DB
|
|
323
|
+
values = state.get("values", {})
|
|
324
|
+
|
|
325
|
+
if not values:
|
|
326
|
+
print("📭 Database is empty.")
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
print("\n=== Current State (JSON) ===")
|
|
330
|
+
print(json.dumps(values, indent=2))
|
|
331
|
+
print("============================\n")
|
|
332
|
+
|
|
333
|
+
except Exception as e:
|
|
334
|
+
print(f"❌ Failed to query database: {e}")
|
|
335
|
+
|
|
336
|
+
def cmd_db_set(args):
|
|
337
|
+
"""Set a value in the thread's virtual database."""
|
|
338
|
+
print(f"📝 Setting {args.key} = {args.value} in thread {args.thread_id}...")
|
|
339
|
+
try:
|
|
340
|
+
# Parse value as JSON if possible, otherwise keep as string
|
|
341
|
+
try:
|
|
342
|
+
val = json.loads(args.value)
|
|
343
|
+
except:
|
|
344
|
+
val = args.value
|
|
345
|
+
|
|
346
|
+
token = get_token()
|
|
347
|
+
client = EpsimoClient(api_key=token)
|
|
348
|
+
client.threads.set_state(args.project_id, args.thread_id, {args.key: val})
|
|
349
|
+
print("✅ State updated successfully.")
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
print(f"❌ Failed to update database: {e}")
|
|
353
|
+
|
|
354
|
+
def cmd_create(args):
|
|
355
|
+
"""Scaffold a new Epsimo MVP project."""
|
|
356
|
+
project_name = args.name
|
|
357
|
+
project_slug = project_name.lower().replace(" ", "-")
|
|
358
|
+
target_dir = os.path.abspath(project_slug)
|
|
359
|
+
|
|
360
|
+
print(f"🏗️ Creating new Epsimo MVP project: {project_name}...")
|
|
361
|
+
|
|
362
|
+
if os.path.exists(target_dir):
|
|
363
|
+
print(f"❌ Directory {project_slug} already exists.")
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
# Path to templates inside the skill
|
|
367
|
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
368
|
+
template_dir = os.path.join(base_dir, "templates", "next-mvp")
|
|
369
|
+
|
|
370
|
+
if not os.path.exists(template_dir):
|
|
371
|
+
print(f"❌ Template directory not found at {template_dir}")
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
os.makedirs(target_dir)
|
|
376
|
+
|
|
377
|
+
# 1. Walk through template dir and copy/transform files
|
|
378
|
+
for root, dirs, files in os.walk(template_dir):
|
|
379
|
+
rel_path = os.path.relpath(root, template_dir)
|
|
380
|
+
|
|
381
|
+
# Create subdirectories
|
|
382
|
+
for d in dirs:
|
|
383
|
+
os.makedirs(os.path.join(target_dir, rel_path, d), exist_ok=True)
|
|
384
|
+
|
|
385
|
+
# Copy files
|
|
386
|
+
for f in files:
|
|
387
|
+
if not f.endswith(".tmpl"): continue
|
|
388
|
+
|
|
389
|
+
src_path = os.path.join(root, f)
|
|
390
|
+
dest_filename = f.replace(".tmpl", "")
|
|
391
|
+
dest_path = os.path.join(target_dir, rel_path, dest_filename)
|
|
392
|
+
|
|
393
|
+
with open(src_path, "r") as src_f:
|
|
394
|
+
content = src_f.read()
|
|
395
|
+
|
|
396
|
+
# Replace placeholders
|
|
397
|
+
content = content.replace("{{PROJECT_NAME}}", project_name)
|
|
398
|
+
content = content.replace("{{PROJECT_SLUG}}", project_slug)
|
|
399
|
+
content = content.replace("{{ASSISTANT_ID}}", "TODO_DEPLOY_FIRST")
|
|
400
|
+
|
|
401
|
+
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
|
402
|
+
with open(dest_path, "w") as dest_f:
|
|
403
|
+
dest_f.write(content)
|
|
404
|
+
|
|
405
|
+
# 2. Copy the UI components from the current codebase to the new project
|
|
406
|
+
# In a real framework, this would be an npm install @epsimo/ui
|
|
407
|
+
# For this MVP, we copy the files.
|
|
408
|
+
print("📦 Injecting Epsimo UI Kit...")
|
|
409
|
+
# Since we are in the context of the current repo:
|
|
410
|
+
src_ui_dir = "/Users/thierry/code/epsimo-frontend/src/components/epsimo"
|
|
411
|
+
dest_ui_dir = os.path.join(target_dir, "components", "epsimo")
|
|
412
|
+
|
|
413
|
+
import shutil
|
|
414
|
+
if os.path.exists(src_ui_dir):
|
|
415
|
+
shutil.copytree(src_ui_dir, dest_ui_dir, dirs_exist_ok=True)
|
|
416
|
+
|
|
417
|
+
print(f"\n✅ Project {project_name} created successfully at ./{project_slug}")
|
|
418
|
+
print("\nNext steps:")
|
|
419
|
+
print(f"1. cd {project_slug}")
|
|
420
|
+
print("2. npm install")
|
|
421
|
+
print("3. epsimo init (to link to platform)")
|
|
422
|
+
print("4. epsimo deploy (to create assistants)")
|
|
423
|
+
print("5. npm run dev")
|
|
424
|
+
|
|
425
|
+
except Exception as e:
|
|
426
|
+
print(f"❌ Failed to create project: {e}")
|
|
427
|
+
|
|
428
|
+
def cmd_run(args):
|
|
429
|
+
"""Run an interactive chat with an assistant."""
|
|
430
|
+
print(f"▶️ Running Assistant: {args.assistant_id}")
|
|
431
|
+
|
|
432
|
+
# 1. Initialize Client
|
|
433
|
+
try:
|
|
434
|
+
client = EpsimoClient() # Auto-loads token from file via get_token in auth logic if not passed,
|
|
435
|
+
# BUT client.py currently expects api_key env var or arg.
|
|
436
|
+
# Let's update client to try get_token if nothing passed?
|
|
437
|
+
# For now, explicit:
|
|
438
|
+
token = get_token()
|
|
439
|
+
client = EpsimoClient(api_key=token)
|
|
440
|
+
except Exception as e:
|
|
441
|
+
print(f"❌ Auth failed: {e}. Try 'epsimo auth'.")
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
# 2. Verify Assistant Exists (and get project context implicitly?)
|
|
445
|
+
# We need a project_id to run things.
|
|
446
|
+
# CLI args should probably include project_id or we find the assistant.
|
|
447
|
+
# Finding assistant across all projects is hard without a "search" endpoint.
|
|
448
|
+
|
|
449
|
+
if not args.project_id:
|
|
450
|
+
print("❌ --project-id is currently required.")
|
|
451
|
+
return
|
|
452
|
+
|
|
453
|
+
# 3. Create Thread
|
|
454
|
+
print("🧵 Creating session thread...")
|
|
455
|
+
try:
|
|
456
|
+
thread = client.threads.create(args.project_id, "CLI Session", args.assistant_id)
|
|
457
|
+
thread_id = thread["thread_id"]
|
|
458
|
+
except Exception as e:
|
|
459
|
+
print(f"❌ Failed to create thread: {e}")
|
|
460
|
+
return
|
|
461
|
+
|
|
462
|
+
# 4. Chat Loop
|
|
463
|
+
print(f"✅ Ready! (Thread: {thread_id})")
|
|
464
|
+
print("Type 'exit' to quit.\n")
|
|
465
|
+
|
|
466
|
+
while True:
|
|
467
|
+
try:
|
|
468
|
+
user_input = input("You > ")
|
|
469
|
+
if user_input.lower() in ["exit", "quit"]:
|
|
470
|
+
break
|
|
471
|
+
|
|
472
|
+
print("Bot > ", end="", flush=True)
|
|
473
|
+
stream = client.threads.run_stream(
|
|
474
|
+
project_id=args.project_id,
|
|
475
|
+
thread_id=thread_id,
|
|
476
|
+
assistant_id=args.assistant_id,
|
|
477
|
+
message=user_input
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
for chunk in stream:
|
|
481
|
+
# Handle both list and dict chunks as discovered in testing
|
|
482
|
+
content = None
|
|
483
|
+
if isinstance(chunk, list):
|
|
484
|
+
for item in chunk:
|
|
485
|
+
if isinstance(item, dict) and "content" in item:
|
|
486
|
+
content = item["content"]
|
|
487
|
+
break
|
|
488
|
+
elif isinstance(chunk, dict) and "content" in chunk:
|
|
489
|
+
content = chunk["content"]
|
|
490
|
+
|
|
491
|
+
if content:
|
|
492
|
+
sys.stdout.write(content)
|
|
493
|
+
sys.stdout.flush()
|
|
494
|
+
print("\n")
|
|
495
|
+
|
|
496
|
+
except KeyboardInterrupt:
|
|
497
|
+
break
|
|
498
|
+
except Exception as e:
|
|
499
|
+
print(f"\n❌ Error: {e}")
|
|
500
|
+
break
|
|
501
|
+
|
|
502
|
+
def main():
|
|
503
|
+
parser = argparse.ArgumentParser(description="Epsimo Agent Framework CLI")
|
|
504
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
505
|
+
|
|
506
|
+
# epsimo init
|
|
507
|
+
init_parser = subparsers.add_parser("init", help="Initialize a new project")
|
|
508
|
+
init_parser.add_argument("--name", help="Project name (defaults to current directory)")
|
|
509
|
+
init_parser.set_defaults(func=cmd_init)
|
|
510
|
+
|
|
511
|
+
# epsimo deploy
|
|
512
|
+
deploy_parser = subparsers.add_parser("deploy", help="Deploy config from epsimo.yaml")
|
|
513
|
+
deploy_parser.set_defaults(func=cmd_deploy)
|
|
514
|
+
|
|
515
|
+
# epsimo create
|
|
516
|
+
create_parser = subparsers.add_parser("create", help="Create a new Epsimo MVP project")
|
|
517
|
+
create_parser.add_argument("name", help="Name of the project")
|
|
518
|
+
create_parser.set_defaults(func=cmd_create)
|
|
519
|
+
|
|
520
|
+
# epsimo auth
|
|
521
|
+
auth_parser = subparsers.add_parser("auth", help="Login to Epsimo")
|
|
522
|
+
auth_parser.set_defaults(func=cmd_auth)
|
|
523
|
+
|
|
524
|
+
# epsimo whoami
|
|
525
|
+
whoami_parser = subparsers.add_parser("whoami", help="Show current user info")
|
|
526
|
+
whoami_parser.set_defaults(func=cmd_whoami)
|
|
527
|
+
|
|
528
|
+
# epsimo projects
|
|
529
|
+
projects_parser = subparsers.add_parser("projects", help="List projects")
|
|
530
|
+
projects_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
531
|
+
projects_parser.set_defaults(func=cmd_projects)
|
|
532
|
+
|
|
533
|
+
# epsimo credits {balance, buy}
|
|
534
|
+
credits_parser = subparsers.add_parser("credits", help="Manage credits and thread usage")
|
|
535
|
+
credits_subparsers = credits_parser.add_subparsers(dest="credits_command", help="Credits command")
|
|
536
|
+
|
|
537
|
+
balance_parser = credits_subparsers.add_parser("balance", help="Check current credit balance")
|
|
538
|
+
balance_parser.set_defaults(func=cmd_balance)
|
|
539
|
+
|
|
540
|
+
buy_parser = credits_subparsers.add_parser("buy", help="Buy more credits")
|
|
541
|
+
buy_parser.add_argument("--quantity", type=int, required=True, help="Number of threads to purchase")
|
|
542
|
+
buy_parser.add_argument("--amount", type=float, help="Total amount to pay in EUR (optional, calculated if omitted)")
|
|
543
|
+
buy_parser.set_defaults(func=cmd_buy)
|
|
544
|
+
|
|
545
|
+
# epsimo assistants --project-id X
|
|
546
|
+
assistants_parser = subparsers.add_parser("assistants", help="List assistants")
|
|
547
|
+
assistants_parser.add_argument("--project-id", required=True, help="Project ID")
|
|
548
|
+
assistants_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
549
|
+
assistants_parser.set_defaults(func=cmd_assistants)
|
|
550
|
+
|
|
551
|
+
# epsimo threads --project-id X
|
|
552
|
+
threads_parser = subparsers.add_parser("threads", help="List threads")
|
|
553
|
+
threads_parser.add_argument("--project-id", required=True, help="Project ID")
|
|
554
|
+
threads_parser.set_defaults(func=cmd_threads)
|
|
555
|
+
|
|
556
|
+
# epsimo run --project-id X --assistant-id Y
|
|
557
|
+
run_parser = subparsers.add_parser("run", help="Run a terminal chat session")
|
|
558
|
+
run_parser.add_argument("--project-id", required=True, help="Project ID")
|
|
559
|
+
run_parser.add_argument("--assistant-id", required=True, help="Assistant ID")
|
|
560
|
+
run_parser.set_defaults(func=cmd_run)
|
|
561
|
+
|
|
562
|
+
# epsimo db query --project-id X --thread-id Y
|
|
563
|
+
db_parser = subparsers.add_parser("db", help="Manage Virtual Database state")
|
|
564
|
+
db_subparsers = db_parser.add_subparsers(dest="db_command", help="DB command")
|
|
565
|
+
|
|
566
|
+
query_parser = db_subparsers.add_parser("query", help="Query the current state of a thread")
|
|
567
|
+
query_parser.add_argument("--project-id", required=True, help="Project ID")
|
|
568
|
+
query_parser.add_argument("--thread-id", required=True, help="Thread ID")
|
|
569
|
+
query_parser.set_defaults(func=cmd_db)
|
|
570
|
+
|
|
571
|
+
set_parser = db_subparsers.add_parser("set", help="Set a value in the thread state")
|
|
572
|
+
set_parser.add_argument("--project-id", required=True, help="Project ID")
|
|
573
|
+
set_parser.add_argument("--thread-id", required=True, help="Thread ID")
|
|
574
|
+
set_parser.add_argument("--key", required=True, help="Key to set")
|
|
575
|
+
set_parser.add_argument("--value", required=True, help="Value to set (JSON strings supported)")
|
|
576
|
+
set_parser.set_defaults(func=cmd_db_set)
|
|
577
|
+
|
|
578
|
+
args = parser.parse_args()
|
|
579
|
+
|
|
580
|
+
if hasattr(args, "func"):
|
|
581
|
+
args.func(args)
|
|
582
|
+
else:
|
|
583
|
+
parser.print_help()
|
|
584
|
+
|
|
585
|
+
if __name__ == "__main__":
|
|
586
|
+
main()
|
package/epsimo/client.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
from .resources.projects import Projects
|
|
4
|
+
from .resources.assistants import Assistants
|
|
5
|
+
from .resources.threads import Threads
|
|
6
|
+
from .resources.files import Files
|
|
7
|
+
from .resources.credits import Credits
|
|
8
|
+
from .resources.db import Database
|
|
9
|
+
|
|
10
|
+
class EpsimoClient:
|
|
11
|
+
def __init__(self, api_key=None, base_url=None):
|
|
12
|
+
self.api_key = api_key or os.environ.get("EPSIMO_API_KEY")
|
|
13
|
+
self.base_url = base_url or os.environ.get("EPSIMO_API_URL", "https://api.epsimoagents.com")
|
|
14
|
+
|
|
15
|
+
# In the future, we might support API Keys directly.
|
|
16
|
+
# For now, we reuse the JWT token logic but wrapped cleanly.
|
|
17
|
+
# If the user passes a token as api_key, we use it.
|
|
18
|
+
self._session = requests.Session()
|
|
19
|
+
if self.api_key:
|
|
20
|
+
self._session.headers.update({"Authorization": f"Bearer {self.api_key}"})
|
|
21
|
+
|
|
22
|
+
self.projects = Projects(self)
|
|
23
|
+
self.assistants = Assistants(self)
|
|
24
|
+
self.threads = Threads(self)
|
|
25
|
+
self.files = Files(self)
|
|
26
|
+
self.credits = Credits(self)
|
|
27
|
+
self.db = Database(self)
|
|
28
|
+
|
|
29
|
+
def request(self, method, path, **kwargs):
|
|
30
|
+
url = f"{self.base_url}{path}"
|
|
31
|
+
response = self._session.request(method, url, **kwargs)
|
|
32
|
+
if not response.ok:
|
|
33
|
+
try:
|
|
34
|
+
import json
|
|
35
|
+
print(f"❌ API Error ({response.status_code}): {json.dumps(response.json(), indent=2)}")
|
|
36
|
+
except:
|
|
37
|
+
print(f"❌ API Error ({response.status_code}): {response.text}")
|
|
38
|
+
response.raise_for_status()
|
|
39
|
+
if response.status_code == 204:
|
|
40
|
+
return None
|
|
41
|
+
return response.json()
|
|
42
|
+
|
|
43
|
+
def get_project_headers(self, project_id):
|
|
44
|
+
"""Fetch/Construct headers including project-specific token."""
|
|
45
|
+
# Using the clean client.projects access
|
|
46
|
+
resp = self.projects.get(project_id)
|
|
47
|
+
token = resp.get("access_token") or resp.get("token") or resp.get("jwt_token")
|
|
48
|
+
|
|
49
|
+
if not token:
|
|
50
|
+
# Just a warning or error?
|
|
51
|
+
print(f"Warning: No specific token found for project {project_id}")
|
|
52
|
+
|
|
53
|
+
return {"Authorization": f"Bearer {token}"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class Assistants:
|
|
2
|
+
def __init__(self, client):
|
|
3
|
+
self.client = client
|
|
4
|
+
|
|
5
|
+
def list(self, project_id):
|
|
6
|
+
"""List assistants in a project."""
|
|
7
|
+
headers = self.client.get_project_headers(project_id)
|
|
8
|
+
return self.client.request("GET", "/assistants/", headers=headers)
|
|
9
|
+
|
|
10
|
+
def create(self, project_id, name, model="gpt-4o", instructions="", tools=None, public=False):
|
|
11
|
+
"""Create a new assistant."""
|
|
12
|
+
headers = self.client.get_project_headers(project_id)
|
|
13
|
+
|
|
14
|
+
# Construct configurable config
|
|
15
|
+
configurable = {
|
|
16
|
+
"type": "agent",
|
|
17
|
+
"type==agent/agent_type": "GPT-4O", # legacy/specific key
|
|
18
|
+
"type==agent/model": model,
|
|
19
|
+
"type==agent/system_message": instructions,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if tools:
|
|
23
|
+
# Normalize tools format if needed, or assume list of dicts
|
|
24
|
+
# The API expects specific structure
|
|
25
|
+
configurable["type==agent/tools"] = tools
|
|
26
|
+
|
|
27
|
+
payload = {
|
|
28
|
+
"name": name,
|
|
29
|
+
"config": {"configurable": configurable},
|
|
30
|
+
"public": public
|
|
31
|
+
}
|
|
32
|
+
return self.client.request("POST", "/assistants/", json=payload, headers=headers)
|
|
33
|
+
|
|
34
|
+
def get(self, project_id, assistant_id):
|
|
35
|
+
"""Get assistant details."""
|
|
36
|
+
headers = self.client.get_project_headers(project_id)
|
|
37
|
+
return self.client.request("GET", f"/assistants/{assistant_id}", headers=headers)
|
|
38
|
+
|
|
39
|
+
def update(self, project_id, assistant_id, payload):
|
|
40
|
+
"""Update assistant settings."""
|
|
41
|
+
headers = self.client.get_project_headers(project_id)
|
|
42
|
+
return self.client.request("PUT", f"/assistants/{assistant_id}", json=payload, headers=headers)
|
|
43
|
+
|
|
44
|
+
def delete(self, project_id, assistant_id):
|
|
45
|
+
"""Delete an assistant."""
|
|
46
|
+
headers = self.client.get_project_headers(project_id)
|
|
47
|
+
return self.client.request("DELETE", f"/assistants/{assistant_id}", headers=headers)
|