Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,87 @@ and paste the markdown.
inline auth login
```

## Alias-Aware JSON Querying

The CLI now supports alias-aware query/path transforms on JSON output:

- `--query-path <PATH>`: select value(s) by dot/bracket path (repeatable)
- `--field <PATH>`: project paths from each item in an array (repeatable)
- `--jsonpath <PATH>`: JSONPath-like dot/bracket selector (repeatable)
- `--sort-path <PATH>`: sort current JSON array by a path
- `--sort-desc`: descending sort order (with `--sort-path`)
- `--jq <FILTER>`: apply jq filter (requires `jq` in PATH)

Rules:

- Aliases are rewritten only in selector/filter strings, never in API payload JSON keys.
- Long-form canonical keys still work exactly as before.
- Mixed-case tokens are not rewritten.
- Quoted bracket keys are treated as literals and are not rewritten (for example: `users["fn"]`).

## Before/After Examples

```bash
# Canonical path
inline doctor --json --query-path config.apiBaseUrl

# Short alias path (same result)
inline doctor --json --query-path cfg.apiBaseUrl

# Canonical user projection
inline users list --json --query-path users --sort-path first_name --field id --field first_name

# Short alias projection (same result)
inline users list --json --query-path u --sort-path fn --field id --field fn

# Preserve a literal key via quoted brackets (no alias rewrite of "fn")
inline users list --json --query-path 'u["fn"]'
```

## Query Key Alias Table

| Alias | Canonical key |
| --- | --- |
| `au` | `auth` |
| `at` | `attachments` |
| `c` | `chats` |
| `cfg` | `config` |
| `cid` | `chat_id` |
| `d` | `dialogs` |
| `dn` | `display_name` |
| `em` | `email` |
| `fid` | `from_id` |
| `fn` | `first_name` |
| `it` | `items` |
| `lm` | `last_message` |
| `lmd` | `last_message_relative_date` |
| `lml` | `last_message_line` |
| `ln` | `last_name` |
| `m` | `message` |
| `mb` | `member` |
| `mbs` | `members` |
| `md` | `media` |
| `mid` | `message_id` |
| `ms` | `messages` |
| `par` | `participant` |
| `ph` | `phone_number` |
| `pid` | `peer_id` |
| `ps` | `participants` |
| `pt` | `peer_type` |
| `pth` | `paths` |
| `rd` | `relative_date` |
| `rmi` | `read_max_id` |
| `s` | `spaces` |
| `sid` | `space_id` |
| `sn` | `sender_name` |
| `sys` | `system` |
| `ti` | `title` |
| `u` | `users` |
| `uc` | `unread_count` |
| `uid` | `user_id` |
| `um` | `unread_mark` |
| `un` | `username` |

## Notes

The CLI is still early and may have bugs.
7 changes: 5 additions & 2 deletions cli/build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::path::PathBuf;

fn main() {
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let manifest_dir =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let proto_dir = manifest_dir.join("..").join("proto");

let protos = [
Expand All @@ -19,5 +20,7 @@ fn main() {

let mut config = prost_build::Config::new();
config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
config.compile_protos(&proto_paths, &include_paths).expect("compile protos");
config
.compile_protos(&proto_paths, &include_paths)
.expect("compile protos");
}
67 changes: 67 additions & 0 deletions cli/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,64 @@ description: Explain and use the Inline CLI (`inline`) for authentication, chats
- `--json`: Output JSON instead of human tables/details (available on all commands). This greatly increases the verbosity and information you can get. Most of the data is either not included or truncated/redacted in the default human readable mode. Use JSON mode when you need exact details of a chat, message, etc. You can start with default mode and switch to json mode for more details and form your response.
- `--pretty`: Pretty-print JSON output (default).
- `--compact`: Compact JSON output (no whitespace).
- `--query-path <PATH>`: Select value(s) via dot/bracket path (repeatable, alias-aware).
- `--field <PATH>`: Project field paths from each item in the current JSON array (repeatable, alias-aware).
- `--jsonpath <PATH>`: Select value(s) via JSONPath-like dot/bracket path (repeatable, alias-aware).
- `--sort-path <PATH>`: Sort current JSON array by the provided path (alias-aware).
- `--sort-desc`: Sort descending when using `--sort-path`.
- `--jq <FILTER>`: Apply jq filter to JSON output (requires `jq` installed; alias-aware for path tokens).

## Query alias rules

- Alias rewriting happens only in query/path strings (`--query-path`, `--field`, `--jsonpath`, `--sort-path`, `--jq`).
- API payload JSON keys are never mutated.
- Canonical long-form keys always remain valid.
- Mixed-case tokens are not rewritten.
- Quoted bracket keys are treated as literals and not rewritten (for example: `users["fn"]`).

## Query key aliases

| Alias | Canonical key |
| --- | --- |
| `au` | `auth` |
| `at` | `attachments` |
| `c` | `chats` |
| `cfg` | `config` |
| `cid` | `chat_id` |
| `d` | `dialogs` |
| `dn` | `display_name` |
| `em` | `email` |
| `fid` | `from_id` |
| `fn` | `first_name` |
| `it` | `items` |
| `lm` | `last_message` |
| `lmd` | `last_message_relative_date` |
| `lml` | `last_message_line` |
| `ln` | `last_name` |
| `m` | `message` |
| `mb` | `member` |
| `mbs` | `members` |
| `md` | `media` |
| `mid` | `message_id` |
| `ms` | `messages` |
| `par` | `participant` |
| `ph` | `phone_number` |
| `pid` | `peer_id` |
| `ps` | `participants` |
| `pt` | `peer_type` |
| `pth` | `paths` |
| `rd` | `relative_date` |
| `rmi` | `read_max_id` |
| `s` | `spaces` |
| `sid` | `space_id` |
| `sn` | `sender_name` |
| `sys` | `system` |
| `ti` | `title` |
| `u` | `users` |
| `uc` | `unread_count` |
| `uid` | `user_id` |
| `um` | `unread_mark` |
| `un` | `username` |

## Subcommands

Expand Down Expand Up @@ -124,6 +182,15 @@ description: Explain and use the Inline CLI (`inline`) for authentication, chats
- `inline auth me`
- Check diagnostics:
- `inline doctor`
- Query-path canonical vs alias (same result):
- `inline doctor --json --query-path config.apiBaseUrl`
- `inline doctor --json --query-path cfg.apiBaseUrl`
- Project and sort with canonical keys:
- `inline users list --json --query-path users --sort-path first_name --field id --field first_name`
- Project and sort with short aliases:
- `inline users list --json --query-path u --sort-path fn --field id --field fn`
- Preserve literal keys using quoted bracket syntax:
- `inline users list --json --query-path 'u["fn"]'`
- Search messages in a chat:
- `inline messages search --chat-id 123 --query "design review"`
- JSON: `inline messages search --chat-id 123 --query "design review" --json`
Expand Down
22 changes: 10 additions & 12 deletions cli/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use thiserror::Error;
use serde_json::{Value, json};
use std::fs;
use std::path::PathBuf;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ApiError {
Expand Down Expand Up @@ -85,7 +85,11 @@ impl ApiClient {
self.post(url, payload).await
}

pub async fn upload_file(&self, token: &str, input: UploadFileInput) -> Result<UploadFileResult, ApiError> {
pub async fn upload_file(
&self,
token: &str,
input: UploadFileInput,
) -> Result<UploadFileResult, ApiError> {
let url = format!("{}/uploadFile", self.base_url);
let mut form = reqwest::multipart::Form::new().text("type", input.file_type.as_str());
let bytes = fs::read(&input.path)?;
Expand Down Expand Up @@ -118,9 +122,7 @@ impl ApiClient {
match api_response {
ApiResponse::Ok { result, .. } => Ok(result),
ApiResponse::Err {
error,
description,
..
error, description, ..
} => Err(ApiError::Api {
error,
description: description.unwrap_or_else(|| "Unknown error".to_string()),
Expand Down Expand Up @@ -221,9 +223,7 @@ impl ApiClient {
match api_response {
ApiResponse::Ok { result, .. } => Ok(result),
ApiResponse::Err {
error,
description,
..
error, description, ..
} => Err(ApiError::Api {
error,
description: description.unwrap_or_else(|| "Unknown error".to_string()),
Expand Down Expand Up @@ -252,9 +252,7 @@ impl ApiClient {
match api_response {
ApiResponse::Ok { result, .. } => Ok(result),
ApiResponse::Err {
error,
description,
..
error, description, ..
} => Err(ApiError::Api {
error,
description: description.unwrap_or_else(|| "Unknown error".to_string()),
Expand Down
24 changes: 17 additions & 7 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,24 @@ impl Config {

let release_base_url = env::var("INLINE_RELEASE_BASE_URL")
.ok()
.or_else(|| if debug { None } else { Some(DEFAULT_RELEASE_BASE_URL.to_string()) })
.or_else(|| {
if debug {
None
} else {
Some(DEFAULT_RELEASE_BASE_URL.to_string())
}
})
.map(|url| url.trim_end_matches('/').to_string());
let release_manifest_url = env::var("INLINE_RELEASE_MANIFEST_URL")
.ok()
.or_else(|| release_base_url.as_ref().map(|base| format!("{base}/manifest.json")));
let release_install_url = env::var("INLINE_RELEASE_INSTALL_URL")
.ok()
.or_else(|| release_base_url.as_ref().map(|base| format!("{base}/install.sh")));
let release_manifest_url = env::var("INLINE_RELEASE_MANIFEST_URL").ok().or_else(|| {
release_base_url
.as_ref()
.map(|base| format!("{base}/manifest.json"))
});
let release_install_url = env::var("INLINE_RELEASE_INSTALL_URL").ok().or_else(|| {
release_base_url
.as_ref()
.map(|base| format!("{base}/install.sh"))
});

Self {
api_base_url,
Expand Down
Loading