@mmmbuto/masix 0.4.1 → 0.4.3

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  # @mmmbuto/masix
2
2
 
3
- Official Termux package for MasiX (MIT).
3
+ Official npm package for the Termux distribution of MasiX (MIT).
4
4
 
5
- MasiX is a Rust automation runtime optimized for smartphone use in Termux and compatible with Linux and macOS source builds.
5
+ MasiX is Termux-first. Linux and macOS are supported through source builds and the Homebrew tap.
6
6
 
7
- Current package line: `0.4.1`.
7
+ Current package line: `0.4.3`.
8
8
 
9
9
  ## Function Summary
10
10
 
@@ -19,6 +19,7 @@ Current package line: `0.4.1`.
19
19
  - Termux boot automation (`masix termux boot enable|disable|status`)
20
20
  - Optional local STT via whisper.cpp (`masix config stt`)
21
21
  - Bundled `plugin-base` packages are included and installed during `npm install`
22
+ - If a bundled `.pkg` is missing, MasiX can fall back to source build for the module
22
23
  - Startup update checks are available on demand via `masix check-update`
23
24
 
24
25
  ## Install (Termux)
@@ -42,11 +43,11 @@ masix status
42
43
  ## Bundled Packages
43
44
 
44
45
  Postinstall installs these bundled `plugin-base` packages into `~/.masix/plugins`:
45
- - `discovery` `0.2.4`
46
+ - `discovery` `0.3.0`
46
47
  - `codex-backend` `0.1.4`
47
48
  - `codex-tools` `0.1.3`
48
49
 
49
- Package artifacts are also included in the npm tarball under `packages/plugin-base/`.
50
+ Package artifacts and source trees are included in the npm tarball under `packages/plugin-base/`.
50
51
 
51
52
  ## Useful Commands
52
53
 
@@ -67,6 +68,7 @@ masix stats
67
68
 
68
69
  - This package targets Android + arm64 in Termux
69
70
  - If no prebuilt binary is available, postinstall builds from source
71
+ - The postinstall step uses `masix plugin install-base` so dependency order and source fallback stay aligned with the CLI
70
72
  - `masix config stt` can auto-pick and auto-download a Whisper model based on device resources
71
73
 
72
74
  ## Full Documentation
package/install.js CHANGED
@@ -7,31 +7,6 @@ const BINARY_NAME = 'masix';
7
7
  const PACKAGE_BIN_PATH = path.join(__dirname, 'prebuilt', BINARY_NAME);
8
8
  const PREBUILT_DIR = path.join(__dirname, 'prebuilt');
9
9
  const BUNDLED_PACKAGES_DIR = path.join(__dirname, 'packages', 'plugin-base');
10
- const TERMUX_PLATFORM = 'android-aarch64-termux';
11
- const BUNDLED_PLUGINS = [
12
- {
13
- plugin: 'discovery',
14
- version: '0.2.4',
15
- packageType: 'mcp_binary',
16
- file: path.join(BUNDLED_PACKAGES_DIR, 'discovery', '0.2.4', 'discovery-android-aarch64-termux.pkg'),
17
- adminOnly: false
18
- },
19
- {
20
- plugin: 'codex-backend',
21
- version: '0.1.4',
22
- packageType: 'library',
23
- file: path.join(BUNDLED_PACKAGES_DIR, 'codex-backend', '0.1.4', 'codex-backend-android-aarch64-termux.pkg'),
24
- adminOnly: true
25
- },
26
- {
27
- plugin: 'codex-tools',
28
- version: '0.1.3',
29
- packageType: 'mcp_binary',
30
- file: path.join(BUNDLED_PACKAGES_DIR, 'codex-tools', '0.1.3', 'codex-tools-android-aarch64-termux.pkg'),
31
- adminOnly: true
32
- }
33
- ];
34
-
35
10
  const isTermux =
36
11
  process.env.TERMUX_VERSION !== undefined ||
37
12
  process.env.PREFIX === '/data/data/com.termux/files/usr';
@@ -100,32 +75,20 @@ function ensureMasixHome() {
100
75
  }
101
76
 
