mercury-tech-cli 1.0.0-beta.2 → 1.0.0-beta.20
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 +14 -8
- package/bin/mercury.js +173 -0
- package/package.json +17 -12
- package/bin/mercury +0 -55
package/README.md
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
# mercury-tech-cli
|
|
2
2
|
|
|
3
|
-
The `mercury` command from npm or bun
|
|
3
|
+
The `mercury` command from npm or bun, on macOS (Apple silicon and Intel), Linux x64 and
|
|
4
|
+
Windows x64.
|
|
4
5
|
|
|
5
6
|
```sh
|
|
6
7
|
bun install -g mercury-tech-cli # or: npm install -g mercury-tech-cli
|
|
7
8
|
mercury
|
|
8
9
|
```
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
SHA-256, and runs the archive's own user-local installer (no administrator
|
|
14
|
-
access: `~/.mercury/versions/` and `~/.local/bin/mercury`). Later runs go
|
|
15
|
-
straight to the installed command; `mercury update` moves between versions.
|
|
11
|
+
The first time you run `mercury`, it downloads the release archive for your
|
|
12
|
+
platform from GitHub Releases, checks its SHA-256 against the release's
|
|
13
|
+
checksum list, and runs the installer included in the archive.
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
You don't need administrator access. Versions are stored under
|
|
16
|
+
`~/.mercury/versions/`, with the `mercury` command in `~/.local/bin` on
|
|
17
|
+
macOS and Linux, or `%LOCALAPPDATA%\Mercury\bin` on Windows.
|
|
18
|
+
|
|
19
|
+
After setup, running `mercury` starts the installed version using its
|
|
20
|
+
bundled Node runtime. Your system's Node version only matters for the
|
|
21
|
+
launcher. Use `mercury update` to change versions.
|
|
22
|
+
|
|
23
|
+
The PowerShell installer is the other Windows route:
|
|
18
24
|
|
|
19
25
|
```powershell
|
|
20
26
|
irm https://mercury-cli.ai/install.ps1 | iex
|
package/bin/mercury.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
// mercury-tech-cli — the `mercury` command from npm or bun, on macOS (Apple
|
|
4
|
+
// silicon and Intel), Linux x64 and Windows x64.
|
|
5
|
+
//
|
|
6
|
+
// Mercury runs on its own bundled runtime, never on npm's or bun's engine, so
|
|
7
|
+
// this launcher only fetches: on first run it downloads the release archive
|
|
8
|
+
// for this machine from GitHub Releases, verifies its SHA-256, unpacks it,
|
|
9
|
+
// and runs the archive's own `mercury install` (user-local, no administrator
|
|
10
|
+
// access: ~/.mercury/versions + ~/.local/bin/mercury; on Windows
|
|
11
|
+
// %LOCALAPPDATA%\Mercury\bin\mercury.cmd). Every later run hands straight to
|
|
12
|
+
// the installed command. Rerunning is safe.
|
|
13
|
+
//
|
|
14
|
+
// Knobs (all optional):
|
|
15
|
+
// MERCURY_VERSION a release tag to install instead of this package's
|
|
16
|
+
// MERCURY_INSTALL_BASE_URL where the assets live (https:// or file://)
|
|
17
|
+
// MERCURY_INSTALL_ARGS extra `mercury install` args, e.g. --force
|
|
18
|
+
// MERCURYCLI_PIN=1 run this package's release even when another
|
|
19
|
+
// version is installed and active (by default an
|
|
20
|
+
// installed Mercury is never downgraded; `mercury
|
|
21
|
+
// update` moves versions)
|
|
22
|
+
const fs = require('node:fs')
|
|
23
|
+
const os = require('node:os')
|
|
24
|
+
const path = require('node:path')
|
|
25
|
+
const crypto = require('node:crypto')
|
|
26
|
+
const { spawn, spawnSync } = require('node:child_process')
|
|
27
|
+
|
|
28
|
+
const PACKAGE_RELEASE = '1.0.0-beta.20'
|
|
29
|
+
const REPO = 'Whq02/MercuryCLI'
|
|
30
|
+
const WIN = process.platform === 'win32'
|
|
31
|
+
const release = (process.env.MERCURY_VERSION || PACKAGE_RELEASE).replace(/^v/, '')
|
|
32
|
+
const tag = `v${release}`
|
|
33
|
+
|
|
34
|
+
// The same roots the product's own stable command resolves.
|
|
35
|
+
const versionsDir = process.env.MERCURY_VERSIONS_DIR
|
|
36
|
+
|| path.join(process.env.MERCURY_CONFIG_DIR || process.env.MERCURY_HOME || path.join(os.homedir(), '.mercury'), 'versions')
|
|
37
|
+
const stable = WIN
|
|
38
|
+
? path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'Mercury', 'bin', 'mercury.cmd')
|
|
39
|
+
: path.join(os.homedir(), '.local', 'bin', 'mercury')
|
|
40
|
+
|
|
41
|
+
function say(msg) { process.stderr.write(`mercury: ${msg}\n`) }
|
|
42
|
+
function die(msg) { say(msg); process.exit(1) }
|
|
43
|
+
|
|
44
|
+
function installedVersion() {
|
|
45
|
+
try { return fs.readFileSync(path.join(versionsDir, 'current.txt'), 'utf8').split(/\r?\n/)[0].trim() } catch { return '' }
|
|
46
|
+
}
|
|
47
|
+
function stableExists() {
|
|
48
|
+
try { fs.accessSync(stable, WIN ? fs.constants.F_OK : fs.constants.X_OK); return true } catch { return false }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Windows: a .cmd runs only through cmd.exe. Each argument is quoted the way
|
|
52
|
+
// the C runtime unquotes it; cmd's /s strips the outer pair around the line.
|
|
53
|
+
function winQuote(arg) {
|
|
54
|
+
if (arg !== '' && !/[\s"]/.test(arg)) return arg
|
|
55
|
+
return '"' + arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, '$1$1') + '"'
|
|
56
|
+
}
|
|
57
|
+
function start(cmd, args, stdio) {
|
|
58
|
+
if (WIN) {
|
|
59
|
+
const line = [cmd, ...args].map(winQuote).join(' ')
|
|
60
|
+
return spawn(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', `"${line}"`], { stdio, windowsVerbatimArguments: true })
|
|
61
|
+
}
|
|
62
|
+
return spawn(cmd, args, { stdio })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handOver(args) {
|
|
66
|
+
const child = start(stable, args, 'inherit')
|
|
67
|
+
// Ctrl+C reaches Mercury directly; this launcher only waits for it.
|
|
68
|
+
const hold = () => {}
|
|
69
|
+
process.on('SIGINT', hold)
|
|
70
|
+
process.on('SIGTERM', hold)
|
|
71
|
+
child.on('error', e => die(`could not start ${stable}: ${e.message}`))
|
|
72
|
+
child.on('exit', (code, signal) => process.exit(code === null ? (signal ? 1 : 0) : code))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function target() {
|
|
76
|
+
const { platform, arch } = process
|
|
77
|
+
if (platform === 'darwin') {
|
|
78
|
+
// A x64 node under Rosetta still runs on Apple silicon: ask the kernel.
|
|
79
|
+
const apple = arch === 'arm64'
|
|
80
|
+
|| (spawnSync('sysctl', ['-n', 'hw.optional.arm64'], { encoding: 'utf8' }).stdout || '').trim() === '1'
|
|
81
|
+
if (apple) return 'macos-arm64'
|
|
82
|
+
return 'macos-x64'
|
|
83
|
+
}
|
|
84
|
+
if (platform === 'linux' && arch === 'x64') return 'linux-x64'
|
|
85
|
+
if (platform === 'linux' && arch === 'arm64') die(`no Linux arm64 build yet — build from source: https://github.com/${REPO}`)
|
|
86
|
+
if (platform === 'win32') {
|
|
87
|
+
if (arch === 'arm64') say('no Windows arm64 build yet — installing the x64 build (runs under emulation)')
|
|
88
|
+
return 'windows-x64'
|
|
89
|
+
}
|
|
90
|
+
die(`no build for ${platform} ${arch} — build from source: https://github.com/${REPO}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function fetchBytes(url) {
|
|
94
|
+
if (url.startsWith('file://')) return fs.readFileSync(new URL(url))
|
|
95
|
+
let last
|
|
96
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
97
|
+
try {
|
|
98
|
+
const res = await fetch(url, { redirect: 'follow' })
|
|
99
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
100
|
+
return Buffer.from(await res.arrayBuffer())
|
|
101
|
+
} catch (e) {
|
|
102
|
+
last = e
|
|
103
|
+
await new Promise(r => setTimeout(r, 1000 * attempt))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw last
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function unpack(archive, dir) {
|
|
110
|
+
if (WIN) {
|
|
111
|
+
// Windows 10 1803+ ships tar.exe (bsdtar), which reads zip; PowerShell is the fallback.
|
|
112
|
+
if (spawnSync('tar', ['-xf', archive, '-C', dir], { stdio: 'ignore' }).status === 0) return
|
|
113
|
+
const q = s => `'${s.replace(/'/g, "''")}'`
|
|
114
|
+
const ps = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command',
|
|
115
|
+
`Expand-Archive -LiteralPath ${q(archive)} -DestinationPath ${q(dir)} -Force`], { stdio: 'ignore' })
|
|
116
|
+
if (ps.status === 0) return
|
|
117
|
+
die('could not unpack the archive: neither tar nor PowerShell Expand-Archive worked')
|
|
118
|
+
}
|
|
119
|
+
const tar = spawnSync('tar', ['-xzf', archive, '-C', dir], { stdio: ['ignore', 'ignore', 'inherit'] })
|
|
120
|
+
if (tar.status !== 0) die('could not unpack the archive: first run needs tar')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The installer's chatter goes to stderr: stdout stays Mercury's own.
|
|
124
|
+
function runInstaller(launcher, extra) {
|
|
125
|
+
return new Promise(resolve => {
|
|
126
|
+
const child = start(launcher, ['install', ...extra], ['inherit', 'pipe', 'inherit'])
|
|
127
|
+
child.stdout.pipe(process.stderr)
|
|
128
|
+
child.on('error', e => die(`could not run the installer: ${e.message}`))
|
|
129
|
+
child.on('exit', code => resolve(code === null ? 1 : code))
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function main() {
|
|
134
|
+
const args = process.argv.slice(2)
|
|
135
|
+
const installed = installedVersion()
|
|
136
|
+
if (stableExists() && (installed === release || (installed !== '' && process.env.MERCURYCLI_PIN !== '1'))) {
|
|
137
|
+
handOver(args)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const t = target()
|
|
142
|
+
const asset = `mercury-${tag}-${t}.${WIN ? 'zip' : 'tar.gz'}`
|
|
143
|
+
const base = process.env.MERCURY_INSTALL_BASE_URL || `https://github.com/${REPO}/releases/download/${tag}`
|
|
144
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mercury-tech-cli-'))
|
|
145
|
+
const cleanup = () => { try { fs.rmSync(tmp, { recursive: true, force: true }) } catch {} }
|
|
146
|
+
process.on('exit', cleanup)
|
|
147
|
+
|
|
148
|
+
say(`first run — installing Mercury ${tag} for ${t}`)
|
|
149
|
+
let bytes, sums
|
|
150
|
+
try { bytes = await fetchBytes(`${base}/${asset}`) } catch (e) { die(`download failed: ${base}/${asset} (${e.message})`) }
|
|
151
|
+
try { sums = (await fetchBytes(`${base}/SHA256SUMS.txt`)).toString('utf8') } catch (e) { die(`download failed: ${base}/SHA256SUMS.txt (${e.message})`) }
|
|
152
|
+
const want = sums.split(/\r?\n/)
|
|
153
|
+
.map(l => l.trim().match(/^([0-9a-fA-F]{64})\s+\*?(.+)$/))
|
|
154
|
+
.find(m => m && m[2] === asset)
|
|
155
|
+
if (!want) die(`${asset} is not listed in SHA256SUMS.txt`)
|
|
156
|
+
const got = crypto.createHash('sha256').update(bytes).digest('hex')
|
|
157
|
+
if (got !== want[1].toLowerCase()) die(`checksum mismatch for ${asset} — nothing installed`)
|
|
158
|
+
say('checksum ok')
|
|
159
|
+
|
|
160
|
+
const archive = path.join(tmp, asset)
|
|
161
|
+
fs.writeFileSync(archive, bytes)
|
|
162
|
+
unpack(archive, tmp)
|
|
163
|
+
const launcher = path.join(tmp, 'mercury', WIN ? 'mercury.cmd' : 'mercury')
|
|
164
|
+
if (!fs.existsSync(launcher)) die('the archive has no launcher')
|
|
165
|
+
const extra = (process.env.MERCURY_INSTALL_ARGS || '').split(' ').filter(Boolean)
|
|
166
|
+
const code = await runInstaller(launcher, extra)
|
|
167
|
+
if (code !== 0) die(`mercury install exited ${code}`)
|
|
168
|
+
if (!stableExists()) die(`installed, but ${stable} is missing — rerun with MERCURY_INSTALL_ARGS=--force`)
|
|
169
|
+
cleanup()
|
|
170
|
+
handOver(args)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
main().catch(e => die(e && e.message ? e.message : String(e)))
|
package/package.json
CHANGED
|
@@ -1,33 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mercury-tech-cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0-beta.20",
|
|
4
|
+
"description": "Run multiple AI coding sessions in one terminal. Mix and match model providers, spawn cross-company agents, and use built-in debugging, screen control, and voice dictation.",
|
|
5
5
|
"homepage": "https://mercury-cli.ai",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "
|
|
8
|
+
"url": "https://github.com/Whq02/MercuryCLI"
|
|
9
9
|
},
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/Whq02/MercuryCLI/issues"
|
|
12
12
|
},
|
|
13
|
-
"license": "
|
|
13
|
+
"license": "BUSL-1.1",
|
|
14
14
|
"bin": {
|
|
15
|
-
"mercury": "bin/mercury"
|
|
15
|
+
"mercury": "bin/mercury.js"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
16
19
|
},
|
|
17
|
-
"os": [
|
|
18
|
-
"darwin",
|
|
19
|
-
"linux"
|
|
20
|
-
],
|
|
21
20
|
"files": [
|
|
22
21
|
"bin",
|
|
23
22
|
"README.md",
|
|
24
23
|
"LICENSE.md"
|
|
25
24
|
],
|
|
26
25
|
"keywords": [
|
|
27
|
-
"
|
|
26
|
+
"ai",
|
|
27
|
+
"coding-agent",
|
|
28
28
|
"cli",
|
|
29
29
|
"terminal",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"llm",
|
|
31
|
+
"agents",
|
|
32
|
+
"mcp",
|
|
33
|
+
"openai",
|
|
34
|
+
"anthropic",
|
|
35
|
+
"gemini",
|
|
36
|
+
"deepseek"
|
|
32
37
|
]
|
|
33
38
|
}
|
package/bin/mercury
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# mercury-tech-cli — the `mercury` command from npm or bun.
|
|
3
|
-
#
|
|
4
|
-
# Mercury runs on its own bundled runtime, never on npm's or bun's engine, so
|
|
5
|
-
# this file is a plain shell script: on first run it downloads the release
|
|
6
|
-
# archive for this machine from GitHub Releases, verifies its SHA-256, and
|
|
7
|
-
# runs the archive's own `mercury install` (user-local, no administrator
|
|
8
|
-
# access: ~/.mercury/versions/ + ~/.local/bin/mercury). Every later run
|
|
9
|
-
# hands straight to the installed command. Rerunning is safe.
|
|
10
|
-
set -eu
|
|
11
|
-
PKG_VERSION="1.0.0-beta.2"
|
|
12
|
-
REPO="Whq02/MercuryCLI"
|
|
13
|
-
home=${MERCURY_CONFIG_DIR:-"$HOME/.mercury"}
|
|
14
|
-
current="$home/versions/current.txt"
|
|
15
|
-
installed=""
|
|
16
|
-
if [ -f "$current" ]; then installed=$(head -1 "$current" 2>/dev/null | tr -d '[:space:]'); fi
|
|
17
|
-
stable="$HOME/.local/bin/mercury"
|
|
18
|
-
|
|
19
|
-
if [ "$installed" = "$PKG_VERSION" ] && [ -x "$stable" ]; then
|
|
20
|
-
exec "$stable" "$@"
|
|
21
|
-
fi
|
|
22
|
-
if [ -n "$installed" ] && [ -x "$stable" ] && [ "${MERCURYCLI_PIN:-}" != "1" ]; then
|
|
23
|
-
# A different version is installed and active: run it — `mercury update`
|
|
24
|
-
# moves versions; this package never downgrades what you have. Set
|
|
25
|
-
# MERCURYCLI_PIN=1 to force this package's version.
|
|
26
|
-
exec "$stable" "$@"
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
die() { printf 'mercury: %s\n' "$*" >&2; exit 1; }
|
|
30
|
-
command -v curl >/dev/null 2>&1 || die "first run needs 'curl'"
|
|
31
|
-
command -v tar >/dev/null 2>&1 || die "first run needs 'tar'"
|
|
32
|
-
os=$(uname -s); arch=$(uname -m)
|
|
33
|
-
case "$os/$arch" in
|
|
34
|
-
Darwin/arm64) target=macos-arm64 ;;
|
|
35
|
-
Darwin/x86_64) die "no Intel Mac build in this release (Apple silicon, Linux x64 and Windows x64 ship) — build from source: https://github.com/$REPO" ;;
|
|
36
|
-
Linux/x86_64|Linux/amd64) target=linux-x64 ;;
|
|
37
|
-
*) die "no build for $os $arch — Windows: irm https://mercury-cli.ai/install.ps1 | iex ; others: build from source at https://github.com/$REPO" ;;
|
|
38
|
-
esac
|
|
39
|
-
version="v$PKG_VERSION"
|
|
40
|
-
asset="mercury-$version-$target.tar.gz"
|
|
41
|
-
base=${MERCURY_INSTALL_BASE_URL:-"https://github.com/$REPO/releases/download/$version"}
|
|
42
|
-
tmp=$(mktemp -d "${TMPDIR:-/tmp}/mercury-tech-cli.XXXXXX")
|
|
43
|
-
trap 'rm -rf "$tmp"' EXIT
|
|
44
|
-
printf 'mercury: first run — installing Mercury %s for %s\n' "$version" "$target" >&2
|
|
45
|
-
curl -fsSL --retry 3 -o "$tmp/$asset" "$base/$asset" || die "download failed: $base/$asset"
|
|
46
|
-
curl -fsSL --retry 3 -o "$tmp/SHA256SUMS.txt" "$base/SHA256SUMS.txt" || die "download failed: $base/SHA256SUMS.txt"
|
|
47
|
-
want=$(grep " $asset\$" "$tmp/SHA256SUMS.txt" | awk '{print $1}' | head -1)
|
|
48
|
-
[ -n "$want" ] || die "$asset is not listed in SHA256SUMS.txt"
|
|
49
|
-
if command -v sha256sum >/dev/null 2>&1; then got=$(sha256sum "$tmp/$asset" | awk '{print $1}'); else got=$(shasum -a 256 "$tmp/$asset" | awk '{print $1}'); fi
|
|
50
|
-
[ "$got" = "$want" ] || die "checksum mismatch for $asset — nothing installed"
|
|
51
|
-
tar -xzf "$tmp/$asset" -C "$tmp"
|
|
52
|
-
[ -x "$tmp/mercury/mercury" ] || die "the archive has no launcher"
|
|
53
|
-
"$tmp/mercury/mercury" install >&2
|
|
54
|
-
[ -x "$stable" ] || die "installed, but $stable is missing — run: $tmp/mercury/mercury install --force"
|
|
55
|
-
exec "$stable" "$@"
|