@krtclcdy/tersio 2.2.0 → 2.4.0
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 +4 -11
- package/package.json +5 -2
- package/tersio.js +174 -66
package/README.md
CHANGED
|
@@ -11,13 +11,13 @@ Terse replies, compact shell output, and minimal code decisions for [Oh My Pi (O
|
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**Default** (macOS/Linux/WSL) — one line. Installs the CLI via npm, then runs the main installer (scope + Combo preset menus) in the same pass:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
curl -fsSL https://
|
|
17
|
+
curl -fsSL https://github.com/KurutoDenzeru/tersio/releases/latest/download/install.sh | sh
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
OMP
|
|
20
|
+
Prefer OMP-managed updates and feature flags? Install as an OMP plugin instead:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
omp plugin install @krtclcdy/tersio
|
|
@@ -30,13 +30,6 @@ omp plugin install '@krtclcdy/tersio[caveman,ponytail]'
|
|
|
30
30
|
omp plugin features @krtclcdy/tersio --disable rtk
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
npm CLI (installs add-ons into your OMP home):
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm install -g @krtclcdy/tersio@latest
|
|
37
|
-
tersio install
|
|
38
|
-
```
|
|
39
|
-
|
|
40
33
|
Then restart OMP and enable a preset:
|
|
41
34
|
|
|
42
35
|
```text
|
|
@@ -167,7 +160,7 @@ The installer writes `<file>.bak` before replacing an extension source; the upda
|
|
|
167
160
|
## Requirements
|
|
168
161
|
|
|
169
162
|
- [OMP](https://github.com/can1357/oh-my-pi)
|
|
170
|
-
- Node.js
|
|
163
|
+
- Node.js 20.12+ with npm
|
|
171
164
|
|
|
172
165
|
Windows/WSL have separate OMP homes — install from the environment where OMP runs. Inside WSL, `command -v npm` must resolve to a Linux path, not `/mnt/c/`.
|
|
173
166
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krtclcdy/tersio",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "✂️ Token-saving OMP add-ons: terse Caveman replies, compact RTK shell output, lean Ponytail code calls — one-command combo presets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"omp",
|
|
@@ -119,11 +119,14 @@
|
|
|
119
119
|
"test": "tsx --test test/*.test.ts test/*/*.test.ts"
|
|
120
120
|
},
|
|
121
121
|
"engines": {
|
|
122
|
-
"node": ">=
|
|
122
|
+
"node": ">=20.12.0"
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|
|
125
125
|
"@types/node": "^26.4.1",
|
|
126
126
|
"tsx": "^4.23.13",
|
|
127
127
|
"typescript": "^7.0.2"
|
|
128
|
+
},
|
|
129
|
+
"dependencies": {
|
|
130
|
+
"@clack/prompts": "^1.8.0"
|
|
128
131
|
}
|
|
129
132
|
}
|
package/tersio.js
CHANGED
|
@@ -10,6 +10,7 @@ import readline from 'node:readline';
|
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { promisify } from 'node:util';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
|
+
import { cancel as clackCancel, confirm as clackConfirm, select as clackSelect, spinner as clackSpinner, tasks as clackTasks } from '@clack/prompts';
|
|
13
14
|
import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API, fetchJson, findFile, httpsGet, httpsDownload, sha256File, parseChecksum, readTextIfExists, rtkPlatformSpec, } from './extensions/lib/utils.js';
|
|
14
15
|
const IS_WINDOWS = process.platform === 'win32';
|
|
15
16
|
const RTK_BINARY_NAME = IS_WINDOWS ? 'rtk.exe' : 'rtk';
|
|
@@ -112,7 +113,18 @@ function debug(...a) {
|
|
|
112
113
|
const RL = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
113
114
|
let rlOpen = true;
|
|
114
115
|
function ask(q) {
|
|
115
|
-
return new Promise((resolve) =>
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
if (!rlOpen) {
|
|
118
|
+
resolve('');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
RL.question(q, resolve);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
resolve('');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
116
128
|
}
|
|
117
129
|
function closeRL() { if (rlOpen) {
|
|
118
130
|
RL.close();
|
|
@@ -139,6 +151,9 @@ async function execP(cmd, args, opts = {}) {
|
|
|
139
151
|
...opts,
|
|
140
152
|
});
|
|
141
153
|
}
|
|
154
|
+
async function execNetwork(label, cmd, args, opts = {}) {
|
|
155
|
+
return withInteractiveSpinner(label, () => execP(cmd, args, opts));
|
|
156
|
+
}
|
|
142
157
|
async function writeIfChanged(dest, content, options = {}) {
|
|
143
158
|
const existing = await readTextIfExists(dest);
|
|
144
159
|
if (existing === content) {
|
|
@@ -321,7 +336,7 @@ async function stepPonytail(pluginsDir, userDir, options) {
|
|
|
321
336
|
else {
|
|
322
337
|
// Try omp plugin install first
|
|
323
338
|
try {
|
|
324
|
-
await
|
|
339
|
+
await execNetwork('Installing Ponytail plugin', OMP_BIN, ['plugin', 'install', PONYTAIL_GITHUB_SPEC], { cwd: pluginsDir });
|
|
325
340
|
console.log(' [ok] omp plugin install ran');
|
|
326
341
|
}
|
|
327
342
|
catch (e) {
|
|
@@ -329,7 +344,7 @@ async function stepPonytail(pluginsDir, userDir, options) {
|
|
|
329
344
|
}
|
|
330
345
|
if (options.reinstall) {
|
|
331
346
|
try {
|
|
332
|
-
await
|
|
347
|
+
await execNetwork('Refreshing Ponytail package', 'npm', ['install', PONYTAIL_NPM_SPEC, '--save', '--no-audit', '--no-fund'], { cwd: pluginsDir, timeout: 120000 });
|
|
333
348
|
console.log(' [ok] Ponytail refreshed');
|
|
334
349
|
}
|
|
335
350
|
catch (e) {
|
|
@@ -343,12 +358,12 @@ async function stepPonytail(pluginsDir, userDir, options) {
|
|
|
343
358
|
if (!ponytailExtExists) {
|
|
344
359
|
console.log(' [info] pi-extension/index.js not found after omp plugin install — trying npm/bun install...');
|
|
345
360
|
try {
|
|
346
|
-
await
|
|
361
|
+
await execNetwork('Installing Ponytail dependencies', 'npm', ['install'], { cwd: pluginsDir, timeout: 120000 });
|
|
347
362
|
console.log(' [ok] npm install completed');
|
|
348
363
|
}
|
|
349
364
|
catch {
|
|
350
365
|
try {
|
|
351
|
-
await
|
|
366
|
+
await execNetwork('Installing Ponytail dependencies', 'bun', ['install'], { cwd: pluginsDir, timeout: 120000 });
|
|
352
367
|
console.log(' [ok] bun install completed');
|
|
353
368
|
}
|
|
354
369
|
catch (e2) {
|
|
@@ -364,7 +379,7 @@ async function stepPonytail(pluginsDir, userDir, options) {
|
|
|
364
379
|
try {
|
|
365
380
|
const dest = path.join(pluginsDir, 'node_modules', '@dietrichgebert', 'ponytail');
|
|
366
381
|
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
367
|
-
await
|
|
382
|
+
await execNetwork('Cloning Ponytail repository', 'git', ['clone', '--depth', '1', 'https://github.com/DietrichGebert/ponytail.git', dest], { timeout: 180000 });
|
|
368
383
|
console.log(' [ok] git clone completed');
|
|
369
384
|
ponytailExtExists = await probeExt();
|
|
370
385
|
}
|
|
@@ -421,11 +436,11 @@ async function stepSelfPlugin(pluginsDir, options) {
|
|
|
421
436
|
await fs.mkdir(pluginsDir, { recursive: true });
|
|
422
437
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
423
438
|
try {
|
|
424
|
-
await
|
|
439
|
+
await execNetwork('Installing Tersio plugin dependencies', 'npm', ['install', '--no-audit', '--no-fund'], { cwd: pluginsDir, timeout: 180000 });
|
|
425
440
|
}
|
|
426
441
|
catch {
|
|
427
442
|
try {
|
|
428
|
-
await
|
|
443
|
+
await execNetwork('Installing Tersio plugin dependencies', 'bun', ['install'], { cwd: pluginsDir, timeout: 180000 });
|
|
429
444
|
}
|
|
430
445
|
catch (e) {
|
|
431
446
|
console.log(` [fail] Could not install ${PACKAGE_NAME} into plugins dir: ${e.message}`);
|
|
@@ -524,7 +539,7 @@ async function extractRtkArchive(archivePath, extractDir) {
|
|
|
524
539
|
async function stepRtk(binDir, options) {
|
|
525
540
|
console.log('\n[3/7] Installing RTK binary...');
|
|
526
541
|
try {
|
|
527
|
-
const release = await fetchJson(RTK_RELEASE_API);
|
|
542
|
+
const release = await withInteractiveSpinner('Finding latest RTK release', () => fetchJson(RTK_RELEASE_API));
|
|
528
543
|
const triple = resolveRtkTriple();
|
|
529
544
|
if (!triple)
|
|
530
545
|
return;
|
|
@@ -541,10 +556,14 @@ async function stepRtk(binDir, options) {
|
|
|
541
556
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'omp-rtk-'));
|
|
542
557
|
try {
|
|
543
558
|
const archivePath = path.join(tmpDir, asset.name);
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
559
|
+
const checksumsText = await withInteractiveSpinner('Downloading RTK binary', async (update) => {
|
|
560
|
+
const [downloadedChecksums] = await Promise.all([
|
|
561
|
+
downloadRtkChecksums(release),
|
|
562
|
+
httpsDownload(asset.browser_download_url, archivePath),
|
|
563
|
+
]);
|
|
564
|
+
update('Verifying RTK checksum');
|
|
565
|
+
return downloadedChecksums;
|
|
566
|
+
});
|
|
548
567
|
if (!await verifyRtkArchive(archivePath, asset.name, checksumsText))
|
|
549
568
|
return;
|
|
550
569
|
const extractDir = path.join(tmpDir, 'extracted');
|
|
@@ -623,7 +642,7 @@ async function fetchCavemanRule(options) {
|
|
|
623
642
|
if (options.dryRun)
|
|
624
643
|
return (await readTextIfExists(path.join(path.dirname(CAVEMAN_INDEX), 'rule.md'))) || '';
|
|
625
644
|
try {
|
|
626
|
-
return await httpsGet(CAVEMAN_REMOTE_RULE);
|
|
645
|
+
return await withInteractiveSpinner('Fetching Caveman rule', () => httpsGet(CAVEMAN_REMOTE_RULE));
|
|
627
646
|
}
|
|
628
647
|
catch (e) {
|
|
629
648
|
console.log(` [warn] Could not fetch caveman rule: ${e.message}`);
|
|
@@ -679,29 +698,61 @@ async function runDoctor() {
|
|
|
679
698
|
const comboIndex = path.join(extDir, 'combo-toggle', 'index.js');
|
|
680
699
|
const modeReinforcement = path.join(extDir, 'shared', 'mode-reinforcement.js');
|
|
681
700
|
const selfPkg = path.join(pluginsDir, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
682
|
-
// Independent probes
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
fs.readdir(
|
|
687
|
-
|
|
688
|
-
readTextIfExists(
|
|
689
|
-
readTextIfExists(
|
|
690
|
-
readTextIfExists(
|
|
691
|
-
readTextIfExists(
|
|
692
|
-
readTextIfExists(
|
|
693
|
-
readTextIfExists(
|
|
694
|
-
readTextIfExists(
|
|
695
|
-
readTextIfExists(
|
|
696
|
-
readTextIfExists(
|
|
697
|
-
readTextIfExists(
|
|
698
|
-
readTextIfExists(
|
|
699
|
-
readTextIfExists(
|
|
700
|
-
|
|
701
|
-
fs.stat(
|
|
702
|
-
fs.stat(
|
|
703
|
-
|
|
704
|
-
|
|
701
|
+
// Independent probes start concurrently; sections report in fixed order as
|
|
702
|
+
// their data settles. Every probe resolves instead of rejecting.
|
|
703
|
+
const probes = {
|
|
704
|
+
ompVersion: execP(OMP_BIN, ['--version']).then((r) => r.stdout.trim(), () => null),
|
|
705
|
+
agentEntries: fs.readdir(agentDir).catch(() => null),
|
|
706
|
+
extEntries: fs.readdir(extDir).catch(() => null),
|
|
707
|
+
sharedStateText: readTextIfExists(path.join(extDir, 'shared', 'session-state.js')),
|
|
708
|
+
configText: readTextIfExists(configPath),
|
|
709
|
+
ponytailPkgText: readTextIfExists(ponytailPkg),
|
|
710
|
+
ponytailExtText: readTextIfExists(ponytailExt),
|
|
711
|
+
pluginsPkgRaw: readTextIfExists(path.join(pluginsDir, 'package.json')),
|
|
712
|
+
selfPkgText: readTextIfExists(selfPkg),
|
|
713
|
+
rtkBinText: readTextIfExists(rtkBin),
|
|
714
|
+
cavemanIndexText: readTextIfExists(cavemanIndex),
|
|
715
|
+
cavemanRuleText: readTextIfExists(cavemanRule),
|
|
716
|
+
rtkIndexText: readTextIfExists(rtkIndex),
|
|
717
|
+
updaterIndexText: readTextIfExists(updaterIndex),
|
|
718
|
+
comboIndexText: readTextIfExists(comboIndex),
|
|
719
|
+
modeReinforcementText: readTextIfExists(modeReinforcement),
|
|
720
|
+
ruleMtime: fs.stat(cavemanRule).catch(() => null),
|
|
721
|
+
rtkMtime: fs.stat(rtkBin).catch(() => null),
|
|
722
|
+
ponytailMtime: fs.stat(ponytailPkg).catch(() => null),
|
|
723
|
+
updateVersion: checkForUpdate(),
|
|
724
|
+
};
|
|
725
|
+
const rtkVersionProbe = probes.rtkBinText.then((text) => text === null ? null : execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => r.stdout.trim() || r.stderr.trim() || null, (e) => {
|
|
726
|
+
const err = e;
|
|
727
|
+
return err.stdout?.trim() || err.stderr?.trim() || null;
|
|
728
|
+
}));
|
|
729
|
+
const [ompVersion, agentEntries, extEntries, sharedStateText, configText, updateVersion] = await runInteractivePhase('Checking environment and installation', () => Promise.all([
|
|
730
|
+
probes.ompVersion,
|
|
731
|
+
probes.agentEntries,
|
|
732
|
+
probes.extEntries,
|
|
733
|
+
probes.sharedStateText,
|
|
734
|
+
probes.configText,
|
|
735
|
+
probes.updateVersion,
|
|
736
|
+
]));
|
|
737
|
+
const [cavemanIndexText, rtkIndexText, updaterIndexText, comboIndexText, modeReinforcementText, ponytailPkgText, ponytailExtText, pluginsPkgRaw, selfPkgText] = await runInteractivePhase('Checking extensions and plugins', () => Promise.all([
|
|
738
|
+
probes.cavemanIndexText,
|
|
739
|
+
probes.rtkIndexText,
|
|
740
|
+
probes.updaterIndexText,
|
|
741
|
+
probes.comboIndexText,
|
|
742
|
+
probes.modeReinforcementText,
|
|
743
|
+
probes.ponytailPkgText,
|
|
744
|
+
probes.ponytailExtText,
|
|
745
|
+
probes.pluginsPkgRaw,
|
|
746
|
+
probes.selfPkgText,
|
|
747
|
+
]));
|
|
748
|
+
const [cavemanRuleText, ruleMtime, rtkBinText, rtkMtime, rtkVersion, ponytailMtime] = await runInteractivePhase('Checking add-ons', () => Promise.all([
|
|
749
|
+
probes.cavemanRuleText,
|
|
750
|
+
probes.ruleMtime,
|
|
751
|
+
probes.rtkBinText,
|
|
752
|
+
probes.rtkMtime,
|
|
753
|
+
rtkVersionProbe,
|
|
754
|
+
probes.ponytailMtime,
|
|
755
|
+
]));
|
|
705
756
|
// Categorized output with a tally; section headers group related probes.
|
|
706
757
|
const tally = { ok: 0, missing: 0, warn: 0 };
|
|
707
758
|
function check(label, ok, detail = '') {
|
|
@@ -750,10 +801,6 @@ async function runDoctor() {
|
|
|
750
801
|
section('Add-ons');
|
|
751
802
|
const ruleAge = ruleMtime ? `updated ${relTime(Date.now() - ruleMtime.mtimeMs)}` : '';
|
|
752
803
|
check('Caveman rule', cavemanRuleText !== null, ruleAge);
|
|
753
|
-
const rtkVersion = rtkBinText === null ? null : await execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => r.stdout.trim() || r.stderr.trim() || null, (e) => {
|
|
754
|
-
const err = e;
|
|
755
|
-
return err.stdout?.trim() || err.stderr?.trim() || null;
|
|
756
|
-
});
|
|
757
804
|
const rtkAge = rtkMtime ? `updated ${relTime(Date.now() - rtkMtime.mtimeMs)}` : '';
|
|
758
805
|
if (rtkBinText === null)
|
|
759
806
|
check('RTK binary', false, rtkBin);
|
|
@@ -855,11 +902,27 @@ async function runUninstall(options = {}) {
|
|
|
855
902
|
console.log(` ${rtkBin}`);
|
|
856
903
|
}
|
|
857
904
|
if (!confirmed) {
|
|
858
|
-
|
|
859
|
-
if (!answer.toLowerCase().startsWith('y')) {
|
|
860
|
-
console.log('Aborted.');
|
|
905
|
+
if (tty()) {
|
|
861
906
|
closeRL();
|
|
862
|
-
|
|
907
|
+
const confirmedChoice = await clackConfirm({ message: 'Remove the listed Tersio files?', initialValue: false });
|
|
908
|
+
if (typeof confirmedChoice !== 'boolean') {
|
|
909
|
+
clackCancel('Aborted.');
|
|
910
|
+
closeRL();
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
if (!confirmedChoice) {
|
|
914
|
+
console.log('Aborted.');
|
|
915
|
+
closeRL();
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
const answer = await ask('\nProceed? [y/N]: ');
|
|
921
|
+
if (!answer.toLowerCase().startsWith('y')) {
|
|
922
|
+
console.log('Aborted.');
|
|
923
|
+
closeRL();
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
863
926
|
}
|
|
864
927
|
}
|
|
865
928
|
// Remove extension directories (independent paths, so concurrently).
|
|
@@ -918,7 +981,6 @@ async function runUninstall(options = {}) {
|
|
|
918
981
|
return true;
|
|
919
982
|
}
|
|
920
983
|
const SCOPE_MAP = { user: '1', project: '2', both: '3' };
|
|
921
|
-
const COMBO_MENU = { '1': 'off', '2': 'medium', '3': 'balanced', '4': 'max' };
|
|
922
984
|
// --- Update check (cached; used by the bare-invocation banner and doctor) ---
|
|
923
985
|
const UPDATE_CHECK_TTL_MS = 6 * 60 * 60 * 1000;
|
|
924
986
|
function newerThan(a, b) {
|
|
@@ -935,7 +997,7 @@ async function latestPublishedVersion() {
|
|
|
935
997
|
const args = IS_WINDOWS
|
|
936
998
|
? ['/d', '/s', '/c', 'npm', 'view', PACKAGE_NAME, 'version', '--prefer-online']
|
|
937
999
|
: ['view', PACKAGE_NAME, 'version', '--prefer-online'];
|
|
938
|
-
const r = await
|
|
1000
|
+
const r = await execNetwork('Checking npm registry for tersio updates', IS_WINDOWS ? process.env.ComSpec || 'cmd.exe' : 'npm', args, {
|
|
939
1001
|
timeout: 4000,
|
|
940
1002
|
maxBuffer: 1024 * 1024,
|
|
941
1003
|
windowsHide: true,
|
|
@@ -1013,7 +1075,7 @@ async function runLatestUpdate() {
|
|
|
1013
1075
|
else {
|
|
1014
1076
|
try {
|
|
1015
1077
|
const globalArgs = IS_WINDOWS ? ['/d', '/s', '/c', 'npm', 'install', '-g', `${PACKAGE_NAME}@latest`, '--no-audit', '--no-fund'] : ['install', '-g', `${PACKAGE_NAME}@latest`, '--no-audit', '--no-fund'];
|
|
1016
|
-
const globalResult = await
|
|
1078
|
+
const globalResult = await execNetwork('Updating global Tersio CLI', npmCommand, globalArgs, {
|
|
1017
1079
|
timeout: 300000,
|
|
1018
1080
|
maxBuffer: 10 * 1024 * 1024,
|
|
1019
1081
|
windowsHide: true,
|
|
@@ -1031,7 +1093,7 @@ async function runLatestUpdate() {
|
|
|
1031
1093
|
}
|
|
1032
1094
|
}
|
|
1033
1095
|
try {
|
|
1034
|
-
const result = await
|
|
1096
|
+
const result = await execNetwork('Running latest Tersio installer', npmCommand, npmCommandArgs, {
|
|
1035
1097
|
timeout: 300000,
|
|
1036
1098
|
maxBuffer: 10 * 1024 * 1024,
|
|
1037
1099
|
windowsHide: true,
|
|
@@ -1049,7 +1111,6 @@ async function runLatestUpdate() {
|
|
|
1049
1111
|
process.stdout.write(err.stdout);
|
|
1050
1112
|
if (err.stderr)
|
|
1051
1113
|
process.stderr.write(err.stderr);
|
|
1052
|
-
console.error(`\n[fail] Could not run ${PACKAGE_NAME}@latest: ${err.message}`);
|
|
1053
1114
|
process.exitCode = 1;
|
|
1054
1115
|
}
|
|
1055
1116
|
}
|
|
@@ -1065,6 +1126,47 @@ function defaultProfile() {
|
|
|
1065
1126
|
function tty() {
|
|
1066
1127
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1067
1128
|
}
|
|
1129
|
+
let spinnerDepth = 0;
|
|
1130
|
+
// Run async work under a TTY-only timer spinner. The spinner is cleared before
|
|
1131
|
+
// the caller prints its normal result line, preserving locked output shapes.
|
|
1132
|
+
async function withInteractiveSpinner(message, work) {
|
|
1133
|
+
if (!tty() || spinnerDepth > 0)
|
|
1134
|
+
return work(() => { });
|
|
1135
|
+
// Clack manages stdin itself. Close the legacy question interface before its
|
|
1136
|
+
// first use; non-interactive callers never reach this branch.
|
|
1137
|
+
closeRL();
|
|
1138
|
+
const active = clackSpinner({ indicator: 'timer' });
|
|
1139
|
+
active.start(message);
|
|
1140
|
+
spinnerDepth += 1;
|
|
1141
|
+
try {
|
|
1142
|
+
return await work((next) => { active.message(next); });
|
|
1143
|
+
}
|
|
1144
|
+
finally {
|
|
1145
|
+
active.clear();
|
|
1146
|
+
spinnerDepth -= 1;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
async function askInteractiveChoice(message, options, initialValue) {
|
|
1150
|
+
if (!tty())
|
|
1151
|
+
return { status: 'unavailable' };
|
|
1152
|
+
closeRL();
|
|
1153
|
+
const choice = await clackSelect({ message, options, initialValue });
|
|
1154
|
+
if (typeof choice !== 'string') {
|
|
1155
|
+
clackCancel('Aborted.');
|
|
1156
|
+
return { status: 'cancelled' };
|
|
1157
|
+
}
|
|
1158
|
+
return { status: 'selected', value: choice };
|
|
1159
|
+
}
|
|
1160
|
+
// Run collecting work under one TTY-only Clack task. Callers print after the
|
|
1161
|
+
// task completes, keeping normal output out of the spinner animation.
|
|
1162
|
+
async function runInteractivePhase(title, collect) {
|
|
1163
|
+
if (!tty())
|
|
1164
|
+
return collect();
|
|
1165
|
+
closeRL();
|
|
1166
|
+
let result;
|
|
1167
|
+
await clackTasks([{ title, task: async () => { result = await collect(); } }]);
|
|
1168
|
+
return result;
|
|
1169
|
+
}
|
|
1068
1170
|
// Determine install scope: reinstall > flag > non-interactive default > prompt.
|
|
1069
1171
|
async function resolveScope() {
|
|
1070
1172
|
if (reinstall) {
|
|
@@ -1085,6 +1187,17 @@ async function resolveScope() {
|
|
|
1085
1187
|
console.log(` Scope: user (${install ? 'install default' : '--scope omitted, defaulting to user with --yes'})`);
|
|
1086
1188
|
return '1';
|
|
1087
1189
|
}
|
|
1190
|
+
if (tty()) {
|
|
1191
|
+
const choice = await askInteractiveChoice('Install scope', [
|
|
1192
|
+
{ value: '1', label: 'User-level', hint: 'all OMP sessions' },
|
|
1193
|
+
{ value: '2', label: 'Project-level', hint: 'this repo only' },
|
|
1194
|
+
{ value: '3', label: 'Both' },
|
|
1195
|
+
], '1');
|
|
1196
|
+
if (choice.status === 'selected')
|
|
1197
|
+
return choice.value;
|
|
1198
|
+
closeRL();
|
|
1199
|
+
process.exit(130);
|
|
1200
|
+
}
|
|
1088
1201
|
console.log('\nInstall scope:');
|
|
1089
1202
|
console.log(' 1) User-level (all OMP sessions)');
|
|
1090
1203
|
console.log(' 2) Project-level (this repo only)');
|
|
@@ -1103,22 +1216,17 @@ async function resolveProfile() {
|
|
|
1103
1216
|
// Only for a real user at a terminal, only when no default flags were
|
|
1104
1217
|
// given, and never for --apply-update runs.
|
|
1105
1218
|
if (tty() && !profileFlagsGiven && !applyUpdate && (install || reinstall)) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
if (COMBO_MENU[answer]) {
|
|
1118
|
-
profile.comboDefault = COMBO_MENU[answer];
|
|
1119
|
-
break;
|
|
1120
|
-
}
|
|
1121
|
-
console.log(` [fail] Invalid choice: ${answer}. Choose 1, 2, 3, or 4.`);
|
|
1219
|
+
const choice = await askInteractiveChoice('Session-start defaults — Combo preset', [
|
|
1220
|
+
{ value: 'off', label: 'off' },
|
|
1221
|
+
{ value: 'medium', label: 'medium', hint: 'caveman=lite, rtk=on, ponytail=lite' },
|
|
1222
|
+
{ value: 'balanced', label: 'balanced', hint: 'caveman=full, rtk=on, ponytail=full' },
|
|
1223
|
+
{ value: 'max', label: 'max', hint: 'caveman=ultra, rtk=on, ponytail=ultra' },
|
|
1224
|
+
], 'off');
|
|
1225
|
+
if (choice.status === 'selected')
|
|
1226
|
+
profile.comboDefault = choice.value;
|
|
1227
|
+
else {
|
|
1228
|
+
closeRL();
|
|
1229
|
+
process.exit(130);
|
|
1122
1230
|
}
|
|
1123
1231
|
}
|
|
1124
1232
|
// Explicit flags always win over the Combo preset.
|