102
77
  function installBundledPlugins() {
103
- for (const plugin of BUNDLED_PLUGINS) {
104
- if (!fs.existsSync(plugin.file)) {
105
- throw new Error(`Bundled package missing: ${plugin.file}`);
106
- }
78
+ if (!fs.existsSync(BUNDLED_PACKAGES_DIR)) {
79
+ throw new Error(`Bundled packages root missing: ${BUNDLED_PACKAGES_DIR}`);
80
+ }
107
81
 
108
- const args = [
82
+ execFileSync(
83
+ PACKAGE_BIN_PATH,
84
+ [
109
85
  'plugin',
110
- 'install-file',
111
- '--file',
112
- plugin.file,
113
- '--plugin',
114
- plugin.plugin,
115
- '--version',
116
- plugin.version,
117
- '--package-type',
118
- plugin.packageType,
119
- '--platform',
120
- TERMUX_PLATFORM
121
- ];
122
-
123
- if (plugin.adminOnly) {
124
- args.push('--admin-only');
125
- }
126
-
127
- execFileSync(PACKAGE_BIN_PATH, args, { stdio: 'inherit' });
128
- }
86
+ 'install-base',
87
+ '--packages-root',
88
+ BUNDLED_PACKAGES_DIR
89
+ ],
90
+ { stdio: 'inherit' }
91
+ );
129
92
  }
130
93
 
131
94
  ensureBinary();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mmmbuto/masix",
3
- "version": "0.4.1",
4
- "description": "Termux-first MIT automation runtime (Telegram, MCP, Cron) with bundled plugin-base packages and in-core STT",
3
+ "version": "0.4.3",
4
+ "description": "Termux-first MIT automation runtime for Android/Termux; Linux/macOS supported via source builds and Homebrew; bundled plugin-base packages auto-install or auto-build as needed",
5
5
  "main": "check-update.js",
