@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
|
@@ -23948,6 +23948,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
23948
23948
|
const userConfigFile = join(userDir, "config.yaml");
|
|
23949
23949
|
const repoDir = repoConfigDirectory(repoRoot);
|
|
23950
23950
|
const repoConfigFile = join(repoDir, "config.yaml");
|
|
23951
|
+
const includeProject = options.layers !== "user";
|
|
23951
23952
|
let userRaw = {};
|
|
23952
23953
|
let userLoadedPath;
|
|
23953
23954
|
if (existsSync(userConfigFile)) {
|
|
@@ -23961,7 +23962,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
23961
23962
|
}
|
|
23962
23963
|
let repoRaw = {};
|
|
23963
23964
|
let repoLoadedPath;
|
|
23964
|
-
if (existsSync(repoConfigFile)) {
|
|
23965
|
+
if (includeProject && existsSync(repoConfigFile)) {
|
|
23965
23966
|
try {
|
|
23966
23967
|
const text = readFileSync(repoConfigFile, "utf8");
|
|
23967
23968
|
repoRaw = (0, import_yaml.parse)(text) ?? {};
|
|
@@ -25461,7 +25462,7 @@ function sessionTokenFixHint(policy) {
|
|
|
25461
25462
|
}
|
|
25462
25463
|
|
|
25463
25464
|
// plugins/kxm/src/mcp-server.ts
|
|
25464
|
-
var VERSION = "0.7.
|
|
25465
|
+
var VERSION = "0.7.179";
|
|
25465
25466
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
25466
25467
|
var inbox = /* @__PURE__ */ new Map();
|
|
25467
25468
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
|
@@ -25121,6 +25121,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
25121
25121
|
const userConfigFile = join11(userDir, "config.yaml");
|
|
25122
25122
|
const repoDir = repoConfigDirectory(repoRoot);
|
|
25123
25123
|
const repoConfigFile = join11(repoDir, "config.yaml");
|
|
25124
|
+
const includeProject = options.layers !== "user";
|
|
25124
25125
|
let userRaw = {};
|
|
25125
25126
|
let userLoadedPath;
|
|
25126
25127
|
if (existsSync10(userConfigFile)) {
|
|
@@ -25134,7 +25135,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
25134
25135
|
}
|
|
25135
25136
|
let repoRaw = {};
|
|
25136
25137
|
let repoLoadedPath;
|
|
25137
|
-
if (existsSync10(repoConfigFile)) {
|
|
25138
|
+
if (includeProject && existsSync10(repoConfigFile)) {
|
|
25138
25139
|
try {
|
|
25139
25140
|
const text = readFileSync8(repoConfigFile, "utf8");
|
|
25140
25141
|
repoRaw = (0, import_yaml5.parse)(text) ?? {};
|
|
@@ -25865,6 +25865,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
25865
25865
|
const userConfigFile = join11(userDir, "config.yaml");
|
|
25866
25866
|
const repoDir = repoConfigDirectory(repoRoot);
|
|
25867
25867
|
const repoConfigFile = join11(repoDir, "config.yaml");
|
|
25868
|
+
const includeProject = options.layers !== "user";
|
|
25868
25869
|
let userRaw = {};
|
|
25869
25870
|
let userLoadedPath;
|
|
25870
25871
|
if (existsSync10(userConfigFile)) {
|
|
@@ -25878,7 +25879,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
25878
25879
|
}
|
|
25879
25880
|
let repoRaw = {};
|
|
25880
25881
|
let repoLoadedPath;
|
|
25881
|
-
if (existsSync10(repoConfigFile)) {
|
|
25882
|
+
if (includeProject && existsSync10(repoConfigFile)) {
|
|
25882
25883
|
try {
|
|
25883
25884
|
const text = readFileSync8(repoConfigFile, "utf8");
|
|
25884
25885
|
repoRaw = (0, import_yaml5.parse)(text) ?? {};
|
|
@@ -23447,6 +23447,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
23447
23447
|
const userConfigFile = join7(userDir, "config.yaml");
|
|
23448
23448
|
const repoDir = repoConfigDirectory(repoRoot);
|
|
23449
23449
|
const repoConfigFile = join7(repoDir, "config.yaml");
|
|
23450
|
+
const includeProject = options.layers !== "user";
|
|
23450
23451
|
let userRaw = {};
|
|
23451
23452
|
let userLoadedPath;
|
|
23452
23453
|
if (existsSync7(userConfigFile)) {
|
|
@@ -23460,7 +23461,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
23460
23461
|
}
|
|
23461
23462
|
let repoRaw = {};
|
|
23462
23463
|
let repoLoadedPath;
|
|
23463
|
-
if (existsSync7(repoConfigFile)) {
|
|
23464
|
+
if (includeProject && existsSync7(repoConfigFile)) {
|
|
23464
23465
|
try {
|
|
23465
23466
|
const text2 = readFileSync5(repoConfigFile, "utf8");
|
|
23466
23467
|
repoRaw = (0, import_yaml6.parse)(text2) ?? {};
|
package/plugins/kxm/package.json
CHANGED
|
@@ -53,7 +53,7 @@ const SUBCOMMANDS: Record<string, string[]> = {
|
|
|
53
53
|
memory: ["brief", "note", "sync"],
|
|
54
54
|
routing: ["report", "benchmark"],
|
|
55
55
|
hub: ["view", "start", "stop", "bind", "unbind"],
|
|
56
|
-
config: ["get", "set", "list"],
|
|
56
|
+
config: ["get", "set", "edit", "list"],
|
|
57
57
|
goal: ["create", "list", "get"],
|
|
58
58
|
task: ["create", "list", "get", "run", "sync"],
|
|
59
59
|
studio: ["layout", "serve"],
|
|
@@ -179,7 +179,7 @@ _kxm() {
|
|
|
179
179
|
_arguments '1:shell:(bash zsh fish)'
|
|
180
180
|
;;
|
|
181
181
|
config)
|
|
182
|
-
_arguments '1:action:(get set list)'
|
|
182
|
+
_arguments '1:action:(get set edit list)'
|
|
183
183
|
;;
|
|
184
184
|
runs)
|
|
185
185
|
_arguments '1:subcommand:(status cancel list)'
|
|
@@ -224,7 +224,7 @@ complete -c kxm -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
|
224
224
|
complete -c kxm -n "__fish_seen_subcommand_from dash" -l screen -a "agents tasks workflows plans inbox procs spend"
|
|
225
225
|
|
|
226
226
|
# Config subcommand
|
|
227
|
-
complete -c kxm -n "__fish_seen_subcommand_from config" -a "get set list"
|
|
227
|
+
complete -c kxm -n "__fish_seen_subcommand_from config" -a "get set edit list"
|
|
228
228
|
|
|
229
229
|
# Runs subcommand
|
|
230
230
|
complete -c kxm -n "__fish_seen_subcommand_from runs" -a "status cancel list"
|
|
@@ -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({
|
|
@@ -48,7 +48,9 @@ import {
|
|
|
48
48
|
setKxmConfigValue,
|
|
49
49
|
getKxmConfigValue,
|
|
50
50
|
formatKxmConfig,
|
|
51
|
+
userConfigDirForEnv,
|
|
51
52
|
} from "../config.ts";
|
|
53
|
+
import { runKxmConfigEditor } from "../config-editor.ts";
|
|
52
54
|
import { generateShellCompletion, type SupportedShell } from "../autocomplete.ts";
|
|
53
55
|
import {
|
|
54
56
|
completionRcTarget,
|
|
@@ -736,6 +738,41 @@ export async function cmdConfigSet(
|
|
|
736
738
|
}
|
|
737
739
|
}
|
|
738
740
|
|
|
741
|
+
export async function cmdConfigEdit(
|
|
742
|
+
runtime: Runtime,
|
|
743
|
+
options: { scope?: string },
|
|
744
|
+
isTty = Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
745
|
+
): Promise<number> {
|
|
746
|
+
const scope = options.scope;
|
|
747
|
+
if (scope !== "project" && scope !== "user") {
|
|
748
|
+
const detail = "config edit requires --scope project or --scope user";
|
|
749
|
+
print(runtime.io, runtime.json, { ok: false, command: "config edit", error: "config_edit_unsupported", detail }, detail);
|
|
750
|
+
return 2;
|
|
751
|
+
}
|
|
752
|
+
if (runtime.json) {
|
|
753
|
+
const detail = "config edit does not print machine-readable output";
|
|
754
|
+
print(runtime.io, runtime.json, { ok: false, command: "config edit", error: "config_edit_unsupported", detail }, detail);
|
|
755
|
+
return 2;
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
const userConfigDir = userConfigDirForEnv(runtime.env);
|
|
759
|
+
return await runKxmConfigEditor({
|
|
760
|
+
target: {
|
|
761
|
+
cwd: runtime.cwd,
|
|
762
|
+
scope,
|
|
763
|
+
...(userConfigDir === undefined ? {} : { userConfigDir }),
|
|
764
|
+
},
|
|
765
|
+
stdout: (text) => runtime.io.stdout(text),
|
|
766
|
+
isTty,
|
|
767
|
+
dryRun: runtime.dryRun,
|
|
768
|
+
});
|
|
769
|
+
} catch (error) {
|
|
770
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
771
|
+
runtime.io.stderr(`config edit failed: ${message}\n`);
|
|
772
|
+
return 1;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
739
776
|
export async function cmdConfigList(runtime: Runtime): Promise<number> {
|
|
740
777
|
try {
|
|
741
778
|
const config = loadKxmConfig(runtime.cwd);
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -165,6 +165,7 @@ import {
|
|
|
165
165
|
cmdImprove,
|
|
166
166
|
cmdConfigGet,
|
|
167
167
|
cmdConfigSet,
|
|
168
|
+
cmdConfigEdit,
|
|
168
169
|
cmdConfigList,
|
|
169
170
|
cmdCompletion,
|
|
170
171
|
cmdCompletionInstall,
|
|
@@ -228,7 +229,7 @@ const DRY_RUN_COMMANDS: ReadonlySet<string> = new Set([
|
|
|
228
229
|
"routing report", "routing benchmark",
|
|
229
230
|
"ssh info", "ssh run", "ssh file", "ssh close",
|
|
230
231
|
"hub view", "hub start", "hub stop", "hub bind", "hub unbind",
|
|
231
|
-
"config get", "config set", "config list",
|
|
232
|
+
"config get", "config set", "config edit", "config list",
|
|
232
233
|
"goal create", "goal list",
|
|
233
234
|
"task create", "task list", "task get", "task run", "task sync",
|
|
234
235
|
"studio layout", "studio serve",
|
|
@@ -772,7 +773,8 @@ function createProgram(ctx: CliContext, result: { code: number }, argv: readonly
|
|
|
772
773
|
.option("--claude", "Install plugin for Claude Code")
|
|
773
774
|
.option("--omp", "Install plugin for Oh My Pi (OMP)")
|
|
774
775
|
.option("--pi", "Install plugin for Pi")
|
|
775
|
-
.
|
|
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 }) {
|
|
776
778
|
result.code = await cmdPluginInstall(runtimeFrom(ctx, this), options);
|
|
777
779
|
});
|
|
778
780
|
|
|
@@ -1430,6 +1432,11 @@ function createProgram(ctx: CliContext, result: { code: number }, argv: readonly
|
|
|
1430
1432
|
.action(async function configSetAction(this: Command, key: string, value: string, options: { scope: string }) {
|
|
1431
1433
|
result.code = await cmdConfigSet(runtimeFrom(ctx, this), key, value, options);
|
|
1432
1434
|
});
|
|
1435
|
+
addGlobalOptions(configCmd.command("edit").description("Edit supported non-secret settings in the terminal panel"))
|
|
1436
|
+
.option("--scope <scope>", "Write scope: project or user")
|
|
1437
|
+
.action(async function configEditAction(this: Command, options: { scope?: string }) {
|
|
1438
|
+
result.code = await cmdConfigEdit(runtimeFrom(ctx, this), options);
|
|
1439
|
+
});
|
|
1433
1440
|
addGlobalOptions(configCmd.command("list", { isDefault: true }).description("List resolved configuration values"))
|
|
1434
1441
|
.action(async function configListAction(this: Command) {
|
|
1435
1442
|
result.code = await cmdConfigList(runtimeFrom(ctx, this));
|