@kontextmind/kxm 0.7.177 → 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 +21 -0
- package/docs/architecture/index.md +2 -1
- package/docs/architecture/inventory.md +8 -6
- package/docs/contributing/decisions.md +27 -1
- package/docs/contributing/development.md +4 -2
- package/docs/contributing/test-matrix.md +1 -0
- package/docs/contributing/tui-components.md +22 -4
- package/docs/reference/cli-reference.md +63 -6
- package/docs/reference/config-reference.md +17 -3
- package/docs/reference/tools.md +6 -4
- package/package.json +2 -2
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/claude-hook.js +40 -48
- package/plugins/kxm/dist/cli.js +2118 -276
- package/plugins/kxm/dist/extension.js +11252 -1492
- package/plugins/kxm/dist/mcp-server.js +3 -2
- package/plugins/kxm/dist/runtime-supervisor.js +2 -1
- package/plugins/kxm/dist/runtime.js +2 -1
- package/plugins/kxm/dist/server.js +2 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/autocomplete.ts +3 -3
- package/plugins/kxm/src/cli/plugins.ts +193 -25
- package/plugins/kxm/src/cli/system.ts +37 -0
- package/plugins/kxm/src/cli.ts +9 -2
- package/plugins/kxm/src/config-editor.ts +479 -0
- package/plugins/kxm/src/config.ts +3 -2
- package/plugins/kxm/src/extension.ts +9 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/session-work.ts +36 -51
|
@@ -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,
|
|
@@ -564,7 +548,7 @@ export function sessionBriefPickerEnabled(input: { env?: NodeJS.ProcessEnv; mode
|
|
|
564
548
|
return reason === "startup" || reason === "new" || reason === "fork";
|
|
565
549
|
}
|
|
566
550
|
|
|
567
|
-
export const KXM_SLASH_SUBCOMMANDS = ["status", "hub", "memory", "progress", "workflow", "help"] as const;
|
|
551
|
+
export const KXM_SLASH_SUBCOMMANDS = ["status", "hub", "memory", "progress", "workflow", "config", "help"] as const;
|
|
568
552
|
export type KxmSlashCommand = "brief" | typeof KXM_SLASH_SUBCOMMANDS[number];
|
|
569
553
|
|
|
570
554
|
export function parseKxmSlashArgs(args: string | undefined): KxmSlashCommand {
|
|
@@ -576,6 +560,7 @@ export function parseKxmSlashArgs(args: string | undefined): KxmSlashCommand {
|
|
|
576
560
|
|| raw === "memory"
|
|
577
561
|
|| raw === "progress"
|
|
578
562
|
|| raw === "workflow"
|
|
563
|
+
|| raw === "config"
|
|
579
564
|
|| raw === "help"
|
|
580
565
|
) return raw;
|
|
581
566
|
return "help";
|