superacli 1.1.2 → 1.1.4
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/LICENSE +22 -0
- package/README.md +10 -1
- package/__tests__/blogwatcher-plugin.test.js +157 -0
- package/__tests__/clix-plugin.test.js +143 -0
- package/__tests__/config.test.js +46 -1
- package/__tests__/himalaya-plugin.test.js +121 -0
- package/__tests__/mcp-adapter.test.js +79 -0
- package/__tests__/mcp-local.test.js +43 -1
- package/__tests__/mongosh-plugin.test.js +106 -0
- package/__tests__/mysql-plugin.test.js +94 -0
- package/__tests__/plugin-blogwatcher.test.js +55 -0
- package/__tests__/plugin-clix.test.js +51 -0
- package/__tests__/plugin-xurl.test.js +51 -0
- package/__tests__/server-config-service.test.js +8 -1
- package/__tests__/skills.test.js +26 -0
- package/__tests__/wacli-plugin.test.js +132 -0
- package/__tests__/xurl-plugin.test.js +176 -0
- package/cli/adapter-schema.js +7 -0
- package/cli/adapters/mcp.js +82 -20
- package/cli/config.js +65 -8
- package/cli/mcp-local.js +50 -4
- package/cli/plugin-install-guidance.js +100 -0
- package/cli/skills.js +55 -0
- package/cli/supercli.js +1 -1
- package/docs/features/adapters.md +6 -2
- package/docs/initial/mcp-local-mode.md +3 -0
- package/docs/skills-catalog.md +50 -0
- package/docs/supported-harnesses.md +20 -0
- package/package.json +2 -1
- package/plugins/blogwatcher/README.md +52 -0
- package/plugins/blogwatcher/plugin.json +195 -0
- package/plugins/blogwatcher/scripts/post-install.js +66 -0
- package/plugins/blogwatcher/scripts/post-uninstall.js +25 -0
- package/plugins/clix/README.md +44 -0
- package/plugins/clix/plugin.json +126 -0
- package/plugins/clix/scripts/post-install.js +66 -0
- package/plugins/clix/scripts/post-uninstall.js +25 -0
- package/plugins/himalaya/README.md +48 -0
- package/plugins/himalaya/plugin.json +157 -0
- package/plugins/mongosh/README.md +56 -0
- package/plugins/mongosh/plugin.json +88 -0
- package/plugins/mysql/README.md +48 -0
- package/plugins/mysql/plugin.json +64 -0
- package/plugins/plugins.json +63 -0
- package/plugins/wacli/README.md +52 -0
- package/plugins/wacli/plugin.json +260 -0
- package/plugins/xurl/README.md +52 -0
- package/plugins/xurl/plugin.json +239 -0
- package/plugins/xurl/scripts/post-install.js +66 -0
- package/plugins/xurl/scripts/post-uninstall.js +25 -0
- package/server/routes/mcp.js +30 -4
- package/server/services/configService.js +9 -1
- package/tests/test-blogwatcher-smoke.sh +48 -0
- package/tests/test-clix-smoke.sh +44 -0
- package/tests/test-himalaya-smoke.sh +47 -0
- package/tests/test-mcp-browser-use-smoke.sh +141 -0
- package/tests/test-mongosh-smoke.sh +40 -0
- package/tests/test-mysql-smoke.sh +37 -0
- package/tests/test-plugins-registry.js +35 -0
- package/tests/test-wacli-smoke.sh +46 -0
- package/tests/test-xurl-smoke.sh +46 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { addProvider, syncCatalog } = require("../../../cli/skills-catalog")
|
|
2
|
+
|
|
3
|
+
const OWNER = "xdevplatform"
|
|
4
|
+
const REPO = "xurl"
|
|
5
|
+
const REF = "main"
|
|
6
|
+
const SOURCE_REPO = `https://github.com/${OWNER}/${REPO}`
|
|
7
|
+
const RAW_BASE_URL = `https://raw.githubusercontent.com/${OWNER}/${REPO}/${REF}`
|
|
8
|
+
|
|
9
|
+
const CATALOG_FILES = [
|
|
10
|
+
{
|
|
11
|
+
id: "root.skill",
|
|
12
|
+
name: "xurl Agent Skill",
|
|
13
|
+
path: "SKILL.md",
|
|
14
|
+
description: "Agent-oriented guidance for safe xurl usage, shortcut commands, and raw X API access.",
|
|
15
|
+
tags: ["agents", "x", "api"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "root.readme",
|
|
19
|
+
name: "xurl Overview",
|
|
20
|
+
path: "README.md",
|
|
21
|
+
description: "Project overview, auth model, installation, and command examples for xurl.",
|
|
22
|
+
tags: ["overview", "x", "oauth"]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
function buildRemoteEntries() {
|
|
27
|
+
return CATALOG_FILES.map(file => ({
|
|
28
|
+
...file,
|
|
29
|
+
source_url: `${RAW_BASE_URL}/${file.path}`
|
|
30
|
+
}))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function run() {
|
|
34
|
+
const entries = buildRemoteEntries()
|
|
35
|
+
addProvider({
|
|
36
|
+
name: "xurl",
|
|
37
|
+
type: "remote_static",
|
|
38
|
+
enabled: true,
|
|
39
|
+
source_repo: SOURCE_REPO,
|
|
40
|
+
ref: REF,
|
|
41
|
+
entries
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const index = syncCatalog()
|
|
45
|
+
return {
|
|
46
|
+
provider: "xurl",
|
|
47
|
+
entries: entries.length,
|
|
48
|
+
synced_skills: Array.isArray(index.skills) ? index.skills.length : 0
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (require.main === module) {
|
|
53
|
+
try {
|
|
54
|
+
const result = run()
|
|
55
|
+
process.stdout.write(JSON.stringify(result))
|
|
56
|
+
} catch (err) {
|
|
57
|
+
process.stderr.write(err.message)
|
|
58
|
+
process.exit(1)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
CATALOG_FILES,
|
|
64
|
+
buildRemoteEntries,
|
|
65
|
+
run
|
|
66
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { removeProvider, syncCatalog } = require("../../../cli/skills-catalog")
|
|
2
|
+
|
|
3
|
+
function run() {
|
|
4
|
+
const removed = removeProvider("xurl")
|
|
5
|
+
const index = syncCatalog()
|
|
6
|
+
return {
|
|
7
|
+
provider: "xurl",
|
|
8
|
+
removed,
|
|
9
|
+
synced_skills: Array.isArray(index.skills) ? index.skills.length : 0
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (require.main === module) {
|
|
14
|
+
try {
|
|
15
|
+
const result = run()
|
|
16
|
+
process.stdout.write(JSON.stringify(result))
|
|
17
|
+
} catch (err) {
|
|
18
|
+
process.stderr.write(err.message)
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
run
|
|
25
|
+
}
|
package/server/routes/mcp.js
CHANGED
|
@@ -4,6 +4,23 @@ const { bumpVersion } = require("../services/configService")
|
|
|
4
4
|
|
|
5
5
|
const router = Router()
|
|
6
6
|
|
|
7
|
+
function sanitizeMcpPayload(payload, fallbackName) {
|
|
8
|
+
const name = typeof payload.name === "string" ? payload.name : fallbackName
|
|
9
|
+
if (!name) return null
|
|
10
|
+
const out = { name }
|
|
11
|
+
if (typeof payload.url === "string") out.url = payload.url
|
|
12
|
+
if (typeof payload.command === "string") out.command = payload.command
|
|
13
|
+
if (Array.isArray(payload.args)) out.args = payload.args.filter(v => typeof v === "string")
|
|
14
|
+
if (payload.headers && typeof payload.headers === "object" && !Array.isArray(payload.headers)) {
|
|
15
|
+
out.headers = Object.fromEntries(Object.entries(payload.headers).filter(([k, v]) => typeof k === "string" && typeof v === "string"))
|
|
16
|
+
}
|
|
17
|
+
if (payload.env && typeof payload.env === "object" && !Array.isArray(payload.env)) {
|
|
18
|
+
out.env = Object.fromEntries(Object.entries(payload.env).filter(([k, v]) => typeof k === "string" && typeof v === "string"))
|
|
19
|
+
}
|
|
20
|
+
if (typeof payload.timeout_ms === "number" && payload.timeout_ms > 0) out.timeout_ms = payload.timeout_ms
|
|
21
|
+
return out
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
async function getAllMCPs() {
|
|
8
25
|
const storage = getStorage()
|
|
9
26
|
const keys = await storage.listKeys("mcp:")
|
|
@@ -28,9 +45,13 @@ router.get("/", async (req, res) => {
|
|
|
28
45
|
router.post("/", async (req, res) => {
|
|
29
46
|
try {
|
|
30
47
|
const storage = getStorage()
|
|
31
|
-
const
|
|
48
|
+
const sanitized = sanitizeMcpPayload(req.body || {})
|
|
49
|
+
if (!sanitized || (!sanitized.url && !sanitized.command)) {
|
|
50
|
+
return res.status(400).json({ error: "MCP server requires name and one of: url or command" })
|
|
51
|
+
}
|
|
52
|
+
const { name } = sanitized
|
|
32
53
|
const key = `mcp:${name}`
|
|
33
|
-
const doc = { _id: key,
|
|
54
|
+
const doc = { _id: key, ...sanitized, createdAt: new Date() }
|
|
34
55
|
await storage.set(key, doc)
|
|
35
56
|
await bumpVersion()
|
|
36
57
|
if (req.headers["content-type"]?.includes("urlencoded")) {
|
|
@@ -47,14 +68,19 @@ router.put("/:id", async (req, res) => {
|
|
|
47
68
|
try {
|
|
48
69
|
const storage = getStorage()
|
|
49
70
|
const id = decodeURIComponent(req.params.id)
|
|
50
|
-
const
|
|
71
|
+
const currentName = id.startsWith("mcp:") ? id.slice(4) : undefined
|
|
72
|
+
const sanitized = sanitizeMcpPayload(req.body || {}, currentName)
|
|
73
|
+
if (!sanitized || (!sanitized.url && !sanitized.command)) {
|
|
74
|
+
return res.status(400).json({ error: "MCP server requires name and one of: url or command" })
|
|
75
|
+
}
|
|
76
|
+
const { name } = sanitized
|
|
51
77
|
|
|
52
78
|
const newKey = `mcp:${name}`
|
|
53
79
|
if (newKey !== id) {
|
|
54
80
|
await storage.delete(id)
|
|
55
81
|
}
|
|
56
82
|
|
|
57
|
-
const doc = { _id: newKey,
|
|
83
|
+
const doc = { _id: newKey, ...sanitized }
|
|
58
84
|
await storage.set(newKey, doc)
|
|
59
85
|
await bumpVersion()
|
|
60
86
|
res.json({ ok: true })
|
|
@@ -15,7 +15,15 @@ async function getCLIConfig() {
|
|
|
15
15
|
ttl: 3600,
|
|
16
16
|
mcp_servers: mcpServers
|
|
17
17
|
.filter(Boolean)
|
|
18
|
-
.map(s => ({
|
|
18
|
+
.map(s => ({
|
|
19
|
+
name: s.name,
|
|
20
|
+
url: s.url,
|
|
21
|
+
command: s.command,
|
|
22
|
+
args: Array.isArray(s.args) ? s.args : undefined,
|
|
23
|
+
headers: s.headers && typeof s.headers === "object" ? s.headers : undefined,
|
|
24
|
+
env: s.env && typeof s.env === "object" ? s.env : undefined,
|
|
25
|
+
timeout_ms: typeof s.timeout_ms === "number" ? s.timeout_ms : undefined
|
|
26
|
+
})),
|
|
19
27
|
specs: specs
|
|
20
28
|
.filter(Boolean)
|
|
21
29
|
.map(s => ({ name: s.name, url: s.url, auth: s.auth || "none" })),
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
CLI="${ROOT_DIR}/cli/supercli.js"
|
|
7
|
+
|
|
8
|
+
echo "== blogwatcher smoke test =="
|
|
9
|
+
|
|
10
|
+
if ! command -v blogwatcher >/dev/null 2>&1; then
|
|
11
|
+
echo "blogwatcher not found in PATH."
|
|
12
|
+
echo "Install it first and verify with: blogwatcher --version"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
17
|
+
echo "curl not found in PATH"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
22
|
+
echo "Node.js not found in PATH"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
TEMP_HOME="$(mktemp -d)"
|
|
27
|
+
trap 'rm -rf "${TEMP_HOME}"' EXIT
|
|
28
|
+
export HOME="${TEMP_HOME}"
|
|
29
|
+
|
|
30
|
+
echo "Installing/refreshing blogwatcher plugin..."
|
|
31
|
+
node "${CLI}" plugins install blogwatcher --on-conflict replace --json >/dev/null
|
|
32
|
+
|
|
33
|
+
echo "Checking indexed skills..."
|
|
34
|
+
node "${CLI}" skills list --catalog --provider blogwatcher --json
|
|
35
|
+
node "${CLI}" skills get blogwatcher:root.skill >/dev/null
|
|
36
|
+
|
|
37
|
+
echo "Running wrapped version command..."
|
|
38
|
+
node "${CLI}" blogwatcher cli version --json
|
|
39
|
+
|
|
40
|
+
echo "Running wrapped add/list/remove flow..."
|
|
41
|
+
node "${CLI}" blogwatcher blogs add --name "Example" --url "https://example.com/blog" --feed-url "https://example.com/feed.xml" --json
|
|
42
|
+
node "${CLI}" blogwatcher blogs list --json
|
|
43
|
+
node "${CLI}" blogwatcher blogs remove --name "Example" --json
|
|
44
|
+
|
|
45
|
+
echo "Running passthrough smoke test..."
|
|
46
|
+
node "${CLI}" blogwatcher --version
|
|
47
|
+
|
|
48
|
+
echo "Smoke test completed."
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
CLI="${ROOT_DIR}/cli/supercli.js"
|
|
7
|
+
|
|
8
|
+
echo "== clix smoke test =="
|
|
9
|
+
|
|
10
|
+
if ! command -v clix >/dev/null 2>&1; then
|
|
11
|
+
echo "clix not found in PATH."
|
|
12
|
+
echo "Install it first and verify with: clix auth status --json"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
17
|
+
echo "curl not found in PATH"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
22
|
+
echo "Node.js not found in PATH"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
echo "Installing/refreshing clix plugin..."
|
|
27
|
+
node "${CLI}" plugins install clix --on-conflict replace --json >/dev/null
|
|
28
|
+
|
|
29
|
+
echo "Checking indexed skills..."
|
|
30
|
+
node "${CLI}" skills list --catalog --provider clix --json
|
|
31
|
+
node "${CLI}" skills get clix:root.skill >/dev/null
|
|
32
|
+
|
|
33
|
+
echo "Running safe wrapped commands..."
|
|
34
|
+
node "${CLI}" clix auth status --json
|
|
35
|
+
|
|
36
|
+
if [[ "${CLIX_LIVE_READONLY:-}" == "1" ]]; then
|
|
37
|
+
echo "Running optional live read-only commands..."
|
|
38
|
+
node "${CLI}" clix timeline list --count 10 --json
|
|
39
|
+
node "${CLI}" clix posts search --query "from:openai" --count 10 --json
|
|
40
|
+
else
|
|
41
|
+
echo "Skipping live read-only calls; set CLIX_LIVE_READONLY=1 to enable them."
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
echo "Smoke test completed."
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
CLI="${ROOT_DIR}/cli/supercli.js"
|
|
7
|
+
|
|
8
|
+
echo "== himalaya smoke test =="
|
|
9
|
+
|
|
10
|
+
if ! command -v himalaya >/dev/null 2>&1; then
|
|
11
|
+
echo "himalaya not found in PATH."
|
|
12
|
+
echo "Install it first and verify with: himalaya --version"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
17
|
+
echo "Node.js not found in PATH"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "Installing/refreshing himalaya plugin..."
|
|
22
|
+
node "${CLI}" plugins install himalaya --on-conflict replace --json >/dev/null
|
|
23
|
+
|
|
24
|
+
echo "Running wrapped version command..."
|
|
25
|
+
node "${CLI}" himalaya cli version --json
|
|
26
|
+
|
|
27
|
+
echo "Running wrapped account list command..."
|
|
28
|
+
node "${CLI}" himalaya account list --json
|
|
29
|
+
|
|
30
|
+
if [[ -n "${HIMALAYA_ACCOUNT:-}" ]]; then
|
|
31
|
+
echo "Running wrapped folder list command..."
|
|
32
|
+
node "${CLI}" himalaya folder list --account "${HIMALAYA_ACCOUNT}" --json
|
|
33
|
+
|
|
34
|
+
if [[ -n "${HIMALAYA_FOLDER:-}" ]]; then
|
|
35
|
+
echo "Running wrapped envelope list command..."
|
|
36
|
+
node "${CLI}" himalaya envelope list --account "${HIMALAYA_ACCOUNT}" --folder "${HIMALAYA_FOLDER}" --page 1 --json
|
|
37
|
+
else
|
|
38
|
+
echo "Skipping envelope list test; set HIMALAYA_FOLDER to enable it."
|
|
39
|
+
fi
|
|
40
|
+
else
|
|
41
|
+
echo "Skipping account-scoped tests; set HIMALAYA_ACCOUNT to enable them."
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
echo "Running passthrough smoke test..."
|
|
45
|
+
node "${CLI}" himalaya --help
|
|
46
|
+
|
|
47
|
+
echo "Smoke test completed."
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
CLI="${ROOT_DIR}/cli/supercli.js"
|
|
7
|
+
|
|
8
|
+
API_KEY=""
|
|
9
|
+
if [[ "${1:-}" == "--api-key" ]]; then
|
|
10
|
+
API_KEY="${2:-}"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
14
|
+
echo "Node.js not found in PATH"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
19
|
+
echo "python3 not found in PATH"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
TMP_HOME="$(mktemp -d)"
|
|
24
|
+
cleanup() {
|
|
25
|
+
rm -rf "${TMP_HOME}"
|
|
26
|
+
}
|
|
27
|
+
trap cleanup EXIT
|
|
28
|
+
|
|
29
|
+
echo "== MCP browser-use smoke test =="
|
|
30
|
+
echo "Using isolated HOME: ${TMP_HOME}"
|
|
31
|
+
|
|
32
|
+
export HOME="${TMP_HOME}"
|
|
33
|
+
|
|
34
|
+
echo "[1/3] Add local stdio MCP server with server-side args/env..."
|
|
35
|
+
node "${CLI}" mcp add mock-bridge \
|
|
36
|
+
--command python3 \
|
|
37
|
+
--args-json '["-c","import json, os, sys; payload=json.load(sys.stdin); out={\"argv\": sys.argv[1:], \"env\": {\"BROWSER_USE_API_KEY\": os.environ.get(\"BROWSER_USE_API_KEY\"), \"SERVER_ONLY\": os.environ.get(\"SERVER_ONLY\"), \"CMD_ONLY\": os.environ.get(\"CMD_ONLY\")}, \"payload\": payload}; print(json.dumps(out))","--server-arg"]' \
|
|
38
|
+
--env-json '{"BROWSER_USE_API_KEY":"server-key","SERVER_ONLY":"1"}' \
|
|
39
|
+
--headers-json '{"X-Server":"1"}' \
|
|
40
|
+
--json >/dev/null
|
|
41
|
+
|
|
42
|
+
echo "[2/3] Inject command using named MCP server + client-side args/env..."
|
|
43
|
+
python3 - "${HOME}/.supercli/config.json" <<'PY'
|
|
44
|
+
import json
|
|
45
|
+
import sys
|
|
46
|
+
|
|
47
|
+
path = sys.argv[1]
|
|
48
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
49
|
+
cfg = json.load(f)
|
|
50
|
+
|
|
51
|
+
cmd = {
|
|
52
|
+
"_id": "command:ai.browser.probe",
|
|
53
|
+
"namespace": "ai",
|
|
54
|
+
"resource": "browser",
|
|
55
|
+
"action": "probe",
|
|
56
|
+
"description": "Smoke: MCP merge wiring",
|
|
57
|
+
"adapter": "mcp",
|
|
58
|
+
"adapterConfig": {
|
|
59
|
+
"server": "mock-bridge",
|
|
60
|
+
"tool": "probe",
|
|
61
|
+
"args": ["--cmd-arg"],
|
|
62
|
+
"env": {
|
|
63
|
+
"BROWSER_USE_API_KEY": "cmd-key",
|
|
64
|
+
"CMD_ONLY": "1"
|
|
65
|
+
},
|
|
66
|
+
"headers": {
|
|
67
|
+
"X-Cmd": "1"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"args": []
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
commands = cfg.get("commands") or []
|
|
74
|
+
found = False
|
|
75
|
+
for i, existing in enumerate(commands):
|
|
76
|
+
if (
|
|
77
|
+
isinstance(existing, dict)
|
|
78
|
+
and existing.get("namespace") == "ai"
|
|
79
|
+
and existing.get("resource") == "browser"
|
|
80
|
+
and existing.get("action") == "probe"
|
|
81
|
+
):
|
|
82
|
+
commands[i] = cmd
|
|
83
|
+
found = True
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
if not found:
|
|
87
|
+
commands.append(cmd)
|
|
88
|
+
|
|
89
|
+
cfg["commands"] = commands
|
|
90
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
91
|
+
json.dump(cfg, f, indent=2)
|
|
92
|
+
PY
|
|
93
|
+
|
|
94
|
+
echo "[3/3] Execute command and assert merged args/env reached process..."
|
|
95
|
+
RESULT="$(node "${CLI}" ai browser probe --json)"
|
|
96
|
+
python3 - "${RESULT}" <<'PY'
|
|
97
|
+
import json
|
|
98
|
+
import sys
|
|
99
|
+
|
|
100
|
+
envelope = json.loads(sys.argv[1])
|
|
101
|
+
data = envelope.get("data") or {}
|
|
102
|
+
argv = data.get("argv") or []
|
|
103
|
+
env = data.get("env") or {}
|
|
104
|
+
payload = data.get("payload") or {}
|
|
105
|
+
|
|
106
|
+
assert "--server-arg" in argv, "missing server-side arg"
|
|
107
|
+
assert "--cmd-arg" in argv, "missing client-side arg"
|
|
108
|
+
assert env.get("SERVER_ONLY") == "1", "missing server env"
|
|
109
|
+
assert env.get("CMD_ONLY") == "1", "missing client env"
|
|
110
|
+
assert env.get("BROWSER_USE_API_KEY") == "cmd-key", "client env should override server env"
|
|
111
|
+
assert payload.get("tool") == "probe", "tool mismatch"
|
|
112
|
+
print("Merged stdio args/env execution checks passed.")
|
|
113
|
+
PY
|
|
114
|
+
|
|
115
|
+
if [[ -n "${API_KEY}" ]]; then
|
|
116
|
+
echo "Optional browser-use config check with provided API key..."
|
|
117
|
+
node "${CLI}" mcp add browser-use \
|
|
118
|
+
--command npx \
|
|
119
|
+
--args-json "[\"mcp-remote\",\"https://api.browser-use.com/mcp\",\"--header\",\"X-Browser-Use-API-Key: ${API_KEY}\"]" \
|
|
120
|
+
--env-json "{\"BROWSER_USE_API_KEY\":\"${API_KEY}\"}" \
|
|
121
|
+
--json >/dev/null
|
|
122
|
+
|
|
123
|
+
LIST_JSON="$(node "${CLI}" mcp list --json)"
|
|
124
|
+
python3 - "${LIST_JSON}" <<'PY'
|
|
125
|
+
import json
|
|
126
|
+
import sys
|
|
127
|
+
|
|
128
|
+
rows = (json.loads(sys.argv[1]) or {}).get("mcp_servers") or []
|
|
129
|
+
browser = next((r for r in rows if isinstance(r, dict) and r.get("name") == "browser-use"), None)
|
|
130
|
+
assert browser is not None, "browser-use server not found"
|
|
131
|
+
assert browser.get("command") == "npx", "browser-use command mismatch"
|
|
132
|
+
args = browser.get("args") or []
|
|
133
|
+
assert len(args) >= 2 and args[0] == "mcp-remote", "browser-use args missing mcp-remote"
|
|
134
|
+
assert args[1] == "https://api.browser-use.com/mcp", "browser-use URL mismatch"
|
|
135
|
+
print("browser-use registration checks passed.")
|
|
136
|
+
PY
|
|
137
|
+
else
|
|
138
|
+
echo "Skipping browser-use API-key registration check (pass --api-key <key> to enable)."
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
echo "Smoke test completed."
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
CLI="${ROOT_DIR}/cli/supercli.js"
|
|
7
|
+
|
|
8
|
+
echo "== mongosh smoke test =="
|
|
9
|
+
|
|
10
|
+
if ! command -v mongosh >/dev/null 2>&1; then
|
|
11
|
+
echo "mongosh not found in PATH."
|
|
12
|
+
echo "Install it first and verify with: mongosh --version"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
17
|
+
echo "Node.js not found in PATH"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "Installing/refreshing mongosh plugin..."
|
|
22
|
+
node "${CLI}" plugins install mongosh --on-conflict replace --json >/dev/null
|
|
23
|
+
|
|
24
|
+
echo "Running wrapped version command..."
|
|
25
|
+
node "${CLI}" mongosh cli version --json
|
|
26
|
+
|
|
27
|
+
if [[ -n "${MONGOSH_HOST:-}" ]]; then
|
|
28
|
+
echo "Running wrapped server ping command..."
|
|
29
|
+
node "${CLI}" mongosh server ping --host "${MONGOSH_HOST}" --json
|
|
30
|
+
elif [[ -n "${MONGODB_URI:-}" ]]; then
|
|
31
|
+
echo "Running passthrough ping command with MONGODB_URI..."
|
|
32
|
+
node "${CLI}" mongosh "${MONGODB_URI}" --quiet --json=relaxed --eval "db.adminCommand({ ping: 1 })" --json
|
|
33
|
+
else
|
|
34
|
+
echo "Skipping live ping test; set MONGOSH_HOST or MONGODB_URI to enable it."
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
echo "Running passthrough smoke test..."
|
|
38
|
+
node "${CLI}" mongosh --help
|
|
39
|
+
|
|
40
|
+
echo "Smoke test completed."
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
CLI="${ROOT_DIR}/cli/supercli.js"
|
|
7
|
+
|
|
8
|
+
echo "== mysql smoke test =="
|
|
9
|
+
|
|
10
|
+
if ! command -v mysql >/dev/null 2>&1; then
|
|
11
|
+
echo "mysql not found in PATH."
|
|
12
|
+
echo "Install it first and verify with: mysql --version"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
17
|
+
echo "Node.js not found in PATH"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "Installing/refreshing mysql plugin..."
|
|
22
|
+
node "${CLI}" plugins install mysql --on-conflict replace --json >/dev/null
|
|
23
|
+
|
|
24
|
+
echo "Running wrapped version command..."
|
|
25
|
+
node "${CLI}" mysql cli version --json
|
|
26
|
+
|
|
27
|
+
if [[ -n "${MYSQL_HOST:-}" && -n "${MYSQL_USER:-}" && -n "${MYSQL_DATABASE:-}" ]]; then
|
|
28
|
+
echo "Running wrapped query command..."
|
|
29
|
+
node "${CLI}" mysql query execute --execute "select 1" --host "${MYSQL_HOST}" --user "${MYSQL_USER}" --database "${MYSQL_DATABASE}" --json
|
|
30
|
+
else
|
|
31
|
+
echo "Skipping live query test; set MYSQL_HOST, MYSQL_USER, and MYSQL_DATABASE to enable it."
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
echo "Running passthrough smoke test..."
|
|
35
|
+
node "${CLI}" mysql --help
|
|
36
|
+
|
|
37
|
+
echo "Smoke test completed."
|
|
@@ -178,6 +178,41 @@ assert(nextestExplore.ok, "nextest explore should succeed")
|
|
|
178
178
|
const nextestExploreData = JSON.parse(nextestExplore.output)
|
|
179
179
|
assert(nextestExploreData.plugins.some(p => p.name === "nextest"), "explore filters should find nextest")
|
|
180
180
|
|
|
181
|
+
const mysqlExplore = runNoServer("plugins explore --name mysql --tags sql --json")
|
|
182
|
+
assert(mysqlExplore.ok, "mysql explore should succeed")
|
|
183
|
+
const mysqlExploreData = JSON.parse(mysqlExplore.output)
|
|
184
|
+
assert(mysqlExploreData.plugins.some(p => p.name === "mysql"), "explore filters should find mysql")
|
|
185
|
+
|
|
186
|
+
const mongoshExplore = runNoServer("plugins explore --name mongosh --tags mongodb --json")
|
|
187
|
+
assert(mongoshExplore.ok, "mongosh explore should succeed")
|
|
188
|
+
const mongoshExploreData = JSON.parse(mongoshExplore.output)
|
|
189
|
+
assert(mongoshExploreData.plugins.some(p => p.name === "mongosh"), "explore filters should find mongosh")
|
|
190
|
+
|
|
191
|
+
const blogwatcherExplore = runNoServer("plugins explore --name blogwatcher --tags rss --json")
|
|
192
|
+
assert(blogwatcherExplore.ok, "blogwatcher explore should succeed")
|
|
193
|
+
const blogwatcherExploreData = JSON.parse(blogwatcherExplore.output)
|
|
194
|
+
assert(blogwatcherExploreData.plugins.some(p => p.name === "blogwatcher"), "explore filters should find blogwatcher")
|
|
195
|
+
|
|
196
|
+
const himalayaExplore = runNoServer("plugins explore --name himalaya --tags email --json")
|
|
197
|
+
assert(himalayaExplore.ok, "himalaya explore should succeed")
|
|
198
|
+
const himalayaExploreData = JSON.parse(himalayaExplore.output)
|
|
199
|
+
assert(himalayaExploreData.plugins.some(p => p.name === "himalaya"), "explore filters should find himalaya")
|
|
200
|
+
|
|
201
|
+
const wacliExplore = runNoServer("plugins explore --name wacli --tags whatsapp --json")
|
|
202
|
+
assert(wacliExplore.ok, "wacli explore should succeed")
|
|
203
|
+
const wacliExploreData = JSON.parse(wacliExplore.output)
|
|
204
|
+
assert(wacliExploreData.plugins.some(p => p.name === "wacli"), "explore filters should find wacli")
|
|
205
|
+
|
|
206
|
+
const xurlExplore = runNoServer("plugins explore --name xurl --tags twitter --json")
|
|
207
|
+
assert(xurlExplore.ok, "xurl explore should succeed")
|
|
208
|
+
const xurlExploreData = JSON.parse(xurlExplore.output)
|
|
209
|
+
assert(xurlExploreData.plugins.some(p => p.name === "xurl"), "explore filters should find xurl")
|
|
210
|
+
|
|
211
|
+
const clixExplore = runNoServer("plugins explore --name clix --tags agents --json")
|
|
212
|
+
assert(clixExplore.ok, "clix explore should succeed")
|
|
213
|
+
const clixExploreData = JSON.parse(clixExplore.output)
|
|
214
|
+
assert(clixExploreData.plugins.some(p => p.name === "clix"), "explore filters should find clix")
|
|
215
|
+
|
|
181
216
|
const clineExplore = runNoServer("plugins explore --name cline --tags streaming --json")
|
|
182
217
|
assert(clineExplore.ok, "cline explore should succeed")
|
|
183
218
|
const clineExploreData = JSON.parse(clineExplore.output)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
CLI="${ROOT_DIR}/cli/supercli.js"
|
|
7
|
+
|
|
8
|
+
echo "== wacli smoke test =="
|
|
9
|
+
|
|
10
|
+
if ! command -v wacli >/dev/null 2>&1; then
|
|
11
|
+
echo "wacli not found in PATH."
|
|
12
|
+
echo "Install it first and verify with: wacli --version"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
17
|
+
echo "Node.js not found in PATH"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "Installing/refreshing wacli plugin..."
|
|
22
|
+
node "${CLI}" plugins install wacli --on-conflict replace --json >/dev/null
|
|
23
|
+
|
|
24
|
+
echo "Running wrapped version command..."
|
|
25
|
+
node "${CLI}" wacli cli version --json
|
|
26
|
+
|
|
27
|
+
TEMP_STORE="$(mktemp -d)"
|
|
28
|
+
trap 'rm -rf "${TEMP_STORE}"' EXIT
|
|
29
|
+
|
|
30
|
+
echo "Running safe diagnostics..."
|
|
31
|
+
node "${CLI}" wacli doctor run --store "${TEMP_STORE}" --json
|
|
32
|
+
node "${CLI}" wacli auth status --store "${TEMP_STORE}" --json
|
|
33
|
+
|
|
34
|
+
if [[ -n "${WACLI_STORE:-}" ]]; then
|
|
35
|
+
echo "Running read-only store-backed commands..."
|
|
36
|
+
node "${CLI}" wacli chats list --store "${WACLI_STORE}" --json
|
|
37
|
+
if [[ -n "${WACLI_CHAT_JID:-}" ]]; then
|
|
38
|
+
node "${CLI}" wacli messages list --store "${WACLI_STORE}" --chat "${WACLI_CHAT_JID}" --limit 10 --json
|
|
39
|
+
else
|
|
40
|
+
echo "Skipping messages list test; set WACLI_CHAT_JID to enable it."
|
|
41
|
+
fi
|
|
42
|
+
else
|
|
43
|
+
echo "Skipping store-backed chat/message tests; set WACLI_STORE to enable them."
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
echo "Smoke test completed."
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
CLI="${ROOT_DIR}/cli/supercli.js"
|
|
7
|
+
|
|
8
|
+
echo "== xurl smoke test =="
|
|
9
|
+
|
|
10
|
+
if ! command -v xurl >/dev/null 2>&1; then
|
|
11
|
+
echo "xurl not found in PATH."
|
|
12
|
+
echo "Install it first and verify with: xurl version"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
17
|
+
echo "curl not found in PATH"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
22
|
+
echo "Node.js not found in PATH"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
echo "Installing/refreshing xurl plugin..."
|
|
27
|
+
node "${CLI}" plugins install xurl --on-conflict replace --json >/dev/null
|
|
28
|
+
|
|
29
|
+
echo "Checking indexed skills..."
|
|
30
|
+
node "${CLI}" skills list --catalog --provider xurl --json
|
|
31
|
+
node "${CLI}" skills get xurl:root.skill >/dev/null
|
|
32
|
+
|
|
33
|
+
echo "Running safe wrapped commands..."
|
|
34
|
+
node "${CLI}" xurl cli version --json
|
|
35
|
+
node "${CLI}" xurl auth status --json
|
|
36
|
+
node "${CLI}" xurl apps list --json
|
|
37
|
+
|
|
38
|
+
if [[ "${XURL_LIVE_READONLY:-}" == "1" ]]; then
|
|
39
|
+
echo "Running optional live read-only commands..."
|
|
40
|
+
node "${CLI}" xurl account whoami --json
|
|
41
|
+
node "${CLI}" xurl posts search --query "from:XDevelopers" --max-results 5 --json
|
|
42
|
+
else
|
|
43
|
+
echo "Skipping live read-only calls; set XURL_LIVE_READONLY=1 to enable them."
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
echo "Smoke test completed."
|