Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# qmtacp

CLI for a running QueryMT ACP WebSocket server (`qmtcode --acp-ws`).
Default endpoint is `ws://127.0.0.1:3030/ws`.
Default endpoint is `ws://127.0.0.1:3030/acp/ws`.

Each invocation connects, initializes, does one job, and exits. Session ids
are the only state. JSON goes to stdout; connection logs go to stderr.
Expand Down
4 changes: 2 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ tags: ["querymt", "acp", "sessions", "automation", "orchestration", "qmtcode"]

# qmtacp

Each invocation connects, does one job, and exits. Default URL: `ws://127.0.0.1:3030/ws`.
Each invocation connects, does one job, and exits. Default URL: `ws://127.0.0.1:3030/acp/ws`.
The server owns session state. Keep the returned `sessionId`.

- Do not invent session, profile, model, mode, effort, or URL values. Discover them (`caps`, `profiles`, `models`, `find`/`sessions`) or take them from JSON.
Expand Down Expand Up @@ -45,7 +45,7 @@ All of these come *before* the subcommand:
qmtacp [--url URL] [--allow-insecure] [--pretty] [--permission allow-once|reject-once|cancel] [--quiet] [--version] [--help] <command>
```

- `--url` / `-u`: `ws://`/`wss://` URL, or `host[:port][/path]`. Default `ws://127.0.0.1:3030/ws`.
- `--url` / `-u`: `ws://`/`wss://` URL, or `host[:port][/path]`. Default `ws://127.0.0.1:3030/acp/ws`.
- `--allow-insecure`: required for plaintext `ws://` to non-loopback hosts.
- `--pretty`: pretty JSON. Do not use on NDJSON streams.
- `--quiet`: hide connection logs on stderr.
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", env!("QMTACP_GIT_
#[command(about = "JSON CLI for a running QueryMT ACP WebSocket server")]
#[command(version = VERSION)]
struct Cli {
/// ACP WebSocket URL or host[:port][/path]. Defaults to ws://127.0.0.1:3030/ws
/// ACP WebSocket URL or host[:port][/path]. Defaults to ws://127.0.0.1:3030/acp/ws
#[arg(short, long, value_name = "url")]
url: Option<String>,

Expand Down
32 changes: 17 additions & 15 deletions src/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use url::Url;

pub const DEFAULT_HOST: &str = "127.0.0.1";
pub const DEFAULT_PORT: u16 = 3030;
pub const DEFAULT_PATH: &str = "/ws";
pub const DEFAULT_PATH: &str = "/acp/ws";

pub fn normalize_acp_ws_url(value: &str, allow_insecure: bool) -> Result<String> {
let trimmed = value.trim();
Expand Down Expand Up @@ -122,16 +122,18 @@ mod tests {
#[test]
fn normalizes_websocket_urls_structurally() {
let cases = [
("127.0.0.1", "ws://127.0.0.1:3030/ws"),
("localhost", "ws://localhost:3030/ws"),
("::1", "ws://[::1]:3030/ws"),
("ws://[::1]", "ws://[::1]:3030/ws"),
("ws://host:80", "ws://host:80/ws"),
("wss://host:443", "wss://host:443/ws"),
("ws://[::1]:80", "ws://[::1]:80/ws"),
("ws://host:9000", "ws://host:9000/ws"),
("127.0.0.1", "ws://127.0.0.1:3030/acp/ws"),
("localhost", "ws://localhost:3030/acp/ws"),
("::1", "ws://[::1]:3030/acp/ws"),
("ws://[::1]", "ws://[::1]:3030/acp/ws"),
("ws://host:80", "ws://host:80/acp/ws"),
("wss://host:443", "wss://host:443/acp/ws"),
("ws://[::1]:80", "ws://[::1]:80/acp/ws"),
("ws://host:9000", "ws://host:9000/acp/ws"),
("host/custom", "ws://host:3030/custom"),
("host?token=test", "ws://host:3030/ws?token=test"),
("host?token=test", "ws://host:3030/acp/ws?token=test"),
("ws://127.0.0.1:3030/acp/ws", "ws://127.0.0.1:3030/acp/ws"),
("ws://127.0.0.1:3030/ws", "ws://127.0.0.1:3030/ws"),
(
"wss://host:9443/custom?token=test",
"wss://host:9443/custom?token=test",
Expand All @@ -154,24 +156,24 @@ mod tests {
assert!(normalize_acp_ws_url("ws://remote.example", false).is_err());
assert_eq!(
normalize_acp_ws_url("wss://remote.example", false).unwrap(),
"wss://remote.example:3030/ws"
"wss://remote.example:3030/acp/ws"
);
assert_eq!(
normalize_acp_ws_url("localhost", false).unwrap(),
"ws://localhost:3030/ws"
"ws://localhost:3030/acp/ws"
);
assert_eq!(
normalize_acp_ws_url("::1", false).unwrap(),
"ws://[::1]:3030/ws"
"ws://[::1]:3030/acp/ws"
);
}

#[test]
fn endpoint_label_redacts_credentials_query_and_fragment() {
assert_eq!(
safe_endpoint_label("wss://user:secret@example.com:443/ws?token=secret#fragment")
safe_endpoint_label("wss://user:secret@example.com:443/acp/ws?token=secret#fragment")
.unwrap(),
"wss://example.com:443/ws"
"wss://example.com:443/acp/ws"
);
}
}
Loading