reallink-cli 0.1.5 → 0.1.7
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.
- package/README.md +5 -0
- package/bin/reallink.cjs +41 -6
- package/package.json +3 -1
- package/rust/Cargo.lock +1 -1
- package/rust/Cargo.toml +1 -1
- package/rust/src/main.rs +211 -11
- package/scripts/postinstall.cjs +37 -0
package/README.md
CHANGED
|
@@ -8,11 +8,16 @@ Rust-based CLI for Reallink authentication, token workflows, and workspace file
|
|
|
8
8
|
npm install -g reallink-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
`npm install` performs a one-time Rust release build during postinstall. After that, `reallink` runs the compiled binary directly (no per-command cargo compile step).
|
|
12
|
+
CLI checks for updates in the background (cached daily) and prints a hint when a newer version is available.
|
|
13
|
+
|
|
11
14
|
## Commands
|
|
12
15
|
|
|
13
16
|
```bash
|
|
14
17
|
reallink login --base-url https://api.real-agent.link
|
|
15
18
|
reallink login --force # replace current saved session
|
|
19
|
+
reallink self-update --check
|
|
20
|
+
reallink self-update
|
|
16
21
|
reallink whoami
|
|
17
22
|
reallink logout
|
|
18
23
|
|
package/bin/reallink.cjs
CHANGED
|
@@ -1,18 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { spawnSync } = require("node:child_process");
|
|
3
|
+
const fs = require("node:fs");
|
|
3
4
|
const path = require("node:path");
|
|
4
5
|
|
|
5
6
|
const packageRoot = path.resolve(__dirname, "..");
|
|
6
7
|
const manifestPath = path.join(packageRoot, "rust", "Cargo.toml");
|
|
7
|
-
const
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const binaryName = process.platform === "win32" ? "reallink-cli.exe" : "reallink-cli";
|
|
10
|
+
const releaseBinaryPath = path.join(packageRoot, "rust", "target", "release", binaryName);
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
function runBinary(binaryPath) {
|
|
13
|
+
return spawnSync(binaryPath, args, {
|
|
14
|
+
stdio: "inherit",
|
|
15
|
+
shell: false
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ensureBinary() {
|
|
20
|
+
if (fs.existsSync(releaseBinaryPath)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const build = spawnSync(
|
|
25
|
+
"cargo",
|
|
26
|
+
["build", "--release", "--manifest-path", manifestPath],
|
|
27
|
+
{
|
|
28
|
+
stdio: "inherit",
|
|
29
|
+
shell: process.platform === "win32"
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (build.error) {
|
|
34
|
+
console.error(
|
|
35
|
+
`Failed to build reallink CLI with cargo: ${build.error.message}\n` +
|
|
36
|
+
"Install Rust (cargo) or reinstall using the one-line installer from real-agent.link."
|
|
37
|
+
);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return build.status === 0 && fs.existsSync(releaseBinaryPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!ensureBinary()) {
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
13
47
|
|
|
48
|
+
const result = runBinary(releaseBinaryPath);
|
|
14
49
|
if (result.error) {
|
|
15
|
-
console.error(`Failed to run
|
|
50
|
+
console.error(`Failed to run reallink binary: ${result.error.message}`);
|
|
16
51
|
process.exit(1);
|
|
17
52
|
}
|
|
18
53
|
|
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reallink-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Rust-based CLI for Reallink auth and API operations",
|
|
5
5
|
"bin": {
|
|
6
6
|
"reallink": "bin/reallink.cjs"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/reallink.cjs",
|
|
10
|
+
"scripts/postinstall.cjs",
|
|
10
11
|
"rust/Cargo.toml",
|
|
11
12
|
"rust/Cargo.lock",
|
|
12
13
|
"rust/src",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
17
|
+
"postinstall": "node ./scripts/postinstall.cjs",
|
|
16
18
|
"build": "cargo build --manifest-path ./rust/Cargo.toml --release",
|
|
17
19
|
"dev": "node ./bin/reallink.cjs --help",
|
|
18
20
|
"pack:local": "npm pack --json"
|
package/rust/Cargo.lock
CHANGED
package/rust/Cargo.toml
CHANGED
package/rust/src/main.rs
CHANGED
|
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|
|
5
5
|
use std::fs;
|
|
6
6
|
use std::io::{self, Write};
|
|
7
7
|
use std::path::{Path, PathBuf};
|
|
8
|
+
use std::process::Command;
|
|
8
9
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
9
10
|
use tokio::time::sleep;
|
|
10
11
|
|
|
@@ -12,6 +13,8 @@ const DEFAULT_BASE_URL: &str = "https://api.real-agent.link";
|
|
|
12
13
|
const CONFIG_DIR_ENV: &str = "REALLINK_CONFIG_DIR";
|
|
13
14
|
const SESSION_DIR_NAME: &str = "reallink";
|
|
14
15
|
const SESSION_FILE_NAME: &str = "session.json";
|
|
16
|
+
const UPDATE_CACHE_FILE_NAME: &str = "update-check.json";
|
|
17
|
+
const VERSION_CHECK_INTERVAL_MS: u128 = 24 * 60 * 60 * 1000;
|
|
15
18
|
|
|
16
19
|
#[derive(Parser)]
|
|
17
20
|
#[command(name = "reallink", version, about = "Reallink CLI")]
|
|
@@ -25,6 +28,7 @@ enum Commands {
|
|
|
25
28
|
Login(LoginArgs),
|
|
26
29
|
Whoami(BaseArgs),
|
|
27
30
|
Logout,
|
|
31
|
+
SelfUpdate(SelfUpdateArgs),
|
|
28
32
|
Project {
|
|
29
33
|
#[command(subcommand)]
|
|
30
34
|
command: ProjectCommands,
|
|
@@ -49,6 +53,12 @@ struct BaseArgs {
|
|
|
49
53
|
base_url: Option<String>,
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
#[derive(Args)]
|
|
57
|
+
struct SelfUpdateArgs {
|
|
58
|
+
#[arg(long, help = "Only check for updates; do not install")]
|
|
59
|
+
check: bool,
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
#[derive(Args)]
|
|
53
63
|
struct LoginArgs {
|
|
54
64
|
#[arg(long, default_value = DEFAULT_BASE_URL)]
|
|
@@ -337,6 +347,12 @@ struct SessionConfig {
|
|
|
337
347
|
updated_at_epoch_ms: u128,
|
|
338
348
|
}
|
|
339
349
|
|
|
350
|
+
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
351
|
+
struct UpdateCheckCache {
|
|
352
|
+
last_checked_epoch_ms: u128,
|
|
353
|
+
latest_version: Option<String>,
|
|
354
|
+
}
|
|
355
|
+
|
|
340
356
|
#[derive(Debug, Serialize, Deserialize)]
|
|
341
357
|
#[serde(rename_all = "camelCase")]
|
|
342
358
|
struct DeviceCodeRequest {
|
|
@@ -611,6 +627,11 @@ fn config_path() -> Result<PathBuf> {
|
|
|
611
627
|
Ok(base.join(SESSION_DIR_NAME).join(SESSION_FILE_NAME))
|
|
612
628
|
}
|
|
613
629
|
|
|
630
|
+
fn update_cache_path() -> Result<PathBuf> {
|
|
631
|
+
let base = resolve_config_root()?;
|
|
632
|
+
Ok(base.join(SESSION_DIR_NAME).join(UPDATE_CACHE_FILE_NAME))
|
|
633
|
+
}
|
|
634
|
+
|
|
614
635
|
fn session_path_display() -> String {
|
|
615
636
|
config_path()
|
|
616
637
|
.map(|path| path.display().to_string())
|
|
@@ -666,6 +687,22 @@ fn clear_session() -> Result<bool> {
|
|
|
666
687
|
Ok(false)
|
|
667
688
|
}
|
|
668
689
|
|
|
690
|
+
fn load_update_cache() -> Option<UpdateCheckCache> {
|
|
691
|
+
let path = update_cache_path().ok()?;
|
|
692
|
+
let raw = fs::read(path).ok()?;
|
|
693
|
+
serde_json::from_slice(&raw).ok()
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
fn save_update_cache(cache: &UpdateCheckCache) -> Result<()> {
|
|
697
|
+
let path = update_cache_path()?;
|
|
698
|
+
if let Some(parent) = path.parent() {
|
|
699
|
+
fs::create_dir_all(parent).with_context(|| format!("Failed to create {}", parent.display()))?;
|
|
700
|
+
}
|
|
701
|
+
let payload = serde_json::to_vec_pretty(cache)?;
|
|
702
|
+
write_atomic(&path, &payload)?;
|
|
703
|
+
Ok(())
|
|
704
|
+
}
|
|
705
|
+
|
|
669
706
|
async fn read_error_body(response: reqwest::Response) -> String {
|
|
670
707
|
match response.text().await {
|
|
671
708
|
Ok(text) if !text.trim().is_empty() => text,
|
|
@@ -673,6 +710,87 @@ async fn read_error_body(response: reqwest::Response) -> String {
|
|
|
673
710
|
}
|
|
674
711
|
}
|
|
675
712
|
|
|
713
|
+
fn with_cli_headers(request: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
|
714
|
+
request
|
|
715
|
+
.header("x-reallink-client", "cli")
|
|
716
|
+
.header("x-reallink-cli-version", env!("CARGO_PKG_VERSION"))
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
fn parse_semver_triplet(version: &str) -> Option<(u64, u64, u64)> {
|
|
720
|
+
let core = version.trim().split('-').next()?;
|
|
721
|
+
let mut parts = core.split('.');
|
|
722
|
+
let major = parts.next()?.parse::<u64>().ok()?;
|
|
723
|
+
let minor = parts.next().unwrap_or("0").parse::<u64>().ok()?;
|
|
724
|
+
let patch = parts.next().unwrap_or("0").parse::<u64>().ok()?;
|
|
725
|
+
Some((major, minor, patch))
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
fn is_newer_version(current: &str, latest: &str) -> bool {
|
|
729
|
+
match (parse_semver_triplet(current), parse_semver_triplet(latest)) {
|
|
730
|
+
(Some(current_parts), Some(latest_parts)) => latest_parts > current_parts,
|
|
731
|
+
_ => false,
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async fn fetch_latest_cli_version(client: &reqwest::Client) -> Option<String> {
|
|
736
|
+
let response = with_cli_headers(client.get("https://registry.npmjs.org/reallink-cli/latest"))
|
|
737
|
+
.timeout(Duration::from_secs(2))
|
|
738
|
+
.send()
|
|
739
|
+
.await
|
|
740
|
+
.ok()?;
|
|
741
|
+
if !response.status().is_success() {
|
|
742
|
+
return None;
|
|
743
|
+
}
|
|
744
|
+
let payload: serde_json::Value = response.json().await.ok()?;
|
|
745
|
+
payload
|
|
746
|
+
.get("version")
|
|
747
|
+
.and_then(|value| value.as_str())
|
|
748
|
+
.map(|value| value.trim().to_string())
|
|
749
|
+
.filter(|value| !value.is_empty())
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
async fn maybe_notify_update(client: &reqwest::Client, force_refresh: bool) {
|
|
753
|
+
if std::env::var("REALLINK_DISABLE_AUTO_UPDATE_CHECK")
|
|
754
|
+
.map(|value| value == "1")
|
|
755
|
+
.unwrap_or(false)
|
|
756
|
+
{
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
let now = now_epoch_ms();
|
|
761
|
+
let cached = load_update_cache();
|
|
762
|
+
let mut latest_version = if !force_refresh {
|
|
763
|
+
cached.as_ref().and_then(|cache| {
|
|
764
|
+
let age = now.saturating_sub(cache.last_checked_epoch_ms);
|
|
765
|
+
if age < VERSION_CHECK_INTERVAL_MS {
|
|
766
|
+
cache.latest_version.clone()
|
|
767
|
+
} else {
|
|
768
|
+
None
|
|
769
|
+
}
|
|
770
|
+
})
|
|
771
|
+
} else {
|
|
772
|
+
None
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
if latest_version.is_none() {
|
|
776
|
+
latest_version = fetch_latest_cli_version(client).await;
|
|
777
|
+
let _ = save_update_cache(&UpdateCheckCache {
|
|
778
|
+
last_checked_epoch_ms: now,
|
|
779
|
+
latest_version: latest_version.clone(),
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
let current = env!("CARGO_PKG_VERSION");
|
|
784
|
+
if let Some(latest) = latest_version {
|
|
785
|
+
if is_newer_version(current, &latest) {
|
|
786
|
+
eprintln!(
|
|
787
|
+
"Update available: {} -> {}. Run `reallink self-update`.",
|
|
788
|
+
current, latest
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
676
794
|
fn clean_virtual_path(value: &str) -> String {
|
|
677
795
|
value
|
|
678
796
|
.split('/')
|
|
@@ -708,9 +826,8 @@ async fn authed_request(
|
|
|
708
826
|
body: Option<serde_json::Value>,
|
|
709
827
|
) -> Result<reqwest::Response> {
|
|
710
828
|
let url = format!("{}{}", normalize_base_url(&session.base_url), path);
|
|
711
|
-
let mut request =
|
|
712
|
-
.request(method.clone(), &url)
|
|
713
|
-
.bearer_auth(&session.access_token);
|
|
829
|
+
let mut request =
|
|
830
|
+
with_cli_headers(client.request(method.clone(), &url).bearer_auth(&session.access_token));
|
|
714
831
|
if let Some(ref body_value) = body {
|
|
715
832
|
request = request.json(body_value);
|
|
716
833
|
}
|
|
@@ -721,9 +838,7 @@ async fn authed_request(
|
|
|
721
838
|
|
|
722
839
|
refresh_session(client, session).await?;
|
|
723
840
|
|
|
724
|
-
let mut retry = client
|
|
725
|
-
.request(method, &url)
|
|
726
|
-
.bearer_auth(&session.access_token);
|
|
841
|
+
let mut retry = with_cli_headers(client.request(method, &url).bearer_auth(&session.access_token));
|
|
727
842
|
if let Some(ref body_value) = body {
|
|
728
843
|
retry = retry.json(body_value);
|
|
729
844
|
}
|
|
@@ -736,7 +851,7 @@ async fn refresh_session(client: &reqwest::Client, session: &mut SessionConfig)
|
|
|
736
851
|
refresh_token: session.refresh_token.clone(),
|
|
737
852
|
session_id: session.session_id.clone(),
|
|
738
853
|
};
|
|
739
|
-
let response = client.post(url).json(&payload).send().await?;
|
|
854
|
+
let response = with_cli_headers(client.post(url).json(&payload)).send().await?;
|
|
740
855
|
if !response.status().is_success() {
|
|
741
856
|
let body = read_error_body(response).await;
|
|
742
857
|
return Err(anyhow!("Refresh failed: {}", body));
|
|
@@ -793,6 +908,9 @@ async fn login_command(client: &reqwest::Client, args: LoginArgs) -> Result<()>
|
|
|
793
908
|
"assets:write".to_string(),
|
|
794
909
|
"trace:read".to_string(),
|
|
795
910
|
"trace:write".to_string(),
|
|
911
|
+
"tools:read".to_string(),
|
|
912
|
+
"tools:write".to_string(),
|
|
913
|
+
"tools:run".to_string(),
|
|
796
914
|
"org:admin".to_string(),
|
|
797
915
|
"project:admin".to_string(),
|
|
798
916
|
]
|
|
@@ -800,8 +918,7 @@ async fn login_command(client: &reqwest::Client, args: LoginArgs) -> Result<()>
|
|
|
800
918
|
args.scope
|
|
801
919
|
};
|
|
802
920
|
|
|
803
|
-
let device_code_response = client
|
|
804
|
-
.post(format!("{}/auth/device/code", base_url))
|
|
921
|
+
let device_code_response = with_cli_headers(client.post(format!("{}/auth/device/code", base_url)))
|
|
805
922
|
.json(&DeviceCodeRequest {
|
|
806
923
|
client_id: args.client_id.clone(),
|
|
807
924
|
scope,
|
|
@@ -838,8 +955,7 @@ async fn login_command(client: &reqwest::Client, args: LoginArgs) -> Result<()>
|
|
|
838
955
|
|
|
839
956
|
sleep(poll_interval).await;
|
|
840
957
|
|
|
841
|
-
let token_response = client
|
|
842
|
-
.post(format!("{}/auth/device/token", base_url))
|
|
958
|
+
let token_response = with_cli_headers(client.post(format!("{}/auth/device/token", base_url)))
|
|
843
959
|
.json(&DeviceTokenRequest {
|
|
844
960
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code".to_string(),
|
|
845
961
|
device_code: device_code.device_code.clone(),
|
|
@@ -1719,6 +1835,85 @@ async fn tool_get_run_command(client: &reqwest::Client, args: ToolGetRunArgs) ->
|
|
|
1719
1835
|
Ok(())
|
|
1720
1836
|
}
|
|
1721
1837
|
|
|
1838
|
+
fn run_and_check_status(mut command: Command, context: &str) -> Result<()> {
|
|
1839
|
+
let status = command
|
|
1840
|
+
.status()
|
|
1841
|
+
.with_context(|| format!("Failed to execute {}", context))?;
|
|
1842
|
+
if !status.success() {
|
|
1843
|
+
return Err(anyhow!("{} exited with status {}", context, status));
|
|
1844
|
+
}
|
|
1845
|
+
Ok(())
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
async fn self_update_command(client: &reqwest::Client, args: SelfUpdateArgs) -> Result<()> {
|
|
1849
|
+
let current = env!("CARGO_PKG_VERSION");
|
|
1850
|
+
let latest = fetch_latest_cli_version(client).await;
|
|
1851
|
+
|
|
1852
|
+
let Some(latest_version) = latest else {
|
|
1853
|
+
println!("Could not check latest version right now.");
|
|
1854
|
+
return Ok(());
|
|
1855
|
+
};
|
|
1856
|
+
|
|
1857
|
+
if !is_newer_version(current, &latest_version) {
|
|
1858
|
+
println!("reallink is up to date ({})", current);
|
|
1859
|
+
return Ok(());
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
println!("Update available: {} -> {}", current, latest_version);
|
|
1863
|
+
if args.check {
|
|
1864
|
+
println!("Run `reallink self-update` to install the update.");
|
|
1865
|
+
return Ok(());
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
let npm_available = Command::new("npm")
|
|
1869
|
+
.arg("--version")
|
|
1870
|
+
.status()
|
|
1871
|
+
.map(|status| status.success())
|
|
1872
|
+
.unwrap_or(false);
|
|
1873
|
+
|
|
1874
|
+
if npm_available {
|
|
1875
|
+
run_and_check_status(
|
|
1876
|
+
{
|
|
1877
|
+
let mut command = Command::new("npm");
|
|
1878
|
+
command.args(["install", "-g", "reallink-cli@latest"]);
|
|
1879
|
+
command
|
|
1880
|
+
},
|
|
1881
|
+
"npm self-update",
|
|
1882
|
+
)?;
|
|
1883
|
+
println!("Updated via npm. Restart your shell if `reallink --version` still shows old version.");
|
|
1884
|
+
return Ok(());
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
if cfg!(windows) {
|
|
1888
|
+
run_and_check_status(
|
|
1889
|
+
{
|
|
1890
|
+
let mut command = Command::new("powershell");
|
|
1891
|
+
command.args([
|
|
1892
|
+
"-NoProfile",
|
|
1893
|
+
"-ExecutionPolicy",
|
|
1894
|
+
"Bypass",
|
|
1895
|
+
"-Command",
|
|
1896
|
+
"irm https://real-agent.link/install.ps1 | iex",
|
|
1897
|
+
]);
|
|
1898
|
+
command
|
|
1899
|
+
},
|
|
1900
|
+
"PowerShell installer update",
|
|
1901
|
+
)?;
|
|
1902
|
+
} else {
|
|
1903
|
+
run_and_check_status(
|
|
1904
|
+
{
|
|
1905
|
+
let mut command = Command::new("sh");
|
|
1906
|
+
command.args(["-c", "curl -fsSL https://real-agent.link/install.sh | bash"]);
|
|
1907
|
+
command
|
|
1908
|
+
},
|
|
1909
|
+
"shell installer update",
|
|
1910
|
+
)?;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
println!("Update installed. Verify with `reallink --version`.");
|
|
1914
|
+
Ok(())
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1722
1917
|
#[tokio::main]
|
|
1723
1918
|
async fn main() -> Result<()> {
|
|
1724
1919
|
let cli = Cli::parse();
|
|
@@ -1726,10 +1921,15 @@ async fn main() -> Result<()> {
|
|
|
1726
1921
|
.user_agent(concat!("reallink-cli/", env!("CARGO_PKG_VERSION")))
|
|
1727
1922
|
.build()?;
|
|
1728
1923
|
|
|
1924
|
+
if !matches!(&cli.command, Commands::SelfUpdate(_)) {
|
|
1925
|
+
maybe_notify_update(&client, false).await;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1729
1928
|
match cli.command {
|
|
1730
1929
|
Commands::Login(args) => login_command(&client, args).await?,
|
|
1731
1930
|
Commands::Whoami(args) => whoami_command(&client, args).await?,
|
|
1732
1931
|
Commands::Logout => logout_command(&client).await?,
|
|
1932
|
+
Commands::SelfUpdate(args) => self_update_command(&client, args).await?,
|
|
1733
1933
|
Commands::Project { command } => match command {
|
|
1734
1934
|
ProjectCommands::List(args) => project_list_command(&client, args).await?,
|
|
1735
1935
|
ProjectCommands::Create(args) => project_create_command(&client, args).await?,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawnSync } = require("node:child_process");
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
7
|
+
const manifestPath = path.join(packageRoot, "rust", "Cargo.toml");
|
|
8
|
+
const binaryName = process.platform === "win32" ? "reallink-cli.exe" : "reallink-cli";
|
|
9
|
+
const releaseBinaryPath = path.join(packageRoot, "rust", "target", "release", binaryName);
|
|
10
|
+
|
|
11
|
+
if (process.env.REALLINK_SKIP_BUILD === "1") {
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(releaseBinaryPath)) {
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const build = spawnSync(
|
|
20
|
+
"cargo",
|
|
21
|
+
["build", "--release", "--manifest-path", manifestPath],
|
|
22
|
+
{
|
|
23
|
+
stdio: "inherit",
|
|
24
|
+
shell: process.platform === "win32"
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (build.error) {
|
|
29
|
+
console.error(
|
|
30
|
+
`reallink-cli postinstall failed: ${build.error.message}\n` +
|
|
31
|
+
"Rust toolchain is required for npm installation. " +
|
|
32
|
+
"Install Rust (https://rustup.rs) or use the hosted installer at https://real-agent.link/install.sh."
|
|
33
|
+
);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process.exit(build.status ?? 1);
|