From ab8067f0c58bf1d69d9abe5699c2f796a0f22502 Mon Sep 17 00:00:00 2001 From: Zhengshou Lai Date: Mon, 13 Apr 2026 11:38:01 +0800 Subject: [PATCH] Initial commit: mycli toolkit with all commands --- Makefile | 51 +++++++ README.md | 88 ++++++++++++ mycli/__init__.py | 3 + mycli/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 214 bytes mycli/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 226 bytes mycli/__pycache__/cli.cpython-310.pyc | Bin 0 -> 927 bytes mycli/__pycache__/cli.cpython-314.pyc | Bin 0 -> 1602 bytes mycli/__pycache__/config.cpython-310.pyc | Bin 0 -> 3125 bytes mycli/__pycache__/config.cpython-314.pyc | Bin 0 -> 5437 bytes mycli/__pycache__/utils.cpython-310.pyc | Bin 0 -> 1510 bytes mycli/__pycache__/utils.cpython-314.pyc | Bin 0 -> 2429 bytes mycli/cli.py | 34 +++++ mycli/commands/__init__.py | 10 ++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 553 bytes .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 569 bytes .../commands/__pycache__/bib.cpython-310.pyc | Bin 0 -> 1702 bytes .../commands/__pycache__/bib.cpython-314.pyc | Bin 0 -> 2730 bytes .../__pycache__/config_cmd.cpython-310.pyc | Bin 0 -> 1959 bytes .../__pycache__/config_cmd.cpython-314.pyc | Bin 0 -> 3142 bytes .../commands/__pycache__/env.cpython-310.pyc | Bin 0 -> 2744 bytes .../commands/__pycache__/env.cpython-314.pyc | Bin 0 -> 5003 bytes .../commands/__pycache__/git.cpython-310.pyc | Bin 0 -> 2876 bytes .../commands/__pycache__/git.cpython-314.pyc | Bin 0 -> 4820 bytes .../__pycache__/image.cpython-310.pyc | Bin 0 -> 3282 bytes .../__pycache__/image.cpython-314.pyc | Bin 0 -> 6364 bytes .../__pycache__/latex.cpython-310.pyc | Bin 0 -> 1485 bytes .../__pycache__/latex.cpython-314.pyc | Bin 0 -> 2414 bytes .../commands/__pycache__/pdf.cpython-310.pyc | Bin 0 -> 2473 bytes .../commands/__pycache__/pdf.cpython-314.pyc | Bin 0 -> 4417 bytes .../__pycache__/self_mgmt.cpython-310.pyc | Bin 0 -> 1972 bytes .../__pycache__/self_mgmt.cpython-314.pyc | Bin 0 -> 3820 bytes .../commands/__pycache__/ssh.cpython-310.pyc | Bin 0 -> 2959 bytes .../commands/__pycache__/ssh.cpython-314.pyc | Bin 0 -> 5629 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 970 bytes .../__pycache__/utils.cpython-314.pyc | Bin 0 -> 1446 bytes .../__pycache__/video.cpython-310.pyc | Bin 0 -> 1171 bytes .../__pycache__/video.cpython-314.pyc | Bin 0 -> 1805 bytes mycli/commands/bib.py | 54 ++++++++ mycli/commands/config_cmd.py | 58 ++++++++ mycli/commands/env.py | 85 ++++++++++++ mycli/commands/git.py | 98 +++++++++++++ mycli/commands/image.py | 131 ++++++++++++++++++ mycli/commands/latex.py | 49 +++++++ mycli/commands/pdf.py | 98 +++++++++++++ mycli/commands/self_mgmt.py | 86 ++++++++++++ mycli/commands/ssh.py | 100 +++++++++++++ mycli/commands/utils.py | 32 +++++ mycli/commands/video.py | 37 +++++ mycli/config.json | 34 +++++ mycli/config.py | 77 ++++++++++ mycli/utils.py | 42 ++++++ pyproject.toml | 15 ++ uv.lock | 58 ++++++++ 53 files changed, 1240 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 mycli/__init__.py create mode 100644 mycli/__pycache__/__init__.cpython-310.pyc create mode 100644 mycli/__pycache__/__init__.cpython-314.pyc create mode 100644 mycli/__pycache__/cli.cpython-310.pyc create mode 100644 mycli/__pycache__/cli.cpython-314.pyc create mode 100644 mycli/__pycache__/config.cpython-310.pyc create mode 100644 mycli/__pycache__/config.cpython-314.pyc create mode 100644 mycli/__pycache__/utils.cpython-310.pyc create mode 100644 mycli/__pycache__/utils.cpython-314.pyc create mode 100644 mycli/cli.py create mode 100644 mycli/commands/__init__.py create mode 100644 mycli/commands/__pycache__/__init__.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/__init__.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/bib.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/bib.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/config_cmd.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/config_cmd.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/env.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/env.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/git.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/git.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/image.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/image.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/latex.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/latex.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/pdf.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/pdf.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/self_mgmt.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/self_mgmt.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/ssh.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/ssh.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/utils.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/utils.cpython-314.pyc create mode 100644 mycli/commands/__pycache__/video.cpython-310.pyc create mode 100644 mycli/commands/__pycache__/video.cpython-314.pyc create mode 100644 mycli/commands/bib.py create mode 100644 mycli/commands/config_cmd.py create mode 100644 mycli/commands/env.py create mode 100644 mycli/commands/git.py create mode 100644 mycli/commands/image.py create mode 100644 mycli/commands/latex.py create mode 100644 mycli/commands/pdf.py create mode 100644 mycli/commands/self_mgmt.py create mode 100644 mycli/commands/ssh.py create mode 100644 mycli/commands/utils.py create mode 100644 mycli/commands/video.py create mode 100644 mycli/config.json create mode 100644 mycli/config.py create mode 100644 mycli/utils.py create mode 100644 pyproject.toml create mode 100644 uv.lock diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0034b5f --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +ROOT_DIR := $(shell pwd) +VENV_MYCLI := $(ROOT_DIR)/.venv/bin/mycli +USER_LOCAL_MYCLI := $(HOME)/.local/bin/mycli +COMP_DIR := $(HOME)/.local/bin/completions +# Use user's login shell for completion detection +SHELL_NAME := $(notdir $(basename $(shell echo $$SHELL))) + +.PHONY: help install uninstall _symlink-mycli _install-completions _uninstall-completions + +help: + @echo "Usage: make [target]" + @echo "" + @echo " install Sync venv, symlink bin, install completions" + @echo " uninstall Remove bin and completions" + +install: + @cd "$(ROOT_DIR)" && \ + if command -v uv >/dev/null 2>&1; then \ + uv sync; \ + else \ + test -x .venv/bin/pip || python3 -m venv .venv; \ + .venv/bin/pip install -e .; \ + fi + @$(MAKE) _symlink-mycli + @$(MAKE) _install-completions + +_symlink-mycli: + @test -x "$(VENV_MYCLI)" || { echo "error: missing $(VENV_MYCLI)"; exit 1; } + @mkdir -p "$(HOME)/.local/bin" + @ln -sf "$(VENV_MYCLI)" "$(USER_LOCAL_MYCLI)" + @echo "Linked $(USER_LOCAL_MYCLI) -> $(VENV_MYCLI)" + +_install-completions: + @mkdir -p "$(COMP_DIR)" +ifeq ($(SHELL_NAME),zsh) + @_MYCLI_COMPLETE=zsh_source mycli > "$(COMP_DIR)/_mycli" 2>/dev/null && \ + echo "Installed zsh completion: $(COMP_DIR)/_mycli" || \ + echo "Warning: failed to generate zsh completion" +else ifeq ($(SHELL_NAME),bash) + @_MYCLI_COMPLETE=bash_source mycli > "$(COMP_DIR)/mycli.bash" 2>/dev/null && \ + echo "Installed bash completion: $(COMP_DIR)/mycli.bash" || \ + echo "Warning: failed to generate bash completion" +endif + +uninstall: _uninstall-completions + @rm -f "$(USER_LOCAL_MYCLI)" + @echo "Removed $(USER_LOCAL_MYCLI)" + +_uninstall-completions: + @rm -f "$(COMP_DIR)/_mycli" "$(COMP_DIR)/mycli.bash" + @echo "Removed completions" diff --git a/README.md b/README.md new file mode 100644 index 0000000..649eba0 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# MyCLI + +Personal CLI toolkit - unified interface for various utility scripts. + +## Installation + +```bash +make install +``` + +## Commands + +```bash +mycli --help + +# PDF utilities +mycli pdf compress *.pdf +mycli pdf crop *.pdf +mycli pdf to-tiff *.pdf + +# Image utilities +mycli image eps-to-pdf *.eps +mycli image jpg-to-pdf *.jpg -o output.pdf +mycli image tiff-to-pdf *.tiff +mycli image tiff-compress *.tiff + +# LaTeX utilities +mycli latex compile *.tex +mycli latex count document.tex + +# Video utilities +mycli video avi-to-mp4 *.avi + +# BibTeX utilities +mycli bib to-markdown *.bib +mycli bib cv-update + +# Utilities +mycli utils albany-cleanup + +# Environment & Navigation +mycli env list # List configured paths +mycli env goto research # Print path for cd +mycli env cd apaam # Start shell in path +mycli env status # Show environment status +cd $(mycli env goto study) # Use in scripts + +# SSH Connections +mycli ssh list # List configured hosts +mycli ssh connect tianhe # SSH to host +mycli ssh connect starlight +mycli ssh tunnel tianhe # Create port tunnel +mycli ssh cmd tianhe "ls" # Run remote command + +# Git Proxy Management +mycli git proxy # Show proxy status +mycli git proxy fastgithub # Set proxy +mycli git proxy pandafan +mycli git proxy -u # Unset proxy +mycli git proxy-list # List available proxies + +# Self Management +mycli self update # Update mycli +mycli self uninstall # Uninstall mycli +mycli self info # Show installation info + +# Configuration (config.json) +mycli config show # Show full config +mycli config get paths # Get paths section +mycli config get paths.apaam # Get specific path +mycli config paths # List all paths with existence check +mycli config where # Show config file location +mycli config edit # Edit config in $EDITOR +``` + +## Configuration + +All settings are stored in `mycli/config.json`: +- `paths`: Quick navigation paths +- `ssh_hosts`: SSH connection settings +- `git_proxies`: Git proxy URLs +- `openfoam`: OpenFOAM mount settings + +## Uninstall + +```bash +make uninstall +``` diff --git a/mycli/__init__.py b/mycli/__init__.py new file mode 100644 index 0000000..a9d68bf --- /dev/null +++ b/mycli/__init__.py @@ -0,0 +1,3 @@ +"""MyCLI - Personal CLI toolkit.""" + +__version__ = "0.1.0" diff --git a/mycli/__pycache__/__init__.cpython-310.pyc b/mycli/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de2e24aa235ef7c606f7bcee22a604c2f3563112 GIT binary patch literal 214 zcmd1j<>g`kg1Y!Snbtu1F^Gc<7=auIATH(s5-AK(3@MCJj44dP44TYUvc8qhKAsA? z3IVA_#rb)OISL?dNq&A#c4moQ6{~@sp`L-CCetnU`1q9kTZlX-=wL5j)TrkV}hsfy4)9Mn=Y)49XAqM4PyaSb-t{JM}zL literal 0 HcmV?d00001 diff --git a/mycli/__pycache__/cli.cpython-310.pyc b/mycli/__pycache__/cli.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4b307694a5181692965b66af217c8233b59723f GIT binary patch literal 927 zcmZ{iL2uJA7>4a6PST{^x&h*Z)N47ko5T?z#JC`_3TfiviokrlC#`6&+m-*Y z9r;TZH%|Nk4qV`KR%JVYEx&$jeYRhJwjK-;1SQPB%UchjA8zot5E#6LR{sLwh$4=; zS7M5x_e)Rt)K>uw6rn_gG*l6dR7_*lqdk?-M5Q!UecD$8I>1Qa+k-pM;*bZN@bIh0 zBOc$NYkZCAPz-O6gkSh~7$O}9MbI%RNXKCjb{rK^$8iyN-0LXmDD9}<(ZCV!@#F^4 zWATKiOV4b@@il^3@1kwv{_gqt_Uz+_k;-H>5>;ciqm`CbGx-TgL9=mc{T08mM5=5q zEGe@_e6eIBxzN_Xl$VyQ8(G#i64i#~irc8J7xyriO$%`?%Bz-=Tvu0e?s!K|Ca04V zOVlfNv^oIc_yUc+-Lp@yPFGnua@T0IE|;>I z%)TLW2tg0>`*ixbhR|ubTht4^p1#-lTEUI$>G}4ZtfsAzy@I95YHNMyV|N~W9~1=U z2)h59a=)!XZF&({!?Q7NS3G#Qry<1Iz$N?zP`h`6&9R$}8FTZDji1_xF|KpQC;=Dw zk{+}MC%INCt9WgkC*wPD*)Z-mo6t$*#Mw4IC(f2ho%BI0nH#-c(Sv=unO-%J_0}j& zGtSxmfXy$f?8iZZgFk-(PB_$K literal 0 HcmV?d00001 diff --git a/mycli/__pycache__/cli.cpython-314.pyc b/mycli/__pycache__/cli.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..027f203b6c36935fcf92077f70e2fe7e0ce7eb13 GIT binary patch literal 1602 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

