scrybe-cli 0.6.2__tar.gz → 0.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/Cargo.lock +14 -14
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/Cargo.toml +12 -12
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/PKG-INFO +1 -1
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/pyproject.toml +1 -1
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/pyproject.toml +1 -1
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/pyproject.toml +1 -1
- scrybe_cli-0.6.3/scrybe-tools/src/autolaunch.rs +228 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/lib.rs +25 -3
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/python/scrybe_cli/__init__.py +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/python/scrybe_cli/py.typed +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-cli/Cargo.toml +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-cli/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-cli/src/lib.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-cli/src/main.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-cli/src/rpc_client.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-cli/src/wrap.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-cli/tests/rpc_socket.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/Cargo.toml +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/src/ast.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/src/change.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/src/content.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/src/document.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/src/error.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/src/lib.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/src/plugin.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-core/src/workspace.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/Cargo.toml +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/python/scrybe_mcp_server/__init__.py +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/python/scrybe_mcp_server/py.typed +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/src/lib.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/src/main.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/src/server.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/docx_packaging.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_invalid_args.json +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_lint_success.json +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_no_live_app.json +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_remote_app_error.json +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_unknown_tool.json +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/live_app_open.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/mcp_contract.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/Cargo.toml +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/python/scrybe_mermaid/__init__.py +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/python/scrybe_mermaid/py.typed +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/src/codec.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/src/error.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/src/lib.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid/tests/test_python_quickstart.py +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid-render/Cargo.toml +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid-render/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid-render/examples/provenance_drive.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mermaid-render/src/lib.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/Cargo.toml +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/src/html.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/src/lib.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/src/math.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/src/mermaid.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/src/theme.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/src/themes/dark.css +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/src/themes/default.css +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-render/src/themes/solarized.css +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-rpc/Cargo.toml +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-rpc/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-rpc/src/client.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-rpc/src/lib.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-rpc/tests/wire_contract.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/Cargo.toml +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/README.md +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/examples/render_drive.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/figures.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/lint.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/schema.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/tools/editor.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/tools/export.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/tools/lint.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/tools/mermaid.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/tools/mod.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/tools/render.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/tools/tabs.rs +0 -0
- {scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-tools/src/tools/ui.rs +0 -0
|
@@ -3915,7 +3915,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
|
3915
3915
|
|
|
3916
3916
|
[[package]]
|
|
3917
3917
|
name = "scrybe-app"
|
|
3918
|
-
version = "0.6.
|
|
3918
|
+
version = "0.6.3"
|
|
3919
3919
|
dependencies = [
|
|
3920
3920
|
"anyhow",
|
|
3921
3921
|
"dirs 5.0.1",
|
|
@@ -3943,7 +3943,7 @@ dependencies = [
|
|
|
3943
3943
|
|
|
3944
3944
|
[[package]]
|
|
3945
3945
|
name = "scrybe-cli"
|
|
3946
|
-
version = "0.6.
|
|
3946
|
+
version = "0.6.3"
|
|
3947
3947
|
dependencies = [
|
|
3948
3948
|
"anyhow",
|
|
3949
3949
|
"clap",
|
|
@@ -3961,7 +3961,7 @@ dependencies = [
|
|
|
3961
3961
|
|
|
3962
3962
|
[[package]]
|
|
3963
3963
|
name = "scrybe-core"
|
|
3964
|
-
version = "0.6.
|
|
3964
|
+
version = "0.6.3"
|
|
3965
3965
|
dependencies = [
|
|
3966
3966
|
"anyhow",
|
|
3967
3967
|
"blake3",
|
|
@@ -3978,7 +3978,7 @@ dependencies = [
|
|
|
3978
3978
|
|
|
3979
3979
|
[[package]]
|
|
3980
3980
|
name = "scrybe-mcp-client"
|
|
3981
|
-
version = "0.6.
|
|
3981
|
+
version = "0.6.3"
|
|
3982
3982
|
dependencies = [
|
|
3983
3983
|
"anyhow",
|
|
3984
3984
|
"dirs 5.0.1",
|
|
@@ -3993,7 +3993,7 @@ dependencies = [
|
|
|
3993
3993
|
|
|
3994
3994
|
[[package]]
|
|
3995
3995
|
name = "scrybe-mcp-server"
|
|
3996
|
-
version = "0.6.
|
|
3996
|
+
version = "0.6.3"
|
|
3997
3997
|
dependencies = [
|
|
3998
3998
|
"anyhow",
|
|
3999
3999
|
"clap",
|
|
@@ -4013,7 +4013,7 @@ dependencies = [
|
|
|
4013
4013
|
|
|
4014
4014
|
[[package]]
|
|
4015
4015
|
name = "scrybe-mermaid"
|
|
4016
|
-
version = "0.6.
|
|
4016
|
+
version = "0.6.3"
|
|
4017
4017
|
dependencies = [
|
|
4018
4018
|
"anyhow",
|
|
4019
4019
|
"base64 0.22.1",
|
|
@@ -4030,7 +4030,7 @@ dependencies = [
|
|
|
4030
4030
|
|
|
4031
4031
|
[[package]]
|
|
4032
4032
|
name = "scrybe-mermaid-render"
|
|
4033
|
-
version = "0.6.
|
|
4033
|
+
version = "0.6.3"
|
|
4034
4034
|
dependencies = [
|
|
4035
4035
|
"anyhow",
|
|
4036
4036
|
"hex",
|
|
@@ -4040,7 +4040,7 @@ dependencies = [
|
|
|
4040
4040
|
|
|
4041
4041
|
[[package]]
|
|
4042
4042
|
name = "scrybe-py"
|
|
4043
|
-
version = "0.6.
|
|
4043
|
+
version = "0.6.3"
|
|
4044
4044
|
dependencies = [
|
|
4045
4045
|
"anyhow",
|
|
4046
4046
|
"pyo3",
|
|
@@ -4054,7 +4054,7 @@ dependencies = [
|
|
|
4054
4054
|
|
|
4055
4055
|
[[package]]
|
|
4056
4056
|
name = "scrybe-ratatui"
|
|
4057
|
-
version = "0.6.
|
|
4057
|
+
version = "0.6.3"
|
|
4058
4058
|
dependencies = [
|
|
4059
4059
|
"ratatui",
|
|
4060
4060
|
"scrybe-core",
|
|
@@ -4062,7 +4062,7 @@ dependencies = [
|
|
|
4062
4062
|
|
|
4063
4063
|
[[package]]
|
|
4064
4064
|
name = "scrybe-render"
|
|
4065
|
-
version = "0.6.
|
|
4065
|
+
version = "0.6.3"
|
|
4066
4066
|
dependencies = [
|
|
4067
4067
|
"anyhow",
|
|
4068
4068
|
"pulldown-cmark",
|
|
@@ -4075,7 +4075,7 @@ dependencies = [
|
|
|
4075
4075
|
|
|
4076
4076
|
[[package]]
|
|
4077
4077
|
name = "scrybe-rpc"
|
|
4078
|
-
version = "0.6.
|
|
4078
|
+
version = "0.6.3"
|
|
4079
4079
|
dependencies = [
|
|
4080
4080
|
"serde",
|
|
4081
4081
|
"serde_json",
|
|
@@ -4084,7 +4084,7 @@ dependencies = [
|
|
|
4084
4084
|
|
|
4085
4085
|
[[package]]
|
|
4086
4086
|
name = "scrybe-tools"
|
|
4087
|
-
version = "0.6.
|
|
4087
|
+
version = "0.6.3"
|
|
4088
4088
|
dependencies = [
|
|
4089
4089
|
"anyhow",
|
|
4090
4090
|
"scrybe-core",
|
|
@@ -4102,7 +4102,7 @@ dependencies = [
|
|
|
4102
4102
|
|
|
4103
4103
|
[[package]]
|
|
4104
4104
|
name = "scrybe-tui"
|
|
4105
|
-
version = "0.6.
|
|
4105
|
+
version = "0.6.3"
|
|
4106
4106
|
dependencies = [
|
|
4107
4107
|
"anyhow",
|
|
4108
4108
|
"clap",
|
|
@@ -4114,7 +4114,7 @@ dependencies = [
|
|
|
4114
4114
|
|
|
4115
4115
|
[[package]]
|
|
4116
4116
|
name = "scrybe-vcs"
|
|
4117
|
-
version = "0.6.
|
|
4117
|
+
version = "0.6.3"
|
|
4118
4118
|
dependencies = [
|
|
4119
4119
|
"anyhow",
|
|
4120
4120
|
"chrono",
|
|
@@ -6,7 +6,7 @@ members = ["scrybe-core", "scrybe-render", "scrybe-mcp-server", "scrybe-mermaid"
|
|
|
6
6
|
# `version.workspace = true`, so a release bumps all crates in lock-step
|
|
7
7
|
# from one place. (See RELEASE.md; issue #128.)
|
|
8
8
|
[workspace.package]
|
|
9
|
-
version = "0.6.
|
|
9
|
+
version = "0.6.3"
|
|
10
10
|
|
|
11
11
|
# Workspace-level dependency pins keep all crates in lock-step.
|
|
12
12
|
[workspace.dependencies]
|
|
@@ -60,17 +60,17 @@ tauri-build = { version = "2", features = [] }
|
|
|
60
60
|
# Internal crates — version pinned (=) so cargo publish accepts the
|
|
61
61
|
# tarballs (path-deps without versions are rejected at publish time).
|
|
62
62
|
# Workspace lock-step: every release bumps all members together.
|
|
63
|
-
scrybe-core = { path = "scrybe-core", version = "=0.6.
|
|
64
|
-
scrybe-render = { path = "scrybe-render", version = "=0.6.
|
|
65
|
-
scrybe-mcp-client = { path = "scrybe-mcp-client", version = "=0.6.
|
|
66
|
-
scrybe-mcp-server = { path = "scrybe-mcp-server", version = "=0.6.
|
|
67
|
-
scrybe-mermaid = { path = "scrybe-mermaid", version = "=0.6.
|
|
68
|
-
scrybe-vcs = { path = "scrybe-vcs", version = "=0.6.
|
|
69
|
-
scrybe-py = { path = "scrybe-py", version = "=0.6.
|
|
70
|
-
scrybe-rpc = { path = "scrybe-rpc", version = "=0.6.
|
|
71
|
-
scrybe-tools = { path = "scrybe-tools", version = "=0.6.
|
|
72
|
-
scrybe-ratatui = { path = "scrybe-ratatui", version = "=0.6.
|
|
73
|
-
scrybe-mermaid-render = { path = "scrybe-mermaid-render", version = "=0.6.
|
|
63
|
+
scrybe-core = { path = "scrybe-core", version = "=0.6.3" }
|
|
64
|
+
scrybe-render = { path = "scrybe-render", version = "=0.6.3" }
|
|
65
|
+
scrybe-mcp-client = { path = "scrybe-mcp-client", version = "=0.6.3" }
|
|
66
|
+
scrybe-mcp-server = { path = "scrybe-mcp-server", version = "=0.6.3" }
|
|
67
|
+
scrybe-mermaid = { path = "scrybe-mermaid", version = "=0.6.3" }
|
|
68
|
+
scrybe-vcs = { path = "scrybe-vcs", version = "=0.6.3" }
|
|
69
|
+
scrybe-py = { path = "scrybe-py", version = "=0.6.3" }
|
|
70
|
+
scrybe-rpc = { path = "scrybe-rpc", version = "=0.6.3" }
|
|
71
|
+
scrybe-tools = { path = "scrybe-tools", version = "=0.6.3" }
|
|
72
|
+
scrybe-ratatui = { path = "scrybe-ratatui", version = "=0.6.3" }
|
|
73
|
+
scrybe-mermaid-render = { path = "scrybe-mermaid-render", version = "=0.6.3" }
|
|
74
74
|
|
|
75
75
|
# Adopted pure-Rust Mermaid → SVG renderer (#132 spike-Pass, 2026-07-17).
|
|
76
76
|
# SVG-only by default here; the `png` feature (resvg) is opted into per-crate.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Copyright 2026 Shawn Hartsock and contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
|
+
|
|
4
|
+
//! Opt-in auto-launch of the Scrybe desktop app (issue #225).
|
|
5
|
+
//!
|
|
6
|
+
//! A stateful tool (`open`, `state`, `list_tabs`, …) reaches the running editor
|
|
7
|
+
//! over `~/.scrybe/sock`. When nothing is listening, [`crate::LiveApp::call`]
|
|
8
|
+
//! reports `NoApp` and the tool returns a clean `no_live_app` — correct, but it
|
|
9
|
+
//! means "open this file in Scrybe" fails whenever the app happens to be closed.
|
|
10
|
+
//!
|
|
11
|
+
//! This module restores an *opt-in* launch: when `SCRYBE_MCP_AUTOLAUNCH` is set,
|
|
12
|
+
//! a no-app call first tries to start the installed app, waits for its socket,
|
|
13
|
+
//! and retries once. It is **off by default** on purpose — an MCP server
|
|
14
|
+
//! spawning a GUI window is a deliberate choice, not a surprise. It also
|
|
15
|
+
//! deliberately does *not* repeat the old `which scrybe-app` mistake: a cask- or
|
|
16
|
+
//! DMG-installed app lives in a `.app` bundle that is never on `PATH`, so we
|
|
17
|
+
//! resolve it by **install location**, not by `which`.
|
|
18
|
+
|
|
19
|
+
use std::path::{Path, PathBuf};
|
|
20
|
+
use std::time::Duration;
|
|
21
|
+
|
|
22
|
+
/// Env var that opts INTO auto-launch. Truthy = `1`/`true`/`yes`/`on`.
|
|
23
|
+
pub const AUTOLAUNCH_ENV: &str = "SCRYBE_MCP_AUTOLAUNCH";
|
|
24
|
+
|
|
25
|
+
/// Optional explicit `Scrybe.app` bundle path, checked before the standard
|
|
26
|
+
/// install locations (a non-default install, or CI).
|
|
27
|
+
pub const APP_ENV: &str = "SCRYBE_APP";
|
|
28
|
+
|
|
29
|
+
/// Liveness probes to make after launching before giving up (the app has to
|
|
30
|
+
/// start and bind its socket). 20 × [`POLL_INTERVAL`] ≈ a 5 s budget.
|
|
31
|
+
const DEFAULT_TRIES: u32 = 20;
|
|
32
|
+
/// Delay between liveness probes while waiting for the app to come up.
|
|
33
|
+
const POLL_INTERVAL: Duration = Duration::from_millis(250);
|
|
34
|
+
|
|
35
|
+
/// Is auto-launch opted in? True only when `SCRYBE_MCP_AUTOLAUNCH` is truthy.
|
|
36
|
+
pub fn enabled() -> bool {
|
|
37
|
+
is_truthy(std::env::var(AUTOLAUNCH_ENV).ok().as_deref())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn is_truthy(v: Option<&str>) -> bool {
|
|
41
|
+
matches!(
|
|
42
|
+
v.map(|s| s.trim().to_ascii_lowercase()).as_deref(),
|
|
43
|
+
Some("1" | "true" | "yes" | "on")
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Resolve the installed `Scrybe.app`, in priority order:
|
|
48
|
+
/// `$SCRYBE_APP` → `/Applications/Scrybe.app` → `$HOME/Applications/Scrybe.app`.
|
|
49
|
+
///
|
|
50
|
+
/// Pure over the injected `env` lookup and `exists` probe, so the resolution
|
|
51
|
+
/// order is unit-testable without touching the real filesystem.
|
|
52
|
+
pub fn resolve_app(
|
|
53
|
+
env: impl Fn(&str) -> Option<String>,
|
|
54
|
+
exists: impl Fn(&Path) -> bool,
|
|
55
|
+
) -> Option<PathBuf> {
|
|
56
|
+
if let Some(p) = env(APP_ENV) {
|
|
57
|
+
let p = PathBuf::from(p);
|
|
58
|
+
if exists(&p) {
|
|
59
|
+
return Some(p);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let mut candidates = vec![PathBuf::from("/Applications/Scrybe.app")];
|
|
63
|
+
if let Some(home) = env("HOME") {
|
|
64
|
+
candidates.push(PathBuf::from(home).join("Applications/Scrybe.app"));
|
|
65
|
+
}
|
|
66
|
+
candidates.into_iter().find(|p| exists(p))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Ensure a reachable app, launching it if it is not already up. Pure
|
|
70
|
+
/// orchestration over the injected `is_live` probe, `launch` action, and
|
|
71
|
+
/// `sleep` — no real subprocess, socket, or wall-clock — so the retry/timeout
|
|
72
|
+
/// logic is unit-testable. Returns `true` if the app is (or became) reachable.
|
|
73
|
+
pub fn ensure_running(
|
|
74
|
+
is_live: impl Fn() -> bool,
|
|
75
|
+
launch: impl Fn() -> bool,
|
|
76
|
+
sleep: impl Fn(),
|
|
77
|
+
tries: u32,
|
|
78
|
+
) -> bool {
|
|
79
|
+
if is_live() {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if !launch() {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
for _ in 0..tries {
|
|
86
|
+
sleep();
|
|
87
|
+
if is_live() {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Launch the resolved `Scrybe.app` detached and in the background. macOS-only;
|
|
95
|
+
/// returns `false` if no app was found or the spawn failed. On every other OS
|
|
96
|
+
/// auto-launch is a no-op — there is no bundle to `open`.
|
|
97
|
+
pub fn launch_installed_app() -> bool {
|
|
98
|
+
launch_app(|k| std::env::var(k).ok(), |p| p.exists())
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#[cfg(target_os = "macos")]
|
|
102
|
+
fn launch_app(env: impl Fn(&str) -> Option<String>, exists: impl Fn(&Path) -> bool) -> bool {
|
|
103
|
+
let Some(app) = resolve_app(env, exists) else {
|
|
104
|
+
return false;
|
|
105
|
+
};
|
|
106
|
+
// `open -g` launches the bundle in the background without stealing focus.
|
|
107
|
+
std::process::Command::new("open")
|
|
108
|
+
.arg("-g")
|
|
109
|
+
.arg(&app)
|
|
110
|
+
.status()
|
|
111
|
+
.map(|s| s.success())
|
|
112
|
+
.unwrap_or(false)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#[cfg(not(target_os = "macos"))]
|
|
116
|
+
fn launch_app(_env: impl Fn(&str) -> Option<String>, _exists: impl Fn(&Path) -> bool) -> bool {
|
|
117
|
+
false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// The production launch-and-wait the live transport calls on a no-app request:
|
|
121
|
+
/// opt-in gated, launches the installed app, and polls the real socket up to the
|
|
122
|
+
/// budget. Returns `true` if the app became reachable.
|
|
123
|
+
pub fn autolaunch_and_wait() -> bool {
|
|
124
|
+
if !enabled() {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
ensure_running(
|
|
128
|
+
scrybe_rpc::client::is_live,
|
|
129
|
+
launch_installed_app,
|
|
130
|
+
|| std::thread::sleep(POLL_INTERVAL),
|
|
131
|
+
DEFAULT_TRIES,
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[cfg(test)]
|
|
136
|
+
mod tests {
|
|
137
|
+
use super::*;
|
|
138
|
+
use std::cell::Cell;
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
fn enabled_is_truthy_only_for_yes_values() {
|
|
142
|
+
for v in ["1", "true", "TRUE", "yes", "on", " on "] {
|
|
143
|
+
assert!(is_truthy(Some(v)), "{v:?} should be truthy");
|
|
144
|
+
}
|
|
145
|
+
for v in ["0", "false", "no", "off", ""] {
|
|
146
|
+
assert!(!is_truthy(Some(v)), "{v:?} should be falsy");
|
|
147
|
+
}
|
|
148
|
+
assert!(!is_truthy(None));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[test]
|
|
152
|
+
fn resolve_prefers_scrybe_app_env_when_it_exists() {
|
|
153
|
+
let app = resolve_app(
|
|
154
|
+
|k| (k == APP_ENV).then(|| "/custom/Scrybe.app".to_string()),
|
|
155
|
+
|p| p == Path::new("/custom/Scrybe.app"),
|
|
156
|
+
);
|
|
157
|
+
assert_eq!(app, Some(PathBuf::from("/custom/Scrybe.app")));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[test]
|
|
161
|
+
fn resolve_skips_a_missing_env_override_and_falls_through() {
|
|
162
|
+
let app = resolve_app(
|
|
163
|
+
|k| (k == APP_ENV).then(|| "/gone/Scrybe.app".to_string()),
|
|
164
|
+
|p| p == Path::new("/Applications/Scrybe.app"),
|
|
165
|
+
);
|
|
166
|
+
assert_eq!(app, Some(PathBuf::from("/Applications/Scrybe.app")));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn resolve_falls_back_to_system_then_user_applications() {
|
|
171
|
+
let sys = resolve_app(|_| None, |p| p == Path::new("/Applications/Scrybe.app"));
|
|
172
|
+
assert_eq!(sys, Some(PathBuf::from("/Applications/Scrybe.app")));
|
|
173
|
+
|
|
174
|
+
let user_app = "/Users/me/Applications/Scrybe.app";
|
|
175
|
+
let user = resolve_app(
|
|
176
|
+
|k| (k == "HOME").then(|| "/Users/me".to_string()),
|
|
177
|
+
|p| p == Path::new(user_app),
|
|
178
|
+
);
|
|
179
|
+
assert_eq!(user, Some(PathBuf::from(user_app)));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn resolve_is_none_when_nothing_is_installed() {
|
|
184
|
+
assert_eq!(resolve_app(|_| None, |_| false), None);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#[test]
|
|
188
|
+
fn ensure_running_short_circuits_without_launching_when_already_live() {
|
|
189
|
+
let launched = Cell::new(false);
|
|
190
|
+
let ok = ensure_running(
|
|
191
|
+
|| true,
|
|
192
|
+
|| {
|
|
193
|
+
launched.set(true);
|
|
194
|
+
true
|
|
195
|
+
},
|
|
196
|
+
|| {},
|
|
197
|
+
20,
|
|
198
|
+
);
|
|
199
|
+
assert!(ok);
|
|
200
|
+
assert!(!launched.get(), "must not launch when already live");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#[test]
|
|
204
|
+
fn ensure_running_is_false_when_launch_fails() {
|
|
205
|
+
assert!(!ensure_running(|| false, || false, || {}, 20));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[test]
|
|
209
|
+
fn ensure_running_polls_until_the_app_comes_up() {
|
|
210
|
+
let ticks = Cell::new(0u32);
|
|
211
|
+
let ok = ensure_running(
|
|
212
|
+
|| ticks.get() >= 3, // live on the 3rd probe after launch
|
|
213
|
+
|| true,
|
|
214
|
+
|| ticks.set(ticks.get() + 1),
|
|
215
|
+
20,
|
|
216
|
+
);
|
|
217
|
+
assert!(ok);
|
|
218
|
+
assert_eq!(ticks.get(), 3);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[test]
|
|
222
|
+
fn ensure_running_gives_up_after_the_budget() {
|
|
223
|
+
let sleeps = Cell::new(0u32);
|
|
224
|
+
let ok = ensure_running(|| false, || true, || sleeps.set(sleeps.get() + 1), 5);
|
|
225
|
+
assert!(!ok);
|
|
226
|
+
assert_eq!(sleeps.get(), 5, "polls exactly `tries` times then gives up");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
use serde_json::Value;
|
|
15
15
|
|
|
16
|
+
pub mod autolaunch;
|
|
16
17
|
pub mod figures;
|
|
17
18
|
pub mod lint;
|
|
18
19
|
pub mod schema;
|
|
@@ -196,12 +197,33 @@ impl Transport for Headless {
|
|
|
196
197
|
/// `call` returns `NoApp` and pure tools should fall back to `Headless`.
|
|
197
198
|
pub struct LiveApp;
|
|
198
199
|
|
|
200
|
+
/// Map a `scrybe-rpc` client result onto the transport error taxonomy: no app
|
|
201
|
+
/// running → [`TransportError::NoApp`], an in-band remote error →
|
|
202
|
+
/// [`TransportError::Remote`], anything else (I/O, timeout, frame) →
|
|
203
|
+
/// [`TransportError::Transport`]. Used for the post-auto-launch retry.
|
|
204
|
+
fn map_send(res: Result<Value, scrybe_rpc::ClientError>) -> Result<Value, TransportError> {
|
|
205
|
+
match res {
|
|
206
|
+
Ok(result) => Ok(result),
|
|
207
|
+
Err(e) if e.is_not_running() => Err(TransportError::NoApp),
|
|
208
|
+
Err(scrybe_rpc::ClientError::Remote(err)) => Err(TransportError::Remote(err)),
|
|
209
|
+
Err(e) => Err(TransportError::Transport(e.to_string())),
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
199
213
|
impl Transport for LiveApp {
|
|
200
214
|
fn call(&self, method: &str, params: Value) -> Result<Value, TransportError> {
|
|
201
|
-
match scrybe_rpc::client::send(method, params) {
|
|
215
|
+
match scrybe_rpc::client::send(method, params.clone()) {
|
|
202
216
|
Ok(result) => Ok(result),
|
|
203
|
-
//
|
|
204
|
-
|
|
217
|
+
// No app running. If auto-launch is opted in (`SCRYBE_MCP_AUTOLAUNCH`),
|
|
218
|
+
// start the installed app, wait for its socket, and retry once;
|
|
219
|
+
// otherwise report NoApp (→ the tool's clean `no_live_app`).
|
|
220
|
+
Err(e) if e.is_not_running() => {
|
|
221
|
+
if crate::autolaunch::autolaunch_and_wait() {
|
|
222
|
+
map_send(scrybe_rpc::client::send(method, params))
|
|
223
|
+
} else {
|
|
224
|
+
Err(TransportError::NoApp)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
205
227
|
// In-band remote error: the app answered. Business, not engine.
|
|
206
228
|
Err(scrybe_rpc::ClientError::Remote(err)) => Err(TransportError::Remote(err)),
|
|
207
229
|
// Everything else (I/O, timeouts, frame/UTF-8/JSON, envelope,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/python/scrybe_mcp_server/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_invalid_args.json
RENAMED
|
File without changes
|
{scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_lint_success.json
RENAMED
|
File without changes
|
{scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_no_live_app.json
RENAMED
|
File without changes
|
{scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_remote_app_error.json
RENAMED
|
File without changes
|
{scrybe_cli-0.6.2 → scrybe_cli-0.6.3}/scrybe-mcp-server/tests/fixtures/call_unknown_tool.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|