cmdop 0.1.26__py3-none-any.whl → 0.1.28__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.
cmdop/__init__.py CHANGED
@@ -128,7 +128,7 @@ from cmdop.logging import (
128
128
  get_log_dir,
129
129
  )
130
130
 
131
- __version__ = "0.1.26"
131
+ __version__ = "0.1.28"
132
132
 
133
133
  __all__ = [
134
134
  # Version
cmdop/services/files.py CHANGED
@@ -53,14 +53,47 @@ class FilesService(BaseService):
53
53
  Provides file system operations.
54
54
 
55
55
  Example:
56
+ >>> # Local IPC - session_id is optional
56
57
  >>> entries = client.files.list("/home/user")
57
- >>> content = client.files.read("/etc/hosts")
58
- >>> client.files.write("/tmp/test.txt", b"Hello")
58
+ >>>
59
+ >>> # Remote - set session_id first
60
+ >>> session = client.terminal.get_active_session()
61
+ >>> client.files.set_session_id(session.session_id)
62
+ >>> entries = client.files.list("/home/user")
63
+ >>>
64
+ >>> # Or pass session_id directly
65
+ >>> entries = client.files.list("/home/user", session_id=session.session_id)
59
66
  """
60
67
 
61
68
  def __init__(self, transport: BaseTransport) -> None:
62
69
  super().__init__(transport)
63
70
  self._stub: Any = None
71
+ self._session_id: str | None = None
72
+
73
+ def set_session_id(self, session_id: str) -> None:
74
+ """
75
+ Set session ID for file operations.
76
+
77
+ Required for remote connections. For local IPC, session_id is optional.
78
+
79
+ Args:
80
+ session_id: Session ID from terminal.get_active_session() or terminal.create()
81
+
82
+ Example:
83
+ >>> session = client.terminal.get_active_session()
84
+ >>> client.files.set_session_id(session.session_id)
85
+ >>> files = client.files.list("/tmp")
86
+ """
87
+ self._session_id = session_id
88
+
89
+ def _get_session_id(self, session_id: str | None = None) -> str:
90
+ """Get session ID from parameter or stored value."""
91
+ sid = session_id or self._session_id
92
+ if not sid:
93
+ # For local IPC, use empty string (server will handle)
94
+ # For remote, this will likely fail but let server return proper error
95
+ return ""
96
+ return sid
64
97
 
65
98
  @property
66
99
  def _get_stub(self) -> Any:
@@ -79,6 +112,7 @@ class FilesService(BaseService):
79
112
  include_hidden: bool = False,
80
113
  page_size: int = 100,
81
114
  page_token: str | None = None,
115
+ session_id: str | None = None,
82
116
  ) -> ListDirectoryResponse:
83
117
  """
84
118
  List directory contents.
@@ -88,6 +122,7 @@ class FilesService(BaseService):
88
122
  include_hidden: Include hidden files
89
123
  page_size: Number of entries per page
90
124
  page_token: Pagination token
125
+ session_id: Session ID (uses stored value if not provided)
91
126
 
92
127
  Returns:
93
128
  Directory listing response
@@ -97,6 +132,7 @@ class FilesService(BaseService):
97
132
  )
98
133
 
99
134
  request = FileListDirectoryRpcRequest(
135
+ session_id=self._get_session_id(session_id),
100
136
  path=path,
101
137
  page_size=page_size,
102
138
  include_hidden=include_hidden,
@@ -131,6 +167,7 @@ class FilesService(BaseService):
131
167
  path: str,
132
168
  offset: int = 0,
133
169
  limit: int = 0,
170
+ session_id: str | None = None,
134
171
  ) -> bytes:
