Repository: edgedb/edgedb-cli
Pull request #746 (2022-06-13 23:42:36)

Change deprecated CONFIGURE SYSTEM to CONFIGURE INSTANCE


Patch:
diff --git a/edgedb-cli-derive/src/into_args.rs b/edgedb-cli-derive/src/into_args.rs
index bc8e1fe..9d4c481 100644
--- a/edgedb-cli-derive/src/into_args.rs
+++ b/edgedb-cli-derive/src/into_args.rs
@@ -75,7 +75,7 @@ pub fn structure(s: &types::Struct) -> TokenStream {
                 FromStr | FromOsStr | TryFromStr | TryFromOsStr => {
                     if field.optional {
                         args.push(quote! {
-                            if let Some(value) = self.#ident {
+                            if let Some(value) = &self.#ident {
                                 process.args([value]);
                             }
                         });
diff --git a/src/cloud/ops.rs b/src/cloud/ops.rs
index 1f696be..0ba0a07 100644
--- a/src/cloud/ops.rs
+++ b/src/cloud/ops.rs
@@ -11,9 +11,11 @@ use edgedb_client::Builder;
 
 use crate::cloud::auth;
 use crate::cloud::client::CloudClient;
+use crate::commands::ExitCode;
 use crate::credentials;
 use crate::options::CloudOptions;
-use crate::print::{self, Highlight};
+use crate::portable::local::is_valid_name;
+use crate::print::{self, echo, err_marker, Highlight};
 use crate::question;
 use crate::table::{self, Cell, Row, Table};
 
@@ -153,6 +155,27 @@ pub async fn create_cloud_instance(
     Ok(())
 }
 
+fn ask_name() -> anyhow::Result<String> {
+    let instances = credentials::all_instance_names()?;
+    loop {
+        let name = question::String::new(
+            "Specify a name for the new instance"
+        ).ask()?;
+        if !is_valid_name(&name) {
+            echo!(err_marker(),
+                "Instance name must be a valid identifier, \
+                 (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)");
+            continue;
+        }
+        if instances.contains(&name) {
+            echo!(err_marker(),
+                "Instance", name.emphasize(), "already exists.");
+            continue;
+        }
+        return Ok(name);
+    }
+}
+
 pub async fn create(
     cmd: &crate::portable::options::Create,
     opts: &crate::options::Options,
@@ -171,8 +194,17 @@ pub async fn create(
         // TODO: use default organization
         orgs[0].id.clone()
     };
+    let name = if let Some(name) = &cmd.name {
+        name.to_owned()
+    } else if cmd.non_interactive {
+        echo!(err_marker(), "Instance name is required \
+                             in non-interactive mode");
+        return Err(ExitCode::new(2).into());
+    } else {
+        ask_name()?
+    };
     let instance = CloudInstanceCreate {
-        name: cmd.name.clone(),
+        name: name.clone(),
         org: org_id
         // version: Some(format!("{}", version.display())),
         // default_database: Some(cmd.default_database.clone()),
@@ -181,11 +213,11 @@ pub async fn create(
     create_cloud_instance(&client, &instance).await?;
     print::echo!(
         "EdgeDB Cloud instance",
-        cmd.name.emphasize(),
+        name.emphasize(),
         "is up and running."
     );
     print::echo!("To connect to the instance run:");
-    print::echo!("  edgedb -I", cmd.name);
+    print::echo!("  edgedb -I", name);
     Ok(())
 }
 
@@ -196,7 +228,7 @@ pub async fn link(
     let mut client = CloudClient::new(&opts.cloud_options)?;
     if cmd.non_interactive {
         if let Some(name) = &cmd.name {
-            if !crate::portable::local::is_valid_name(name) {
+            if !is_valid_name(name) {
                 print::error(
                     "Instance name must be a valid identifier, \
                              (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)",
@@ -236,7 +268,7 @@ pub async fn link(
                     "Input the name of the EdgeDB Cloud instance to connect to",
                 )
                 .ask()?;
-                if !crate::portable::local::is_valid_name(&name) {
+                if !is_valid_name(&name) {
                     print::error(
                         "Instance name must be a valid identifier, \
                                  (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)",
@@ -272,7 +304,7 @@ pub async fn link(
                     .default(&cloud_name)
                     .ask()?
             };
-            if !crate::portable::local::is_valid_name(&name) {
+            if !is_valid_name(&name) {
                 print::error(
                     "Instance name must be a valid identifier, \
                          (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)",
diff --git a/src/commands/configure.rs b/src/commands/configure.rs
index 62cd0e7..4f67501 100644
--- a/src/commands/configure.rs
+++ b/src/commands/configure.rs
@@ -10,7 +10,7 @@ async fn set_string(cli: &mut Connection, name: &str, value: &ConfigStr)
     -> Result<(), anyhow::Error>
 {
     print::completion(&cli.execute(
-        &format!("CONFIGURE SYSTEM SET {} := {}",
+        &format!("CONFIGURE INSTANCE SET {} := {}",
             name, quote_string(&value.value))
     ).await?);
     Ok(())
@@ -20,7 +20,7 @@ async fn set_duration(cli: &mut Connection, name: &str, value: &ConfigStr)
     -> Result<(), anyhow::Error>
 {
     print::completion(&cli.execute(
-        &format!("CONFIGURE SYSTEM SET {} := <duration>{}",
+        &format!("CONFIGURE INSTANCE SET {} := <duration>{}",
             name, quote_string(&value.value))
     ).await?);
     Ok(())
@@ -53,7 +53,7 @@ pub async fn configure(cli: &mut Connection, _options: &Options,
                     "comment := {}", quote_string(comment_text)))
             }
             print::completion(&cli.execute(&format!(r###"
-                CONFIGURE SYSTEM INSERT Auth {{
+                CONFIGURE INSTANCE INSERT Auth {{
                     {}
                 }}
                 "###,
@@ -63,7 +63,7 @@ pub async fn configure(cli: &mut Connection, _options: &Options,
         }
         C::Set(Set { parameter: S::ListenAddresses(param) }) => {
             print::completion(&cli.execute(
-                &format!("CONFIGURE SYSTEM SET listen_addresses := {{{}}}",
+                &format!("CONFIGURE INSTANCE SET listen_addresses := {{{}}}",
                 param.address.iter().map(|x| quote_string(x))
                     .collect::<Vec<_>>().join(", "))
             ).await?);
@@ -71,7 +71,7 @@ pub async fn configure(cli: &mut Connection, _options: &Options,
         }
         C::Set(Set { parameter: S::ListenPort(param) }) => {
             print::completion(&cli.execute(
-                &format!("CONFIGURE SYSTEM SET listen_port := {}", param.port)
+                &format!("CONFIGURE INSTANCE SET listen_port := {}", param.port)
             ).await?);
             Ok(())
         }
@@ -124,7 +124,7 @@ pub async fn configure(cli: &mut Connection, _options: &Options,
                 C::ApplyAccessPolicies => "apply_access_policies",
             };
             print::completion(&cli.execute(
-                &format!("CONFIGURE SYSTEM RESET {}", name)
+                &format!("CONFIGURE INSTANCE RESET {}", name)
             ).await?);
             Ok(())
         }
diff --git a/src/migrations/create.rs b/src/migrations/create.rs
index 16c4d1f..161ab64 100644
--- a/src/migrations/create.rs
+++ b/src/migrations/create.rs
@@ -13,12 +13,13 @@ use edgedb_client::errors::{Error, QueryError};
 use edgedb_client::errors::{ErrorKind, ClientConnectionEosError};
 use edgedb_derive::Queryable;
 use edgedb_protocol::queryable::Queryable;
-use edgeql_parser::hash::Hasher;
 use edgeql_parser::expr;
-use edgeql_parser::tokenizer::{TokenStream, Kind as TokenKind};
+use edgeql_parser::hash::Hasher;
 use edgeql_parser::schema_file::validate;
+use edgeql_parser::tokenizer::{TokenStream, Kind as TokenKind};
 use fn_error_context::context;
 use immutable_chunkmap::set::SetM as Set;
+use linked_hash_map::LinkedHashMap;
 use rustyline::error::ReadlineError;
 use serde::Deserialize;
 
@@ -28,7 +29,7 @@ use crate::commands::{Options, ExitCode};
 use crate::error_display::print_query_error;
 use crate::highlight;
 use crate::migrations::context::Context;
-use crate::migrations::migration;
+use crate::migrations::migration::{self, MigrationFile};
 use crate::migrations::print_error::print_migration_error;
 use crate::migrations::prompt;
 use crate::migrations::timeout;
@@ -204,6 +205,41 @@ pub async fn execute_start_migration(ctx: &Context, cli: &mut Connection)
     }
 }
 
+async fn first_migration(cli: &mut Connection, ctx: &Context,
+                         options: &CreateMigration)
+    -> anyhow::Result<()>
+{
+    execute_start_migration(&ctx, cli).await?;
+    let exec = async {
+        execute(cli, "POPULATE MIGRATION").await?;
+        let descr = query_row::<CurrentMigration>(cli,
+            "DESCRIBE CURRENT MIGRATION AS JSON"
+        ).await?;
+        if descr.parent != "initial" {
+            // We know there are zero revisions in the filesystem
+            anyhow::bail!("There is no database revision {} \
+                in the filesystem. Consider updating sources.",
+                descr.parent);
+        }
+        if !descr.complete {
+            return Err(bug::error("First migration populated is not complete"));
+        }
+        if descr.confirmed.is_empty() && !options.allow_empty {
+            print::warn("No schema changes detected.");
+            return Err(ExitCode::new(4))?;
+        }
+        write_migration(ctx, &descr, 1, false).await?;
+        Ok(())
+    }.await;
+    if cli.is_consistent() {
+        let abort = cli.execute("ABORT MIGRATION").await
+            .map(|_| ()).map_err(|e| e.into());
+        return exec.and(abort)
+    } else {
+        return exec
+    }
+}
+
 async fn run_non_interactive(ctx: &Context, cli: &mut Connection, index: u64,
     options: &CreateMigration)
     -> anyhow::Result<()>
@@ -516,53 +552,65 @@ async fn _write_migration(descr: &CurrentMigration, filepath: &Path,
 
 pub async fn create(cli: &mut Connection, _options: &Options,
     create: &CreateMigration)
-    -> Result<(), anyhow::Error>
+    -> anyhow::Result<()>
 {
     let ctx = Context::from_config(&create.cfg);
     let migrations = migration::read_all(&ctx, true).await?;
 
     let old_timeout = timeout::inhibit_for_transaction(cli).await?;
+    // This decision must be done early on because of the bug in EdgeDB:
+    //   https://github.com/edgedb/edgedb/issues/3958
+    let exec = if migrations.len() == 0 {
+        first_migration(cli, &ctx, create).await
+    } else {
+        normal_migration(cli, &ctx, migrations, create).await
+    };
+    if cli.is_consistent() {
+        let timeout = timeout::restore_for_transaction(cli, old_timeout).await;
+        exec.and(timeout)
+    } else {
+        exec
+    }
+}
+
+async fn normal_migration(cli: &mut Connection, ctx: &Context,
+                          migrations: LinkedHashMap<String, MigrationFile>,
+                          create: &CreateMigration)
+    -> anyhow::Result<()>
+{
+    execute_start_migration(&ctx, cli).await?;
     let exec = async {
-        execute_start_migration(&ctx, cli).await?;
-        let exec = async {
-            let descr = query_row::<CurrentMigration>(cli,
-                "DESCRIBE CURRENT MIGRATION AS JSON",
-            ).await?;
-            let db_migration = if descr.parent == "initial" {
-                None
-            } else {
-                Some(&descr.parent)
-            };
-            if db_migration != migrations.keys().last() {
-                anyhow::bail!("Database must be updated to the last migration \
-                    on the filesystem for `migration create`. Run:\n  \
-                    edgedb migrate");
-            }
+        let descr = query_row::<CurrentMigration>(cli,
+            "DESCRIBE CURRENT MIGRATION AS JSON",
+        ).await?;
+        let db_migration = if descr.parent == "initial" {
+            None
+        } else {
+            Some(&descr.parent)
+        };
+        if db_migration != migrations.keys().last() {
+            anyhow::bail!("Database must be updated to the last migration \
+                on the filesystem for `migration create`. Run:\n  \
+                edgedb migrate");
+        }
 
-            let num_migrations = migrations.len() as u64 +1;
-            if create.non_interactive {
-                run_non_interactive(&ctx, cli, num_migrations, &create).await
-            } else {
-                if create.allow_unsafe {
-                    log::warn!("The `--allow-unsafe` flag is unused \
-                                in interactive mode");
-                }
-                run_interactive(&ctx, cli, num_migrations, &create).await
-            }
-        }.await;
-        if cli.is_consistent() {
-            let abort = cli.execute("ABORT MIGRATION").await
-                .map(|_| ()).map_err(|e| e.into());
-            exec.and(abort)
+        let num_migrations = migrations.len() as u64 +1;
+        if create.non_interactive {
+            run_non_interactive(&ctx, cli, num_migrations, &create).await
         } else {
-            exec
+            if create.allow_unsafe {
+                log::warn!("The `--allow-unsafe` flag is unused \
+                            in interactive mode");
+            }
+            run_interactive(&ctx, cli, num_migrations, &create).await
         }
     }.await;
     if cli.is_consistent() {
-        let timeout = timeout::restore_for_transaction(cli, old_timeout).await;
-        exec.and(timeout)
+        let abort = cli.execute("ABORT MIGRATION").await
+            .map(|_| ()).map_err(|e| e.into());
+        return exec.and(abort)
     } else {
-        exec
+        return exec
     }
 }
 
diff --git a/src/portable/control.rs b/src/portable/control.rs
index 20b93f4..1697256 100644
--- a/src/portable/control.rs
+++ b/src/portable/control.rs
@@ -10,7 +10,7 @@ use crate::credentials;
 use crate::hint::HintExt;
 use crate::platform::current_exe;
 use crate::portable::local::{InstanceInfo, runstate_dir, open_lock, lock_file};
-use crate::portable::options::{Start, Stop, Restart, Logs};
+use crate::portable::options::{Start, Stop, Restart, Logs, instance_arg};
 use crate::portable::ver;
 use crate::portable::{windows, linux, macos};
 use crate::process;
@@ -208,7 +208,8 @@ fn set_inheritable(file: &impl std::os::unix::io::AsRawFd)
 }
 
 pub fn start(options: &Start) -> anyhow::Result<()> {
-    let meta = InstanceInfo::read(&options.name)?;
+    let name = instance_arg(&options.name, &options.instance)?;
+    let meta = InstanceInfo::read(name)?;
     ensure_runstate_dir(&meta.name)?;
     if options.foreground || options.managed_by.is_some() {
         let lock_path = lock_file(&meta.name)?;
@@ -231,7 +232,7 @@ pub fn start(options: &Start) -> anyhow::Result<()> {
                 log::warn!("Process is already running by {}. \
                             Stopping...", locked_by.escape_default());
                 needs_restart = true;
-                do_stop(&options.name)
+                do_stop(name)
                     .context("cannot stop service")?;
             } else {
                 anyhow::bail!("Process is already running by {}. \
@@ -376,7 +377,8 @@ pub fn do_stop(name: &str) -> anyhow::Result<()> {
 }
 
 pub fn stop(options: &Stop) -> anyhow::Result<()> {
-    let meta = InstanceInfo::read(&options.name)?;
+    let name = instance_arg(&options.name, &options.instance)?;
+    let meta = InstanceInfo::read(name)?;
     do_stop(&meta.name)
 }
 
@@ -470,7 +472,8 @@ pub fn do_restart(inst: &InstanceInfo) -> anyhow::Result<()> {
 }
 
 pub fn restart(options: &Restart) -> anyhow::Result<()> {
-    let meta = InstanceInfo::read(&options.name)?;
+    let name = instance_arg(&options.name, &options.instance)?;
+    let meta = InstanceInfo::read(name)?;
     do_restart(&meta)
 }
 
diff --git a/src/portable/create.rs b/src/portable/create.rs
index c32a953..c241cad 100644
--- a/src/portable/create.rs
+++ b/src/portable/create.rs
@@ -11,18 +11,42 @@ use crate::platform;
 use crate::portable::control::{self_signed_arg, ensure_runstate_dir};
 use crate::portable::exit_codes;
 use crate::portable::install;
-use crate::portable::local::{Paths, InstanceInfo, write_json, allocate_port};
+use crate::portable::local::{Paths, InstanceInfo};
+use crate::portable::local::{write_json, allocate_port, is_valid_name};
 use crate::portable::options::{Create, StartConf};
 use crate::portable::platform::optional_docker_check;
 use crate::portable::repository::{Query};
 use crate::portable::reset_password::{password_hash, generate_password};
 use crate::portable::{windows, linux, macos};
-use crate::print::{self, echo, Highlight};
+use crate::print::{self, echo, err_marker, Highlight};
 use crate::process;
+use crate::question;
 
 use edgedb_client::credentials::Credentials;
 
 
+fn ask_name() -> anyhow::Result<String> {
+    let instances = credentials::all_instance_names()?;
+    loop {
+        let name = question::String::new(
+            "Specify a name for the new instance"
+        ).ask()?;
+        if !is_valid_name(&name) {
+            echo!(err_marker(),
+                "Instance name must be a valid identifier, \
+                 (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)");
+            continue;
+        }
+        if instances.contains(&name) {
+            echo!(err_marker(),
+                "Instance", name.emphasize(), "already exists.");
+            continue;
+        }
+        return Ok(name);
+    }
+}
+
+
 pub fn create(options: &Create) -> anyhow::Result<()> {
     if optional_docker_check()? {
         print::error(
@@ -31,20 +55,30 @@ pub fn create(options: &Create) -> anyhow::Result<()> {
         return Err(ExitCode::new(exit_codes::DOCKER_CONTAINER))?;
     }
 
-    let paths = Paths::get(&options.name)?;
+    let name = if let Some(name) = &options.name {
+        name.to_owned()
+    } else if options.non_interactive {
+        echo!(err_marker(), "Instance name is required \
+                             in non-interactive mode");
+        return Err(ExitCode::new(2).into());
+    } else {
+        ask_name()?
+    };
+
+    let paths = Paths::get(&name)?;
     paths.check_exists()
-        .with_context(|| format!("instance {:?} detected", options.name))
+        .with_context(|| format!("instance {:?} detected", name))
         .with_hint(|| format!("Use `edgedb destroy {}` \
                               to remove remains of unused instance",
-                              options.name))?;
+                              name))?;
 
     let port = options.port.map(Ok)
-        .unwrap_or_else(|| allocate_port(&options.name))?;
+        .unwrap_or_else(|| allocate_port(&name))?;
 
     let info = if cfg!(windows) {
-        windows::create_instance(options, port, &paths)?;
+        windows::create_instance(options, &name, port, &paths)?;
         InstanceInfo {
-            name: options.name.clone(),
+            name: name.clone(),
             installation: None,
             port,
             start_conf: options.start_conf,
@@ -53,7 +87,7 @@ pub fn create(options: &Create) -> anyhow::Result<()> {
         let query = Query::from_options(options.nightly, &options.version)?;
         let inst = install::version(&query).context("error installing EdgeDB")?;
         let info = InstanceInfo {
-            name: options.name.clone(),
+            name: name.clone(),
             installation: Some(inst),
             port,
             start_conf: options.start_conf,
@@ -70,22 +104,22 @@ pub fn create(options: &Create) -> anyhow::Result<()> {
 
     match (create_service(&info), options.start_conf) {
         (Ok(()), StartConf::Manual) => {
-            echo!("Instance", options.name.emphasize(), "is ready.");
+            echo!("Instance", name.emphasize(), "is ready.");
             eprintln!("You can start it manually via: \n  \
                 edgedb instance start [--foreground] {}",
-                options.name);
+                name);
         }
         (Ok(()), StartConf::Auto) => {
-            echo!("Instance", options.name.emphasize(), "is up and running.");
+            echo!("Instance", name.emphasize(), "is up and running.");
             echo!("To connect to the instance run:");
-            echo!("  edgedb -I", options.name);
+            echo!("  edgedb -I", name);
         }
         (Err(e), _) => {
             eprintln!("Bootstrapping complete, \
                 but there was an error creating the service: {:#}", e);
             eprintln!("You can start it manually via: \n  \
                 edgedb instance start {}",
-                options.name);
+                name);
             return Err(ExitCode::new(exit_codes::CANNOT_CREATE_SERVICE))?;
         }
     }
diff --git a/src/portable/destroy.rs b/src/portable/destroy.rs
index 1aec920..f1e9853 100644
--- a/src/portable/destroy.rs
+++ b/src/portable/destroy.rs
@@ -7,8 +7,8 @@ use crate::options::Options;
 use crate::portable::control;
 use crate::portable::exit_codes;
 use crate::portable::local;
-use crate::portable::options::Destroy;
-use crate::portable::project::{self};
+use crate::portable::options::{Destroy, instance_arg};
+use crate::portable::project;
 use crate::portable::windows;
 use crate::print::{self, echo, Highlight};
 use crate::question;
@@ -25,12 +25,14 @@ pub fn print_warning(name: &str, project_dirs: &[PathBuf]) {
     eprintln!("  edgedb instance destroy {:?} --force", name);
 }
 
-pub fn with_projects<F>(name: &str, force: bool, f: F) -> anyhow::Result<()>
-    where F: FnOnce() -> anyhow::Result<()>
+pub fn with_projects(name: &str, force: bool,
+                     warn: impl FnOnce(&str, &[PathBuf]),
+                     f: impl FnOnce() -> anyhow::Result<()>)
+    -> anyhow::Result<()>
 {
     let project_dirs = project::find_project_dirs(&name)?;
     if !force && !project_dirs.is_empty() {
-        print_warning(&name, &project_dirs);
+        warn(&name, &project_dirs);
         return Err(ExitCode::new(exit_codes::NEEDS_FORCE))?;
     }
     f()?;
@@ -45,18 +47,18 @@ pub fn with_projects<F>(name: &str, force: bool, f: F) -> anyhow::Result<()>
 }
 
 pub fn destroy(options: &Destroy, opts: &Options) -> anyhow::Result<()> {
-    with_projects(&options.name, options.force, || {
+    let name = instance_arg(&options.name, &options.instance)?;
+    with_projects(&name, options.force, print_warning, || {
         if !options.force && !options.non_interactive {
             let q = question::Confirm::new_dangerous(
-                format!("Do you really want to delete instance {:?}?",
-                        options.name)
+                format!("Do you really want to delete instance {:?}?", name)
             );
             if !q.ask()? {
                 print::error("Canceled.");
                 return Err(ExitCode::new(exit_codes::NOT_CONFIRMED).into());
             }
         }
-        match do_destroy(options, opts) {
+        match do_destroy(options, opts, name) {
             Ok(()) => Ok(()),
             Err(e) if e.is::<InstanceNotFound>() => {
                 print::error(e);
@@ -66,21 +68,22 @@ pub fn destroy(options: &Destroy, opts: &Options) -> anyhow::Result<()> {
         }
     })?;
     if !options.quiet {
-        echo!("Instance", options.name.emphasize(),
-              "is successfully deleted.");
+        echo!("Instance", name.emphasize(), "is successfully deleted.");
     }
     Ok(())
 }
 
-fn do_destroy(options: &Destroy, opts: &Options) -> anyhow::Result<()> {
+fn do_destroy(options: &Destroy, opts: &Options, name: &str)
+    -> anyhow::Result<()>
+{
     if cfg!(windows) {
         return windows::destroy(options);
     }
-    let paths = local::Paths::get(&options.name)?;
+    let paths = local::Paths::get(name)?;
     log::debug!("Paths {:?}", paths);
     let mut found = false;
     let mut not_found_err = None;
-    match control::stop_and_disable(&options.name) {
+    match control::stop_and_disable(name) {
         Ok(f) => found = f,
         Err(e) if e.is::<InstanceNotFound>() => {
             not_found_err = Some(e);
@@ -145,10 +148,11 @@ fn do_destroy(options: &Destroy, opts: &Options) -> anyhow::Result<()> {
 
 pub fn force_by_name(name: &str, options: &Options) -> anyhow::Result<()> {
     do_destroy(&Destroy {
-        name: name.to_string(),
+        name: None,
+        instance: Some(name.to_string()),
         verbose: false,
         force: true,
         quiet: false,
         non_interactive: true,
-    }, options)
+    }, options, name)
 }
diff --git a/src/portable/link.rs b/src/portable/link.rs
index 3ca7a60..e9145d6 100644
--- a/src/portable/link.rs
+++ b/src/portable/link.rs
@@ -1,5 +1,6 @@
 use std::fmt;
 use std::fs;
+use std::path::PathBuf;
 use std::sync::{Mutex, Arc};
 use std::time::SystemTime;
 
@@ -21,8 +22,10 @@ use crate::credentials;
 use crate::hint::{HintedError, HintExt};
 use crate::options::{Options, ConnectionOptions};
 use crate::options::{conn_params, load_tls_options};
+use crate::portable::destroy::with_projects;
 use crate::portable::local::{InstanceInfo, is_valid_name};
-use crate::portable::options::{Link, Unlink};
+use crate::portable::options::{Link, Unlink, instance_arg};
+use crate::portable::project;
 use crate::print;
 use crate::question;
 use crate::tty_password;
@@ -353,16 +356,25 @@ fn prompt_conn_params(
     Ok(())
 }
 
+pub fn print_warning(name: &str, project_dirs: &[PathBuf]) {
+    project::print_instance_in_use_warning(name, project_dirs);
+    eprintln!("If you really want to unlink the instance, run:");
+    eprintln!("  edgedb instance unlink {:?} --force", name);
+}
+
 pub fn unlink(options: &Unlink) -> anyhow::Result<()> {
-    let inst = InstanceInfo::try_read(&options.name)?;
+    let name = instance_arg(&options.name, &options.instance)?;
+    let inst = InstanceInfo::try_read(name)?;
     if inst.is_some() {
         return Err(
             anyhow::anyhow!("cannot unlink local instance {:?}.", options.name)
         ).with_hint(|| format!(
             "use `edgedb instance destroy {}` to remove the instance",
-             options.name))?;
+             name))?;
     }
-    fs::remove_file(credentials::path(&options.name)?)
-        .with_context(|| format!("cannot unlink {}", options.name))?;
+    with_projects(&name, options.force, print_warning, || {
+        fs::remove_file(credentials::path(name)?)
+            .with_context(|| format!("cannot unlink {}", name))
+    })?;
     Ok(())
 }
diff --git a/src/portable/linux.rs b/src/portable/linux.rs
index 1c30727..e17a246 100644
--- a/src/portable/linux.rs
+++ b/src/portable/linux.rs
@@ -8,7 +8,7 @@ use fn_error_context::context;
 use crate::platform::{home_dir, current_exe};
 use crate::portable::destroy::InstanceNotFound;
 use crate::portable::local::{InstanceInfo, runstate_dir, log_file};
-use crate::portable::options::{StartConf, Logs};
+use crate::portable::options::{StartConf, Logs, instance_arg};
 use crate::portable::status::Service;
 use crate::process;
 
@@ -278,10 +278,11 @@ pub fn external_status(inst: &InstanceInfo) -> anyhow::Result<()> {
 }
 
 pub fn logs(options: &Logs) -> anyhow::Result<()> {
-    if detect_systemd(&options.name) {
+    let name = instance_arg(&options.name, &options.instance)?;
+    if detect_systemd(&name) {
         let mut cmd = process::Native::new(
             "logs", "journalctl", "journalctl");
-        cmd.arg("--user-unit").arg(unit_name(&options.name));
+        cmd.arg("--user-unit").arg(unit_name(&name));
         if let Some(n) = options.tail  {
             cmd.arg(format!("--lines={}", n));
         }
@@ -297,7 +298,7 @@ pub fn logs(options: &Logs) -> anyhow::Result<()> {
         if options.follow {
             cmd.arg("-F");
         }
-        cmd.arg(log_file(&options.name)?);
+        cmd.arg(log_file(&name)?);
         cmd.no_proxy().run()
     }
 }
diff --git a/src/portable/macos.rs b/src/portable/macos.rs
index 3f472fc..6ed88e0 100644
--- a/src/portable/macos.rs
+++ b/src/portable/macos.rs
@@ -9,7 +9,7 @@ use crate::commands::ExitCode;
 use crate::platform::{home_dir, get_current_uid, data_dir};
 use crate::platform::{current_exe};
 use crate::portable::local::{InstanceInfo, log_file, runstate_dir};
-use crate::portable::options::{StartConf, Logs};
+use crate::portable::options::{StartConf, Logs, instance_arg};
 use crate::portable::status::Service;
 use crate::print::{self, echo, Highlight};
 use crate::process;
@@ -402,6 +402,7 @@ pub fn logs(options: &Logs) -> anyhow::Result<()> {
     if options.follow {
         cmd.arg("-F");
     }
-    cmd.arg(log_file(&options.name)?);
+    let name = instance_arg(&options.name, &options.instance)?;
+    cmd.arg(log_file(name)?);
     cmd.no_proxy().run()
 }
diff --git a/src/portable/options.rs b/src/portable/options.rs
index 030e453..f5eb046 100644
--- a/src/portable/options.rs
+++ b/src/portable/options.rs
@@ -5,8 +5,10 @@ use clap::{ValueHint};
 use serde::{Serialize, Deserialize};
 use edgedb_cli_derive::{EdbClap, IntoArgs};
 
+use crate::commands::ExitCode;
 use crate::portable::local::is_valid_name;
 use crate::portable::ver;
+use crate::print::{echo, warn, err_marker};
 use crate::process::{self, IntoArg};
 
 
@@ -122,10 +124,11 @@ pub enum StartConf {
 
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Create {
-    /// Name of the created instance
+    /// Name of the created instance. Asked interactively if not specified
     #[clap(validator(instance_name_opt))]
-    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    #[clap(value_hint=ValueHint::Other)]
+    pub name: Option<String>,
+
     #[clap(long)]
     pub nightly: bool,
     #[clap(long, conflicts_with="nightly")]
@@ -154,14 +157,25 @@ pub struct Create {
     /// Create the EdgeDB Cloud instance under the given organization
     #[clap(long, hide=true)]
     pub cloud_org: Option<String>,
+
+    /// Do not ask questions, assume user wants to delete instance
+    #[clap(long)]
+    pub non_interactive: bool,
 }
 
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Destroy {
     /// Name of the instance to destroy
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Name of the instance to destroy
+    #[clap(short='I', long)]
     #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
+
     /// Verbose output
     #[clap(short='v', long, overrides_with="quiet")]
     pub verbose: bool,
@@ -214,17 +228,34 @@ pub struct Link {
 #[clap(long_about = "Unlink from a remote EdgeDB instance.")]
 pub struct Unlink {
     /// Specify the name of the remote instance.
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Specify the name of the remote instance.
+    #[clap(short='I', long)]
     #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
+
+    /// Force destroy even if instance is referred to by a project
+    #[clap(long)]
+    pub force: bool,
 }
 
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Start {
     /// Name of the instance to start
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Name of the instance to start
+    #[clap(short='I', long)]
     #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
+
     #[clap(long)]
     #[cfg_attr(target_os="linux",
         clap(help="Start the server in the foreground rather than using \
@@ -250,17 +281,29 @@ pub struct Start {
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Stop {
     /// Name of the instance to stop
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Name of the instance to restart
+    #[clap(short='I', long)]
     #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
 }
 
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Restart {
     /// Name of the instance to restart
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]
+    pub name: Option<String>,
+
+    /// Name of the instance to restart
+    #[clap(short='I', long)]
     #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
 }
 
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
@@ -296,9 +339,15 @@ pub struct List {
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Status {
     /// Name of the instance
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Name of the instance
+    #[clap(short='I', long)]
     #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
 
     /// Show current systems service info
     #[clap(long, conflicts_with_all=&["debug", "json", "extended"])]
@@ -326,9 +375,15 @@ pub struct Status {
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Logs {
     /// Name of the instance
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Name of the instance
+    #[clap(short='I', long)]
     #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
 
     /// Number of lines to show
     #[clap(short='n', long)]
@@ -354,8 +409,15 @@ pub struct Upgrade {
     pub to_nightly: bool,
 
     /// Instance to upgrade
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Instance to upgrade
+    #[clap(short='I', long)]
+    #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
 
     /// Verbose output
     #[clap(short='v', long)]
@@ -375,8 +437,15 @@ pub struct Upgrade {
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Revert {
     /// Name of the instance to revert
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Name of the instance to revert
+    #[clap(short='I', long)]
+    #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
 
     /// Do not check if upgrade is in progress
     #[clap(long)]
@@ -390,9 +459,16 @@ pub struct Revert {
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct ResetPassword {
     /// Name of the instance to reset
+    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
+    pub name: Option<String>,
+
+    /// Name of the instance to reset
+    #[clap(short='I', long)]
     #[clap(validator(instance_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
-    pub name: String,
+    pub instance: Option<String>,
+
     /// User to change password for. Default is got from credentials file.
     #[clap(long)]
     pub user: Option<String>,
@@ -473,3 +549,24 @@ pub fn instance_name_opt(name: &str) -> Result<(), String> {
                 (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)".into())
 }
 
+pub fn instance_arg<'x>(positional: &'x Option<String>,
+                        named: &'x Option<String>)
+    -> anyhow::Result<&'x str>
+{
+    if let Some(name) = positional {
+        if named.is_some() {
+            echo!(err_marker(), "Instance name is specified twice \
+                as positional argument and via `-I`. \
+                The latter is preferred.");
+            return Err(ExitCode::new(2).into());
+        }
+        warn(format_args!("Specifying instance name as positional argument is \
+            deprecated. Use `-I {}` instead.", name));
+        return Ok(name);
+    }
+    if let Some(name) = named {
+        return Ok(name);
+    }
+    echo!(err_marker(), "Instance name argument is required, use '-I name'");
+    return Err(ExitCode::new(2).into());
+}
diff --git a/src/portable/project.rs b/src/portable/project.rs
index 11e0b99..7d2f0a5 100644
--- a/src/portable/project.rs
+++ b/src/portable/project.rs
@@ -567,7 +567,7 @@ fn do_init(name: &str, pkg: &PackageInfo,
     let (instance, svc_result) = if cfg!(windows) {
         let q = repository::Query::from_version(&pkg.version.specific())?;
         windows::create_instance(&options::Create {
-            name: name.into(),
+            name: Some(name.into()),
             nightly: q.is_nightly(),
             version: q.version,
             port: Some(port),
@@ -576,7 +576,8 @@ fn do_init(name: &str, pkg: &PackageInfo,
             default_user: "edgedb".into(),
             cloud: false,
             cloud_org: None,
-        }, port, &paths)?;
+            non_interactive: true,
+        }, name, port, &paths)?;
         let svc_result = create::create_service(&InstanceInfo {
             name: name.into(),
             installation: None,
@@ -1439,7 +1440,8 @@ pub fn update_toml(options: &Upgrade) -> anyhow::Result<()> {
                     to_latest: false,
                     to_version: query.version.clone(),
                     to_nightly: query.is_nightly(),
-                    name: name.clone(),
+                    name: None,
+                    instance: Some(name.clone()),
                     verbose: false,
                     force: options.force,
                     force_dump_restore: options.force,
@@ -1541,7 +1543,8 @@ pub fn upgrade_instance(options: &Upgrade) -> anyhow::Result<()> {
                 to_latest: false,
                 to_version: cfg_ver.version.clone(),
                 to_nightly: cfg_ver.is_nightly(),
-                name: instance_name.into(),
+                name: None,
+                instance: Some(instance_name.into()),
                 verbose: false,
                 force: options.force,
                 force_dump_restore: options.force,
diff --git a/src/portable/reset_password.rs b/src/portable/reset_password.rs
index 3884206..316c561 100644
--- a/src/portable/reset_password.rs
+++ b/src/portable/reset_password.rs
@@ -13,7 +13,7 @@ use edgeql_parser::helpers::{quote_string, quote_name};
 
 use crate::credentials;
 use crate::portable::local::InstanceInfo;
-use crate::portable::options::ResetPassword;
+use crate::portable::options::{ResetPassword, instance_arg};
 use crate::print;
 use crate::tty_password;
 
@@ -39,7 +39,8 @@ fn read_credentials(path: &Path) -> anyhow::Result<Credentials> {
 
 
 pub fn reset_password(options: &ResetPassword) -> anyhow::Result<()> {
-    let credentials_file = credentials::path(&options.name)?;
+    let name = instance_arg(&options.name, &options.instance)?;
+    let credentials_file = credentials::path(name)?;
     let (creds, save, user) = if credentials_file.exists() {
         let creds = read_credentials(&credentials_file)?;
         let user = options.user.clone().unwrap_or_else(|| creds.user.clone());
@@ -71,7 +72,7 @@ pub fn reset_password(options: &ResetPassword) -> anyhow::Result<()> {
         generate_password()
     };
 
-    let inst = InstanceInfo::read(&options.name)?;
+    let inst = InstanceInfo::read(name)?;
     task::block_on(async {
         let conn_params = inst.admin_conn_params().await?;
         let mut cli = conn_params.build()?.connect().await?;
diff --git a/src/portable/revert.rs b/src/portable/revert.rs
index 8ba33b4..e5f50f5 100644
--- a/src/portable/revert.rs
+++ b/src/portable/revert.rs
@@ -10,7 +10,7 @@ use crate::portable::create;
 use crate::portable::exit_codes;
 use crate::portable::install;
 use crate::portable::local::Paths;
-use crate::portable::options::{Revert, StartConf};
+use crate::portable::options::{Revert, StartConf, instance_arg};
 use crate::portable::status::{instance_status, DataDirectory, BackupStatus};
 use crate::print::{self, echo, Highlight};
 use crate::process;
@@ -20,7 +20,8 @@ use crate::question;
 pub fn revert(options: &Revert) -> anyhow::Result<()> {
     use BackupStatus::*;
 
-    let status = instance_status(&options.name)?;
+    let name = instance_arg(&options.name, &options.instance)?;
+    let status = instance_status(name)?;
     let (backup_info, old_inst) = match status.backup {
         Absent => anyhow::bail!("cannot find backup directory to revert"),
         Exists { backup_meta: Err(e), ..}
@@ -65,7 +66,7 @@ pub fn revert(options: &Revert) -> anyhow::Result<()> {
     }
 
     if old_inst.start_conf != StartConf::Manual {
-        if let Err(e) = control::do_stop(&options.name) {
+        if let Err(e) = control::do_stop(name) {
             print::error(format!("Error stopping service: {:#}", e));
             if !options.no_confirm {
                 let q = question::Confirm::new("Do you want to proceed?");
@@ -80,7 +81,7 @@ pub fn revert(options: &Revert) -> anyhow::Result<()> {
     install::specific(&old_inst.get_version()?.specific())
         .context("error installing old EdgeDB version")?;
 
-    let paths = Paths::get(&options.name)?;
+    let paths = Paths::get(name)?;
     let tmp_path = tmp_file_path(&paths.data_dir);
     fs::rename(&paths.data_dir, &tmp_path)?;
     fs::rename(&paths.backup_dir, &paths.data_dir)?;
diff --git a/src/portable/status.rs b/src/portable/status.rs
index a84c4e7..b4a5463 100644
--- a/src/portable/status.rs
+++ b/src/portable/status.rs
@@ -27,7 +27,7 @@ use crate::portable::control;
 use crate::portable::exit_codes;
 use crate::portable::local::{InstanceInfo, Paths};
 use crate::portable::local::{read_ports, is_valid_name, lock_file};
-use crate::portable::options::{Status, List};
+use crate::portable::options::{Status, List, instance_arg};
 use crate::portable::upgrade::{UpgradeMeta, BackupMeta};
 use crate::portable::{windows, linux, macos};
 use crate::print::{self, echo, Highlight};
@@ -130,7 +130,8 @@ pub fn status(options: &Status) -> anyhow::Result<()> {
 }
 
 fn external_status(options: &Status) -> anyhow::Result<()> {
-    let ref meta = InstanceInfo::read(&options.name)?;
+    let name = instance_arg(&options.name, &options.instance)?;
+    let ref meta = InstanceInfo::read(name)?;
     if cfg!(windows) {
         windows::external_status(meta)
     } else if cfg!(target_os="macos") {
@@ -228,10 +229,11 @@ pub fn instance_status(name: &str) -> anyhow::Result<FullStatus> {
 }
 
 fn normal_status(options: &Status) -> anyhow::Result<()> {
-    let meta = InstanceInfo::try_read(&options.name).transpose();
+    let name = instance_arg(&options.name, &options.instance)?;
+    let meta = InstanceInfo::try_read(name).transpose();
     if let Some(meta) = meta {
-        let paths = Paths::get(&options.name)?;
-        let status = status_from_meta(&options.name, &paths, meta);
+        let paths = Paths::get(name)?;
+        let status = status_from_meta(name, &paths, meta);
         if options.debug {
             println!("{:#?}", status);
             Ok(())
@@ -313,9 +315,10 @@ async fn intermediate_feedback<F, D>(future: F, text: impl FnOnce() -> D)
 }
 
 pub fn remote_status(options: &Status) -> anyhow::Result<()> {
+    let name = instance_arg(&options.name, &options.instance)?;
     let status = task::block_on(
         intermediate_feedback(
-            _remote_status(&options.name, options.quiet),
+            _remote_status(name, options.quiet),
             || "Trying to connect...",
         )
     )?;
diff --git a/src/portable/upgrade.rs b/src/portable/upgrade.rs
index 56dc045..7d8ac8b 100644
--- a/src/portable/upgrade.rs
+++ b/src/portable/upgrade.rs
@@ -13,7 +13,7 @@ use crate::portable::create;
 use crate::portable::exit_codes;
 use crate::portable::install;
 use crate::portable::local::{InstanceInfo, InstallInfo, Paths, write_json};
-use crate::portable::options::{Upgrade, StartConf};
+use crate::portable::options::{Upgrade, StartConf, instance_arg};
 use crate::portable::project;
 use crate::portable::repository::{self, Query, PackageInfo, Channel};
 use crate::portable::ver;
@@ -87,7 +87,8 @@ fn check_project(name: &str, force: bool, ver_query: &Query)
 }
 
 pub fn upgrade(options: &Upgrade) -> anyhow::Result<()> {
-    let inst = InstanceInfo::read(&options.name)?;
+    let name = instance_arg(&options.name, &options.instance)?;
+    let inst = InstanceInfo::read(name)?;
     let inst_ver = inst.get_version()?.specific();
     let ver_option = options.to_latest || options.to_nightly ||
         options.to_version.is_some();
@@ -96,7 +97,7 @@ pub fn upgrade(options: &Upgrade) -> anyhow::Result<()> {
     } else {
         Query::from_version(&inst_ver)?
     };
-    check_project(&options.name, options.force, &ver_query)?;
+    check_project(name, options.force, &ver_query)?;
 
     if cfg!(windows) {
         return windows::upgrade(options);
diff --git a/src/portable/windows.rs b/src/portable/windows.rs
index 6a771f1..d11974c 100644
--- a/src/portable/windows.rs
+++ b/src/portable/windows.rs
@@ -24,7 +24,7 @@ use crate::portable::control;
 use crate::portable::destroy;
 use crate::portable::exit_codes;
 use crate::portable::local::{InstanceInfo, Paths, write_json};
-use crate::portable::options::{self, Logs, StartConf};
+use crate::portable::options::{self, Logs, StartConf, instance_arg};
 use crate::portable::project;
 use crate::portable::repository::{self, download, PackageHash, PackageInfo};
 use crate::portable::status::{self, Service};
@@ -160,7 +160,8 @@ fn path_to_linux(path: &Path) -> anyhow::Result<String> {
     Ok(result)
 }
 
-pub fn create_instance(options: &options::Create, port: u16, paths: &Paths)
+pub fn create_instance(options: &options::Create, name: &str,
+                       port: u16, paths: &Paths)
     -> anyhow::Result<()>
 {
     let wsl = ensure_wsl()?;
@@ -176,12 +177,13 @@ pub fn create_instance(options: &options::Create, port: u16, paths: &Paths)
     if let Some(dir) = paths.credentials.parent() {
         fs_err::create_dir_all(&dir)?;
     }
-    wsl.copy_out(credentials_linux(&options.name), &paths.credentials)?;
+    wsl.copy_out(credentials_linux(&name), &paths.credentials)?;
 
     Ok(())
 }
 
 pub fn destroy(options: &options::Destroy) -> anyhow::Result<()> {
+    let name = instance_arg(&options.name, &options.instance)?;
     let mut found = false;
     if let Some(wsl) = get_wsl()? {
         let options = options::Destroy {
@@ -200,7 +202,7 @@ pub fn destroy(options: &options::Destroy) -> anyhow::Result<()> {
         }
     }
 
-    let paths = Paths::get(&options.name)?;
+    let paths = Paths::get(&name)?;
     if paths.credentials.exists() {
         found = true;
         log::info!(target: "edgedb::portable::destroy",
@@ -216,7 +218,7 @@ pub fn destroy(options: &options::Destroy) -> anyhow::Result<()> {
         }
     }
     if !found {
-        echo!("No instance named {:?} found", options.name);
+        echo!("No instance named {:?} found", name);
         return Err(ExitCode::new(exit_codes::INSTANCE_NOT_FOUND).into());
     }
     Ok(())
@@ -635,12 +637,13 @@ pub fn info(options: &options::Info) -> anyhow::Result<()> {
 }
 
 pub fn reset_password(options: &options::ResetPassword) -> anyhow::Result<()> {
+    let name = instance_arg(&options.name, &options.instance)?;
     if let Some(wsl) = get_wsl()? {
         wsl.edgedb()
             .arg("instance").arg("reset-password").args(options)
             .run()?;
-        wsl.copy_out(credentials_linux(&options.name),
-                     credentials::path(&options.name)?)?;
+        wsl.copy_out(credentials_linux(name),
+                     credentials::path(name)?)?;
     } else {
         anyhow::bail!("WSL distribution is not installed, \
                        so no EdgeDB instances are present.");
@@ -798,6 +801,7 @@ pub fn list(options: &options::List) -> anyhow::Result<()> {
 }
 
 pub fn upgrade(options: &options::Upgrade) -> anyhow::Result<()> {
+    let name = instance_arg(&options.name, &options.instance)?;
     let wsl = try_get_wsl()?;
     wsl.edgedb()
         .arg("instance")
@@ -805,12 +809,12 @@ pub fn upgrade(options: &options::Upgrade) -> anyhow::Result<()> {
         .args(options)
         .run()?;
     // credentials might be updated on upgrade if we change format somehow
-    wsl.copy_out(credentials_linux(&options.name),
-                 credentials::path(&options.name)?)?;
+    wsl.copy_out(credentials_linux(name), credentials::path(name)?)?;
     Ok(())
 }
 
 pub fn revert(options: &options::Revert) -> anyhow::Result<()> {
+    let name = instance_arg(&options.name, &options.instance)?;
     let wsl = try_get_wsl()?;
     wsl.edgedb()
         .arg("instance")
@@ -818,8 +822,7 @@ pub fn revert(options: &options::Revert) -> anyhow::Result<()> {
         .args(options)
         .run()?;
     // credentials might be updated on upgrade if we change format somehow
-    wsl.copy_out(credentials_linux(&options.name),
-                 credentials::path(&options.name)?)?;
+    wsl.copy_out(credentials_linux(name), credentials::path(name)?)?;
     Ok(())
 }
 


Test patch:
diff --git a/tests/.gitignore b/tests/.gitignore
index f084cfe..6aa0a2a 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -3,5 +3,6 @@
 /migrations/db1/modified1/migrations/00002.edgeql
 /migrations/db1/modified2/migrations/00002.edgeql
 /migrations/db1/modified3/migrations/00002.edgeql
-/migrations/db2/migrations/00001.edgeql
+/migrations/db2/initial/migrations/00001.edgeql
+/migrations/db2/modified1/migrations/00002.edgeql
 /migrations/db3/migrations/00002.edgeql
diff --git a/tests/func/migrations.rs b/tests/func/migrations.rs
index 51dc666..e733de2 100644
--- a/tests/func/migrations.rs
+++ b/tests/func/migrations.rs
@@ -426,15 +426,31 @@ fn modified3_interactive() -> anyhow::Result<()> {
 
 #[test]
 fn prompt_id() -> anyhow::Result<()> {
-    fs::remove_file("tests/migrations/db2/migrations/00001.edgeql")
+    fs::remove_file("tests/migrations/db2/initial/migrations/00001.edgeql")
+        .ok();
+    fs::remove_file("tests/migrations/db2/modified1/migrations/00002.edgeql")
         .ok();
     SERVER.admin_cmd()
         .arg("database").arg("create").arg("db2")
         .assert().success();
+    SERVER.admin_cmd()
+        .arg("--database=db2")
+        .arg("migration").arg("create")
+        .arg("--schema-dir=tests/migrations/db2/initial")
+        .assert().success();  // should not ask any questions on first rev
+    SERVER.admin_cmd()
+        .arg("--database=db2")
+        .arg("migrate")
+        .arg("--schema-dir=tests/migrations/db2/initial")
+        .assert().success()
+        .stderr(contains("Applied \
+            m1fvz72asuad3xkor4unxshp524wp6stgdnbd34vxvjfjkrzemonkq \
+            (00001.edgeql)\n"));
+
     let mut cmd = SERVER.custom_interactive(|cmd| {
         cmd.arg("--database=db2");
         cmd.arg("migration").arg("create");
-        cmd.arg("--schema-dir=tests/migrations/db2");
+        cmd.arg("--schema-dir=tests/migrations/db2/modified1");
     });
     cmd.exp_string("[y,n,l,c,b,s,q,?]").unwrap();
     cmd.send_line("yes").unwrap();
diff --git a/tests/migrations/db2/initial/default.esdl b/tests/migrations/db2/initial/default.esdl
new file mode 100644
index 0000000..9137e90
--- /dev/null
+++ b/tests/migrations/db2/initial/default.esdl
@@ -0,0 +1,3 @@
+module default {
+    type X;
+};
diff --git a/tests/migrations/db2/default.esdl b/tests/migrations/db2/modified1/default.esdl
similarity index 88%
rename from tests/migrations/db2/default.esdl
rename to tests/migrations/db2/modified1/default.esdl
index 45772bb..8f72608 100644
--- a/tests/migrations/db2/default.esdl
+++ b/tests/migrations/db2/modified1/default.esdl
@@ -1,4 +1,5 @@
 module default {
+    type X;
     type Bar { link spam -> Spam };
     type Spam { link bar -> Bar };
 };
diff --git a/tests/migrations/db2/modified1/migrations/00001.edgeql b/tests/migrations/db2/modified1/migrations/00001.edgeql
new file mode 100644
index 0000000..767bafa
--- /dev/null
+++ b/tests/migrations/db2/modified1/migrations/00001.edgeql
@@ -0,0 +1,5 @@
+CREATE MIGRATION m1fvz72asuad3xkor4unxshp524wp6stgdnbd34vxvjfjkrzemonkq
+    ONTO initial
+{
+  CREATE TYPE default::X;
+};