orrin-cli 0.3.0b1__tar.gz → 0.3.0b3__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,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: orrin-cli
3
+ Version: 0.3.0b3
4
+ Summary: Orrin CLI
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: typer[all]>=0.12.0
9
+ Requires-Dist: click<8.2
10
+ Dynamic: license-file
11
+
12
+ # Orrin CLI v0.3.0b3
13
+
14
+ ## What's New
15
+
16
+ In v0.3.0b3, we introduce the ability to mount custom handlers to Terminal Sessions, start mounted handlers, and unmount handlers.
@@ -0,0 +1,5 @@
1
+ # Orrin CLI v0.3.0b3
2
+
3
+ ## What's New
4
+
5
+ In v0.3.0b3, we introduce the ability to mount custom handlers to Terminal Sessions, start mounted handlers, and unmount handlers.
@@ -1,16 +1,19 @@
1
1
  import webbrowser
2
- from click import Choice
3
- import typer
4
- import requests
2
+ from click import Choice # type: ignore
3
+ import typer # type: ignore
4
+ import requests # type: ignore
5
5
  import json
6
6
  import os
7
7
  from pathlib import Path
8
- from platformdirs import user_config_dir
9
- import yaml, tomli, toml
8
+ from platformdirs import user_config_dir # type: ignore
9
+ import yaml, tomli, toml # type: ignore
10
10
  from pathlib import Path
11
11
  from orrin.Spinner import Spinner
12
- import websockets
12
+ import websockets # type: ignore
13
13
  import asyncio
14
+ import importlib.util
15
+ import sys
16
+ from pathlib import Path
14
17
 
