nexrall-code 0.5.116 → 0.5.117
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/dist/index.js +12 -12
- package/package.json +11 -16
- package/dist/audit.js +0 -102
- package/dist/commands/auth.js +0 -134
- package/dist/commands/chat.js +0 -2715
- package/dist/commands/plugin.js +0 -393
- package/dist/commands/sessions.js +0 -62
- package/dist/commands/update.js +0 -174
- package/dist/permissions/handler.js +0 -332
- package/dist/terminal/terminalSetup.js +0 -395
- package/dist/trust.js +0 -137
- package/dist/ui/diff.js +0 -51
- package/dist/ui/inkReadlineAdapter.js +0 -116
- package/dist/ui/inkTerminal.js +0 -1097
- package/dist/ui/resizeRepaint.js +0 -36
- package/dist/ui/screen.js +0 -482
- package/dist/ui/theme.js +0 -385
- package/dist/ui/trustPrompt.js +0 -126
package/dist/index.js
CHANGED
|
@@ -12526,7 +12526,7 @@ var require_agentTypes = __commonJS({
|
|
|
12526
12526
|
return t2;
|
|
12527
12527
|
}
|
|
12528
12528
|
exports2.VALID_MODELS = [
|
|
12529
|
-
"claude-sonnet-5",
|
|
12529
|
+
"claude-sonnet-5-5",
|
|
12530
12530
|
"claude-opus-5-5",
|
|
12531
12531
|
"claude-fable-5-1",
|
|
12532
12532
|
"gpt-5.4",
|
|
@@ -106306,8 +106306,8 @@ var require_modelCatalogue = __commonJS({
|
|
|
106306
106306
|
}
|
|
106307
106307
|
exports2.CHEAPEST_MODEL_ALIAS = "cheapest";
|
|
106308
106308
|
var LEGACY_ALIAS_IDS = {
|
|
106309
|
-
turbo: "claude-sonnet-5",
|
|
106310
|
-
fast: "claude-sonnet-5",
|
|
106309
|
+
turbo: "claude-sonnet-5-5",
|
|
106310
|
+
fast: "claude-sonnet-5-5",
|
|
106311
106311
|
pro: "claude-opus-5-5",
|
|
106312
106312
|
ultra: "claude-fable-5-1"
|
|
106313
106313
|
};
|
|
@@ -107626,7 +107626,7 @@ ${partial}` : "",
|
|
|
107626
107626
|
ultra: 1e6,
|
|
107627
107627
|
fast: 2e5,
|
|
107628
107628
|
// Anthropic, by real model id.
|
|
107629
|
-
"claude-sonnet-5": 1e6,
|
|
107629
|
+
"claude-sonnet-5-5": 1e6,
|
|
107630
107630
|
"claude-opus-5-5": 1e6,
|
|
107631
107631
|
"claude-fable-5-1": 1e6,
|
|
107632
107632
|
"claude-haiku-4-5-20251001": 2e5,
|
|
@@ -156779,7 +156779,7 @@ var CLI_VERSION = (() => {
|
|
|
156779
156779
|
return "unknown";
|
|
156780
156780
|
})();
|
|
156781
156781
|
var MODEL_LABELS = {
|
|
156782
|
-
"claude-sonnet-5": "Claude Sonnet 5",
|
|
156782
|
+
"claude-sonnet-5-5": "Claude Sonnet 5.5",
|
|
156783
156783
|
"claude-opus-5-5": "Claude Opus 5.5",
|
|
156784
156784
|
"claude-fable-5-1": "Claude Fable 5.1",
|
|
156785
156785
|
"claude-haiku-4-5-20251001": "Claude Haiku 4.5",
|
|
@@ -156794,15 +156794,15 @@ var MODEL_LABELS = {
|
|
|
156794
156794
|
"glm-5.3": "GLM 5.3"
|
|
156795
156795
|
};
|
|
156796
156796
|
var LEGACY_MODEL_ALIASES = {
|
|
156797
|
-
turbo: "claude-sonnet-5",
|
|
156798
|
-
auto: "claude-sonnet-5",
|
|
156797
|
+
turbo: "claude-sonnet-5-5",
|
|
156798
|
+
auto: "claude-sonnet-5-5",
|
|
156799
156799
|
pro: "claude-opus-5-5",
|
|
156800
156800
|
power: "claude-opus-5-5",
|
|
156801
156801
|
ultra: "claude-fable-5-1",
|
|
156802
156802
|
fast: "claude-haiku-4-5-20251001"
|
|
156803
156803
|
};
|
|
156804
156804
|
var SELECTABLE_MODELS = [
|
|
156805
|
-
"claude-sonnet-5",
|
|
156805
|
+
"claude-sonnet-5-5",
|
|
156806
156806
|
"claude-opus-5-5",
|
|
156807
156807
|
"claude-fable-5-1",
|
|
156808
156808
|
"gpt-5.6-sol",
|
|
@@ -156822,7 +156822,7 @@ var SELECTABLE_MODELS = [
|
|
|
156822
156822
|
"glm-5.3"
|
|
156823
156823
|
];
|
|
156824
156824
|
var MODEL_COST_MULTIPLIER = {
|
|
156825
|
-
"claude-sonnet-5": 1.5,
|
|
156825
|
+
"claude-sonnet-5-5": 1.5,
|
|
156826
156826
|
"claude-opus-5-5": 3,
|
|
156827
156827
|
"claude-fable-5-1": 5,
|
|
156828
156828
|
// GPT-5.6 family (2026-07 launch, Terra/Luna repriced 2026-07-30) — same
|
|
@@ -158526,7 +158526,7 @@ ${caption}` : `[Attached: ${rel}]`;
|
|
|
158526
158526
|
const content = [{ type: "text", text: label }];
|
|
158527
158527
|
const needsSidecar = isPdf ? needsPdfSidecar(model) : needsVisionSidecar(model);
|
|
158528
158528
|
if (needsSidecar) {
|
|
158529
|
-
console.log(source_default.dim(` ${resolveModelLabel(modelAlias)} can't read ${isPdf ? "PDFs" : "images"} directly \u2014 describing ${rel} with Claude Sonnet 5 first\u2026`));
|
|
158529
|
+
console.log(source_default.dim(` ${resolveModelLabel(modelAlias)} can't read ${isPdf ? "PDFs" : "images"} directly \u2014 describing ${rel} with Claude Sonnet 5.5 first\u2026`));
|
|
158530
158530
|
try {
|
|
158531
158531
|
const { text } = await (0, import_code_core5.describeAttachment)(isPdf ? "pdf" : "image", data, mimeType, rel);
|
|
158532
158532
|
content.push({ type: "text", text: `<attachment_description>
|
|
@@ -159219,12 +159219,12 @@ program2.command("sessions").description("List or delete saved chat sessions").o
|
|
|
159219
159219
|
console.log(source_default.dim(" nex sessions --delete <id> to remove one"));
|
|
159220
159220
|
console.log();
|
|
159221
159221
|
});
|
|
159222
|
-
program2.option("-p, --pro", "Use Claude Opus 5 (most capable)").option("-t, --turbo", "Use Claude Sonnet 5 (fast
|
|
159222
|
+
program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, --turbo", "Use Claude Sonnet 5.5 (fast)").option("-u, --ultra", "Use Claude Fable 5.1 (most powerful)").option("-y, --yolo", "Auto-approve all tool calls (no permission prompts)").option("-d, --dir <path>", "Set working directory (default: current directory)").option("-m, --model <name>", "Explicit model, e.g. claude-opus-5-5 | gpt-5.4 | gpt-4.1").option("-r, --resume [id]", "Resume last session, or a specific session id").option("-w, --worktree [name]", "Isolate this session in its own worktree \u2014 a separate directory (and, in a git repo, its own branch) so it can never overwrite files another session (or you) are touching in the main checkout. Omit a name for an auto-generated one; pass an existing name to resume that same worktree.").option("-e, --effort <level>", "Thinking effort \u2014 real levels depend on the model (e.g. DeepSeek: low|high|max, Qwen: low|high); run `nex --model <id>` then `/effort` with no argument to see a model's scale").option("--no-banner", "Skip the ASCII banner (useful in scripts/pipes)").option("--output-format <fmt>", "One-shot output format: text | json | stream-json (implies auto-approve)").option("--audit [path]", "Log every tool call (and refusal) to a JSONL trail (default: .nexrall/audit/<session>.jsonl)").argument("[prompt...]", "One-shot prompt \u2014 if omitted, starts interactive mode").action(async (promptParts, options) => {
|
|
159223
159223
|
if (!(0, import_code_core8.isAuthenticated)()) {
|
|
159224
159224
|
console.error("Not logged in. Run: nex auth");
|
|
159225
159225
|
process.exit(1);
|
|
159226
159226
|
}
|
|
159227
|
-
const model = options.model ? options.model : options.ultra ? "claude-fable-5-1" : options.pro ? "claude-opus-5-5" : options.turbo ? "claude-sonnet-5" : "deepseek-flash";
|
|
159227
|
+
const model = options.model ? options.model : options.ultra ? "claude-fable-5-1" : options.pro ? "claude-opus-5-5" : options.turbo ? "claude-sonnet-5-5" : "deepseek-flash";
|
|
159228
159228
|
const workDir = options.dir ? path7.resolve(options.dir) : process.cwd();
|
|
159229
159229
|
if (options.yolo)
|
|
159230
159230
|
setAutoApprove("all");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexrall-code",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.117",
|
|
4
4
|
"description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -12,11 +12,6 @@
|
|
|
12
12
|
"ci"
|
|
13
13
|
],
|
|
14
14
|
"homepage": "https://nexrall.com",
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/nexrall/nexrall-code.git",
|
|
18
|
-
"directory": "packages/cli"
|
|
19
|
-
},
|
|
20
15
|
"license": "MIT",
|
|
21
16
|
"type": "module",
|
|
22
17
|
"bin": {
|
|
@@ -33,13 +28,6 @@
|
|
|
33
28
|
"engines": {
|
|
34
29
|
"node": ">=18"
|
|
35
30
|
},
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "node build.js",
|
|
38
|
-
"dev": "tsx src/index.ts",
|
|
39
|
-
"start": "node dist/index.js",
|
|
40
|
-
"test": "tsc --noEmit && tsc --noEmit -p test/tsconfig.json && node --import tsx --test test/*.test.ts test/*.test.mts",
|
|
41
|
-
"release": "node build.js && node scripts/upload-release.cjs"
|
|
42
|
-
},
|
|
43
31
|
"dependencies": {
|
|
44
32
|
"chalk": "^5.3.0",
|
|
45
33
|
"commander": "^12.0.0",
|
|
@@ -53,13 +41,20 @@
|
|
|
53
41
|
},
|
|
54
42
|
"devDependencies": {
|
|
55
43
|
"@aws-sdk/client-s3": "^3.600.0",
|
|
56
|
-
"@nexrall/code-core": "workspace:*",
|
|
57
44
|
"@types/diff": "^5.2.1",
|
|
58
45
|
"@types/node": "^20.0.0",
|
|
59
46
|
"@types/prompts": "^2.4.9",
|
|
60
47
|
"@types/react": "^19.2.18",
|
|
61
48
|
"esbuild": "^0.20.0",
|
|
62
49
|
"tsx": "^4.7.0",
|
|
63
|
-
"typescript": "^6.0.3"
|
|
50
|
+
"typescript": "^6.0.3",
|
|
51
|
+
"@nexrall/code-core": "1.4.65"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "node build.js",
|
|
55
|
+
"dev": "tsx src/index.ts",
|
|
56
|
+
"start": "node dist/index.js",
|
|
57
|
+
"test": "tsc --noEmit && tsc --noEmit -p test/tsconfig.json && node --import tsx --test test/*.test.ts test/*.test.mts",
|
|
58
|
+
"release": "node build.js && node scripts/upload-release.cjs"
|
|
64
59
|
}
|
|
65
|
-
}
|
|
60
|
+
}
|
package/dist/audit.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `--audit [path]` — the CLI's opt-in tool-call trail.
|
|
3
|
-
*
|
|
4
|
-
* WHY THE CLI HAS THIS AT ALL
|
|
5
|
-
*
|
|
6
|
-
* The trail was originally skipped here: an interactive terminal already has a
|
|
7
|
-
* human watching every permission prompt, so the human IS the oversight layer,
|
|
8
|
-
* and shipping a flag nobody asked for is just maintenance surface.
|
|
9
|
-
*
|
|
10
|
-
* Headless mode is what changes the answer. `--output-format json|stream-json`
|
|
11
|
-
* auto-approves every tool call (there is no TTY to ask on) and is meant for CI
|
|
12
|
-
* and scripted runs — i.e. an agent acting with nobody watching. That is where a
|
|
13
|
-
* trail is worth MOST, not least: without one there is no way to answer "what
|
|
14
|
-
* did last night's job actually touch?". The consumer that makes it useful also
|
|
15
|
-
* already exists, since stream-json output is machine-readable by design.
|
|
16
|
-
*
|
|
17
|
-
* Still OPT-IN, including in headless: writing an unrequested file into a user's
|
|
18
|
-
* working directory is not a default worth taking, and every other host
|
|
19
|
-
* (code-core, the SDK, Desktop) keeps auditing off unless asked. Opt-in
|
|
20
|
-
* consistency is itself a property worth having.
|
|
21
|
-
*
|
|
22
|
-
* State lives at module level rather than being threaded through runTurn()'s
|
|
23
|
-
* parameter list, matching setMode()/setAutoApprove() in permissions/. That list
|
|
24
|
-
* already carries ten positional parameters across six call sites; an eleventh
|
|
25
|
-
* would have to be added correctly six times, and the failure mode of missing
|
|
26
|
-
* one is a silently unaudited turn — the exact defect this feature exists to
|
|
27
|
-
* prevent, and the one already hit once in @nexrall/agent.
|
|
28
|
-
*
|
|
29
|
-
* See docs/NEXRALL_AUDIT_TRAIL_LAYER.md in the Nexrall repo.
|
|
30
|
-
*/
|
|
31
|
-
import * as path from 'path';
|
|
32
|
-
import { createJsonlAuditSink, loadAuth } from '@nexrall/code-core';
|
|
33
|
-
let _state;
|
|
34
|
-
/**
|
|
35
|
-
* One sink per resolved path.
|
|
36
|
-
*
|
|
37
|
-
* Cached because the default path is derived from the session id, so `/clear`
|
|
38
|
-
* legitimately starts a NEW trail file, while an explicit `--audit <path>`
|
|
39
|
-
* keeps appending to one. Re-creating a sink per turn would also re-run the
|
|
40
|
-
* mkdir bookkeeping on every call for no benefit.
|
|
41
|
-
*/
|
|
42
|
-
const _sinks = new Map();
|
|
43
|
-
/** Default trail location when `--audit` is given with no path. */
|
|
44
|
-
export function defaultAuditPath(workDir, sessionId) {
|
|
45
|
-
return path.join(workDir, '.nexrall', 'audit', `${sessionId}.jsonl`);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Resolve where this session's trail is written.
|
|
49
|
-
*
|
|
50
|
-
* An explicit path is resolved against the CWD the user invoked from, not
|
|
51
|
-
* `workDir`: a CI job typically wants the trail in its artifacts directory,
|
|
52
|
-
* which is usually not the repo being worked on.
|
|
53
|
-
*/
|
|
54
|
-
export function resolveAuditPath(state) {
|
|
55
|
-
return state.filePath
|
|
56
|
-
? path.resolve(state.filePath)
|
|
57
|
-
: defaultAuditPath(state.workDir, state.getSessionId());
|
|
58
|
-
}
|
|
59
|
-
/** Enable auditing for this process. Returns the path used for the first session. */
|
|
60
|
-
export function enableAudit(opts) {
|
|
61
|
-
_state = opts;
|
|
62
|
-
return resolveAuditPath(opts);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* The audit config for the CURRENT turn, or undefined when `--audit` was not
|
|
66
|
-
* passed.
|
|
67
|
-
*
|
|
68
|
-
* Built per call (cheap: a map lookup plus an object literal) so a session id
|
|
69
|
-
* changed by `/clear` or `/resume` is picked up on the very next turn.
|
|
70
|
-
*/
|
|
71
|
-
export function currentAudit() {
|
|
72
|
-
if (!_state)
|
|
73
|
-
return undefined;
|
|
74
|
-
const target = resolveAuditPath(_state);
|
|
75
|
-
let sink = _sinks.get(target);
|
|
76
|
-
if (!sink) {
|
|
77
|
-
sink = createJsonlAuditSink(target);
|
|
78
|
-
_sinks.set(target, sink);
|
|
79
|
-
}
|
|
80
|
-
const cfg = {
|
|
81
|
-
sink,
|
|
82
|
-
// Pinned per turn to the CLI's live session id, so a whole conversation —
|
|
83
|
-
// every turn, and every sub-agent it delegates to — shares one session in
|
|
84
|
-
// the trail. code-core mints an id per runAgentLoop() call and the REPL
|
|
85
|
-
// calls it once PER TURN, so leaving this unset would make each turn look
|
|
86
|
-
// like a separate session and break the link between a later write and the
|
|
87
|
-
// request that caused it. Same trap as @nexrall/agent's and Desktop's.
|
|
88
|
-
sessionId: _state.getSessionId(),
|
|
89
|
-
};
|
|
90
|
-
// The human half of dual attribution. Absent when the account has no email on
|
|
91
|
-
// record — never substituted with a placeholder, because a fabricated
|
|
92
|
-
// attribution in a compliance record is worse than a visibly missing one.
|
|
93
|
-
const email = loadAuth()?.email;
|
|
94
|
-
if (email)
|
|
95
|
-
cfg.userId = email;
|
|
96
|
-
return cfg;
|
|
97
|
-
}
|
|
98
|
-
/** Test seam — resets module state between cases. */
|
|
99
|
-
export function resetAudit() {
|
|
100
|
-
_state = undefined;
|
|
101
|
-
_sinks.clear();
|
|
102
|
-
}
|
package/dist/commands/auth.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import * as readline from 'readline';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { login, saveAuth, clearAuth, isAuthenticated, getBalance, revokeRefreshToken } from '@nexrall/code-core';
|
|
4
|
-
// ─── Hidden Password Prompt ───────────────────────────────────────────────────
|
|
5
|
-
function prompt(question, hidden = false) {
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
const rl = readline.createInterface({
|
|
8
|
-
input: process.stdin,
|
|
9
|
-
output: process.stdout,
|
|
10
|
-
terminal: true,
|
|
11
|
-
});
|
|
12
|
-
if (hidden) {
|
|
13
|
-
// Temporarily suppress echoing for password input
|
|
14
|
-
process.stdout.write(question);
|
|
15
|
-
let value = '';
|
|
16
|
-
const { stdin } = process;
|
|
17
|
-
const wasRaw = stdin.isRaw;
|
|
18
|
-
stdin.setRawMode(true);
|
|
19
|
-
stdin.resume();
|
|
20
|
-
stdin.setEncoding('utf8');
|
|
21
|
-
const onData = (char) => {
|
|
22
|
-
if (char === '\n' || char === '\r' || char === '') {
|
|
23
|
-
// Enter or Ctrl+C
|
|
24
|
-
stdin.setRawMode(wasRaw ?? false);
|
|
25
|
-
stdin.pause();
|
|
26
|
-
stdin.removeListener('data', onData);
|
|
27
|
-
rl.close();
|
|
28
|
-
process.stdout.write('\n');
|
|
29
|
-
if (char === '') {
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
resolve(value);
|
|
33
|
-
}
|
|
34
|
-
else if (char === '' || char === '\b') {
|
|
35
|
-
// Backspace
|
|
36
|
-
if (value.length > 0) {
|
|
37
|
-
value = value.slice(0, -1);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
value += char;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
stdin.on('data', onData);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
rl.question(question, (answer) => {
|
|
48
|
-
rl.close();
|
|
49
|
-
resolve(answer.trim());
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
// ─── Auth Command ─────────────────────────────────────────────────────────────
|
|
55
|
-
export async function authCommand() {
|
|
56
|
-
console.log();
|
|
57
|
-
console.log(chalk.bold.blue(' Nexrall Code — Login'));
|
|
58
|
-
console.log(chalk.dim(' ─────────────────────'));
|
|
59
|
-
console.log();
|
|
60
|
-
if (isAuthenticated()) {
|
|
61
|
-
const overwrite = await prompt(chalk.yellow(' Already logged in. Login again? ') + chalk.dim('[y/n] > '));
|
|
62
|
-
if (overwrite !== 'y' && overwrite !== 'yes') {
|
|
63
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
console.log();
|
|
67
|
-
}
|
|
68
|
-
const email = await prompt(chalk.bold(' Email ') + chalk.dim('> '));
|
|
69
|
-
const password = await prompt(chalk.bold(' Password ') + chalk.dim('> '), true);
|
|
70
|
-
console.log();
|
|
71
|
-
const spinner = startSpinner('Logging in…');
|
|
72
|
-
try {
|
|
73
|
-
const authConfig = await login(email, password);
|
|
74
|
-
saveAuth(authConfig);
|
|
75
|
-
stopSpinner(spinner);
|
|
76
|
-
console.log(chalk.green(' ✓ Logged in as ') + chalk.bold(authConfig.email ?? email));
|
|
77
|
-
console.log();
|
|
78
|
-
// Show balance
|
|
79
|
-
try {
|
|
80
|
-
const balance = await getBalance();
|
|
81
|
-
console.log(chalk.dim(' Wallet balance: ') + chalk.cyan(`$${balance.toFixed(4)}`));
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
// Non-fatal — balance fetch can fail
|
|
85
|
-
}
|
|
86
|
-
console.log();
|
|
87
|
-
console.log(chalk.dim(' Run ') + chalk.bold('nex') + chalk.dim(' to start coding.'));
|
|
88
|
-
console.log();
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
stopSpinner(spinner);
|
|
92
|
-
console.log();
|
|
93
|
-
console.log(chalk.red(' ✗ Login failed: ') + String(err.message));
|
|
94
|
-
console.log();
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// ─── Logout Command ───────────────────────────────────────────────────────────
|
|
99
|
-
export async function logoutCommand() {
|
|
100
|
-
console.log();
|
|
101
|
-
if (!isAuthenticated()) {
|
|
102
|
-
console.log(chalk.dim(' Not logged in.'));
|
|
103
|
-
console.log();
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const confirm = await prompt(chalk.yellow(' Log out? ') + chalk.dim('[y/n] > '));
|
|
107
|
-
if (confirm !== 'y' && confirm !== 'yes') {
|
|
108
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
109
|
-
console.log();
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
// Revoke server-side BEFORE clearing the local file — the refresh token has to be
|
|
113
|
-
// read from it. Without this, "logging out" only deleted the local copy while the
|
|
114
|
-
// 60-day refresh token stayed valid and could keep minting access tokens for
|
|
115
|
-
// anyone who recovered it (a synced dotfile, a backup, a shared machine).
|
|
116
|
-
await revokeRefreshToken();
|
|
117
|
-
clearAuth();
|
|
118
|
-
console.log(chalk.green(' ✓ Logged out.'));
|
|
119
|
-
console.log();
|
|
120
|
-
}
|
|
121
|
-
function startSpinner(text) {
|
|
122
|
-
const frames = ['◐', '◓', '◑', '◒'];
|
|
123
|
-
let i = 0;
|
|
124
|
-
process.stdout.write(' ');
|
|
125
|
-
const interval = setInterval(() => {
|
|
126
|
-
process.stdout.write(`\r ${chalk.cyan(frames[i % frames.length])} ${chalk.dim(text)}`);
|
|
127
|
-
i++;
|
|
128
|
-
}, 120);
|
|
129
|
-
return { interval };
|
|
130
|
-
}
|
|
131
|
-
function stopSpinner(handle) {
|
|
132
|
-
clearInterval(handle.interval);
|
|
133
|
-
process.stdout.write('\r\x1b[K'); // Clear the spinner line
|
|
134
|
-
}
|