@kontextmind/kxm 0.7.178 → 0.7.179
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +11 -0
- package/docs/contributing/decisions.md +27 -1
- package/docs/reference/cli-reference.md +34 -5
- package/docs/reference/tools.md +6 -4
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/claude-hook.js +36 -45
- package/plugins/kxm/dist/cli.js +376 -259
- package/plugins/kxm/dist/extension.js +29 -37
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/plugins.ts +193 -25
- package/plugins/kxm/src/cli.ts +2 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/session-work.ts +34 -50
|
@@ -37827,12 +37827,12 @@ var MAX_SESSION_BRIEF_TASKS = 5;
|
|
|
37827
37827
|
var MAX_SESSION_BRIEF_PLANS = 5;
|
|
37828
37828
|
var SESSION_BRIEF_SCHEMA = "kxm.session-brief.v1";
|
|
37829
37829
|
var DEFAULT_SESSION_BRIEF_STALE_SECONDS = 5;
|
|
37830
|
-
function
|
|
37830
|
+
function hubStatusPart(hub) {
|
|
37831
37831
|
const suffix = hub?.scope === "remote" ? "/remote" : "";
|
|
37832
|
-
if (hub?.state === "
|
|
37833
|
-
if (hub?.state === "
|
|
37834
|
-
if (hub?.
|
|
37835
|
-
return
|
|
37832
|
+
if (hub?.state === "off" || hub?.state === void 0 && hub?.online === false) return `hub:off${suffix}`;
|
|
37833
|
+
if (hub?.state === "unknown") return `hub:unknown${suffix}`;
|
|
37834
|
+
if (hub?.scope === "remote") return "hub:remote";
|
|
37835
|
+
return void 0;
|
|
37836
37836
|
}
|
|
37837
37837
|
function truncate(value, width) {
|
|
37838
37838
|
const text = value.replace(/\s+/g, " ").trim();
|
|
@@ -37908,40 +37908,34 @@ function readGitShip(cwd) {
|
|
|
37908
37908
|
return void 0;
|
|
37909
37909
|
}
|
|
37910
37910
|
}
|
|
37911
|
-
function formatSessionStatusLine(stats, current, hub, ship, updateLatest
|
|
37912
|
-
const
|
|
37913
|
-
|
|
37914
|
-
|
|
37915
|
-
|
|
37916
|
-
|
|
37917
|
-
|
|
37918
|
-
return finalLine.length <= 80 ? finalLine : `${finalLine.slice(0, 79)}\u2026`;
|
|
37919
|
-
}
|
|
37920
|
-
const parts = [];
|
|
37921
|
-
if (current?.kind === "task") parts.push(`${head} ${current.detail}`);
|
|
37922
|
-
else if (current?.kind === "plan") parts.push(`${head} plan ${truncate(current.detail, 36)}`);
|
|
37923
|
-
else parts.push(head);
|
|
37924
|
-
parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
37911
|
+
function formatSessionStatusLine(stats, current, hub, ship, updateLatest) {
|
|
37912
|
+
const parts = ["kxm"];
|
|
37913
|
+
const hubPart = hubStatusPart(hub);
|
|
37914
|
+
if (hubPart) parts.push(hubPart);
|
|
37915
|
+
if (current?.kind === "task") parts.push(current.detail);
|
|
37916
|
+
else if (current?.kind === "plan") parts.push(`plan ${truncate(current.detail, 36)}`);
|
|
37917
|
+
if (stats.activeTasks > 0) parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
37925
37918
|
if (stats.waitingTasks > 0) parts.push(`${stats.waitingTasks} waiting`);
|
|
37926
|
-
parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
37919
|
+
if (stats.planCount > 0) parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
37927
37920
|
if (stats.inbox > 0) parts.push(`inbox ${stats.inbox}`);
|
|
37928
37921
|
if (ship?.dirty) parts.push("dirty");
|
|
37929
37922
|
else if (ship && ship.ahead > 0) parts.push(`${ship.ahead} local`);
|
|
37930
|
-
if (cost) parts.push(cost);
|
|
37931
37923
|
if (updateLatest) parts.push(`upd ${updateLatest}`);
|
|
37932
|
-
|
|
37933
|
-
return line.length <= 80 ? line : `${line.slice(0, 79)}\u2026`;
|
|
37924
|
+
return truncate(parts.join(" \xB7 "), 80);
|
|
37934
37925
|
}
|
|
37935
37926
|
function formatSessionWidget(stats, current, hub, ship, updateLatest, cost) {
|
|
37936
|
-
const
|
|
37937
|
-
const
|
|
37938
|
-
|
|
37939
|
-
|
|
37927
|
+
const suffix = hub?.scope === "remote" ? "/remote" : "";
|
|
37928
|
+
const hubMark = hub?.state === "on" || hub?.state === void 0 && hub?.online === true ? `hub:on${suffix}` : hub?.state === "off" || hub?.state === void 0 && hub?.online === false ? `hub:off${suffix}` : hub?.state === "unknown" ? `hub:unknown${suffix}` : void 0;
|
|
37929
|
+
const summary = ["KXM", hubMark];
|
|
37930
|
+
if (stats.activeTasks > 0) summary.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
37931
|
+
if (stats.waitingTasks > 0) summary.push(`${stats.waitingTasks} waiting`);
|
|
37932
|
+
if (stats.planCount > 0) summary.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
37933
|
+
if (stats.inbox > 0) summary.push(`inbox ${stats.inbox}`);
|
|
37934
|
+
const lines = [summary.filter(Boolean).join(" \xB7 ")];
|
|
37940
37935
|
if (current) lines.push(`now ${current.kind} ${truncate(current.detail, 56)}`);
|
|
37941
|
-
else if (stats.latestPlan) lines.push(`plan
|
|
37942
|
-
|
|
37943
|
-
lines.push(
|
|
37944
|
-
if (cost) lines.push(`cost ${cost}`);
|
|
37936
|
+
else if (stats.latestPlan) lines.push(`plan ${truncate(stats.latestPlan, 60)}`);
|
|
37937
|
+
if (ship?.dirty || ship && ship.ahead > 0) lines.push(formatShipLine(ship));
|
|
37938
|
+
if (cost && cost !== "unknown") lines.push(`cost ${cost}`);
|
|
37945
37939
|
if (updateLatest) lines.push(`update ${updateLatest} available \xB7 kxm update --kxm`);
|
|
37946
37940
|
return lines;
|
|
37947
37941
|
}
|
|
@@ -37961,7 +37955,7 @@ function buildSessionBrief(snapshot, current, hub, ship, updateLatest, cost, ses
|
|
|
37961
37955
|
const tasks = uniqueLabels(recentTasks(snapshot.runs).map(taskItem));
|
|
37962
37956
|
const plans = uniqueLabels(snapshot.plans.slice(0, MAX_SESSION_BRIEF_PLANS).map(planItem));
|
|
37963
37957
|
const resolvedHub = hub ?? { state: "off", evidence: "unconfigured", online: false };
|
|
37964
|
-
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest
|
|
37958
|
+
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest);
|
|
37965
37959
|
const widgetLines = formatSessionWidget(stats, current, resolvedHub, ship, updateLatest, cost);
|
|
37966
37960
|
return {
|
|
37967
37961
|
schema: SESSION_BRIEF_SCHEMA,
|
|
@@ -38025,7 +38019,7 @@ function estimateSessionCost(stateDir) {
|
|
|
38025
38019
|
latestModel = r.effectiveModel ?? r.requestedModel ?? latestModel;
|
|
38026
38020
|
latestHarness = r.harness ?? latestHarness;
|
|
38027
38021
|
}
|
|
38028
|
-
if (!hasMetered) return
|
|
38022
|
+
if (!hasMetered) return void 0;
|
|
38029
38023
|
const route = latestHarness && latestModel ? `${latestHarness}/${latestModel}` : latestModel;
|
|
38030
38024
|
return `$${totalCost.toFixed(2)} sess${route ? ` \xB7 ${route}` : ""}`;
|
|
38031
38025
|
} catch {
|
|
@@ -38062,8 +38056,7 @@ function loadSessionBrief(cwd, env = process.env, current, hub, options = {}) {
|
|
|
38062
38056
|
current,
|
|
38063
38057
|
effectiveHub,
|
|
38064
38058
|
options.ship,
|
|
38065
|
-
options.updateLatest
|
|
38066
|
-
effectiveCost
|
|
38059
|
+
options.updateLatest
|
|
38067
38060
|
);
|
|
38068
38061
|
const widgetLines = formatSessionWidget(
|
|
38069
38062
|
cached2.stats,
|
|
@@ -38128,8 +38121,7 @@ async function loadSessionBriefAsync(cwd, env = process.env, current, hub, optio
|
|
|
38128
38121
|
current,
|
|
38129
38122
|
effectiveHub,
|
|
38130
38123
|
options.ship,
|
|
38131
|
-
void 0
|
|
38132
|
-
effectiveCost
|
|
38124
|
+
void 0
|
|
38133
38125
|
);
|
|
38134
38126
|
const widgetLines = formatSessionWidget(
|
|
38135
38127
|
cached2.stats,
|
|
@@ -25462,7 +25462,7 @@ function sessionTokenFixHint(policy) {
|
|
|
25462
25462
|
}
|
|
25463
25463
|
|
|
25464
25464
|
// plugins/kxm/src/mcp-server.ts
|
|
25465
|
-
var VERSION = "0.7.
|
|
25465
|
+
var VERSION = "0.7.179";
|
|
25466
25466
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
25467
25467
|
var inbox = /* @__PURE__ */ new Map();
|
|
25468
25468
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
package/plugins/kxm/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
2
5
|
import { findKxmRepoRoot } from "../repo-root.ts";
|
|
3
6
|
import { probeHarnesses } from "../harness.ts";
|
|
4
|
-
import { print, type Runtime } from "./types.ts";
|
|
7
|
+
import { print, type CliSpawnResult, type Runtime } from "./types.ts";
|
|
5
8
|
|
|
6
9
|
export type PluginHarnessId = "pi" | "omp" | "claude";
|
|
7
10
|
|
|
@@ -12,6 +15,7 @@ export interface PluginInstallOptions {
|
|
|
12
15
|
claude?: boolean | undefined;
|
|
13
16
|
omp?: boolean | undefined;
|
|
14
17
|
pi?: boolean | undefined;
|
|
18
|
+
statusBand?: boolean | undefined;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
export interface PluginInstallHarnessResult {
|
|
@@ -23,6 +27,149 @@ export interface PluginInstallHarnessResult {
|
|
|
23
27
|
error?: string | undefined;
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
const OMP_STATUS_SETTINGS = [
|
|
31
|
+
{ key: "statusLine.leftSegments", configKey: "leftSegments", value: ["model", "mode", "git", "pr", "subagents"] },
|
|
32
|
+
{ key: "statusLine.rightSegments", configKey: "rightSegments", value: ["usage", "cost", "context_pct"] },
|
|
33
|
+
{ key: "statusLine.preset", configKey: "preset", value: "custom" },
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
function spawnHarness(runtime: Runtime, command: string, args: readonly string[]): CliSpawnResult {
|
|
37
|
+
return runtime.io.spawnSync
|
|
38
|
+
? runtime.io.spawnSync(command, args)
|
|
39
|
+
: spawnSync(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function spawnFailed(run: CliSpawnResult): boolean {
|
|
43
|
+
return Boolean(run.error) || run.status !== 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function spawnFailureDetail(run: CliSpawnResult, fallback: string): string {
|
|
47
|
+
return (run.stderr || run.stdout || run.error?.message || fallback).trim();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface OmpStatusBandFailure {
|
|
51
|
+
args: readonly string[];
|
|
52
|
+
run: CliSpawnResult;
|
|
53
|
+
rollbackAttempted: boolean;
|
|
54
|
+
rollbackFailed: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface OmpGlobalSettingSnapshot {
|
|
58
|
+
present: boolean;
|
|
59
|
+
value?: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ompConfigValueArg(value: unknown): string {
|
|
63
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readOmpGlobalStatusSettings(
|
|
67
|
+
runtime: Runtime,
|
|
68
|
+
command: string,
|
|
69
|
+
): { previous: Record<string, OmpGlobalSettingSnapshot> } | OmpStatusBandFailure {
|
|
70
|
+
const args = ["config", "path"];
|
|
71
|
+
const run = spawnHarness(runtime, command, args);
|
|
72
|
+
if (spawnFailed(run)) return { args, run, rollbackAttempted: false, rollbackFailed: false };
|
|
73
|
+
|
|
74
|
+
const agentDir = run.stdout.trim();
|
|
75
|
+
if (!agentDir || !isAbsolute(agentDir) || !existsSync(agentDir)) {
|
|
76
|
+
return {
|
|
77
|
+
args,
|
|
78
|
+
run: { status: 1, stdout: run.stdout, stderr: "omp config path did not return an existing absolute directory" },
|
|
79
|
+
rollbackAttempted: false,
|
|
80
|
+
rollbackFailed: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const configPath = ["config.yml", "config.yaml"].map((name) => join(agentDir, name)).find(existsSync);
|
|
85
|
+
let document: unknown = {};
|
|
86
|
+
try {
|
|
87
|
+
if (configPath) document = parseYaml(readFileSync(configPath, "utf8"));
|
|
88
|
+
} catch {
|
|
89
|
+
return {
|
|
90
|
+
args,
|
|
91
|
+
run: { status: 1, stdout: run.stdout, stderr: `could not read OMP global config at ${configPath}` },
|
|
92
|
+
rollbackAttempted: false,
|
|
93
|
+
rollbackFailed: false,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const root = document && typeof document === "object" && !Array.isArray(document) ? document : {};
|
|
98
|
+
const rawStatusLine = "statusLine" in root ? root.statusLine : undefined;
|
|
99
|
+
const statusLine = rawStatusLine && typeof rawStatusLine === "object" && !Array.isArray(rawStatusLine)
|
|
100
|
+
? rawStatusLine
|
|
101
|
+
: {};
|
|
102
|
+
const previous: Record<string, OmpGlobalSettingSnapshot> = {};
|
|
103
|
+
for (const setting of OMP_STATUS_SETTINGS) {
|
|
104
|
+
const value = statusLine[setting.configKey as keyof typeof statusLine];
|
|
105
|
+
previous[setting.key] = Object.hasOwn(statusLine, setting.configKey) && value !== null && value !== undefined
|
|
106
|
+
? { present: true, value }
|
|
107
|
+
: { present: false };
|
|
108
|
+
}
|
|
109
|
+
return { previous };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function rollbackOmpStatusBand(
|
|
113
|
+
runtime: Runtime,
|
|
114
|
+
command: string,
|
|
115
|
+
settings: readonly (typeof OMP_STATUS_SETTINGS)[number][],
|
|
116
|
+
previous: Readonly<Record<string, OmpGlobalSettingSnapshot>>,
|
|
117
|
+
): boolean {
|
|
118
|
+
let failed = false;
|
|
119
|
+
for (const setting of [...settings].reverse()) {
|
|
120
|
+
const snapshot = previous[setting.key];
|
|
121
|
+
const args = snapshot?.present
|
|
122
|
+
? ["config", "set", setting.key, ompConfigValueArg(snapshot.value), "--json"]
|
|
123
|
+
: ["config", "reset", setting.key, "--json"];
|
|
124
|
+
if (spawnFailed(spawnHarness(runtime, command, args))) failed = true;
|
|
125
|
+
}
|
|
126
|
+
return failed;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function configureOmpStatusBand(runtime: Runtime, command: string): OmpStatusBandFailure | undefined {
|
|
130
|
+
const snapshot = readOmpGlobalStatusSettings(runtime, command);
|
|
131
|
+
if ("run" in snapshot) return snapshot;
|
|
132
|
+
|
|
133
|
+
const written: (typeof OMP_STATUS_SETTINGS)[number][] = [];
|
|
134
|
+
for (const setting of OMP_STATUS_SETTINGS) {
|
|
135
|
+
const args = ["config", "set", setting.key, ompConfigValueArg(setting.value), "--json"];
|
|
136
|
+
written.push(setting);
|
|
137
|
+
const run = spawnHarness(runtime, command, args);
|
|
138
|
+
if (spawnFailed(run)) {
|
|
139
|
+
return {
|
|
140
|
+
args,
|
|
141
|
+
run,
|
|
142
|
+
rollbackAttempted: true,
|
|
143
|
+
rollbackFailed: rollbackOmpStatusBand(runtime, command, written, snapshot.previous),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const setting of OMP_STATUS_SETTINGS) {
|
|
149
|
+
const args = ["config", "get", setting.key, "--json"];
|
|
150
|
+
const run = spawnHarness(runtime, command, args);
|
|
151
|
+
let actual: unknown;
|
|
152
|
+
try {
|
|
153
|
+
const parsed: unknown = JSON.parse(run.stdout);
|
|
154
|
+
actual = parsed && typeof parsed === "object" && "value" in parsed ? parsed.value : undefined;
|
|
155
|
+
} catch {
|
|
156
|
+
actual = undefined;
|
|
157
|
+
}
|
|
158
|
+
if (spawnFailed(run) || JSON.stringify(actual) !== JSON.stringify(setting.value)) {
|
|
159
|
+
const mismatch = spawnFailed(run)
|
|
160
|
+
? run
|
|
161
|
+
: { status: 1, stdout: run.stdout, stderr: `effective ${setting.key} is shadowed or did not persist` };
|
|
162
|
+
return {
|
|
163
|
+
args,
|
|
164
|
+
run: mismatch,
|
|
165
|
+
rollbackAttempted: true,
|
|
166
|
+
rollbackFailed: rollbackOmpStatusBand(runtime, command, written, snapshot.previous),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
26
173
|
export async function cmdPluginInstall(
|
|
27
174
|
runtime: Runtime,
|
|
28
175
|
options: PluginInstallOptions,
|
|
@@ -41,6 +188,17 @@ export async function cmdPluginInstall(
|
|
|
41
188
|
? requestedHarnesses
|
|
42
189
|
: PLUGIN_SUPPORTED_HARNESSES;
|
|
43
190
|
|
|
191
|
+
if (options.statusBand && isExplicit && !options.omp) {
|
|
192
|
+
const detail = "--status-band requires OMP to be among the selected harnesses";
|
|
193
|
+
print(
|
|
194
|
+
runtime.io,
|
|
195
|
+
runtime.json,
|
|
196
|
+
{ ok: false, command: "plugin install", error: "omp_status_band_requires_omp", results: [] },
|
|
197
|
+
detail,
|
|
198
|
+
);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
44
202
|
const results: PluginInstallHarnessResult[] = [];
|
|
45
203
|
|
|
46
204
|
for (const harnessId of targetHarnesses) {
|
|
@@ -48,7 +206,7 @@ export async function cmdPluginInstall(
|
|
|
48
206
|
const detected = Boolean(status?.detected && status?.command);
|
|
49
207
|
|
|
50
208
|
if (!detected) {
|
|
51
|
-
if (isExplicit) {
|
|
209
|
+
if (isExplicit || (options.statusBand && harnessId === "omp")) {
|
|
52
210
|
results.push({
|
|
53
211
|
harness: harnessId,
|
|
54
212
|
status: "failed",
|
|
@@ -72,17 +230,15 @@ export async function cmdPluginInstall(
|
|
|
72
230
|
detail: `would install kxm plugin into pi: ${command} ${args.join(" ")}`,
|
|
73
231
|
});
|
|
74
232
|
} else {
|
|
75
|
-
const run = runtime
|
|
76
|
-
|
|
77
|
-
: spawnSync(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
78
|
-
if (run.error || (run.status !== 0 && run.status !== null)) {
|
|
233
|
+
const run = spawnHarness(runtime, command, args);
|
|
234
|
+
if (spawnFailed(run)) {
|
|
79
235
|
results.push({
|
|
80
236
|
harness: "pi",
|
|
81
237
|
status: "failed",
|
|
82
238
|
command,
|
|
83
239
|
args,
|
|
84
240
|
error: run.error?.message || "install_failed",
|
|
85
|
-
detail: (run
|
|
241
|
+
detail: spawnFailureDetail(run, "pi install exited with error"),
|
|
86
242
|
});
|
|
87
243
|
} else {
|
|
88
244
|
results.push({
|
|
@@ -102,29 +258,43 @@ export async function cmdPluginInstall(
|
|
|
102
258
|
status: "would",
|
|
103
259
|
command,
|
|
104
260
|
args,
|
|
105
|
-
detail:
|
|
261
|
+
detail: options.statusBand
|
|
262
|
+
? `would install kxm plugin into omp and configure the focused subscription status band: ${command} ${args.join(" ")}`
|
|
263
|
+
: `would install kxm plugin into omp: ${command} ${args.join(" ")}`,
|
|
106
264
|
});
|
|
107
265
|
} else {
|
|
108
|
-
const run = runtime
|
|
109
|
-
|
|
110
|
-
: spawnSync(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
111
|
-
if (run.error || (run.status !== 0 && run.status !== null)) {
|
|
266
|
+
const run = spawnHarness(runtime, command, args);
|
|
267
|
+
if (spawnFailed(run)) {
|
|
112
268
|
results.push({
|
|
113
269
|
harness: "omp",
|
|
114
270
|
status: "failed",
|
|
115
271
|
command,
|
|
116
272
|
args,
|
|
117
273
|
error: run.error?.message || "install_failed",
|
|
118
|
-
detail: (run
|
|
274
|
+
detail: spawnFailureDetail(run, "omp plugin install exited with error"),
|
|
119
275
|
});
|
|
120
276
|
} else {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
277
|
+
const configFailure = options.statusBand ? configureOmpStatusBand(runtime, command) : undefined;
|
|
278
|
+
if (configFailure) {
|
|
279
|
+
results.push({
|
|
280
|
+
harness: "omp",
|
|
281
|
+
status: "failed",
|
|
282
|
+
command,
|
|
283
|
+
args: configFailure.args,
|
|
284
|
+
error: "status_band_config_failed",
|
|
285
|
+
detail: `installed kxm plugin into omp, but ${configFailure.args.slice(0, 3).join(" ")} failed: ${spawnFailureDetail(configFailure.run, "omp status band configuration exited with error")}${configFailure.rollbackAttempted ? (configFailure.rollbackFailed ? "; previous settings could not be fully restored" : "; previous settings restored") : ""}`,
|
|
286
|
+
});
|
|
287
|
+
} else {
|
|
288
|
+
results.push({
|
|
289
|
+
harness: "omp",
|
|
290
|
+
status: "passed",
|
|
291
|
+
command,
|
|
292
|
+
args,
|
|
293
|
+
detail: options.statusBand
|
|
294
|
+
? "installed kxm plugin into omp and configured the focused subscription status band; restart omp"
|
|
295
|
+
: "installed kxm plugin into omp",
|
|
296
|
+
});
|
|
297
|
+
}
|
|
128
298
|
}
|
|
129
299
|
}
|
|
130
300
|
} else if (harnessId === "claude") {
|
|
@@ -144,17 +314,15 @@ export async function cmdPluginInstall(
|
|
|
144
314
|
} else {
|
|
145
315
|
spawnSync(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
146
316
|
}
|
|
147
|
-
const run = runtime
|
|
148
|
-
|
|
149
|
-
: spawnSync(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
150
|
-
if (run.error || (run.status !== 0 && run.status !== null)) {
|
|
317
|
+
const run = spawnHarness(runtime, command, installArgs);
|
|
318
|
+
if (spawnFailed(run)) {
|
|
151
319
|
results.push({
|
|
152
320
|
harness: "claude",
|
|
153
321
|
status: "failed",
|
|
154
322
|
command,
|
|
155
323
|
args: installArgs,
|
|
156
324
|
error: run.error?.message || "install_failed",
|
|
157
|
-
detail: (run
|
|
325
|
+
detail: spawnFailureDetail(run, "claude install exited with error"),
|
|
158
326
|
});
|
|
159
327
|
} else {
|
|
160
328
|
results.push({
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -773,7 +773,8 @@ function createProgram(ctx: CliContext, result: { code: number }, argv: readonly
|
|
|
773
773
|
.option("--claude", "Install plugin for Claude Code")
|
|
774
774
|
.option("--omp", "Install plugin for Oh My Pi (OMP)")
|
|
775
775
|
.option("--pi", "Install plugin for Pi")
|
|
776
|
-
.
|
|
776
|
+
.option("--status-band", "Configure OMP's focused status band with live subscription usage")
|
|
777
|
+
.action(async function pluginInstallAction(this: Command, options: { all?: boolean; claude?: boolean; omp?: boolean; pi?: boolean; statusBand?: boolean }) {
|
|
777
778
|
result.code = await cmdPluginInstall(runtimeFrom(ctx, this), options);
|
|
778
779
|
});
|
|
779
780
|
|
|
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
|
|
|
11
11
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
12
12
|
import { sessionTokenFixHint } from "./session-token-hint.ts";
|
|
13
13
|
|
|
14
|
-
const VERSION = "0.7.
|
|
14
|
+
const VERSION = "0.7.179";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|
|
@@ -66,15 +66,12 @@ export interface SessionBrief {
|
|
|
66
66
|
sessionToken?: string;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function
|
|
70
|
-
// A remote hub carries a bearer over a network; a loopback one does not. The JSON
|
|
71
|
-
// said so while every text surface printed the same bytes for both, which is the
|
|
72
|
-
// distinction an operator actually reads.
|
|
69
|
+
function hubStatusPart(hub?: Partial<SessionHubStatus>): string | undefined {
|
|
73
70
|
const suffix = hub?.scope === "remote" ? "/remote" : "";
|
|
74
|
-
if (hub?.state === "
|
|
75
|
-
if (hub?.state === "
|
|
76
|
-
if (hub?.
|
|
77
|
-
return
|
|
71
|
+
if (hub?.state === "off" || (hub?.state === undefined && hub?.online === false)) return `hub:off${suffix}`;
|
|
72
|
+
if (hub?.state === "unknown") return `hub:unknown${suffix}`;
|
|
73
|
+
if (hub?.scope === "remote") return "hub:remote";
|
|
74
|
+
return undefined;
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
function truncate(value: string, width: number): string {
|
|
@@ -172,12 +169,12 @@ export function formatCostLine(input?: {
|
|
|
172
169
|
model?: string;
|
|
173
170
|
}): string | undefined {
|
|
174
171
|
if (!input) return undefined;
|
|
175
|
-
if (input.sessionCostUsd === undefined && input.runCostUsd === undefined) return
|
|
172
|
+
if (input.sessionCostUsd === undefined && input.runCostUsd === undefined) return undefined;
|
|
176
173
|
const sess = input.sessionCostUsd !== undefined ? `$${input.sessionCostUsd.toFixed(2)} sess` : undefined;
|
|
177
174
|
const run = input.runCostUsd !== undefined ? `$${input.runCostUsd.toFixed(2)} run` : undefined;
|
|
178
175
|
const route = input.harness && input.model ? `${input.harness}/${input.model}` : (input.model ?? undefined);
|
|
179
176
|
const parts = [sess, run, route].filter(Boolean);
|
|
180
|
-
return parts.length > 0 ? parts.join(" · ") :
|
|
177
|
+
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
181
178
|
}
|
|
182
179
|
|
|
183
180
|
export function renderStatusLine(
|
|
@@ -186,11 +183,10 @@ export function renderStatusLine(
|
|
|
186
183
|
hub?: Partial<SessionHubStatus>,
|
|
187
184
|
ship?: SessionShipStatus,
|
|
188
185
|
updateLatest?: string,
|
|
189
|
-
cost?: string,
|
|
190
186
|
): string {
|
|
191
187
|
if ("schema" in statsOrBrief && statsOrBrief.schema === "kxm.session-brief.v1") {
|
|
192
188
|
const brief = statsOrBrief;
|
|
193
|
-
if (!current && !hub && !ship && !updateLatest
|
|
189
|
+
if (!current && !hub && !ship && !updateLatest) {
|
|
194
190
|
return brief.statusLine;
|
|
195
191
|
}
|
|
196
192
|
return formatSessionStatusLine(
|
|
@@ -199,10 +195,9 @@ export function renderStatusLine(
|
|
|
199
195
|
hub ?? brief.hub,
|
|
200
196
|
ship,
|
|
201
197
|
updateLatest,
|
|
202
|
-
cost ?? brief.cost,
|
|
203
198
|
);
|
|
204
199
|
}
|
|
205
|
-
return formatSessionStatusLine(statsOrBrief as SessionWorkStats, current, hub, ship, updateLatest
|
|
200
|
+
return formatSessionStatusLine(statsOrBrief as SessionWorkStats, current, hub, ship, updateLatest);
|
|
206
201
|
}
|
|
207
202
|
|
|
208
203
|
export function formatSessionStatusLine(
|
|
@@ -211,32 +206,20 @@ export function formatSessionStatusLine(
|
|
|
211
206
|
hub?: Partial<SessionHubStatus>,
|
|
212
207
|
ship?: SessionShipStatus,
|
|
213
208
|
updateLatest?: string,
|
|
214
|
-
cost?: string,
|
|
215
209
|
): string {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const withCost = cost ? `${withShip} · ${cost}` : withShip;
|
|
223
|
-
const finalLine = updateLatest ? `${withCost} · upd ${updateLatest}` : withCost;
|
|
224
|
-
return finalLine.length <= 80 ? finalLine : `${finalLine.slice(0, 79)}…`;
|
|
225
|
-
}
|
|
226
|
-
const parts: string[] = [];
|
|
227
|
-
if (current?.kind === "task") parts.push(`${head} ${current.detail}`);
|
|
228
|
-
else if (current?.kind === "plan") parts.push(`${head} plan ${truncate(current.detail, 36)}`);
|
|
229
|
-
else parts.push(head);
|
|
230
|
-
parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
210
|
+
const parts = ["kxm"];
|
|
211
|
+
const hubPart = hubStatusPart(hub);
|
|
212
|
+
if (hubPart) parts.push(hubPart);
|
|
213
|
+
if (current?.kind === "task") parts.push(current.detail);
|
|
214
|
+
else if (current?.kind === "plan") parts.push(`plan ${truncate(current.detail, 36)}`);
|
|
215
|
+
if (stats.activeTasks > 0) parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
231
216
|
if (stats.waitingTasks > 0) parts.push(`${stats.waitingTasks} waiting`);
|
|
232
|
-
parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
217
|
+
if (stats.planCount > 0) parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
233
218
|
if (stats.inbox > 0) parts.push(`inbox ${stats.inbox}`);
|
|
234
219
|
if (ship?.dirty) parts.push("dirty");
|
|
235
220
|
else if (ship && ship.ahead > 0) parts.push(`${ship.ahead} local`);
|
|
236
|
-
if (cost) parts.push(cost);
|
|
237
221
|
if (updateLatest) parts.push(`upd ${updateLatest}`);
|
|
238
|
-
|
|
239
|
-
return line.length <= 80 ? line : `${line.slice(0, 79)}…`;
|
|
222
|
+
return truncate(parts.join(" · "), 80);
|
|
240
223
|
}
|
|
241
224
|
|
|
242
225
|
export function formatSessionWidget(
|
|
@@ -247,19 +230,22 @@ export function formatSessionWidget(
|
|
|
247
230
|
updateLatest?: string,
|
|
248
231
|
cost?: string,
|
|
249
232
|
): string[] {
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
233
|
+
const suffix = hub?.scope === "remote" ? "/remote" : "";
|
|
234
|
+
const hubMark = hub?.state === "on" || (hub?.state === undefined && hub?.online === true)
|
|
235
|
+
? `hub:on${suffix}`
|
|
236
|
+
: (hub?.state === "off" || (hub?.state === undefined && hub?.online === false)
|
|
237
|
+
? `hub:off${suffix}`
|
|
238
|
+
: (hub?.state === "unknown" ? `hub:unknown${suffix}` : undefined));
|
|
239
|
+
const summary = ["KXM", hubMark];
|
|
240
|
+
if (stats.activeTasks > 0) summary.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
241
|
+
if (stats.waitingTasks > 0) summary.push(`${stats.waitingTasks} waiting`);
|
|
242
|
+
if (stats.planCount > 0) summary.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
243
|
+
if (stats.inbox > 0) summary.push(`inbox ${stats.inbox}`);
|
|
244
|
+
const lines = [summary.filter(Boolean).join(" · ")];
|
|
258
245
|
if (current) lines.push(`now ${current.kind} ${truncate(current.detail, 56)}`);
|
|
259
|
-
else if (stats.latestPlan) lines.push(`plan
|
|
260
|
-
|
|
261
|
-
lines.push(
|
|
262
|
-
if (cost) lines.push(`cost ${cost}`);
|
|
246
|
+
else if (stats.latestPlan) lines.push(`plan ${truncate(stats.latestPlan, 60)}`);
|
|
247
|
+
if (ship?.dirty || (ship && ship.ahead > 0)) lines.push(formatShipLine(ship));
|
|
248
|
+
if (cost && cost !== "unknown") lines.push(`cost ${cost}`);
|
|
263
249
|
if (updateLatest) lines.push(`update ${updateLatest} available · kxm update --kxm`);
|
|
264
250
|
return lines;
|
|
265
251
|
}
|
|
@@ -290,7 +276,7 @@ export function buildSessionBrief(
|
|
|
290
276
|
const tasks = uniqueLabels(recentTasks(snapshot.runs).map(taskItem));
|
|
291
277
|
const plans = uniqueLabels(snapshot.plans.slice(0, MAX_SESSION_BRIEF_PLANS).map(planItem));
|
|
292
278
|
const resolvedHub: SessionHubStatus = hub ?? { state: "off", evidence: "unconfigured", online: false };
|
|
293
|
-
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest
|
|
279
|
+
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest);
|
|
294
280
|
const widgetLines = formatSessionWidget(stats, current, resolvedHub, ship, updateLatest, cost);
|
|
295
281
|
return {
|
|
296
282
|
schema: SESSION_BRIEF_SCHEMA,
|
|
@@ -359,7 +345,7 @@ export function estimateSessionCost(stateDir: string): string | undefined {
|
|
|
359
345
|
latestModel = (r.effectiveModel as string | undefined) ?? (r.requestedModel as string | undefined) ?? latestModel;
|
|
360
346
|
latestHarness = (r.harness as string | undefined) ?? latestHarness;
|
|
361
347
|
}
|
|
362
|
-
if (!hasMetered) return
|
|
348
|
+
if (!hasMetered) return undefined;
|
|
363
349
|
const route = latestHarness && latestModel ? `${latestHarness}/${latestModel}` : latestModel;
|
|
364
350
|
return `$${totalCost.toFixed(2)} sess${route ? ` · ${route}` : ""}`;
|
|
365
351
|
} catch {
|
|
@@ -417,7 +403,6 @@ export function loadSessionBrief(
|
|
|
417
403
|
effectiveHub,
|
|
418
404
|
options.ship,
|
|
419
405
|
options.updateLatest,
|
|
420
|
-
effectiveCost,
|
|
421
406
|
);
|
|
422
407
|
const widgetLines = formatSessionWidget(
|
|
423
408
|
cached.stats,
|
|
@@ -499,7 +484,6 @@ export async function loadSessionBriefAsync(
|
|
|
499
484
|
effectiveHub,
|
|
500
485
|
options.ship,
|
|
501
486
|
undefined,
|
|
502
|
-
effectiveCost,
|
|
503
487
|
);
|
|
504
488
|
const widgetLines = formatSessionWidget(
|
|
505
489
|
cached.stats,
|