135
172
  """
136
173
  Read file contents.
@@ -139,6 +176,7 @@ class FilesService(BaseService):
139
176
  path: File path to read
140
177
  offset: Byte offset to start reading
141
178
  limit: Maximum bytes to read (0 = entire file)
179
+ session_id: Session ID (uses stored value if not provided)
142
180
 
143
181
  Returns:
144
182
  File contents as bytes
@@ -146,6 +184,7 @@ class FilesService(BaseService):
146
184
  from cmdop._generated.file_rpc.file_crud_pb2 import FileReadRpcRequest
147
185
 
148
186
  request = FileReadRpcRequest(
187
+ session_id=self._get_session_id(session_id),
149
188
  path=path,
150
189
  )
151
190
 
@@ -158,6 +197,7 @@ class FilesService(BaseService):
158
197
  content: bytes | str,
159
198
  create_parents: bool = False,
160
199
  overwrite: bool = True,
200
+ session_id: str | None = None,
161
201
  ) -> None:
162
202
  """
163
203
  Write file contents.
@@ -167,6 +207,7 @@ class FilesService(BaseService):
167
207
  content: Content to write (bytes or string)
168
208
  create_parents: Create parent directories
169
209
  overwrite: Overwrite existing file
210
+ session_id: Session ID (uses stored value if not provided)
170
211
  """
171
212
  from cmdop._generated.file_rpc.file_crud_pb2 import FileWriteRpcRequest
172
213
 
@@ -174,6 +215,7 @@ class FilesService(BaseService):
174
215
  content = content.encode("utf-8")
175
216
 
176
217
  request = FileWriteRpcRequest(
218
+ session_id=self._get_session_id(session_id),
177
219
  path=path,
178
220
  content=content,
179
221
  create_parents=create_parents,
@@ -181,70 +223,95 @@ class FilesService(BaseService):
181
223
 
182
224
  self._call_sync(self._get_stub.FileWrite, request)
183
225
 
184
- def delete(self, path: str, recursive: bool = False) -> None:
226
+ def delete(
227
+ self,
228
+ path: str,
229
+ recursive: bool = False,
230
+ session_id: str | None = None,
231
+ ) -> None:
185
232
  """
186
233
  Delete file or directory.
187
234
 
188
235
  Args:
189
236
  path: Path to delete
190
237
  recursive: Delete directory recursively
238
+ session_id: Session ID (uses stored value if not provided)
191
239
  """
192
240
  from cmdop._generated.file_rpc.file_crud_pb2 import FileDeleteRpcRequest
193
241
 
194
242
  request = FileDeleteRpcRequest(
243
+ session_id=self._get_session_id(session_id),
195
244
  path=path,
196
245
  recursive=recursive,
197
246
  )
198
247
 
199
248
  self._call_sync(self._get_stub.FileDelete, request)
200
249
 
201
- def copy(self, source: str, destination: str) -> None:
250
+ def copy(
251
+ self,
252
+ source: str,
253
+ destination: str,
254
+ session_id: str | None = None,
255
+ ) -> None:
202
256
  """
203
257
  Copy file or directory.
204
258
 
205
259
  Args:
206
260
  source: Source path
207
261
  destination: Destination path
262
+ session_id: Session ID (uses stored value if not provided)
208
263
  """
209
264
  from cmdop._generated.file_rpc.file_crud_pb2 import FileCopyRpcRequest
210
265
 
211
266
  request = FileCopyRpcRequest(
267
+ session_id=self._get_session_id(session_id),
212
268
  source_path=source,
213
269
  destination_path=destination,
214
270
  )
215
271
 
216
272
  self._call_sync(self._get_stub.FileCopy, request)
217
273
 
218
- def move(self, source: str, destination: str) -> None:
274
+ def move(
275
+ self,
276
+ source: str,
277
+ destination: str,
278
+ session_id: str | None = None,
279
+ ) -> None:
219
280
  """
220
281
  Move/rename file or directory.
221
282
 
222
283
  Args:
223
284
  source: Source path
224
285
  destination: Destination path
