feat(cli): add pic command-line client (login, apps, scripts, logs)

Adds a new workspace crate `picloud-cli` shipping a `pic` binary that
drives the edit-deploy-invoke-tail-logs loop against PiCloud's admin
and execute HTTP surface. Eight subcommands cover the minimum a
developer needs to never open the dashboard:

  pic login                    (paste URL + bearer token, validates via /auth/me)
  pic whoami                   (re-validates and prints principal)
  pic apps ls | create
  pic scripts ls | deploy | invoke
  pic logs <id>

Credentials persist as TOML under the platform config dir (resolved
via `directories`); on POSIX the file is forced to mode 0600.
PICLOUD_URL + PICLOUD_TOKEN env vars short-circuit interactive prompts
for CI and integration tests.

The CLI redeclares minimal request/response structs in `client.rs`
rather than depending on `manager-core` — keeps the blast radius
contained without touching the existing crate boundaries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-28 20:53:49 +02:00
parent b42e273479
commit 7b50047730
13 changed files with 1448 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
//! `pic whoami` — re-validates the saved token by hitting `/auth/me`
//! every time. Cached username in the credentials file is for
//! display-only contexts; this command is the source of truth.
use anyhow::Result;
use crate::client::Client;
use crate::config::load;
pub async fn run() -> Result<()> {
let creds = load()?;
let client = Client::from_creds(&creds)?;
let me = client.auth_me().await?;
let role = match me.instance_role {
picloud_shared::InstanceRole::Owner => "owner",
picloud_shared::InstanceRole::Admin => "admin",
picloud_shared::InstanceRole::Member => "member",
};
let email = me.email.as_deref().unwrap_or("-");
println!("{}\t{role}\t{email}\t{}", me.username, creds.url);
Ok(())
}