15
18
  app = typer.Typer(help="""
16
19
  Welcome to Orrin CLI v0.1.9!\n\n
@@ -1196,7 +1199,276 @@ def start_default_handler():
1196
1199
  typer.echo('\nValue must be non-zero, or within bounds.')
1197
1200
  return
1198
1201
 
1199
- asyncio.run(establish_default_handler(config['terminal_sessions'][session_to_start]['session_id'], config['api_key']))
1202
+ try:
1203
+ asyncio.run(establish_default_handler(config['terminal_sessions'][session_to_start]['session_id'], config['api_key'], refresh))
1204
+ except KeyboardInterrupt as _:
1205
+ typer.echo('Exited handler connection. Connection closed.')
1206
+
1207
+ # Volantarily call `refresh` to ensure sessions are updated
1208
+ refresh()
1209
+ except Exception as e:
1210
+ typer.echo(f'Exception: {str(e)}')
1211
+
1212
+ def load_handler_module(handler_path: str | Path):
1213
+ """Load a handler module with proper support for relative imports."""
1214
+ handler_path = Path(handler_path).resolve()
1215
+
1216
+ if not handler_path.is_file():
1217
+ typer.echo(f"\n❌ Could not load file: {handler_path}")
1218
+ raise typer.Exit(1)
1219
+
1220
+ module_name = handler_path.stem
1221
+ module_dir = handler_path.parent
1222
+
1223
+ # Create module spec
1224
+ spec = importlib.util.spec_from_file_location(module_name, handler_path)
1225
+ if spec is None:
1226
+ typer.echo(f"\n❌ Could not create spec for: {handler_path}")
1227
+ raise typer.Exit(1)
1228
+
1229
+ # Create module
1230
+ module = importlib.util.module_from_spec(spec)
1231
+
1232
+ # === Critical for relative imports (especially 'from . import TSHandler') ===
1233
+ # Set the package name so relative imports work
1234
+ try:
1235
+ # If the file is inside the orrinsdk package, use proper package name
1236
+ if "orrinsdk" in str(handler_path):
1237
+ module.__package__ = "orrinsdk"
1238
+ else:
1239
+ # Fallback
1240
+ module.__package__ = module_dir.name
1241
+ except:
1242
+ module.__package__ = module_name
1243
+
1244
+ # Register and execute
1245
+ sys.modules[spec.name] = module
1246
+ spec.loader.exec_module(module)
1247
+
1248
+ return module
1249
+
1250
+ @sessions.command(help='Mount handler to a terminal session')
1251
+ def mount_handler(
1252
+ handler_file: str,
1253
+ run: bool = typer.Option(False, "--run", "-r", help="Run the handler immediately after mounting")
1254
+ ):
1255
+ """Mount a custom handler to a terminal session."""
1256
+
1257
+ # === Validation ===
1258
+ if not os.path.isfile(handler_file):
1259
+ typer.echo("\n❌ " + typer.style("File not found:", fg=typer.colors.RED) + f" {handler_file}")
1260
+ raise typer.Exit(1)
1261
+
1262
+ config = load_config()
1263
+
1264
+ if config is None or "api_key" not in config:
1265
+ typer.echo("\n⚠️ " + typer.style("Developer API not configured.", fg=typer.colors.YELLOW))
1266
+ typer.echo(" Run: " + typer.style("orrin config configure-dev", fg=typer.colors.BRIGHT_YELLOW, bold=True))
1267
+ raise typer.Exit(1)
1268
+
1269
+ if not config.get("terminal_sessions"):
1270
+ typer.echo("\n⚠️ " + typer.style("No terminal sessions found.", fg=typer.colors.YELLOW))
1271
+ typer.echo(" Create one with: " + typer.style("orrin sessions create", fg=typer.colors.BRIGHT_YELLOW, bold=True))
1272
+ raise typer.Exit(1)
1273
+
1274
+ # === Show available sessions ===
1275
+ typer.echo("\n" + typer.style("Available Terminal Sessions:", fg=typer.colors.BLUE, bold=True))
1276
+ for i, session in enumerate(config["terminal_sessions"]):
1277
+ typer.echo(f" {i+1}. {session['session_name']} (ID: {session['session_id']})")
1278
+
1279
+ # === User selection ===
1280
+ try:
1281
+ selection = typer.prompt(
1282
+ "\nSelect session number",
1283
+ type=int,
1284
+ default=1
1285
+ )
1286
+ session_idx = selection - 1
1287
+
1288
+ if session_idx < 0 or session_idx >= len(config["terminal_sessions"]):
1289
+ typer.echo("\n❌ " + typer.style("Invalid selection.", fg=typer.colors.RED))
1290
+ raise typer.Exit(1)
1291
+
1292
+ except Exception:
1293
+ typer.echo("\n❌ " + typer.style("Invalid input.", fg=typer.colors.RED))
1294
+ raise typer.Exit(1)
1295
+
1296
+ selected_session = config["terminal_sessions"][session_idx]
1297
+
1298
+ # === Load and validate handler ===
1299
+ try:
1300
+ module = load_handler_module(handler_file) # ← Recommended to extract this
1301
+ if not hasattr(module, "handler_entry"):
1302
+ raise AttributeError("Function 'handler_entry' not found")
1303
+ handler_func = getattr(module, "handler_entry")
1304
+ except Exception as e:
1305
+ typer.echo("\n❌ " + typer.style(f"Failed to load handler: {e}", fg=typer.colors.RED))
1306
+ raise typer.Exit(1)
1307
+
1308
+ # === Check for existing mounted handler ===
1309
+ if "mounted_handlers" in config:
1310
+ for mh in config["mounted_handlers"]:
1311
+ if mh.get("terminal_session_id") == selected_session["session_id"]:
1312
+ typer.echo("\n⚠️ " + typer.style("This session already has a mounted handler.", fg=typer.colors.YELLOW))
1313
+ typer.echo(f" Current handler: {mh['path']}")
1314
+ typer.echo(" Unmount it first using: " + typer.style("orrin sessions unmount-handler", fg=typer.colors.BRIGHT_YELLOW))
1315
+ raise typer.Exit(1)
1316
+
1317
+ # === Run handler if requested ===
1318
+ if run:
1319
+ typer.echo(f"\n▶️ Running handler {typer.style(Path(handler_file).name, fg=typer.colors.BRIGHT_GREEN)}...")
1320
+ try:
1321
+ handler_func(terminal_session_id=selected_session["session_id"])
1322
+ typer.echo("✅ Handler executed successfully.\n")
1323
+ except Exception as e:
1324
+ typer.echo("⚠️ " + typer.style(f"Handler execution failed: {e}", fg=typer.colors.RED))
1325
+
1326
+ # === Save mounted handler ===
1327
+ if "mounted_handlers" not in config:
1328
+ config["mounted_handlers"] = []
1329
+
1330
+ config["mounted_handlers"].append({
1331
+ "path": str(handler_file),
1332
+ "terminal_session_id": selected_session["session_id"]
1333
+ })
1334
+
1335
+ write_config(config)
1336
+
1337
+ # === Final success message ===
1338
+ typer.echo("\n" + typer.style("✅ Successfully mounted handler!", fg=typer.colors.GREEN, bold=True))
1339
+ typer.echo(f" Handler : {typer.style(Path(handler_file).name, fg=typer.colors.BRIGHT_GREEN)}")
1340
+ typer.echo(f" Session : {typer.style(selected_session['session_name'], bold=True)}")
1341
+ typer.echo(f" Session ID : {selected_session['session_id']}")
1342
+
1343
+ @sessions.command(help='Unmount a custom handler from a terminal session')
1344
+ def unmount_handler():
1345
+ config = load_config()
1346
+
1347
+ if config is None or "api_key" not in config:
1348
+ typer.echo("\n⚠️ " + typer.style("Developer API not configured.", fg=typer.colors.YELLOW))
1349
+ typer.echo(" Run: " + typer.style("orrin config configure-dev", fg=typer.colors.BRIGHT_YELLOW, bold=True))
1350
+ raise typer.Exit(1)
1351
+
1352
+ if not config.get("terminal_sessions"):
1353
+ typer.echo("\n⚠️ " + typer.style("No terminal sessions found.", fg=typer.colors.YELLOW))
1354
+ typer.echo(" Create one with: " + typer.style("orrin sessions create", fg=typer.colors.BRIGHT_YELLOW, bold=True))
1355
+ raise typer.Exit(1)
1356
+
1357
+ if not config.get('mounted_handlers'):
1358
+ typer.echo("\n⚠️ " + typer.style("No mounted handlers.", fg=typer.colors.YELLOW))
1359
+ typer.echo(" Mount a new handler with: " + typer.style("orrin sessions mount-handler", fg=typer.colors.BRIGHT_YELLOW, bold=True))
1360
+ raise typer.Exit(1)
1361
+
1362
+ # Get terminal sessions with custom handlers mounted
1363
+ sessions_with_mh = []
1364
+ for mh in config.get('mounted_handlers'):
1365
+ sessions_with_mh.append(mh['terminal_session_id'])
1366
+
1367
+ # === Show available sessions ===
1368
+ typer.echo("\n" + typer.style("Available Terminal Sessions:", fg=typer.colors.BLUE, bold=True))
1369
+ for i, sessionID in enumerate(sessions_with_mh):
1370
+ typer.echo(f" {i+1}. {sessionID}")
1371
+
1372
+ # === User selection ===
1373
+ try:
1374
+ selection = typer.prompt(
1375
+ "\nSelect session number",
1376
+ type=int,
1377
+ default=1
1378
+ )
1379
+ session_idx = selection - 1
1380
+
1381
+ if session_idx < 0 or session_idx >= len(sessions_with_mh):
1382
+ typer.echo("\n❌ " + typer.style("Invalid selection.", fg=typer.colors.RED))
1383
+ raise typer.Exit(1)
1384
+ except Exception as e:
1385
+ typer.echo("\n❌ " + typer.style(f"Failed to load handler: {e}", fg=typer.colors.RED))
1386
+ raise typer.Exit(1)
1387
+
1388
+ for i in range(len(config.get('mounted_handlers'))):
1389
+ if config.get('mounted_handlers')[i]['terminal_session_id'] == sessions_with_mh[session_idx]:
1390
+ del config.get('mounted_handlers')[i]
1391
+ break
1392
+
1393
+ if config['mounted_handlers'] == []:
1394
+ del config['mounted_handlers']
1395
+
1396
+ write_config(config)
1397
+ typer.echo("✅ Handler unmounted successfully.\n")
1398
+
1399
+ @sessions.command(help='Run a terminal sessions custom handler')
1400
+ def run_handler():
1401
+ config = load_config()
1402
+
1403
+ if config is None or "api_key" not in config:
1404
+ typer.echo("\n⚠️ " + typer.style("Developer API not configured.", fg=typer.colors.YELLOW))
1405
+ typer.echo(" Run: " + typer.style("orrin config configure-dev", fg=typer.colors.BRIGHT_YELLOW, bold=True))
1406
+ raise typer.Exit(1)
1407
+
1408
+ if not config.get("terminal_sessions"):
1409
+ typer.echo("\n⚠️ " + typer.style("No terminal sessions found.", fg=typer.colors.YELLOW))
1410
+ typer.echo(" Create one with: " + typer.style("orrin sessions create", fg=typer.colors.BRIGHT_YELLOW, bold=True))
1411
+ raise typer.Exit(1)
1412
+
1413
+ if not config.get('mounted_handlers'):
1414
+ typer.echo("\n⚠️ " + typer.style("No mounted handlers.", fg=typer.colors.YELLOW))
1415
+ typer.echo(" Mount a new handler with: " + typer.style("orrin sessions mount-handler", fg=typer.colors.BRIGHT_YELLOW, bold=True))
1416
+ raise typer.Exit(1)
1417
+
1418
+ # Get terminal sessions with custom handlers mounted
1419
+ sessions_with_mh = []
1420
+ for mh in config.get('mounted_handlers'):
1421
+ sessions_with_mh.append(mh['terminal_session_id'])
1422
+
1423
+ # === Show available sessions ===
1424
+ typer.echo("\n" + typer.style("Available Terminal Sessions:", fg=typer.colors.BLUE, bold=True))
1425
+ for i, sessionID in enumerate(sessions_with_mh):
1426
+ typer.echo(f" {i+1}. {sessionID}")
1427
+
1428
+ # === User selection ===
1429
+ try:
1430
+ selection = typer.prompt(
1431
+ "\nSelect session number",
1432
+ type=int,
1433
+ default=1
1434
+ )
1435
+ session_idx = selection - 1
1436
+
1437
+ if session_idx < 0 or session_idx >= len(sessions_with_mh):
1438
+ typer.echo("\n❌ " + typer.style("Invalid selection.", fg=typer.colors.RED))
1439
+ raise typer.Exit(1)
1440
+ except Exception as e:
1441
+ typer.echo("\n❌ " + typer.style(f"Failed to load handler: {e}", fg=typer.colors.RED))
1442
+ raise typer.Exit(1)
1443
+
1444
+ # === Load and validate handler ===
1445
+ try:
1446
+ handler_file = next(
1447
+ (
1448
+ mh['path']
1449
+ for mh in config.get('mounted_handlers')
1450
+ if mh['terminal_session_id'] == sessions_with_mh[session_idx]
1451
+ ),
1452
+ None
1453
+ )
1454
+
1455
+ if handler_file is None:
1456
+ raise Exception('No mounted handler found for selected terminal session')
1457
+
1458
+ module = load_handler_module(handler_file) # ← Recommended to extract this
1459
+ if not hasattr(module, "handler_entry"):
1460
+ raise AttributeError("Function 'handler_entry' not found")
1461
+ handler_func = getattr(module, "handler_entry")
1462
+ except Exception as e:
1463
+ typer.echo("\n❌ " + typer.style(f"Failed to load handler: {e}", fg=typer.colors.RED))
1464
+ raise typer.Exit(1)
1465
+
1466
+ typer.echo(f"\n▶️ Running handler {typer.style(Path(handler_file).name, fg=typer.colors.BRIGHT_GREEN)}...")
1467
+ try:
1468
+ handler_func(terminal_session_id=sessions_with_mh[session_idx])
1469
+ typer.echo("✅ Handler executed successfully.\n")
1470
+ except Exception as e:
1471
+ typer.echo("⚠️ " + typer.style(f"Handler execution failed: {e}", fg=typer.colors.RED))
1200
1472
 
1201
1473
  @sessions.command(help='Remove a specific connection')
1202
1474
  def remove_connection(session_name: str, type: str, connection_name: str):
@@ -1273,7 +1545,7 @@ def refresh():
1273
1545
 
1274
1546
  if resp.ok:
1275
1547
  sessions = resp.json()['Sessions']
1276
- config['sessions'] = sessions
1548
+ config['terminal_sessions'] = sessions
1277
1549
  write_config(config)
1278
1550
 
1279
1551
  typer.echo(
@@ -1302,6 +1574,43 @@ def refresh():
1302
1574
  fg=typer.colors.RED
1303
1575
  )
1304
1576
  )
1577
+
1578
+ @sessions.command(help='Show all active and inactive sessions')
1579
+ def show():
1580
+ config = load_config()
1581
+
1582
+ if config is None:
1583
+ typer.echo('\nYou have not configured a developer API. Please run:')
1584
+ typer.echo('\t' + typer.style(' orrin config configure-dev ', fg=typer.colors.YELLOW) + '\n')
1585
+ return
1586
+
1587
+ if not 'terminal_sessions' in config:
1588
+ typer.echo('\nYou have no sessions. You can create one by running:')
1589
+ typer.echo('\t' + typer.style(' orrin sessions create ', fg=typer.colors.YELLOW) + '\n')
1590
+ return
1591
+
1592
+ active_sessions = [session for session in config['terminal_sessions'] if session['active']]
1593
+ inactive_sessions = [session for session in config['terminal_sessions'] if not session['active']]
1594
+
1595
+ typer.echo('\nActive Terminal Sessions:')
1596
+
1597
+ if active_sessions == []:
1598
+ typer.echo('\tNo active sessions')
1599
+ else:
1600
+ for i in range(len(active_sessions)):
1601
+ typer.echo('\t' + f'{active_sessions[i]["session_name"]}, id: {active_sessions[i]["session_id"]}')
1602
+
1603
+
1604
+ typer.echo('\nInactive Terminal Sessions:')
1605
+
1606
+ if inactive_sessions == []:
1607
+ typer.echo('\tAll sessions are active')
1608
+ else:
1609
+ for i in range(len(inactive_sessions)):
1610
+ typer.echo('\t' + f'{inactive_sessions[i]["session_name"]}, id: {inactive_sessions[i]["session_id"]}')
1611
+
1612
+ typer.echo('')
1613
+
1305
1614
  # ---------------------------
1306
1615
 
1307
1616
  def main():
@@ -1,26 +1,42 @@
1
1
  import websockets
2
- import json
2
+ import json, subprocess
3
3
  import typer
4
4
 
5
5
  # Helper function: establish websockt connection as default handler
6
6
  # Developer can create a sender websocket
7
- async def establish_default_handler(session_id, auth):
7
+ async def establish_default_handler(session_id, auth, r):
8
8
  async with websockets.connect(
9
- f"wss://stellr-company.com/terminal_sessions/connect/{session_id}",
10
- additional_headers={ 'authorization': auth }
9
+ f"wss://stellr-company.com/terminal_sessions/connect/{session_id}"
11
10
  ) as ws:
12
- await ws.send(json.dumps({'connection_type': 'handler', 'websocket_dependencies': []}))
11
+ # Send authorization
12
+ await ws.send(f'AUTH: {auth}')
13
+
14
+ # Send intitial data telling connection what we are
15
+ await ws.send(json.dumps({
16
+ 'connection_type': 'handler',
17
+ 'websocket_dependencies': [], # no inherent dependencies
18
+ 'handler_name': 'test'
19
+ }))
20
+
13
21
  data = await ws.recv()
14
22
  data = json.loads(data)
15
23
  connection_id = data['connection_id']
16
24
 
17
25
  typer.echo(f'Default handler connection id: {connection_id}\nSession ID: {session_id}\nUse this when creating sender websockets.\nYou are free to create as many custom handlers/senders as you would like, and are not restricted to the default handler.\n')
18
26
 
27
+ r()
28
+
19
29
  while True:
20
30
  data = await ws.recv()
21
31
  data = json.loads(data)
22
32
 
23
- if not 'type' in data: continue
33
+ if not 'type' in data:
34
+ await ws.send(json.dumps({
35
+ 'type': 'respond',
36
+ 'message': 'fuck off.',
37
+ 'to': data['sender'],
38
+ 'response_type': 'string'
39
+ }))
24
40
 
25
41
  match data['type']:
26
42
  case 'close':
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: orrin-cli
3
+ Version: 0.3.0b3
4
+ Summary: Orrin CLI
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: typer[all]>=0.12.0
9
+ Requires-Dist: click<8.2
10
+ Dynamic: license-file
11
+
12
+ # Orrin CLI v0.3.0b3
13
+
14
+ ## What's New
15
+
16
+ In v0.3.0b3, we introduce the ability to mount custom handlers to Terminal Sessions, start mounted handlers, and unmount handlers.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "orrin-cli"
3
3
  readme = "README.md"
4
- version = "0.3.0b1"
4
+ version = "0.3.0b3"
5
5
  description = "Orrin CLI"
6
6
  requires-python = ">=3.9"
7
7
  dependencies = [
@@ -1,18 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: orrin-cli
3
- Version: 0.3.0b1
4
- Summary: Orrin CLI
5
- Requires-Python: >=3.9
6
- Description-Content-Type: text/markdown
7
- License-File: LICENSE
8
- Requires-Dist: typer[all]>=0.12.0
9
- Requires-Dist: click<8.2
10
- Dynamic: license-file
11
-
12
- # Orrin CLI v0.2.0.2
13
-
14
- ## What's New
15
-
16
- In v0.2.0.2, a minor change was made to support new icon sizes of 64x64, 128x128, 256x256, and 512x512.
17
-
18
- Please refer to [the docs](https://stellr-company.com/orrin/docs) for more information over Orrin CLI.
@@ -1,7 +0,0 @@
1
- # Orrin CLI v0.2.0.2
2
-
3
- ## What's New
4
-
5
- In v0.2.0.2, a minor change was made to support new icon sizes of 64x64, 128x128, 256x256, and 512x512.
6
-
7
- Please refer to [the docs](https://stellr-company.com/orrin/docs) for more information over Orrin CLI.
@@ -1,18 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: orrin-cli
3
- Version: 0.3.0b1
4
- Summary: Orrin CLI
5
- Requires-Python: >=3.9
6
- Description-Content-Type: text/markdown
7
- License-File: LICENSE
8
- Requires-Dist: typer[all]>=0.12.0
9
- Requires-Dist: click<8.2
10
- Dynamic: license-file
11
-
12
- # Orrin CLI v0.2.0.2
13
-
14
- ## What's New
15
-
16
- In v0.2.0.2, a minor change was made to support new icon sizes of 64x64, 128x128, 256x256, and 512x512.
17
-
18
- Please refer to [the docs](https://stellr-company.com/orrin/docs) for more information over Orrin CLI.
File without changes
File without changes