286
+ session_id: Session ID (uses stored value if not provided)
225
287
  """
226
288
  from cmdop._generated.file_rpc.file_crud_pb2 import FileMoveRpcRequest
227
289
 
228
290
  request = FileMoveRpcRequest(
291
+ session_id=self._get_session_id(session_id),
229
292
  source_path=source,
230
293
  destination_path=destination,
231
294
  )
232
295
 
233
296
  self._call_sync(self._get_stub.FileMove, request)
234
297
 
235
- def info(self, path: str) -> FileInfo:
298
+ def info(self, path: str, session_id: str | None = None) -> FileInfo:
236
299
  """
237
300
  Get file information.
238
301
 
239
302
  Args:
240
303
  path: File path
304
+ session_id: Session ID (uses stored value if not provided)
241
305
 
242
306
  Returns:
243
307
  Detailed file information
244
308
  """
245
309
  from cmdop._generated.file_rpc.file_crud_pb2 import FileGetInfoRpcRequest
246
310
 
247
- request = FileGetInfoRpcRequest(path=path)
311
+ request = FileGetInfoRpcRequest(
312
+ session_id=self._get_session_id(session_id),
313
+ path=path,
314
+ )
248
315
  response = self._call_sync(self._get_stub.FileGetInfo, request)
249
316
 
250
317
  entry = response.result.entry
@@ -256,19 +323,26 @@ class FilesService(BaseService):
256
323
  permissions=entry.permissions if hasattr(entry, "permissions") else None,
257
324
  )
258
325
 
259
- def mkdir(self, path: str, create_parents: bool = True) -> None:
326
+ def mkdir(
327
+ self,
328
+ path: str,
329
+ create_parents: bool = True,
330
+ session_id: str | None = None,
331
+ ) -> None:
260
332
  """
261
333
  Create directory.
262
334
 
263
335
  Args:
264
336
  path: Directory path to create
265
337
  create_parents: Create parent directories
338
+ session_id: Session ID (uses stored value if not provided)
266
339
  """
267
340
  from cmdop._generated.file_rpc.file_crud_pb2 import (
268
341
  FileCreateDirectoryRpcRequest,
269
342
  )
270
343
 
271
344
  request = FileCreateDirectoryRpcRequest(
345
+ session_id=self._get_session_id(session_id),
272
346
  path=path,
273
347
  create_parents=create_parents,
274
348
  )
@@ -281,11 +355,36 @@ class AsyncFilesService(BaseService):
281
355
  Asynchronous files service.
282
356
 
283
357
  Provides async file system operations.
358
+
359
+ Example:
360
+ >>> # Remote - set session_id first
361
+ >>> session = await client.terminal.get_active_session()
362
+ >>> client.files.set_session_id(session.session_id)
363
+ >>> entries = await client.files.list("/home/user")
284
364
  """
285
365
 
286
366
  def __init__(self, transport: BaseTransport) -> None:
287
367
  super().__init__(transport)
288
368
  self._stub: Any = None
369
+ self._session_id: str | None = None
370
+
371
+ def set_session_id(self, session_id: str) -> None:
372
+ """
373
+ Set session ID for file operations.
374
+
375
+ Required for remote connections. For local IPC, session_id is optional.
376
+
377
+ Args:
378
+ session_id: Session ID from terminal.get_active_session() or terminal.create()
379
+ """
380
+ self._session_id = session_id
381
+
382
+ def _get_session_id(self, session_id: str | None = None) -> str:
383
+ """Get session ID from parameter or stored value."""
384
+ sid = session_id or self._session_id
385
+ if not sid:
386
+ return ""
387
+ return sid
289
388
 
290
389
  @property
291
390
  def _get_stub(self) -> Any:
@@ -304,6 +403,7 @@ class AsyncFilesService(BaseService):
304
403
  include_hidden: bool = False,
305
404
  page_size: int = 100,
306
405
  page_token: str | None = None,
406
+ session_id: str | None = None,
307
407
  ) -> ListDirectoryResponse:
308
408
  """List directory contents."""
309
409
  from cmdop._generated.file_rpc.directory_pb2 import (
@@ -311,6 +411,7 @@ class AsyncFilesService(BaseService):
311
411
  )
