@kontextmind/kxm 0.7.115 → 0.7.119

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.
@@ -15624,6 +15624,7 @@ var READ_ONLY_ONESHOT_ARGS = Object.freeze({
15624
15624
  // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
15625
15625
  // is not enough: extensions and hooks can still run with their own permissions.
15626
15626
  pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
15627
+ omp: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
15627
15628
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--permission-mode", "plan", "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
15628
15629
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
15629
15630
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -15635,6 +15636,7 @@ function oneShotReadOnlyArgs(harness) {
15635
15636
  }
15636
15637
  var WRITER_ONESHOT_ARGS = Object.freeze({
15637
15638
  pi: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
15639
+ omp: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
15638
15640
  grok: Object.freeze(["--always-approve", "--no-subagents", "--disable-web-search"])
15639
15641
  });
15640
15642
  function oneShotWriterArgs(harness) {
@@ -15663,6 +15665,26 @@ var BUILTIN_HARNESSES = Object.freeze([
15663
15665
  usageParser: parsePiOneShotUsage
15664
15666
  }
15665
15667
  },
15668
+ {
15669
+ id: "omp",
15670
+ label: "Oh My Pi",
15671
+ default: false,
15672
+ mode: "headless",
15673
+ commands: ["omp"],
15674
+ versionArgs: ["--version"],
15675
+ authArgs: ["models", "--json"],
15676
+ update: {
15677
+ self: ["update"],
15678
+ extensions: ["plugin", "upgrade"],
15679
+ models: ["models", "refresh"]
15680
+ },
15681
+ oneShot: {
15682
+ argv: ["-p", ...oneShotReadOnlyArgs("omp"), "--mode", "json"],
15683
+ promptVia: "arg",
15684
+ outputFormat: "json",
15685
+ usageParser: parsePiOneShotUsage
15686
+ }
15687
+ },
15666
15688
  {
15667
15689
  id: "claude",
15668
15690
  label: "Claude Code",
@@ -15911,6 +15933,16 @@ function interpretAuth(id, result) {
15911
15933
  if (commandSucceeded(result) && lines.some((line) => line === GROK_LOGIN_LINE)) return { authenticated: true, issues: [] };
15912
15934
  return { authenticated: null, issues: ["auth_unparsed"] };
15913
15935
  }
15936
+ if (id === "omp") {
15937
+ if (commandSucceeded(result)) {
15938
+ const payload = parseJsonObject(result.stdout);
15939
+ if (payload && Array.isArray(payload.models) && payload.models.length > 0) {
15940
+ return { authenticated: true, issues: [] };
15941
+ }
15942
+ if (commandSucceeded(result)) return { authenticated: true, issues: [] };
15943
+ }
15944
+ return { authenticated: null, issues: ["auth_unparsed"] };
15945
+ }
15914
15946
  if (id === "agy") {
15915
15947
  const text = `${result.stdout}
15916
15948
  ${result.stderr}`;
@@ -16104,7 +16136,7 @@ function validateHarnessModelPair(harnessId, modelSpec) {
16104
16136
  }
16105
16137
  return { valid: true };
16106
16138
  }
16107
- if (harnessId === "pi") {
16139
+ if (harnessId === "pi" || harnessId === "omp") {
16108
16140
  if (!provider && model?.includes("/")) {
16109
16141
  const idx = model.indexOf("/");
16110
16142
  provider = model.slice(0, idx).toLowerCase();
@@ -16113,17 +16145,17 @@ function validateHarnessModelPair(harnessId, modelSpec) {
16113
16145
  if (!provider) return { valid: true };
16114
16146
  const blocked = (message) => ({ valid: false, issue: "pi_native_impersonation_blocked", message });
16115
16147
  if (PI_NATIVE_BRAKE_PROVIDERS.includes(provider)) {
16116
- return blocked(`pi must not impersonate native provider ${provider}; use the native harness`);
16148
+ return blocked(`${harnessId} must not impersonate native provider ${provider}; use the native harness`);
16117
16149
  }
16118
16150
  if (provider === "antigravity" && model && PI_ANTIGRAVITY_MODEL_ID.test(model)) return { valid: true };
16119
16151
  const owned = Object.hasOwn(PI_VENDOR_OWNED_PROVIDERS, provider) ? PI_VENDOR_OWNED_PROVIDERS[provider] : void 0;
16120
16152
  if (owned) {
16121
- return blocked(`pi provider ${provider} bills native vendor ${owned} for ${model ?? "this model"}; use the native harness`);
16153
+ return blocked(`${harnessId} provider ${provider} bills native vendor ${owned} for ${model ?? "this model"}; use the native harness`);
16122
16154
  }
16123
16155
  const segment = model?.includes("/") ? model.slice(0, model.indexOf("/")).toLowerCase() : void 0;
16124
16156
  const vendor = segment && Object.hasOwn(PI_VENDOR_SEGMENT_ALIASES, segment) ? PI_VENDOR_SEGMENT_ALIASES[segment] : segment;
16125
16157
  if (vendor && PI_NATIVE_BRAKE_PROVIDERS.includes(vendor)) {
16126
- return blocked(`pi must not bill native vendor ${vendor} through ${provider}; use the native harness`);
16158
+ return blocked(`${harnessId} must not bill native vendor ${vendor} through ${provider}; use the native harness`);
16127
16159
  }
16128
16160
  return { valid: true };
16129
16161
  }
@@ -15621,6 +15621,7 @@ var READ_ONLY_ONESHOT_ARGS = Object.freeze({
15621
15621
  // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
15622
15622
  // is not enough: extensions and hooks can still run with their own permissions.
15623
15623
  pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
15624
+ omp: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
15624
15625
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--permission-mode", "plan", "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
15625
15626
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
15626
15627
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -15632,6 +15633,7 @@ function oneShotReadOnlyArgs(harness) {
15632
15633
  }
15633
15634
  var WRITER_ONESHOT_ARGS = Object.freeze({
15634
15635
  pi: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
15636
+ omp: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
15635
15637
  grok: Object.freeze(["--always-approve", "--no-subagents", "--disable-web-search"])
15636
15638
  });
15637
15639
  function oneShotWriterArgs(harness) {
@@ -15660,6 +15662,26 @@ var BUILTIN_HARNESSES = Object.freeze([
15660
15662
  usageParser: parsePiOneShotUsage
15661
15663
  }
15662
15664
  },
15665
+ {
15666
+ id: "omp",
15667
+ label: "Oh My Pi",
15668
+ default: false,
15669
+ mode: "headless",
15670
+ commands: ["omp"],
15671
+ versionArgs: ["--version"],
15672
+ authArgs: ["models", "--json"],
15673
+ update: {
15674
+ self: ["update"],
15675
+ extensions: ["plugin", "upgrade"],
15676
+ models: ["models", "refresh"]
15677
+ },
15678
+ oneShot: {
15679
+ argv: ["-p", ...oneShotReadOnlyArgs("omp"), "--mode", "json"],
15680
+ promptVia: "arg",
15681
+ outputFormat: "json",
15682
+ usageParser: parsePiOneShotUsage
15683
+ }
15684
+ },
15663
15685
  {
15664
15686
  id: "claude",
15665
15687
  label: "Claude Code",
@@ -15913,6 +15935,16 @@ function interpretAuth(id, result) {
15913
15935
  if (commandSucceeded(result) && lines.some((line) => line === GROK_LOGIN_LINE)) return { authenticated: true, issues: [] };
15914
15936
  return { authenticated: null, issues: ["auth_unparsed"] };
15915
15937
  }
15938
+ if (id === "omp") {
15939
+ if (commandSucceeded(result)) {
15940
+ const payload = parseJsonObject(result.stdout);
15941
+ if (payload && Array.isArray(payload.models) && payload.models.length > 0) {
15942
+ return { authenticated: true, issues: [] };
15943
+ }
15944
+ if (commandSucceeded(result)) return { authenticated: true, issues: [] };
15945
+ }
15946
+ return { authenticated: null, issues: ["auth_unparsed"] };
15947
+ }
15916
15948
  if (id === "agy") {
15917
15949
  const text = `${result.stdout}
15918
15950
  ${result.stderr}`;
@@ -16127,7 +16159,7 @@ function validateHarnessModelPair(harnessId, modelSpec) {
16127
16159
  }
16128
16160
  return { valid: true };
16129
16161
  }
16130
- if (harnessId === "pi") {
16162
+ if (harnessId === "pi" || harnessId === "omp") {
16131
16163
  if (!provider && model?.includes("/")) {
16132
16164
  const idx = model.indexOf("/");
16133
16165
  provider = model.slice(0, idx).toLowerCase();
@@ -16136,17 +16168,17 @@ function validateHarnessModelPair(harnessId, modelSpec) {
16136
16168
  if (!provider) return { valid: true };
16137
16169
  const blocked = (message) => ({ valid: false, issue: "pi_native_impersonation_blocked", message });
16138
16170
  if (PI_NATIVE_BRAKE_PROVIDERS.includes(provider)) {
16139
- return blocked(`pi must not impersonate native provider ${provider}; use the native harness`);
16171
+ return blocked(`${harnessId} must not impersonate native provider ${provider}; use the native harness`);
16140
16172
  }
16141
16173
  if (provider === "antigravity" && model && PI_ANTIGRAVITY_MODEL_ID.test(model)) return { valid: true };
16142
16174
  const owned = Object.hasOwn(PI_VENDOR_OWNED_PROVIDERS, provider) ? PI_VENDOR_OWNED_PROVIDERS[provider] : void 0;
16143
16175
  if (owned) {
16144
- return blocked(`pi provider ${provider} bills native vendor ${owned} for ${model ?? "this model"}; use the native harness`);
16176
+ return blocked(`${harnessId} provider ${provider} bills native vendor ${owned} for ${model ?? "this model"}; use the native harness`);
16145
16177
  }
16146
16178
  const segment = model?.includes("/") ? model.slice(0, model.indexOf("/")).toLowerCase() : void 0;
16147
16179
  const vendor = segment && Object.hasOwn(PI_VENDOR_SEGMENT_ALIASES, segment) ? PI_VENDOR_SEGMENT_ALIASES[segment] : segment;
16148
16180
  if (vendor && PI_NATIVE_BRAKE_PROVIDERS.includes(vendor)) {
16149
- return blocked(`pi must not bill native vendor ${vendor} through ${provider}; use the native harness`);
16181
+ return blocked(`${harnessId} must not bill native vendor ${vendor} through ${provider}; use the native harness`);
16150
16182
  }
16151
16183
  return { valid: true };
16152
16184
  }
@@ -15262,6 +15262,7 @@ var READ_ONLY_ONESHOT_ARGS = Object.freeze({
15262
15262
  // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
15263
15263
  // is not enough: extensions and hooks can still run with their own permissions.
15264
15264
  pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
15265
+ omp: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
15265
15266
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--permission-mode", "plan", "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
15266
15267
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
15267
15268
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -15273,6 +15274,7 @@ function oneShotReadOnlyArgs(harness) {
15273
15274
  }
15274
15275
  var WRITER_ONESHOT_ARGS = Object.freeze({
15275
15276
  pi: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
15277
+ omp: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
15276
15278
  grok: Object.freeze(["--always-approve", "--no-subagents", "--disable-web-search"])
15277
15279
  });
15278
15280
  var BUILTIN_HARNESSES = Object.freeze([
@@ -15295,6 +15297,26 @@ var BUILTIN_HARNESSES = Object.freeze([
15295
15297
  usageParser: parsePiOneShotUsage
15296
15298
  }
15297
15299
  },
15300
+ {
15301
+ id: "omp",
15302
+ label: "Oh My Pi",
15303
+ default: false,
15304
+ mode: "headless",
15305
+ commands: ["omp"],
15306
+ versionArgs: ["--version"],
15307
+ authArgs: ["models", "--json"],
15308
+ update: {
15309
+ self: ["update"],
15310
+ extensions: ["plugin", "upgrade"],
15311
+ models: ["models", "refresh"]
15312
+ },
15313
+ oneShot: {
15314
+ argv: ["-p", ...oneShotReadOnlyArgs("omp"), "--mode", "json"],
15315
+ promptVia: "arg",
15316
+ outputFormat: "json",
15317
+ usageParser: parsePiOneShotUsage
15318
+ }
15319
+ },
15298
15320
  {
15299
15321
  id: "claude",
15300
15322
  label: "Claude Code",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.115",
3
+ "version": "0.7.119",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -23,6 +23,12 @@ Issuing a token is an operator action. When kxm_* tools fail with
23
23
  `No active session token found in env or disk` for such a file even though it
24
24
  still blocks the tools.
25
25
 
26
+ ## Plugins
27
+
28
+ | Command | Purpose | Options |
29
+ |---|---|---|
30
+ | `kxm plugin install` | Install KXM plugin for discovered or specified harnesses | `--all`, `--claude`, `--omp`, `--pi`, `--dry-run`, `--json` |
31
+
26
32
  ## Updates
27
33
 
28
34
  | Command | Purpose |
@@ -8,6 +8,7 @@ const TOP_LEVEL_COMMANDS = [
8
8
  "runs",
9
9
  "tenant",
10
10
  "harness",
11
+ "plugin",
11
12
  "update",
12
13
  "runtime",
13
14
  "trust",
@@ -38,6 +39,7 @@ const SUBCOMMANDS: Record<string, string[]> = {
38
39
  runs: ["status", "cancel", "list"],
39
40
  tenant: ["status"],
40
41
  harness: ["list"],
42
+ plugin: ["install"],
41
43
  runtime: ["start", "status", "stop"],
42
44
  trust: ["diff", "check"],
43
45
  agent: ["worker"],
@@ -137,6 +139,7 @@ _kxm() {
137
139
  'run:Create a KXM workflow run'
138
140
  'runs:Inspect KXM runs'
139
141
  'harness:Detect coding-agent harnesses and auth'
142
+ 'plugin:Install or manage KXM harness plugins'
140
143
  'update:Update kxm, harness CLIs, and model catalogs'
141
144
  'runtime:Manage the KXM Runtime supervisor'
142
145
  'trust:Permission-diff trust review'
@@ -0,0 +1,201 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { findKxmRepoRoot } from "../repo-root.ts";
3
+ import { probeHarnesses } from "../harness.ts";
4
+ import { print, type Runtime } from "./types.ts";
5
+
6
+ export type PluginHarnessId = "pi" | "omp" | "claude";
7
+
8
+ export const PLUGIN_SUPPORTED_HARNESSES: readonly PluginHarnessId[] = Object.freeze(["pi", "omp", "claude"]);
9
+
10
+ export interface PluginInstallOptions {
11
+ all?: boolean | undefined;
12
+ claude?: boolean | undefined;
13
+ omp?: boolean | undefined;
14
+ pi?: boolean | undefined;
15
+ }
16
+
17
+ export interface PluginInstallHarnessResult {
18
+ harness: string;
19
+ status: "passed" | "failed" | "would" | "skipped";
20
+ command?: string | undefined;
21
+ args?: readonly string[] | undefined;
22
+ detail?: string | undefined;
23
+ error?: string | undefined;
24
+ }
25
+
26
+ export async function cmdPluginInstall(
27
+ runtime: Runtime,
28
+ options: PluginInstallOptions,
29
+ ): Promise<number> {
30
+ const repoRoot = findKxmRepoRoot(import.meta.url);
31
+ const env = runtime.env.PATH ? runtime.env : { ...process.env, ...runtime.env };
32
+ const inventory = probeHarnesses({ env });
33
+
34
+ const isExplicit = Boolean(options.claude || options.omp || options.pi);
35
+ const requestedHarnesses: PluginHarnessId[] = [];
36
+ if (options.pi) requestedHarnesses.push("pi");
37
+ if (options.omp) requestedHarnesses.push("omp");
38
+ if (options.claude) requestedHarnesses.push("claude");
39
+
40
+ const targetHarnesses: readonly PluginHarnessId[] = isExplicit
41
+ ? requestedHarnesses
42
+ : PLUGIN_SUPPORTED_HARNESSES;
43
+
44
+ const results: PluginInstallHarnessResult[] = [];
45
+
46
+ for (const harnessId of targetHarnesses) {
47
+ const status = inventory.harnesses.find((h) => h.id === harnessId);
48
+ const detected = Boolean(status?.detected && status?.command);
49
+
50
+ if (!detected) {
51
+ if (isExplicit) {
52
+ results.push({
53
+ harness: harnessId,
54
+ status: "failed",
55
+ error: "harness_not_detected",
56
+ detail: `${harnessId} harness is not installed or not found on PATH`,
57
+ });
58
+ }
59
+ continue;
60
+ }
61
+
62
+ const command = status!.command!;
63
+
64
+ if (harnessId === "pi") {
65
+ const args = ["install", repoRoot, "-a"];
66
+ if (runtime.dryRun) {
67
+ results.push({
68
+ harness: "pi",
69
+ status: "would",
70
+ command,
71
+ args,
72
+ detail: `would install kxm plugin into pi: ${command} ${args.join(" ")}`,
73
+ });
74
+ } else {
75
+ const run = runtime.io.spawnSync
76
+ ? runtime.io.spawnSync(command, args)
77
+ : spawnSync(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
78
+ if (run.error || (run.status !== 0 && run.status !== null)) {
79
+ results.push({
80
+ harness: "pi",
81
+ status: "failed",
82
+ command,
83
+ args,
84
+ error: run.error?.message || "install_failed",
85
+ detail: (run.stderr || run.stdout || "pi install exited with error").trim(),
86
+ });
87
+ } else {
88
+ results.push({
89
+ harness: "pi",
90
+ status: "passed",
91
+ command,
92
+ args,
93
+ detail: "installed kxm plugin into pi",
94
+ });
95
+ }
96
+ }
97
+ } else if (harnessId === "omp") {
98
+ const args = ["plugin", "install", repoRoot];
99
+ if (runtime.dryRun) {
100
+ results.push({
101
+ harness: "omp",
102
+ status: "would",
103
+ command,
104
+ args,
105
+ detail: `would install kxm plugin into omp: ${command} ${args.join(" ")}`,
106
+ });
107
+ } else {
108
+ const run = runtime.io.spawnSync
109
+ ? runtime.io.spawnSync(command, args)
110
+ : spawnSync(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
111
+ if (run.error || (run.status !== 0 && run.status !== null)) {
112
+ results.push({
113
+ harness: "omp",
114
+ status: "failed",
115
+ command,
116
+ args,
117
+ error: run.error?.message || "install_failed",
118
+ detail: (run.stderr || run.stdout || "omp plugin install exited with error").trim(),
119
+ });
120
+ } else {
121
+ results.push({
122
+ harness: "omp",
123
+ status: "passed",
124
+ command,
125
+ args,
126
+ detail: "installed kxm plugin into omp",
127
+ });
128
+ }
129
+ }
130
+ } else if (harnessId === "claude") {
131
+ const marketplaceArgs = ["plugin", "marketplace", "add", repoRoot];
132
+ const installArgs = ["plugin", "install", "kxm", "-y"];
133
+ if (runtime.dryRun) {
134
+ results.push({
135
+ harness: "claude",
136
+ status: "would",
137
+ command,
138
+ args: installArgs,
139
+ detail: `would add marketplace and install kxm plugin into claude: ${command} ${installArgs.join(" ")}`,
140
+ });
141
+ } else {
142
+ if (runtime.io.spawnSync) {
143
+ runtime.io.spawnSync(command, marketplaceArgs);
144
+ } else {
145
+ spawnSync(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
146
+ }
147
+ const run = runtime.io.spawnSync
148
+ ? runtime.io.spawnSync(command, installArgs)
149
+ : spawnSync(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
150
+ if (run.error || (run.status !== 0 && run.status !== null)) {
151
+ results.push({
152
+ harness: "claude",
153
+ status: "failed",
154
+ command,
155
+ args: installArgs,
156
+ error: run.error?.message || "install_failed",
157
+ detail: (run.stderr || run.stdout || "claude plugin install exited with error").trim(),
158
+ });
159
+ } else {
160
+ results.push({
161
+ harness: "claude",
162
+ status: "passed",
163
+ command,
164
+ args: installArgs,
165
+ detail: "installed kxm plugin into claude",
166
+ });
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ if (results.length === 0) {
173
+ const errorMsg = "no supported harnesses (pi, omp, claude) detected on PATH";
174
+ print(
175
+ runtime.io,
176
+ runtime.json,
177
+ { ok: false, command: "plugin install", error: "no_harnesses_detected", results: [] },
178
+ errorMsg,
179
+ );
180
+ return 1;
181
+ }
182
+
183
+ const ok = results.every((r) => r.status === "passed" || r.status === "would");
184
+ const summary = results
185
+ .map((r) => `${r.harness}: ${r.status}${r.detail ? ` (${r.detail})` : ""}`)
186
+ .join("\n");
187
+
188
+ print(
189
+ runtime.io,
190
+ runtime.json,
191
+ {
192
+ ok,
193
+ command: "plugin install",
194
+ ...(runtime.dryRun ? { dryRun: true } : {}),
195
+ results,
196
+ },
197
+ summary,
198
+ );
199
+
200
+ return ok ? 0 : 1;
201
+ }
@@ -120,6 +120,8 @@ cmdBackup,
120
120
  cmdKxmRuntime,
121
121
  } from "./cli/project.ts";
122
122
 
123
+ import { cmdPluginInstall } from "./cli/plugins.ts";
124
+
123
125
  import {
124
126
  cmdStatus,
125
127
  cmdDash,
@@ -185,6 +187,7 @@ const USAGE_ERROR_CODES = new Set([
185
187
  */
186
188
  const DRY_RUN_COMMANDS: ReadonlySet<string> = new Set([
187
189
  "init", "backup", "restore", "run", "explain", "suggest", "update", "dash", "completion", "completion install",
190
+ "plugin install", "plugin",
188
191
  "runs status", "runs drive", "runs receipt", "runs cancel", "runs list",
189
192
  "tenant status", "models inventory-refresh", "harness list", "auth token",
190
193
  "routes list", "routes count", "routes admit", "routes disable",
@@ -506,6 +509,17 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
506
509
  harnessCmd.helpCommand("help", "Show harness help");
507
510
  addGlobalOptions(harnessCmd.command("list").description("Show installed harnesses, auth, and native updaters")).action(bind(cmdHarnessList));
508
511
 
512
+ const pluginCmd = addGlobalOptions(program.command("plugin").description("Install or manage KXM harness plugins"));
513
+ pluginCmd.helpCommand("help", "Show plugin help");
514
+ addGlobalOptions(pluginCmd.command("install", { isDefault: true }).description("Install the KXM plugin into discovered or specified harnesses"))
515
+ .option("--all", "Install plugin for all discovered harnesses (default)")
516
+ .option("--claude", "Install plugin for Claude Code")
517
+ .option("--omp", "Install plugin for Oh My Pi (OMP)")
518
+ .option("--pi", "Install plugin for Pi")
519
+ .action(async function pluginInstallAction(this: Command, options: { all?: boolean; claude?: boolean; omp?: boolean; pi?: boolean }) {
520
+ result.code = await cmdPluginInstall(runtimeFrom(ctx, this), options);
521
+ });
522
+
509
523
  const authCmd = addGlobalOptions(program.command("auth").description("Manage credentials, tokens, and authorization"));
510
524
  authCmd.helpCommand("help", "Show auth help");
511
525
  addGlobalOptions(authCmd.command("token").description("Inspect, issue, or clear local disk session tokens"))
@@ -508,6 +508,7 @@ const READ_ONLY_ONESHOT_ARGS = Object.freeze({
508
508
  // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone
509
509
  // is not enough: extensions and hooks can still run with their own permissions.
510
510
  pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
511
+ omp: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]),
511
512
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--permission-mode", "plan", "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
512
513
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
513
514
  grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
@@ -537,6 +538,7 @@ export function oneShotReadOnlyArgs(harness: string): readonly string[] | undefi
537
538
  */
538
539
  const WRITER_ONESHOT_ARGS = Object.freeze({
539
540
  pi: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
541
+ omp: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
540
542
  grok: Object.freeze(["--always-approve", "--no-subagents", "--disable-web-search"]),
541
543
  });
542
544
 
@@ -568,6 +570,26 @@ export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([
568
570
  usageParser: parsePiOneShotUsage,
569
571
  },
570
572
  },
573
+ {
574
+ id: "omp",
575
+ label: "Oh My Pi",
576
+ default: false,
577
+ mode: "headless",
578
+ commands: ["omp"],
579
+ versionArgs: ["--version"],
580
+ authArgs: ["models", "--json"],
581
+ update: {
582
+ self: ["update"],
583
+ extensions: ["plugin", "upgrade"],
584
+ models: ["models", "refresh"],
585
+ },
586
+ oneShot: {
587
+ argv: ["-p", ...oneShotReadOnlyArgs("omp")!, "--mode", "json"],
588
+ promptVia: "arg",
589
+ outputFormat: "json",
590
+ usageParser: parsePiOneShotUsage,
591
+ },
592
+ },
571
593
  {
572
594
  id: "claude",
573
595
  label: "Claude Code",
@@ -857,6 +879,16 @@ function interpretAuth(id: string, result: HarnessCommandResult): Pick<HarnessSt
857
879
  if (commandSucceeded(result) && lines.some((line) => line === GROK_LOGIN_LINE)) return { authenticated: true, issues: [] };
858
880
  return { authenticated: null, issues: ["auth_unparsed"] };
859
881
  }
882
+ if (id === "omp") {
883
+ if (commandSucceeded(result)) {
884
+ const payload = parseJsonObject(result.stdout);
885
+ if (payload && Array.isArray(payload.models) && payload.models.length > 0) {
886
+ return { authenticated: true, issues: [] };
887
+ }
888
+ if (commandSucceeded(result)) return { authenticated: true, issues: [] };
889
+ }
890
+ return { authenticated: null, issues: ["auth_unparsed"] };
891
+ }
860
892
  if (id === "agy") {
861
893
  const text = `${result.stdout}\n${result.stderr}`;
862
894
  if (!commandSucceeded(result) || !text.trim()) {
@@ -1118,7 +1150,7 @@ export function validateHarnessModelPair(
1118
1150
  return { valid: true };
1119
1151
  }
1120
1152
 
1121
- if (harnessId === "pi") {
1153
+ if (harnessId === "pi" || harnessId === "omp") {
1122
1154
  if (!provider && model?.includes("/")) {
1123
1155
  const idx = model.indexOf("/");
1124
1156
  provider = model.slice(0, idx).toLowerCase();
@@ -1127,17 +1159,17 @@ export function validateHarnessModelPair(
1127
1159
  if (!provider) return { valid: true };
1128
1160
  const blocked = (message: string): HarnessModelValidation => ({ valid: false, issue: "pi_native_impersonation_blocked", message });
1129
1161
  if (PI_NATIVE_BRAKE_PROVIDERS.includes(provider)) {
1130
- return blocked(`pi must not impersonate native provider ${provider}; use the native harness`);
1162
+ return blocked(`${harnessId} must not impersonate native provider ${provider}; use the native harness`);
1131
1163
  }
1132
1164
  if (provider === "antigravity" && model && PI_ANTIGRAVITY_MODEL_ID.test(model)) return { valid: true };
1133
1165
  const owned = Object.hasOwn(PI_VENDOR_OWNED_PROVIDERS, provider) ? PI_VENDOR_OWNED_PROVIDERS[provider] : undefined;
1134
1166
  if (owned) {
1135
- return blocked(`pi provider ${provider} bills native vendor ${owned} for ${model ?? "this model"}; use the native harness`);
1167
+ return blocked(`${harnessId} provider ${provider} bills native vendor ${owned} for ${model ?? "this model"}; use the native harness`);
1136
1168
  }
1137
1169
  const segment = model?.includes("/") ? model.slice(0, model.indexOf("/")).toLowerCase() : undefined;
1138
1170
  const vendor = segment && Object.hasOwn(PI_VENDOR_SEGMENT_ALIASES, segment) ? PI_VENDOR_SEGMENT_ALIASES[segment] : segment;
1139
1171
  if (vendor && PI_NATIVE_BRAKE_PROVIDERS.includes(vendor)) {
1140
- return blocked(`pi must not bill native vendor ${vendor} through ${provider}; use the native harness`);
1172
+ return blocked(`${harnessId} must not bill native vendor ${vendor} through ${provider}; use the native harness`);
1141
1173
  }
1142
1174
  return { valid: true };
1143
1175
  }
@@ -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.115";
14
+ const VERSION = "0.7.119";
15
15
  const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
16
16
  const inbox = new Map<string, MessageRecord>();
17
17
  const notifiedInbox = new Set<string>();
@@ -92,6 +92,12 @@ export function claudeBridgeRegistrationNotice(
92
92
  report: ClaudeBridgeRegistration | undefined,
93
93
  pi?: object,
94
94
  ): { message: string; type: "warning" } | undefined {
95
+ if (report?.warning) {
96
+ return {
97
+ message: boundedRegistrationWarning(report.warning),
98
+ type: "warning",
99
+ };
100
+ }
95
101
  const conflict = Boolean(report?.conflict) || (pi ? claudeBridgeStandaloneToolsPresent(pi) : false);
96
102
  if (!conflict) return undefined;
97
103
  return {
@@ -112,13 +118,23 @@ export function registerClaudeBridgeProvider(pi: ExtensionAPI): ClaudeBridgeRegi
112
118
  return { registered: false, conflict: false };
113
119
  }
114
120
 
115
- pi.registerProvider(PROVIDER_ID, {
116
- name: PROVIDER_NAME,
117
- baseUrl: PROVIDER_ID,
118
- api: CLAUDE_BRIDGE_API,
119
- models: registeredClaudeBridgeModels(),
120
- streamSimple: streamClaudeBridge,
121
- } as unknown as Parameters<ExtensionAPI["registerProvider"]>[1]);
122
-
123
- return { registered: true, conflict: false };
121
+ try {
122
+ pi.registerProvider(PROVIDER_ID, {
123
+ name: PROVIDER_NAME,
124
+ baseUrl: PROVIDER_ID,
125
+ apiKey: PROVIDER_ID,
126
+ api: CLAUDE_BRIDGE_API,
127
+ models: registeredClaudeBridgeModels(),
128
+ streamSimple: streamClaudeBridge,
129
+ } as unknown as Parameters<ExtensionAPI["registerProvider"]>[1]);
130
+
131
+ return { registered: true, conflict: false };
132
+ } catch (error) {
133
+ const reason = error instanceof Error ? error.message : String(error);
134
+ return {
135
+ registered: false,
136
+ conflict: false,
137
+ warning: `kxm: claude-bridge provider registration failed (${reason}). Configure apiKey or ensure provider auth is valid.`,
138
+ };
139
+ }
124
140
  }