@krtclcdy/tersio 2.4.1 → 2.6.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 +33 -50
- package/cli/brand.webp +0 -0
- package/cli/common.js +257 -0
- package/cli/dashboard.html +404 -0
- package/cli/dashboard.js +72 -0
- package/cli/doctor.js +150 -0
- package/cli/install.js +610 -0
- package/cli/interactive.js +72 -0
- package/cli/uninstall.js +171 -0
- package/cli/update.js +202 -0
- package/cli/usage.js +71 -0
- package/extensions/ai-addons-updater/index.js +28 -23
- package/extensions/shared/usage-ledger.js +156 -0
- package/extensions/tersio-commands/index.js +148 -0
- package/package.json +12 -2
- package/tersio.js +24 -1342
package/README.md
CHANGED
|
@@ -33,10 +33,10 @@ omp plugin features @krtclcdy/tersio --disable rtk
|
|
|
33
33
|
Then restart OMP and enable a preset:
|
|
34
34
|
|
|
35
35
|
```text
|
|
36
|
-
/combo medium
|
|
36
|
+
/tersio combo medium
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Individual toggles: `/caveman full` · `/rtk on` · `/ponytail full`. Everything starts off until you enable it.
|
|
39
|
+
Individual toggles: `/tersio caveman full` · `/tersio rtk on` · `/tersio ponytail full`. Everything starts off until you enable it.
|
|
40
40
|
|
|
41
41
|
Session-start defaults (prompted during `install`, or flags):
|
|
42
42
|
|
|
@@ -44,7 +44,7 @@ Session-start defaults (prompted during `install`, or flags):
|
|
|
44
44
|
tersio install --combo-default balanced --caveman-default lite --rtk-default on
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Defaults apply to fresh sessions only — anything persisted with `/combo`, `/caveman`, or `/rtk` wins. Stored as plugin settings (`omp plugin config get @krtclcdy/tersio comboDefault`).
|
|
47
|
+
Defaults apply to fresh sessions only — anything persisted with `/tersio combo`, `/tersio caveman`, or `/tersio rtk` wins. Stored as plugin settings (`omp plugin config get @krtclcdy/tersio comboDefault`).
|
|
48
48
|
|
|
49
49
|
## What it installs
|
|
50
50
|
|
|
@@ -54,7 +54,7 @@ Defaults apply to fresh sessions only — anything persisted with `/combo`, `/ca
|
|
|
54
54
|
| **RTK** | Routes noisy shell commands through the RTK binary for compact output |
|
|
55
55
|
| **Ponytail** | Minimal, YAGNI-oriented code decisions |
|
|
56
56
|
| **Combo** | Toggles all three at once. Presets: `off`, `medium`, `balanced`, `max` |
|
|
57
|
-
| **Updater** | `/
|
|
57
|
+
| **Updater** | `/tersio check` and `/tersio update` cover Ponytail, RTK, and Caveman in-session, with dry-run and backups |
|
|
58
58
|
|
|
59
59
|
User-level installs also register the package in `~/.omp/plugins` (visible in OMP **Settings → Plugins**), directly through the plugin manifest. Active modes reassert after Ponytail's prompt block on every top-level turn, including after history compaction.
|
|
60
60
|
|
|
@@ -81,6 +81,8 @@ Break-even math, balanced preset (caveman full + rtk + ponytail): `⌈overhead
|
|
|
81
81
|
| `tersio update` | Refresh the CLI, extensions, and add-ons (RTK binary, Caveman rule, Ponytail) |
|
|
82
82
|
| `tersio reinstall` | Fresh install, preserving the Ponytail package |
|
|
83
83
|
| `tersio doctor` | Check OMP, extension, Ponytail, and RTK health |
|
|
84
|
+
| `tersio usage` | Ledger-backed usage + savings report |
|
|
85
|
+
| `tersio dashboard` | Serve the gain dashboard on localhost (`--open`, `--export <file>`, `--port <n>`) |
|
|
84
86
|
| `tersio uninstall` | Remove extensions, registration, and the Ponytail plugin (`--keep-ponytail` keeps Ponytail; `--remove-rtk` also removes the RTK binary) |
|
|
85
87
|
| `tersio version` | Print version |
|
|
86
88
|
|
|
@@ -88,63 +90,44 @@ Flags: `--dry-run`, `--yes`/`-y`, `--verbose`, `--scope`, `--combo-default`/`--c
|
|
|
88
90
|
|
|
89
91
|
## Commands reference
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
Everything runs under one root. Bare `/tersio` prints status.
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
| Command | Purpose |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `/tersio combo off\|medium\|balanced\|max\|status` | preset for all three modes (start here) |
|
|
98
|
+
| `/tersio caveman lite\|full\|ultra\|wenyan\|off\|status` | terse-reply mode |
|
|
99
|
+
| `/tersio rtk on\|off\|status` | compact shell-output toggle |
|
|
100
|
+
| `/tersio ponytail off\|lite\|full\|ultra\|review\|status` | minimal-code level |
|
|
101
|
+
| `/tersio status` | active modes + combo level |
|
|
102
|
+
| `/tersio check` | add-on version check (same as old `/ai-addons check`) |
|
|
103
|
+
| `/tersio update <ponytail\|rtk\|caveman\|all> [--dry-run]` | update add-ons, preview with dry-run |
|
|
104
|
+
| `/tersio gain` | savings summary + dashboard hint |
|
|
105
|
+
| `/tersio usage` | ledger report for this machine |
|
|
106
|
+
| `/tersio help` | this table |
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
### Combo — one preset for all three
|
|
103
109
|
|
|
104
|
-
|
|
110
|
+
Start here: one setting drives all three modes (`medium` = lite/lite/on, `balanced` = full/full/on, `max` = ultra/ultra/on, `off` = normal). Tweaking a mode individually drops to a `custom` mix.
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
/rtk on enable compact RTK output
|
|
108
|
-
/rtk off disable
|
|
109
|
-
/rtk status show current state
|
|
110
|
-
```
|
|
112
|
+
### Caveman — terse replies
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
Pleasantries and restatements go; substance stays. Ladder: `lite`, `full`, `ultra` (fragments only), `wenyan` (compressed classical-Chinese-inspired register).
|
|
113
115
|
|
|
114
|
-
###
|
|
116
|
+
### RTK — compact shell output
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
/ponytail lite light guidance
|
|
118
|
-
/ponytail full full YAGNI enforcement
|
|
119
|
-
/ponytail ultra aggressive simplification
|
|
120
|
-
/ponytail off disable
|
|
121
|
-
/ponytail status show current state
|
|
122
|
-
```
|
|
118
|
+
A real binary, not a prompt trick: `rtk git status|diff|grep|test|tsc|lint` strips noise before output reaches the model. Exact bytes (checksums, patches) bypass it by policy.
|
|
123
119
|
|
|
124
|
-
###
|
|
120
|
+
### Ponytail — minimal code (YAGNI)
|
|
125
121
|
|
|
126
|
-
|
|
127
|
-
/ai-addons check check all add-on versions
|
|
128
|
-
/ai-addons status same as check
|
|
129
|
-
/ai-addons update ponytail update Ponytail
|
|
130
|
-
/ai-addons update rtk update the RTK binary
|
|
131
|
-
/ai-addons update caveman update the Caveman rule
|
|
132
|
-
/ai-addons update all update all three
|
|
133
|
-
/ai-addons update all --dry-run preview without changes
|
|
134
|
-
```
|
|
122
|
+
You Aren't Gonna Need It: smallest working change, no speculative abstractions, no new deps for stdlib jobs. `lite` nudges, `full` enforces, `ultra` challenges the requirement, `review` audits for over-engineering.
|
|
135
123
|
|
|
136
|
-
|
|
124
|
+
`/tersio` persists state and reloads OMP without emitting separate command messages. Presets propagate to task subagents and light the Combo footer indicator; mixed individual settings report via `/tersio status`.
|
|
137
125
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
/combo balanced caveman=full, rtk=on, ponytail=full
|
|
142
|
-
/combo max caveman=ultra, rtk=on, ponytail=ultra
|
|
143
|
-
/combo status show the level and underlying modes
|
|
144
|
-
/combo help show available levels
|
|
145
|
-
```
|
|
126
|
+
### Legacy aliases (still work)
|
|
127
|
+
|
|
128
|
+
The pre-`/tersio` commands remain registered: `/caveman`, `/rtk`, `/combo`, `/ai-addons`. Prefer `/tersio` in new muscle memory; help output lists the aliases as deprecated-but-working.
|
|
146
129
|
|
|
147
|
-
|
|
130
|
+
Usage rows land in `~/.omp/plugins/tersio-usage.jsonl` (local only); `tersio dashboard` serves them as a plain-HTML page on 127.0.0.1.
|
|
148
131
|
|
|
149
132
|
## Files and backups
|
|
150
133
|
|
|
@@ -176,7 +159,7 @@ npm exec --yes --prefer-online --package=@krtclcdy/tersio@latest -- tersio insta
|
|
|
176
159
|
|
|
177
160
|
**RTK missing or not executable:** run `tersio reinstall`, then `tersio doctor`. On Linux/macOS: `chmod +x ~/.bun/bin/rtk`.
|
|
178
161
|
|
|
179
|
-
**Checksum warning or failure:** the installer aborts on RTK checksum mismatch but warns and continues when checksum metadata is unavailable; `/
|
|
162
|
+
**Checksum warning or failure:** the installer aborts on RTK checksum mismatch but warns and continues when checksum metadata is unavailable; `/tersio update rtk` aborts when metadata is missing.
|
|
180
163
|
|
|
181
164
|
## License
|
|
182
165
|
|
package/cli/brand.webp
ADDED
|
Binary file
|
package/cli/common.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// cli/common.ts — shared foundation: CLI flags, paths, exec, file/config helpers.
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { readTextIfExists } from '../extensions/lib/utils.js';
|
|
8
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
9
|
+
const RTK_BINARY_NAME = IS_WINDOWS ? 'rtk.exe' : 'rtk';
|
|
10
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
11
|
+
const OMP_AGENT_DIR = path.join(HOME, '.omp', 'agent');
|
|
12
|
+
const OMP_PLUGINS_DIR = path.join(HOME, '.omp', 'plugins');
|
|
13
|
+
const BUN_BIN_DIR = path.join(HOME, '.bun', 'bin');
|
|
14
|
+
const OMP_BIN = IS_WINDOWS ? 'omp.cmd' : 'omp';
|
|
15
|
+
const PACKAGE_NAME = '@krtclcdy/tersio';
|
|
16
|
+
const PACKAGE_BIN = 'tersio';
|
|
17
|
+
const { version: PACKAGE_VERSION } = createRequire(import.meta.url)('../package.json');
|
|
18
|
+
// Session-start mode defaults for fresh installs.
|
|
19
|
+
const CAVEMAN_DEFAULTS = new Set(['off', 'lite', 'full', 'ultra', 'wenyan']);
|
|
20
|
+
const PONYTAIL_DEFAULTS = new Set(['off', 'lite', 'full', 'ultra', 'review']);
|
|
21
|
+
const RTK_DEFAULTS = new Set(['on', 'off']);
|
|
22
|
+
// ponytail: Combo preset implies all three modes. Mirrors COMBO_LEVELS in
|
|
23
|
+
// extensions/shared/session-state.ts (rtk as boolean here). Single source.
|
|
24
|
+
const COMBO_PRESET_MODES = {
|
|
25
|
+
off: { caveman: 'off', rtk: false, ponytail: 'off' },
|
|
26
|
+
medium: { caveman: 'lite', rtk: true, ponytail: 'lite' },
|
|
27
|
+
balanced: { caveman: 'full', rtk: true, ponytail: 'full' },
|
|
28
|
+
max: { caveman: 'ultra', rtk: true, ponytail: 'ultra' },
|
|
29
|
+
};
|
|
30
|
+
const COMBO_DEFAULTS = new Set(Object.keys(COMBO_PRESET_MODES));
|
|
31
|
+
function parseEnum(value, valid, flag) {
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
const v = value.trim().toLowerCase();
|
|
35
|
+
if (!valid.has(v)) {
|
|
36
|
+
console.error(`[fail] Invalid ${flag}: ${value}. Valid: ${[...valid].join(', ')}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
function flagValue(name) {
|
|
42
|
+
const i = args.indexOf(name);
|
|
43
|
+
if (i !== -1)
|
|
44
|
+
return args[i + 1];
|
|
45
|
+
const prefix = `${name}=`;
|
|
46
|
+
return args.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
|
|
47
|
+
}
|
|
48
|
+
// --- CLI flags ---
|
|
49
|
+
const COMMANDS = new Set(['install', 'update', 'reinstall', 'doctor', 'uninstall', 'usage', 'dashboard', 'version', 'help']);
|
|
50
|
+
const args = process.argv.slice(2);
|
|
51
|
+
const commandArg = args.find((arg, index) => !arg.startsWith('-') && args[index - 1] !== '--scope');
|
|
52
|
+
const command = commandArg?.toLowerCase() || null;
|
|
53
|
+
const unknownCommand = command !== null && !COMMANDS.has(command);
|
|
54
|
+
const install = command === 'install';
|
|
55
|
+
const update = command === 'update';
|
|
56
|
+
const reinstall = command === 'reinstall';
|
|
57
|
+
const showVersion = command === 'version' || args.includes('--version') || args.includes('-v');
|
|
58
|
+
const showHelp = command === 'help' || args.includes('--help') || args.includes('-h');
|
|
59
|
+
const applyUpdate = args.includes('--apply-update');
|
|
60
|
+
const dryRun = args.includes('--dry-run');
|
|
61
|
+
const yes = args.includes('--yes') || args.includes('-y') || install || update || reinstall || applyUpdate;
|
|
62
|
+
const verbose = args.includes('--verbose');
|
|
63
|
+
const doctor = command === 'doctor' || args.includes('--doctor');
|
|
64
|
+
const usage = command === 'usage' || args.includes('--usage');
|
|
65
|
+
const dashboard = command === 'dashboard' || args.includes('--dashboard');
|
|
66
|
+
const dashboardPort = Number.parseInt(flagValue('--port') ?? '', 10) || 0;
|
|
67
|
+
const dashboardOpen = args.includes('--open');
|
|
68
|
+
const dashboardExport = flagValue('--export') ?? null;
|
|
69
|
+
const uninstall = command === 'uninstall' || args.includes('--uninstall');
|
|
70
|
+
const removePonytail = args.includes('--remove-ponytail');
|
|
71
|
+
const keepPonytail = args.includes('--keep-ponytail');
|
|
72
|
+
const removeRtk = args.includes('--remove-rtk');
|
|
73
|
+
const scopeFlag = flagValue('--scope')?.toLowerCase() ?? null;
|
|
74
|
+
// --- Profile selection (session-start mode defaults) ---
|
|
75
|
+
const comboDefaultFlag = parseEnum(flagValue('--combo-default'), COMBO_DEFAULTS, '--combo-default');
|
|
76
|
+
const cavemanDefaultFlag = parseEnum(flagValue('--caveman-default'), CAVEMAN_DEFAULTS, '--caveman-default');
|
|
77
|
+
const ponytailDefaultFlag = parseEnum(flagValue('--ponytail-default'), PONYTAIL_DEFAULTS, '--ponytail-default');
|
|
78
|
+
const rtkDefaultFlag = parseEnum(flagValue('--rtk-default'), RTK_DEFAULTS, '--rtk-default');
|
|
79
|
+
const profileFlagsGiven = [comboDefaultFlag, cavemanDefaultFlag, rtkDefaultFlag, ponytailDefaultFlag]
|
|
80
|
+
.some((flag) => flag !== undefined);
|
|
81
|
+
function debug(...a) {
|
|
82
|
+
if (verbose)
|
|
83
|
+
console.log(' [debug]', ...a);
|
|
84
|
+
}
|
|
85
|
+
const execFileP = promisify(execFile);
|
|
86
|
+
async function execP(cmd, args, opts = {}) {
|
|
87
|
+
return execFileP(cmd, args, {
|
|
88
|
+
timeout: opts.timeout || 120000,
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
...opts,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function writeIfChanged(dest, content, options = {}) {
|
|
94
|
+
const existing = await readTextIfExists(dest);
|
|
95
|
+
if (existing === content) {
|
|
96
|
+
debug(`${dest} already up to date`);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (options.dryRun) {
|
|
100
|
+
console.log(` [dry-run] would write ${dest}`);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
104
|
+
if (existing !== null) {
|
|
105
|
+
await fs.copyFile(dest, `${dest}.bak`);
|
|
106
|
+
debug(`${path.basename(dest)} → ${path.basename(dest)}.bak`);
|
|
107
|
+
}
|
|
108
|
+
await fs.writeFile(dest, content, 'utf8');
|
|
109
|
+
console.log(` [write] ${dest}`);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
// omp ships 'extensions: null'; appending list items under a null scalar breaks
|
|
113
|
+
// YAML parsing. Normalize null/~/[]/empty to a mapping key first.
|
|
114
|
+
const EXTENSIONS_KEY_RE = /^\s*extensions\s*:/i;
|
|
115
|
+
const EXTENSIONS_NULL_RE = /^\s*extensions\s*:\s*(?:\[\s*\]|null|~)?\s*$/i;
|
|
116
|
+
function normalizeExtensionsKey(lines) {
|
|
117
|
+
const idx = lines.findIndex((l) => EXTENSIONS_NULL_RE.test(l));
|
|
118
|
+
if (idx === -1)
|
|
119
|
+
return false;
|
|
120
|
+
lines[idx] = 'extensions:';
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
async function writeConfigLines(configPath, lines, logMsg) {
|
|
124
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
125
|
+
await fs.copyFile(configPath, `${configPath}.bak`).catch(() => { });
|
|
126
|
+
await fs.writeFile(configPath, lines.join('\n'), 'utf8');
|
|
127
|
+
console.log(logMsg);
|
|
128
|
+
}
|
|
129
|
+
async function ensureExtensionInConfig(configPath, extensionPath, label, options = {}) {
|
|
130
|
+
const normalizedPath = extensionPath.replace(/\\/g, '/');
|
|
131
|
+
const line = ` - ${normalizedPath}`;
|
|
132
|
+
const raw = await readTextIfExists(configPath);
|
|
133
|
+
let lines = (raw || '').split('\n');
|
|
134
|
+
if (lines.some((l) => l.includes(normalizedPath))) {
|
|
135
|
+
debug(`${label} already in config.yml`);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const extLineIdx = lines.findIndex((l) => EXTENSIONS_KEY_RE.test(l));
|
|
139
|
+
if (options.dryRun) {
|
|
140
|
+
console.log(` [dry-run] would add ${label} to config.yml: ${normalizedPath}`);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (normalizeExtensionsKey(lines)) {
|
|
144
|
+
lines.splice(extLineIdx + 1, 0, line, '');
|
|
145
|
+
}
|
|
146
|
+
else if (extLineIdx === -1) {
|
|
147
|
+
lines.push('extensions:');
|
|
148
|
+
lines.push(line);
|
|
149
|
+
lines.push('');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
lines.splice(extLineIdx + 1, 0, line);
|
|
153
|
+
}
|
|
154
|
+
await writeConfigLines(configPath, lines, ` [write] Added ${label} to config.yml`);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
async function ensureExtensionAfterConfigEntry(configPath, extensionPath, afterPath, label, options = {}) {
|
|
158
|
+
const normalizedPath = extensionPath.replace(/\\/g, '/');
|
|
159
|
+
const normalizedAfterPath = afterPath.replace(/\\/g, '/');
|
|
160
|
+
const line = ` - ${normalizedPath}`;
|
|
161
|
+
const raw = await readTextIfExists(configPath);
|
|
162
|
+
const lines = (raw || '').split('\n');
|
|
163
|
+
const existingIndex = lines.findIndex((entry) => entry.includes(normalizedPath));
|
|
164
|
+
const afterIndex = lines.findIndex((entry) => entry.includes(normalizedAfterPath));
|
|
165
|
+
if (existingIndex !== -1 && afterIndex !== -1 && existingIndex === afterIndex + 1)
|
|
166
|
+
return false;
|
|
167
|
+
if (options.dryRun) {
|
|
168
|
+
console.log(` [dry-run] would place ${label} after Ponytail in config.yml: ${normalizedPath}`);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
if (existingIndex !== -1)
|
|
172
|
+
lines.splice(existingIndex, 1);
|
|
173
|
+
const refreshedAfterIndex = lines.findIndex((entry) => entry.includes(normalizedAfterPath));
|
|
174
|
+
if (refreshedAfterIndex !== -1) {
|
|
175
|
+
lines.splice(refreshedAfterIndex + 1, 0, line);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const extensionsIndex = lines.findIndex((entry) => EXTENSIONS_KEY_RE.test(entry));
|
|
179
|
+
if (extensionsIndex === -1) {
|
|
180
|
+
lines.push('extensions:', line, '');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
normalizeExtensionsKey(lines);
|
|
184
|
+
lines.splice(extensionsIndex + 1, 0, line);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await writeConfigLines(configPath, lines, ` [write] Placed ${label} after Ponytail in config.yml`);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
function readPonytailConfig() {
|
|
191
|
+
const dir = process.env.XDG_CONFIG_HOME
|
|
192
|
+
? path.join(process.env.XDG_CONFIG_HOME, 'ponytail')
|
|
193
|
+
: path.join(HOME, '.config', 'ponytail');
|
|
194
|
+
return { dir, path: path.join(dir, 'config.json') };
|
|
195
|
+
}
|
|
196
|
+
function parseJsonObject(text) {
|
|
197
|
+
if (!text)
|
|
198
|
+
return null;
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(text.replace(/^\uFEFF/, ''));
|
|
201
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
202
|
+
return null;
|
|
203
|
+
return parsed;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function parsePonytailConfig(existing) {
|
|
210
|
+
return parseJsonObject(existing) ?? {};
|
|
211
|
+
}
|
|
212
|
+
async function patchPonytailConfig(apply, dryRunNote, writeNote, options = {}) {
|
|
213
|
+
const config = readPonytailConfig();
|
|
214
|
+
if (options.dryRun) {
|
|
215
|
+
console.log(` [dry-run] ${dryRunNote} in ${config.path}`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const cfg = parsePonytailConfig(await readTextIfExists(config.path));
|
|
219
|
+
if (!apply(cfg)) {
|
|
220
|
+
debug('Ponytail config already up to date');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
await fs.mkdir(config.dir, { recursive: true });
|
|
224
|
+
await fs.writeFile(config.path, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
225
|
+
console.log(` [write] ${writeNote} in ${config.path}`);
|
|
226
|
+
}
|
|
227
|
+
async function ensurePonytailConfigValue(key, value, options = {}) {
|
|
228
|
+
await patchPonytailConfig((cfg) => {
|
|
229
|
+
if (cfg[key] === value)
|
|
230
|
+
return false;
|
|
231
|
+
cfg[key] = value;
|
|
232
|
+
return true;
|
|
233
|
+
}, `would set Ponytail ${key}=${value}`, `Set Ponytail ${key}=${value}`, options);
|
|
234
|
+
}
|
|
235
|
+
// Read plugins/package.json tolerantly; corrupt or missing files start fresh
|
|
236
|
+
// with installer-managed defaults.
|
|
237
|
+
async function readPluginsPackage(pkgPath) {
|
|
238
|
+
const pkg = parseJsonObject(await readTextIfExists(pkgPath)) ?? {};
|
|
239
|
+
return { ...pkg, name: pkg.name || 'omp-plugins', private: true, dependencies: pkg.dependencies || {} };
|
|
240
|
+
}
|
|
241
|
+
const SCOPE_MAP = { user: '1', project: '2', both: '3' };
|
|
242
|
+
function relTime(ageMs) {
|
|
243
|
+
const mins = Math.floor(ageMs / 60000);
|
|
244
|
+
if (mins < 1)
|
|
245
|
+
return 'just now';
|
|
246
|
+
if (mins < 60)
|
|
247
|
+
return `${mins} min${mins === 1 ? '' : 's'} ago`;
|
|
248
|
+
const hours = Math.floor(mins / 60);
|
|
249
|
+
if (hours < 24)
|
|
250
|
+
return `${hours} hour${hours === 1 ? '' : 's'} ago`;
|
|
251
|
+
const days = Math.floor(hours / 24);
|
|
252
|
+
if (days < 14)
|
|
253
|
+
return `${days} day${days === 1 ? '' : 's'} ago`;
|
|
254
|
+
const weeks = Math.floor(days / 7);
|
|
255
|
+
return `${weeks} week${weeks === 1 ? '' : 's'} ago`;
|
|
256
|
+
}
|
|
257
|
+
export { IS_WINDOWS, RTK_BINARY_NAME, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, BUN_BIN_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_BIN, PACKAGE_VERSION, CAVEMAN_DEFAULTS, PONYTAIL_DEFAULTS, RTK_DEFAULTS, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, unknownCommand, install, update, reinstall, showVersion, showHelp, applyUpdate, dryRun, yes, verbose, doctor, uninstall, usage, dashboard, dashboardPort, dashboardOpen, dashboardExport, removePonytail, keepPonytail, removeRtk, scopeFlag, comboDefaultFlag, cavemanDefaultFlag, ponytailDefaultFlag, rtkDefaultFlag, profileFlagsGiven, debug, execFileP, execP, writeIfChanged, normalizeExtensionsKey, EXTENSIONS_KEY_RE, writeConfigLines, ensureExtensionInConfig, ensureExtensionAfterConfigEntry, readPonytailConfig, parseJsonObject, parsePonytailConfig, patchPonytailConfig, ensurePonytailConfigValue, readPluginsPackage, SCOPE_MAP, relTime, };
|