PM?!;nuVxjAa zg)O^2m!G^J@=8`d#inGRjtG>r=nSku4o1FhY5*R2(6@_Ie2PyAKXjk=vakDPLY!442Q{*c-ydpPD=PsRn^XyE^hn??r&JF$P(9OnYKbH?OaKIQ?AUm(% zlCfs|jDd$|DDC{2%tRqqVqZ_7=zG1S=G|XH_*KQER$ezWqX4RPnM|&b&1B3FPKITy zbFkcvqkY(+9nC}7J&)ZecCSEZw?cUdwWOZSsFr2wr%RSbVaDc~0yL|E2WDcNb8biX zJixM%i4MfNdKgd@y03o)yS^C0 zKC4{RjWevWW)IjtCjiY8XA=C#V9QTC81Fa7;THEsvA$mwIF7qbVqcNK7o`46((xr} ZTlXE{xM4VeI+r6n@i)EI@e3cD?Z5g@K(qh= literal 0 HcmV?d00001 diff --git a/mycli/__pycache__/utils.cpython-310.pyc b/mycli/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24dfad971830db811a454dacee8da0334020a78e GIT binary patch literal 1510 zcmZuxUuzsU5SMoM_V(6xYB$tPO6n?wK5R;$*;^fw zmg_B-Qc^?nl74|wh@mg}4*dlE2qJHN@>l3X>5TRqJEdAQ`m>{%(Qkeuxv>!wXn$sJ z@;3n?f8pYCYs2Cz=z1RnCj-JM_X;|oZuAD88~uULeIA^ULBQ8|C|V-CPIz0ic_h3^ zGzxi#$0w~phkK7mvR;4w*yKfS7Hm|NnaO3TSz2<&MJ82hq|$686`L-yBJZAoV{(vD z99GSP5rQQ?bo~(sLtfDro4}s%`zj|7#tOXWpX-|B@;@a zw_}xNVpJ8yqML!aw+$U2KA%9>e}Jf?7i39tvZTM$U+5F^2;wkg@fju0JWA>QWlOxGH&{Szif$n0VG39a|p| zIYX@LopaSuniqnzd2Yr`e4iV@jtiDhg(;{z@$QjSM)2<0b66*9c55H>52V>g^Y;*7 z+k!-9Pi;$!Y%Faw0);Z23LD%5dp6e9(M-uqXl>uWn-<{h+h*CEj+HQ#D$%VCwc!w= z4VnW)43a|cuj49JbO+4{<8O23#EHSJ=SAc_4Z^| zO+{&R@4@2hyzDu~tz-oh)!o^`w&yCH&4gNq>uCnU^ET-fdX2_3`f(G+kZ!v9I>?xM z|I|AT$%YUTu&aB!kXc&jJKdEbfV$&|FJ+-^3wL$msckT=uA}%6#Rn*^fv_9znCrln z4naiz6&k#!Zou@=`q`8N%wqvChbz3WOwJoJ8S`}?x<*fiOz6^;%aZ26SmZO5ipy}r6xCp(gsqWVqtx4fZazx4ZHc@0G*{vUN2lnx2KX< Ug}8&It1;w&L|#Op`=4(A3&W6+Pyhe` literal 0 HcmV?d00001 diff --git a/mycli/__pycache__/utils.cpython-314.pyc b/mycli/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dda4fdb3922c4f2d4420b5cdee50c4636b60d40d GIT binary patch literal 2429 zcmbVNO>7fK6rR~#JBhuJA438mz$8I{Lt;aL5~Wcp^H%|C8kP`L)xz5BuH%LEt~EO@ zNz{WyE48ABAo@cpRVs%b!l_q!st`TUo*cD^vh|HidrO;0p!U)?yS5WZq*h&NXWq`f zdGF0P-+S-C=1ma<&yQNgtVjqw*hUQR}In*Uthm!n^W|@gm z4cFYm%p-tTT0RYe3NfVXKgWc+SQom{1$3mke*zuG3Wi7;k5vB`@C1)Xm>oD-^GujS zauADU5Y5Tw9h(@G@_?qOsu|g$Wz8hCx_J;9;N1=H@mFcyA!9U&RLA}Cl96!BQ~I(DNqwi?&(=th@u z4TKz4q=N9eVB)lY&0Kp-UcU$8eD^=b8G<-BueY#KoS@mVUUOe{N$=$z*PNwoy>2&0 zWdj?4NBCG3(f^6ZmYO#>dxA&uf}IUxQ5tHvzM#AD9bB*J=CFxg6L%sL#~Wv3CxB+0 z{Idw|V0VNXtVnI{(F?mwhsZq1x72#hn3;4`s-LkLtQHF_E_sqlr(7?5^_o9p z@WRvGsuZ~417;d64<278P zYN23IZ}8E^;rnHaVlO|{&SMD;)CwEF`L&r4`YlAC&-#5(4;lw zE6rpD2~DITx6Ylrajx7su%Lc(E|IL_WUd-J`YSs45PAsyDb(0L5i2(K3K)y(hj1oi5p2B93j4 zWl0grx%4|fmM3a?9r&IzfbZdexbcuDS*FW8(PhBgNq~iNo_H;gT?Na4ys7d%j=eCn zpuEalew3a7A~};$ZOBuV8JP^_!0^iN9)O{Qo}nmy^$$LLI{szvXKycY@oVN!-*-Mv zr596a|2ORdeXU;o;UffZls7frnEt(`1&wS1C-H<3bQox1zbQqVsAM&R{r<%Zi(e`< z=&^8e$gZHF1x?PhrxKA!3et2aLf`?&hDfc_@V$_^K}r(UB^n88>s>MwP?R_=W1TET;h~gKQY9udT2r+G!skUZBC;^aw z#s0;Kurn@FehvOQje+EH#_iqPA6!}#R5DsnWN9D#@$YOW2tev~B)89v+!&efU5p;R zbFmyfx{w8_EFJZ4DUQeEVXq;R(H#wLd^>FN{Zh_7KSg|tcag6h_-h=_J9^PFMkvo$ j&K{S*uq0xPpP)leQOmEW^(m77499TnS$~Z*zJ-4PJp>eP literal 0 HcmV?d00001 diff --git a/mycli/cli.py b/mycli/cli.py new file mode 100644 index 0000000..8b3c61f --- /dev/null +++ b/mycli/cli.py @@ -0,0 +1,34 @@ +"""MyCLI main entry point.""" + +import click + +from mycli.commands import pdf, image, latex, video, bib, utils, env_cmd, ssh_cmd, git_cmd, self_cmd, config_cmd + + +@click.group() +@click.version_option(version="0.1.0", prog_name="mycli") +def cli(): + """Personal CLI toolkit.""" + pass + + +# Register command groups +cli.add_command(pdf.pdf) +cli.add_command(image.image) +cli.add_command(latex.latex) +cli.add_command(video.video) +cli.add_command(bib.bib) +cli.add_command(utils.utils) +cli.add_command(env_cmd) +cli.add_command(ssh_cmd) +cli.add_command(git_cmd) +cli.add_command(self_cmd) +cli.add_command(config_cmd) + + +def main(): + cli() + + +if __name__ == "__main__": + main() diff --git a/mycli/commands/__init__.py b/mycli/commands/__init__.py new file mode 100644 index 0000000..79bfce1 --- /dev/null +++ b/mycli/commands/__init__.py @@ -0,0 +1,10 @@ +"""MyCLI subcommands.""" + +from . import pdf, image, latex, video, bib, utils +from .env import env_cmd +from .ssh import ssh_cmd +from .git import git_cmd +from .self_mgmt import self_cmd +from .config_cmd import config_cmd + +__all__ = ["pdf", "image", "latex", "video", "bib", "utils", "env_cmd", "ssh_cmd", "git_cmd", "self_cmd", "config_cmd"] diff --git a/mycli/commands/__pycache__/__init__.cpython-310.pyc b/mycli/commands/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d08acca4bbb02d4f0359dbb3009ed53b40df3fa7 GIT binary patch literal 553 zcmaKoyH3L}7=@iU*Iwx53A%P^UH}0C79`37EMBZOPE$+%6BH+i@;Gd~lEub^#1k;# z-)eygTmE!@Svo#G^>ElDc73kjFFtHWGLH;>5E~OpSe1nd)v7I?O!z28Q5(9JYZG>@RC?~2xTqd^ z4g#gt+k-4+yO)VB)Ml@Hd0lN}>85^zKW``^K!k`05hD^riopJwK4M^TGoPB6b5ZA< z8}h%2p*Ed!V$_Cnc8uC=&Xc7s8kErf+N1YMDb8O>Gr>0zU#HoU3OXL w9TZiov*qqwRvGMaCHFj3nQ<;FTLWJ_>`ZbDwdhoFg7xaLML=f$!28+$H}7DBvH$=8 literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/__init__.cpython-314.pyc b/mycli/commands/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6306df937a8dbca9108fbcc61acd64c627ddbba GIT binary patch literal 569 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 literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/config_cmd.cpython-310.pyc b/mycli/commands/__pycache__/config_cmd.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..031511384b3f84dfe60b1a19cf0d27d458811164 GIT binary patch literal 1959 zcmZux&2QsG6rV50j+^v@EoIdTVOD?$NXc?SLRAHHfdgzoTHp{Uk?WbJ?mBjM?67T= zNI>`669*0jDUrDFAE6N^IQ7hp1LD1L+N3HY&DVP~Z+`Q8A6dQb6KKC5{1FEZA%Ed! zu?g6`3qAi821(jPQppn9rgmm+W@p~!F!Mz8oVG)W;>Q&!q;pPXMY_+(8Ew1Dop{ol zQdyOrl~k?7pHjsqzFY!LP1aEZrH_Q~CpKREPIJQbiEnbVu-Qm<&{Ka2f!<;B}c5L7EjdHX6;nO9c60gPBU4N}$5j zRGJU8>V$a|$HSG6;SL-R4zocj-w8g4AmK{uOZ;cUFKNOQGJc%LXPYpR2ZkP zV=yR=^DH%v9Q69RaUfcv$402=C^O=Nlehq`(VfPzFc;WHqD>Wu?O4qVu+5iY@P$Wx zdY9JNI`gUCw3qu~ag^T(03_anZpqiMC<9~DR|#(XSBV&YASepNatWcAo7suO?2NA!YR2(q9H8AlfBtbc z|1{)5@YRs-2f-dHhirz5kd)6wf7F)S+`VrsS$H%oh^b*HPdLBA}t?yNZ53TV7g zcWoKBS!fB@mL7cW|2Q(x|esBab-kOnK~|fntGf@uk{_zCr0r-+J!d>?SZWc6w&^-n)CxJ?Fb;zjM!R zFAw?=$lCRFRT2<-O`&kLr|ghshx z(J@i%n^BaD^4(yGNK~jpo%Rr!-cj#xsPIzKMLnI4wzovPY!_{xqYeJ|l$a?D!GD>f z5%m}CxhMj=*bTU=#NsMb+(kR+YL}O22Y1n~aJ4H-w9BJRD+-6ET0Yga8|qjNCrwq? zq)bvvjwu;MGbLHiz+1|Q422oXBU3fZ!cO6p*OD1UE)4*QB8L#a<$(eGM`E3wY8a&U zq|Bt8R>5M4tH{tZgno}{+IReAz7@Rz#Y8})

>dT z*aj1FCKbs}TK?iI_JPd+JcbvVX|(Aq<5GE8Q6NV7|+ zCU=vU!J;JRFt)?CZIfY^#tfH%9lBkL0uxyFH!?wpxfO(F8vPAO1G57&1D_Atc)2mf zw76T!q+B2>s3Ao5M=>9QuhZyVm}AHxL6(Uz%6WtuQJe&yN{oq<&`{|t?D@UId+H$7 zB`B%HX)2T9+Y>OxTw|kbl#|%wBoLv7_(!h+|~!XmCmTxLt^nUB%TI!^91=503Fb@0eA5M-Z%dZ-$W|&D$ zRxCcH%BCe`u?n{Mn})7gLMoTZ8kPXbA>XoiMIP5JzIQ@3fi;8iEVaRYp=gU67e0qXcru=+v&z5cv$|I&2tnpb?`-Mb#B$tzE;tOi^Q3{QQs+q?m%pDXN-Saad1|+xd5DQgc38zMER@`cNzEgQt;U^ z5e1%NJ(7-1Mm7)D8>V<>SH&uU8HGrDwv`bDUCDA}fw>!`}qK# zt&gB-K~3=6ub@82m@E@_&xc}eO`vHgmIHv4`a-R!i&L2}LymMVt!Th*HgXh`T-{Ex zo-svJCbIaHwJ#0G5;l~cv@R#p#;J%q-9}c{Lh~meSqgPM6X*Gdt@+fWa6bHl_>V#G z&g`uRmli(^yvY$)zd!l5&_9~uBD@X_qk+6MQTqZeT@sOK_UG=-Ml6q^V^c|4f@&%m!}2H-0Mx)FA@E*k!XDg6^t@mo0ehvZ=vFPB7{U}gIrtvtv<`?Q#1bpqRTl5=gj*&@HW(z07opA+kzZQ_+O`#6K;N5j9PWVD)*sVy;mz}i) zt{2UA3ikv`0#XDidb=c(h+@)o!$XN}mt_BJ|4jdv zw^u7#o>#Q2@U3)7xWe)#5-D9yBybJrTdZ#L&l2f%C1N|W-3nt^&t>Vw#PSwyF!Tms z`I2~y-gW4KPFaU(h$)!u04-lnMo;C^$|-yTJd%_*UO}_LF%0u7I{X?1enVxik@yE{ z+VCLOJKHeRkU#vAY1m|0rfiEtY~uzQZTJx@t}@lnnd*GsvOQhpPFIyq_bxN_Ym8@h z|IGfpu*}qMvVNwKsMe9DVS|U)jryXwbIVNQChKK-$n?GqGTPWvoPLKldo{}(psW7^ JtOsbY{{@AWN2~w< literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/env.cpython-310.pyc b/mycli/commands/__pycache__/env.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44ee59ecb3e9ccc9be856b28b29b46f985db8410 GIT binary patch literal 2744 zcmZuz&2tn*6rb+-*x5;TNkRxA3N{MDTDYr}UQ&S)0rdcZssM)(YHKpx*_~u|CUno{ zV`@{ShNGN3ctEQ*NB*VJlb*PE^TNUMy`D`-0<+cMue;xSzxR8+X|3iN@Y(#&=nvO0 z{>H)K&xXNQP}DzAalb(Jl34p}fkyXVxz9>%v(*!N>SGNYr@!nE`VXVq!SSPwWxl z45#=6v>t~suHnx=sn@6U{7F7t&im7P_SBJGGyL?yu2Xt_R_{8)XGgncI9)OtbNQv) z$wnm8q$iS$tp{0Gu^{2>X+H|rSQ2bRogjN9$DU?)KMF z(&}x8aWtyKwATaWRZAor&GqfVYvZ~4%sQ%uI=ZIB04hw7LHm=SC*Cp!jn>9k(BT9O zq51iDyY`S%7vHFTs8_|?DsWpn?D1(BXf-TT)^Ne>CcxDWYA>A`> zX*%wZ%!DzxZI|3Kp1k*%?$8}`U=Hm)L!RSi0*PVA%A8?k0M2`1yZ}cJtQQ8Ow~(VryMqzk;V>+|nOrjQlKmrKCV}Ax^^}R@a*cvj5`sfBp8$ zsQvXdWo)tGju--zm#09Wa8xQY!3#UeL{Alt*n&fqoPsrZ5~_wRPebEll4ygLC4d$J zC_h4>13F912JekhmteWX(wYG*CkP|ffAS)pMnitYU1$>)4d>6$Il_sVcXMKBatour zjQAZopa`=1R7=|fdW<6@>C9dQs4?U3K^P!oxLe91(3!hh9he<5fc%fl;~v01mVbN` zGG|9H9!U`bskhnXIOr!~S16WC!p4}$WPq|KQW%7Seh~I+FO%aG-A)y+>XWr571q*I>6qGX<7ScIE=vEgaV-asN}@O*TBK~tsk555b9 z2^q2QRDXG0rlC-(Fl9e!SQ;@6V&T9oC}eAWk3&}pxsIl*aI(TXN)CF&rN~8()aeAY zvWYi4Dhsl{3ZZ}SAt^BkG>Sv$?2{5R+CeTmPDY}ULo&{B3&?0|R2=RM2(JJcU8Ete zYK$Pu0dXcY!ruOgd<^JY;p4kRQ~F!IT0;s$2CbJFlus^|S@Z;-cr(+O%4e6l=_cmC zgQvALteMxYUAx8*KgFO|SmAb%048*$BhP|u@*GwlK;_fVujZ6}pVKESpY(9sy>*d% zbMMAo+?li1qcnzxO||x?TPdusq(QH>+>c_OPrfCQk1=*P?I%FZc~;mRDMXUjfWZ(& zy>cGl6%ZXIqjhuuu9$b_dDQGftT0G4jU~Q1BQBQ#abbVmukCB2(LWXyvITPAKvDR< zIaYnd^)qh+mwR@5KY&rY-a ztDyfU6l3L7KZ{^9=7ORc%YHiyfMkWmqcAH>mC4aF(~)U^y?kaXqrV1)3&E}bKj@c4 zCmJ2A+Jr-9>5Qe5mAlYOc|R7{@qJcbKxG*i>@Jw*(s4RYJad+KHc$YbBTDMT^(d}Y KN25s!Y3+Z6f{Szj literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/env.cpython-314.pyc b/mycli/commands/__pycache__/env.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..735d24a5c9787670dfe82844bfbaa70324b1868b GIT binary patch literal 5003 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 literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/git.cpython-314.pyc b/mycli/commands/__pycache__/git.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c0b5c165724a6182745d43c0562a7c6dd9d0753 GIT binary patch literal 4820 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 B9z*~D literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/image.cpython-314.pyc b/mycli/commands/__pycache__/image.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..669988139a240e2934f75c0044d5b15dd2c7a819 GIT binary patch literal 6364 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 m=LM}Y_Cm)?d(1^9 zuquVpLufCdy~J$}L23{5R`rxWg9s}jD|$+$s5er$w{K>>#Hoa$j^#IRX5M@An>W9C z`+h7MM$mrOcZ~x9q33kqH?{(U{YM-^D@a8)`W&g;IA?Rv^W*$jzz+0#bAm0vTwq)r zlWb{Bw&i|wOPEUdpv^6 zM*`kF*rl2+JRR&pYFKSiBWiR6&Pm&mmN7LB5ff_bp{1n0^$M$x|ES)M9&whoH=d;( zFi9S|W(nwJwPU1_3AJ;C_f~I7YVv4Yc1-1tqjXm_@oC{2o>m;&C>geat;}SabA_pb zT}<4M*WlPnfRWzOKB~x=2aM`x9SC&6A%Grfwtz($`U5H8Of zWnATlup6xCb1Z5#;+@1>14miMa|NelyFw9{D%H@?kWQ9|AUoh!t|qS*%|&IgSg@5v z(<$l7ELL>0jH#nxm{}C+N*yfb?~*nW!H&$IZkKb-|CD;Z;$Ue5S)LRfIyQ4_$uX z^{Na63|8AOGV0+55!tUB}2RI3Ro+egr?gKST;$EgtE|;JjwuJ$Z{U|;mVbQ zZDUe)gMc8<5Q$(oI5?;%gl3n}#4^zeiuDzU6|^5i?Oki~ZmOqt^+D)i?8n%~;*(T% zZEPo=ym#l@J5S>SkK+T|@xjf(di>}W zdlTPH)VepOHzu}{*?Q#UZX{Xj{H1MU`sd!QRQ5OLY2?HGHq>=;KLDG0Z%5QjrwnYT zcp)zmUNS7(qJ_dRI7t4!NA;{x4sBK2-QO zGE>a)uX9Y5fFpAC)J1wUc&J3T6ay+$6Iqn^@RC+?bRdKRETLu3N@rYA$8dSqM$-W| zrWGo-LvY@7?1}>!f(4tj!_lkq4I1*CGMSbdyeQ!^u*cTH))lP^)LTNoMx+ZwIzSZY zT!~3;P4=+ngU$BE?{R1r>cjbIu zH?@4;4O`A^g_s()EXLeo$(UttfdO}0SV_*%Z-+%FRPUhTJTaXLE5)qp(!9ArD7CDd zyOLR<-vW>5>`e3Zdu*7-}dUfj9B=BGQIg4}og(tS7BO}5BBV7K5zyxm1Tx0yZF19lae zx~-J%#QCk&b-NdNz885sth`1)Zk9+wx9U-=q$asmy~BK7<3a=?OfsU^skuSB4O|yx z&kyjnQiT~(AdG;1HNJuRLE7u~{CeaC9ecbxtZcqggbtEZ@@9EyH{>E*^5a(63VKVc zf!k~IP82RZ=|A>5OYOevdt*A$v(#mcO1GbA@CGy}p$dd*!h~01fmB!UFiiA@=kxIO z8H^pUOYhW0GfXVO4|<;9EKZfn2faGDp&zT|gAz@Q&=s6_9G&k4!M3G=YB~`Xw>iW#ALit@A*+eTioyBNlf(1;7K^C6VGqAf6WJm_u zDFMzmM|41+sY4AQt?p~$+CgSOhm^tJBb`x%IWrN~1M)(7sUo-sI<6vpK#xf?bz}@w z@nvKTO_m+d7YfVC|MZj)oo0VD+nV9f!B>H38}oH6^L(Ks7wON88RHT8OAf^aCPnbjq2J8 za1MnRaquzQe7f~$cY7^1%j~<|jkO(IJbtuO{W>m`8JxDgwzIRb`PKHyQoK-Rcn`qm z9wtKnTMXaIXO(+#t{gt?McrPs4(M5li$BN+P42L4zoB;dfgR^2X28vt?VVS|(cKnz z_hZYpS0R?m_GT$95SR&V`oSK6tJi3FKP9>!9CDFl4!x*V8}qa<5{V8#clQ&@-Butl zpAr*b=ht0cG6fbm#~gzV;3fJsKvQCZ1%S{_Vu2rO=*+|j!Su)+Xvbtkn;N1Qu_h5^0isfI zp$hk!qNyTg;U?k=kT^Sz9tbSkaT*1?GGLJ0tbz8di)$dbS+ZoT#2jv&N3wwACXz)Y zXsWmYq(o&f#4TjpMq(qGmNn_=4}rG~Gem`!G4eE3i_)iyME#qd#by+gBd^hr<$-s? zQ6XZE_W9krab|UE!)7CHNfiY=YQh))R}&!HKZ*3A0e;79 z*Dqu{@{M=-k%<*#Kp^*_Vqk-Wl@7JTWEqxy8=V|PCm&y}2A$_zL^428VDD_Kub&T* zUXea1O!7<$FWcfOm=kBmn`O)r(;cabt@7A2;JQg>CR*&hjb`f7XkG`Emxk0B=hs;4 zyRp54`z{7Jf#B8f;!ogTh*+@9aXJyIV(QF6QY~OQ{j2^(&&0YDo4uuAsQAN<3BItETjSjpan{r}7Uz0r(a=(iW zH!-TMz;n6GcY#op$DcDn8iVLbto!YN^?d#STX}dN2vsb|dP^;;hL$Flo+lK{P)Sx& X`cKhRp(0qf@7&-6`ox#f3+f4mMBu9e%7!ei%Ch@idq}0WyH}>B&(L$wamD#99H74#H}eV zJ-dvoy~K43M9Coz+Cxp$-#%B$fanGOvT2`LM;rqXxf`x6$p?^`)0XI z%Ca2QP0@igGn{#U^Y(r3%{=FC_9195h}*K>gwO->!*1pd{Mh{sjnFM5APv2O1ZtYn zC>ZH!ddi_WdTVQp#=x3m+BxOYTvKk%-HWa<3C}Z1>xaOE>T}I|&6@}|sO*{G z9Hwe>kk)RmHpZh!a0y+4TkxE6)ak;FeR~FUUcm=4I_;IlcofBv(0!4zH3>}uD>R>S z)+m7IV0nHa0D3v07m7>f4j{>1nsU(n>NtjUrZpN&!mlUUF)QQBzEEab$j zl#(!3uxd2nLN1k7R#sp&-G~x|NFYLxF}p69033*5TJI!+fmY5JvJ0B5m0p#yQHQ~3YkA3Vi_+3UA*&foM#|=iAQ7*{z)c`1 zr-v?OlvRE;v!L;-N+B!qixMv?ISFiV%W7&VyReK~z{<1*P?#dr4F9Gt{|sKeg_4Lv zlYd1h@0hpXj=;ZmxFz;-n(PQA9riA8{th^qB$Kd!Xpxvhf;ebjvx7sRPp={*g7hvl z(+4t{1o82{Ce$_ModP2`A>lx}5xhx?MMJdU6Pl7vGMgvg#%xK(wk__>yOQ*AYhCOm zn&w&SCrbG3S!0Am#U>dR3C*U|x};t-Q$UCF`U&q4ZR_tv^ThRLOS&Loai<`alh%up z&O~c%zPE4K4!nn1+Ol8U%E!x7sKi0gSZj&)6}>6R?;e$`@2Qx6ChR6jg&r zl=E8LpyFRJafoz15Vr$b63K!=FROY>OdX#{PLEBD!pbU^H3=e7oV}PhclpwU?v9Dy zzC1lKM&^Bt!157(?mfzIrZZCdy6)lmaftX4em3gE#K8u$ zoK+S9V}+$9`KIB_DytGUnpR~kld{$vs)m6-E2pm;jFiqOm_)DP#!^1Jkd~ruOybFK zTaW}0;fjvF_ducWqqyx%VCa&-}&JPr`%iQ`JD|&TDVHb}!KPaiDK2&|m7W1WtU)h3^Nu z?**eD2czX@zqu7Wvp#vBYpZg{s$8VXb&$U!M~lac9cAvhD)(&lNN>@#akI<~Y;&z| zPyM8Mr`6Z$UmyECjJVJ?7yPO3$G&oRtduET-PG<*-aTE8j90h`Q1chI<<9vx&u?@W zZx+8_4xOy9L)&a&e&EWH29!n+6X z?$rZh;Q0pDM@ao$%PF^NC)0Q z7*Z(0Jn0Bra1V4*#)Jw!Mi%-=_Y8C@*(Wflfnb2FlWv6U!KXt20`@6E@L8bUir$U+ z(m<)R92u@~U*8k9oyDo*NI4X*u!AIQJAOG<^1OTDgXcGIzCT(HpZh3ykG;6t`be1S zZcT}4^3sx7SEZacr*y=go#&5O0wD2>T|`b&!GTaB>(?SEZNfARk+rjHfI zdblU*g$K@SH3)nt@lY6@j}^x6LiiuX>pGaX5#*$(C#gNV|!-ZrM>UX`$B;z3jRx#`Kty@+te&95J@i;hV=!s8GeTxTt23nJ;7 zDBTmYVgiE3E>ng>m+Rp19tfUp5P2YiTvoBMr0#pvNe|~bKk4BM?QBtn*F=MM*#Tu ze?fyBLM~&5Q(9D%>yV4EoP#0@H5TeIxmibB40kFeD(O_p@TrBxJXX@=!cD4iCMz%2 z{7tfx(PZA_UlDC$$x_5ii~356dZ_A@lJ<3hCi z^~mkW54#`IH06Z~Ne6bwY{!Ra=j)LlMAolvQQ<1(dcEg%&)e<|TpF)XC&}-w+g(*A zP-Q$-um4Whn_Yise__jeuFAGmy?y^|a?$jTp9lxC_3InMrMB|$g$gxWrTh=+2o*Oq zk?tb38vtT59U@)FPCIPxoUzp>x2U!!sDDf@OC_kif^C_>9S_;s>9K`=XNwAbnb6?o dwF)(6o$@g?9Od7qW!zM2+8_Rh5BAN^@oyGZWpn@l literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/self_mgmt.cpython-310.pyc b/mycli/commands/__pycache__/self_mgmt.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfe354ecc65f6dcabd257454bf6089e358eb51cb GIT binary patch literal 1972 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 O&R)9y?t{HB4EPTu|LVE` literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/utils.cpython-310.pyc b/mycli/commands/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3139527208da2401f840c36fac7c5f98e97431c4 GIT binary patch literal 970 zcmZ`%&2G~`5Z+y{V<&AwTS3dw#|Dw)h=eKxwAWUMQXxe`#_?`z)3w*^+LT!7sa$!2 zs#0>~L3oK3Hx8(;z=>It(q0&AXJ=-|^YhKfZnuMgez70vcY@GQtK20Il;?2tdteNW z5yOlKoRKlXR(6ClcHpLhX71RuxS(Gz9yoSW6j}3hlmB|4J-307{hg>PCbZnpR z5(Z@#j;6pEhNz@|xS#5n3lZg9RywGf94XhsgKtP#zgzZuXLO{w(j!qPI+4|APsUZo z^HPuYr!UidlucuiF8NK7BO6fYxN$zrMjPDCBmf~)RDViM)kK`eNbS4G_(449e@ zRF&6ckiV(74ikA6oF!2ioXJYCV8R0?bKWP$OLcfGq7y?CE{fQ;cS8;cE}W*_V-OfV z$0cIq0?%PI3%rNkJvb*bq&7=3cNm?KOLRpR7^9Ev8JW`s!f1w>`vje1upqVF__eGD zYagOU^Je%GG5-qU{k8Tx_2XUJNKh5Qi>9%lg9smFU literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/utils.cpython-314.pyc b/mycli/commands/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b0e84e5202501d51c17c675ea13975adb30f494 GIT binary patch literal 1446 zcma)5-EZ4e6hGG=aX#H-tCXR(i>pCLSd&OoB@mRM!G_jW?SjX$J#33y`>ru{Y|9@j zF)v{}Y*e5Y@jxDV9RCIq?~y=NXH{a-Hu2ih2VS|?wVP21!IgaOIrn_~cg_W|kOgx7 z(Dw9S7=Y(gNKQIt4u2+0A7t?09>{0`U1L0^LV0F^^;mMV3*0r{<128NZRQjA(LD`w zu^@BhW8M?)3eD2UD&b^)4ju6%+NtDqbWekvk_9;}XXNbLTx`k9xl!aKTXK6@nK=j5 zd{DaS>4xt0B-QG66;pHTRTOef#p|pPrqCfz25w`=u^d+&WdxuFu>jm*iZJ9?AQFxV z&MfP$iVZ_CvE{px9|hO3TVMGJ9G+Mn(iq`<vk{XBhH~=rR z$2S9}W_}u!Z*;5;X``cf(uU<5nzV`~%`$P72?gD4-#3)Ckn3Q>R^#7Jf&$R;qmJJp zuYCfxjqV)lWt2eFBsM3H%p}?(eEb1`&9vZrY@|&BOv0TQHh?B=;lv>Tvn?hM7mwpq zU}*-R#WXV~Oo^C)T!9w4hh_^S3mYw62FSo!cqRX9|0_S2bWj0VIQQT6@-37nV|kJN zr(ZQ4%zi{+k$hXa9Kj@cxNBRE;`F5Zx`AEkO}nFz3HrSKR<$0YdXPDm1MyabmxH_3 z1N^QOl&%mjf-4yg0vhdH7O|nOM;au?~u)gkXtjX)sQE%rmlw^Ry$Uh@=ZfG z*Q;zOkdbMV&_r+frWbNiaTMGcNpk4Sh4GxV<2iH2$zzdxu1!cE_OnnB`&ajhYDPDNY{hm9umoIHCZY~baKH+|RFf7-1@{MQt;?~mFOW!Q-=1be3e7m@L z^&khO*}p_ckL{%kTiMO*@bt`1`uvL{Vf*#~4{i-#e{Xntekc8YH2T$CD74#}rMBCl zV3RF2^wp?WN2CSkqQ$Bo?GJ4wN!2%wKU(r zmz)XWPv_tLjF5d6A@mf=FX7B{$iIX)_EW$Jk0u^Yd^L5zFer1#0yDNxIr3YGX6nnS w;aKHYB<-TqqsfPpkJkrtPsWFH%fF$OT_heb1#~I$q2xNEbo$Q}d5miL8v-&jivR!s literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/video.cpython-310.pyc b/mycli/commands/__pycache__/video.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cf8c9009f97841e94f026cf6b59b727795c233c GIT binary patch literal 1171 zcmZ8g&1>976xV#{V?VtKX;OSBI+gBWccGXbObBgb0zJe8(xxCNLe|W#6iXU;X5@{9 zd~&b8^cn)Y$Nn4nGlt%J%DK=(4^5x6HZgr--snBvoA>p5Vl?VoK=0Wf>0iXM{%nQ2 zPiI=p^76pg%QWNa!$+VWp*p*W`%DxZT-{WK%Pd+Mn((ZQ+km&!7Bz&^_2M`9 zX{;mbm!nIpp(|HtgBrX=pgFuG4S9_>1SX14iTvzr(2xyfWQ#6ogXCA*-Z+dlM5sbc>L6Ht6)u|Ml8sF7ve4kSnx90vm!vuh7Z^ER3&l>wAXT9!zFZfBCeViT1yH z^@HEF#n-}r{^G!|R9dY3G zA2>T_=pnex;=}X6&C-)MUw?DZiBUkJD1HVov*U!vr^nNtY#V6kDibHhQPpypzA<(t z)?AvwI@L*ds{;VUr-t%45wgXZAxg`sE!(v+E(|Y(;TgA#wC1u1Jjgt3t22(MbXn<7 zXp>Is(xM~OB81xRfvmm&<~jG#7-7^$6hDOfA;xGQZtq|GH~lF0!DKo%ZWuBVhoR}K z>ZFt+=1O&e6R5STM?PpH3_8@#+a*(yfM%Dsy^^9TJBS+h2F%6-Ja&kObW08E(gnMd nbGI;E;@;z2uqxwE+O<=k1EZFQhseVd?2tb4>@k`^(htyom^e9K literal 0 HcmV?d00001 diff --git a/mycli/commands/__pycache__/video.cpython-314.pyc b/mycli/commands/__pycache__/video.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45e2fd7af9935c34cc17cf4b57009f1190db6b56 GIT binary patch literal 1805 zcmah}O>7%Q6rTOJ9mjS=oF9@jTM-2evfW0et*TN9T182!25U=A1xZ)VdSkC*d(G@@ z+*n9W%b|fwD=q{fL8NjpCvwb-b%W47mb{}e3021_9)WZSJhsh4G`y|DZy zYy5>eLIvMYSQS$;)aZ-1Y{R7gZ{^i~&7hmNYIdQl)Io-5EQV@qY7E$mfl5cwUlGGL41l zL=dxZsaP!Ab5wTR`FZOOmCAPA#I&Psk&^Dq0RP4U6-}dLV+IB)I0Ppo%-Td1fUOR& zrAjSNh>5E-0Fmn~J1W~Qsky|FKyh?bRaG2dChSS*#MU7c94-S0;9%y$|nxdiQB`~X7-Hx7l7VF!I4?T@1p2QQ) z(XIH|mGS4%p50jdi_qtxRlRwuIkjHcIJfcI=D?Nh*i}&fJ>2zh>a(fU!L`!b2Tig* zzJ6h|_tJLwt=(|%PFMeG=G&LnZap4amp6hxp8Ms^ZSCe}Ze~lH-HhGd=^6M-5;`M? zanyeX=6ZL(_rU-72Ua3N6w7?T6UO(7mCRkGHtN9Zr65 z>i(%m$|`PN-R91+|H1o%D+{ZcX7^_1`ZhPc!$l7G7#HzeF!C {output}") + continue + + click.echo(f"Converting: {bib_file} -> {output}") + content = bib_file.read_text() + md_content = f"# Bibliography\n\n```bibtex\n{content}\n```\n" + output.write_text(md_content) + + +@bib.command("cv-update") +@handle_errors +def cv_update(): + """Update CV bibliography using biber.""" + cv_path = config.get_path("cv") + if not cv_path: + # Fallback to hardcoded path + cv_path = Path.home() / "Documents/Personal/resume_cv/cv_lai_maintaining_latex/cv_lai_maintaining" + + if not cv_path.exists(): + click.echo(f"CV file not found: {cv_path}") + return + + click.echo(f"Updating CV bibliography: {cv_path}") + run_command(["biber", str(cv_path)], check=False) diff --git a/mycli/commands/config_cmd.py b/mycli/commands/config_cmd.py new file mode 100644 index 0000000..adb3a7f --- /dev/null +++ b/mycli/commands/config_cmd.py @@ -0,0 +1,58 @@ +"""Configuration management commands.""" + +import json +import os + +import click + +from mycli.config import config + + +@click.group(name="config") +def config_cmd(): + """Manage mycli configuration.""" + pass + + +@config_cmd.command("show") +def show(): + """Show current configuration.""" + config.show() + + +@config_cmd.command("get") +@click.argument("key") +def get_value(key): + """Get a configuration value by key (dot notation).""" + value = config.get(key) + if value is not None: + if isinstance(value, dict): + print(json.dumps(value, indent=2, ensure_ascii=False)) + else: + print(value) + else: + click.echo(f"Key not found: {key}", err=True) + raise click.Exit(1) + + +@config_cmd.command("paths") +def list_paths(): + """List all configured paths.""" + paths = config.get_all_paths() + max_len = max(len(name) for name in paths.keys()) + from pathlib import Path + for name, path in sorted(paths.items()): + exists = "✓" if path.exists() else "✗" + click.echo(f"{name:{max_len}} [{exists}] {path}") + + +@config_cmd.command("edit") +def edit(): + """Open configuration file in editor.""" + config.edit() + + +@config_cmd.command("where") +def where(): + """Show configuration file location.""" + click.echo(config._config_path) diff --git a/mycli/commands/env.py b/mycli/commands/env.py new file mode 100644 index 0000000..2d67add --- /dev/null +++ b/mycli/commands/env.py @@ -0,0 +1,85 @@ +"""Environment paths and quick navigation.""" + +import functools +import os +import subprocess +from pathlib import Path + +import click + +from mycli.config import config +from mycli.utils import handle_errors + + +@functools.lru_cache(maxsize=1) +def _get_paths() -> dict[str, Path]: + """Lazy load paths from config.""" + return config.get_all_paths() + + +@click.group(name="env") +def env_cmd(): + """Environment paths and navigation.""" + pass + + +@env_cmd.command("goto") +@click.argument("name") +@handle_errors +def goto(name): + """Print path for cd (use: cd $(mycli env goto )).""" + paths = _get_paths() + if name not in paths: + click.echo(f"Unknown path: {name}", err=True) + click.echo(f"Available: {', '.join(paths.keys())}", err=True) + raise click.Exit(1) + click.echo(paths[name]) + + +@env_cmd.command("list") +@handle_errors +def list_paths(): + """List all configured paths.""" + paths = _get_paths() + max_len = max(len(k) for k in paths.keys()) + for name, path in sorted(paths.items()): + exists = "✓" if path.exists() else "✗" + click.echo(f"{name:{max_len}} {exists} {path}") + + +@env_cmd.command("cd") +@click.argument("name") +@handle_errors +def cd_path(name): + """Change directory (launches new shell).""" + paths = _get_paths() + if name not in paths: + click.echo(f"Unknown path: {name}", err=True) + raise click.Exit(1) + + target = paths[name] + if not target.exists(): + click.echo(f"Path does not exist: {target}", err=True) + raise click.Exit(1) + + shell = os.environ.get("SHELL", "/bin/zsh") + click.echo(f"Starting shell in: {target}") + subprocess.run([shell], cwd=target) + + +@env_cmd.command("status") +@handle_errors +def status(): + """Show environment status.""" + paths = _get_paths() + click.secho("=== Paths ===", fg="cyan") + for name, path in sorted(paths.items()): + exists = "✓" if path.exists() else "✗" + click.echo(f" {name:12} [{exists}] {path}") + + click.secho("\n=== OpenFOAM ===", fg="cyan") + openfoam_path = "/Volumes/OpenFOAM/openfoam/build" + if Path("/Volumes/OpenFOAM").exists(): + click.secho(f" Mounted: {openfoam_path}", fg="green") + else: + click.secho(" Not mounted", fg="red") diff --git a/mycli/commands/git.py b/mycli/commands/git.py new file mode 100644 index 0000000..fd3bd8b --- /dev/null +++ b/mycli/commands/git.py @@ -0,0 +1,98 @@ +"""Git utilities including proxy management.""" + +import subprocess + +import click + +from mycli.config import config + + +def _get_proxies(): + """Get git proxies from config.""" + return config.get_all_git_proxies() + + +@click.group(name="git") +def git_cmd(): + """Git utilities.""" + pass + + +@git_cmd.command("proxy") +@click.argument("name", required=False) +@click.option("--unset", "-u", is_flag=True, help="Unset proxy") +@click.option("--status", "-s", is_flag=True, help="Show current proxy status") +def proxy_cmd(name, unset, status): + """Manage git proxy settings. + + Examples: + mycli git proxy fastgithub # Set fastgithub proxy + mycli git proxy pandafan # Set pandafan proxy + mycli git proxy -u # Unset proxy + mycli git proxy -s # Show status + """ + if status or (not name and not unset): + _show_status() + return + + if unset: + subprocess.run(["git", "config", "--global", "--unset", "http.proxy"], capture_output=True) + subprocess.run(["git", "config", "--global", "--unset", "https.proxy"], capture_output=True) + click.secho("Git proxy unset", fg="green") + return + + proxies = _get_proxies() + if name not in proxies: + click.echo(f"Unknown proxy: {name}", err=True) + click.echo(f"Available: {', '.join(proxies.keys())}", err=True) + raise click.Exit(1) + + proxy_url = proxies[name] + subprocess.run(["git", "config", "--global", "http.proxy", proxy_url], check=True) + subprocess.run(["git", "config", "--global", "https.proxy", proxy_url], check=True) + click.secho(f"Git proxy set to {name}: {proxy_url}", fg="green") + + +def _show_status(): + """Show current git proxy status.""" + result = subprocess.run( + ["git", "config", "--global", "http.proxy"], + capture_output=True, + text=True, + ) + http_proxy = result.stdout.strip() if result.returncode == 0 else "(not set)" + + result = subprocess.run( + ["git", "config", "--global", "https.proxy"], + capture_output=True, + text=True, + ) + https_proxy = result.stdout.strip() if result.returncode == 0 else "(not set)" + + # 检查当前设置的代理名称 + proxies = _get_proxies() + current_name = None + for proxy_name, url in proxies.items(): + if http_proxy == url: + current_name = proxy_name + break + + click.secho("Git proxy status:", fg="cyan") + if current_name: + click.echo(f" Active: {current_name}") + click.echo(f" http.proxy: {http_proxy}") + click.echo(f" https.proxy: {https_proxy}") + + click.secho("\nAvailable proxies:", fg="cyan") + for proxy_name, url in proxies.items(): + marker = "*" if proxy_name == current_name else " " + click.echo(f" [{marker}] {proxy_name:12} {url}") + + +@git_cmd.command("proxy-list") +def proxy_list(): + """List available git proxies.""" + proxies = _get_proxies() + click.secho("Available proxies:", fg="cyan") + for name, url in proxies.items(): + click.echo(f" {name:12} {url}") diff --git a/mycli/commands/image.py b/mycli/commands/image.py new file mode 100644 index 0000000..2b5ac30 --- /dev/null +++ b/mycli/commands/image.py @@ -0,0 +1,131 @@ +"""Image utilities.""" + +import subprocess +from pathlib import Path + +import click + +from mycli.utils import handle_errors, run_command + + +@click.group() +def image(): + """Image manipulation commands.""" + pass + + +@image.command("eps-to-pdf") +@click.argument("files", nargs=-1, required=True) +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def eps_to_pdf(files, dry_run): + """Convert EPS files to PDF.""" + for pattern in files: + for eps_file in Path(".").glob(pattern): + if eps_file.suffix.lower() not in (".eps", ".ps"): + continue + output = eps_file.with_suffix(".pdf") + + if dry_run: + click.echo(f"Would convert: {eps_file} -> {output}") + continue + + click.echo(f"Converting: {eps_file} -> {output}") + run_command(["epstopdf", str(eps_file)], check=False) + + +@image.command("eps-fix") +@click.argument("file", type=click.Path(exists=True)) +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def eps_fix(file, dry_run): + """Fix EPS files created by print command.""" + path = Path(file) + content = path.read_text() + + if "/f/fill" not in content: + click.echo("File already fixed or not created by print command") + return + + if dry_run: + click.echo(f"Would fix: {file}") + return + + lines = content.split("\n") + new_lines = [] + for line in lines: + if "/f/fill" in line: + line = line.replace("/f/fill", "") + new_lines.append(line) + + path.write_text("\n".join(new_lines)) + click.echo(f"Fixed: {file}") + + +@image.command("jpg-to-pdf") +@click.argument("files", nargs=-1, required=True) +@click.option("--output", "-o", help="Output PDF file") +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def jpg_to_pdf(files, output, dry_run): + """Convert JPG files to PDF.""" + images = [] + for pattern in files: + for jpg_file in Path(".").glob(pattern): + if jpg_file.suffix.lower() in (".jpg", ".jpeg"): + images.append(str(jpg_file)) + + if not images: + click.echo("No JPG files found") + return + + output = output or "output.pdf" + + if dry_run: + click.echo(f"Would convert {len(images)} images to {output}") + return + + click.echo(f"Converting {len(images)} images to {output}") + run_command(["convert"] + images + [output], check=False) + + +@image.command("tiff-to-pdf") +@click.argument("files", nargs=-1, required=True) +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def tiff_to_pdf(files, dry_run): + """Convert TIFF files to PDF.""" + for pattern in files: + for tiff_file in Path(".").glob(pattern): + if tiff_file.suffix.lower() not in (".tif", ".tiff"): + continue + output = tiff_file.with_suffix(".pdf") + + if dry_run: + click.echo(f"Would convert: {tiff_file} -> {output}") + continue + + click.echo(f"Converting: {tiff_file} -> {output}") + run_command(["convert", str(tiff_file), str(output)], check=False) + + +@image.command("tiff-compress") +@click.argument("files", nargs=-1, required=True) +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def tiff_compress(files, dry_run): + """Compress TIFF files.""" + for pattern in files: + for tiff_file in Path(".").glob(pattern): + if tiff_file.suffix.lower() not in (".tif", ".tiff"): + continue + + if dry_run: + click.echo(f"Would compress: {tiff_file}") + continue + + click.echo(f"Compressing: {tiff_file}") + run_command( + ["convert", str(tiff_file), "-compress", "zip", str(tiff_file)], + check=False, + ) diff --git a/mycli/commands/latex.py b/mycli/commands/latex.py new file mode 100644 index 0000000..e2cec03 --- /dev/null +++ b/mycli/commands/latex.py @@ -0,0 +1,49 @@ +"""LaTeX utilities.""" + +import subprocess +from pathlib import Path + +import click + +from mycli.utils import handle_errors, run_command + + +@click.group() +def latex(): + """LaTeX compilation and utilities.""" + pass + + +@latex.command("compile") +@click.argument("files", nargs=-1, required=True) +@click.option("--engine", "-e", default="pdflatex", help="LaTeX engine") +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def compile(files, engine, dry_run): + """Compile LaTeX files.""" + for pattern in files: + for tex_file in Path(".").glob(pattern): + if tex_file.suffix.lower() != ".tex": + continue + + if dry_run: + click.echo(f"Would compile: {tex_file}") + continue + + click.echo(f"Compiling: {tex_file}") + run_command([engine, str(tex_file)], check=False) + + +@latex.command("count") +@click.argument("file", type=click.Path(exists=True)) +@handle_errors +def count(file): + """Count words in a LaTeX file (excluding commands).""" + result = run_command( + ["detex", file], + capture_output=True, + text=True, + check=False, + ) + words = len(result.stdout.split()) + click.echo(f"Words: {words}") diff --git a/mycli/commands/pdf.py b/mycli/commands/pdf.py new file mode 100644 index 0000000..99e14be --- /dev/null +++ b/mycli/commands/pdf.py @@ -0,0 +1,98 @@ +"""PDF utilities.""" + +import subprocess +from pathlib import Path + +import click + +from mycli.utils import handle_errors, run_command + + +@click.group() +def pdf(): + """PDF manipulation commands.""" + pass + + +@pdf.command("compress") +@click.argument("files", nargs=-1, required=True) +@click.option( + "--quality", + "-q", + type=click.Choice(["screen", "ebook", "printer", "prepress"]), + default="screen", + help="Compression quality level", +) +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def compress(files, quality, dry_run): + """Compress PDF files using ghostscript.""" + for pattern in files: + for pdf_file in Path(".").glob(pattern): + if pdf_file.suffix.lower() != ".pdf": + continue + + output = pdf_file.with_suffix(".compressed.pdf") + cmd = [ + "gs", + "-sDEVICE=pdfwrite", + "-dNOPAUSE", + "-dQUIET", + "-dBATCH", + f"-dPDFSETTINGS=/{quality}", + "-dCompatibilityLevel=1.4", + f"-sOutputFile={output}", + str(pdf_file), + ] + + if dry_run: + click.echo(f"Would compress: {pdf_file} -> {output}") + continue + + click.echo(f"Compressing: {pdf_file}") + run_command(cmd, check=True) + output.replace(pdf_file) + click.echo(f" Done: {pdf_file}") + + +@pdf.command("crop") +@click.argument("files", nargs=-1, required=True) +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def crop(files, dry_run): + """Crop PDF files.""" + for pattern in files: + for pdf_file in Path(".").glob(pattern): + if pdf_file.suffix.lower() != ".pdf": + continue + + if dry_run: + click.echo(f"Would crop: {pdf_file}") + continue + + click.echo(f"Cropping: {pdf_file}") + run_command(["pdfcrop", str(pdf_file), str(pdf_file)], check=False) + + +@pdf.command("to-tiff") +@click.argument("files", nargs=-1, required=True) +@click.option("--density", "-d", default="300", help="DPI density") +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def to_tiff(files, density, dry_run): + """Convert PDF to TIFF.""" + for pattern in files: + for pdf_file in Path(".").glob(pattern): + if pdf_file.suffix.lower() != ".pdf": + continue + output = pdf_file.with_suffix(".tiff") + + if dry_run: + click.echo(f"Would convert: {pdf_file} -> {output}") + continue + + click.echo(f"Converting: {pdf_file} -> {output}") + run_command( + ["convert", "-density", density, str(pdf_file), "-compress", "zip", str(output)], + check=False, + ) diff --git a/mycli/commands/self_mgmt.py b/mycli/commands/self_mgmt.py new file mode 100644 index 0000000..a42c8e4 --- /dev/null +++ b/mycli/commands/self_mgmt.py @@ -0,0 +1,86 @@ +"""Self-management commands for mycli.""" + +import os +import subprocess +import sys +from pathlib import Path + +import click + + +def _get_project_root() -> Path: + """Get mycli project root.""" + # This file is at mycli/commands/self.py + return Path(__file__).parent.parent.parent + + +@click.group(name="self") +def self_cmd(): + """Manage mycli itself.""" + pass + + +@self_cmd.command("update") +def update(): + """Update mycli from the local repository.""" + root = _get_project_root() + + click.secho("Updating mycli...", fg="cyan") + + # Run make install + result = subprocess.run( + ["make", "install"], + cwd=str(root), + capture_output=True, + text=True, + ) + + if result.returncode == 0: + click.secho("mycli updated successfully.", fg="green") + else: + click.secho("Update failed:", fg="red", err=True) + click.echo(result.stderr, err=True) + raise click.Exit(1) + + +@self_cmd.command("uninstall") +def uninstall(): + """Uninstall mycli.""" + root = _get_project_root() + + click.secho("Uninstalling mycli...", fg="cyan") + + result = subprocess.run( + ["make", "uninstall"], + cwd=str(root), + capture_output=True, + text=True, + ) + + if result.returncode == 0: + click.secho("mycli uninstalled.", fg="green") + else: + click.secho("Uninstall failed:", fg="red", err=True) + click.echo(result.stderr, err=True) + raise click.Exit(1) + + +@self_cmd.command("info") +def info(): + """Show mycli installation info.""" + root = _get_project_root() + + click.secho("MyCLI Info:", fg="cyan") + click.echo(f" Project root: {root}") + click.echo(f" Commands dir: {Path(__file__).parent}") + + # Check if in PATH + mycli_path = subprocess.run( + ["which", "mycli"], + capture_output=True, + text=True, + ) + if mycli_path.returncode == 0: + click.echo(f" Executable: {mycli_path.stdout.strip()}") + else: + click.secho(" Executable: not in PATH", fg="yellow") diff --git a/mycli/commands/ssh.py b/mycli/commands/ssh.py new file mode 100644 index 0000000..8dd2378 --- /dev/null +++ b/mycli/commands/ssh.py @@ -0,0 +1,100 @@ +"""SSH connection utilities.""" + +import subprocess +from pathlib import Path + +import click + +from mycli.config import config +from mycli.utils import handle_errors + + +def _get_hosts() -> dict: + """Get SSH hosts from config.""" + return config.get("ssh_hosts", {}) + + +def _build_ssh_base_cmd(host_config: dict) -> list[str]: + """Build base SSH command with host and port.""" + cmd = ["ssh", "-p", str(host_config["port"])] + + if "key" in host_config: + key_path = Path(host_config["key"]).expanduser() + cmd.extend(["-i", str(key_path)]) + + cmd.append(host_config["host"]) + return cmd + + +@click.group(name="ssh") +def ssh_cmd(): + """SSH connection utilities.""" + pass + + +@ssh_cmd.command("connect") +@click.argument("name") +@handle_errors +def connect(name): + """Connect to a configured host.""" + hosts = _get_hosts() + if name not in hosts: + click.echo(f"Unknown host: {name}", err=True) + click.echo(f"Available: {', '.join(hosts.keys())}", err=True) + raise click.Exit(1) + + cmd = _build_ssh_base_cmd(hosts[name]) + click.secho(f"Connecting to {name}...", fg="cyan") + subprocess.run(cmd) + + +@ssh_cmd.command("tunnel") +@click.argument("name") +@click.option("--local-port", "-l", default=11111, help="Local port") +@click.option("--remote-port", "-r", default=11111, help="Remote port") +@handle_errors +def tunnel(name, local_port, remote_port): + """Create SSH tunnel with port forwarding.""" + hosts = _get_hosts() + if name not in hosts: + click.echo(f"Unknown host: {name}", err=True) + raise click.Exit(1) + + host_config = hosts[name] + cmd = ["ssh", "-p", str(host_config["port"]), "-L", f"{local_port}:localhost:{remote_port}"] + + if "key" in host_config: + key_path = Path(host_config["key"]).expanduser() + cmd.extend(["-i", str(key_path)]) + + cmd.append(host_config["host"]) + + click.secho(f"Creating tunnel {local_port} -> {remote_port} on {name}...", fg="cyan") + click.secho(f"Command: {' '.join(cmd)}", fg="dim") + subprocess.run(cmd) + + +@ssh_cmd.command("list") +def list_hosts(): + """List configured SSH hosts.""" + hosts = _get_hosts() + click.secho("Configured hosts:", fg="cyan") + for name, host_config in sorted(hosts.items()): + click.echo(f" {name:12} {host_config['host']}:{host_config['port']}") + + +@ssh_cmd.command("cmd") +@click.argument("name") +@click.argument("command") +@handle_errors +def run_cmd(name, command): + """Run a command on remote host.""" + hosts = _get_hosts() + if name not in hosts: + click.echo(f"Unknown host: {name}", err=True) + raise click.Exit(1) + + cmd = _build_ssh_base_cmd(hosts[name]) + cmd.append(command) + + subprocess.run(cmd) diff --git a/mycli/commands/utils.py b/mycli/commands/utils.py new file mode 100644 index 0000000..f3e5bf6 --- /dev/null +++ b/mycli/commands/utils.py @@ -0,0 +1,32 @@ +"""Utility commands.""" + +from pathlib import Path + +import click + +from mycli.utils import handle_errors + + +@click.group() +def utils(): + """Miscellaneous utilities.""" + pass + + +@utils.command("albany-cleanup") +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def albany_cleanup(dry_run): + """Clean up Albany temporary files (phalanx_*).""" + count = 0 + for file in Path(".").glob("phalanx_*"): + if dry_run: + click.echo(f"Would remove: {file}") + else: + file.unlink() + count += 1 + + if dry_run: + click.echo("Dry run completed") + else: + click.echo(f"Removed {count} Albany temporary files") diff --git a/mycli/commands/video.py b/mycli/commands/video.py new file mode 100644 index 0000000..e9f8450 --- /dev/null +++ b/mycli/commands/video.py @@ -0,0 +1,37 @@ +"""Video utilities.""" + +import subprocess +from pathlib import Path + +import click + +from mycli.utils import handle_errors, run_command + + +@click.group() +def video(): + """Video conversion commands.""" + pass + + +@video.command("avi-to-mp4") +@click.argument("files", nargs=-1, required=True) +@click.option("--dry-run", "-n", is_flag=True, help="Show what would be done") +@handle_errors +def avi_to_mp4(files, dry_run): + """Convert AVI files to MP4 using ffmpeg.""" + for pattern in files: + for avi_file in Path(".").glob(pattern): + if avi_file.suffix.lower() != ".avi": + continue + output = avi_file.with_suffix(".mp4") + + if dry_run: + click.echo(f"Would convert: {avi_file} -> {output}") + continue + + click.echo(f"Converting: {avi_file} -> {output}") + run_command( + ["ffmpeg", "-i", str(avi_file), "-c:v", "libx264", "-c:a", "aac", str(output)], + check=False, + ) diff --git a/mycli/config.json b/mycli/config.json new file mode 100644 index 0000000..3cdff66 --- /dev/null +++ b/mycli/config.json @@ -0,0 +1,34 @@ +{ + "paths": { + "study": "~/Documents/myStudy/self_learning", + "research": "~/Documents/myResearch", + "apaam": "~/Documents/myResearch/myProjects/apaam", + "tianhe": "~/Documents/myWork/sysu/0_中山大学/hpc_tianhe", + "academia": "~/Library/Mobile Documents/iCloud~md~obsidian/Documents/myacademia", + "webpage": "~/Documents/myWork/sysu/4_成果归档/mywebpage", + "slides": "~/Documents/myWork/sysu/4_成果归档/myslides", + "myclaude": "~/Documents/myResearch/myProjects/apaam/repo/myclaude", + "myagent": "~/Documents/myResearch/myProjects/apaam/repo/myagent" + }, + "ssh_hosts": { + "tianhe": { + "host": "sysu_lchuangxy_1@172.16.31.31", + "port": 6666, + "key": "~/Documents/myWork/sysu/0_中山大学/hpc_tianhe/sysu_lchuangxy_1.id" + }, + "starlight": { + "host": "sysu_lchuang_1@proxy.nscc-gz.cn", + "port": 23 + } + }, + "git_proxies": { + "fastgithub": "http://127.0.0.1:38457", + "pandafan": "http://127.0.0.1:10080" + }, + "openfoam": { + "dmg_path": "~/Documents/myResearch/myProjects/apaam/repo/OpenFOAM.dmg", + "mount_point": "/Volumes/OpenFOAM", + "build_path": "/Volumes/OpenFOAM/openfoam/build" + }, + "cv": "~/Documents/Personal/resume_cv/cv_lai_maintaining_latex/cv_lai_maintaining" +} diff --git a/mycli/config.py b/mycli/config.py new file mode 100644 index 0000000..793a2b3 --- /dev/null +++ b/mycli/config.py @@ -0,0 +1,77 @@ +"""Configuration management for mycli.""" + +import json +import os +from pathlib import Path +from typing import Any + + +class Config: + """Configuration manager.""" + + def __init__(self): + self._data: dict = {} + self._config_path = Path(__file__).parent / "config.json" + self._load() + + def _load(self): + """Load configuration from JSON file.""" + if self._config_path.exists(): + with open(self._config_path) as f: + self._data = json.load(f) + else: + self._data = {} + + def get(self, key: str, default: Any = None) -> Any: + """Get configuration value by dot notation key. + + Examples: + config.get("paths.research") + config.get("ssh_hosts.tianhe.port") + """ + keys = key.split(".") + value = self._data + for k in keys: + if isinstance(value, dict) and k in value: + value = value[k] + else: + return default + return value + + def get_path(self, name: str) -> Path | None: + """Get a path and expand ~ to home directory.""" + path_str = self.get(f"paths.{name}") + if path_str: + return Path(path_str).expanduser() + return None + + def get_all_paths(self) -> dict[str, Path]: + """Get all paths as Path objects.""" + paths = self.get("paths", {}) + return {name: Path(path).expanduser() for name, path in paths.items()} + + def get_ssh_host(self, name: str) -> dict | None: + """Get SSH host configuration.""" + return self.get(f"ssh_hosts.{name}") + + def get_git_proxy(self, name: str) -> str | None: + """Get Git proxy URL.""" + return self.get(f"git_proxies.{name}") + + def get_all_git_proxies(self) -> dict[str, str]: + """Get all Git proxy configurations.""" + return self.get("git_proxies", {}) + + def edit(self): + """Open config file in default editor.""" + editor = os.environ.get("EDITOR", "vim") + os.system(f"{editor} {self._config_path}") + + def show(self): + """Display configuration.""" + import json + print(json.dumps(self._data, indent=2, ensure_ascii=False)) + + +# Global config instance +config = Config() diff --git a/mycli/utils.py b/mycli/utils.py new file mode 100644 index 0000000..fc7022b --- /dev/null +++ b/mycli/utils.py @@ -0,0 +1,42 @@ +"""Utility functions and decorators for mycli.""" + +import functools +import subprocess + +import click + + +def handle_errors(func): + """Decorator to handle common errors gracefully.""" + + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except FileNotFoundError as e: + click.secho(f"Command not found: {e.filename}", fg="red", err=True) + raise click.Exit(1) + except subprocess.CalledProcessError as e: + click.secho(f"Command failed with exit code {e.returncode}", fg="red", err=True) + if e.stderr: + click.echo(e.stderr, err=True) + raise click.Exit(1) + except KeyboardInterrupt: + click.echo("\nAborted.") + raise click.Exit(130) + + return wrapper + + +def run_command(cmd: list[str], check: bool = True, **kwargs) -> subprocess.CompletedProcess: + """Run a subprocess command with consistent error handling. + + Args: + cmd: Command and arguments as list + check: Whether to check return code + **kwargs: Additional args for subprocess.run + + Returns: + CompletedProcess instance + """ + return subprocess.run(cmd, check=check, **kwargs) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bc77df9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "mycli" +version = "0.1.0" +description = "Personal CLI toolkit" +requires-python = ">=3.9" +dependencies = [ + "click>=8.0", +] + +[project.scripts] +mycli = "mycli.cli:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..be2a933 --- /dev/null +++ b/uv.lock @@ -0,0 +1,58 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click" +version = "8.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "mycli" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.metadata] +requires-dist = [{ name = "click", specifier = ">=8.0" }]