312
412
 
313
413
  request = FileListDirectoryRpcRequest(
414
+ session_id=self._get_session_id(session_id),
314
415
  path=path,
315
416
  page_size=page_size,
316
417
  include_hidden=include_hidden,
@@ -340,11 +441,20 @@ class AsyncFilesService(BaseService):
340
441
  total_count=response.result.total_count,
341
442
  )
342
443
 
343
- async def read(self, path: str, offset: int = 0, limit: int = 0) -> bytes:
444
+ async def read(
445
+ self,
446
+ path: str,
447
+ offset: int = 0,
448
+ limit: int = 0,
449
+ session_id: str | None = None,
450
+ ) -> bytes:
344
451
  """Read file contents."""
345
452
  from cmdop._generated.file_rpc.file_crud_pb2 import FileReadRpcRequest
346
453
 
347
- request = FileReadRpcRequest(path=path)
454
+ request = FileReadRpcRequest(
455
+ session_id=self._get_session_id(session_id),
456
+ path=path,
457
+ )
348
458
  response = await self._call_async(self._get_stub.FileRead, request)
349
459
  return response.result.content
350
460
 
@@ -354,6 +464,7 @@ class AsyncFilesService(BaseService):
354
464
  content: bytes | str,
355
465
  create_parents: bool = False,
356
466
  overwrite: bool = True,
467
+ session_id: str | None = None,
357
468
  ) -> None:
358
469
  """Write file contents."""
359
470
  from cmdop._generated.file_rpc.file_crud_pb2 import FileWriteRpcRequest
@@ -362,44 +473,69 @@ class AsyncFilesService(BaseService):
362
473
  content = content.encode("utf-8")
363
474
 
364
475
  request = FileWriteRpcRequest(
476
+ session_id=self._get_session_id(session_id),
365
477
  path=path,
366
478
  content=content,
367
479
  create_parents=create_parents,
368
480
  )
369
481
  await self._call_async(self._get_stub.FileWrite, request)
370
482
 
371
- async def delete(self, path: str, recursive: bool = False) -> None:
483
+ async def delete(
484
+ self,
485
+ path: str,
486
+ recursive: bool = False,
487
+ session_id: str | None = None,
488
+ ) -> None:
372
489
  """Delete file or directory."""
373
490
  from cmdop._generated.file_rpc.file_crud_pb2 import FileDeleteRpcRequest
374
491
 
375
- request = FileDeleteRpcRequest(path=path, recursive=recursive)
492
+ request = FileDeleteRpcRequest(
493
+ session_id=self._get_session_id(session_id),
494
+ path=path,
495
+ recursive=recursive,
496
+ )
376
497
  await self._call_async(self._get_stub.FileDelete, request)
377
498
 
378
- async def copy(self, source: str, destination: str) -> None:
499
+ async def copy(
500
+ self,
501
+ source: str,
502
+ destination: str,
503
+ session_id: str | None = None,
504
+ ) -> None:
379
505
  """Copy file or directory."""
380
506
  from cmdop._generated.file_rpc.file_crud_pb2 import FileCopyRpcRequest
381
507
 
382
508
  request = FileCopyRpcRequest(
509
+ session_id=self._get_session_id(session_id),
383
510
  source_path=source,
384
511
  destination_path=destination,
385
512
  )
386
513
  await self._call_async(self._get_stub.FileCopy, request)
387
514
 
388
- async def move(self, source: str, destination: str) -> None:
515
+ async def move(
516
+ self,
517
+ source: str,
518
+ destination: str,
519
+ session_id: str | None = None,
520
+ ) -> None:
389
521
  """Move/rename file or directory."""
390
522
  from cmdop._generated.file_rpc.file_crud_pb2 import FileMoveRpcRequest
391
523
 
392
524
  request = FileMoveRpcRequest(
525
+ session_id=self._get_session_id(session_id),
393
526
  source_path=source,
394
527
  destination_path=destination,
395
528
  )
