@fredlackey/devutils 0.0.18 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -141
- package/package.json +8 -83
- package/src/api/loader.js +229 -0
- package/src/api/registry.json +62 -0
- package/src/cli.js +293 -60
- package/src/commands/ai/index.js +16 -0
- package/src/commands/ai/launch.js +112 -0
- package/src/commands/ai/list.js +54 -0
- package/src/commands/ai/resume.js +70 -0
- package/src/commands/ai/sessions.js +121 -0
- package/src/commands/ai/set.js +131 -0
- package/src/commands/ai/show.js +74 -0
- package/src/commands/ai/tools.js +46 -0
- package/src/commands/alias/add.js +93 -0
- package/src/commands/alias/helpers.js +107 -0
- package/src/commands/alias/index.js +14 -0
- package/src/commands/alias/list.js +55 -0
- package/src/commands/alias/remove.js +62 -0
- package/src/commands/alias/sync.js +109 -0
- package/src/commands/api/disable.js +73 -0
- package/src/commands/api/enable.js +148 -0
- package/src/commands/api/index.js +15 -0
- package/src/commands/api/list.js +66 -0
- package/src/commands/api/update.js +87 -0
- package/src/commands/auth/index.js +15 -0
- package/src/commands/auth/list.js +49 -0
- package/src/commands/auth/login.js +384 -0
- package/src/commands/auth/logout.js +111 -0
- package/src/commands/auth/refresh.js +184 -0
- package/src/commands/auth/services.js +169 -0
- package/src/commands/auth/status.js +104 -0
- package/src/commands/config/export.js +224 -0
- package/src/commands/config/get.js +52 -0
- package/src/commands/config/import.js +308 -0
- package/src/commands/config/index.js +17 -0
- package/src/commands/config/init.js +143 -0
- package/src/commands/config/reset.js +57 -0
- package/src/commands/config/set.js +93 -0
- package/src/commands/config/show.js +35 -0
- package/src/commands/help.js +338 -0
- package/src/commands/identity/add.js +133 -0
- package/src/commands/identity/index.js +17 -0
- package/src/commands/identity/link.js +76 -0
- package/src/commands/identity/list.js +48 -0
- package/src/commands/identity/remove.js +72 -0
- package/src/commands/identity/show.js +65 -0
- package/src/commands/identity/sync.js +172 -0
- package/src/commands/identity/unlink.js +57 -0
- package/src/commands/ignore/add.js +165 -0
- package/src/commands/ignore/index.js +14 -0
- package/src/commands/ignore/list.js +89 -0
- package/src/commands/ignore/markers.js +43 -0
- package/src/commands/ignore/remove.js +164 -0
- package/src/commands/ignore/show.js +169 -0
- package/src/commands/machine/detect.js +122 -0
- package/src/commands/machine/index.js +14 -0
- package/src/commands/machine/list.js +74 -0
- package/src/commands/machine/set.js +106 -0
- package/src/commands/machine/show.js +35 -0
- package/src/commands/schema.js +152 -0
- package/src/commands/search/collections.js +134 -0
- package/src/commands/search/get.js +71 -0
- package/src/commands/search/index-cmd.js +54 -0
- package/src/commands/search/index.js +21 -0
- package/src/commands/search/keyword.js +60 -0
- package/src/commands/search/qmd.js +70 -0
- package/src/commands/search/query.js +64 -0
- package/src/commands/search/semantic.js +62 -0
- package/src/commands/search/status.js +46 -0
- package/src/commands/status.js +224 -171
- package/src/commands/tools/check.js +79 -0
- package/src/commands/tools/index.js +14 -0
- package/src/commands/tools/install.js +110 -0
- package/src/commands/tools/list.js +91 -0
- package/src/commands/tools/search.js +60 -0
- package/src/commands/update.js +83 -112
- package/src/commands/util/add.js +151 -0
- package/src/commands/util/index.js +15 -0
- package/src/commands/util/list.js +97 -0
- package/src/commands/util/remove.js +76 -0
- package/src/commands/util/run.js +79 -0
- package/src/commands/util/show.js +67 -0
- package/src/commands/version.js +21 -88
- package/src/installers/_template.js +104 -0
- package/src/installers/git.js +150 -0
- package/src/installers/homebrew.js +190 -0
- package/src/installers/node.js +223 -0
- package/src/installers/registry.json +29 -0
- package/src/lib/config.js +125 -0
- package/src/lib/detect.js +74 -0
- package/src/lib/errors.js +114 -0
- package/src/lib/github.js +315 -0
- package/src/lib/installer.js +225 -0
- package/src/lib/output.js +239 -0
- package/src/lib/platform.js +112 -0
- package/src/lib/platforms/amazon-linux.js +41 -0
- package/src/lib/platforms/gitbash.js +46 -0
- package/src/lib/platforms/macos.js +45 -0
- package/src/lib/platforms/raspbian.js +41 -0
- package/src/lib/platforms/ubuntu.js +39 -0
- package/src/lib/platforms/windows.js +45 -0
- package/src/lib/prompt.js +161 -0
- package/src/lib/schema.js +211 -0
- package/src/lib/shell.js +75 -0
- package/src/patterns/gitignore/claude-code.txt +25 -0
- package/src/patterns/gitignore/docker.txt +15 -0
- package/src/patterns/gitignore/go.txt +24 -0
- package/src/patterns/gitignore/java.txt +38 -0
- package/src/patterns/gitignore/jetbrains.txt +26 -0
- package/src/patterns/gitignore/linux.txt +18 -0
- package/src/patterns/gitignore/macos.txt +27 -0
- package/src/patterns/gitignore/node.txt +51 -0
- package/src/patterns/gitignore/python.txt +55 -0
- package/src/patterns/gitignore/rust.txt +14 -0
- package/src/patterns/gitignore/terraform.txt +30 -0
- package/src/patterns/gitignore/vscode.txt +15 -0
- package/src/patterns/gitignore/windows.txt +25 -0
- package/src/utils/clone/index.js +165 -0
- package/src/utils/git-push/index.js +230 -0
- package/src/utils/git-status/index.js +116 -0
- package/src/utils/git-status/unix.sh +75 -0
- package/src/utils/registry.json +41 -0
- package/bin/dev.js +0 -16
- package/files/README.md +0 -0
- package/files/claude/.claude/commands/setup-context.md +0 -3
- package/files/monorepos/_archive/README.md +0 -36
- package/files/monorepos/_legacy/README.md +0 -36
- package/files/monorepos/ai-docs/README.md +0 -33
- package/files/monorepos/apps/README.md +0 -24
- package/files/monorepos/docs/README.md +0 -40
- package/files/monorepos/packages/README.md +0 -25
- package/files/monorepos/research/README.md +0 -29
- package/files/monorepos/scripts/README.md +0 -24
- package/src/commands/README.md +0 -41
- package/src/commands/configure.js +0 -199
- package/src/commands/identity.js +0 -1630
- package/src/commands/ignore.js +0 -247
- package/src/commands/install.js +0 -526
- package/src/commands/setup.js +0 -246
- package/src/completion.js +0 -284
- package/src/constants.js +0 -45
- package/src/ignore/claude-code.txt +0 -10
- package/src/ignore/docker.txt +0 -18
- package/src/ignore/linux.txt +0 -23
- package/src/ignore/macos.txt +0 -36
- package/src/ignore/node.txt +0 -55
- package/src/ignore/terraform.txt +0 -37
- package/src/ignore/vscode.txt +0 -18
- package/src/ignore/windows.txt +0 -35
- package/src/index.js +0 -0
- package/src/installs/README.md +0 -399
- package/src/installs/adobe-creative-cloud.js +0 -546
- package/src/installs/adobe-creative-cloud.md +0 -605
- package/src/installs/appcleaner.js +0 -321
- package/src/installs/appcleaner.md +0 -699
- package/src/installs/apt-transport-https.js +0 -390
- package/src/installs/apt-transport-https.md +0 -678
- package/src/installs/atomicparsley.js +0 -642
- package/src/installs/atomicparsley.md +0 -795
- package/src/installs/aws-cli.js +0 -797
- package/src/installs/aws-cli.md +0 -727
- package/src/installs/balena-etcher.js +0 -710
- package/src/installs/balena-etcher.md +0 -761
- package/src/installs/bambu-studio.js +0 -1143
- package/src/installs/bambu-studio.md +0 -780
- package/src/installs/bash-completion.js +0 -575
- package/src/installs/bash-completion.md +0 -833
- package/src/installs/bash.js +0 -417
- package/src/installs/bash.md +0 -993
- package/src/installs/beyond-compare.js +0 -603
- package/src/installs/beyond-compare.md +0 -813
- package/src/installs/brave-browser.js +0 -968
- package/src/installs/brave-browser.md +0 -650
- package/src/installs/build-essential.js +0 -529
- package/src/installs/build-essential.md +0 -977
- package/src/installs/ca-certificates.js +0 -618
- package/src/installs/ca-certificates.md +0 -937
- package/src/installs/caffeine.js +0 -508
- package/src/installs/caffeine.md +0 -839
- package/src/installs/camtasia.js +0 -596
- package/src/installs/camtasia.md +0 -762
- package/src/installs/chatgpt.js +0 -476
- package/src/installs/chatgpt.md +0 -814
- package/src/installs/chocolatey.js +0 -456
- package/src/installs/chocolatey.md +0 -661
- package/src/installs/chrome-canary.js +0 -419
- package/src/installs/chrome-canary.md +0 -641
- package/src/installs/chromium.js +0 -667
- package/src/installs/chromium.md +0 -838
- package/src/installs/claude-code.js +0 -576
- package/src/installs/claude-code.md +0 -1173
- package/src/installs/cloudflare-warp.js +0 -900
- package/src/installs/cloudflare-warp.md +0 -1047
- package/src/installs/comet-browser.js +0 -588
- package/src/installs/comet-browser.md +0 -731
- package/src/installs/curl.js +0 -379
- package/src/installs/curl.md +0 -714
- package/src/installs/cursor.js +0 -579
- package/src/installs/cursor.md +0 -970
- package/src/installs/dbeaver.js +0 -924
- package/src/installs/dbeaver.md +0 -939
- package/src/installs/dbschema.js +0 -692
- package/src/installs/dbschema.md +0 -925
- package/src/installs/dependencies.md +0 -453
- package/src/installs/development-tools.js +0 -600
- package/src/installs/development-tools.md +0 -977
- package/src/installs/docker.js +0 -1029
- package/src/installs/docker.md +0 -1109
- package/src/installs/drawio.js +0 -1019
- package/src/installs/drawio.md +0 -795
- package/src/installs/elmedia-player.js +0 -347
- package/src/installs/elmedia-player.md +0 -556
- package/src/installs/ffmpeg.js +0 -889
- package/src/installs/ffmpeg.md +0 -852
- package/src/installs/file.js +0 -464
- package/src/installs/file.md +0 -987
- package/src/installs/gemini-cli.js +0 -811
- package/src/installs/gemini-cli.md +0 -1153
- package/src/installs/git.js +0 -400
- package/src/installs/git.md +0 -907
- package/src/installs/gitego.js +0 -949
- package/src/installs/gitego.md +0 -1172
- package/src/installs/go.js +0 -931
- package/src/installs/go.md +0 -958
- package/src/installs/google-antigravity.js +0 -913
- package/src/installs/google-antigravity.md +0 -1075
- package/src/installs/google-chrome.js +0 -833
- package/src/installs/google-chrome.md +0 -862
- package/src/installs/gpg.js +0 -480
- package/src/installs/gpg.md +0 -1056
- package/src/installs/homebrew.js +0 -1028
- package/src/installs/homebrew.md +0 -988
- package/src/installs/imageoptim.js +0 -968
- package/src/installs/imageoptim.md +0 -1119
- package/src/installs/installers.json +0 -4032
- package/src/installs/installers.json.tmp +0 -3953
- package/src/installs/jq.js +0 -400
- package/src/installs/jq.md +0 -809
- package/src/installs/keyboard-maestro.js +0 -719
- package/src/installs/keyboard-maestro.md +0 -825
- package/src/installs/kiro.js +0 -864
- package/src/installs/kiro.md +0 -1015
- package/src/installs/latex.js +0 -789
- package/src/installs/latex.md +0 -1095
- package/src/installs/lftp.js +0 -356
- package/src/installs/lftp.md +0 -907
- package/src/installs/lsb-release.js +0 -346
- package/src/installs/lsb-release.md +0 -814
- package/src/installs/messenger.js +0 -847
- package/src/installs/messenger.md +0 -900
- package/src/installs/microsoft-office.js +0 -568
- package/src/installs/microsoft-office.md +0 -760
- package/src/installs/microsoft-teams.js +0 -801
- package/src/installs/microsoft-teams.md +0 -886
- package/src/installs/moom.js +0 -326
- package/src/installs/moom.md +0 -570
- package/src/installs/node.js +0 -904
- package/src/installs/node.md +0 -1153
- package/src/installs/nordpass.js +0 -716
- package/src/installs/nordpass.md +0 -921
- package/src/installs/nordvpn.js +0 -892
- package/src/installs/nordvpn.md +0 -1052
- package/src/installs/nvm.js +0 -995
- package/src/installs/nvm.md +0 -1057
- package/src/installs/ohmyzsh.js +0 -529
- package/src/installs/ohmyzsh.md +0 -1094
- package/src/installs/openssh.js +0 -804
- package/src/installs/openssh.md +0 -1056
- package/src/installs/pandoc.js +0 -662
- package/src/installs/pandoc.md +0 -1036
- package/src/installs/parallels-desktop.js +0 -431
- package/src/installs/parallels-desktop.md +0 -446
- package/src/installs/pinentry.js +0 -510
- package/src/installs/pinentry.md +0 -1142
- package/src/installs/pngyu.js +0 -869
- package/src/installs/pngyu.md +0 -896
- package/src/installs/postman.js +0 -799
- package/src/installs/postman.md +0 -940
- package/src/installs/procps.js +0 -425
- package/src/installs/procps.md +0 -851
- package/src/installs/safari-tech-preview.js +0 -374
- package/src/installs/safari-tech-preview.md +0 -533
- package/src/installs/sfnt2woff.js +0 -658
- package/src/installs/sfnt2woff.md +0 -795
- package/src/installs/shellcheck.js +0 -481
- package/src/installs/shellcheck.md +0 -1005
- package/src/installs/slack.js +0 -741
- package/src/installs/slack.md +0 -865
- package/src/installs/snagit.js +0 -585
- package/src/installs/snagit.md +0 -844
- package/src/installs/software-properties-common.js +0 -372
- package/src/installs/software-properties-common.md +0 -805
- package/src/installs/spotify.js +0 -877
- package/src/installs/spotify.md +0 -901
- package/src/installs/studio-3t.js +0 -823
- package/src/installs/studio-3t.md +0 -918
- package/src/installs/sublime-text.js +0 -804
- package/src/installs/sublime-text.md +0 -914
- package/src/installs/superwhisper.js +0 -706
- package/src/installs/superwhisper.md +0 -630
- package/src/installs/tailscale.js +0 -745
- package/src/installs/tailscale.md +0 -1100
- package/src/installs/tar.js +0 -389
- package/src/installs/tar.md +0 -946
- package/src/installs/termius.js +0 -798
- package/src/installs/termius.md +0 -844
- package/src/installs/terraform.js +0 -779
- package/src/installs/terraform.md +0 -899
- package/src/installs/tfenv.js +0 -778
- package/src/installs/tfenv.md +0 -1091
- package/src/installs/tidal.js +0 -771
- package/src/installs/tidal.md +0 -864
- package/src/installs/tmux.js +0 -346
- package/src/installs/tmux.md +0 -1030
- package/src/installs/tree.js +0 -411
- package/src/installs/tree.md +0 -833
- package/src/installs/unzip.js +0 -460
- package/src/installs/unzip.md +0 -879
- package/src/installs/vim.js +0 -421
- package/src/installs/vim.md +0 -1040
- package/src/installs/vlc.js +0 -821
- package/src/installs/vlc.md +0 -927
- package/src/installs/vscode.js +0 -843
- package/src/installs/vscode.md +0 -1002
- package/src/installs/wget.js +0 -420
- package/src/installs/wget.md +0 -791
- package/src/installs/whatsapp.js +0 -729
- package/src/installs/whatsapp.md +0 -854
- package/src/installs/winpty.js +0 -352
- package/src/installs/winpty.md +0 -620
- package/src/installs/woff2.js +0 -553
- package/src/installs/woff2.md +0 -977
- package/src/installs/wsl.js +0 -572
- package/src/installs/wsl.md +0 -699
- package/src/installs/xcode-clt.js +0 -520
- package/src/installs/xcode-clt.md +0 -351
- package/src/installs/xcode.js +0 -560
- package/src/installs/xcode.md +0 -573
- package/src/installs/yarn.js +0 -824
- package/src/installs/yarn.md +0 -1074
- package/src/installs/yq.js +0 -654
- package/src/installs/yq.md +0 -944
- package/src/installs/yt-dlp.js +0 -701
- package/src/installs/yt-dlp.md +0 -946
- package/src/installs/yum-utils.js +0 -297
- package/src/installs/yum-utils.md +0 -648
- package/src/installs/zoom.js +0 -759
- package/src/installs/zoom.md +0 -884
- package/src/installs/zsh.js +0 -455
- package/src/installs/zsh.md +0 -1008
- package/src/scripts/README.md +0 -617
- package/src/scripts/STATUS.md +0 -208
- package/src/scripts/afk.js +0 -411
- package/src/scripts/backup-all.js +0 -746
- package/src/scripts/backup-source.js +0 -727
- package/src/scripts/brewd.js +0 -389
- package/src/scripts/brewi.js +0 -520
- package/src/scripts/brewr.js +0 -527
- package/src/scripts/brews.js +0 -477
- package/src/scripts/brewu.js +0 -504
- package/src/scripts/c.js +0 -201
- package/src/scripts/ccurl.js +0 -341
- package/src/scripts/certbot-crontab-init.js +0 -504
- package/src/scripts/certbot-init.js +0 -657
- package/src/scripts/ch.js +0 -355
- package/src/scripts/claude-danger.js +0 -268
- package/src/scripts/clean-dev.js +0 -435
- package/src/scripts/clear-dns-cache.js +0 -541
- package/src/scripts/clone.js +0 -435
- package/src/scripts/code-all.js +0 -437
- package/src/scripts/count-files.js +0 -211
- package/src/scripts/count-folders.js +0 -211
- package/src/scripts/count.js +0 -264
- package/src/scripts/d.js +0 -219
- package/src/scripts/datauri.js +0 -389
- package/src/scripts/delete-files.js +0 -380
- package/src/scripts/docker-clean.js +0 -426
- package/src/scripts/dp.js +0 -442
- package/src/scripts/e.js +0 -390
- package/src/scripts/empty-trash.js +0 -513
- package/src/scripts/evm.js +0 -444
- package/src/scripts/fetch-github-repos.js +0 -456
- package/src/scripts/get-channel.js +0 -345
- package/src/scripts/get-course.js +0 -399
- package/src/scripts/get-dependencies.js +0 -306
- package/src/scripts/get-folder.js +0 -799
- package/src/scripts/get-tunes.js +0 -426
- package/src/scripts/get-video.js +0 -367
- package/src/scripts/git-backup.js +0 -577
- package/src/scripts/git-clone.js +0 -493
- package/src/scripts/git-pup.js +0 -319
- package/src/scripts/git-push.js +0 -396
- package/src/scripts/h.js +0 -622
- package/src/scripts/hide-desktop-icons.js +0 -499
- package/src/scripts/hide-hidden-files.js +0 -538
- package/src/scripts/install-dependencies-from.js +0 -456
- package/src/scripts/ips.js +0 -663
- package/src/scripts/iso.js +0 -370
- package/src/scripts/killni.js +0 -577
- package/src/scripts/ll.js +0 -467
- package/src/scripts/local-ip.js +0 -325
- package/src/scripts/m.js +0 -524
- package/src/scripts/map.js +0 -309
- package/src/scripts/mkd.js +0 -351
- package/src/scripts/ncu-update-all.js +0 -457
- package/src/scripts/nginx-init.js +0 -718
- package/src/scripts/npmi.js +0 -382
- package/src/scripts/o.js +0 -511
- package/src/scripts/org-by-date.js +0 -338
- package/src/scripts/p.js +0 -224
- package/src/scripts/packages.js +0 -330
- package/src/scripts/path.js +0 -225
- package/src/scripts/ports.js +0 -597
- package/src/scripts/q.js +0 -305
- package/src/scripts/refresh-files.js +0 -394
- package/src/scripts/remove-smaller-files.js +0 -516
- package/src/scripts/rename-files-with-date.js +0 -533
- package/src/scripts/resize-image.js +0 -539
- package/src/scripts/rm-safe.js +0 -669
- package/src/scripts/s.js +0 -540
- package/src/scripts/set-git-public.js +0 -365
- package/src/scripts/show-desktop-icons.js +0 -475
- package/src/scripts/show-hidden-files.js +0 -472
- package/src/scripts/tpa.js +0 -280
- package/src/scripts/tpo.js +0 -280
- package/src/scripts/u.js +0 -505
- package/src/scripts/vpush.js +0 -437
- package/src/scripts/y.js +0 -283
- package/src/utils/README.md +0 -95
- package/src/utils/common/apps.js +0 -143
- package/src/utils/common/display.js +0 -157
- package/src/utils/common/network.js +0 -185
- package/src/utils/common/os.js +0 -294
- package/src/utils/common/package-manager.js +0 -301
- package/src/utils/common/privileges.js +0 -138
- package/src/utils/common/shell.js +0 -261
- package/src/utils/macos/apps.js +0 -228
- package/src/utils/macos/brew.js +0 -315
- package/src/utils/ubuntu/apt.js +0 -307
- package/src/utils/ubuntu/desktop.js +0 -292
- package/src/utils/ubuntu/snap.js +0 -344
- package/src/utils/ubuntu/systemd.js +0 -286
- package/src/utils/windows/choco.js +0 -465
- package/src/utils/windows/env.js +0 -246
- package/src/utils/windows/registry.js +0 -269
- package/src/utils/windows/shell.js +0 -240
- package/src/utils/windows/winget.js +0 -489
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = path.join(os.homedir(), '.devutils', 'config.json');
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
description: 'Create a new identity profile.',
|
|
11
|
+
arguments: [
|
|
12
|
+
{ name: 'name', description: 'Short name for this identity (e.g., personal, work)', required: true },
|
|
13
|
+
],
|
|
14
|
+
flags: [
|
|
15
|
+
{ name: 'email', description: 'Git author email (required)' },
|
|
16
|
+
{ name: 'ssh-key', description: 'Path to SSH private key file' },
|
|
17
|
+
{ name: 'gpg-key', description: 'GPG key ID for commit signing' },
|
|
18
|
+
{ name: 'generate-key', description: 'Generate a new SSH key pair for this identity' },
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
async function run(args, context) {
|
|
23
|
+
const identityName = args.positional[0];
|
|
24
|
+
if (!identityName) {
|
|
25
|
+
context.errors.throwError(400, 'Missing identity name. Usage: dev identity add <name> --email <email>', 'identity');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Validate name format
|
|
30
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(identityName)) {
|
|
31
|
+
context.errors.throwError(400, 'Identity name must be lowercase letters, numbers, and hyphens only.', 'identity');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const email = args.flags.email;
|
|
36
|
+
if (!email) {
|
|
37
|
+
context.errors.throwError(400, 'Email is required. Use --email your@email.com.', 'identity');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Load config
|
|
42
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
43
|
+
context.errors.throwError(404, 'Config not found. Run "dev config init" first.', 'identity');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
47
|
+
const identities = config.identities || [];
|
|
48
|
+
|
|
49
|
+
// Check for duplicates (case-insensitive)
|
|
50
|
+
if (identities.some(id => id.name.toLowerCase() === identityName.toLowerCase())) {
|
|
51
|
+
context.errors.throwError(400, `Identity '${identityName}' already exists. Use a different name or remove it first.`, 'identity');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle SSH key
|
|
56
|
+
let sshKeyPath = null;
|
|
57
|
+
const sshKeyFlag = args.flags['ssh-key'];
|
|
58
|
+
const generateKey = args.flags['generate-key'];
|
|
59
|
+
|
|
60
|
+
if (sshKeyFlag && generateKey) {
|
|
61
|
+
context.errors.throwError(400, 'Provide either --ssh-key or --generate-key, not both.', 'identity');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (sshKeyFlag) {
|
|
66
|
+
const resolved = path.resolve(sshKeyFlag);
|
|
67
|
+
if (!fs.existsSync(resolved)) {
|
|
68
|
+
context.errors.throwError(404, `SSH key file not found: ${resolved}`, 'identity');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
sshKeyPath = resolved;
|
|
72
|
+
} else if (generateKey) {
|
|
73
|
+
sshKeyPath = await generateSshKey(identityName, email, context);
|
|
74
|
+
if (!sshKeyPath) return; // Error already reported
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Handle GPG key
|
|
78
|
+
const gpgKey = args.flags['gpg-key'] || null;
|
|
79
|
+
|
|
80
|
+
// Build identity
|
|
81
|
+
const identity = {
|
|
82
|
+
name: identityName,
|
|
83
|
+
email: email,
|
|
84
|
+
sshKey: sshKeyPath,
|
|
85
|
+
gpgKey: gpgKey,
|
|
86
|
+
folders: [],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Save
|
|
90
|
+
identities.push(identity);
|
|
91
|
+
config.identities = identities;
|
|
92
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
|
|
93
|
+
|
|
94
|
+
context.output.info(`Identity '${identityName}' created.`);
|
|
95
|
+
context.output.out(identity);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generates an ED25519 SSH key pair for the given identity.
|
|
100
|
+
* @param {string} name - The identity name (used in the key filename).
|
|
101
|
+
* @param {string} email - The email (used as the key comment).
|
|
102
|
+
* @param {object} context - The command context.
|
|
103
|
+
* @returns {Promise<string|null>} The key path, or null on failure.
|
|
104
|
+
*/
|
|
105
|
+
async function generateSshKey(name, email, context) {
|
|
106
|
+
const sshDir = path.join(os.homedir(), '.ssh');
|
|
107
|
+
if (!fs.existsSync(sshDir)) {
|
|
108
|
+
fs.mkdirSync(sshDir, { mode: 0o700 });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const keyPath = path.join(sshDir, `id_ed25519_${name}`);
|
|
112
|
+
if (fs.existsSync(keyPath)) {
|
|
113
|
+
context.errors.throwError(400, `SSH key already exists at ${keyPath}. Use --ssh-key ${keyPath} to reference it.`, 'identity');
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = await context.shell.exec(
|
|
118
|
+
`ssh-keygen -t ed25519 -C "${email}" -f "${keyPath}" -N ""`
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (result.exitCode !== 0) {
|
|
122
|
+
context.errors.throwError(500, `Failed to generate SSH key: ${result.stderr}`, 'identity');
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
context.output.info(`SSH key generated: ${keyPath}`);
|
|
127
|
+
context.output.info(`Public key: ${keyPath}.pub`);
|
|
128
|
+
context.output.info('Add the public key to your GitHub account at https://github.com/settings/keys');
|
|
129
|
+
|
|
130
|
+
return keyPath;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity service registration.
|
|
3
|
+
* Git identities, SSH keys, GPG signing.
|
|
4
|
+
*/
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'identity',
|
|
7
|
+
description: 'Git identities, SSH keys, GPG signing',
|
|
8
|
+
commands: {
|
|
9
|
+
add: () => require('./add'),
|
|
10
|
+
remove: () => require('./remove'),
|
|
11
|
+
list: () => require('./list'),
|
|
12
|
+
show: () => require('./show'),
|
|
13
|
+
link: () => require('./link'),
|
|
14
|
+
unlink: () => require('./unlink'),
|
|
15
|
+
sync: () => require('./sync'),
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = path.join(os.homedir(), '.devutils', 'config.json');
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
description: 'Link an identity to a folder path.',
|
|
11
|
+
arguments: [
|
|
12
|
+
{ name: 'name', description: 'Identity name', required: true },
|
|
13
|
+
{ name: 'folder', description: 'Folder path to link (absolute or relative)', required: true },
|
|
14
|
+
],
|
|
15
|
+
flags: [
|
|
16
|
+
{ name: 'remote', description: 'Git remote hostname (default: github.com)' },
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function run(args, context) {
|
|
21
|
+
const name = args.positional[0];
|
|
22
|
+
const folder = args.positional[1];
|
|
23
|
+
|
|
24
|
+
if (!name || !folder) {
|
|
25
|
+
context.errors.throwError(400, 'Usage: dev identity link <name> <folder> [--remote <host>]', 'identity');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const absolutePath = path.resolve(folder);
|
|
30
|
+
if (!fs.existsSync(absolutePath)) {
|
|
31
|
+
context.errors.throwError(404, `Folder not found: ${absolutePath}`, 'identity');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
36
|
+
context.errors.throwError(404, 'Config not found. Run "dev config init" first.', 'identity');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
40
|
+
const identities = config.identities || [];
|
|
41
|
+
|
|
42
|
+
// Find the target identity
|
|
43
|
+
const identity = identities.find(id => id.name.toLowerCase() === name.toLowerCase());
|
|
44
|
+
if (!identity) {
|
|
45
|
+
context.errors.throwError(404, `Identity '${name}' not found.`, 'identity');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const remote = args.flags.remote || 'github.com';
|
|
50
|
+
|
|
51
|
+
// Check if folder is linked to a different identity
|
|
52
|
+
for (const id of identities) {
|
|
53
|
+
const existing = (id.folders || []).find(f => f.path === absolutePath);
|
|
54
|
+
if (existing && id.name.toLowerCase() !== name.toLowerCase()) {
|
|
55
|
+
context.output.info(`Warning: Folder '${absolutePath}' is currently linked to identity '${id.name}'. Re-linking to '${identity.name}'.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Remove folder from all identities (avoid duplicates)
|
|
60
|
+
for (const id of identities) {
|
|
61
|
+
id.folders = (id.folders || []).filter(f => f.path !== absolutePath);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Add the link
|
|
65
|
+
identity.folders = identity.folders || [];
|
|
66
|
+
identity.folders.push({ path: absolutePath, remote });
|
|
67
|
+
|
|
68
|
+
// Save
|
|
69
|
+
config.identities = identities;
|
|
70
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
|
|
71
|
+
|
|
72
|
+
context.output.info(`Linked '${absolutePath}' to identity '${identity.name}' (remote: ${remote}).`);
|
|
73
|
+
context.output.info("Run 'dev identity sync' to apply changes to SSH and git config.");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = path.join(os.homedir(), '.devutils', 'config.json');
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
description: 'List all configured identities.',
|
|
11
|
+
arguments: [],
|
|
12
|
+
flags: [],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
async function run(args, context) {
|
|
16
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
17
|
+
context.errors.throwError(404, 'Config not found. Run "dev config init" first.', 'identity');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
21
|
+
const identities = config.identities || [];
|
|
22
|
+
|
|
23
|
+
if (identities.length === 0) {
|
|
24
|
+
context.output.info('No identities configured.');
|
|
25
|
+
context.output.info('');
|
|
26
|
+
context.output.info('Create one with:');
|
|
27
|
+
context.output.info(' dev identity add <name> --email <email>');
|
|
28
|
+
context.output.info('');
|
|
29
|
+
context.output.info('Example:');
|
|
30
|
+
context.output.info(' dev identity add personal --email fred@example.com');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Build table rows
|
|
35
|
+
const rows = identities.map(id => ({
|
|
36
|
+
Name: id.name,
|
|
37
|
+
Email: id.email,
|
|
38
|
+
'SSH Key': id.sshKey ? 'yes' : 'no',
|
|
39
|
+
'GPG Key': id.gpgKey ? 'yes' : 'no',
|
|
40
|
+
'Linked Folders': (id.folders || []).length,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
context.output.out(rows);
|
|
44
|
+
const count = identities.length;
|
|
45
|
+
context.output.info(`\n${count} ${count === 1 ? 'identity' : 'identities'} configured.`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = path.join(os.homedir(), '.devutils', 'config.json');
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
description: 'Remove an identity profile.',
|
|
11
|
+
arguments: [
|
|
12
|
+
{ name: 'name', description: 'Name of the identity to remove', required: true },
|
|
13
|
+
],
|
|
14
|
+
flags: [
|
|
15
|
+
{ name: 'confirm', description: 'Skip the confirmation prompt' },
|
|
16
|
+
{ name: 'force', description: 'Remove even if the identity has linked folders' },
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function run(args, context) {
|
|
21
|
+
const name = args.positional[0];
|
|
22
|
+
if (!name) {
|
|
23
|
+
context.errors.throwError(400, 'Missing identity name. Usage: dev identity remove <name>', 'identity');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
28
|
+
context.errors.throwError(404, 'Config not found. Run "dev config init" first.', 'identity');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
32
|
+
const identities = config.identities || [];
|
|
33
|
+
|
|
34
|
+
// Find the identity
|
|
35
|
+
const identity = identities.find(id => id.name.toLowerCase() === name.toLowerCase());
|
|
36
|
+
if (!identity) {
|
|
37
|
+
context.errors.throwError(404, `Identity '${name}' not found.`, 'identity');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check for linked folders
|
|
42
|
+
const folders = identity.folders || [];
|
|
43
|
+
if (folders.length > 0 && !args.flags.force) {
|
|
44
|
+
const folderList = folders.map(f => ` ${f.path || f}`).join('\n');
|
|
45
|
+
context.errors.throwError(
|
|
46
|
+
400,
|
|
47
|
+
`Cannot remove identity '${identity.name}' because it has ${folders.length} linked folder(s):\n${folderList}\nUse --force to remove anyway, or unlink the folders first.`,
|
|
48
|
+
'identity'
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Confirm
|
|
54
|
+
if (!args.flags.confirm) {
|
|
55
|
+
const proceed = await context.prompt.confirm(`Remove identity '${identity.name}'? This cannot be undone.`, false);
|
|
56
|
+
if (!proceed) {
|
|
57
|
+
context.output.info('Cancelled.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Remove from array
|
|
63
|
+
config.identities = identities.filter(id => id.name.toLowerCase() !== name.toLowerCase());
|
|
64
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
|
|
65
|
+
|
|
66
|
+
context.output.info(`Identity '${identity.name}' removed.`);
|
|
67
|
+
if (identity.sshKey) {
|
|
68
|
+
context.output.info(`Note: SSH key at ${identity.sshKey} was not deleted. Remove it manually if no longer needed.`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = path.join(os.homedir(), '.devutils', 'config.json');
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
description: 'Show details of a specific identity.',
|
|
11
|
+
arguments: [
|
|
12
|
+
{ name: 'name', description: 'Name of the identity to show', required: true },
|
|
13
|
+
],
|
|
14
|
+
flags: [],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function run(args, context) {
|
|
18
|
+
const name = args.positional[0];
|
|
19
|
+
if (!name) {
|
|
20
|
+
context.errors.throwError(400, 'Missing identity name. Usage: dev identity show <name>', 'identity');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
25
|
+
context.errors.throwError(404, 'Config not found. Run "dev config init" first.', 'identity');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
29
|
+
const identities = config.identities || [];
|
|
30
|
+
|
|
31
|
+
const identity = identities.find(id => id.name.toLowerCase() === name.toLowerCase());
|
|
32
|
+
if (!identity) {
|
|
33
|
+
context.errors.throwError(404, `Identity '${name}' not found. Run 'dev identity list' to see available identities.`, 'identity');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// For JSON callers, output the raw object
|
|
38
|
+
if (context.flags.format === 'json') {
|
|
39
|
+
context.output.out(identity);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Formatted display for humans
|
|
44
|
+
const sshStatus = identity.sshKey
|
|
45
|
+
? `${identity.sshKey} ${fs.existsSync(identity.sshKey) ? '(exists)' : '(missing!)'}`
|
|
46
|
+
: '(not configured)';
|
|
47
|
+
const gpgStatus = identity.gpgKey || '(not configured)';
|
|
48
|
+
const folders = identity.folders || [];
|
|
49
|
+
|
|
50
|
+
context.output.info(`Identity: ${identity.name}`);
|
|
51
|
+
context.output.info('');
|
|
52
|
+
context.output.info(` Email: ${identity.email}`);
|
|
53
|
+
context.output.info(` SSH Key: ${sshStatus}`);
|
|
54
|
+
context.output.info(` GPG Key: ${gpgStatus}`);
|
|
55
|
+
context.output.info(` Linked Folders (${folders.length}):`);
|
|
56
|
+
if (folders.length === 0) {
|
|
57
|
+
context.output.info(' (none)');
|
|
58
|
+
} else {
|
|
59
|
+
for (const f of folders) {
|
|
60
|
+
context.output.info(` ${f.path || f}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = path.join(os.homedir(), '.devutils', 'config.json');
|
|
8
|
+
|
|
9
|
+
const SSH_START = '# >>> DevUtils Managed SSH Config - DO NOT EDIT BETWEEN MARKERS >>>';
|
|
10
|
+
const SSH_END = '# <<< DevUtils Managed SSH Config <<<';
|
|
11
|
+
const GIT_START = '# >>> DevUtils Managed Git Config - DO NOT EDIT BETWEEN MARKERS >>>';
|
|
12
|
+
const GIT_END = '# <<< DevUtils Managed Git Config <<<';
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
description: 'Regenerate SSH config and git config from identity definitions.',
|
|
16
|
+
arguments: [],
|
|
17
|
+
flags: [
|
|
18
|
+
{ name: 'dry-run', description: 'Show what would be written without making changes' },
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Builds SSH config blocks for an identity.
|
|
24
|
+
* @param {object} identity - The identity object.
|
|
25
|
+
* @returns {string} The SSH config block text.
|
|
26
|
+
*/
|
|
27
|
+
function buildSshConfigBlock(identity) {
|
|
28
|
+
const remotes = [...new Set((identity.folders || []).map(f => f.remote || 'github.com'))];
|
|
29
|
+
const blocks = [];
|
|
30
|
+
|
|
31
|
+
for (const remote of remotes) {
|
|
32
|
+
blocks.push([
|
|
33
|
+
`# DevUtils managed - ${identity.name}`,
|
|
34
|
+
`Host ${remote}-${identity.name}`,
|
|
35
|
+
` HostName ${remote}`,
|
|
36
|
+
` User git`,
|
|
37
|
+
` IdentityFile ${identity.sshKey}`,
|
|
38
|
+
` IdentitiesOnly yes`,
|
|
39
|
+
].join('\n'));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return blocks.join('\n\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Updates a config file by replacing content between markers or appending.
|
|
47
|
+
* @param {string} filePath - The file to update.
|
|
48
|
+
* @param {string} startMarker - The start marker.
|
|
49
|
+
* @param {string} endMarker - The end marker.
|
|
50
|
+
* @param {string} newContent - The new content between markers.
|
|
51
|
+
* @param {boolean} dryRun - If true, don't write the file.
|
|
52
|
+
* @param {object} [writeOpts] - Options for writeFileSync (e.g., mode).
|
|
53
|
+
* @returns {string} The full file content.
|
|
54
|
+
*/
|
|
55
|
+
function updateManagedSection(filePath, startMarker, endMarker, newContent, dryRun, writeOpts = {}) {
|
|
56
|
+
let existing = '';
|
|
57
|
+
if (fs.existsSync(filePath)) {
|
|
58
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const startIdx = existing.indexOf(startMarker);
|
|
62
|
+
const endIdx = existing.indexOf(endMarker);
|
|
63
|
+
|
|
64
|
+
let updated;
|
|
65
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
66
|
+
updated = existing.substring(0, startIdx) +
|
|
67
|
+
startMarker + '\n' + newContent + '\n' + endMarker +
|
|
68
|
+
existing.substring(endIdx + endMarker.length);
|
|
69
|
+
} else {
|
|
70
|
+
updated = existing.trimEnd() + '\n\n' +
|
|
71
|
+
startMarker + '\n' + newContent + '\n' + endMarker + '\n';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!dryRun) {
|
|
75
|
+
const dir = path.dirname(filePath);
|
|
76
|
+
if (!fs.existsSync(dir)) {
|
|
77
|
+
fs.mkdirSync(dir, { mode: 0o700, recursive: true });
|
|
78
|
+
}
|
|
79
|
+
fs.writeFileSync(filePath, updated, writeOpts);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return updated;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function run(args, context) {
|
|
86
|
+
const dryRun = args.flags['dry-run'] || context.flags.dryRun;
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
89
|
+
context.errors.throwError(404, 'Config not found. Run "dev config init" first.', 'identity');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
93
|
+
const identities = config.identities || [];
|
|
94
|
+
|
|
95
|
+
if (identities.length === 0) {
|
|
96
|
+
context.output.info('No identities configured. Nothing to sync.');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Build SSH config
|
|
101
|
+
const sshBlocks = [];
|
|
102
|
+
for (const id of identities) {
|
|
103
|
+
if (!id.sshKey) {
|
|
104
|
+
context.output.info(`Warning: Skipping SSH config for '${id.name}' (no SSH key configured).`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if ((id.folders || []).length === 0) {
|
|
108
|
+
context.output.info(`Warning: Identity '${id.name}' has no linked folders.`);
|
|
109
|
+
}
|
|
110
|
+
sshBlocks.push(buildSshConfigBlock(id));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const sshConfigPath = path.join(os.homedir(), '.ssh', 'config');
|
|
114
|
+
const sshContent = sshBlocks.join('\n\n');
|
|
115
|
+
|
|
116
|
+
if (dryRun) {
|
|
117
|
+
context.output.info('=== SSH Config (dry run) ===');
|
|
118
|
+
context.output.info(sshContent || '(no SSH entries)');
|
|
119
|
+
} else if (sshContent) {
|
|
120
|
+
updateManagedSection(sshConfigPath, SSH_START, SSH_END, sshContent, false, { mode: 0o600 });
|
|
121
|
+
context.output.info(`SSH config updated: ${sshConfigPath}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Build git config includes
|
|
125
|
+
const gitConfigPath = path.join(os.homedir(), '.gitconfig');
|
|
126
|
+
const includeLines = [];
|
|
127
|
+
|
|
128
|
+
for (const id of identities) {
|
|
129
|
+
const folders = id.folders || [];
|
|
130
|
+
if (folders.length === 0) continue;
|
|
131
|
+
|
|
132
|
+
// Write per-identity gitconfig
|
|
133
|
+
const idConfigPath = path.join(os.homedir(), `.gitconfig-${id.name}`);
|
|
134
|
+
const lines = ['[user]', ` email = ${id.email}`];
|
|
135
|
+
if (id.gpgKey) {
|
|
136
|
+
lines.push(` signingkey = ${id.gpgKey}`);
|
|
137
|
+
lines.push('[commit]');
|
|
138
|
+
lines.push(' gpgsign = true');
|
|
139
|
+
}
|
|
140
|
+
const idContent = lines.join('\n') + '\n';
|
|
141
|
+
|
|
142
|
+
if (dryRun) {
|
|
143
|
+
context.output.info(`\n=== ${idConfigPath} (dry run) ===`);
|
|
144
|
+
context.output.info(idContent);
|
|
145
|
+
} else {
|
|
146
|
+
fs.writeFileSync(idConfigPath, idContent);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Build includeIf entries for each unique folder
|
|
150
|
+
for (const f of folders) {
|
|
151
|
+
const folderPath = f.path.endsWith('/') ? f.path : f.path + '/';
|
|
152
|
+
includeLines.push(`[includeIf "gitdir:${folderPath}"]`);
|
|
153
|
+
includeLines.push(` path = ${idConfigPath}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const gitIncludeContent = includeLines.join('\n');
|
|
158
|
+
|
|
159
|
+
if (dryRun) {
|
|
160
|
+
context.output.info('\n=== Git Config includes (dry run) ===');
|
|
161
|
+
context.output.info(gitIncludeContent || '(no git config entries)');
|
|
162
|
+
} else if (gitIncludeContent) {
|
|
163
|
+
updateManagedSection(gitConfigPath, GIT_START, GIT_END, gitIncludeContent, false);
|
|
164
|
+
context.output.info(`Git config updated: ${gitConfigPath}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!dryRun) {
|
|
168
|
+
context.output.info('Sync complete.');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = path.join(os.homedir(), '.devutils', 'config.json');
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
description: 'Remove a folder link from an identity.',
|
|
11
|
+
arguments: [
|
|
12
|
+
{ name: 'name', description: 'Identity name', required: true },
|
|
13
|
+
{ name: 'folder', description: 'Folder path to unlink', required: true },
|
|
14
|
+
],
|
|
15
|
+
flags: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function run(args, context) {
|
|
19
|
+
const name = args.positional[0];
|
|
20
|
+
const folder = args.positional[1];
|
|
21
|
+
|
|
22
|
+
if (!name || !folder) {
|
|
23
|
+
context.errors.throwError(400, 'Usage: dev identity unlink <name> <folder>', 'identity');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const absolutePath = path.resolve(folder);
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
30
|
+
context.errors.throwError(404, 'Config not found. Run "dev config init" first.', 'identity');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
34
|
+
const identities = config.identities || [];
|
|
35
|
+
|
|
36
|
+
const identity = identities.find(id => id.name.toLowerCase() === name.toLowerCase());
|
|
37
|
+
if (!identity) {
|
|
38
|
+
context.errors.throwError(404, `Identity '${name}' not found.`, 'identity');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const folders = identity.folders || [];
|
|
43
|
+
const found = folders.find(f => f.path === absolutePath);
|
|
44
|
+
if (!found) {
|
|
45
|
+
context.output.info(`Folder '${absolutePath}' is not linked to identity '${identity.name}'. Nothing to do.`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
identity.folders = folders.filter(f => f.path !== absolutePath);
|
|
50
|
+
config.identities = identities;
|
|
51
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
|
|
52
|
+
|
|
53
|
+
context.output.info(`Unlinked '${absolutePath}' from identity '${identity.name}'.`);
|
|
54
|
+
context.output.info("Run 'dev identity sync' to update SSH and git config.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { meta, run };
|