Repository: edgedb/edgedb-cli
Pull request #721 (2022-05-19 20:59:30)

Update instance routing to use org/instance
depends on https://github.com/edgedb/nebula/pull/14


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 4c347c7..60445e8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1206,7 +1206,7 @@ dependencies = [
 [[package]]
 name = "edgedb-client"
 version = "0.3.0"
-source = "git+https://github.com/edgedb/edgedb-rust#b8be0a885de87f3a9cc578e8f28ab1b78516bada"
+source = "git+https://github.com/edgedb/edgedb-rust#6b385b3c9f8c548fddc61cd53c1182c13c92ded7"
 dependencies = [
  "anyhow",
  "async-std",
@@ -1239,7 +1239,7 @@ dependencies = [
 [[package]]
 name = "edgedb-derive"
 version = "0.4.0"
-source = "git+https://github.com/edgedb/edgedb-rust#b8be0a885de87f3a9cc578e8f28ab1b78516bada"
+source = "git+https://github.com/edgedb/edgedb-rust#6b385b3c9f8c548fddc61cd53c1182c13c92ded7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1250,7 +1250,7 @@ dependencies = [
 [[package]]
 name = "edgedb-errors"
 version = "0.3.0"
-source = "git+https://github.com/edgedb/edgedb-rust#b8be0a885de87f3a9cc578e8f28ab1b78516bada"
+source = "git+https://github.com/edgedb/edgedb-rust#6b385b3c9f8c548fddc61cd53c1182c13c92ded7"
 dependencies = [
  "bytes",
 ]
@@ -1258,7 +1258,7 @@ dependencies = [
 [[package]]
 name = "edgedb-protocol"
 version = "0.4.0"
-source = "git+https://github.com/edgedb/edgedb-rust#b8be0a885de87f3a9cc578e8f28ab1b78516bada"
+source = "git+https://github.com/edgedb/edgedb-rust#6b385b3c9f8c548fddc61cd53c1182c13c92ded7"
 dependencies = [
  "bigdecimal",
  "bitflags",
diff --git a/src/cli/install.rs b/src/cli/install.rs
index 3d680ca..bfb3c64 100644
--- a/src/cli/install.rs
+++ b/src/cli/install.rs
@@ -409,7 +409,6 @@ fn try_project_init(new_layout: bool) -> anyhow::Result<InitResult> {
             no_migrations: false,
             link: false,
             server_start_conf: None,
-            cloud: false,
         };
         let options = crate::options::CloudOptions {
             cloud_base_url: None,
diff --git a/src/cloud/ops.rs b/src/cloud/ops.rs
index 0ba0a07..5859ee3 100644
--- a/src/cloud/ops.rs
+++ b/src/cloud/ops.rs
@@ -1,20 +1,17 @@
 use std::collections::HashMap;
 use std::fs;
 use std::io;
-use std::path::PathBuf;
 use std::time::{Duration, Instant};
 
 use async_std::task;
-use colorful::Colorful;
 use edgedb_client::credentials::Credentials;
 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::portable::local::is_valid_name;
+use crate::portable::local::is_valid_instance_name;
 use crate::print::{self, echo, err_marker, Highlight};
 use crate::question;
 use crate::table::{self, Cell, Row, Table};
@@ -26,12 +23,23 @@ const INSTANCE_CREATION_POLLING_INTERVAL : Duration = Duration::from_secs(1);
 pub struct CloudInstance {
     id: String,
     name: String,
+    org_slug: String,
     dsn: String,
     status: String,
     #[serde(skip_serializing_if = "Option::is_none")]
     tls_ca: Option<String>,
 }
 
+impl CloudInstance {
+    pub fn as_credentials(&self) -> anyhow::Result<Credentials> {
+        let mut creds = task::block_on(Builder::uninitialized().read_dsn(&self.dsn))?.as_credentials()?;
+        creds.tls_ca = self.tls_ca.clone();
+        creds.cloud_instance_id = Some(self.id.clone());
+        creds.cloud_original_dsn = Some(self.dsn.clone());
+        Ok(creds)
+    }
+}
+
 #[derive(Debug, serde::Serialize)]
 struct InstanceStatus {
     cloud_instance: CloudInstance,
@@ -83,18 +91,13 @@ pub struct CloudInstanceCreate {
 }
 
 pub async fn find_cloud_instance_by_name(
-    name: &str,
+    inst: &str,
+    org: &str,
     client: &CloudClient,
 ) -> anyhow::Result<Option<CloudInstance>> {
-    let instances: Vec<CloudInstance> = client.get("instances/").await?;
-    if let Some(instance) = instances
-        .into_iter()
-        .find(|instance| instance.name.eq(&name))
-    {
-        Ok(Some(instance))
-    } else {
-        Ok(None)
-    }
+    let instance: CloudInstance = client.get(format!("orgs/{}/instances/{}", org, inst)).await?;
+    println!("{:?}", instance);
+    Ok(Some(instance))
 }
 
 async fn wait_instance_create(
@@ -105,6 +108,7 @@ async fn wait_instance_create(
     if !quiet && instance.status == "creating" {
         print::echo!("Waiting for EdgeDB Cloud instance creation...");
     }
+    let url = format!("orgs/{}/instances/{}", instance.org_slug, instance.name);
     let deadline = Instant::now() + INSTANCE_CREATION_WAIT_TIME;
     while Instant::now() < deadline {
         if instance.dsn != "" {
@@ -119,7 +123,7 @@ async fn wait_instance_create(
         if instance.status == "creating" {
             task::sleep(INSTANCE_CREATION_POLLING_INTERVAL).await;
         }
-        instance = client.get(format!("instances/{}", instance.id)).await?;
+        instance = client.get(&url).await?;
     }
     if instance.dsn != "" {
         Ok(instance)
@@ -128,30 +132,15 @@ async fn wait_instance_create(
     }
 }
 
-async fn write_credentials(cred_path: &PathBuf, instance: CloudInstance) -> anyhow::Result<()> {
-    let mut creds = Builder::uninitialized()
-        .read_dsn(&instance.dsn)
-        .await?
-        .as_credentials()?;
-    creds.tls_ca = instance.tls_ca;
-    creds.cloud_instance_id = Some(instance.id);
-    creds.cloud_original_dsn = Some(instance.dsn);
-    credentials::write(&cred_path, &creds).await
-}
-
 pub async fn create_cloud_instance(
     client: &CloudClient,
     instance: &CloudInstanceCreate,
 ) -> anyhow::Result<()> {
-    let cred_path = credentials::path(&instance.name)?;
-    if cred_path.exists() {
-        anyhow::bail!("File {} exists; abort.", cred_path.display());
-    }
+    let url = format!("orgs/{}/instances", instance.org);
     let instance: CloudInstance = client
-        .post("instances/", serde_json::to_value(instance)?)
+        .post(url, serde_json::to_value(instance)?)
         .await?;
-    let instance = wait_instance_create(instance, client, false).await?;
-    write_credentials(&cred_path, instance).await?;
+    wait_instance_create(instance, client, false).await?;
     Ok(())
 }
 
@@ -161,7 +150,7 @@ fn ask_name() -> anyhow::Result<String> {
         let name = question::String::new(
             "Specify a name for the new instance"
         ).ask()?;
-        if !is_valid_name(&name) {
+        if !is_valid_instance_name(&name) {
             echo!(err_marker(),
                 "Instance name must be a valid identifier, \
                  (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)");
@@ -176,24 +165,26 @@ fn ask_name() -> anyhow::Result<String> {
     }
 }
 
+pub fn split_cloud_instance_name(name: &str) -> anyhow::Result<(String, String)> {
+    let mut splitter = name.splitn(2, '/');
+    match splitter.next() {
+        None => unreachable!(),
+        Some("") => anyhow::bail!("empty instance name"),
+        Some(org) => match splitter.next() {
+            None => anyhow::bail!("cloud instance must be in the form ORG/INST"),
+            Some("") => anyhow::bail!("invalid instance name: missing instance"),
+            Some(inst) => Ok((String::from(org), String::from(inst))),
+        },
+    }
+}
+
 pub async fn create(
     cmd: &crate::portable::options::Create,
     opts: &crate::options::Options,
 ) -> anyhow::Result<()> {
     let client = CloudClient::new(&opts.cloud_options)?;
     client.ensure_authenticated(false)?;
-    // let version = Query::from_options(cmd.nightly, &cmd.version)?;
-    let orgs: Vec<Org> = client.get("orgs/").await?;
-    let org_id = if let Some(name) = &cmd.cloud_org {
-        if let Some(org) = orgs.iter().find(|org| org.name.eq(name)) {
-            org.id.clone()
-        } else {
-            anyhow::bail!("Organization {} not found", name);
-        }
-    } else {
-        // TODO: use default organization
-        orgs[0].id.clone()
-    };
+
     let name = if let Some(name) = &cmd.name {
         name.to_owned()
     } else if cmd.non_interactive {
@@ -203,9 +194,11 @@ pub async fn create(
     } else {
         ask_name()?
     };
+
+    let (org, inst_name) = split_cloud_instance_name(&name)?;
     let instance = CloudInstanceCreate {
-        name: name.clone(),
-        org: org_id
+        name: inst_name.clone(),
+        org,
         // version: Some(format!("{}", version.display())),
         // default_database: Some(cmd.default_database.clone()),
         // default_user: Some(cmd.default_user.clone()),
@@ -221,150 +214,20 @@ pub async fn create(
     Ok(())
 }
 
-pub async fn link(
-    cmd: &crate::portable::options::Link,
-    opts: &crate::options::Options,
-) -> anyhow::Result<()> {
-    let mut client = CloudClient::new(&opts.cloud_options)?;
-    if cmd.non_interactive {
-        if let Some(name) = &cmd.name {
-            if !is_valid_name(name) {
-                print::error(
-                    "Instance name must be a valid identifier, \
-                             (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)",
-                );
-            }
-            let cred_path = credentials::path(name)?;
-            if cred_path.exists() && !cmd.overwrite {
-                anyhow::bail!("File {} exists; abort.", cred_path.display());
-            }
-        } else {
-            anyhow::bail!("Name is mandatory if --non-interactive is set.");
-        }
-    }
-    if !client.is_logged_in {
-        if !cmd.non_interactive {
-            let mut q = question::Confirm::new(
-                "You're not authenticated to the EdgeDB Cloud yet, login now?",
-            );
-            if q.default(true).ask()? {
-                auth::do_login(&client).await?;
-                client = CloudClient::new(&opts.cloud_options)?;
-            } else {
-                print::error("Aborted.");
-                return Ok(());
-            }
-        }
-        client.ensure_authenticated(false)?;
-    };
-    let cloud_name = if let Some(name) = &cmd.name {
-        name.clone()
-    } else {
-        if cmd.non_interactive {
-            unreachable!("Already checked previously");
-        } else {
-            loop {
-                let name = question::String::new(
-                    "Input the name of the EdgeDB Cloud instance to connect to",
-                )
-                .ask()?;
-                if !is_valid_name(&name) {
-                    print::error(
-                        "Instance name must be a valid identifier, \
-                                 (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)",
-                    );
-                    continue;
-                }
-                break name;
-            }
-        }
-    };
-    let instance = if let Some(instance) = find_cloud_instance_by_name(&cloud_name, &client).await?
-    {
-        wait_instance_create(instance, &client, cmd.quiet).await?
-    } else {
-        anyhow::bail!("No such Cloud instance named {}", cloud_name);
-    };
-
-    let (cred_path, instance_name) = if let Some(name) = &cmd.name {
-        let cred_path = credentials::path(&name)?;
-        if cred_path.exists() && cmd.overwrite && !cmd.quiet {
-            print::warn(format!("Overwriting {}", cred_path.display()));
-        }
-        (cred_path, name.clone())
-    } else {
-        assert!(!cmd.non_interactive, "Already checked previously");
-        let same_name_exists = credentials::path(&cloud_name)?.exists() && !cmd.overwrite;
-        loop {
-            let name = if same_name_exists {
-                question::String::new("Specify a local name to refer to the EdgeDB Cloud instance")
-                    .ask()?
-            } else {
-                question::String::new("Use the same name locally?")
-                    .default(&cloud_name)
-                    .ask()?
-            };
-            if !is_valid_name(&name) {
-                print::error(
-                    "Instance name must be a valid identifier, \
-                         (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)",
-                );
-                continue;
-            }
-            let cred_path = credentials::path(&name)?;
-            if cred_path.exists() {
-                if cmd.overwrite {
-                    if !cmd.quiet {
-                        print::warn(format!("Overwriting {}", cred_path.display()));
-                    }
-                } else {
-                    let mut q = question::Confirm::new_dangerous(format!(
-                        "{} exists! Overwrite?",
-                        cred_path.display()
-                    ));
-                    q.default(false);
-                    if !q.ask()? {
-                        continue;
-                    }
-                }
-            }
-            break (cred_path, name);
-        }
-    };
-
-    write_credentials(&cred_path, instance).await?;
-    if !cmd.quiet {
-        let mut msg = "Successfully linked to EdgeDB Cloud instance.".to_string();
-        if print::use_color() {
-            msg = format!("{}", msg.bold().light_green());
-        }
-        eprintln!(
-            "{} To connect run:\
-            \n  edgedb -I {}",
-            msg,
-            instance_name.escape_default(),
-        );
-    }
-    Ok(())
-}
-
-async fn destroy(instance_id: &str, options: &CloudOptions) -> anyhow::Result<()> {
-    log::info!("Destroying EdgeDB Cloud instance: {}", instance_id);
+async fn destroy(name: &str, org: &str, options: &CloudOptions) -> anyhow::Result<()> {
+    log::info!("Destroying EdgeDB Cloud instance: {}/{}", name, org);
     let client = CloudClient::new(options)?;
     client.ensure_authenticated(false)?;
-    let _: CloudInstance = client.delete(format!("instances/{}", instance_id)).await?;
+    let _: CloudInstance = client.delete(format!("orgs/{}/instances/{}", org, name)).await?;
     Ok(())
 }
 
 pub fn try_to_destroy(
-    cred_path: &PathBuf,
+    name: &str,
+    org: &str,
     options: &crate::options::Options,
 ) -> anyhow::Result<()> {
-    let file = io::BufReader::new(fs::File::open(cred_path)?);
-    let credentials: Credentials = serde_json::from_reader(file)?;
-    if let Some(instance_id) = credentials.cloud_instance_id {
-        task::block_on(destroy(&instance_id, &options.cloud_options))?;
-    }
+    task::block_on(destroy(name, org, &options.cloud_options))?;
     Ok(())
 }
 
@@ -414,7 +277,7 @@ pub async fn list(
         let mut table = Table::new();
         table.set_format(*table::FORMAT);
         table.set_titles(Row::new(
-            ["Kind", "Name", "Cloud Name", "Status"]
+            ["Kind", "Name", "Status"]
                 .iter()
                 .map(|x| table::header_cell(x))
                 .collect(),
@@ -422,8 +285,7 @@ pub async fn list(
         for instance in instances.values() {
             table.add_row(Row::new(vec![
                 Cell::new("cloud"),
-                Cell::new(instance.instance_name.as_deref().unwrap_or("-")),
-                Cell::new(&instance.cloud_instance.name),
+                Cell::new(&format!("{}/{}", instance.cloud_instance.org_slug, instance.cloud_instance.name)),
                 Cell::new(&instance.cloud_instance.status),
             ]));
         }
@@ -431,51 +293,3 @@ pub async fn list(
     }
     Ok(())
 }
-
-pub async fn ask_link_existing_cloud_instance(client: &CloudClient) -> anyhow::Result<String> {
-    let cloud_instances: Vec<CloudInstance> = client.get("instances/").await?;
-    let mut cloud_instances = cloud_instances
-        .into_iter()
-        .map(|inst| (inst.name.clone(), inst))
-        .collect::<HashMap<_, _>>();
-    let instances = credentials::all_instance_names()?;
-
-    let mut q = question::String::new(
-        "Specify the name of EdgeDB Cloud instance \
-                               to link with this project",
-    );
-    loop {
-        let target_name = q.ask()?;
-
-        if instances.contains(&target_name) {
-            print::error(format!(
-                "{:?} is a locally-used name, try --link without --cloud",
-                target_name
-            ));
-        } else if let Some(inst) = cloud_instances.remove(&target_name) {
-            let inst = wait_instance_create(inst, client, false).await?;
-            let cred_path = credentials::path(&target_name)?;
-            write_credentials(&cred_path, inst).await?;
-            return Ok(target_name);
-        } else {
-            print::error(format!("Cloud instance {:?} doesn't exist", target_name));
-        }
-    }
-}
-
-pub async fn link_existing_cloud_instance(client: &CloudClient, name: &str) -> anyhow::Result<()> {
-    let cred_path = credentials::path(&name)?;
-    if cred_path.exists() {
-        anyhow::bail!(
-            "{:?} is a locally-used name, try --link without --cloud",
-            name,
-        );
-    }
-    if let Some(inst) = find_cloud_instance_by_name(name, client).await? {
-        let inst = wait_instance_create(inst, client, false).await?;
-        write_credentials(&cred_path, inst).await?;
-        Ok(())
-    } else {
-        anyhow::bail!(format!("Cloud instance {:?} doesn't exist", name));
-    }
-}
diff --git a/src/commands/backslash.rs b/src/commands/backslash.rs
index 86b0ec8..8a4eafb 100644
--- a/src/commands/backslash.rs
+++ b/src/commands/backslash.rs
@@ -663,8 +663,10 @@ pub async fn execute(cmd: &BackslashCmd, prompt: &mut repl::State)
                          prompt.edgeql_state.clone())
                     )
             };
-            let value = desc.decode(&data)?;
-            println!("Descriptor id: {}", desc.descriptor_id());
+            let desc = desc.decode()?;
+            let codec = desc.build_codec()?;
+            let value = codec.decode(&data.data)?;
+            println!("Descriptor id: {}", desc.id());
             print::native_to_stdout(
                 stream::from_iter([Ok::<_, Error>(value)]),
                 &prompt.print,
@@ -680,8 +682,8 @@ pub async fn execute(cmd: &BackslashCmd, prompt: &mut repl::State)
                     .map(|c| c.get_state_desc())
                     .unwrap_or(prompt.edgeql_state_desc.clone())
             };
-            let typedesc = desc.decoded()?;
-            eprintln!("Descriptor id: {}", desc.descriptor_id());
+            let typedesc = desc.decode()?;
+            eprintln!("Descriptor id: {}", desc.id);
             eprintln!("Descriptor: {:#?}", typedesc.descriptors());
             eprintln!("Codec: {:#?}", typedesc.build_codec()?);
             Ok(Skip)
diff --git a/src/credentials.rs b/src/credentials.rs
index c0bbb8f..e5a2b1b 100644
--- a/src/credentials.rs
+++ b/src/credentials.rs
@@ -12,7 +12,7 @@ use edgedb_client::credentials::Credentials;
 
 use crate::platform::{config_dir, tmp_file_name};
 use crate::question;
-use crate::portable::local::is_valid_name;
+use crate::portable::local::is_valid_instance_name;
 
 
 pub fn base_dir() -> anyhow::Result<PathBuf> {
@@ -35,7 +35,7 @@ pub fn all_instance_names() -> anyhow::Result<BTreeSet<String>> {
         let item = item?;
         if let Ok(filename) = item.file_name().into_string() {
             if let Some(name) = filename.strip_suffix(".json") {
-                if is_valid_name(name) {
+                if is_valid_instance_name(name) {
                     result.insert(name.into());
                 }
             }
diff --git a/src/interactive.rs b/src/interactive.rs
index 7f378bc..c0ebdc4 100644
--- a/src/interactive.rs
+++ b/src/interactive.rs
@@ -12,14 +12,14 @@ use async_std::channel::{bounded as channel};
 use bytes::{Bytes, BytesMut};
 use colorful::Colorful;
 
-use edgedb_client::client::{EdgeqlStateDesc, EdgeqlState};
 use edgedb_client::errors::{ErrorKind, ClientEncodingError};
 use edgedb_client::errors::{Error as EdgedbError, StateMismatchError};
 use edgedb_protocol::client_message::ClientMessage;
 use edgedb_protocol::client_message::{DescribeStatement, DescribeAspect};
 use edgedb_protocol::client_message::{Execute0, Execute1};
 use edgedb_protocol::client_message::{Parse, Prepare, IoFormat, Cardinality};
-use edgedb_protocol::common::{Capabilities, CompilationFlags};
+use edgedb_protocol::common::{Capabilities, CompilationFlags, State};
+use edgedb_protocol::common::{RawTypedesc};
 use edgedb_protocol::model::Duration;
 use edgedb_protocol::query_arg::{Encoder, QueryArgs};
 use edgedb_protocol::server_message::ServerMessage;
@@ -136,8 +136,8 @@ pub fn main(options: Options, cfg: Config) -> Result<(), anyhow::Error> {
         last_version: None,
         connection: None,
         initial_text: "".into(),
-        edgeql_state_desc: EdgeqlStateDesc::uninitialized(),
-        edgeql_state: EdgeqlState::empty(),
+        edgeql_state_desc: RawTypedesc::uninitialized(),
+        edgeql_state: State::empty(),
     };
     let handle = task::spawn(_main(options, state, cfg));
     prompt::main(repl_wr, control_rd)?;
@@ -303,8 +303,7 @@ async fn execute_query1(options: &Options, mut state: &mut repl::State,
                 Json => IoFormat::Json,
             },
             expected_cardinality: Cardinality::Many,
-            state_typedesc_id: seq.get_state_typedesc_id(),
-            state_data: seq.get_state_data(),
+            state: seq.get_state().clone(),
             command_text: String::from(statement),
         }),
         ClientMessage::Flush,
@@ -390,10 +389,9 @@ async fn execute_query1(options: &Options, mut state: &mut repl::State,
             },
             expected_cardinality: Cardinality::Many,
             command_text: String::from(statement),
-            state_typedesc_id: seq.get_state_typedesc_id(),
-            state_data: seq.get_state_data(),
-            input_typedesc_id: data_description.input_typedesc_id,
-            output_typedesc_id: data_description.output_typedesc_id,
+            state: seq.get_state().clone(),
+            input_typedesc_id: data_description.input.id,
+            output_typedesc_id: data_description.output.id,
             arguments: arguments.freeze(),
         }),
         ClientMessage::Sync,
diff --git a/src/options.rs b/src/options.rs
index 913814c..bc6125e 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -52,6 +52,7 @@ pub struct ConnectionOptions {
     /// Local instance name created with `edgedb instance create` to connect to
     /// (overrides host and port)
     #[clap(short='I', long, help_heading=Some(CONN_OPTIONS_GROUP))]
+    #[clap(validator(crate::portable::options::instance_or_cloud_name_opt))]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
     pub instance: Option<String>,
 
@@ -608,7 +609,7 @@ impl Options {
     }
 
     pub fn create_connector(&self) -> anyhow::Result<Connector> {
-        Ok(Connector::new(conn_params(&self.conn_options)))
+        Ok(Connector::new(conn_params(&self)))
     }
 }
 
@@ -630,7 +631,8 @@ fn set_password(options: &ConnectionOptions, builder: &mut Builder)
     Ok(())
 }
 
-pub fn conn_params(tmp: &ConnectionOptions) -> anyhow::Result<Builder> {
+pub fn conn_params(opts: &Options) -> anyhow::Result<Builder> {
+    let tmp = &opts.conn_options;
     let mut bld = Builder::uninitialized();
     if let Some(path) = &tmp.unix_path {
         bld.unix_path(path, tmp.port, tmp.admin);
@@ -652,7 +654,15 @@ pub fn conn_params(tmp: &ConnectionOptions) -> anyhow::Result<Builder> {
         task::block_on(bld.read_dsn(dsn))?;
         bld.read_extra_env_vars()?;
     } else if let Some(instance) = &tmp.instance {
-        task::block_on(bld.read_instance(instance))?;
+        if instance.contains("/") {
+            let (org, inst_name) = crate::cloud::ops::split_cloud_instance_name(&instance)?;
+            let client = crate::cloud::client::CloudClient::new(&opts.cloud_options)?;
+            let inst = task::block_on(crate::cloud::ops::find_cloud_instance_by_name(
+                    &inst_name, &org, &client))?;
+            bld.credentials(&inst.expect("missing instance").as_credentials()?)?;
+        } else {
+            task::block_on(bld.read_instance(instance))?;
+        }
         bld.read_extra_env_vars()?;
     } else if let Some(file_path) = &tmp.credentials_file {
         task::block_on(bld.read_credentials(file_path))?;
diff --git a/src/portable/create.rs b/src/portable/create.rs
index 525ea37..ecce469 100644
--- a/src/portable/create.rs
+++ b/src/portable/create.rs
@@ -5,6 +5,7 @@ use async_std::task;
 use fn_error_context::context;
 
 use crate::commands::ExitCode;
+use crate::cloud;
 use crate::credentials;
 use crate::hint::HintExt;
 use crate::platform;
@@ -12,7 +13,7 @@ use crate::portable::control::{self, self_signed_arg, ensure_runstate_dir};
 use crate::portable::exit_codes;
 use crate::portable::install;
 use crate::portable::local::{Paths, InstanceInfo};
-use crate::portable::local::{write_json, allocate_port, is_valid_name};
+use crate::portable::local::{write_json, allocate_port, is_valid_instance_name};
 use crate::portable::options::{Create, Start};
 use crate::portable::platform::optional_docker_check;
 use crate::portable::repository::{Query};
@@ -31,7 +32,7 @@ fn ask_name() -> anyhow::Result<String> {
         let name = question::String::new(
             "Specify a name for the new instance"
         ).ask()?;
-        if !is_valid_name(&name) {
+        if !is_valid_instance_name(&name) {
             echo!(err_marker(),
                 "Instance name must be a valid identifier, \
                  (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)");
@@ -47,22 +48,22 @@ fn ask_name() -> anyhow::Result<String> {
 }
 
 
-pub fn create(options: &Create) -> anyhow::Result<()> {
+pub fn create(cmd: &Create, opts: &crate::options::Options) -> anyhow::Result<()> {
     if optional_docker_check()? {
         print::error(
             "`edgedb instance create` in a Docker container is not supported.",
         );
         return Err(ExitCode::new(exit_codes::DOCKER_CONTAINER))?;
     }
-    if options.start_conf.is_some() {
+    if cmd.start_conf.is_some() {
         print::warn("The option `--start-conf` is deprecated. \
                      Use `edgedb instance start/stop` to control \
                      the instance.");
     }
 
-    let name = if let Some(name) = &options.name {
+    let name = if let Some(name) = &cmd.name {
         name.to_owned()
-    } else if options.non_interactive {
+    } else if cmd.non_interactive {
         echo!(err_marker(), "Instance name is required \
                              in non-interactive mode");
         return Err(ExitCode::new(2).into());
@@ -70,6 +71,10 @@ pub fn create(options: &Create) -> anyhow::Result<()> {
         ask_name()?
     };
 
+    if name.contains("/") {
+        return task::block_on(cloud::ops::create(cmd, opts));
+    };
+
     let paths = Paths::get(&name)?;
     paths.check_exists()
         .with_context(|| format!("instance {:?} detected", name))
@@ -77,18 +82,18 @@ pub fn create(options: &Create) -> anyhow::Result<()> {
                               to remove remains of unused instance",
                               name))?;
 
-    let port = options.port.map(Ok)
+    let port = cmd.port.map(Ok)
         .unwrap_or_else(|| allocate_port(&name))?;
 
     let info = if cfg!(windows) {
-        windows::create_instance(options, &name, port, &paths)?;
+        windows::create_instance(cmd, &name, port, &paths)?;
         InstanceInfo {
             name: name.clone(),
             installation: None,
             port,
         }
     } else {
-        let query = Query::from_options(options.nightly, &options.version)?;
+        let query = Query::from_options(cmd.nightly, &cmd.version)?;
         let inst = install::version(&query).context("error installing EdgeDB")?;
         let info = InstanceInfo {
             name: name.clone(),
@@ -96,7 +101,7 @@ pub fn create(options: &Create) -> anyhow::Result<()> {
             port,
         };
         bootstrap(&paths, &info,
-                  &options.default_database, &options.default_user)?;
+                  &cmd.default_database, &cmd.default_user)?;
         info
     };
 
diff --git a/src/portable/destroy.rs b/src/portable/destroy.rs
index a9f63a3..35cb857 100644
--- a/src/portable/destroy.rs
+++ b/src/portable/destroy.rs
@@ -104,14 +104,6 @@ fn do_destroy(options: &Destroy, opts: &Options, name: &str)
     }
     if paths.credentials.exists(){
         found = true;
-        if let Err(e) = crate::cloud::ops::try_to_destroy(&paths.credentials, opts) {
-            let msg = format!("Failed to destroy EdgeDB Cloud instance: {:#}", e);
-            if options.force {
-                print::warn(msg);
-            } else {
-                anyhow::bail!(msg);
-            }
-        }
         log::info!("Removing credentials file {:?}", &paths.credentials);
         fs::remove_file(&paths.credentials)?;
     }
@@ -137,6 +129,19 @@ fn do_destroy(options: &Destroy, opts: &Options, name: &str)
         log::info!("Removing upgrade marker {:?}", paths.upgrade_marker);
         fs::remove_file(&paths.upgrade_marker)?;
     }
+    if name.contains("/") {
+        found = true;
+        log::info!("Removing cloud instance {}", name);
+        let (org_slug, inst_name) = crate::cloud::ops::split_cloud_instance_name(name)?;
+        if let Err(e) = crate::cloud::ops::try_to_destroy(&inst_name, &org_slug, opts) {
+            let msg = format!("Failed to destroy EdgeDB Cloud instance: {:#}", e);
+            if options.force {
+                print::warn(msg);
+            } else {
+                anyhow::bail!(msg);
+            }
+        }
+    }
     if found {
         Ok(())
     } else if let Some(e) = not_found_err {
diff --git a/src/portable/link.rs b/src/portable/link.rs
index 7772a32..e5b0d1a 100644
--- a/src/portable/link.rs
+++ b/src/portable/link.rs
@@ -25,7 +25,7 @@ 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::local::{InstanceInfo, is_valid_instance_name};
 use crate::portable::options::{Link, Unlink, instance_arg};
 use crate::portable::project;
 use crate::print;
@@ -152,7 +152,16 @@ fn is_no_credentials_error(mut e: &anyhow::Error) -> bool {
 }
 
 pub fn link(cmd: &Link, opts: &Options) -> anyhow::Result<()> {
-    let mut builder = match conn_params(&opts.conn_options) {
+    if let Some(name) = cmd.name.clone() {
+        if name.contains("/") {
+            anyhow::bail!(
+                "cloud instances cannot be linked\
+                \nTo connect run:\
+                \n  edgedb -I {}", name);
+        }
+    }
+
+    let mut builder = match conn_params(&opts) {
         Ok(builder) => builder,
         Err(e) if is_no_credentials_error(&e) => {
             Builder::uninitialized()
@@ -229,7 +238,7 @@ pub fn link(cmd: &Link, opts: &Options) -> anyhow::Result<()> {
                     let name = question::String::new(
                         "Specify a new instance name for the remote server"
                     ).default(&default).ask()?;
-                    if !is_valid_name(&name) {
+                    if !is_valid_instance_name(&name) {
                         print::error(
                             "Instance name must be a valid identifier, \
                              (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)");
diff --git a/src/portable/local.rs b/src/portable/local.rs
index b8dab76..92abef6 100644
--- a/src/portable/local.rs
+++ b/src/portable/local.rs
@@ -326,7 +326,7 @@ impl InstallInfo {
     }
 }
 
-pub fn is_valid_name(name: &str) -> bool {
+pub fn is_valid_instance_name(name: &str) -> bool {
     let mut chars = name.chars();
     match chars.next() {
         Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
@@ -340,6 +340,20 @@ pub fn is_valid_name(name: &str) -> bool {
     return true;
 }
 
+pub fn is_valid_org_name(name: &str) -> bool {
+    let mut chars = name.chars();
+    match chars.next() {
+        Some(c) if c.is_ascii_alphanumeric() => {}
+        _ => return false,
+    }
+    for c in chars {
+        if !c.is_ascii_alphanumeric() && c != '-' {
+            return false;
+        }
+    }
+    return true;
+}
+
 #[derive(Debug, thiserror::Error)]
 #[error("Not a local instance")]
 pub struct NonLocalInstance;
diff --git a/src/portable/main.rs b/src/portable/main.rs
index 462fbc0..b68562c 100644
--- a/src/portable/main.rs
+++ b/src/portable/main.rs
@@ -1,6 +1,3 @@
-use async_std::task;
-
-use crate::cloud;
 use crate::options::Options;
 use crate::portable::project::ProjectCommand;
 use crate::portable::options::{ServerCommand, ServerInstanceCommand};
@@ -43,12 +40,10 @@ pub fn instance_main(cmd: &ServerInstanceCommand, options: &Options)
     use crate::portable::options::InstanceCommand::*;
 
     match &cmd.subcommand {
-        Create(c) if c.cloud => task::block_on(cloud::ops::create(c, options)),
-        Create(c) => create::create(c),
+        Create(c) => create::create(c, options),
         Destroy(c) => destroy::destroy(c, options),
         ResetPassword(c) if cfg!(windows) => windows::reset_password(c),
         ResetPassword(c) => reset_password::reset_password(c),
-        Link(c) if c.cloud => task::block_on(cloud::ops::link(c, options)),
         Link(c) => link::link(c, &options),
         List(c) if cfg!(windows) => windows::list(c),
         List(c) => status::list(c, options),
diff --git a/src/portable/options.rs b/src/portable/options.rs
index 68d256f..ae64d88 100644
--- a/src/portable/options.rs
+++ b/src/portable/options.rs
@@ -6,7 +6,7 @@ use serde::{Serialize, Deserialize};
 use edgedb_cli_derive::{EdbClap, IntoArgs};
 
 use crate::commands::ExitCode;
-use crate::portable::local::is_valid_name;
+use crate::portable::local::{is_valid_instance_name, is_valid_org_name};
 use crate::portable::ver;
 use crate::print::{echo, warn, err_marker};
 use crate::process::{self, IntoArg};
@@ -128,7 +128,7 @@ pub enum StartConf {
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Create {
     /// Name of the created instance. Asked interactively if not specified
-    #[clap(validator(instance_name_opt))]
+    #[clap(validator(instance_or_cloud_name_opt))]
     #[clap(value_hint=ValueHint::Other)]
     pub name: Option<String>,
 
@@ -152,14 +152,6 @@ pub struct Create {
     #[clap(long, default_value="edgedb")]
     pub default_user: String,
 
-    /// Create an EdgeDB Cloud instance rather than a local instance
-    #[clap(long, hide=true)]
-    pub cloud: bool,
-
-    /// 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,
@@ -168,7 +160,7 @@ pub struct Create {
 #[derive(EdbClap, IntoArgs, Debug, Clone)]
 pub struct Destroy {
     /// Name of the instance to destroy
-    #[clap(validator(instance_name_opt), hide=true)]
+    #[clap(validator(instance_or_cloud_name_opt), hide=true)]
     #[clap(value_hint=ValueHint::Other)]  // TODO complete instance name
     pub name: Option<String>,
 
@@ -220,10 +212,6 @@ pub struct Link {
     /// Overwrite existing credential file if any.
     #[clap(long)]
     pub overwrite: bool,
-
-    /// Link to an EdgeDB Cloud instance rather than a regular remote instance
-    #[clap(long, hide=true)]
-    pub cloud: bool,
 }
 
 #[derive(EdbClap, Clone, Debug)]
@@ -553,13 +541,37 @@ impl fmt::Display for StartConf {
 }
 
 pub fn instance_name_opt(name: &str) -> Result<(), String> {
-    if is_valid_name(&name) {
+    if is_valid_instance_name(&name) {
         return Ok(())
     }
     return Err("instance name must be a valid identifier, \
                 (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)".into())
 }
 
+pub fn instance_or_cloud_name_opt(name: &str) -> Result<(), String> {
+    if is_valid_instance_name(&name) {
+        return Ok(())
+    }
+    if !name.contains("/") {
+        return Err("instance name must be a valid identifier, \
+            regex: ^[a-zA-Z_][a-zA-Z_0-9]*$ or a cloud instance name ORG/INST.".into())
+    }
+    match crate::cloud::ops::split_cloud_instance_name(&name) {
+        Ok((org_slug, inst_name)) => {
+            if !is_valid_instance_name(&inst_name) {
+                return Err(format!("instance name \"{}\" must be a valid identifier, \
+                    regex: ^[a-zA-Z_][a-zA-Z_0-9]*$", inst_name).into())
+            }
+            if !is_valid_org_name(&org_slug) {
+                return Err(format!("org name \"{}\" must be a valid identifier, \
+                    regex: ^[a-zA-Z0-9][a-zA-Z0-9-]{{0,38}}$", org_slug).into())
+            }
+            return Ok(())
+        },
+        Err(e) => Err(format!("invalid cloud instance name: {}", e)),
+    }
+}
+
 pub fn instance_arg<'x>(positional: &'x Option<String>,
                         named: &'x Option<String>)
     -> anyhow::Result<&'x str>
diff --git a/src/portable/project.rs b/src/portable/project.rs
index 416a6b2..1f50630 100644
--- a/src/portable/project.rs
+++ b/src/portable/project.rs
@@ -30,7 +30,7 @@ use crate::portable::create;
 use crate::portable::destroy;
 use crate::portable::exit_codes;
 use crate::portable::install;
-use crate::portable::local::{InstanceInfo, Paths, allocate_port, is_valid_name};
+use crate::portable::local::{InstanceInfo, Paths, allocate_port, is_valid_instance_name};
 use crate::portable::options::{self, instance_name_opt, StartConf, Start};
 use crate::portable::platform::{optional_docker_check};
 use crate::portable::repository::{self, Channel, Query, PackageInfo};
@@ -126,10 +126,6 @@ pub struct Init {
     /// Run in non-interactive mode (accepting all defaults)
     #[clap(long)]
     pub non_interactive: bool,
-
-    /// Use EdgeDB Cloud to initialize this project
-    #[clap(long, hide=true)]
-    pub cloud: bool,
 }
 
 #[derive(EdbClap, Debug, Clone)]
@@ -232,7 +228,7 @@ pub fn init(options: &Init, opts: &crate::options::Options) -> anyhow::Result<()
             let dir = fs::canonicalize(&dir)?;
             if dir.join("edgedb.toml").exists() {
                 if options.link {
-                    link(options, &dir, opts)?
+                    link(options, &dir)?
                 } else {
                     init_existing(options, &dir, &opts.cloud_options)?
                 }
@@ -253,7 +249,7 @@ pub fn init(options: &Init, opts: &crate::options::Options) -> anyhow::Result<()
             if let Some(dir) = search_dir(&base_dir) {
                 let dir = fs::canonicalize(&dir)?;
                 if options.link {
-                    link(options, &dir, opts)?
+                    link(options, &dir)?
                 } else {
                     init_existing(options, &dir, &opts.cloud_options)?
                 }
@@ -290,7 +286,7 @@ fn ask_existing_instance_name() -> anyhow::Result<String> {
     }
 }
 
-fn link(options: &Init, project_dir: &Path, opts: &crate::options::Options)
+fn link(options: &Init, project_dir: &Path)
     -> anyhow::Result<ProjectInfo>
 {
     echo!("Found `edgedb.toml` in", project_dir.display());
@@ -306,20 +302,14 @@ fn link(options: &Init, project_dir: &Path, opts: &crate::options::Options)
     let ver_query = config.edgedb.server_version;
 
     let name = if let Some(name) = &options.server_instance {
-        if options.cloud {
-            let client = CloudClient::new(&opts.cloud_options)?;
-            task::block_on(crate::cloud::ops::link_existing_cloud_instance(
-                &client, name,
-            ))?
+        if name.contains("/") {
+            anyhow::bail!(format!("Cloud instances can not be linked\nconnect to this instance with\n    edgedb -I {}", name))
         }
         name.clone()
     } else if options.non_interactive {
         anyhow::bail!("Existing instance name should be specified \
                        with `--server-instance` argument when linking project \
                        in non-interactive mode")
-    } else if options.cloud {
-        let client = CloudClient::new(&opts.cloud_options)?;
-        task::block_on(crate::cloud::ops::ask_link_existing_cloud_instance(&client))?
     } else {
         ask_existing_instance_name()?
     };
@@ -396,7 +386,7 @@ fn ask_name(dir: &Path, options: &Init) -> anyhow::Result<(String, bool)> {
     q.default(&default_name);
     loop {
         let target_name = q.ask()?;
-        if !is_valid_name(&target_name) {
+        if !is_valid_instance_name(&target_name) {
             print::error("Instance name must be a valid identifier, \
                          (regex: ^[a-zA-Z_][a-zA-Z_0-9]*$)");
             continue;
@@ -475,7 +465,7 @@ pub fn init_existing(options: &Init, project_dir: &Path, cloud_options: &crate::
 
     echo!("Checking EdgeDB versions...");
 
-    if options.cloud {
+    if name.contains("/") {
         let mut client = CloudClient::new(cloud_options)?;
         if !client.is_logged_in {
             if !options.non_interactive {
@@ -491,12 +481,11 @@ pub fn init_existing(options: &Init, project_dir: &Path, cloud_options: &crate::
             }
             client.ensure_authenticated(false)?;
         };
-        let org = task::block_on(ask_cloud_org(&client, options))?;
+        let (org, inst) = crate::cloud::ops::split_cloud_instance_name(&name)?;
         if let Some(_instance) = task::block_on(crate::cloud::ops::find_cloud_instance_by_name(
-            &name, &client,
+            &inst, &org, &client,
         ))? {
             ask_link_cloud_instance(options, &name)?;
-            task::block_on(crate::cloud::ops::link_existing_cloud_instance(&client, &name))?;
             let inst = Handle::probe(&name, project_dir, &schema_dir)?;
             inst.check_version(&ver_query);
             return do_link(&inst, options, &stash_dir);
@@ -561,8 +550,6 @@ fn do_init(name: &str, pkg: &PackageInfo,
             start_conf: None,
             default_database: "edgedb".into(),
             default_user: "edgedb".into(),
-            cloud: false,
-            cloud_org: None,
             non_interactive: true,
         }, name, port, &paths)?;
         create::create_service(&InstanceInfo {
@@ -627,7 +614,7 @@ fn do_cloud_init(
 ) -> anyhow::Result<ProjectInfo> {
     let instance = crate::cloud::ops::CloudInstanceCreate {
         name: name.clone(),
-        org,
+        org: org.clone(),
         // version: Some(version),
         // default_database: None,
         // default_user: None,
@@ -635,19 +622,20 @@ fn do_cloud_init(
     task::block_on(
         crate::cloud::ops::create_cloud_instance(client, &instance)
     )?;
-    write_stash_dir(stash_dir, project_dir, &name)?;
+    let full_name = format!("{}/{}", org, name);
+    write_stash_dir(stash_dir, project_dir, &full_name)?;
     if !options.no_migrations {
         let handle = Handle {
-            name: name.clone(),
+            name: full_name.clone(),
             schema_dir: schema_dir.into(),
             instance: InstanceKind::Remote,
             project_dir: project_dir.into(),
         };
         task::block_on(migrate(&handle, false))?;
     }
-    print_initialized(&name, &options.project_dir);
+    print_initialized(&full_name, &options.project_dir);
     Ok(ProjectInfo {
-        instance_name: name,
+        instance_name: full_name,
         stash_dir: stash_dir.into(),
     })
 }
@@ -697,7 +685,7 @@ pub fn init_new(options: &Init, project_dir: &Path, opts: &crate::options::Optio
 
     echo!("Checking EdgeDB versions...");
 
-    if options.cloud {
+    if name.contains("/") {
         let mut client = CloudClient::new(&opts.cloud_options)?;
         if !client.is_logged_in {
             if !options.non_interactive {
@@ -713,12 +701,11 @@ pub fn init_new(options: &Init, project_dir: &Path, opts: &crate::options::Optio
             }
             client.ensure_authenticated(false)?;
         };
-        let org = task::block_on(ask_cloud_org(&client, options))?;
+        let (org, inst) = crate::cloud::ops::split_cloud_instance_name(&name)?;
         if let Some(_instance) = task::block_on(crate::cloud::ops::find_cloud_instance_by_name(
-            &name, &client,
+            &inst, &org, &client,
         ))? {
             ask_link_cloud_instance(options, &name)?;
-            task::block_on(crate::cloud::ops::link_existing_cloud_instance(&client, &name))?;
             let inst = Handle::probe(&name, project_dir, &schema_dir)?;
             write_config(&config_path,
                          &Query::from_version(&inst.get_version()?.specific())?)?;
@@ -743,7 +730,7 @@ pub fn init_new(options: &Init, project_dir: &Path, opts: &crate::options::Optio
             write_schema_default(&schema_dir_path)?;
         }
 
-        do_cloud_init(name, org, &stash_dir, &project_dir, &schema_dir, options, &client)  // , version)
+        do_cloud_init(inst, org, &stash_dir, &project_dir, &schema_dir, options, &client)  // , version)
     } else {
         let pkg = ask_version(options)?;
 
@@ -1131,19 +1118,6 @@ fn ask_cloud_version(options: &Init) -> anyhow::Result<String> {
     }
 }
 
-async fn ask_cloud_org(client: &CloudClient, options: &Init) -> anyhow::Result<String> {
-    let orgs: Vec<crate::cloud::ops::Org> = client.get("orgs/").await?;
-    if options.non_interactive {
-        Ok(orgs.into_iter().next().unwrap().id)
-    } else {
-        let mut q = question::Numeric::new("Choose an organization:");
-        for org in orgs {
-            q.option(org.name, org.id);
-        }
-        Ok(q.ask()?)
-    }
-}
-
 fn print_versions(title: &str) -> anyhow::Result<()> {
     let mut avail = repository::get_server_packages(Channel::Stable)?;
     avail.sort_by(|a, b| b.version.cmp(&a.version));
diff --git a/src/portable/status.rs b/src/portable/status.rs
index 55d9d5a..c48d900 100644
--- a/src/portable/status.rs
+++ b/src/portable/status.rs
@@ -24,7 +24,7 @@ use crate::platform::{data_dir};
 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::local::{read_ports, is_valid_instance_name, lock_file};
 use crate::portable::options::{Status, List, instance_arg};
 use crate::portable::upgrade::{UpgradeMeta, BackupMeta};
 use crate::portable::{windows, linux, macos};
@@ -344,7 +344,7 @@ pub fn list_local<'x>(dir: &'x Path)
             res => return Some(Err(res.with_context(err_ctx).unwrap_err())),
         };
         let fname = entry.file_name();
-        let name_op = fname.to_str().and_then(|x| is_valid_name(x).then(|| x));
+        let name_op = fname.to_str().and_then(|x| is_valid_instance_name(x).then(|| x));
         if let Some(name) = name_op {
             return Some(Ok((name.into(), entry.path())))
         } else {
diff --git a/src/prompt/variable.rs b/src/prompt/variable.rs
index 70fa55b..6d9cc80 100644
--- a/src/prompt/variable.rs
+++ b/src/prompt/variable.rs
@@ -7,6 +7,7 @@ use anyhow::Context as _;
 use colorful::Colorful;
 use bigdecimal::BigDecimal;
 use edgedb_protocol::value::Value;
+use edgedb_protocol::model;
 use num_bigint::ToBigInt;
 use rustyline::completion::Completer;
 use rustyline::error::ReadlineError;
@@ -150,7 +151,9 @@ impl VariableInput for Json {
             Err(e) if e.classify()  == serde_json::error::Category::Eof
             => Err(Error::Incomplete),
             Err(e) => Err(no_pos_err(e)),
-            Ok(_) => Ok(Value::Json(input.into())),
+            Ok(_) => Ok(Value::Json(unsafe {
+                model::Json::new_unchecked(input.into())
+            })),
         }
     }
 }
diff --git a/src/repl.rs b/src/repl.rs
index c7858d0..e2858dd 100644
--- a/src/repl.rs
+++ b/src/repl.rs
@@ -3,8 +3,10 @@ use std::time::Duration;
 
 use anyhow::Context;
 use async_std::channel::{Sender, Receiver, RecvError};
+use bytes::BytesMut;
 use colorful::Colorful;
-use edgedb_client::client::{Connection, EdgeqlStateDesc, EdgeqlState};
+use edgedb_client::client::Connection;
+use edgedb_protocol::common::{State as EdgeqlState, RawTypedesc};
 use edgedb_protocol::model::{Duration as EdbDuration};
 use edgedb_protocol::server_message::TransactionState;
 
@@ -64,7 +66,7 @@ pub struct State {
     pub connection: Option<Connection>,
     pub last_version: Option<String>,
     pub initial_text: String,
-    pub edgeql_state_desc: EdgeqlStateDesc,
+    pub edgeql_state_desc: RawTypedesc,
     pub edgeql_state: EdgeqlState,
 }
 
@@ -262,14 +264,22 @@ impl State {
     }
     pub fn try_update_state(&mut self) -> anyhow::Result<bool> {
         if let Some(conn) = &mut self.connection {
-            match self.edgeql_state_desc.decode(&self.edgeql_state)? {
-                Some(value) => {
-                    let nstate = conn.get_state_desc().encode(&value)?;
-                    self.edgeql_state = nstate.clone();
-                    conn.set_state(nstate);
-                    return Ok(true)
-                }
-                None => {}
+            if self.edgeql_state.data.len() > 0 {
+                let desc = self.edgeql_state_desc.decode()?;
+                let codec = desc.build_codec()?;
+                let value = codec.decode(&self.edgeql_state.data)?;
+
+                let desc = conn.get_state_desc().decode()?;
+                let codec = desc.build_codec()?;
+                let mut buf = BytesMut::with_capacity(
+                    self.edgeql_state.data.len());
+                codec.encode(&mut buf, &value)?;
+                self.edgeql_state = EdgeqlState {
+                    typedesc_id: desc.id().clone(),
+                    data: buf.freeze(),
+                };
+                conn.set_state(self.edgeql_state.clone());
+                return Ok(true)
             }
         }
         Ok(false)
diff --git a/src/variables.rs b/src/variables.rs
index 451b843..da8778a 100644
--- a/src/variables.rs
+++ b/src/variables.rs
@@ -4,7 +4,7 @@ use std::sync::Arc;
 
 use edgedb_protocol::value::Value;
 use edgedb_protocol::codec;
-use edgedb_protocol::descriptors::{InputTypedesc, Descriptor};
+use edgedb_protocol::descriptors::{Typedesc, Descriptor};
 use crate::repl;
 use crate::prompt;
 use crate::prompt::variable::{self, VariableInput};
@@ -14,7 +14,7 @@ use crate::prompt::variable::{self, VariableInput};
 pub struct Canceled;
 
 
-pub async fn input_variables(desc: &InputTypedesc, state: &mut repl::PromptRpc)
+pub async fn input_variables(desc: &Typedesc, state: &mut repl::PromptRpc)
     -> Result<Value, anyhow::Error>
 {
     // only for protocol < 0.12
@@ -66,7 +66,7 @@ pub async fn input_variables(desc: &InputTypedesc, state: &mut repl::PromptRpc)
     }
 }
 
-async fn input_item(name: &str, mut item: &Descriptor, all: &InputTypedesc,
+async fn input_item(name: &str, mut item: &Descriptor, all: &Typedesc,
     state: &mut repl::PromptRpc, optional: bool)
     -> Result<Option<Value>, anyhow::Error>
 {


Test patch:
diff --git a/src/print/tests.rs b/src/print/tests.rs
index 73295b8..06ba16d 100644
--- a/src/print/tests.rs
+++ b/src/print/tests.rs
@@ -3,9 +3,10 @@ use std::convert::TryFrom;
 use std::str::FromStr;
 use std::pin::Pin;
 
-use async_std::task;
 use async_std::stream::Stream;
+use async_std::task;
 use bigdecimal::BigDecimal;
+use bytes::Bytes;
 
 use edgedb_protocol::value::Value;
 use edgedb_protocol::model::Datetime;
@@ -317,13 +318,13 @@ fn str() {
 #[test]
 fn bytes() {
     assert_eq!(
-        test_format(&[Value::Bytes(b"hello".to_vec())]).unwrap(),
+        test_format(&[Value::Bytes(Bytes::from_static(b"hello"))]).unwrap(),
         "{b'hello'}");
     assert_eq!(
-        test_format(&[Value::Bytes(b"a\nb".to_vec())]).unwrap(),
+        test_format(&[Value::Bytes(Bytes::from_static(b"a\nb"))]).unwrap(),
         "{b'a\\nb'}");
     assert_eq!(
-        test_format(&[Value::Bytes(b"a'b".to_vec())]).unwrap(),
+        test_format(&[Value::Bytes(Bytes::from_static(b"a'b"))]).unwrap(),
         r"{b'a\'b'}");
 }