6
6
  "files": [
7
7
  "README.md",
@@ -4,6 +4,8 @@
4
4
  "visibility": "plugin-base",
5
5
  "license": "MIT",
6
6
  "package_type": "mcp_binary",
7
+ "entrypoint": "masix-plugin-discovery",
8
+ "dependencies": [],
7
9
  "platforms": [
8
10
  {
9
11
  "id": "android-aarch64-termux",
@@ -21,6 +23,11 @@
21
23
  "sha256": "b850b42f50e142e65577a418ecace3fefbec32502dba96c71f82593b37e5a004"
22
24
  }
23
25
  ],
26
+ "source": {
27
+ "git_url": "https://github.com/DioNanos/MasiX.git",
28
+ "git_ref": "v0.4.2",
29
+ "subdir": "packages/plugin-base/discovery/source"
30
+ },
24
31
  "install": {
25
32
  "command": "masix plugin install-file --file <path-to-pkg> --plugin discovery --version 0.2.4 --package-type mcp_binary"
26
33
  },
@@ -0,0 +1,2 @@
1
+ d5180da4bf40e1d751d1a785bad7febba517d37df6be5183176b12ee31523a21 discovery-android-aarch64-termux.pkg
2
+ bfcc03af9258882631d86d5c4eafef613f765ede35b22808d26a6377ff6d3351 discovery-linux-x86_64.pkg
@@ -0,0 +1,34 @@
1
+ {
2
+ "plugin_id": "discovery",
3
+ "version": "0.3.0",
4
+ "visibility": "plugin-base",
5
+ "license": "MIT",
6
+ "package_type": "mcp_binary",
7
+ "entrypoint": "masix-plugin-discovery",
8
+ "dependencies": [],
9
+ "platforms": [
10
+ {
11
+ "id": "android-aarch64-termux",
12
+ "file": "discovery-android-aarch64-termux.pkg",
13
+ "sha256": "d5180da4bf40e1d751d1a785bad7febba517d37df6be5183176b12ee31523a21"
14
+ },
15
+ {
16
+ "id": "linux-x86_64",
17
+ "file": "discovery-linux-x86_64.pkg",
18
+ "sha256": "bfcc03af9258882631d86d5c4eafef613f765ede35b22808d26a6377ff6d3351"
19
+ }
20
+ ],
21
+ "source": {
22
+ "git_url": "https://github.com/DioNanos/MasiX.git",
23
+ "git_ref": "v0.4.3",
24
+ "subdir": "packages/plugin-base/discovery/source"
25
+ },
26
+ "install": {
27
+ "command": "masix plugin install-file --file <path-to-pkg> --plugin discovery --version 0.3.0 --package-type mcp_binary"
28
+ },
29
+ "notes": [
30
+ "Package usable without plugin server.",
31
+ "Tor support is optional and degrades gracefully to clearnet.",
32
+ "macOS currently relies on bundled source-build fallback until an Apple prebuilt artifact is refreshed."
33
+ ]
34
+ }
@@ -1,5 +1,12 @@
1
1
  # discovery Package Changelog
2
2
 
3
+ ## 0.3.0 - 2026-03-21
4
+
5
+ - Added anti-censorship transport support with Tor auto-detect and graceful clearnet fallback.
6
+ - Expanded SearXNG defaults and added `tor-search` plus `search-status`.
7
+ - Upgraded `torrent-search` with mirror catalog federation, provider filtering, richer metadata, and magnet cache support.
8
+ - Refreshed package artifacts for `linux-x86_64` and `android-aarch64-termux`; macOS currently uses bundled source-build fallback until the Apple prebuilt artifact is refreshed.
9
+
3
10
  ## 0.2.4 - 2026-03-05
4
11
 
5
12
  - Refreshed package artifacts for android-aarch64-termux, linux-x86_64, macos-aarch64.
@@ -1,12 +1,14 @@
1
1
  # discovery (`plugin-base`)
2
2
 
3
- `discovery` provides MCP discovery and fetch tools bundled with the MIT repository.
3
+ `discovery` provides MCP discovery, fetch, torrent metadata, and anti-censorship search tools bundled with the MIT repository.
4
4
 
5
5
  ## What It Is
6
6
 
7
7
  - Package type: `mcp_binary`
8
8
  - Visibility: `plugin-base`
9
9
  - Distribution: local `.pkg` install supported (`install-file`)
10
+ - Tor: optional, graceful fallback to clearnet
11
+ - Status tool: `search-status`
10
12
 
11
13
  ## Install Example
12
14
 
@@ -14,9 +16,9 @@ Termux:
14
16
 
15
17
  ```bash
16
18
  masix plugin install-file \
17
- --file packages/plugin-base/discovery/0.2.4/discovery-android-aarch64-termux.pkg \
19
+ --file packages/plugin-base/discovery/0.3.0/discovery-android-aarch64-termux.pkg \
18
20
  --plugin discovery \
19
- --version 0.2.4 \
21
+ --version 0.3.0 \
20
22
  --package-type mcp_binary
21
23
  ```
22
24
 
@@ -24,25 +26,32 @@ Linux:
24
26
 
25
27
  ```bash
26
28
  masix plugin install-file \
27
- --file packages/plugin-base/discovery/0.2.4/discovery-linux-x86_64.pkg \
29
+ --file packages/plugin-base/discovery/0.3.0/discovery-linux-x86_64.pkg \
28
30
  --plugin discovery \
29
- --version 0.2.4 \
31
+ --version 0.3.0 \
30
32
  --package-type mcp_binary
31
33
  ```
32
34
 
33
35
  macOS (Apple Silicon):
34
36
 
35
37
  ```bash
36
- masix plugin install-file \
37
- --file packages/plugin-base/discovery/0.2.4/discovery-macos-aarch64.pkg \
38
- --plugin discovery \
39
- --version 0.2.4 \
40
- --package-type mcp_binary
38
+ masix plugin install-base
41
39
  ```
42
40
 
41
+ `0.3.0` currently uses bundled source-build fallback on macOS until the Apple prebuilt artifact is refreshed.
42
+
43
43
  After install, run:
44
44
 
45
45
  ```bash
46
46
  masix plugin enable discovery
47
47
  masix restart
48
48
  ```
49
+
50
+ Available commands in `0.3.0`:
51
+
52
+ - `web-search`
53
+ - `tor-search`
54
+ - `web-fetch`
55
+ - `torrent-search`
56
+ - `torrent-extract`
57
+ - `search-status`
@@ -1,13 +1,14 @@
1
1
  [package]
2
2
  name = "masix-plugin-discovery"
3
- version = "0.2.4"
3
+ version = "0.3.0"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
 
7
7
  [dependencies]
8
8
  anyhow.workspace = true
9
9
  clap.workspace = true
10
- reqwest.workspace = true
10
+ dirs.workspace = true
11
+ reqwest = { workspace = true, features = ["socks"] }
11
12
  scraper.workspace = true
12
13
  serde.workspace = true
13
14
  serde_json.workspace = true
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "discovery",
3
3
  "name": "MasiX Discovery Tools",
4
- "version": "0.2.4",
4
+ "version": "0.3.0",
5
5
  "visibility": "plugin-base",
6
6
  "package_type": "mcp_binary",
7
7
  "entrypoint": "masix-plugin-discovery",
@@ -12,19 +12,24 @@
12
12
  ],
13
13
  "platforms_tested": [
14
14
  "linux-x86_64",
15
- "android-aarch64-termux",
16
- "macos-aarch64"
15
+ "android-aarch64-termux"
17
16
  ],
18
17
  "platforms_planned": [],