396
529
  await self._call_async(self._get_stub.FileMove, request)
397
530
 
398
- async def info(self, path: str) -> FileInfo:
531
+ async def info(self, path: str, session_id: str | None = None) -> FileInfo:
399
532
  """Get file information."""
400
533
  from cmdop._generated.file_rpc.file_crud_pb2 import FileGetInfoRpcRequest
401
534
 
402
- request = FileGetInfoRpcRequest(path=path)
535
+ request = FileGetInfoRpcRequest(
536
+ session_id=self._get_session_id(session_id),
537
+ path=path,
538
+ )
403
539
  response = await self._call_async(self._get_stub.FileGetInfo, request)
404
540
 
405
541
  entry = response.result.entry
@@ -410,13 +546,19 @@ class AsyncFilesService(BaseService):
410
546
  modified_at=_parse_timestamp(entry.modified_at),
411
547
  )
412
548
 
413
- async def mkdir(self, path: str, create_parents: bool = True) -> None:
549
+ async def mkdir(
550
+ self,
551
+ path: str,
552
+ create_parents: bool = True,
553
+ session_id: str | None = None,
554
+ ) -> None:
414
555
  """Create directory."""
415
556
  from cmdop._generated.file_rpc.file_crud_pb2 import (
416
557
  FileCreateDirectoryRpcRequest,
417
558
  )
418
559
 
