@bahulam/code 2.6.8 → 2.6.11
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/package.json +4 -3
- package/src/core/bundled-runtime.mjs +20 -4
- package/src/index.mjs +1 -1
- package/src/terminal/main.mjs +1 -0
- package/src/ui/approval.mjs +67 -34
- package/src/ui/banner.mjs +38 -41
- package/src/ui/input-dock.mjs +3 -0
- package/src/ui/term.mjs +16 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bahulam/code",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.11",
|
|
4
4
|
"description": "Bahulam Code \u2014 abundance, in your terminal. CLI-first, reliability-first, sub-agents, 65.6% SWE-bench Verified.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"web-tree-sitter": "^0.26.9"
|
|
50
50
|
},
|
|
51
51
|
"optionalDependencies": {
|
|
52
|
-
"@bahulam/runtime-darwin-arm64": "2.6.
|
|
53
|
-
"@bahulam/runtime-linux-x64": "2.6.
|
|
52
|
+
"@bahulam/runtime-darwin-arm64": "2.6.11",
|
|
53
|
+
"@bahulam/runtime-linux-x64": "2.6.11",
|
|
54
|
+
"@bahulam/runtime-win32-x64": "2.6.11"
|
|
54
55
|
}
|
|
55
56
|
}
|
|
@@ -90,7 +90,17 @@ function _runtimeRoot() {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function _runtimeBin() {
|
|
93
|
-
|
|
93
|
+
const binDir = path.join(_runtimeRoot(), 'bin');
|
|
94
|
+
if (IS_WINDOWS) {
|
|
95
|
+
// Prefer whichever launcher ships in the Windows runtime package.
|
|
96
|
+
// Current build emits .cmd; keep .exe as a future option.
|
|
97
|
+
for (const name of ['bahulam-agent.cmd', 'bahulam-agent.exe', 'bahulam-agent.bat']) {
|
|
98
|
+
const p = path.join(binDir, name);
|
|
99
|
+
if (fs.existsSync(p)) return p;
|
|
100
|
+
}
|
|
101
|
+
return path.join(binDir, 'bahulam-agent.cmd');
|
|
102
|
+
}
|
|
103
|
+
return path.join(binDir, 'bahulam-agent');
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
// Read the framework LICENSE_KEY from a config file so shipped end-users
|
|
@@ -195,7 +205,7 @@ export async function ensureRuntimeReady() {
|
|
|
195
205
|
if (!fs.existsSync(bin)) {
|
|
196
206
|
throw new Error(
|
|
197
207
|
`Bahulam runtime not found at ${bin}. ` +
|
|
198
|
-
`Re-run \`npm install -g @
|
|
208
|
+
`Re-run \`npm install -g @bahulam/code\` to fetch the runtime, ` +
|
|
199
209
|
`or set BAHULAM_RUNTIME_ROOT to a valid install path.`,
|
|
200
210
|
);
|
|
201
211
|
}
|
|
@@ -221,10 +231,16 @@ export async function ensureRuntimeReady() {
|
|
|
221
231
|
env: {
|
|
222
232
|
...process.env,
|
|
223
233
|
BAHULAM_CLI_LOCAL: '1',
|
|
234
|
+
// Framework validates LICENSE_KEY against the license portal on import.
|
|
235
|
+
// The portal is now embedded inside the Bahulam Gateway itself
|
|
236
|
+
// (see codekepler-bahulam-gateway/app/main.py mount at /portal), so
|
|
237
|
+
// the phone-home shares gateway uptime. Framework code (_license.py)
|
|
238
|
+
// reads LICENSE_PORTAL_URL and hits {url}/api/heartbeat.
|
|
239
|
+
LICENSE_PORTAL_URL: process.env.LICENSE_PORTAL_URL || 'https://gateway.bahulam.ai/portal',
|
|
224
240
|
// Framework requires LICENSE_KEY at import time. Sourced from
|
|
225
241
|
// ~/.bahulam/license.jwt so end users don't need to export a var.
|
|
226
242
|
...(licenseKey ? { LICENSE_KEY: licenseKey } : {}),
|
|
227
|
-
// Bahulam CLI token from `bahulam
|
|
243
|
+
// Bahulam CLI token from `bahulam login` (saved in
|
|
228
244
|
// ~/.bahulam/config.json). BahulamGateway reads it from any of
|
|
229
245
|
// BAHULAM_API_KEY / BAHULAM_CLI_TOKEN / BAHULAM_GATEWAY_API_KEY —
|
|
230
246
|
// we set both so it works whether the framework prefers one over
|
|
@@ -345,7 +361,7 @@ export function isRuntimeInstalled() {
|
|
|
345
361
|
return fs.existsSync(_runtimeBin());
|
|
346
362
|
}
|
|
347
363
|
|
|
348
|
-
/** Diagnostic snapshot for `bahulam
|
|
364
|
+
/** Diagnostic snapshot for `bahulam doctor`. */
|
|
349
365
|
export function runtimeInfo() {
|
|
350
366
|
return {
|
|
351
367
|
installed: isRuntimeInstalled(),
|
package/src/index.mjs
CHANGED
|
@@ -231,7 +231,7 @@ import { startTerminalRepl } from './terminal/repl.mjs';
|
|
|
231
231
|
|
|
232
232
|
async function main() {
|
|
233
233
|
const args = parseArgs(process.argv.slice(2));
|
|
234
|
-
if (args.version) { console.log(`@
|
|
234
|
+
if (args.version) { console.log(`@bahulam/code ${VERSION}`); process.exit(0); }
|
|
235
235
|
if (args.help) { printUsage(); process.exit(0); }
|
|
236
236
|
|
|
237
237
|
const auth = new TarangAuth();
|
package/src/terminal/main.mjs
CHANGED
|
@@ -237,6 +237,7 @@ async function main() {
|
|
|
237
237
|
KEPLER_CONFIG_DIR Legacy config directory override
|
|
238
238
|
KEPLER_RECONNECT_MAX_ELAPSED_MS
|
|
239
239
|
Max reconnect window for dropped streams
|
|
240
|
+
BAHULAM_TTY_MODE=stable Scrollback-safe transcript if fixed dock redraws leak
|
|
240
241
|
KEPLER_BLOCK_SEPARATOR Tool/content separator: space, dotted, or off
|
|
241
242
|
|
|
242
243
|
\x1b[2mDocs: https://bahulam.ai\x1b[0m
|
package/src/ui/approval.mjs
CHANGED
|
@@ -64,21 +64,23 @@ export function defaultOptions(tier) {
|
|
|
64
64
|
* @param {number} [opts.selected] — index of the highlighted option
|
|
65
65
|
*/
|
|
66
66
|
export function renderApprovalPrompt({
|
|
67
|
-
tool, args = {}, tier, why
|
|
67
|
+
tool, args = {}, tier, why = '', width,
|
|
68
68
|
options, selected = 0,
|
|
69
69
|
} = {}) {
|
|
70
70
|
const cols = Math.max(60, Math.min(width || process.stderr.columns || 96, 120));
|
|
71
71
|
const explicit = requiresExplicitApproval(tier);
|
|
72
72
|
const accent = explicit ? paint.brand.accent : paint.brand.data;
|
|
73
73
|
const opts = options || defaultOptions(tier);
|
|
74
|
-
const title = `${
|
|
74
|
+
const title = `${approvalTitle(tier).toLowerCase()} · ${tierLabel(tier)} · ${tool || 'tool'}`;
|
|
75
75
|
|
|
76
76
|
const lines = [
|
|
77
|
-
|
|
77
|
+
blockHeader(title, accent),
|
|
78
78
|
...subjectRows(tool, args, cols, accent),
|
|
79
|
+
...riskRows(tool, args, tier, accent),
|
|
80
|
+
...reasonRows(why, cols, accent),
|
|
81
|
+
blockLine(accent),
|
|
79
82
|
...decisionRows(opts, selected, accent),
|
|
80
|
-
|
|
81
|
-
horizontalRule('', cols, accent),
|
|
83
|
+
blockLine(accent, paint.text.dim('↑↓ move · Enter pick · letter shortcut · Esc cancel')),
|
|
82
84
|
];
|
|
83
85
|
|
|
84
86
|
return '\n' + lines.join('\n');
|
|
@@ -110,24 +112,6 @@ export function renderInlinePrompt({ tool, args = {}, tier, why = '' } = {}) {
|
|
|
110
112
|
|
|
111
113
|
export { TIERS };
|
|
112
114
|
|
|
113
|
-
function riskIcon(tier) {
|
|
114
|
-
switch (tier) {
|
|
115
|
-
case TIERS.SENSITIVE_READ:
|
|
116
|
-
return '🔐';
|
|
117
|
-
case TIERS.SHELL_DANGEROUS:
|
|
118
|
-
case TIERS.DESTRUCTIVE:
|
|
119
|
-
return '🔴';
|
|
120
|
-
case TIERS.SHELL_MEDIUM:
|
|
121
|
-
return '⚠';
|
|
122
|
-
case TIERS.NETWORK:
|
|
123
|
-
return '🌐';
|
|
124
|
-
case TIERS.LOCAL_EDIT:
|
|
125
|
-
return '✎';
|
|
126
|
-
default:
|
|
127
|
-
return '◈';
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
115
|
function tierTitle(tier) {
|
|
132
116
|
switch (tier) {
|
|
133
117
|
case TIERS.SENSITIVE_READ: return 'SENSITIVE';
|
|
@@ -153,40 +137,89 @@ function approvalTitle(tier) {
|
|
|
153
137
|
}
|
|
154
138
|
}
|
|
155
139
|
|
|
156
|
-
function
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
140
|
+
function blockHeader(title, accent) {
|
|
141
|
+
return ` ${accent('╭─')} ${paint.bold(accent(title))}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function blockLine(accent, text = '') {
|
|
145
|
+
return ` ${accent('│')}${text ? ` ${text}` : ''}`;
|
|
161
146
|
}
|
|
162
147
|
|
|
163
148
|
function subjectRows(tool, args, cols, accent) {
|
|
164
149
|
const rows = [];
|
|
165
|
-
const available = Math.max(24, cols -
|
|
150
|
+
const available = Math.max(24, cols - 5);
|
|
166
151
|
const summary = toolDisplaySummary(tool, args, {});
|
|
167
152
|
const label = `${icon(tool)} ${paint.text.primary(toolDisplayLabel(tool))}`;
|
|
168
153
|
const details = subjectDetails(tool, args, summary, Math.max(24, available - visibleWidth(label) - 1));
|
|
169
154
|
|
|
170
155
|
if (details.length === 1 && visibleWidth(`${label} ${details[0]}`) <= available) {
|
|
171
|
-
rows.push(
|
|
156
|
+
rows.push(blockLine(accent, `${label} ${paint.text.primary(details[0])}`));
|
|
172
157
|
return rows;
|
|
173
158
|
}
|
|
174
159
|
|
|
175
|
-
rows.push(
|
|
160
|
+
rows.push(blockLine(accent, label));
|
|
176
161
|
for (const line of subjectDetails(tool, args, summary, Math.max(24, available - 2))) {
|
|
177
|
-
rows.push(
|
|
162
|
+
rows.push(blockLine(accent, `${paint.text.dim(' ')}${paint.text.primary(line)}`));
|
|
178
163
|
}
|
|
179
164
|
return rows;
|
|
180
165
|
}
|
|
181
166
|
|
|
182
167
|
function decisionRows(opts, selected, accent) {
|
|
183
|
-
const rows = [
|
|
168
|
+
const rows = [blockLine(accent, paint.text.dim('Decision'))];
|
|
184
169
|
for (let i = 0; i < opts.length; i++) {
|
|
185
|
-
rows.push(
|
|
170
|
+
rows.push(blockLine(accent, optionToken(opts[i], i === selected, accent)));
|
|
186
171
|
}
|
|
187
172
|
return rows;
|
|
188
173
|
}
|
|
189
174
|
|
|
175
|
+
function reasonRows(why, cols, accent) {
|
|
176
|
+
const reason = String(why || '').replace(/\s+/g, ' ').trim();
|
|
177
|
+
if (!reason) return [];
|
|
178
|
+
const label = paint.text.dim('reason ');
|
|
179
|
+
const firstWidth = Math.max(20, cols - 5 - visibleWidth(label));
|
|
180
|
+
const restWidth = Math.max(20, cols - 5 - 7);
|
|
181
|
+
const wrapped = wrapText(reason, firstWidth);
|
|
182
|
+
const rows = [];
|
|
183
|
+
wrapped.forEach((line, index) => {
|
|
184
|
+
if (index === 0) {
|
|
185
|
+
rows.push(blockLine(accent, `${label}${paint.text.primary(line)}`));
|
|
186
|
+
} else {
|
|
187
|
+
for (const rest of wrapText(line, restWidth)) {
|
|
188
|
+
rows.push(blockLine(accent, `${paint.text.dim(' ')}${paint.text.primary(rest)}`));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return rows;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function riskRows(tool, args = {}, tier, accent) {
|
|
196
|
+
const terms = riskTerms(tool, args, tier);
|
|
197
|
+
if (!terms.length) return [];
|
|
198
|
+
return [blockLine(accent, `${paint.text.dim('risk ')}${paint.state.warn(terms.join(', '))}`)];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function riskTerms(tool, args = {}, tier) {
|
|
202
|
+
const terms = [];
|
|
203
|
+
if (tool === 'shell') {
|
|
204
|
+
const command = String(args.command || args.cmd || '');
|
|
205
|
+
const patterns = [
|
|
206
|
+
[/rm\s+-[^\n]*r[^\n]*f|rm\s+-[^\n]*f[^\n]*r/i, 'rm -rf'],
|
|
207
|
+
[/\bsudo\b/i, 'sudo'],
|
|
208
|
+
[/\bgit\s+push\b[^\n]*(?:--force|-f)\b/i, 'force push'],
|
|
209
|
+
[/\bnpm\s+publish\b/i, 'publish'],
|
|
210
|
+
[/\b(?:kill|pkill)\b/i, 'process kill'],
|
|
211
|
+
[/\bchmod\s+777\b/i, 'chmod 777'],
|
|
212
|
+
[/\bcurl\b|\bwget\b/i, 'network download'],
|
|
213
|
+
];
|
|
214
|
+
for (const [re, label] of patterns) {
|
|
215
|
+
if (re.test(command)) terms.push(label);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (tier === TIERS.SENSITIVE_READ) terms.push('sensitive read');
|
|
219
|
+
if (tier === TIERS.DESTRUCTIVE) terms.push('destructive');
|
|
220
|
+
return [...new Set(terms)].slice(0, 3);
|
|
221
|
+
}
|
|
222
|
+
|
|
190
223
|
function optionToken(option, selected, accent) {
|
|
191
224
|
const cursor = selected ? accent('▸ ') : paint.text.dim(' ');
|
|
192
225
|
const keyTag = paint.text.dim('[') + (selected ? accent(option.key) : paint.brand.data(option.key)) + paint.text.dim('] ');
|
package/src/ui/banner.mjs
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
10
|
|
|
11
|
-
import { icons } from './icons.mjs';
|
|
12
11
|
import { paint, strip } from './palette.mjs';
|
|
13
12
|
import { term } from './term.mjs';
|
|
14
13
|
|
|
@@ -17,56 +16,54 @@ const write = (s) => { try { out.write(s); } catch {} };
|
|
|
17
16
|
|
|
18
17
|
// ── Brand banner ─────────────────────────────────────────────────────────
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
function abundanceGlyph() {
|
|
20
|
+
const loop = term().unicode ? '∞' : 'o';
|
|
21
|
+
return [
|
|
22
|
+
` ${loop}${loop} ${loop}${loop} `,
|
|
23
|
+
`${loop} ${loop} ${loop} ${loop}`,
|
|
24
|
+
`${loop} ${loop} ${loop}`,
|
|
25
|
+
`${loop} ${loop} ${loop} ${loop}`,
|
|
26
|
+
` ${loop}${loop} ${loop}${loop} `,
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function wordmarkLines() {
|
|
31
|
+
return [
|
|
32
|
+
'████ ███ █ █ █ █ █ ███ █ █',
|
|
33
|
+
'█ █ █ █ █ █ █ █ █ █ █ ██ ██',
|
|
34
|
+
'████ █████ █████ █ █ █ █████ █ █ █',
|
|
35
|
+
'█ █ █ █ █ █ █ █ █ █ █ █ █',
|
|
36
|
+
'████ █ █ █ █ ███ █████ █ █ █ █',
|
|
37
|
+
];
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
/**
|
|
34
|
-
* Render the
|
|
35
|
-
* tall, and fits any terminal ≥40 cols.
|
|
41
|
+
* Render the branded startup banner.
|
|
36
42
|
*/
|
|
37
|
-
function
|
|
43
|
+
export function renderBanner(version = '') {
|
|
38
44
|
const dim = paint.text.dim;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
const mark = abundanceGlyph();
|
|
46
|
+
const wordmark = wordmarkLines();
|
|
47
|
+
const code = paint.brand.data('code');
|
|
48
|
+
const vTag = version ? `${dim(' · v' + version)}` : '';
|
|
49
|
+
const markWidth = 11;
|
|
50
|
+
const wordmarkIndent = ' '.repeat(2 + markWidth + 2);
|
|
51
|
+
const lines = [''];
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < wordmark.length; i++) {
|
|
54
|
+
lines.push(` ${paint.brand.accent(mark[i].padEnd(markWidth))} ${paint.bold(paint.brand.primary(wordmark[i]))}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
lines.push(`${wordmarkIndent}${code} ${dim('· abundance in your terminal')}${vTag}`);
|
|
58
|
+
lines.push('');
|
|
59
|
+
return lines.join('\n');
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
/**
|
|
54
|
-
* Print the branded
|
|
63
|
+
* Print the branded startup banner.
|
|
55
64
|
*/
|
|
56
65
|
export function printBanner(version = '') {
|
|
57
|
-
|
|
58
|
-
const orbit = paint.brand.accent(icons.orbit);
|
|
59
|
-
const dev = paint.brand.data('0xB0');
|
|
60
|
-
const vTag = version ? `${dim(' — ')}${dim('v' + version)}` : '';
|
|
61
|
-
|
|
62
|
-
write('\n');
|
|
63
|
-
write(` ${orbit} ${dim('· bahulam — abundance')}\n`);
|
|
64
|
-
write('\n');
|
|
65
|
-
write(`${wordmarkFrame()}\n`);
|
|
66
|
-
write('\n');
|
|
67
|
-
write(` ${dim('abundance, in your terminal')}\n`);
|
|
68
|
-
write(` ${dev}${vTag}\n`);
|
|
69
|
-
write('\n');
|
|
66
|
+
write(renderBanner(version));
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
// ── Project info bar ─────────────────────────────────────────────────────
|
package/src/ui/input-dock.mjs
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
*
|
|
30
30
|
* Config:
|
|
31
31
|
* BAHULAM_INPUT_ROWS_MAX / KEPLER_INPUT_ROWS_MAX 1..12 hard cap on input row growth (default 6)
|
|
32
|
+
* BAHULAM_TTY_MODE=stable / KEPLER_TTY_MODE=stable scrollback-safe transcript, no fixed dock
|
|
33
|
+
* BAHULAM_PLAIN=1 / KEPLER_PLAIN=1 deterministic no-ANSI output for automation
|
|
32
34
|
* BAHULAM_FIXED_INPUT=0 / KEPLER_FIXED_INPUT=0 disable dock entirely (fallback readline)
|
|
33
35
|
*/
|
|
34
36
|
|
|
@@ -347,6 +349,7 @@ export function isInputDockMounted() {
|
|
|
347
349
|
export function mountInputDock({ inputRowsMax: requestedMax } = {}) {
|
|
348
350
|
const t = term();
|
|
349
351
|
if (!t.isTTY || t.plain) return false;
|
|
352
|
+
if (t.ttyMode !== 'rich' || t.fixedInput === false) return false;
|
|
350
353
|
if (process.env.BAHULAM_FIXED_INPUT === '0' || process.env.KEPLER_FIXED_INPUT === '0') return false;
|
|
351
354
|
if (mounted) return true;
|
|
352
355
|
|
package/src/ui/term.mjs
CHANGED
|
@@ -33,7 +33,10 @@ function readEnv() {
|
|
|
33
33
|
return {
|
|
34
34
|
NO_COLOR: env.NO_COLOR,
|
|
35
35
|
FORCE_COLOR: env.FORCE_COLOR,
|
|
36
|
+
BAHULAM_PLAIN: env.BAHULAM_PLAIN,
|
|
36
37
|
KEPLER_PLAIN: env.KEPLER_PLAIN,
|
|
38
|
+
BAHULAM_TTY_MODE: env.BAHULAM_TTY_MODE,
|
|
39
|
+
KEPLER_TTY_MODE: env.KEPLER_TTY_MODE,
|
|
37
40
|
COLORTERM: (env.COLORTERM || '').toLowerCase(),
|
|
38
41
|
TERM: (env.TERM || '').toLowerCase(),
|
|
39
42
|
TERM_PROGRAM: env.TERM_PROGRAM || '',
|
|
@@ -44,6 +47,7 @@ function readEnv() {
|
|
|
44
47
|
function detectColorLevel(env, isTTY) {
|
|
45
48
|
// Hard opt-out (https://no-color.org). Honored even on TTYs.
|
|
46
49
|
if (env.NO_COLOR !== undefined && env.NO_COLOR !== '') return 'none';
|
|
50
|
+
if (env.BAHULAM_PLAIN === '1') return 'none';
|
|
47
51
|
if (env.KEPLER_PLAIN === '1') return 'none';
|
|
48
52
|
|
|
49
53
|
// Hard opt-in. FORCE_COLOR=1|2|3 maps to ansi16|ansi256|truecolor.
|
|
@@ -79,6 +83,7 @@ function detectColorLevel(env, isTTY) {
|
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
function detectUnicode(env) {
|
|
86
|
+
if (env.BAHULAM_PLAIN === '1') return false;
|
|
82
87
|
if (env.KEPLER_PLAIN === '1') return false;
|
|
83
88
|
// Most modern terminals on macOS/Linux handle UTF-8.
|
|
84
89
|
// Windows ConEmu / older terminals are the main holdouts; conservative
|
|
@@ -89,10 +94,18 @@ function detectUnicode(env) {
|
|
|
89
94
|
return false;
|
|
90
95
|
}
|
|
91
96
|
|
|
97
|
+
function detectTtyMode(env) {
|
|
98
|
+
const raw = String(env.BAHULAM_TTY_MODE || env.KEPLER_TTY_MODE || '').trim().toLowerCase();
|
|
99
|
+
if (env.BAHULAM_PLAIN === '1' || env.KEPLER_PLAIN === '1') return 'plain';
|
|
100
|
+
if (raw === 'stable' || raw === 'simple' || raw === 'static' || raw === 'transcript') return 'stable';
|
|
101
|
+
return 'rich';
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
function compute() {
|
|
93
105
|
const env = readEnv();
|
|
94
106
|
const isTTY = !!(process.stdout && process.stdout.isTTY);
|
|
95
107
|
const level = detectColorLevel(env, isTTY);
|
|
108
|
+
const ttyMode = detectTtyMode(env);
|
|
96
109
|
return {
|
|
97
110
|
isTTY,
|
|
98
111
|
colorLevel: level, // 'none' | 'ansi16' | 'ansi256' | 'truecolor'
|
|
@@ -100,7 +113,9 @@ function compute() {
|
|
|
100
113
|
truecolor: level === 'truecolor',
|
|
101
114
|
ansi256: level === 'ansi256' || level === 'truecolor',
|
|
102
115
|
unicode: detectUnicode(env),
|
|
103
|
-
|
|
116
|
+
ttyMode, // 'rich' | 'stable' | 'plain'
|
|
117
|
+
plain: ttyMode === 'plain',
|
|
118
|
+
fixedInput: ttyMode === 'rich',
|
|
104
119
|
columns: (process.stdout && process.stdout.columns) || 80,
|
|
105
120
|
rows: (process.stdout && process.stdout.rows) || 24,
|
|
106
121
|
ci: !!env.CI,
|