Akamai Krypton CLI and SSH Agent (v2)

Related tags

Command-line akr
Overview

Akamai "Krypton" FIDO2 SSH Agent and CLI

The akr command line utility is Akamai's "Krypton" SSH Agent, the successor to kr which works exclusively with the Akamai MFA Authenticator for iOS and Android. Akr enables your smart phone to become a "push-based" FIDO2 authenticator for SSH authentication.

akr enables SSH to authenticate with a FIDO2 key stored in the Akamai MFA Authenticator app (iOS + Android).

akr runs as an SSH agent: when you run ssh [user@server], SSH asks the agent for a FIDO2 private key signature operation. This request is routed to a paired mobile phone (running the Akamai MFA app), where the user decides whether to allow the operation or not. If allowed, the phone simply sends the signature back to the agent. Private keys never leaves the phone.

⚠️ akr is currently in early-preview mode! Please contact us with any issues you find or feature suggestions.

Getting Started

First run

  1. First, run akr setup to create configurations and start the agent
  2. Next, pair your device: run akr pair
  3. Scan the QR code with the Akamai MFA app
  4. Run akr generate --name mykey to generate your first SSH key in Akamai MFA. This will output your SSH public key.
  5. Add your public key to a server or github.com

Verify everything works

To verify whether your Akamai MFA FIDO2 key works, try the following:

$ ssh ssh.demo.krypt.co -p 5000

If everything works correctly, you should see something like this:

Hello John!

You have successfully authenticated to the Akamai MFA SSH FIDO2 test server! 

Overview of Commands

Usage:
akr [options] [command] [arguments]

Options:

Syntax Description
-V, --version Display the version number for the akr client.
-h, --help Display usage information for akr client.

Commands:

Command Description Example
setup Setup the background daemon and updates ssh configuration akr setup --ssh-config-path
pair Pair with your phone/tablet akr pair
generate Generate a new SSH credential akr generate --name
unpair Unpair from your phone/tablet akr unpair
load Load public keys from the Akamai MFA app on your phone/tablet akr load
status Get pairing info from your phone/tablet akr status
check Health check of all the dep systems and system configs akr check

Requirements

  • macOS (10.15+) or Linux (64 Bit) (Debian, RHEL, and CentOS).
  • OpenSSH Client and Server 8.2+

Installation instructions

macOS (brew)

brew install akamai/mfa/akr

Debian

curl -SsL https://akamai.github.io/akr-pkg/debian/KEY.gpg | sudo apt-key add -
sudo curl -SsL -o /etc/apt/sources.list.d/akr.list https://akamai.github.io/akr-pkg/debian/akr.list
sudo apt update
sudo apt install akr

CentOS/RHEL

sudo vim /etc/yum.repos.d/akr.repo

[akr]
name=akr repository
baseurl=https://akamai.github.io/akr-pkg/rpm/
gpgcheck=0
enabled=1
sudo yum -y update
sudo yum -y install akr

Build from source

