mcli-framework 7.12.4__py3-none-any.whl → 7.13.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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/commands_cmd.py +387 -2
- mcli/app/main.py +1 -8
- {mcli_framework-7.12.4.dist-info → mcli_framework-7.13.0.dist-info}/METADATA +1 -1
- {mcli_framework-7.12.4.dist-info → mcli_framework-7.13.0.dist-info}/RECORD +8 -9
- mcli/app/store_cmd.py +0 -448
- {mcli_framework-7.12.4.dist-info → mcli_framework-7.13.0.dist-info}/WHEEL +0 -0
- {mcli_framework-7.12.4.dist-info → mcli_framework-7.13.0.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.12.4.dist-info → mcli_framework-7.13.0.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.12.4.dist-info → mcli_framework-7.13.0.dist-info}/top_level.txt +0 -0
mcli/app/commands_cmd.py
CHANGED
|
@@ -4,6 +4,7 @@ import inspect
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
+
import shutil
|
|
7
8
|
import subprocess
|
|
8
9
|
import tempfile
|
|
9
10
|
from datetime import datetime
|
|
@@ -17,7 +18,7 @@ from mcli.lib.api.daemon_client import get_daemon_client
|
|
|
17
18
|
from mcli.lib.custom_commands import get_command_manager
|
|
18
19
|
from mcli.lib.discovery.command_discovery import get_command_discovery
|
|
19
20
|
from mcli.lib.logger.logger import get_logger
|
|
20
|
-
from mcli.lib.ui.styling import console
|
|
21
|
+
from mcli.lib.ui.styling import console, error, info, success, warning
|
|
21
22
|
|
|
22
23
|
logger = get_logger(__name__)
|
|
23
24
|
|
|
@@ -189,6 +190,390 @@ def workflow():
|
|
|
189
190
|
commands = workflow
|
|
190
191
|
|
|
191
192
|
|
|
193
|
+
# Helper function for store commands
|
|
194
|
+
def _get_store_path() -> Path:
|
|
195
|
+
"""Get store path from config or default."""
|
|
196
|
+
config_file = Path.home() / ".mcli" / "store.conf"
|
|
197
|
+
|
|
198
|
+
if config_file.exists():
|
|
199
|
+
store_path = Path(config_file.read_text().strip())
|
|
200
|
+
if store_path.exists():
|
|
201
|
+
return store_path
|
|
202
|
+
|
|
203
|
+
# Use default
|
|
204
|
+
return DEFAULT_STORE_PATH
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# Store commands (git sync for workflows)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@workflow.command(name="init")
|
|
211
|
+
@click.option("--path", "-p", type=click.Path(), help=f"Store path (default: {DEFAULT_STORE_PATH})")
|
|
212
|
+
@click.option("--remote", "-r", help="Git remote URL (optional)")
|
|
213
|
+
def init_store(path, remote):
|
|
214
|
+
"""Initialize command store with git."""
|
|
215
|
+
store_path = Path(path) if path else DEFAULT_STORE_PATH
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
# Create store directory
|
|
219
|
+
store_path.mkdir(parents=True, exist_ok=True)
|
|
220
|
+
|
|
221
|
+
# Initialize git if not already initialized
|
|
222
|
+
git_dir = store_path / ".git"
|
|
223
|
+
if not git_dir.exists():
|
|
224
|
+
subprocess.run(["git", "init"], cwd=store_path, check=True, capture_output=True)
|
|
225
|
+
success(f"Initialized git repository at {store_path}")
|
|
226
|
+
|
|
227
|
+
# Create .gitignore
|
|
228
|
+
gitignore = store_path / ".gitignore"
|
|
229
|
+
gitignore.write_text("*.backup\n.DS_Store\n")
|
|
230
|
+
|
|
231
|
+
# Create README
|
|
232
|
+
readme = store_path / "README.md"
|
|
233
|
+
readme.write_text(
|
|
234
|
+
f"""# MCLI Commands Store
|
|
235
|
+
|
|
236
|
+
Personal workflow commands for mcli framework.
|
|
237
|
+
|
|
238
|
+
## Usage
|
|
239
|
+
|
|
240
|
+
Push commands:
|
|
241
|
+
```bash
|
|
242
|
+
mcli workflow push
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Pull commands:
|
|
246
|
+
```bash
|
|
247
|
+
mcli workflow pull
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Sync (bidirectional):
|
|
251
|
+
```bash
|
|
252
|
+
mcli workflow sync
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Structure
|
|
256
|
+
|
|
257
|
+
All JSON command files from `~/.mcli/commands/` are stored here and version controlled.
|
|
258
|
+
|
|
259
|
+
Last updated: {datetime.now().isoformat()}
|
|
260
|
+
"""
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Add remote if provided
|
|
264
|
+
if remote:
|
|
265
|
+
subprocess.run(
|
|
266
|
+
["git", "remote", "add", "origin", remote], cwd=store_path, check=True
|
|
267
|
+
)
|
|
268
|
+
success(f"Added remote: {remote}")
|
|
269
|
+
else:
|
|
270
|
+
info(f"Git repository already exists at {store_path}")
|
|
271
|
+
|
|
272
|
+
# Save store path to config
|
|
273
|
+
config_file = Path.home() / ".mcli" / "store.conf"
|
|
274
|
+
config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
275
|
+
config_file.write_text(str(store_path))
|
|
276
|
+
|
|
277
|
+
success(f"Command store initialized at {store_path}")
|
|
278
|
+
info(f"Store path saved to {config_file}")
|
|
279
|
+
|
|
280
|
+
except subprocess.CalledProcessError as e:
|
|
281
|
+
error(f"Git command failed: {e}")
|
|
282
|
+
logger.error(f"Git init failed: {e}")
|
|
283
|
+
except Exception as e:
|
|
284
|
+
error(f"Failed to initialize store: {e}")
|
|
285
|
+
logger.exception(e)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@workflow.command(name="push")
|
|
289
|
+
@click.option("--message", "-m", help="Commit message")
|
|
290
|
+
@click.option("--all", "-a", is_flag=True, help="Push all files (including backups)")
|
|
291
|
+
@click.option(
|
|
292
|
+
"--global", "-g", "is_global", is_flag=True, help="Push global commands instead of local"
|
|
293
|
+
)
|
|
294
|
+
def push_commands(message, all, is_global):
|
|
295
|
+
"""
|
|
296
|
+
Push commands to git store.
|
|
297
|
+
|
|
298
|
+
By default pushes local commands (if in git repo), use --global/-g for global commands.
|
|
299
|
+
"""
|
|
300
|
+
try:
|
|
301
|
+
store_path = _get_store_path()
|
|
302
|
+
from mcli.lib.paths import get_custom_commands_dir
|
|
303
|
+
|
|
304
|
+
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
305
|
+
|
|
306
|
+
# Copy commands to store
|
|
307
|
+
info(f"Copying commands from {COMMANDS_PATH} to {store_path}...")
|
|
308
|
+
|
|
309
|
+
copied_count = 0
|
|
310
|
+
for item in COMMANDS_PATH.glob("*"):
|
|
311
|
+
# Skip backups unless --all specified
|
|
312
|
+
if not all and item.name.endswith(".backup"):
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
dest = store_path / item.name
|
|
316
|
+
if item.is_file():
|
|
317
|
+
shutil.copy2(item, dest)
|
|
318
|
+
copied_count += 1
|
|
319
|
+
elif item.is_dir():
|
|
320
|
+
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
321
|
+
copied_count += 1
|
|
322
|
+
|
|
323
|
+
success(f"Copied {copied_count} items to store")
|
|
324
|
+
|
|
325
|
+
# Git add, commit, push
|
|
326
|
+
subprocess.run(["git", "add", "."], cwd=store_path, check=True)
|
|
327
|
+
|
|
328
|
+
# Check if there are changes
|
|
329
|
+
result = subprocess.run(
|
|
330
|
+
["git", "status", "--porcelain"], cwd=store_path, capture_output=True, text=True
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
if not result.stdout.strip():
|
|
334
|
+
info("No changes to commit")
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
# Commit with message
|
|
338
|
+
commit_msg = message or f"Update commands {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
339
|
+
subprocess.run(["git", "commit", "-m", commit_msg], cwd=store_path, check=True)
|
|
340
|
+
success(f"Committed changes: {commit_msg}")
|
|
341
|
+
|
|
342
|
+
# Push to remote if configured
|
|
343
|
+
try:
|
|
344
|
+
subprocess.run(["git", "push"], cwd=store_path, check=True, capture_output=True)
|
|
345
|
+
success("Pushed to remote")
|
|
346
|
+
except subprocess.CalledProcessError:
|
|
347
|
+
warning("No remote configured or push failed. Commands committed locally.")
|
|
348
|
+
|
|
349
|
+
except Exception as e:
|
|
350
|
+
error(f"Failed to push commands: {e}")
|
|
351
|
+
logger.exception(e)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@workflow.command(name="pull")
|
|
355
|
+
@click.option("--force", "-f", is_flag=True, help="Overwrite local commands without backup")
|
|
356
|
+
@click.option(
|
|
357
|
+
"--global", "-g", "is_global", is_flag=True, help="Pull to global commands instead of local"
|
|
358
|
+
)
|
|
359
|
+
def pull_commands(force, is_global):
|
|
360
|
+
"""
|
|
361
|
+
Pull commands from git store.
|
|
362
|
+
|
|
363
|
+
By default pulls to local commands (if in git repo), use --global/-g for global commands.
|
|
364
|
+
"""
|
|
365
|
+
try:
|
|
366
|
+
store_path = _get_store_path()
|
|
367
|
+
from mcli.lib.paths import get_custom_commands_dir
|
|
368
|
+
|
|
369
|
+
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
370
|
+
|
|
371
|
+
# Pull from remote
|
|
372
|
+
try:
|
|
373
|
+
subprocess.run(["git", "pull"], cwd=store_path, check=True)
|
|
374
|
+
success("Pulled latest changes from remote")
|
|
375
|
+
except subprocess.CalledProcessError:
|
|
376
|
+
warning("No remote configured or pull failed. Using local store.")
|
|
377
|
+
|
|
378
|
+
# Backup existing commands if not force
|
|
379
|
+
if not force and COMMANDS_PATH.exists():
|
|
380
|
+
backup_dir = (
|
|
381
|
+
COMMANDS_PATH.parent / f"commands_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
382
|
+
)
|
|
383
|
+
shutil.copytree(COMMANDS_PATH, backup_dir)
|
|
384
|
+
info(f"Backed up existing commands to {backup_dir}")
|
|
385
|
+
|
|
386
|
+
# Copy from store to commands directory
|
|
387
|
+
info(f"Copying commands from {store_path} to {COMMANDS_PATH}...")
|
|
388
|
+
|
|
389
|
+
COMMANDS_PATH.mkdir(parents=True, exist_ok=True)
|
|
390
|
+
|
|
391
|
+
copied_count = 0
|
|
392
|
+
for item in store_path.glob("*"):
|
|
393
|
+
# Skip git directory and README
|
|
394
|
+
if item.name in [".git", "README.md", ".gitignore"]:
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
dest = COMMANDS_PATH / item.name
|
|
398
|
+
if item.is_file():
|
|
399
|
+
shutil.copy2(item, dest)
|
|
400
|
+
copied_count += 1
|
|
401
|
+
elif item.is_dir():
|
|
402
|
+
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
403
|
+
copied_count += 1
|
|
404
|
+
|
|
405
|
+
success(f"Pulled {copied_count} items from store")
|
|
406
|
+
|
|
407
|
+
except Exception as e:
|
|
408
|
+
error(f"Failed to pull commands: {e}")
|
|
409
|
+
logger.exception(e)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@workflow.command(name="sync")
|
|
413
|
+
@click.option("--message", "-m", help="Commit message if pushing")
|
|
414
|
+
@click.option(
|
|
415
|
+
"--global", "-g", "is_global", is_flag=True, help="Sync global commands instead of local"
|
|
416
|
+
)
|
|
417
|
+
def sync_commands(message, is_global):
|
|
418
|
+
"""
|
|
419
|
+
Sync commands bidirectionally (pull then push if changes).
|
|
420
|
+
|
|
421
|
+
By default syncs local commands (if in git repo), use --global/-g for global commands.
|
|
422
|
+
"""
|
|
423
|
+
try:
|
|
424
|
+
store_path = _get_store_path()
|
|
425
|
+
from mcli.lib.paths import get_custom_commands_dir
|
|
426
|
+
|
|
427
|
+
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
428
|
+
|
|
429
|
+
# First pull
|
|
430
|
+
info("Pulling latest changes...")
|
|
431
|
+
try:
|
|
432
|
+
subprocess.run(["git", "pull"], cwd=store_path, check=True, capture_output=True)
|
|
433
|
+
success("Pulled from remote")
|
|
434
|
+
except subprocess.CalledProcessError:
|
|
435
|
+
warning("No remote or pull failed")
|
|
436
|
+
|
|
437
|
+
# Then push local changes
|
|
438
|
+
info("Pushing local changes...")
|
|
439
|
+
|
|
440
|
+
# Copy commands
|
|
441
|
+
for item in COMMANDS_PATH.glob("*"):
|
|
442
|
+
if item.name.endswith(".backup"):
|
|
443
|
+
continue
|
|
444
|
+
dest = store_path / item.name
|
|
445
|
+
if item.is_file():
|
|
446
|
+
shutil.copy2(item, dest)
|
|
447
|
+
elif item.is_dir():
|
|
448
|
+
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
449
|
+
|
|
450
|
+
# Check for changes
|
|
451
|
+
result = subprocess.run(
|
|
452
|
+
["git", "status", "--porcelain"], cwd=store_path, capture_output=True, text=True
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
if not result.stdout.strip():
|
|
456
|
+
success("Everything in sync!")
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
# Commit and push
|
|
460
|
+
subprocess.run(["git", "add", "."], cwd=store_path, check=True)
|
|
461
|
+
commit_msg = message or f"Sync commands {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
462
|
+
subprocess.run(["git", "commit", "-m", commit_msg], cwd=store_path, check=True)
|
|
463
|
+
|
|
464
|
+
try:
|
|
465
|
+
subprocess.run(["git", "push"], cwd=store_path, check=True, capture_output=True)
|
|
466
|
+
success("Synced and pushed to remote")
|
|
467
|
+
except subprocess.CalledProcessError:
|
|
468
|
+
success("Synced locally (no remote configured)")
|
|
469
|
+
|
|
470
|
+
except Exception as e:
|
|
471
|
+
error(f"Sync failed: {e}")
|
|
472
|
+
logger.exception(e)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@workflow.command(name="status")
|
|
476
|
+
def store_status():
|
|
477
|
+
"""Show git status of command store."""
|
|
478
|
+
try:
|
|
479
|
+
store_path = _get_store_path()
|
|
480
|
+
|
|
481
|
+
click.echo(f"\n📦 Store: {store_path}\n")
|
|
482
|
+
|
|
483
|
+
# Git status
|
|
484
|
+
result = subprocess.run(
|
|
485
|
+
["git", "status", "--short", "--branch"], cwd=store_path, capture_output=True, text=True
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
if result.stdout:
|
|
489
|
+
click.echo(result.stdout)
|
|
490
|
+
|
|
491
|
+
# Show remote
|
|
492
|
+
result = subprocess.run(
|
|
493
|
+
["git", "remote", "-v"], cwd=store_path, capture_output=True, text=True
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
if result.stdout:
|
|
497
|
+
click.echo("\n🌐 Remotes:")
|
|
498
|
+
click.echo(result.stdout)
|
|
499
|
+
else:
|
|
500
|
+
info("\nNo remote configured")
|
|
501
|
+
|
|
502
|
+
click.echo()
|
|
503
|
+
|
|
504
|
+
except Exception as e:
|
|
505
|
+
error(f"Failed to get status: {e}")
|
|
506
|
+
logger.exception(e)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@workflow.command(name="config")
|
|
510
|
+
@click.option("--remote", "-r", help="Set git remote URL")
|
|
511
|
+
@click.option("--path", "-p", type=click.Path(), help="Change store path")
|
|
512
|
+
def configure_store(remote, path):
|
|
513
|
+
"""Configure store settings."""
|
|
514
|
+
try:
|
|
515
|
+
store_path = _get_store_path()
|
|
516
|
+
|
|
517
|
+
if path:
|
|
518
|
+
new_path = Path(path).expanduser().resolve()
|
|
519
|
+
config_file = Path.home() / ".mcli" / "store.conf"
|
|
520
|
+
config_file.write_text(str(new_path))
|
|
521
|
+
success(f"Store path updated to: {new_path}")
|
|
522
|
+
return
|
|
523
|
+
|
|
524
|
+
if remote:
|
|
525
|
+
# Check if remote exists
|
|
526
|
+
result = subprocess.run(
|
|
527
|
+
["git", "remote"], cwd=store_path, capture_output=True, text=True
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if "origin" in result.stdout:
|
|
531
|
+
subprocess.run(
|
|
532
|
+
["git", "remote", "set-url", "origin", remote], cwd=store_path, check=True
|
|
533
|
+
)
|
|
534
|
+
success(f"Updated remote URL: {remote}")
|
|
535
|
+
else:
|
|
536
|
+
subprocess.run(
|
|
537
|
+
["git", "remote", "add", "origin", remote], cwd=store_path, check=True
|
|
538
|
+
)
|
|
539
|
+
success(f"Added remote URL: {remote}")
|
|
540
|
+
|
|
541
|
+
except Exception as e:
|
|
542
|
+
error(f"Configuration failed: {e}")
|
|
543
|
+
logger.exception(e)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
@workflow.command(name="show")
|
|
547
|
+
@click.argument("command_name")
|
|
548
|
+
@click.option("--store-dir", "-s", is_flag=True, help="Show from store instead of local")
|
|
549
|
+
def show_command(command_name, store_dir):
|
|
550
|
+
"""Show command file contents."""
|
|
551
|
+
try:
|
|
552
|
+
if store_dir:
|
|
553
|
+
store_path = _get_store_path()
|
|
554
|
+
path = store_path / command_name
|
|
555
|
+
else:
|
|
556
|
+
path = COMMANDS_PATH / command_name
|
|
557
|
+
|
|
558
|
+
if not path.exists():
|
|
559
|
+
error(f"Command not found: {command_name}")
|
|
560
|
+
return
|
|
561
|
+
|
|
562
|
+
if path.is_file():
|
|
563
|
+
click.echo(f"\n📄 {path}:\n")
|
|
564
|
+
click.echo(path.read_text())
|
|
565
|
+
else:
|
|
566
|
+
info(f"{command_name} is a directory")
|
|
567
|
+
for item in sorted(path.glob("*")):
|
|
568
|
+
click.echo(f" {item.name}")
|
|
569
|
+
|
|
570
|
+
click.echo()
|
|
571
|
+
|
|
572
|
+
except Exception as e:
|
|
573
|
+
error(f"Failed to show command: {e}")
|
|
574
|
+
logger.exception(e)
|
|
575
|
+
|
|
576
|
+
|
|
192
577
|
# init command moved to init_cmd.py as top-level command
|
|
193
578
|
|
|
194
579
|
|
|
@@ -1324,7 +1709,7 @@ def edit_command(command_name, editor, is_global):
|
|
|
1324
1709
|
# Moved from mcli.self for better organization
|
|
1325
1710
|
|
|
1326
1711
|
|
|
1327
|
-
@workflow.command("extract
|
|
1712
|
+
@workflow.command("extract")
|
|
1328
1713
|
@click.option(
|
|
1329
1714
|
"--output", "-o", type=click.Path(), help="Output file (default: workflow-commands.json)"
|
|
1330
1715
|
)
|
mcli/app/main.py
CHANGED
|
@@ -347,14 +347,7 @@ def _add_lazy_commands(app: click.Group):
|
|
|
347
347
|
except ImportError as e:
|
|
348
348
|
logger.debug(f"Could not load lock group: {e}")
|
|
349
349
|
|
|
350
|
-
#
|
|
351
|
-
try:
|
|
352
|
-
from mcli.app.store_cmd import store
|
|
353
|
-
|
|
354
|
-
app.add_command(store, name="store")
|
|
355
|
-
logger.debug("Added store group")
|
|
356
|
-
except ImportError as e:
|
|
357
|
-
logger.debug(f"Could not load store group: {e}")
|
|
350
|
+
# Store commands moved to workflow group
|
|
358
351
|
|
|
359
352
|
# Workflow management - load immediately for fast access (renamed from 'commands')
|
|
360
353
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcli-framework
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.13.0
|
|
4
4
|
Summary: Portable workflow framework - transform any script into a versioned, schedulable command. Store in ~/.mcli/workflows/, version with lockfile, run as daemon or cron job.
|
|
5
5
|
Author-email: Luis Fernandez de la Vara <luis@lefv.io>
|
|
6
6
|
Maintainer-email: Luis Fernandez de la Vara <luis@lefv.io>
|
|
@@ -3,13 +3,12 @@ mcli/__main__.py,sha256=nKdf3WqtXi5PhWhZGjpXKAT3a2yGUYkYCBgSLxk4hSQ,295
|
|
|
3
3
|
mcli/cli.py,sha256=3OgzOodRtHGZnsq5H3Fy-hl7JKwjJZa5ig2L4-izK2E,382
|
|
4
4
|
mcli/config.toml,sha256=263yEVvP_W9F2zOLssUBgy7amKaRAFQuBrfxcMhKxaQ,1706
|
|
5
5
|
mcli/app/__init__.py,sha256=UWt7rsI_lHjZ29_iH5lX6qSLt__uBMmp5W-Rt3BEG0E,447
|
|
6
|
-
mcli/app/commands_cmd.py,sha256=
|
|
6
|
+
mcli/app/commands_cmd.py,sha256=A-YhX8dATAUTQe8c9jzLnwJXIdPKEdzYyZWcYVhIFwg,63098
|
|
7
7
|
mcli/app/completion_helpers.py,sha256=_gBHJukhseferp3G7pj6QkDvAhVt72nsfbyOHcnBazg,7484
|
|
8
8
|
mcli/app/init_cmd.py,sha256=VWeCAaN5tinFBBHgzmt_JR3UdSTNVBofXXRZ4FNdkcw,12428
|
|
9
9
|
mcli/app/lock_cmd.py,sha256=0m7xkpWxfJ8vxTgyvRNY2XKAwJj_CB7Sf-wvnte5RS4,12883
|
|
10
|
-
mcli/app/main.py,sha256=
|
|
10
|
+
mcli/app/main.py,sha256=ppQm1Jl8x6sub3_2X3eot00pYham8FeHg8l0OV0Nw9I,18865
|
|
11
11
|
mcli/app/model_cmd.py,sha256=LQQD8FaebFoaJGK3u_kt19wZ3HJyo_ecwSMYyC2xIp8,2497
|
|
12
|
-
mcli/app/store_cmd.py,sha256=N7FhnrhIeY6Hetv6kW4mcJ97EQvhgh0pwHiwoZSfzhU,14315
|
|
13
12
|
mcli/app/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
13
|
mcli/app/model/model.py,sha256=w3yrZ2bgh47V0SlpNHcuMkBX0fC9gni66VEwpBg0iYU,38968
|
|
15
14
|
mcli/app/video/__init__.py,sha256=NS3kaM8lWKL-gVh7WLGBqNda0-QJS6MCY6iW_GWPUco,44
|
|
@@ -271,9 +270,9 @@ mcli/workflow/sync/test_cmd.py,sha256=E-DItNtiBTGE9ofmfrjm9qjgYjeBpMXp08fmRk6nhR
|
|
|
271
270
|
mcli/workflow/videos/__init__.py,sha256=aV3DEoO7qdKJY4odWKoQbOKDQq4ludTeCLnZcupOFIM,25
|
|
272
271
|
mcli/workflow/wakatime/__init__.py,sha256=gD6SKA7dJ28HXFd1w04YuB_WgL0SWosbEFtT2uS__pg,2595
|
|
273
272
|
mcli/workflow/wakatime/wakatime.py,sha256=ig86g04qXgRrx6UXsIKd9Cv9OFhJnQLL2Plylf8TMAA,134
|
|
274
|
-
mcli_framework-7.
|
|
275
|
-
mcli_framework-7.
|
|
276
|
-
mcli_framework-7.
|
|
277
|
-
mcli_framework-7.
|
|
278
|
-
mcli_framework-7.
|
|
279
|
-
mcli_framework-7.
|
|
273
|
+
mcli_framework-7.13.0.dist-info/licenses/LICENSE,sha256=sahwAMfrJv2-V66HNPTp7A9UmMjxtyejwTZZoWQvEcI,1075
|
|
274
|
+
mcli_framework-7.13.0.dist-info/METADATA,sha256=-QZ_QbgHr6odb5UvHD7pzy5b3mpNRreBagwT70IdV-Q,18635
|
|
275
|
+
mcli_framework-7.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
276
|
+
mcli_framework-7.13.0.dist-info/entry_points.txt,sha256=dYrZbDIm-KUPsl1wfv600Kx_8sMy89phMkCihbDRgP8,261
|
|
277
|
+
mcli_framework-7.13.0.dist-info/top_level.txt,sha256=_bnO8J2EUkliWivey_1le0UrnocFKmyVMQjbQ8iVXjc,5
|
|
278
|
+
mcli_framework-7.13.0.dist-info/RECORD,,
|
mcli/app/store_cmd.py
DELETED
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Top-level store management commands for MCLI.
|
|
3
|
-
Manages command store - sync ~/.mcli/commands/ to git.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import shutil
|
|
7
|
-
import subprocess
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
import click
|
|
12
|
-
|
|
13
|
-
from mcli.lib.logger.logger import get_logger
|
|
14
|
-
from mcli.lib.ui.styling import error, info, success, warning
|
|
15
|
-
|
|
16
|
-
logger = get_logger(__name__)
|
|
17
|
-
|
|
18
|
-
# Command store configuration
|
|
19
|
-
DEFAULT_STORE_PATH = Path.home() / "repos" / "mcli-commands"
|
|
20
|
-
COMMANDS_PATH = Path.home() / ".mcli" / "commands"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _get_store_path() -> Path:
|
|
24
|
-
"""Get store path from config or default."""
|
|
25
|
-
config_file = Path.home() / ".mcli" / "store.conf"
|
|
26
|
-
|
|
27
|
-
if config_file.exists():
|
|
28
|
-
store_path = Path(config_file.read_text().strip())
|
|
29
|
-
if store_path.exists():
|
|
30
|
-
return store_path
|
|
31
|
-
|
|
32
|
-
# Use default
|
|
33
|
-
return DEFAULT_STORE_PATH
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@click.group(name="store")
|
|
37
|
-
def store():
|
|
38
|
-
"""Manage command store - sync ~/.mcli/commands/ to git."""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@store.command(name="init")
|
|
42
|
-
@click.option("--path", "-p", type=click.Path(), help=f"Store path (default: {DEFAULT_STORE_PATH})")
|
|
43
|
-
@click.option("--remote", "-r", help="Git remote URL (optional)")
|
|
44
|
-
def init_store(path, remote):
|
|
45
|
-
"""Initialize command store with git."""
|
|
46
|
-
store_path = Path(path) if path else DEFAULT_STORE_PATH
|
|
47
|
-
|
|
48
|
-
try:
|
|
49
|
-
# Create store directory
|
|
50
|
-
store_path.mkdir(parents=True, exist_ok=True)
|
|
51
|
-
|
|
52
|
-
# Initialize git if not already initialized
|
|
53
|
-
git_dir = store_path / ".git"
|
|
54
|
-
if not git_dir.exists():
|
|
55
|
-
subprocess.run(["git", "init"], cwd=store_path, check=True, capture_output=True)
|
|
56
|
-
success(f"Initialized git repository at {store_path}")
|
|
57
|
-
|
|
58
|
-
# Create .gitignore
|
|
59
|
-
gitignore = store_path / ".gitignore"
|
|
60
|
-
gitignore.write_text("*.backup\n.DS_Store\n")
|
|
61
|
-
|
|
62
|
-
# Create README
|
|
63
|
-
readme = store_path / "README.md"
|
|
64
|
-
readme.write_text(
|
|
65
|
-
f"""# MCLI Commands Store
|
|
66
|
-
|
|
67
|
-
Personal workflow commands for mcli framework.
|
|
68
|
-
|
|
69
|
-
## Usage
|
|
70
|
-
|
|
71
|
-
Push commands:
|
|
72
|
-
```bash
|
|
73
|
-
mcli store push
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Pull commands:
|
|
77
|
-
```bash
|
|
78
|
-
mcli store pull
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Sync (bidirectional):
|
|
82
|
-
```bash
|
|
83
|
-
mcli store sync
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Structure
|
|
87
|
-
|
|
88
|
-
All JSON command files from `~/.mcli/commands/` are stored here and version controlled.
|
|
89
|
-
|
|
90
|
-
Last updated: {datetime.now().isoformat()}
|
|
91
|
-
"""
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
# Add remote if provided
|
|
95
|
-
if remote:
|
|
96
|
-
subprocess.run(
|
|
97
|
-
["git", "remote", "add", "origin", remote], cwd=store_path, check=True
|
|
98
|
-
)
|
|
99
|
-
success(f"Added remote: {remote}")
|
|
100
|
-
else:
|
|
101
|
-
info(f"Git repository already exists at {store_path}")
|
|
102
|
-
|
|
103
|
-
# Save store path to config
|
|
104
|
-
config_file = Path.home() / ".mcli" / "store.conf"
|
|
105
|
-
config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
106
|
-
config_file.write_text(str(store_path))
|
|
107
|
-
|
|
108
|
-
success(f"Command store initialized at {store_path}")
|
|
109
|
-
info(f"Store path saved to {config_file}")
|
|
110
|
-
|
|
111
|
-
except subprocess.CalledProcessError as e:
|
|
112
|
-
error(f"Git command failed: {e}")
|
|
113
|
-
logger.error(f"Git init failed: {e}")
|
|
114
|
-
except Exception as e:
|
|
115
|
-
error(f"Failed to initialize store: {e}")
|
|
116
|
-
logger.exception(e)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
@store.command(name="push")
|
|
120
|
-
@click.option("--message", "-m", help="Commit message")
|
|
121
|
-
@click.option("--all", "-a", is_flag=True, help="Push all files (including backups)")
|
|
122
|
-
@click.option(
|
|
123
|
-
"--global", "-g", "is_global", is_flag=True, help="Push global commands instead of local"
|
|
124
|
-
)
|
|
125
|
-
def push_commands(message, all, is_global):
|
|
126
|
-
"""
|
|
127
|
-
Push commands to git store.
|
|
128
|
-
|
|
129
|
-
By default pushes local commands (if in git repo), use --global/-g for global commands.
|
|
130
|
-
"""
|
|
131
|
-
try:
|
|
132
|
-
store_path = _get_store_path()
|
|
133
|
-
from mcli.lib.paths import get_custom_commands_dir
|
|
134
|
-
|
|
135
|
-
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
136
|
-
|
|
137
|
-
# Copy commands to store
|
|
138
|
-
info(f"Copying commands from {COMMANDS_PATH} to {store_path}...")
|
|
139
|
-
|
|
140
|
-
copied_count = 0
|
|
141
|
-
for item in COMMANDS_PATH.glob("*"):
|
|
142
|
-
# Skip backups unless --all specified
|
|
143
|
-
if not all and item.name.endswith(".backup"):
|
|
144
|
-
continue
|
|
145
|
-
|
|
146
|
-
dest = store_path / item.name
|
|
147
|
-
if item.is_file():
|
|
148
|
-
shutil.copy2(item, dest)
|
|
149
|
-
copied_count += 1
|
|
150
|
-
elif item.is_dir():
|
|
151
|
-
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
152
|
-
copied_count += 1
|
|
153
|
-
|
|
154
|
-
success(f"Copied {copied_count} items to store")
|
|
155
|
-
|
|
156
|
-
# Git add, commit, push
|
|
157
|
-
subprocess.run(["git", "add", "."], cwd=store_path, check=True)
|
|
158
|
-
|
|
159
|
-
# Check if there are changes
|
|
160
|
-
result = subprocess.run(
|
|
161
|
-
["git", "status", "--porcelain"], cwd=store_path, capture_output=True, text=True
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
if not result.stdout.strip():
|
|
165
|
-
info("No changes to commit")
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
# Commit with message
|
|
169
|
-
commit_msg = message or f"Update commands {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
170
|
-
subprocess.run(["git", "commit", "-m", commit_msg], cwd=store_path, check=True)
|
|
171
|
-
success(f"Committed changes: {commit_msg}")
|
|
172
|
-
|
|
173
|
-
# Push to remote if configured
|
|
174
|
-
try:
|
|
175
|
-
subprocess.run(["git", "push"], cwd=store_path, check=True, capture_output=True)
|
|
176
|
-
success("Pushed to remote")
|
|
177
|
-
except subprocess.CalledProcessError:
|
|
178
|
-
warning("No remote configured or push failed. Commands committed locally.")
|
|
179
|
-
|
|
180
|
-
except Exception as e:
|
|
181
|
-
error(f"Failed to push commands: {e}")
|
|
182
|
-
logger.exception(e)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
@store.command(name="pull")
|
|
186
|
-
@click.option("--force", "-f", is_flag=True, help="Overwrite local commands without backup")
|
|
187
|
-
@click.option(
|
|
188
|
-
"--global", "-g", "is_global", is_flag=True, help="Pull to global commands instead of local"
|
|
189
|
-
)
|
|
190
|
-
def pull_commands(force, is_global):
|
|
191
|
-
"""
|
|
192
|
-
Pull commands from git store.
|
|
193
|
-
|
|
194
|
-
By default pulls to local commands (if in git repo), use --global/-g for global commands.
|
|
195
|
-
"""
|
|
196
|
-
try:
|
|
197
|
-
store_path = _get_store_path()
|
|
198
|
-
from mcli.lib.paths import get_custom_commands_dir
|
|
199
|
-
|
|
200
|
-
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
201
|
-
|
|
202
|
-
# Pull from remote
|
|
203
|
-
try:
|
|
204
|
-
subprocess.run(["git", "pull"], cwd=store_path, check=True)
|
|
205
|
-
success("Pulled latest changes from remote")
|
|
206
|
-
except subprocess.CalledProcessError:
|
|
207
|
-
warning("No remote configured or pull failed. Using local store.")
|
|
208
|
-
|
|
209
|
-
# Backup existing commands if not force
|
|
210
|
-
if not force and COMMANDS_PATH.exists():
|
|
211
|
-
backup_dir = (
|
|
212
|
-
COMMANDS_PATH.parent / f"commands_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
213
|
-
)
|
|
214
|
-
shutil.copytree(COMMANDS_PATH, backup_dir)
|
|
215
|
-
info(f"Backed up existing commands to {backup_dir}")
|
|
216
|
-
|
|
217
|
-
# Copy from store to commands directory
|
|
218
|
-
info(f"Copying commands from {store_path} to {COMMANDS_PATH}...")
|
|
219
|
-
|
|
220
|
-
COMMANDS_PATH.mkdir(parents=True, exist_ok=True)
|
|
221
|
-
|
|
222
|
-
copied_count = 0
|
|
223
|
-
for item in store_path.glob("*"):
|
|
224
|
-
# Skip git directory and README
|
|
225
|
-
if item.name in [".git", "README.md", ".gitignore"]:
|
|
226
|
-
continue
|
|
227
|
-
|
|
228
|
-
dest = COMMANDS_PATH / item.name
|
|
229
|
-
if item.is_file():
|
|
230
|
-
shutil.copy2(item, dest)
|
|
231
|
-
copied_count += 1
|
|
232
|
-
elif item.is_dir():
|
|
233
|
-
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
234
|
-
copied_count += 1
|
|
235
|
-
|
|
236
|
-
success(f"Pulled {copied_count} items from store")
|
|
237
|
-
|
|
238
|
-
except Exception as e:
|
|
239
|
-
error(f"Failed to pull commands: {e}")
|
|
240
|
-
logger.exception(e)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
@store.command(name="sync")
|
|
244
|
-
@click.option("--message", "-m", help="Commit message if pushing")
|
|
245
|
-
@click.option(
|
|
246
|
-
"--global", "-g", "is_global", is_flag=True, help="Sync global commands instead of local"
|
|
247
|
-
)
|
|
248
|
-
def sync_commands(message, is_global):
|
|
249
|
-
"""
|
|
250
|
-
Sync commands bidirectionally (pull then push if changes).
|
|
251
|
-
|
|
252
|
-
By default syncs local commands (if in git repo), use --global/-g for global commands.
|
|
253
|
-
"""
|
|
254
|
-
try:
|
|
255
|
-
store_path = _get_store_path()
|
|
256
|
-
from mcli.lib.paths import get_custom_commands_dir
|
|
257
|
-
|
|
258
|
-
COMMANDS_PATH = get_custom_commands_dir(global_mode=is_global)
|
|
259
|
-
|
|
260
|
-
# First pull
|
|
261
|
-
info("Pulling latest changes...")
|
|
262
|
-
try:
|
|
263
|
-
subprocess.run(["git", "pull"], cwd=store_path, check=True, capture_output=True)
|
|
264
|
-
success("Pulled from remote")
|
|
265
|
-
except subprocess.CalledProcessError:
|
|
266
|
-
warning("No remote or pull failed")
|
|
267
|
-
|
|
268
|
-
# Then push local changes
|
|
269
|
-
info("Pushing local changes...")
|
|
270
|
-
|
|
271
|
-
# Copy commands
|
|
272
|
-
for item in COMMANDS_PATH.glob("*"):
|
|
273
|
-
if item.name.endswith(".backup"):
|
|
274
|
-
continue
|
|
275
|
-
dest = store_path / item.name
|
|
276
|
-
if item.is_file():
|
|
277
|
-
shutil.copy2(item, dest)
|
|
278
|
-
elif item.is_dir():
|
|
279
|
-
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
280
|
-
|
|
281
|
-
# Check for changes
|
|
282
|
-
result = subprocess.run(
|
|
283
|
-
["git", "status", "--porcelain"], cwd=store_path, capture_output=True, text=True
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
if not result.stdout.strip():
|
|
287
|
-
success("Everything in sync!")
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
# Commit and push
|
|
291
|
-
subprocess.run(["git", "add", "."], cwd=store_path, check=True)
|
|
292
|
-
commit_msg = message or f"Sync commands {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
293
|
-
subprocess.run(["git", "commit", "-m", commit_msg], cwd=store_path, check=True)
|
|
294
|
-
|
|
295
|
-
try:
|
|
296
|
-
subprocess.run(["git", "push"], cwd=store_path, check=True, capture_output=True)
|
|
297
|
-
success("Synced and pushed to remote")
|
|
298
|
-
except subprocess.CalledProcessError:
|
|
299
|
-
success("Synced locally (no remote configured)")
|
|
300
|
-
|
|
301
|
-
except Exception as e:
|
|
302
|
-
error(f"Sync failed: {e}")
|
|
303
|
-
logger.exception(e)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
@store.command(name="status")
|
|
307
|
-
def store_status():
|
|
308
|
-
"""Show git status of command store."""
|
|
309
|
-
try:
|
|
310
|
-
store_path = _get_store_path()
|
|
311
|
-
|
|
312
|
-
click.echo(f"\n📦 Store: {store_path}\n")
|
|
313
|
-
|
|
314
|
-
# Git status
|
|
315
|
-
result = subprocess.run(
|
|
316
|
-
["git", "status", "--short", "--branch"], cwd=store_path, capture_output=True, text=True
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
if result.stdout:
|
|
320
|
-
click.echo(result.stdout)
|
|
321
|
-
|
|
322
|
-
# Show remote
|
|
323
|
-
result = subprocess.run(
|
|
324
|
-
["git", "remote", "-v"], cwd=store_path, capture_output=True, text=True
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
if result.stdout:
|
|
328
|
-
click.echo("\n🌐 Remotes:")
|
|
329
|
-
click.echo(result.stdout)
|
|
330
|
-
else:
|
|
331
|
-
info("\nNo remote configured")
|
|
332
|
-
|
|
333
|
-
click.echo()
|
|
334
|
-
|
|
335
|
-
except Exception as e:
|
|
336
|
-
error(f"Failed to get status: {e}")
|
|
337
|
-
logger.exception(e)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
@store.command(name="config")
|
|
341
|
-
@click.option("--remote", "-r", help="Set git remote URL")
|
|
342
|
-
@click.option("--path", "-p", type=click.Path(), help="Change store path")
|
|
343
|
-
def configure_store(remote, path):
|
|
344
|
-
"""Configure store settings."""
|
|
345
|
-
try:
|
|
346
|
-
store_path = _get_store_path()
|
|
347
|
-
|
|
348
|
-
if path:
|
|
349
|
-
new_path = Path(path).expanduser().resolve()
|
|
350
|
-
config_file = Path.home() / ".mcli" / "store.conf"
|
|
351
|
-
config_file.write_text(str(new_path))
|
|
352
|
-
success(f"Store path updated to: {new_path}")
|
|
353
|
-
return
|
|
354
|
-
|
|
355
|
-
if remote:
|
|
356
|
-
# Check if remote exists
|
|
357
|
-
result = subprocess.run(
|
|
358
|
-
["git", "remote"], cwd=store_path, capture_output=True, text=True
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
if "origin" in result.stdout:
|
|
362
|
-
subprocess.run(
|
|
363
|
-
["git", "remote", "set-url", "origin", remote], cwd=store_path, check=True
|
|
364
|
-
)
|
|
365
|
-
success(f"Updated remote URL: {remote}")
|
|
366
|
-
else:
|
|
367
|
-
subprocess.run(
|
|
368
|
-
["git", "remote", "add", "origin", remote], cwd=store_path, check=True
|
|
369
|
-
)
|
|
370
|
-
success(f"Added remote URL: {remote}")
|
|
371
|
-
|
|
372
|
-
except Exception as e:
|
|
373
|
-
error(f"Configuration failed: {e}")
|
|
374
|
-
logger.exception(e)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
@store.command(name="list")
|
|
378
|
-
@click.option("--store-dir", "-s", is_flag=True, help="List store instead of local")
|
|
379
|
-
def list_commands(store_dir):
|
|
380
|
-
"""List all commands."""
|
|
381
|
-
try:
|
|
382
|
-
if store_dir:
|
|
383
|
-
store_path = _get_store_path()
|
|
384
|
-
path = store_path
|
|
385
|
-
title = f"Commands in store ({store_path})"
|
|
386
|
-
else:
|
|
387
|
-
path = COMMANDS_PATH
|
|
388
|
-
title = f"Local commands ({COMMANDS_PATH})"
|
|
389
|
-
|
|
390
|
-
click.echo(f"\n{title}:\n")
|
|
391
|
-
|
|
392
|
-
if not path.exists():
|
|
393
|
-
warning(f"Directory does not exist: {path}")
|
|
394
|
-
return
|
|
395
|
-
|
|
396
|
-
items = sorted(path.glob("*"))
|
|
397
|
-
if not items:
|
|
398
|
-
info("No commands found")
|
|
399
|
-
return
|
|
400
|
-
|
|
401
|
-
for item in items:
|
|
402
|
-
if item.name in [".git", ".gitignore", "README.md"]:
|
|
403
|
-
continue
|
|
404
|
-
|
|
405
|
-
if item.is_file():
|
|
406
|
-
size = item.stat().st_size / 1024
|
|
407
|
-
modified = datetime.fromtimestamp(item.stat().st_mtime).strftime("%Y-%m-%d %H:%M")
|
|
408
|
-
click.echo(f" 📄 {item.name:<40} {size:>8.1f} KB {modified}")
|
|
409
|
-
elif item.is_dir():
|
|
410
|
-
count = len(list(item.glob("*")))
|
|
411
|
-
click.echo(f" 📁 {item.name:<40} {count:>3} files")
|
|
412
|
-
|
|
413
|
-
click.echo()
|
|
414
|
-
|
|
415
|
-
except Exception as e:
|
|
416
|
-
error(f"Failed to list commands: {e}")
|
|
417
|
-
logger.exception(e)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
@store.command(name="show")
|
|
421
|
-
@click.argument("command_name")
|
|
422
|
-
@click.option("--store-dir", "-s", is_flag=True, help="Show from store instead of local")
|
|
423
|
-
def show_command(command_name, store_dir):
|
|
424
|
-
"""Show command file contents."""
|
|
425
|
-
try:
|
|
426
|
-
if store_dir:
|
|
427
|
-
store_path = _get_store_path()
|
|
428
|
-
path = store_path / command_name
|
|
429
|
-
else:
|
|
430
|
-
path = COMMANDS_PATH / command_name
|
|
431
|
-
|
|
432
|
-
if not path.exists():
|
|
433
|
-
error(f"Command not found: {command_name}")
|
|
434
|
-
return
|
|
435
|
-
|
|
436
|
-
if path.is_file():
|
|
437
|
-
click.echo(f"\n📄 {path}:\n")
|
|
438
|
-
click.echo(path.read_text())
|
|
439
|
-
else:
|
|
440
|
-
info(f"{command_name} is a directory")
|
|
441
|
-
for item in sorted(path.glob("*")):
|
|
442
|
-
click.echo(f" {item.name}")
|
|
443
|
-
|
|
444
|
-
click.echo()
|
|
445
|
-
|
|
446
|
-
except Exception as e:
|
|
447
|
-
error(f"Failed to show command: {e}")
|
|
448
|
-
logger.exception(e)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|