19
18
  "commands": [
20
19
  "web-search",
20
+ "tor-search",
21
21
  "web-fetch",
22
22
  "torrent-search",
23
23
  "torrent-extract",
24
+ "search-status",
25
+ "manifest",
24
26
  "serve-mcp"
25
27
  ],
26
28
  "tool_access": {
27
29
  "default_required_role": "user",
28
- "per_tool_required_role": {}
30
+ "per_tool_required_role": {
31
+ "tor_search": "user",
32
+ "search_status": "user"
33
+ }
29
34
  }
30
35
  }
@@ -0,0 +1,103 @@
1
+ use anyhow::{anyhow, Result};
2
+ use reqwest::Client;
3
+ use serde::Deserialize;
4
+ use std::net::IpAddr;
5
+ use std::time::Duration;
6
+
7
+ const DOH_TIMEOUT_SECS: u64 = 5;
8
+ const CLOUDFLARE_DOH: &str = "https://cloudflare-dns.com/dns-query";
9
+ const GOOGLE_DOH: &str = "https://dns.google/dns-query";
10
+ const DNS_TYPE_A: &str = "A";
11
+ const DNS_TYPE_AAAA: &str = "AAAA";
12
+
13
+ #[derive(Debug, Clone)]
14
+ pub struct DohResolver {
15
+ client: Client,
16
+ }
17
+
18
+ #[derive(Debug, Deserialize)]
19
+ struct DohResponse {
20
+ #[serde(default, rename = "Answer")]
21
+ answer: Vec<DohAnswer>,
22
+ }
23
+
24
+ #[derive(Debug, Deserialize)]
25
+ struct DohAnswer {
26
+ #[serde(default)]
27
+ data: String,
28
+ #[serde(default, rename = "type")]
29
+ record_type: u32,
30
+ }
31
+
32
+ impl DohResolver {
33
+ pub fn new(user_agent: &str) -> Result<Self> {
34
+ let client = Client::builder()
35
+ .user_agent(user_agent)
36
+ .timeout(Duration::from_secs(DOH_TIMEOUT_SECS))
37
+ .build()?;
38
+ Ok(Self { client })
39
+ }
40
+
41
+ pub async fn resolve(&self, hostname: &str) -> Result<Vec<IpAddr>> {
42
+ // Try IPv4 via Cloudflare first (most common case)
43
+ if let Ok(r) = self.resolve_via(CLOUDFLARE_DOH, hostname, DNS_TYPE_A).await {
44
+ if !r.is_empty() {
45
+ return Ok(r);
46
+ }
47
+ }
48
+ // Try IPv6 via Cloudflare
49
+ if let Ok(r) = self.resolve_via(CLOUDFLARE_DOH, hostname, DNS_TYPE_AAAA).await {
50
+ if !r.is_empty() {
51
+ return Ok(r);
52
+ }
53
+ }
54
+ // IPv4 via Google fallback
55
+ if let Ok(r) = self.resolve_via(GOOGLE_DOH, hostname, DNS_TYPE_A).await {
56
+ if !r.is_empty() {
57
+ return Ok(r);
58
+ }
59
+ }
60
+ // IPv6 via Google fallback
61
+ self.resolve_via(GOOGLE_DOH, hostname, DNS_TYPE_AAAA).await
62
+ }
63
+
64
+ pub async fn resolve_with_fallback(&self, hostname: &str) -> String {
65
+ self.resolve(hostname)
66
+ .await
67
+ .ok()
68
+ .and_then(|items| items.into_iter().next())
69
+ .map(|ip| ip.to_string())
70
+ .unwrap_or_else(|| hostname.to_string())
71
+ }
72
+
73
+ async fn resolve_via(&self, endpoint: &str, hostname: &str, dns_type: &str) -> Result<Vec<IpAddr>> {
74
+ let response = self
75
+ .client
76
+ .get(endpoint)
77
+ .query(&[("name", hostname), ("type", dns_type)])
78
+ .header("Accept", "application/dns-json")
79
+ .send()
80
+ .await?;
81
+
82
+ if !response.status().is_success() {
83
+ return Err(anyhow!(
84
+ "DoH resolver {} returned HTTP {}",
85
+ endpoint,
86
+ response.status()
87
+ ));
88
+ }
89
+
90
+ let payload: DohResponse = response.json().await?;
91
+ let mut output = Vec::new();
92
+ for answer in payload.answer {
93
+ // Accept A (1) and AAAA (28) records
94
+ if answer.record_type != 1 && answer.record_type != 28 {
95
+ continue;
96
+ }
97
+ if let Ok(ip) = answer.data.parse::<IpAddr>() {
98
+ output.push(ip);
99
+ }
100
+ }
101
+ Ok(output)
102
+ }
103
+ }
@@ -0,0 +1,113 @@
1
+ use anyhow::Result;
2
+ use serde::{Deserialize, Serialize};
3
+ use std::collections::HashMap;
4
+ use std::path::{Path, PathBuf};
5
+ use std::time::{SystemTime, UNIX_EPOCH};
6
+ use tokio::sync::Mutex;
7
+
8
+ const CACHE_TTL_SECS: u64 = 72 * 60 * 60;
9
+
10
+ #[derive(Debug, Clone, Serialize, Deserialize)]
11
+ struct CacheEntry {
12
+ magnet: String,
13
+ expires_at: u64,
14
+ }
15
+
16
+ #[derive(Debug)]
17
+ pub struct MagnetCache {
18
+ path: PathBuf,
19
+ entries: Mutex<HashMap<String, CacheEntry>>,
20
+ }
21
+
22
+ impl MagnetCache {
23
+ pub async fn new(data_dir: Option<&Path>) -> Result<Self> {
24
+ let root = resolve_cache_root(data_dir);
25
+ std::fs::create_dir_all(&root)?;
26
+ let path = root.join("magnet_cache.json");
27
+ let entries = if path.is_file() {
28
+ let content = std::fs::read_to_string(&path).unwrap_or_default();
29
+ serde_json::from_str(&content).unwrap_or_default()
30
+ } else {
31
+ HashMap::new()
32
+ };
33
+ let cache = Self {
34
+ path,
35
+ entries: Mutex::new(entries),
36
+ };
37
+ cache.cleanup_expired().await?;
38
+ Ok(cache)
39
+ }
40
+
41
+ pub async fn get(&self, url: &str) -> Option<String> {
42
+ let mut entries = self.entries.lock().await;
43
+ let now = now_unix_secs();
44
+ let value = entries.get(url).cloned();
45
+ match value {
46
+ Some(entry) if entry.expires_at > now => Some(entry.magnet),
47
+ Some(_) => {
48
+ entries.remove(url);
49
+ let _ = self.flush_locked(&entries);
50
+ None
51
+ }
52
+ None => None,
53
+ }
54
+ }
55
+
56
+ pub async fn set(&self, url: &str, magnet: &str) -> Result<()> {
57
+ let mut entries = self.entries.lock().await;
58
+ entries.insert(
59
+ url.to_string(),
60
+ CacheEntry {
61
+ magnet: magnet.to_string(),
62
+ expires_at: now_unix_secs().saturating_add(CACHE_TTL_SECS),
63
+ },
64
+ );
65
+ self.flush_locked(&entries)
66
+ }
67
+
68
+ pub async fn size(&self) -> usize {
69
+ self.entries.lock().await.len()
70
+ }
71
+
72
+ pub const fn ttl_hours() -> u64 {
73
+ 72
74
+ }
75
+
76
+ async fn cleanup_expired(&self) -> Result<()> {
77
+ let mut entries = self.entries.lock().await;
78
+ let now = now_unix_secs();
79
+ entries.retain(|_, entry| entry.expires_at > now);
80
+ self.flush_locked(&entries)
81
+ }
82
+
83
+ fn flush_locked(&self, entries: &HashMap<String, CacheEntry>) -> Result<()> {
84
+ let content = serde_json::to_string_pretty(entries)?;
85
+ let tmp = self.path.with_extension("json.tmp");
86
+ std::fs::write(&tmp, &content)?;
87
+ std::fs::rename(&tmp, &self.path)?;
88
+ Ok(())
89
+ }
90
+ }
91
+
92
+ fn resolve_cache_root(data_dir: Option<&Path>) -> PathBuf {
93
+ if let Ok(value) = std::env::var("MASIX_DATA_DIR") {
94
+ let trimmed = value.trim();
95
+ if !trimmed.is_empty() {
96
+ return PathBuf::from(trimmed).join("discovery");
97
+ }
98
+ }
99
+ if let Some(path) = data_dir {
100
+ return path.join("discovery");
101
+ }
102
+ dirs::home_dir()
103
+ .unwrap_or_else(|| PathBuf::from("."))
104
+ .join(".masix")
105
+ .join("discovery")
106
+ }
107
+
108
+ fn now_unix_secs() -> u64 {
109
+ SystemTime::now()
110
+ .duration_since(UNIX_EPOCH)
111
+ .map(|duration| duration.as_secs())
112
+ .unwrap_or(0)
113
+ }