akr is built entirely with Rust. Ensure you have Rust installed (https://rustup.rs) and run cargo build.

Notes on Configuration

Running akr setup updates your SSH config file and installs the akr ssh-agent as a background service on your system. To see what akr configures, run akr setup --print-only.

The SSH config additions looks as follows:

# Begin Akamai MFA SSH Config
Host *
	IdentityAgent /Users/
   
    /.akr/akr-ssh-agent.sock
# End Akamai MFA SSH Config

   

This enables your native system SSH to communicate to the akr ssh-agent process over a unix socket.

Security Disclosure

For any security related questions, please contact our security team. Please disclose any issues responsibly using our Akamai Security GPG Public Key and send communications to [email protected].

License

Copyright (c) 2021, Akamai Technologies. All rights reserved.

Comments
  • Unable to pair to MFA app

    Unable to pair to MFA app

    akr seems to generate a valid QR code, but it is not being picked up by the Akamai MFA app.

    $ akr pair
    ...
    Scan the above QR code to pair your device...
    Error: Response was never received
    
    $ akr --version
    akr - Akamai Krypton 1.0
    

    on x86_64 Arch Linux

    Android app is Akamai MFA v1.10.0 (45)

    Scanning the QR code with a third party scanner app I get a URL like https://mfa.akamai.com/#redacted

    bug 
    opened by tg90nor 13
  • "$ ssh ssh.demo.krypt.co -p 5000" is not working

    I'm trying to get started akr, and when I try to execute $ ssh ssh.demo.krypt.co -p 5000, it notifies me with some warning messages. I have configured kr(not akr) before and my ~/.ssh/config looks like this. I'm assuming I should remove all of the settings that kr has created before I execute akr.

    # Added by Krypton
    Host *
    	IdentityAgent ~/.kr/krd-agent.sock
    	ProxyCommand /usr/local/bin/krssh %h %p
    	IdentityFile ~/.ssh/id_krypton
    	IdentityFile ~/.ssh/id_ed25519
    	IdentityFile ~/.ssh/id_rsa
    	IdentityFile ~/.ssh/id_ecdsa
    	IdentityFile ~/.ssh/id_dsa
    
    # Begin Akamai MFA SSH Config
    Host *
    	IdentityAgent /Users/hyamaguc/.akr/akr-ssh-agent.sock
    # End Akamai MFA SSH Config
    
    
    $ ssh ssh.demo.krypt.co -p 5000
    Krypton ▶ Requesting SSH authentication from phone
    Krypton ▶ Workstation not yet paired. Please run "kr pair" and scan the QRCode with the Krypton mobile app.
    sign_and_send_pubkey: signing failed: agent refused operation
    no such identity: /Users/hyamaguc/.ssh/id_ed25519: No such file or directory
    
    
    
    ░█████╗░██╗░░██╗░█████╗░███╗░░░███╗░█████╗░██╗  ███╗░░░███╗███████╗░█████╗░
    ██╔══██╗██║░██╔╝██╔══██╗████╗░████║██╔══██╗██║  ████╗░████║██╔════╝██╔══██╗
    ███████║█████═╝░███████║██╔████╔██║███████║██║  ██╔████╔██║█████╗░░███████║
    ██╔══██║██╔═██╗░██╔══██║██║╚██╔╝██║██╔══██║██║  ██║╚██╔╝██║██╔══╝░░██╔══██║
    ██║░░██║██║░╚██╗██║░░██║██║░╚═╝░██║██║░░██║██║  ██║░╚═╝░██║██║░░░░░██║░░██║
    ╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░░░░╚═╝╚═╝░░╚═╝╚═╝  ╚═╝░░░░░╚═╝╚═╝░░░░░╚═╝░░╚═╝
    
    ██╗░░██╗██████╗░██╗░░░██╗██████╗░████████╗░█████╗░███╗░░██╗
    ██║░██╔╝██╔══██╗╚██╗░██╔╝██╔══██╗╚══██╔══╝██╔══██╗████╗░██║
    █████═╝░██████╔╝░╚████╔╝░██████╔╝░░░██║░░░██║░░██║██╔██╗██║
    ██╔═██╗░██╔══██╗░░╚██╔╝░░██╔═══╝░░░░██║░░░██║░░██║██║╚████║
    ██║░╚██╗██║░░██║░░░██║░░░██║░░░░░░░░██║░░░╚█████╔╝██║░╚███║
    ╚═╝░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░░░░░░╚═╝░░░░╚════╝░╚═╝░░╚══╝
    
    
    
    It looks like you are not using an Akamai MFA FIDO2 SSH key. Make sure kr is installed on this workstation and paired with Akamai MFA.
    Check out https://mfa.akamai.com/help for more information.
    
    Connection to ssh.demo.krypt.co closed.
    
    opened by hisashiyamaguchi 4
  • Bump hyper from 0.14.8 to 0.14.19

    Bump hyper from 0.14.8 to 0.14.19

    Bumps hyper from 0.14.8 to 0.14.19.

    Release notes

    Sourced from hyper's releases.

    v0.14.19

    Bug Fixes

    • http1: fix preserving header case without enabling ffi (#2820) (6a35c175)
    • server: don't add implicit content-length to HEAD responses (#2836) (67b73138)

    Features

    Breaking Changes

    • ffi (unstable):
      • hyper_clientconn_options_new no longer sets the http1_preserve_header_case connection option by default. Users should now call hyper_clientconn_options_set_preserve_header_case if they desire that functionality. (78de8914)

    New Contributors ❤️

    v0.14.18

    Bug Fixes

    • ffi: don't build C libraries by default (1c663706)

    Features

    • client: add HttpInfo::local_addr() method (055b4e7e, closes #2767)

    New Contributors

    v0.14.17

    Bug Fixes

    ... (truncated)

    Changelog

    Sourced from hyper's changelog.

    v0.14.19 (2022-05-27)

    Bug Fixes

    • http1: fix preserving header case without enabling ffi (#2820) (6a35c175)
    • server: don't add implicit content-length to HEAD responses (#2836) (67b73138)

    Features

    Breaking Changes

    • ffi (unstable):
      • hyper_clientconn_options_new no longer sets the http1_preserve_header_case connection option by default. Users should now call hyper_clientconn_options_set_preserve_header_case if they desire that functionality. (78de8914)

    v0.14.18 (2022-03-22)

    Bug Fixes

    • ffi: don't build C libraries by default (1c663706)

    Features

    • client: add HttpInfo::local_addr() method (055b4e7e, closes #2767)

    v0.14.17 (2022-02-10)

    Bug Fixes

    Features

    ... (truncated)

    Commits
    • f8e2a83 v0.14.19
    • a929df8 docs(various): fix typos in VISION and ROADMAP (#2875)
    • 3a755a6 chore(lib): update tokio-util to 0.7 (#2762)
    • 4678be9 docs(contrib): add guide for Triaging Issues
    • 775fac1 docs(lib): propose 1.0 roadmap (#2806)
    • a32658c feat(server): add Connection::http2_max_header_list_size option (#2828)
    • 67b7313 fix(server): don't add implicit content-length to HEAD responses (#2836)
    • faf24c6 refactor(http1): assorted code readability improvements in h1/conn.rs (#2817)
    • 6a35c17 fix(http1): fix preserving header case without enabling ffi (#2820)
    • 89598df docs(lib): fix some typos (#2818)
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • Configuring ssh key access to prompt only first time

    Configuring ssh key access to prompt only first time

    Greetings,

    Is there any way to configure akr to only prompt for ssh key access the first time, or with a configurable timeout? This would better approximate the workflow of adding a passphrase-protected key to my ssh-agent at the beginning of my workday.

    --cro

    opened by cro 1
  • Push notifications on linux and macOS

    Push notifications on linux and macOS

    we should still use this on linux, but on macOS should do something like invoke AppleScript or some notification command.

    Originally posted by @agrinman in https://github.com/akamai/akr/pull/1#r659871610

    opened by agrinman 1
  • switch to azure queues and enable akr to use local ssh keys

    switch to azure queues and enable akr to use local ssh keys

    This PR covers two features

    1. enable local RSA, ECDSA and ED25519 keys to work with akr.
    2. switch to azure queues.

    Feature 1 makes sure that if the user has any local ssh keys that he/she wants to work with akr, they can simple load the keys under ~/.ssh and akr will load those in the agent on startup. It supports RSA, ECDSA and ED25519 keys at the moment.

    enhancement feature-request 
    opened by nikhilty 0
  • Bump hyper from 0.14.8 to 0.14.22

    Bump hyper from 0.14.8 to 0.14.22

    Bumps hyper from 0.14.8 to 0.14.22.

    Release notes

    Sourced from hyper's releases.

    v0.14.22

    Bug Fixes

    • server: fix compile-time cfgs for TCP keepalive options (#3039) (e8765e0f, closes #3038)

    v0.14.21

    Bug Fixes

    • client: send an error back to client when dispatch misbehaves () (9fa36382, closes #2649)
    • http1: fix http1_header_read_timeout to use same future (#2891) (c5a14e7c)

    Features

    • http1: allow ignoring invalid header lines in requests (73dd4746)
    • server: add Server::tcp_keepalive_interval and Server::tcp_keepalive_retries (#2991) (287d7124)

    New Contributors

    v0.14.20

    Bug Fixes

    • http1: fix http1_header_read_timeout to use same future (#2891) (c5a14e7c)

    Features

    • ext: support non-canonical HTTP/1 reason phrases (#2792) (b2052a43)

    New Contributors

    v0.14.19

    Bug Fixes

    • http1: fix preserving header case without enabling ffi (#2820) (6a35c175)
    • server: don't add implicit content-length to HEAD responses (#2836) (67b73138)

    Features

    Breaking Changes

    ... (truncated)

    Changelog

    Sourced from hyper's changelog.

    v0.14.22 (2022-10-31)

    Bug Fixes

    • server: fix compile-time cfgs for TCP keepalive options (#3039) (e8765e0f, closes #3038)

    v0.14.21 (2022-10-31)

    Bug Fixes

    • client: send an error back to client when dispatch misbehaves () (9fa36382, closes #2649)
    • http1: fix http1_header_read_timeout to use same future (#2891) (c5a14e7c)

    Features

    • http1: allow ignoring invalid header lines in requests (73dd4746)
    • server: add Server::tcp_keepalive_interval and Server::tcp_keepalive_retries (#2991) (287d7124)

    v0.14.20 (2022-07-07)

    Bug Fixes

    • http1: fix http1_header_read_timeout to use same future (#2891) (c5a14e7c)

    Features

    • ext: support non-canonical HTTP/1 reason phrases (#2792) (b2052a43)

    v0.14.19 (2022-05-27)

    Bug Fixes

    • http1: fix preserving header case without enabling ffi (#2820) (6a35c175)
    • server: don't add implicit content-length to HEAD responses (#2836) (67b73138)

    Features

    ... (truncated)

    Commits
    • 04d637e v0.14.22
    • e8765e0 fix(server): fix compile-time cfgs for TCP keepalive options (#3039)
    • 9ad4055 v0.14.21
    • 9fa3638 fix(client): send an error back to client when dispatch misbehaves (fixes #2649)
    • 78e2c58 chore(http2): add a server feature-gate on some server-only method
    • 73dd474 feat(h1): allow ignoring invalid header lines in requests
    • 287d712 feat(server): add Server::tcp_keepalive_interval and `Server::tcp_keepalive...
    • 0ff6213 v0.14.20
    • 128bc7f chore(lib): bump MSRV to 1.56 (#2902)
    • c5a14e7 fix(http1): fix http1_header_read_timeout to use same future (#2891)
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • systemd service fails to start (code=exited, status=216/GROUP)

    systemd service fails to start (code=exited, status=216/GROUP)

    ❯ systemctl --user status akr
    × akr.service - akr
         Loaded: loaded (/home/qdl/.config/systemd/user/akr.service; enabled; preset: enabled)
         Active: failed (Result: exit-code) since Mon 2022-12-05 22:44:33 CET; 20s ago
       Duration: 211us
        Process: 66362 ExecStart=/usr/bin/akr start (code=exited, status=216/GROUP)
       Main PID: 66362 (code=exited, status=216/GROUP)
            CPU: 0
    
    Dez 05 22:44:33 arco3 systemd[710]: akr.service: Scheduled restart job, restart counter is at 5.
    Dez 05 22:44:33 arco3 systemd[710]: Stopped akr.
    Dez 05 22:44:33 arco3 systemd[710]: akr.service: Start request repeated too quickly.
    Dez 05 22:44:33 arco3 systemd[710]: akr.service: Failed with result 'exit-code'.
    Dez 05 22:44:33 arco3 systemd[710]: Failed to start akr.
    

    As stated on Stackexchange removing the line containing User=username from the service file fixes this.

    opened by qdlmcfresh 0
  • Support for ssh-add with non FIDO2 keys

    Support for ssh-add with non FIDO2 keys

    Would be great to have the option to add a non FIDO2-Key to the agent. eg. ssh-add path/to/privkey should add the key to the agent instead of returning success but not adding it with the message: add error: not a fido2 ssh keypair in ssh_agent.rs

    opened by qdlmcfresh 1
  • `Error: Response was never received`

    `Error: Response was never received`

    Recently Akamai MFA is not working for a few days in my environment. Is this happening only for me?

    phenomenon

    $ akr check
    Error: Response was never received
    $ ssh ssh.demo.krypt.co -p 5000
    kex_exchange_identification: read: Connection reset by peer
    Connection reset by 2a09:8280:1::3:f7 port 5000
    

    environment

    macOS

    $ akr --version
    akr - Akamai Krypton 1.0
    

    iOS

    Akamai MFA v1.6.0.7

    opened by acevif 0
  • Multi-device support

    Multi-device support

    Is it possible for the current Android/iOS app to be paired with more than one computer running akr? If not, this should be a feature.

    I used the old kr tool as another way to sync my key between desktops/laptops.

    feature-request 
    opened by viggy96 1
Releases(1.1.1)
Owner
Akamai
A collection of official Akamai tools, community projects, and other goodies.
Akamai
A small CLI wrapper for authenticating with SSH keys from Hashicorp Vault

vaultssh A small CLI wrapper for authenticating with SSH keys from Hashicorp Vault vaultssh is a small CLI wrapper for automatically fetching and usin

Joshua Gilman 50 Dec 10, 2022
Multi-threaded CLI torrent scraper for displaying searched for magnet links; tailored for use with plex & ssh.

magnetfinder Multi-threaded CLI torrent aggregator; scrapes torrent results from multiple websites and delivers them into a table in your terminal! Su

Ryan 59 Dec 10, 2022
Multi-threaded CLI torrent scraper for displaying searched for magnet links; tailored for use with plex & ssh.

magnetfinder Multi-threaded CLI torrent aggregator; scrapes torrent results from multiple websites and delivers them into a table in your terminal! Su

null 59 Dec 10, 2022
Cedar-agent is the easiest way to deploy and run Cedar

Cedar Agent What is Cedar-Agent? Cedar-Agent is an HTTP server designed to efficiently manage a policy store and a data store. It provides a seamless

Permit.io 30 May 26, 2023
A customizable MCTS planner with advanced featured tailored to multi-agent simulations and emergent narratives.

NPC engine Core:  Utils:  © 2020-2022 ETH Zurich and other contributors. See AUTHORS.txt for more details. A customizable Monte Carlo Tree Search (MCT

Game Technology Center, ETH Zurich 30 Jun 6, 2023
TinyTodo is a Cedar Agent example, with a server in Rust and client in python

TinyTodo - OPAL and Cedar Agent Demo TinyTodo is a simple application for managing task lists. It uses OPAL and Cedar Agent to control who has access

Permit.io 4 Aug 9, 2023
Figma Agent for Linux (a.k.a. Font Helper)

Figma Agent for Linux (a.k.a. Font Helper)

Neetly 32 Dec 25, 2022
1 library and 2 binary crates to run SSH/SCP commands on a "mass" of hosts in parallel

massh 1 library and 2 binary crates to run SSH/SCP commands on a "mass" of hosts in parallel. The binary crates are CLI and GUI "frontends" for the li

null 2 Oct 16, 2022
🦴🖥️ // An ssh manager

?? ??️ ssh-man ssh-man is an SSH manager. I often forget the credentials and IPs to servers I frequently SSH into so Im making ssh-man to help me reme

mellowmarshe 4 Dec 9, 2021
FastSSH is a TUI that allows you to quickly connect to your services by navigating through your SSH config.

Connect quickly to your services ?? FastSSH is a TUI that allows you to quickly connect to your services by navigating through your SSH config. Instal

Julien 85 Dec 14, 2022
A CLI tool to get help with CLI tools 🐙

A CLI tool to get help with CLI tools ?? halp aims to help find the correct arguments for command-line tools by checking the predefined list of common

Orhun Parmaksız 566 Apr 16, 2023
AniTUI is a CLI (and in the future a TUI) app for searching and wathching anime in MPV.

AniTUI is a CLI (and in the future a TUI) app for searching and wathching anime in MPV. This is a Rust rewrite (quite literally a rewrite) of Pystardu

null 7 Oct 31, 2022
CLI utility that screencaptures your Linux desktop and streams it to Kodi via UPNP/DLNA and RTSP

desktopcast Desktopcast is a little CLI application that allows you to cast your Linux desktop to any UPNP/DLNA device capable of the AVTransfer servi

Markus Ebner 25 Apr 16, 2023
This CLI utility facilitates effortless manipulation and exploration of TOML, YAML, JSON and RON files.

???????? This CLI utility facilitates effortless manipulation and exploration of TOML, YAML, JSON and RON files.

Moe 3 Apr 26, 2023
A CLI tool for CIs and build scripts, making file system based caching easy and correct (locking, eviction, etc.)

FS Dir Cache A CLI tool for CIs and build scripts, making file system based caching easy and correct (locking, eviction, etc.) When working on build s

Dawid Ciężarkiewicz 5 Aug 29, 2023
CLI calculator app and library

calc Yet another CLI calculator. Inspired by the excellent https://github.com/alfredxing/calc. Installation With a Rust toolchain in place: cargo inst

Peter Goodspeed-Niklaus 34 Nov 13, 2022
CLI Tool for tagging and organizing files by tags.

wutag ?? ??️ CLI tool for tagging and organizing files by tags. Install If you use arch Linux and have AUR repositories set up you can use your favour

Wojciech Kępka 32 Dec 6, 2022
CLI tool to bake your fresh and hot MD files

At least once in your Rust dev lifetime you wanted to make sure all code examples in your markdown files are up-to-date, correct and code is formated, but you couldn't make that done with already existing tools - fear not!

Patryk Budzyński 39 May 8, 2021
CLI search and replace | Space Age seD

SAD! Space Age seD What does it do? Basically sad is a Batch File Edit tool. It will show you a really nice diff of proposed changes before you commit

i love my dog 986 Dec 29, 2022