@nonbot/cli 0.10.15 → 0.10.17
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/CHANGELOG.md +15 -0
- package/LICENSE +22 -11
- package/README.md +14 -1
- package/dist/commands/doctor.js +67 -24
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mcp.js +409 -0
- package/dist/commands/run.js +1 -1
- package/dist/index.js +47 -15
- package/dist/lib/local-status.js +54 -0
- package/dist/lib/mcp-account.js +232 -0
- package/dist/lib/mcp-config.js +89 -11
- package/dist/lib/mcp-probe.js +226 -0
- package/dist/lib/mcp-trust.js +63 -0
- package/dist/lib/quickstart-menu.js +86 -0
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
What changed for the person running the CLI, newest first. Update with
|
|
4
4
|
`npm i -g @nonbot/cli@latest`.
|
|
5
5
|
|
|
6
|
+
## 0.10.17 (2026-11-11)
|
|
7
|
+
|
|
8
|
+
- Typing `nonbot` alone now shows who you're signed in as and whether the daemon is running (or "not signed in — start with: nonbot login"). The login row says "switch account" when you're already signed in.
|
|
9
|
+
- `nonbot help` and `nonbot -h` open with the gradient wordmark.
|
|
10
|
+
- Under the wordmark: "non.bot CLI v… · non.bot MCP v…" — both versions side by side (the MCP's is read from this machine, so the screen stays instant).
|
|
11
|
+
- Help and error messages say "To use" instead of "Usage", which read like a page of your account usage.
|
|
12
|
+
|
|
13
|
+
## 0.10.16 (2026-11-11)
|
|
14
|
+
|
|
15
|
+
- New: `nonbot mcp` shows the non.bot MCP in one place: its version and what's new since, every Claude app and config file it's set up in (and duplicates), a live start with its tool count by group, how much of each conversation its tool list uses, which tools your apps switch off, the token it uses (last four characters only), whether that token is valid and what it may do, and which tools Claude runs without asking. `--quick` skips the live start.
|
|
16
|
+
- Typing `nonbot` alone now has a **help** option under update: click it (or press h) and the full command list prints right below; any other key exits. Outside a real terminal it never waits. `nonbot help` also works.
|
|
17
|
+
- `nonbot doctor` opens with the gradient wordmark and reads as one tidy grid. It now starts the MCP too and shows the result ("170 tools in 281ms"), lists retired MCP servers on one row with `nonbot mcp clean`, and points at `nonbot mcp` for the full check.
|
|
18
|
+
- A project's own `.mcp.json` is never started until you approve it once (`nonbot mcp test --include-project` shows the exact command and asks). The approval is remembered for that exact command only, so a repo that later changes it has to be approved again.
|
|
19
|
+
- New: `nonbot mcp test` starts every configured copy and checks it answers; `nonbot mcp add` registers it in Claude Code; `nonbot mcp clean` removes retired MCP servers from the files they're in (it asks first, and keeps a backup of Claude Desktop's config).
|
|
20
|
+
|
|
6
21
|
## 0.10.15 (2026-11-11)
|
|
7
22
|
|
|
8
23
|
- New: `nonbot logout` signs you out on this machine and stops the daemon.
|
package/LICENSE
CHANGED
|
@@ -6,24 +6,35 @@ This software (the "@nonbot/cli" package and all of its contents, the
|
|
|
6
6
|
"Software") is the proprietary and confidential property of non.bot. It is
|
|
7
7
|
published to the npm registry for the sole purpose of allowing authorized
|
|
8
8
|
non.bot account holders to install and run it as the local host for the
|
|
9
|
-
non.bot "Run" feature.
|
|
9
|
+
non.bot "Run" feature. Publication on the npm registry, or on any mirror, cache
|
|
10
|
+
or CDN that copies it, does not make the Software open source, does not place
|
|
11
|
+
it in the public domain, and grants no right beyond the limited license below.
|
|
10
12
|
|
|
11
13
|
PERMITTED USE
|
|
12
14
|
|
|
13
|
-
Subject to your compliance with the non.bot Terms of Service
|
|
14
|
-
a limited, non-exclusive,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
Subject to your compliance with the non.bot Terms of Service
|
|
16
|
+
(https://non.bot/terms-of-service), you are granted a limited, non-exclusive,
|
|
17
|
+
non-transferable, non-sublicensable, revocable license to download, install,
|
|
18
|
+
and execute unmodified copies of the Software on machines you control, solely
|
|
19
|
+
to connect to and operate the non.bot service. This license ends automatically
|
|
20
|
+
if you breach it or the Terms of Service, or when your non.bot account ends;
|
|
21
|
+
you must then stop using the Software and delete every copy of it.
|
|
17
22
|
|
|
18
23
|
PROHIBITED WITHOUT PRIOR WRITTEN PERMISSION FROM non.bot
|
|
19
24
|
|
|
20
|
-
- Copying, redistributing, sublicensing, selling, or publishing the
|
|
21
|
-
or any portion of it;
|
|
25
|
+
- Copying, redistributing, sublicensing, selling, renting, or publishing the
|
|
26
|
+
Software or any portion of it;
|
|
22
27
|
- Modifying, adapting, translating, or creating derivative works;
|
|
23
|
-
- Reverse-engineering, decompiling, or disassembling the Software except to
|
|
24
|
-
the limited extent
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
- Reverse-engineering, decompiling, or disassembling the Software, except to
|
|
29
|
+
the limited extent applicable law expressly permits despite this
|
|
30
|
+
restriction;
|
|
31
|
+
- Using the Software, or any technique, method, prompt, or design embodied in
|
|
32
|
+
it, to build, train, or operate a competing product or service;
|
|
33
|
+
- Using the Software, or any part or output of it, to train, fine-tune,
|
|
34
|
+
evaluate, or otherwise improve any machine-learning or artificial-
|
|
35
|
+
intelligence model, dataset, or system;
|
|
36
|
+
- Removing, obscuring, or altering any copyright, license, or proprietary
|
|
37
|
+
notice.
|
|
27
38
|
|
|
28
39
|
NO PATENT OR OTHER IP LICENSE
|
|
29
40
|
|
package/README.md
CHANGED
|
@@ -78,8 +78,14 @@ nonbot daemon Run the poll loop and launch terminals (the main command
|
|
|
78
78
|
nonbot run <id> Launch a single Run by id, then exit
|
|
79
79
|
nonbot status Show login + daemon status
|
|
80
80
|
nonbot doctor Health check — platform, auth, server, providers, terminal, MCP
|
|
81
|
+
nonbot mcp The non.bot MCP: version, where it's set up, a live start, tools,
|
|
82
|
+
your token's permissions (add --quick to skip starting it)
|
|
83
|
+
nonbot mcp test Start every configured copy of the MCP and check it answers
|
|
84
|
+
nonbot mcp add Register the MCP in Claude Code (claude mcp add … @nonbot/mcp)
|
|
85
|
+
nonbot mcp clean Remove retired MCP servers (portfolio-mcp, context-graph, jobshop-mcp)
|
|
81
86
|
nonbot logs Show recent local Run launches
|
|
82
87
|
nonbot update Install the latest version (npm i -g @nonbot/cli@latest)
|
|
88
|
+
nonbot update mcp Show the MCP's version and update it
|
|
83
89
|
nonbot profiles List configured accounts
|
|
84
90
|
nonbot choir <session> Coordinated-run engine — one agent per git worktree, merged back by hand
|
|
85
91
|
nonbot run-mcp Per-pane MCP server over stdio (launched by .mcp.json, not by hand)
|
|
@@ -135,7 +141,7 @@ daemon asks the npm registry for the latest `@nonbot/cli`. If it is newer than t
|
|
|
135
141
|
running, it prints a single line:
|
|
136
142
|
|
|
137
143
|
```
|
|
138
|
-
Update available: @nonbot/cli 0.9.13 → 0.9.14 (
|
|
144
|
+
Update available: @nonbot/cli 0.9.13 → 0.9.14 (run: nonbot update)
|
|
139
145
|
```
|
|
140
146
|
|
|
141
147
|
The probe has a 3-second timeout and any failure is silent. Set
|
|
@@ -183,6 +189,13 @@ are redacted. Redaction is pattern-based; treat a snapshot as terminal output.
|
|
|
183
189
|
saved end-of-run output in the local history (`nonbot logs`). A cloud runner
|
|
184
190
|
(`--cloud`) additionally registers its machine id and its allowed repository origins.
|
|
185
191
|
|
|
192
|
+
**When you run `nonbot mcp` or `nonbot update mcp`:** your token goes to non.bot to
|
|
193
|
+
check it is valid and read its permissions and which apps are on; the CLI asks the npm
|
|
194
|
+
registry for `@nonbot/mcp`'s versions and fetches the mcp.non.bot home page to read
|
|
195
|
+
its version. `nonbot mcp` (not `--quick`) also starts the MCP the way Claude does,
|
|
196
|
+
which makes the MCP's own requests to non.bot. The token is shown only as its last
|
|
197
|
+
four characters.
|
|
198
|
+
|
|
186
199
|
To show a neutral name instead of the host name, set `NONBOT_MACHINE_NAME` before
|
|
187
200
|
starting the daemon (up to 64 characters):
|
|
188
201
|
|
package/dist/commands/doctor.js
CHANGED
|
@@ -3,15 +3,18 @@ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { loadAuth } from '../lib/auth.js';
|
|
6
|
-
import { scanMcpConfigs, describeNonbotMcp } from '../lib/mcp-config.js';
|
|
6
|
+
import { scanMcpConfigs, describeNonbotMcp, LEGACY_MCP_NAMES } from '../lib/mcp-config.js';
|
|
7
7
|
import { nonbotMcpStatuses, mcpRow } from '../lib/mcp-status.js';
|
|
8
|
+
import { launchSpecOf } from '../lib/mcp-config.js';
|
|
9
|
+
import { probeMcp } from '../lib/mcp-probe.js';
|
|
10
|
+
import { mayStartEntry } from '../lib/mcp-trust.js';
|
|
8
11
|
import { resolveTerminal } from '../lib/terminal.js';
|
|
9
12
|
import { VERSION } from '../version.js';
|
|
10
|
-
import { header, statusRow, errorBlock, isTTY, ANSI } from '../lib/output.js';
|
|
13
|
+
import { header, statusRow, errorBlock, isTTY, ANSI, wordmarkArt, WORDMARK_WIDTH } from '../lib/output.js';
|
|
11
14
|
const HEARTBEAT_FRESH_MS = 35_000;
|
|
12
15
|
const PROVIDER_CLIS = ['claude', 'codex', 'gemini'];
|
|
13
16
|
export const NONBOT_MCP_ADD_HINT = 'claude mcp add nonbot -- npx -y @nonbot/mcp';
|
|
14
|
-
const LEGACY_MCPS =
|
|
17
|
+
const LEGACY_MCPS = LEGACY_MCP_NAMES;
|
|
15
18
|
const EXPECTED_SKILLS = [];
|
|
16
19
|
function isSkillInstalled(skillName) {
|
|
17
20
|
const base = join(homedir(), '.claude');
|
|
@@ -137,7 +140,15 @@ export async function runDoctorCommand(args = [], deps = {}) {
|
|
|
137
140
|
const errRow = (sig, label, detail = '') => errLog(statusRow(sig, label, detail, { labelWidth: LABEL_WIDTH, stream: process.stderr }) + '\n');
|
|
138
141
|
const hint = (text) => log(' '.repeat(HINT_INDENT) + paintDim(text) + '\n');
|
|
139
142
|
const section = (title) => log('\n' + paintSection(title) + '\n');
|
|
140
|
-
|
|
143
|
+
const cols = deps.columns ?? process.stdout.columns ?? 0;
|
|
144
|
+
const sub = `doctor · v${VERSION} · ${process.platform}/${process.arch}`;
|
|
145
|
+
if (cols === 0 || cols >= WORDMARK_WIDTH + 4) {
|
|
146
|
+
log('\n' + wordmarkArt({ indent: ' ' }) + '\n\n');
|
|
147
|
+
log(` ${paintDim(sub)}\n`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
log(header('non.bot doctor', `v${VERSION} · ${process.platform}/${process.arch}`) + '\n');
|
|
151
|
+
}
|
|
141
152
|
const auth = await loader();
|
|
142
153
|
if (!auth) {
|
|
143
154
|
log('\n');
|
|
@@ -154,7 +165,7 @@ export async function runDoctorCommand(args = [], deps = {}) {
|
|
|
154
165
|
try {
|
|
155
166
|
const res = await fetchImpl(`${auth.baseUrl}/api/auth/me`, { headers });
|
|
156
167
|
if (res.ok) {
|
|
157
|
-
row('✓', 'Server', `${auth.baseUrl} · token accepted`);
|
|
168
|
+
row('✓', 'Server', `${auth.baseUrl.replace(/^https?:\/\//, '')} · token accepted`);
|
|
158
169
|
}
|
|
159
170
|
else if (res.status === 401) {
|
|
160
171
|
errRow('✗', 'Server', 'token rejected (HTTP 401) — sign in again: nonbot login');
|
|
@@ -196,7 +207,7 @@ export async function runDoctorCommand(args = [], deps = {}) {
|
|
|
196
207
|
}
|
|
197
208
|
const storeUrl = `${auth.baseUrl}/whygraph/store?tab=apps&app=${gate.app ?? 'run'}`;
|
|
198
209
|
if (gate.error === 'app_disabled') {
|
|
199
|
-
row('✗', 'Run app', 'switched off — the daemon gets no runs
|
|
210
|
+
row('✗', 'Run app', 'switched off — the daemon gets no runs');
|
|
200
211
|
hint(`turn it on: ${storeUrl}`);
|
|
201
212
|
}
|
|
202
213
|
else if (gate.error === 'app_not_installed') {
|
|
@@ -217,7 +228,8 @@ export async function runDoctorCommand(args = [], deps = {}) {
|
|
|
217
228
|
section('AGENTS');
|
|
218
229
|
const providers = PROVIDER_CLIS.map((bin) => ({ bin, r: checkBinary(bin) }));
|
|
219
230
|
const anyProvider = providers.some((p) => p.r.installed);
|
|
220
|
-
|
|
231
|
+
const ordered = [...providers.filter((p) => p.r.installed), ...providers.filter((p) => !p.r.installed)];
|
|
232
|
+
for (const { bin, r } of ordered) {
|
|
221
233
|
if (r.installed)
|
|
222
234
|
row('✓', bin, bareVersion(r.version));
|
|
223
235
|
else if (anyProvider)
|
|
@@ -232,7 +244,7 @@ export async function runDoctorCommand(args = [], deps = {}) {
|
|
|
232
244
|
const { cmd } = profile.launch('/tmp/nonbot-doctor-probe.sh');
|
|
233
245
|
const launcher = checkBinary(cmd);
|
|
234
246
|
if (launcher.installed) {
|
|
235
|
-
row('✓', 'Opens runs in', `${profile.displayName}
|
|
247
|
+
row('✓', 'Opens runs in', `${profile.displayName} (via ${cmd})`);
|
|
236
248
|
}
|
|
237
249
|
else {
|
|
238
250
|
row('⚠', 'Terminal', `${profile.displayName} — launcher "${cmd}" not found on PATH`);
|
|
@@ -253,7 +265,7 @@ export async function runDoctorCommand(args = [], deps = {}) {
|
|
|
253
265
|
const appl = checkBinary('iterm-applescript');
|
|
254
266
|
if (appl.installed) {
|
|
255
267
|
const v = bareVersion(appl.version);
|
|
256
|
-
row('✓', 'iTerm', v ? `${v} ·
|
|
268
|
+
row('✓', 'iTerm', v ? `${v} · AppleScript OK` : 'AppleScript OK');
|
|
257
269
|
}
|
|
258
270
|
else {
|
|
259
271
|
row('⚠', 'iTerm', appl.version ? `AppleScript blocked: ${appl.version}` : 'AppleScript blocked');
|
|
@@ -261,29 +273,60 @@ export async function runDoctorCommand(args = [], deps = {}) {
|
|
|
261
273
|
}
|
|
262
274
|
}
|
|
263
275
|
}
|
|
264
|
-
section('
|
|
276
|
+
section('MCP');
|
|
265
277
|
const statusSource = deps.nonbotMcp ?? (deps.checkBinary ? null : () => nonbotMcpStatuses({ fetchImpl: deps.fetchImpl }));
|
|
266
278
|
const statuses = statusSource ? await statusSource().catch(() => []) : null;
|
|
267
279
|
const nonbotMcp = statuses ? { installed: statuses.length > 0 } : checkBinary('mcp:nonbot');
|
|
268
280
|
if (statuses && statuses.length > 0) {
|
|
269
|
-
const
|
|
270
|
-
|
|
281
|
+
const primary = statuses.find((s) => s.entry.scope && s.entry.scope !== 'desktop') ?? statuses[0];
|
|
282
|
+
const r = mcpRow(primary);
|
|
283
|
+
row(r.sigil, 'Installed', r.detail);
|
|
271
284
|
if (r.hint)
|
|
272
285
|
hint(r.hint);
|
|
286
|
+
const startSource = deps.mcpStart ??
|
|
287
|
+
(deps.checkBinary
|
|
288
|
+
? null
|
|
289
|
+
: async (entry) => {
|
|
290
|
+
const spec = launchSpecOf(entry);
|
|
291
|
+
if (!spec)
|
|
292
|
+
return { state: 'unknown' };
|
|
293
|
+
if (!mayStartEntry(entry, spec))
|
|
294
|
+
return { state: 'unapproved' };
|
|
295
|
+
return { state: 'started', result: await probeMcp(spec, { fetchImpl: deps.fetchImpl, fallbackBearer: auth.pat, timeoutMs: 20_000 }) };
|
|
296
|
+
});
|
|
297
|
+
if (startSource) {
|
|
298
|
+
const s = await startSource(primary.entry).catch(() => ({ state: 'unknown' }));
|
|
299
|
+
if (s.state === 'started' && s.result.ok) {
|
|
300
|
+
const took = s.result.ms < 1000 ? `${s.result.ms}ms` : `${(s.result.ms / 1000).toFixed(1)}s`;
|
|
301
|
+
const v = s.result.serverVersion;
|
|
302
|
+
const differs = v && primary.installed && v !== primary.installed ? ` · but reports v${v}` : '';
|
|
303
|
+
row(differs ? '⚠' : '✓', 'Starts', `${s.result.tools.length} tools in ${took}${differs}`);
|
|
304
|
+
}
|
|
305
|
+
else if (s.state === 'started') {
|
|
306
|
+
row('✗', 'Starts', s.result.error ?? 'it did not answer');
|
|
307
|
+
hint('see it fail: nonbot mcp test');
|
|
308
|
+
}
|
|
309
|
+
else if (s.state === 'unapproved') {
|
|
310
|
+
row('ℹ', 'Starts', `not started — ${primary.entry.source} is a project file`);
|
|
311
|
+
hint('approve it once: nonbot mcp test --include-project');
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
row('ℹ', 'Starts', 'not checked — could not read how it is launched');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
273
317
|
}
|
|
274
318
|
else if (nonbotMcp.installed) {
|
|
275
319
|
const how = nonbotMcp.version ?? 'configured (@nonbot/mcp)';
|
|
276
|
-
row(/pinned/.test(how) ? '⚠' : '✓', '
|
|
320
|
+
row(/pinned/.test(how) ? '⚠' : '✓', 'Installed', how);
|
|
277
321
|
}
|
|
278
322
|
else {
|
|
279
|
-
row('⚠', '
|
|
280
|
-
hint(
|
|
323
|
+
row('⚠', 'Not set up', 'runs cannot report back without the non.bot MCP');
|
|
324
|
+
hint('add it: nonbot mcp add');
|
|
281
325
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
326
|
+
const legacy = LEGACY_MCPS.map((name) => ({ name, r: checkBinary(`mcp:${name}`) })).filter((l) => l.r.installed);
|
|
327
|
+
if (legacy.length) {
|
|
328
|
+
const files = [...new Set(legacy.map((l) => l.r.version).filter((v) => !!v))];
|
|
329
|
+
row('ℹ', 'Retired', `${legacy.map((l) => l.name).join(', ')}${files.length === 1 ? ` in ${files[0]}` : ''} · remove: nonbot mcp clean`);
|
|
287
330
|
}
|
|
288
331
|
for (const name of EXPECTED_SKILLS) {
|
|
289
332
|
const r = checkBinary(`skill:${name}`);
|
|
@@ -294,9 +337,9 @@ export async function runDoctorCommand(args = [], deps = {}) {
|
|
|
294
337
|
}
|
|
295
338
|
section('COMMANDS');
|
|
296
339
|
log(commandRow('nonbot daemon', 'start listening for runs'));
|
|
297
|
-
log(commandRow('Ctrl-C', 'stop
|
|
298
|
-
log(commandRow('nonbot
|
|
299
|
-
log(commandRow('nonbot logout', 'sign out
|
|
340
|
+
log(commandRow('Ctrl-C', 'stop it · from another terminal: nonbot daemon stop'));
|
|
341
|
+
log(commandRow('nonbot mcp', 'the full MCP check'));
|
|
342
|
+
log(commandRow('nonbot logout', 'sign out and stop the daemon'));
|
|
300
343
|
return exitCode;
|
|
301
344
|
}
|
|
302
345
|
const LABEL_WIDTH = 14;
|
|
@@ -308,7 +351,7 @@ function paintSection(title) {
|
|
|
308
351
|
return isTTY(process.stdout) ? ` ${ANSI.bold}${title}${ANSI.reset}` : ` ${title}`;
|
|
309
352
|
}
|
|
310
353
|
function commandRow(cmd, what) {
|
|
311
|
-
const padded = cmd.padEnd(LABEL_WIDTH +
|
|
354
|
+
const padded = cmd.padEnd(LABEL_WIDTH + 1, ' ');
|
|
312
355
|
if (!isTTY(process.stdout))
|
|
313
356
|
return ` ${padded}${what}\n`;
|
|
314
357
|
return ` ${ANSI.cyan}${padded}${ANSI.reset}${ANSI.dim}${what}${ANSI.reset}\n`;
|
package/dist/commands/logs.js
CHANGED
|
@@ -73,7 +73,7 @@ export async function runLogsCommand(args = [], deps = {}) {
|
|
|
73
73
|
const opts = parseArgs(args);
|
|
74
74
|
if (opts.error) {
|
|
75
75
|
errLog(statusRow('✗', 'nonbot logs', opts.error, { stream: process.stderr }) + '\n');
|
|
76
|
-
errLog('
|
|
76
|
+
errLog(' To use: nonbot logs [--limit <n>] [--json] [--follow|-f]\n');
|
|
77
77
|
return 1;
|
|
78
78
|
}
|
|
79
79
|
const entries = await readActivityLog(opts.limit);
|