@krtclcdy/tersio 2.24.0 → 2.25.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 +28 -24
- package/cli/banner.js +7 -5
- package/cli/common.js +14 -13
- package/cli/currency.js +2 -7
- package/cli/dashboard.js +220 -13
- package/cli/doctor-fix.js +99 -29
- package/cli/doctor.js +32 -35
- package/cli/hosts.js +23 -26
- package/cli/install.js +228 -115
- package/cli/interactive.js +65 -21
- package/cli/manifest.js +8 -4
- package/cli/profile.js +49 -16
- package/cli/reset.js +3 -7
- package/cli/rtk-wiring.js +29 -32
- package/cli/settings.js +40 -16
- package/cli/uninstall.js +79 -29
- package/cli/update.js +27 -20
- package/cli/usage.js +54 -19
- package/dashboard/dist/index.html +18 -18
- package/extensions/ai-addons-updater/index.js +75 -20
- package/extensions/ai-addons-updater/index.ts +67 -23
- package/extensions/caveman-session/index.js +33 -75
- package/extensions/caveman-session/index.ts +35 -77
- package/extensions/combo-toggle/index.js +40 -62
- package/extensions/combo-toggle/index.ts +38 -72
- package/extensions/lib/utils.js +64 -15
- package/extensions/lib/utils.ts +62 -15
- package/extensions/opencode/index.js +104 -0
- package/extensions/opencode/server.js +190 -0
- package/extensions/opencode/server.ts +200 -0
- package/extensions/rtk-session/index.js +38 -57
- package/extensions/rtk-session/index.ts +36 -55
- package/extensions/shared/carbon.js +4 -10
- package/extensions/shared/carbon.ts +5 -10
- package/extensions/shared/host.js +95 -56
- package/extensions/shared/host.ts +106 -60
- package/extensions/shared/plugin-settings.js +6 -18
- package/extensions/shared/plugin-settings.ts +6 -18
- package/extensions/shared/pricing.js +79 -27
- package/extensions/shared/pricing.ts +80 -28
- package/extensions/shared/rtk-gain.js +4 -11
- package/extensions/shared/rtk-gain.ts +4 -11
- package/extensions/shared/session-state.js +28 -39
- package/extensions/shared/session-state.ts +29 -38
- package/extensions/shared/status.js +38 -0
- package/extensions/shared/status.ts +45 -0
- package/extensions/shared/types.js +1 -2
- package/extensions/shared/types.ts +2 -2
- package/extensions/shared/usage-ledger.js +312 -57
- package/extensions/shared/usage-ledger.ts +321 -61
- package/extensions/shared/usage-store.js +354 -38
- package/extensions/shared/usage-store.ts +360 -43
- package/extensions/tersio-commands/index.js +5 -14
- package/extensions/tersio-commands/index.ts +5 -14
- package/package.json +12 -7
- package/tersio.js +4 -6
- package/cli/agent-hosts.js +0 -42
- package/cli/agents.js +0 -266
- package/cli/layer.js +0 -24
- package/cli/omp-layer.js +0 -51
- package/cli/pi-layer.js +0 -37
- package/cli/pi-wiring.js +0 -92
- package/extensions/shared/omp-prompt.js +0 -12
package/cli/interactive.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/interactive.ts — TTY layer: readline, Clack spinners, selects, task phases.
|
|
2
2
|
import readline from 'node:readline';
|
|
3
|
-
import { cancel as clackCancel, confirm as clackConfirm, select as clackSelect, spinner as clackSpinner, tasks as clackTasks } from '@clack/prompts';
|
|
3
|
+
import { cancel as clackCancel, confirm as clackConfirm, log as clackLog, select as clackSelect, spinner as clackSpinner, tasks as clackTasks } from '@clack/prompts';
|
|
4
4
|
import { execP } from './common.js';
|
|
5
5
|
const RL = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6
6
|
let rlOpen = true;
|
|
@@ -26,13 +26,11 @@ function tty() {
|
|
|
26
26
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
27
27
|
}
|
|
28
28
|
let spinnerDepth = 0;
|
|
29
|
-
// Run async work under a TTY-only timer spinner. The spinner is cleared before
|
|
30
|
-
// the caller prints its normal result line, preserving locked output shapes.
|
|
29
|
+
// Run async work under a TTY-only timer spinner. The spinner is cleared before the caller prints its normal result line, preserving locked output shapes.
|
|
31
30
|
async function withInteractiveSpinner(message, work) {
|
|
32
31
|
if (!tty() || spinnerDepth > 0)
|
|
33
32
|
return work(() => { });
|
|
34
|
-
// Clack manages stdin itself. Close the legacy question interface before its
|
|
35
|
-
// first use; non-interactive callers never reach this branch.
|
|
33
|
+
// Clack manages stdin itself. Close the legacy question interface before its first use; non-interactive callers never reach this branch.
|
|
36
34
|
closeRL();
|
|
37
35
|
const active = clackSpinner({ indicator: 'timer' });
|
|
38
36
|
active.start(message);
|
|
@@ -49,26 +47,33 @@ async function askInteractiveChoice(message, options, initialValue) {
|
|
|
49
47
|
if (!tty())
|
|
50
48
|
return { status: 'unavailable' };
|
|
51
49
|
closeRL();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
try {
|
|
51
|
+
const choice = await clackSelect({ message, options, initialValue });
|
|
52
|
+
if (typeof choice !== 'string') {
|
|
53
|
+
clackCancel('Aborted.');
|
|
54
|
+
return { status: 'cancelled' };
|
|
55
|
+
}
|
|
56
|
+
return { status: 'selected', value: choice };
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
56
59
|
}
|
|
57
|
-
return { status: 'selected', value: choice };
|
|
58
60
|
}
|
|
59
61
|
async function askInteractiveConfirm(message, initialValue = true) {
|
|
60
62
|
if (!tty())
|
|
61
63
|
return { status: 'unavailable' };
|
|
62
64
|
closeRL();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
try {
|
|
66
|
+
const answer = await clackConfirm({ message, initialValue });
|
|
67
|
+
if (typeof answer !== 'boolean') {
|
|
68
|
+
clackCancel('Aborted.');
|
|
69
|
+
return { status: 'cancelled' };
|
|
70
|
+
}
|
|
71
|
+
return { status: 'confirmed', value: answer };
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
67
74
|
}
|
|
68
|
-
return { status: 'confirmed', value: answer };
|
|
69
75
|
}
|
|
70
|
-
// Confirm a destructive run: a Clack dialog at a terminal, a plain prompt in a
|
|
71
|
-
// pipe or script. False means the user declined or aborted.
|
|
76
|
+
// Confirm a destructive run: a Clack dialog at a terminal, a plain prompt in a pipe or script. False means the user declined or aborted.
|
|
72
77
|
async function confirmDestructive(message) {
|
|
73
78
|
if (tty()) {
|
|
74
79
|
closeRL();
|
|
@@ -93,17 +98,56 @@ async function confirmDestructive(message) {
|
|
|
93
98
|
}
|
|
94
99
|
return true;
|
|
95
100
|
}
|
|
96
|
-
// Run collecting work under one TTY-only Clack task. Callers print after the
|
|
97
|
-
// task completes, keeping normal output out of the spinner animation.
|
|
101
|
+
// Run collecting work under one TTY-only Clack task. Callers print after the task completes, keeping normal output out of the spinner animation.
|
|
98
102
|
async function runInteractivePhase(title, collect) {
|
|
99
103
|
if (!tty())
|
|
100
104
|
return collect();
|
|
101
105
|
closeRL();
|
|
102
106
|
let result;
|
|
103
|
-
|
|
107
|
+
try {
|
|
108
|
+
await clackTasks([{ title, task: async () => { result = await collect(); } }]);
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
const TAG_KIND = {
|
|
115
|
+
fail: 'error', warn: 'warn', hint: 'info', ok: 'success', note: 'message',
|
|
116
|
+
write: 'step', rm: 'step', migrate: 'step', skip: 'info', 'dry-run': 'info',
|
|
117
|
+
};
|
|
118
|
+
function sayTagged(line) {
|
|
119
|
+
const m = /^\s*\[([\w-]+)\]\s?([\s\S]*)$/.exec(line);
|
|
120
|
+
const kind = (m && TAG_KIND[m[1]]) || 'info';
|
|
121
|
+
if (tty() && m)
|
|
122
|
+
clackLog[kind](m[2]);
|
|
123
|
+
else
|
|
124
|
+
console.log(line);
|
|
125
|
+
}
|
|
126
|
+
// One live-updating task line instead of a spinner per step.
|
|
127
|
+
async function withInteractiveTask(title, work) {
|
|
128
|
+
if (!tty())
|
|
129
|
+
return work(() => { });
|
|
130
|
+
closeRL();
|
|
131
|
+
let result;
|
|
132
|
+
const spin = clackSpinner();
|
|
133
|
+
spin.start(title);
|
|
134
|
+
try {
|
|
135
|
+
result = await work((message) => spin.message(message));
|
|
136
|
+
spin.stop('done');
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
spin.error(shortSpinnerError(e));
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
}
|
|
104
144
|
return result;
|
|
105
145
|
}
|
|
146
|
+
function shortSpinnerError(e) {
|
|
147
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
148
|
+
return (msg.split('\n')[0] || 'failed').slice(0, 120);
|
|
149
|
+
}
|
|
106
150
|
async function execNetwork(label, cmd, args, opts = {}) {
|
|
107
151
|
return withInteractiveSpinner(label, () => execP(cmd, args, opts));
|
|
108
152
|
}
|
|
109
|
-
export { ask, closeRL, tty, withInteractiveSpinner, execNetwork, askInteractiveChoice, askInteractiveConfirm, confirmDestructive, runInteractivePhase, };
|
|
153
|
+
export { ask, closeRL, tty, withInteractiveSpinner, withInteractiveTask, execNetwork, sayTagged, askInteractiveChoice, askInteractiveConfirm, confirmDestructive, runInteractivePhase, };
|
package/cli/manifest.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
//
|
|
2
|
-
// extension tree. The installer, `doctor --fix extensions`, the doctor's
|
|
3
|
-
// per-host tree row, and the loadability test all read it, so none of them
|
|
4
|
-
// can drift from what actually ships.
|
|
1
|
+
// Single list of install writes, shared by installer, doctor, and tests.
|
|
5
2
|
import path from 'node:path';
|
|
6
3
|
import { fileURLToPath } from 'node:url';
|
|
7
4
|
export const EXT_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'extensions');
|
|
5
|
+
/** Root entrypoint OpenCode resolves for the installed tree; re-exports the versioned entry beside it. Built by join so the path never reads as a relative import (repo-integrity scans literals). */
|
|
6
|
+
const SERVER_SPEC = `./${['opencode', 'server.ts'].join('/')}`;
|
|
7
|
+
export const OPENCODE_SERVER_SHIM = `// Generated by \`tersio install --host opencode\`: OpenCode resolves <pluginDir>/server.ts.
|
|
8
|
+
export { default } from "${SERVER_SPEC}";
|
|
9
|
+
`;
|
|
8
10
|
/** Every file in the tree, as paths relative to the extensions directory. */
|
|
9
11
|
export const TREE_FILES = [
|
|
10
12
|
'shared/host.ts',
|
|
11
13
|
'shared/session-state.ts',
|
|
14
|
+
'shared/status.ts',
|
|
12
15
|
'shared/types.ts',
|
|
13
16
|
'shared/plugin-settings.ts',
|
|
14
17
|
'shared/usage-ledger.ts',
|
|
@@ -21,6 +24,7 @@ export const TREE_FILES = [
|
|
|
21
24
|
'combo-toggle/index.ts',
|
|
22
25
|
'tersio-commands/index.ts',
|
|
23
26
|
'ai-addons-updater/index.ts',
|
|
27
|
+
'opencode/server.ts',
|
|
24
28
|
];
|
|
25
29
|
export function sourcePath(relative) {
|
|
26
30
|
return path.join(EXT_DIR, ...relative.split('/'));
|
package/cli/profile.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
// cli/profile.ts — session-start defaults
|
|
2
|
-
|
|
3
|
-
// Stored in ~/.tersio/settings.json, which every host reads. Extracted from
|
|
4
|
-
// cli/install.ts so both install and settings share it.
|
|
5
|
-
import { existsSync, promises as fs } from 'node:fs';
|
|
1
|
+
// cli/profile.ts — session-start defaults and the display-currency default,
|
|
2
|
+
import { existsSync, promises as fs, readFileSync } from 'node:fs';
|
|
6
3
|
import path from 'node:path';
|
|
7
4
|
import { CAVEMAN_DEFAULTS, COMBO_PRESET_MODES, PACKAGE_NAME, PONYTAIL_DEFAULTS, verbose, } from './common.js';
|
|
8
5
|
import os from 'node:os';
|
|
9
6
|
import { DEFAULT_CURRENCY, isCurrencyCode } from './currency.js';
|
|
10
7
|
import { readTextIfExists } from '../extensions/lib/utils.js';
|
|
11
8
|
import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
|
|
9
|
+
function readTextIfExistsSync(file) {
|
|
10
|
+
try {
|
|
11
|
+
return existsSync(file) ? readFileSync(file, 'utf8') : null;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export const BACKUP_SCHEDULES = new Set(['manual', 'daily', 'weekly', 'monthly']);
|
|
12
18
|
function defaultProfile() {
|
|
13
19
|
return {
|
|
14
20
|
comboDefault: 'off',
|
|
@@ -16,18 +22,20 @@ function defaultProfile() {
|
|
|
16
22
|
rtkDefault: false,
|
|
17
23
|
ponytailDefault: 'off',
|
|
18
24
|
currency: DEFAULT_CURRENCY,
|
|
25
|
+
backupSchedule: 'monthly',
|
|
26
|
+
subagentMarkers: [],
|
|
19
27
|
};
|
|
20
28
|
}
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
// never lose a user's choice.
|
|
24
|
-
async function storedProfile() {
|
|
29
|
+
// Seed once from OMP plugin settings so a pre-~/.tersio install keeps its values. Sync: awaiting let piped stdin arrive before `ask()` attached, so a scripted answer was swallowed and a destructive confirm defaulted to abort.
|
|
30
|
+
function storedProfileSync() {
|
|
25
31
|
const base = defaultProfile();
|
|
26
|
-
const stored = (
|
|
27
|
-
? parseStored(await readTextIfExists(tersioSettingsFile()))
|
|
28
|
-
: parseStored(await readTextIfExists(legacyOmpLockPath()));
|
|
32
|
+
const stored = parseStored(readTextIfExistsSync(tersioSettingsFile()) ?? readTextIfExistsSync(legacyOmpLockPath()));
|
|
29
33
|
if (!stored)
|
|
30
34
|
return base;
|
|
35
|
+
applyStored(base, stored);
|
|
36
|
+
return base;
|
|
37
|
+
}
|
|
38
|
+
function applyStored(base, stored) {
|
|
31
39
|
if (typeof stored.comboDefault === 'string' && stored.comboDefault in COMBO_PRESET_MODES)
|
|
32
40
|
base.comboDefault = stored.comboDefault;
|
|
33
41
|
if (typeof stored.cavemanDefault === 'string' && CAVEMAN_DEFAULTS.has(stored.cavemanDefault))
|
|
@@ -39,7 +47,16 @@ async function storedProfile() {
|
|
|
39
47
|
if (typeof stored.currency === 'string' && isCurrencyCode(stored.currency.trim().toUpperCase())) {
|
|
40
48
|
base.currency = stored.currency.trim().toUpperCase();
|
|
41
49
|
}
|
|
42
|
-
|
|
50
|
+
if (typeof stored.backupSchedule === 'string' && BACKUP_SCHEDULES.has(stored.backupSchedule)) {
|
|
51
|
+
base.backupSchedule = stored.backupSchedule;
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(stored.subagentMarkers)) {
|
|
54
|
+
// An empty marker would match every turn.
|
|
55
|
+
base.subagentMarkers = stored.subagentMarkers.filter((m) => typeof m === 'string' && m.trim() !== '');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function storedProfile() {
|
|
59
|
+
return storedProfileSync();
|
|
43
60
|
}
|
|
44
61
|
function legacyOmpLockPath() {
|
|
45
62
|
const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
@@ -63,8 +80,7 @@ function parseStored(raw) {
|
|
|
63
80
|
return null;
|
|
64
81
|
}
|
|
65
82
|
}
|
|
66
|
-
// Persist the profile where every host reads it. `omp plugin config get` no
|
|
67
|
-
// longer reports it; `tersio settings` does.
|
|
83
|
+
// Persist the profile where every host reads it. `omp plugin config get` no longer reports it; `tersio settings` does.
|
|
68
84
|
async function writePluginSettings(profile, options) {
|
|
69
85
|
const file = tersioSettingsFile();
|
|
70
86
|
const values = {
|
|
@@ -75,6 +91,8 @@ async function writePluginSettings(profile, options) {
|
|
|
75
91
|
rtkDefault: profile.rtkDefault,
|
|
76
92
|
ponytailDefault: profile.ponytailDefault,
|
|
77
93
|
currency: profile.currency,
|
|
94
|
+
backupSchedule: profile.backupSchedule,
|
|
95
|
+
...(profile.subagentMarkers.length > 0 ? { subagentMarkers: profile.subagentMarkers } : {}),
|
|
78
96
|
};
|
|
79
97
|
if (options.dryRun) {
|
|
80
98
|
if (verbose && !options.quiet)
|
|
@@ -88,4 +106,19 @@ async function writePluginSettings(profile, options) {
|
|
|
88
106
|
function formatProfile(profile) {
|
|
89
107
|
return `combo=${profile.comboDefault} (caveman=${profile.cavemanDefault} · rtk=${profile.rtkDefault ? 'on' : 'off'} · ponytail=${profile.ponytailDefault}) · currency=${profile.currency}`;
|
|
90
108
|
}
|
|
91
|
-
export { defaultProfile, formatProfile, storedProfile, writePluginSettings };
|
|
109
|
+
export { defaultProfile, formatProfile, storedProfile, storedProfileSync, writePluginSettings };
|
|
110
|
+
/** The status line the extensions print, read from the persisted profile. */
|
|
111
|
+
export function formatCliStatus(profile) {
|
|
112
|
+
const { comboDefault, cavemanDefault, rtkDefault, ponytailDefault } = profile;
|
|
113
|
+
if (comboDefault === 'off' && cavemanDefault === 'off' && !rtkDefault && ponytailDefault === 'off') {
|
|
114
|
+
return '🧩 combo OFF';
|
|
115
|
+
}
|
|
116
|
+
const preset = COMBO_PRESET_MODES[comboDefault];
|
|
117
|
+
const isPreset = preset !== undefined
|
|
118
|
+
&& preset.caveman === cavemanDefault
|
|
119
|
+
&& preset.rtk === rtkDefault
|
|
120
|
+
&& preset.ponytail === ponytailDefault;
|
|
121
|
+
const level = isPreset ? comboDefault.toUpperCase() : 'CUSTOM';
|
|
122
|
+
const rtk = rtkDefault ? 'ON' : 'OFF';
|
|
123
|
+
return `🧩 combo ${level}: 🪨caveman=${cavemanDefault.toUpperCase()} ⚡rtk=${rtk} 🦥ponytail=${ponytailDefault.toUpperCase()}`;
|
|
124
|
+
}
|
package/cli/reset.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
// watermark that filters session-derived and RTK-metered rows out of every
|
|
3
|
-
// derived view. Session transcripts (~/.omp/agent/sessions) and the RTK
|
|
4
|
-
// database are host/tool-owned and are never touched; the watermark only
|
|
5
|
-
// changes what tersio shows.
|
|
1
|
+
// Clears tersio-owned stats only; transcripts and RTK data stay.
|
|
6
2
|
import { dryRun, yes } from './common.js';
|
|
7
|
-
import { confirmDestructive } from './interactive.js';
|
|
3
|
+
import { confirmDestructive, sayTagged } from './interactive.js';
|
|
8
4
|
import { clearUsageLedger, importSessionTokens, ledgerPath, markReset, readUsage, sessionsDir } from '../extensions/shared/usage-ledger.js';
|
|
9
5
|
import { clearUsageDb, usageDbPath } from '../extensions/shared/usage-store.js';
|
|
10
6
|
import { readRtkGain, rtkDbPath } from '../extensions/shared/rtk-gain.js';
|
|
@@ -29,7 +25,7 @@ async function runReset() {
|
|
|
29
25
|
const cleared = clearUsageLedger();
|
|
30
26
|
const dbCleared = clearUsageDb();
|
|
31
27
|
const ts = markReset();
|
|
32
|
-
|
|
28
|
+
sayTagged(`[ok] reset — removed ${cleared} usage rows${dbCleared ? ' + usage.db' : ''}; statistics view starts at ${new Date(ts).toISOString()}`);
|
|
33
29
|
return true;
|
|
34
30
|
}
|
|
35
31
|
export { runReset };
|
package/cli/rtk-wiring.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// `rtk init -g --agent omp` writes rtk's tool_call extension to
|
|
3
|
-
// ~/.omp/agent/extensions/rtk.ts but does not register it, and OMP loads only
|
|
4
|
-
// listed extensions, so we add the path to config.yml. Every rewritten bash
|
|
5
|
-
// call then lands in rtk's history.db. Fail-open: a failed wire only downgrades
|
|
6
|
-
// to manual `rtk` prefixing. No cli/common.ts import (argv side effects).
|
|
1
|
+
// OMP loads only listed extensions, so register rtk's path; fail-open.
|
|
7
2
|
import { promises as fs } from 'node:fs';
|
|
8
3
|
import os from 'node:os';
|
|
9
4
|
import path from 'node:path';
|
|
@@ -31,25 +26,39 @@ function execFileP(cmd, args, timeout) {
|
|
|
31
26
|
});
|
|
32
27
|
});
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
export async function wireRtkOmp(rtkBin, options = {}) {
|
|
29
|
+
async function runWire(rtkBin, spec, options) {
|
|
36
30
|
if (!options.dryRun)
|
|
37
|
-
debugWire(options,
|
|
31
|
+
debugWire(options, `Wiring rtk → ${spec.host} (${spec.mechanism} rewrite)…`);
|
|
38
32
|
if (options.dryRun)
|
|
39
33
|
return true;
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
try {
|
|
35
|
+
await tryRtkInit(rtkBin, spec.initArgs);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
console.log(` [warn] could not wire rtk → ${spec.host}: ${shortWiringError(e)}`);
|
|
39
|
+
console.log(` [hint] Manual: ${spec.hint}`);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
export async function wireRtkOmp(rtkBin, options = {}) {
|
|
45
|
+
if (!(await runWire(rtkBin, { host: 'OMP', mechanism: 'bash tool_call', initArgs: ['init', '-g', '--agent', 'omp'], hint: 'rtk init -g --agent omp (needs rtk >= 0.49)' }, options)))
|
|
42
46
|
return false;
|
|
43
47
|
await ensureRtkInConfig(options);
|
|
44
48
|
return true;
|
|
45
49
|
}
|
|
50
|
+
export async function wireRtkOpencode(rtkBin, options = {}) {
|
|
51
|
+
return runWire(rtkBin, { host: 'OpenCode', mechanism: 'tool.execute.before', initArgs: ['init', '-g', '--opencode'], hint: 'rtk init -g --opencode' }, options);
|
|
52
|
+
}
|
|
53
|
+
export async function wireRtkPi(rtkBin, options = {}) {
|
|
54
|
+
return runWire(rtkBin, { host: 'pi', mechanism: 'bash tool_call', initArgs: ['init', '-g', '--agent', 'pi'], hint: 'rtk init -g --agent pi' }, options);
|
|
55
|
+
}
|
|
46
56
|
// The rtk.ts path rtk init writes (OMP_AGENT_DIR inlined; see header note).
|
|
47
57
|
function rtkExtensionPath() {
|
|
48
58
|
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
49
59
|
return path.join(home, '.omp', 'agent', 'extensions', 'rtk.ts');
|
|
50
60
|
}
|
|
51
|
-
// Append rtk.ts after the existing entries (or create the key) so the wire
|
|
52
|
-
// takes effect on the next OMP start.
|
|
61
|
+
// Append rtk.ts after the existing entries (or create the key) so the wire takes effect on the next OMP start.
|
|
53
62
|
export async function ensureRtkInConfig(options) {
|
|
54
63
|
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
55
64
|
const configPath = path.join(home, '.omp', 'agent', 'config.yml');
|
|
@@ -86,26 +95,14 @@ export async function ensureRtkInConfig(options) {
|
|
|
86
95
|
console.log(` [warn] could not register rtk in config.yml: ${e.message}`);
|
|
87
96
|
}
|
|
88
97
|
}
|
|
89
|
-
async function
|
|
98
|
+
async function tryRtkInit(rtkBin, initArgs) {
|
|
90
99
|
try {
|
|
91
|
-
await execFileP(rtkBin,
|
|
92
|
-
|
|
93
|
-
return true;
|
|
100
|
+
await execFileP(rtkBin, initArgs, 30000);
|
|
101
|
+
return;
|
|
94
102
|
}
|
|
95
|
-
catch
|
|
96
|
-
// A fresh binary can lose its first exec to macOS Gatekeeper,
|
|
97
|
-
// retry once before reporting failure.
|
|
98
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
99
|
-
try {
|
|
100
|
-
await execFileP(rtkBin, ['init', '-g', '--agent', 'omp'], 30000);
|
|
101
|
-
debugWire(options, 'rtk OMP extension wired');
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
catch (e) {
|
|
105
|
-
void first;
|
|
106
|
-
console.log(` [warn] could not wire rtk → OMP: ${shortWiringError(e)}`);
|
|
107
|
-
console.log(' [hint] Manual: rtk init -g --agent omp (needs rtk >= 0.49)');
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
103
|
+
catch {
|
|
104
|
+
// A fresh binary can lose its first exec to macOS Gatekeeper: settle, then retry once.
|
|
110
105
|
}
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
107
|
+
await execFileP(rtkBin, initArgs, 30000);
|
|
111
108
|
}
|
package/cli/settings.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
// cli/settings.ts — view/edit session-start defaults
|
|
2
|
-
// plus the display-currency default for usage/dashboard reports.
|
|
3
|
-
// Non-interactive: `tersio settings --combo-default balanced` (plus per-mode
|
|
4
|
-
// override flags and --currency). Interactive: clack selects seeded from the
|
|
5
|
-
// stored profile.
|
|
1
|
+
// cli/settings.ts — view/edit session-start defaults and the display currency.
|
|
6
2
|
import path from 'node:path';
|
|
7
|
-
import { CAVEMAN_DEFAULTS, COMBO_DEFAULTS, COMBO_PRESET_MODES, OMP_PLUGINS_DIR, PACKAGE_NAME, PONYTAIL_DEFAULTS, cavemanDefaultFlag, comboDefaultFlag, currency, currencyGiven, diagScheduleFlag, dryRun, ponytailDefaultFlag, profileFlagsGiven, rtkDefaultFlag, settingArg, } from './common.js';
|
|
3
|
+
import { CAVEMAN_DEFAULTS, COMBO_DEFAULTS, COMBO_PRESET_MODES, OMP_PLUGINS_DIR, PACKAGE_NAME, PONYTAIL_DEFAULTS, cavemanDefaultFlag, comboDefaultFlag, currency, currencyGiven, diagScheduleFlag, dryRun, ponytailDefaultFlag, profileFlagsGiven, rtkDefaultFlag, settingArg, subagentMarkerFlags, subagentMarkersGiven, } from './common.js';
|
|
8
4
|
import { CURRENCY_CODES } from './currency.js';
|
|
9
5
|
import { textTable } from './usage.js';
|
|
10
|
-
import { askInteractiveChoice, closeRL, tty } from './interactive.js';
|
|
11
|
-
import { formatProfile, storedProfile, writePluginSettings } from './profile.js';
|
|
6
|
+
import { ask, askInteractiveChoice, closeRL, tty } from './interactive.js';
|
|
7
|
+
import { formatCliStatus, formatProfile, storedProfile, writePluginSettings } from './profile.js';
|
|
12
8
|
import { readDiagSchedule, setDiagSchedule } from './dashboard.js';
|
|
13
9
|
function applyFlags(base) {
|
|
14
10
|
const next = { ...base };
|
|
@@ -24,6 +20,8 @@ function applyFlags(base) {
|
|
|
24
20
|
next.rtkDefault = rtkDefaultFlag === 'on';
|
|
25
21
|
if (ponytailDefaultFlag !== undefined)
|
|
26
22
|
next.ponytailDefault = ponytailDefaultFlag;
|
|
23
|
+
if (subagentMarkersGiven)
|
|
24
|
+
next.subagentMarkers = subagentMarkerFlags;
|
|
27
25
|
if (currencyGiven)
|
|
28
26
|
next.currency = currency;
|
|
29
27
|
return next;
|
|
@@ -44,6 +42,8 @@ async function askProfile(current) {
|
|
|
44
42
|
rtkDefault: preset.rtk,
|
|
45
43
|
ponytailDefault: preset.ponytail,
|
|
46
44
|
currency: current.currency,
|
|
45
|
+
backupSchedule: current.backupSchedule,
|
|
46
|
+
subagentMarkers: current.subagentMarkers,
|
|
47
47
|
};
|
|
48
48
|
const caveman = await askInteractiveChoice('Caveman default', [...CAVEMAN_DEFAULTS].map((v) => ({
|
|
49
49
|
value: v, label: v,
|
|
@@ -70,8 +70,26 @@ async function askProfile(current) {
|
|
|
70
70
|
if (cur.status !== 'selected')
|
|
71
71
|
return null;
|
|
72
72
|
next.currency = cur.value;
|
|
73
|
+
const markers = await askSubagentMarkers(next.subagentMarkers);
|
|
74
|
+
if (markers === null)
|
|
75
|
+
return null;
|
|
76
|
+
next.subagentMarkers = markers;
|
|
73
77
|
return next;
|
|
74
78
|
}
|
|
79
|
+
// One marker per line; an empty line ends the list. Null aborts.
|
|
80
|
+
async function askSubagentMarkers(current) {
|
|
81
|
+
console.log(' Subagent prompt markers (one per line, empty line to finish).');
|
|
82
|
+
if (current.length > 0)
|
|
83
|
+
console.log(` Current: ${current.join(' | ')}`);
|
|
84
|
+
const entered = [];
|
|
85
|
+
for (;;) {
|
|
86
|
+
const line = (await ask(` marker ${entered.length + 1}: `)).trim();
|
|
87
|
+
if (line === '')
|
|
88
|
+
break;
|
|
89
|
+
entered.push(line);
|
|
90
|
+
}
|
|
91
|
+
return entered;
|
|
92
|
+
}
|
|
75
93
|
async function askDiagSchedule(current) {
|
|
76
94
|
const picked = await askInteractiveChoice('Diagnosis auto-check', [
|
|
77
95
|
{ value: 'manual', label: 'manual', hint: 'check on open' },
|
|
@@ -83,12 +101,11 @@ async function askDiagSchedule(current) {
|
|
|
83
101
|
return null;
|
|
84
102
|
return picked.value;
|
|
85
103
|
}
|
|
86
|
-
const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'currency', 'diagnosis'];
|
|
104
|
+
const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'currency', 'diagnosis', 'markers'];
|
|
87
105
|
function settingsUsage() {
|
|
88
|
-
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--diag-schedule manual|daily|weekly|monthly] [--dry-run]');
|
|
106
|
+
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis|markers] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--diag-schedule manual|daily|weekly|monthly] [--subagent-marker <text>]... [--dry-run]');
|
|
89
107
|
}
|
|
90
|
-
// Single-setting jump: `tersio settings diagnosis` prompts only that value
|
|
91
|
-
// instead of walking the whole chain. Returns false when aborted.
|
|
108
|
+
// Single-setting jump: `tersio settings diagnosis` prompts only that value instead of walking the whole chain. Returns false when aborted.
|
|
92
109
|
async function runSingleSetting(name, current, nextDiag) {
|
|
93
110
|
const next = { ...current };
|
|
94
111
|
let diag = nextDiag;
|
|
@@ -153,6 +170,13 @@ async function runSingleSetting(name, current, nextDiag) {
|
|
|
153
170
|
diag = picked;
|
|
154
171
|
break;
|
|
155
172
|
}
|
|
173
|
+
case 'markers': {
|
|
174
|
+
const markers = await askSubagentMarkers(next.subagentMarkers);
|
|
175
|
+
if (markers === null)
|
|
176
|
+
return null;
|
|
177
|
+
next.subagentMarkers = markers;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
156
180
|
default: {
|
|
157
181
|
console.error(`[fail] Invalid setting: ${name}. Valid: ${SETTING_NAMES.join(', ')}`);
|
|
158
182
|
process.exit(1);
|
|
@@ -162,15 +186,14 @@ async function runSingleSetting(name, current, nextDiag) {
|
|
|
162
186
|
}
|
|
163
187
|
function printSettingsTable(current) {
|
|
164
188
|
console.log('\n=== Tersio Settings ===');
|
|
165
|
-
//
|
|
166
|
-
// persisting it (its picker POSTs to /currency); settings can still set
|
|
167
|
-
// the stored default via --currency or the prompt below.
|
|
189
|
+
// The dashboard owns displaying currency; settings only sets the default.
|
|
168
190
|
const rows = [
|
|
169
191
|
['combo', current.comboDefault, [...COMBO_DEFAULTS].join(' | ')],
|
|
170
192
|
['caveman', current.cavemanDefault, [...CAVEMAN_DEFAULTS].join(' | ')],
|
|
171
193
|
['rtk', current.rtkDefault ? 'on' : 'off', 'on | off'],
|
|
172
194
|
['ponytail', current.ponytailDefault, [...PONYTAIL_DEFAULTS].join(' | ')],
|
|
173
195
|
['diagnosis', readDiagSchedule(), 'manual | daily | weekly | monthly'],
|
|
196
|
+
['markers', current.subagentMarkers.length === 0 ? 'default' : current.subagentMarkers.join(' | '), 'host subagent prompt text'],
|
|
174
197
|
];
|
|
175
198
|
for (const l of textTable(['Setting', 'Current', 'Valid values'], rows, [false, false, false], 64)) {
|
|
176
199
|
console.log(l);
|
|
@@ -206,7 +229,7 @@ async function runSettings() {
|
|
|
206
229
|
next = single.profile;
|
|
207
230
|
nextDiag = single.diag;
|
|
208
231
|
}
|
|
209
|
-
else if (profileFlagsGiven || currencyGiven || diagScheduleFlag !== undefined) {
|
|
232
|
+
else if (profileFlagsGiven || currencyGiven || diagScheduleFlag !== undefined || subagentMarkersGiven) {
|
|
210
233
|
next = applyFlags(current);
|
|
211
234
|
}
|
|
212
235
|
else if (tty()) {
|
|
@@ -237,6 +260,7 @@ async function runSettings() {
|
|
|
237
260
|
}
|
|
238
261
|
await writePluginSettings(next, {});
|
|
239
262
|
setDiagSchedule(nextDiag);
|
|
263
|
+
console.log(` ${formatCliStatus(next)}`);
|
|
240
264
|
console.log(` Defaults: ${formatProfile(next)} · diagnosis=${nextDiag}`);
|
|
241
265
|
console.log(' Applies to fresh sessions only — anything persisted with /combo, /caveman, or /rtk wins.');
|
|
242
266
|
console.log(' Currency applies to usage/dashboard reports; --currency overrides it per run.');
|