@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,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const loader = require('../../api/loader');
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
description: 'List installed and available API plugins',
|
|
7
|
+
arguments: [],
|
|
8
|
+
flags: [
|
|
9
|
+
{ name: 'installed', type: 'boolean', description: 'Show only installed plugins' },
|
|
10
|
+
{ name: 'available', type: 'boolean', description: 'Show only available (not installed) plugins' }
|
|
11
|
+
]
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lists installed and/or available API plugins.
|
|
16
|
+
* Shows both by default, or filters with --installed / --available flags.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} args - Parsed CLI arguments { positional, flags }.
|
|
19
|
+
* @param {object} context - CLI context { output, errors }.
|
|
20
|
+
*/
|
|
21
|
+
async function run(args, context) {
|
|
22
|
+
const installed = loader.getInstalledPlugins();
|
|
23
|
+
const registry = loader.getRegistryPlugins();
|
|
24
|
+
const installedNames = Object.keys(installed);
|
|
25
|
+
|
|
26
|
+
// Build the installed list
|
|
27
|
+
const installedList = installedNames.map(name => ({
|
|
28
|
+
name,
|
|
29
|
+
package: installed[name].package,
|
|
30
|
+
version: installed[name].version || 'unknown',
|
|
31
|
+
source: installed[name].source || 'npm',
|
|
32
|
+
installedAt: installed[name].installedAt || ''
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// Build the available list (registry entries that are not installed)
|
|
36
|
+
const availableList = registry
|
|
37
|
+
.filter(entry => !installed[entry.name])
|
|
38
|
+
.map(entry => ({
|
|
39
|
+
name: entry.name,
|
|
40
|
+
package: entry.package,
|
|
41
|
+
description: entry.description
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// Apply filters
|
|
45
|
+
const showInstalled = args.flags.installed || !args.flags.available;
|
|
46
|
+
const showAvailable = args.flags.available || !args.flags.installed;
|
|
47
|
+
|
|
48
|
+
const result = {};
|
|
49
|
+
|
|
50
|
+
if (showInstalled) {
|
|
51
|
+
result.installed = installedList;
|
|
52
|
+
}
|
|
53
|
+
if (showAvailable) {
|
|
54
|
+
result.available = availableList;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If only installed was requested and nothing is installed, give a hint
|
|
58
|
+
if (args.flags.installed && installedList.length === 0) {
|
|
59
|
+
context.output.info('No API plugins installed. Run "dev api enable <name>" to install one.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
context.output.out(result);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const shell = require('../../lib/shell');
|
|
6
|
+
const loader = require('../../api/loader');
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
description: 'Update an installed API plugin to the latest version',
|
|
10
|
+
arguments: [
|
|
11
|
+
{ name: 'name', description: 'Plugin name to update', required: true }
|
|
12
|
+
],
|
|
13
|
+
flags: []
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Updates an installed API plugin to the latest version.
|
|
18
|
+
* For npm-sourced plugins, runs npm update. For git-sourced plugins,
|
|
19
|
+
* re-runs npm install with the original git URL.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} args - Parsed CLI arguments { positional, flags }.
|
|
22
|
+
* @param {object} context - CLI context { output, errors }.
|
|
23
|
+
*/
|
|
24
|
+
async function run(args, context) {
|
|
25
|
+
const pluginName = args.positional[0];
|
|
26
|
+
|
|
27
|
+
if (!pluginName) {
|
|
28
|
+
context.errors.throwError(400, 'Missing required argument: <name>. Example: dev api update gmail', 'api');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if the plugin is installed
|
|
33
|
+
const plugins = loader.readPluginsJson();
|
|
34
|
+
const entry = plugins[pluginName];
|
|
35
|
+
|
|
36
|
+
if (!entry) {
|
|
37
|
+
context.output.info(`Plugin "${pluginName}" is not installed.`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const previousVersion = entry.version || 'unknown';
|
|
42
|
+
const pluginsDir = loader.PLUGINS_DIR;
|
|
43
|
+
|
|
44
|
+
// Determine update strategy based on source type
|
|
45
|
+
let result;
|
|
46
|
+
if (entry.source === 'git' && entry.url) {
|
|
47
|
+
// Git-sourced plugins: re-install from the original URL
|
|
48
|
+
context.output.info(`Updating ${pluginName} from git...`);
|
|
49
|
+
result = await shell.exec(`npm install ${entry.url}`, { cwd: pluginsDir });
|
|
50
|
+
} else {
|
|
51
|
+
// npm-sourced plugins: use npm update
|
|
52
|
+
context.output.info(`Updating ${pluginName}...`);
|
|
53
|
+
result = await shell.exec(`npm update ${entry.package}`, { cwd: pluginsDir });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (result.exitCode !== 0) {
|
|
57
|
+
context.errors.throwError(
|
|
58
|
+
500,
|
|
59
|
+
`Failed to update plugin "${pluginName}".\n${result.stderr || result.stdout}`,
|
|
60
|
+
'api'
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Read the new version from the plugin's package.json
|
|
66
|
+
let newVersion = 'unknown';
|
|
67
|
+
try {
|
|
68
|
+
const pluginPkgPath = path.join(pluginsDir, 'node_modules', entry.package, 'package.json');
|
|
69
|
+
const pluginPkg = JSON.parse(fs.readFileSync(pluginPkgPath, 'utf8'));
|
|
70
|
+
newVersion = pluginPkg.version || 'unknown';
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Could not read version
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Update the version in plugins.json
|
|
76
|
+
plugins[pluginName].version = newVersion;
|
|
77
|
+
fs.writeFileSync(loader.PLUGINS_FILE, JSON.stringify(plugins, null, 2) + '\n');
|
|
78
|
+
|
|
79
|
+
// Report the result
|
|
80
|
+
if (newVersion !== previousVersion) {
|
|
81
|
+
context.output.info(`Updated ${pluginName} from ${previousVersion} to ${newVersion}.`);
|
|
82
|
+
} else {
|
|
83
|
+
context.output.info(`${pluginName} is already at the latest version (${newVersion}).`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth service registration.
|
|
3
|
+
* OAuth and credential management.
|
|
4
|
+
*/
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'auth',
|
|
7
|
+
description: 'OAuth and credential management',
|
|
8
|
+
commands: {
|
|
9
|
+
login: () => require('./login'),
|
|
10
|
+
logout: () => require('./logout'),
|
|
11
|
+
list: () => require('./list'),
|
|
12
|
+
status: () => require('./status'),
|
|
13
|
+
refresh: () => require('./refresh'),
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { AUTH_SERVICES, readCredential, getTokenStatus } = require('./services');
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
description: 'List all connected services and their token status',
|
|
7
|
+
arguments: [],
|
|
8
|
+
flags: []
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run the auth list command.
|
|
13
|
+
* Scans all known services from AUTH_SERVICES and shows their auth status.
|
|
14
|
+
* Shows 'valid', 'expired', or 'missing' for each service.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} args - Parsed CLI arguments (positional, flags).
|
|
17
|
+
* @param {object} context - CLI context (output, prompt, errors).
|
|
18
|
+
*/
|
|
19
|
+
async function run(args, context) {
|
|
20
|
+
const services = Object.keys(AUTH_SERVICES);
|
|
21
|
+
const results = [];
|
|
22
|
+
|
|
23
|
+
for (const service of services) {
|
|
24
|
+
const config = AUTH_SERVICES[service];
|
|
25
|
+
const credential = readCredential(service);
|
|
26
|
+
const status = getTokenStatus(credential);
|
|
27
|
+
|
|
28
|
+
results.push({
|
|
29
|
+
service: service,
|
|
30
|
+
type: config.type,
|
|
31
|
+
status: status,
|
|
32
|
+
authenticatedAt: credential && credential.authenticatedAt ? credential.authenticatedAt : '-'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if any services are connected
|
|
37
|
+
const hasConnected = results.some(r => r.status !== 'missing');
|
|
38
|
+
|
|
39
|
+
if (!hasConnected) {
|
|
40
|
+
context.output.info('No services authenticated. Run "dev auth login <service>" to connect.');
|
|
41
|
+
context.output.info('');
|
|
42
|
+
context.output.info('Available services: ' + services.join(', '));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
context.output.out(results);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const url = require('url');
|
|
9
|
+
const { AUTH_SERVICES, AUTH_DIR, CLIENTS_DIR, readCredential, isSensitiveField } = require('./services');
|
|
10
|
+
const platform = require('../../lib/platform');
|
|
11
|
+
|
|
12
|
+
const OAUTH_PORT = 9876;
|
|
13
|
+
const OAUTH_REDIRECT_URI = `http://localhost:${OAUTH_PORT}/callback`;
|
|
14
|
+
const OAUTH_TIMEOUT_MS = 60000;
|
|
15
|
+
|
|
16
|
+
const meta = {
|
|
17
|
+
description: 'Authenticate with an external service (OAuth browser flow or API key prompt)',
|
|
18
|
+
arguments: [
|
|
19
|
+
{ name: 'service', description: 'Service name to authenticate with (e.g., google, aws, cloudflare)', required: true }
|
|
20
|
+
],
|
|
21
|
+
flags: [
|
|
22
|
+
{ name: 'scopes', type: 'string', description: 'Comma-separated OAuth scopes to request (OAuth services only)' }
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Open a URL in the user's default browser.
|
|
28
|
+
* Uses the appropriate command for the detected OS.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} targetUrl - The URL to open.
|
|
31
|
+
* @returns {Promise<void>}
|
|
32
|
+
*/
|
|
33
|
+
async function openBrowser(targetUrl) {
|
|
34
|
+
const shell = require('../../lib/shell');
|
|
35
|
+
const plat = platform.detect();
|
|
36
|
+
let cmd;
|
|
37
|
+
|
|
38
|
+
if (plat.type === 'macos') {
|
|
39
|
+
cmd = `open "${targetUrl}"`;
|
|
40
|
+
} else if (plat.type === 'windows' || plat.type === 'gitbash') {
|
|
41
|
+
cmd = `start "" "${targetUrl}"`;
|
|
42
|
+
} else {
|
|
43
|
+
// Linux variants
|
|
44
|
+
cmd = `xdg-open "${targetUrl}"`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await shell.exec(cmd);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Make an HTTPS POST request with form-encoded data.
|
|
52
|
+
* Uses Node.js built-in https module (no external dependencies).
|
|
53
|
+
*
|
|
54
|
+
* @param {string} targetUrl - The URL to POST to.
|
|
55
|
+
* @param {Object<string, string>} data - Key/value pairs to send as form data.
|
|
56
|
+
* @returns {Promise<{ statusCode: number, body: string }>}
|
|
57
|
+
*/
|
|
58
|
+
function httpsPost(targetUrl, data) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const postData = new URLSearchParams(data).toString();
|
|
61
|
+
const parsed = new URL(targetUrl);
|
|
62
|
+
|
|
63
|
+
const options = {
|
|
64
|
+
hostname: parsed.hostname,
|
|
65
|
+
port: parsed.port || 443,
|
|
66
|
+
path: parsed.pathname + parsed.search,
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
70
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const req = https.request(options, (res) => {
|
|
75
|
+
let body = '';
|
|
76
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
77
|
+
res.on('end', () => {
|
|
78
|
+
resolve({ statusCode: res.statusCode, body });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
req.on('error', reject);
|
|
83
|
+
req.write(postData);
|
|
84
|
+
req.end();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Start a temporary local HTTP server to capture the OAuth callback.
|
|
90
|
+
* The server listens on OAUTH_PORT, waits for the authorization code,
|
|
91
|
+
* and shuts down after receiving it (or after a timeout).
|
|
92
|
+
*
|
|
93
|
+
* @returns {Promise<string>} The authorization code from the callback.
|
|
94
|
+
*/
|
|
95
|
+
function waitForOAuthCallback() {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const server = http.createServer((req, res) => {
|
|
98
|
+
const parsed = url.parse(req.url, true);
|
|
99
|
+
|
|
100
|
+
if (parsed.pathname === '/callback') {
|
|
101
|
+
const code = parsed.query.code;
|
|
102
|
+
const error = parsed.query.error;
|
|
103
|
+
|
|
104
|
+
if (error) {
|
|
105
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
106
|
+
res.end('<html><body><h2>Authentication failed</h2><p>You can close this window.</p></body></html>');
|
|
107
|
+
server.close();
|
|
108
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (code) {
|
|
113
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
114
|
+
res.end('<html><body><h2>Authentication successful!</h2><p>You can close this window and return to the terminal.</p></body></html>');
|
|
115
|
+
server.close();
|
|
116
|
+
resolve(code);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
121
|
+
res.end('Missing authorization code.');
|
|
122
|
+
} else {
|
|
123
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
124
|
+
res.end('Not found.');
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Set a timeout so the server doesn't hang forever
|
|
129
|
+
const timeout = setTimeout(() => {
|
|
130
|
+
server.close();
|
|
131
|
+
reject(new Error('OAuth callback timed out. Please try again.'));
|
|
132
|
+
}, OAUTH_TIMEOUT_MS);
|
|
133
|
+
|
|
134
|
+
server.on('close', () => {
|
|
135
|
+
clearTimeout(timeout);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
server.listen(OAUTH_PORT, () => {
|
|
139
|
+
// Server is ready; the caller will open the browser
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Handle OAuth login flow for a service (e.g., Google).
|
|
146
|
+
* Opens the browser for consent, waits for the callback, exchanges the
|
|
147
|
+
* authorization code for tokens, and saves them to disk.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} service - The service name.
|
|
150
|
+
* @param {object} serviceConfig - The service config from AUTH_SERVICES.
|
|
151
|
+
* @param {object} args - Parsed CLI arguments.
|
|
152
|
+
* @param {object} context - CLI context (output, prompt, errors).
|
|
153
|
+
*/
|
|
154
|
+
async function handleOAuthLogin(service, serviceConfig, args, context) {
|
|
155
|
+
// Read client credentials
|
|
156
|
+
const clientFile = path.join(CLIENTS_DIR, serviceConfig.clientFile);
|
|
157
|
+
if (!fs.existsSync(clientFile)) {
|
|
158
|
+
context.output.info(`Client credentials not found: ${clientFile}`);
|
|
159
|
+
context.output.info('');
|
|
160
|
+
context.output.info('To set up OAuth for this service:');
|
|
161
|
+
context.output.info(` 1. Create a client credentials file at ${clientFile}`);
|
|
162
|
+
context.output.info(' 2. Include "clientId" and "clientSecret" fields');
|
|
163
|
+
context.output.info(' 3. Run this command again');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let clientCreds;
|
|
168
|
+
try {
|
|
169
|
+
clientCreds = JSON.parse(fs.readFileSync(clientFile, 'utf8'));
|
|
170
|
+
} catch {
|
|
171
|
+
context.errors.throwError(500, `Invalid client credentials file: ${clientFile}`, 'auth');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!clientCreds.clientId || !clientCreds.clientSecret) {
|
|
176
|
+
context.errors.throwError(400, 'Client credentials file must contain "clientId" and "clientSecret".', 'auth');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Build scopes: start with defaults, add any from --scopes flag
|
|
181
|
+
const scopes = [...serviceConfig.defaultScopes];
|
|
182
|
+
if (args.flags.scopes) {
|
|
183
|
+
const extra = args.flags.scopes.split(',').map(s => s.trim()).filter(Boolean);
|
|
184
|
+
for (const scope of extra) {
|
|
185
|
+
if (!scopes.includes(scope)) {
|
|
186
|
+
scopes.push(scope);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Determine redirect URI (use client file value if set, otherwise default)
|
|
192
|
+
const redirectUri = clientCreds.redirectUri || OAUTH_REDIRECT_URI;
|
|
193
|
+
|
|
194
|
+
// Build authorization URL
|
|
195
|
+
const authParams = new URLSearchParams({
|
|
196
|
+
client_id: clientCreds.clientId,
|
|
197
|
+
redirect_uri: redirectUri,
|
|
198
|
+
response_type: 'code',
|
|
199
|
+
scope: scopes.join(' '),
|
|
200
|
+
access_type: 'offline',
|
|
201
|
+
prompt: 'consent'
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const authUrl = `${serviceConfig.authUrl}?${authParams.toString()}`;
|
|
205
|
+
|
|
206
|
+
context.output.info('Opening browser for authentication...');
|
|
207
|
+
context.output.info('If the browser does not open, visit this URL:');
|
|
208
|
+
context.output.info('');
|
|
209
|
+
context.output.info(` ${authUrl}`);
|
|
210
|
+
context.output.info('');
|
|
211
|
+
|
|
212
|
+
// Start the callback server and open the browser
|
|
213
|
+
const callbackPromise = waitForOAuthCallback();
|
|
214
|
+
await openBrowser(authUrl);
|
|
215
|
+
|
|
216
|
+
let code;
|
|
217
|
+
try {
|
|
218
|
+
code = await callbackPromise;
|
|
219
|
+
} catch (err) {
|
|
220
|
+
context.errors.throwError(500, err.message, 'auth');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Exchange authorization code for tokens
|
|
225
|
+
context.output.info('Exchanging authorization code for tokens...');
|
|
226
|
+
|
|
227
|
+
let tokenResponse;
|
|
228
|
+
try {
|
|
229
|
+
tokenResponse = await httpsPost(serviceConfig.tokenUrl, {
|
|
230
|
+
code: code,
|
|
231
|
+
client_id: clientCreds.clientId,
|
|
232
|
+
client_secret: clientCreds.clientSecret,
|
|
233
|
+
redirect_uri: redirectUri,
|
|
234
|
+
grant_type: 'authorization_code'
|
|
235
|
+
});
|
|
236
|
+
} catch (err) {
|
|
237
|
+
context.errors.throwError(500, `Token exchange failed: ${err.message}`, 'auth');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let tokenData;
|
|
242
|
+
try {
|
|
243
|
+
tokenData = JSON.parse(tokenResponse.body);
|
|
244
|
+
} catch {
|
|
245
|
+
context.errors.throwError(500, 'Failed to parse token response.', 'auth');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (tokenData.error) {
|
|
250
|
+
context.errors.throwError(500, `Token error: ${tokenData.error_description || tokenData.error}`, 'auth');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Calculate expiry time
|
|
255
|
+
const now = new Date();
|
|
256
|
+
const expiresAt = tokenData.expires_in
|
|
257
|
+
? new Date(now.getTime() + tokenData.expires_in * 1000).toISOString()
|
|
258
|
+
: null;
|
|
259
|
+
|
|
260
|
+
// Save tokens
|
|
261
|
+
const credential = {
|
|
262
|
+
type: 'oauth',
|
|
263
|
+
accessToken: tokenData.access_token,
|
|
264
|
+
refreshToken: tokenData.refresh_token || null,
|
|
265
|
+
expiresAt: expiresAt,
|
|
266
|
+
scopes: scopes,
|
|
267
|
+
authenticatedAt: now.toISOString()
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
271
|
+
fs.writeFileSync(
|
|
272
|
+
path.join(AUTH_DIR, `${service}.json`),
|
|
273
|
+
JSON.stringify(credential, null, 2) + '\n'
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
context.output.info('');
|
|
277
|
+
context.output.info(`Successfully authenticated with ${service}.`);
|
|
278
|
+
context.output.info(`Credentials stored in ~/.devutils/auth/${service}.json`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Handle API key login flow for a service (e.g., AWS, Cloudflare).
|
|
283
|
+
* Prompts the user for each required field and saves them to disk.
|
|
284
|
+
*
|
|
285
|
+
* @param {string} service - The service name.
|
|
286
|
+
* @param {object} serviceConfig - The service config from AUTH_SERVICES.
|
|
287
|
+
* @param {object} context - CLI context (output, prompt, errors).
|
|
288
|
+
*/
|
|
289
|
+
async function handleApiKeyLogin(service, serviceConfig, context) {
|
|
290
|
+
const credentials = {};
|
|
291
|
+
|
|
292
|
+
for (let i = 0; i < serviceConfig.fields.length; i++) {
|
|
293
|
+
const field = serviceConfig.fields[i];
|
|
294
|
+
const label = serviceConfig.fieldLabels[i];
|
|
295
|
+
|
|
296
|
+
if (isSensitiveField(field)) {
|
|
297
|
+
credentials[field] = await context.prompt.password(label);
|
|
298
|
+
} else {
|
|
299
|
+
credentials[field] = await context.prompt.ask(label, '');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!credentials[field]) {
|
|
303
|
+
context.errors.throwError(400, `${label} is required.`, 'auth');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const credential = {
|
|
309
|
+
type: 'api-key',
|
|
310
|
+
credentials: credentials,
|
|
311
|
+
authenticatedAt: new Date().toISOString()
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
315
|
+
fs.writeFileSync(
|
|
316
|
+
path.join(AUTH_DIR, `${service}.json`),
|
|
317
|
+
JSON.stringify(credential, null, 2) + '\n'
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
context.output.info('');
|
|
321
|
+
context.output.info(`Successfully authenticated with ${service}.`);
|
|
322
|
+
context.output.info(`Credentials stored in ~/.devutils/auth/${service}.json`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Run the auth login command.
|
|
327
|
+
* Validates the service name, checks for existing credentials, and branches
|
|
328
|
+
* into the appropriate auth flow (OAuth or API key).
|
|
329
|
+
*
|
|
330
|
+
* @param {object} args - Parsed CLI arguments (positional, flags).
|
|
331
|
+
* @param {object} context - CLI context (output, prompt, errors).
|
|
332
|
+
*/
|
|
333
|
+
async function run(args, context) {
|
|
334
|
+
const service = args.positional[0];
|
|
335
|
+
|
|
336
|
+
if (!service) {
|
|
337
|
+
context.errors.throwError(400, 'Missing required argument: <service>. Example: dev auth login google', 'auth');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const serviceConfig = AUTH_SERVICES[service];
|
|
342
|
+
if (!serviceConfig) {
|
|
343
|
+
const supported = Object.keys(AUTH_SERVICES).join(', ');
|
|
344
|
+
context.errors.throwError(400, `Unknown service '${service}'. Supported services: ${supported}`, 'auth');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Check for existing credentials
|
|
349
|
+
const existing = readCredential(service);
|
|
350
|
+
if (existing) {
|
|
351
|
+
context.output.info(`Already authenticated with ${service}.`);
|
|
352
|
+
|
|
353
|
+
if (existing.type === 'api-key' && existing.credentials) {
|
|
354
|
+
// Show a masked preview of the existing credentials
|
|
355
|
+
const fields = Object.keys(existing.credentials);
|
|
356
|
+
for (const field of fields) {
|
|
357
|
+
const val = existing.credentials[field];
|
|
358
|
+
if (isSensitiveField(field)) {
|
|
359
|
+
context.output.info(` ${field}: ****`);
|
|
360
|
+
} else {
|
|
361
|
+
context.output.info(` ${field}: ${val}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (existing.authenticatedAt) {
|
|
367
|
+
context.output.info(` Authenticated: ${existing.authenticatedAt}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const reauth = await context.prompt.confirm('Do you want to re-authenticate?', false);
|
|
371
|
+
if (!reauth) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Branch by auth type
|
|
377
|
+
if (serviceConfig.type === 'oauth') {
|
|
378
|
+
await handleOAuthLogin(service, serviceConfig, args, context);
|
|
379
|
+
} else if (serviceConfig.type === 'api-key') {
|
|
380
|
+
await handleApiKeyLogin(service, serviceConfig, context);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = { meta, run };
|