419
560
  request = FileCreateDirectoryRpcRequest(
561
+ session_id=self._get_session_id(session_id),
420
562
  path=path,
421
563
  create_parents=create_parents,
422
564
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmdop
3
- Version: 0.1.26
3
+ Version: 0.1.28
4
4
  Summary: Python SDK for CMDOP agent interaction
5
5
  Project-URL: Homepage, https://cmdop.com
6
6
  Project-URL: Documentation, https://cmdop.com
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Classifier: Typing :: Typed
21
21
  Requires-Python: >=3.10
22
+ Requires-Dist: beautifulsoup4>=4.14.3
22
23
  Requires-Dist: grpcio>=1.60.0
23
24
  Requires-Dist: httpx>=0.27.0
24
25
  Requires-Dist: protobuf>=4.25.0
@@ -290,14 +291,31 @@ client.terminal.close(session.session_id)
290
291
  ## Files
291
292
 
292
293
  ```python
294
+ # Local IPC - session_id not required
293
295
  client.files.list("/var/log")
294
296
  client.files.read("/etc/nginx/nginx.conf")
295
297
  client.files.write("/tmp/config.json", b'{"key": "value"}')
296
- client.files.delete("/tmp/old.txt")
297
- client.files.copy("/src", "/dst")
298
- client.files.move("/old", "/new")
299
- client.files.mkdir("/new/dir")
300
- client.files.info("/path")
298
+
299
+ # Remote - session_id required
300
+ session = client.terminal.get_active_session()
301
+ client.files.set_session_id(session.session_id) # Set once
302
+ client.files.list("/var/log")
303
+ client.files.read("/etc/nginx/nginx.conf")
304
+
305
+ # Or pass session_id directly to each call
306
+ client.files.list("/var/log", session_id=session.session_id)
307
+ ```
308
+
309
+ **Files methods:**
310
+ ```python
311
+ client.files.list("/path", include_hidden=True) # List directory
312
+ client.files.read("/path/file.txt") # Read file
313
+ client.files.write("/path/file.txt", b"data") # Write file
314
+ client.files.delete("/path", recursive=True) # Delete file/dir
315
+ client.files.copy("/src", "/dst") # Copy
316
+ client.files.move("/old", "/new") # Move/rename
317
+ client.files.mkdir("/new/dir") # Create directory
318
+ client.files.info("/path") # Get file info
301
319
  ```
302
320
 
303
321
  ## SDKBaseModel
@@ -1,4 +1,4 @@
1
- cmdop/__init__.py,sha256=YoLf9cziSSoOYsiDoTLlGpm5kV8kTptdMl4e4ccK4xc,5271
1
+ cmdop/__init__.py,sha256=ICgs-pVllaoCC_UzbiGoDalx1ZdVYLvbAXdLmNALLUk,5271
2
2
  cmdop/client.py,sha256=nTotStZPBfYN3TrHH-OlEJMSVAXskYMQRkocsFmyaBY,14601
3
3
  cmdop/config.py,sha256=vpw1aGCyS4NKlZyzVur81Lt06QmN3FnscZji0bypUi0,4398
4
4
  cmdop/discovery.py,sha256=HNxSOa5tSuG7ppfFs21XdviW5ucjpRswVPguhX5j8Dg,7479
@@ -163,7 +163,7 @@ cmdop/services/__init__.py,sha256=RRslzuyux163fuHbxo82igMDeqlWj9vplkHmbN06A7k,89
163
163
  cmdop/services/agent.py,sha256=PbXuqdVGXRAJQUhkSYM3xgpgnOox1kr55pUbPG8iG2U,11092
164
164
  cmdop/services/base.py,sha256=BCYIUME5gfpDHAief30sQNVzrlAgHx6QGZ6AbLHQx5w,3087
165
165
  cmdop/services/extract.py,sha256=zwzikEKH4T4OnrprmJg3wqBWQJr-DYsSZgJGK_2yIHU,11119
166
- cmdop/services/files.py,sha256=RGhfo7tW6diuUWC_EQQ-Y9zO1btm6mBTji0SWWa0fdo,12548
166
+ cmdop/services/files.py,sha256=SzGFKkQfkIkHIQf5g_UsaOU6luXF1N3KwSVzMtJWG3I,17180
167
167
  cmdop/services/terminal.py,sha256=9SSWBexe2rWgMd-hGBEs9mcax3l7x_U84VHZpMC4xK8,17512
168
168
  cmdop/services/browser/__init__.py,sha256=31Ofu9RCYTAedPKLvnor8J7oGDgTjbqJ58OkxxHYwdk,1270
169
169
  cmdop/services/browser/models.py,sha256=9MpNFgSgZDIznmTmsCUByEN31t_iQ6kAza1BsPSsuJs,5320
@@ -198,7 +198,7 @@ cmdop/transport/base.py,sha256=2pkV8i9epgp_21dyReCfX47abRUrnALm0W5BXb-Fuz0,5571
198
198
  cmdop/transport/discovery.py,sha256=rcGAuVrR1l6jwcP0dqZxVhX1NsFK7sRHygFMCLmmUbA,10673
199
199
  cmdop/transport/local.py,sha256=ob6tWVxSdKwblHSMK8CkgjyuSdQoAeWgy5OAUd5ZNuE,7411
200
200
  cmdop/transport/remote.py,sha256=FNVqus9wOv7LlxKarXjLmSyvJiHwhvPbNDOPv1IQkmE,4329
201
- cmdop-0.1.26.dist-info/METADATA,sha256=yoBVrJGRJhJNBV9OYfng50ZlGxTjZABBVuInKSMBxDU,9790
202
- cmdop-0.1.26.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
203
- cmdop-0.1.26.dist-info/licenses/LICENSE,sha256=6hyzbI1QVXW6B-XT7PaQ6UG9lns11Y_nnap8uUKGUqo,1062
204
- cmdop-0.1.26.dist-info/RECORD,,
201
+ cmdop-0.1.28.dist-info/METADATA,sha256=DQ-6-awSrNB7dwy_j_7cfj6tsNIAl-zogYNTd3WVJbQ,10563
202
+ cmdop-0.1.28.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
203
+ cmdop-0.1.28.dist-info/licenses/LICENSE,sha256=6hyzbI1QVXW6B-XT7PaQ6UG9lns11Y_nnap8uUKGUqo,1062
204
+ cmdop-0.1.28.dist-info/RECORD,,
File without changes