@kontextmind/kxm 0.7.0 → 0.7.4
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/.kxm/roles/writer.yaml +2 -0
- package/docs/README.md +2 -0
- package/docs/agent-skills.md +5 -2
- package/docs/configuration.md +10 -1
- package/docs/getting-started.md +21 -0
- package/docs/operations.md +24 -0
- package/docs/skills/repo-work-delivery.md +5 -0
- package/docs/skills.md +2 -0
- package/docs/troubleshooting.md +22 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +2257 -439
- package/plugins/kxm/dist/core.js +57 -0
- package/plugins/kxm/dist/extension.js +40 -3
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +140 -17
- package/plugins/kxm/dist/server.js +129 -4
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +136 -13
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +9 -0
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +9 -2
- package/plugins/kxm/src/autocomplete.ts +1 -1
- package/plugins/kxm/src/cli.ts +507 -9
- package/plugins/kxm/src/completion-install.ts +223 -0
- package/plugins/kxm/src/database.ts +1 -1
- package/plugins/kxm/src/external-effects.ts +1 -1
- package/plugins/kxm/src/hub-env.ts +193 -0
- package/plugins/kxm/src/init-guide-setup.ts +547 -0
- package/plugins/kxm/src/local-snapshot.ts +1 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/protocol.ts +111 -0
- package/plugins/kxm/src/role.ts +335 -0
- package/plugins/kxm/src/safety-integrity.ts +76 -0
- package/plugins/kxm/src/sqlite.ts +76 -0
- package/plugins/kxm/src/store.ts +1 -1
- package/plugins/kxm/src/vnext-bindings.ts +1 -1
- package/plugins/kxm/src/vnext-config.ts +38 -1
- package/plugins/kxm/src/vnext-engine-command.ts +2 -0
- package/plugins/kxm/src/vnext-engine.ts +16 -0
- package/plugins/kxm/src/vnext-harness.ts +4 -2
- package/plugins/kxm/src/vnext-oneshot-process.ts +21 -4
- package/plugins/kxm/src/vnext-oneshot-producer.ts +18 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +1 -1
- package/plugins/kxm/src/workflow-tui.ts +1 -1
- package/plugins/kxm/src/workflow.ts +144 -0
- package/scripts/kxm-bump-version.mjs +146 -0
- package/scripts/kxm-hub.mjs +145 -3
- package/scripts/kxm-publish-npm.mjs +3 -1
- package/scripts/kxm-release-github.mjs +3 -1
- package/scripts/kxm.mjs +0 -0
|
@@ -15126,6 +15126,19 @@ var OUTPUT_LIMIT = 8 * 1024 * 1024;
|
|
|
15126
15126
|
var KILL_GRACE_MS = 250;
|
|
15127
15127
|
var DRAIN_GRACE_MS = 500;
|
|
15128
15128
|
var REAP_GRACE_MS = 1e3;
|
|
15129
|
+
function killProcessTree(child, signal = "SIGKILL") {
|
|
15130
|
+
if (process.platform !== "win32" && child.pid) {
|
|
15131
|
+
try {
|
|
15132
|
+
process.kill(-child.pid, signal);
|
|
15133
|
+
return;
|
|
15134
|
+
} catch {
|
|
15135
|
+
}
|
|
15136
|
+
}
|
|
15137
|
+
try {
|
|
15138
|
+
child.kill(signal);
|
|
15139
|
+
} catch {
|
|
15140
|
+
}
|
|
15141
|
+
}
|
|
15129
15142
|
function defaultSpawn(command, args, options) {
|
|
15130
15143
|
if (options.signal?.aborted) {
|
|
15131
15144
|
return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
|
|
@@ -15162,11 +15175,7 @@ function defaultSpawn(command, args, options) {
|
|
|
15162
15175
|
resolve7({ stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), code, signal, started, observedChildExit, terminationRequested: stopping, ...error ? { error } : {} });
|
|
15163
15176
|
};
|
|
15164
15177
|
const kill = (requested) => {
|
|
15165
|
-
|
|
15166
|
-
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, requested);
|
|
15167
|
-
else child.kill(requested);
|
|
15168
|
-
} catch {
|
|
15169
|
-
}
|
|
15178
|
+
killProcessTree(child, requested);
|
|
15170
15179
|
};
|
|
15171
15180
|
const stop = (reason) => {
|
|
15172
15181
|
if (finished) return;
|
|
@@ -15493,7 +15502,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
|
|
|
15493
15502
|
var READ_ONLY_ONESHOT_ARGS = Object.freeze({
|
|
15494
15503
|
claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
|
|
15495
15504
|
codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
|
|
15496
|
-
grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"])
|
|
15505
|
+
grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
|
|
15506
|
+
agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
|
|
15507
|
+
kimi: Object.freeze(["--plan"])
|
|
15497
15508
|
});
|
|
15498
15509
|
function oneShotReadOnlyArgs(harness) {
|
|
15499
15510
|
return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
|
|
@@ -15547,7 +15558,7 @@ var BUILTIN_HARNESSES = Object.freeze([
|
|
|
15547
15558
|
authArgs: ["provider", "list"],
|
|
15548
15559
|
update: { self: ["upgrade"] },
|
|
15549
15560
|
oneShot: {
|
|
15550
|
-
argv: ["--output-format", "stream-json", "-p"],
|
|
15561
|
+
argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
|
|
15551
15562
|
promptVia: "arg",
|
|
15552
15563
|
outputFormat: "stream-json",
|
|
15553
15564
|
usageParser: parseKimiOneShotUsage
|
|
@@ -15610,7 +15621,7 @@ var BUILTIN_HARNESSES = Object.freeze([
|
|
|
15610
15621
|
authArgs: ["models"],
|
|
15611
15622
|
update: { self: ["update"] },
|
|
15612
15623
|
oneShot: {
|
|
15613
|
-
argv: ["--output-format", "json", "-p"],
|
|
15624
|
+
argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
|
|
15614
15625
|
promptVia: "arg",
|
|
15615
15626
|
outputFormat: "json",
|
|
15616
15627
|
usageParser: parseAgyOneShotUsage
|
|
@@ -16905,7 +16916,7 @@ function validateModelReferences(models, issues) {
|
|
|
16905
16916
|
};
|
|
16906
16917
|
for (const id of models.keys()) walk(id, []);
|
|
16907
16918
|
}
|
|
16908
|
-
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry) {
|
|
16919
|
+
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
|
|
16909
16920
|
const issues = [];
|
|
16910
16921
|
const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
|
|
16911
16922
|
const repositoryIds = /* @__PURE__ */ new Set();
|
|
@@ -16973,6 +16984,33 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
16973
16984
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
16974
16985
|
if (!workflows.has(defaultWorkflow)) issues.push(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
16975
16986
|
for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
|
|
16987
|
+
if (projectRoot) {
|
|
16988
|
+
const writerRolePath = join(projectRoot, ".kxm", "roles", "writer.yaml");
|
|
16989
|
+
const implementerAgent = agents.get("implementer") ?? agents.get("writer");
|
|
16990
|
+
if (existsSync2(writerRolePath) && implementerAgent) {
|
|
16991
|
+
try {
|
|
16992
|
+
const rawRole = parseRestrictedYaml2(readFileSync(writerRolePath, "utf8"));
|
|
16993
|
+
const roleObj = objectValue(rawRole);
|
|
16994
|
+
const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
|
|
16995
|
+
const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
|
|
16996
|
+
const agentModelObj = objectValue(implementerAgent.value.model);
|
|
16997
|
+
const agentModelStr = stringValue(implementerAgent.value.model);
|
|
16998
|
+
const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
|
|
16999
|
+
const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
|
|
17000
|
+
const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
|
|
17001
|
+
if (enabledRosterModels.length > 0 && canonicalAgentModel) {
|
|
17002
|
+
const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
|
|
17003
|
+
if (!matches) {
|
|
17004
|
+
issues.push(issue2("semantic", "role_roster_conflicts_with_agent", ".kxm/roles/writer.yaml", `role roster in .kxm/roles/writer.yaml does not include agent model ${canonicalAgentModel} from ${implementerAgent.logicalPath}`));
|
|
17005
|
+
}
|
|
17006
|
+
}
|
|
17007
|
+
} catch (error) {
|
|
17008
|
+
if (error instanceof VnextConfigError) {
|
|
17009
|
+
issues.push(...error.issues);
|
|
17010
|
+
}
|
|
17011
|
+
}
|
|
17012
|
+
}
|
|
17013
|
+
}
|
|
16976
17014
|
return sortIssues2(issues);
|
|
16977
17015
|
}
|
|
16978
17016
|
function vnextCanonicalJson(value) {
|
|
@@ -17153,7 +17191,7 @@ function loadVnextProject(projectRoot, options = {}) {
|
|
|
17153
17191
|
}
|
|
17154
17192
|
}
|
|
17155
17193
|
if (loadIssues.length > 0) throw new VnextConfigError(loadIssues);
|
|
17156
|
-
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry);
|
|
17194
|
+
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
|
|
17157
17195
|
if (issues.length > 0) throw new VnextConfigError(issues);
|
|
17158
17196
|
const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
|
|
17159
17197
|
return {
|
|
@@ -17265,7 +17303,47 @@ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:pa
|
|
|
17265
17303
|
|
|
17266
17304
|
// plugins/kxm/src/vnext-bindings.ts
|
|
17267
17305
|
import { homedir } from "node:os";
|
|
17268
|
-
|
|
17306
|
+
|
|
17307
|
+
// plugins/kxm/src/sqlite.ts
|
|
17308
|
+
import { createRequire } from "node:module";
|
|
17309
|
+
var requireFromHere = createRequire(import.meta.url);
|
|
17310
|
+
function loadNative() {
|
|
17311
|
+
try {
|
|
17312
|
+
const mod = requireFromHere("node:sqlite");
|
|
17313
|
+
if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
|
|
17314
|
+
} catch {
|
|
17315
|
+
}
|
|
17316
|
+
try {
|
|
17317
|
+
const mod = requireFromHere("bun:sqlite");
|
|
17318
|
+
const Ctor = mod?.DatabaseSync ?? mod?.Database;
|
|
17319
|
+
if (Ctor) return { Ctor, bun: true };
|
|
17320
|
+
} catch {
|
|
17321
|
+
}
|
|
17322
|
+
throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
|
|
17323
|
+
}
|
|
17324
|
+
var native = loadNative();
|
|
17325
|
+
var DatabaseSync = class {
|
|
17326
|
+
inner;
|
|
17327
|
+
constructor(path, options) {
|
|
17328
|
+
let normalized = options;
|
|
17329
|
+
if (native.bun && options) {
|
|
17330
|
+
const { readOnly, ...rest } = options;
|
|
17331
|
+
normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
|
|
17332
|
+
}
|
|
17333
|
+
this.inner = normalized === void 0 ? new native.Ctor(path) : new native.Ctor(path, normalized);
|
|
17334
|
+
}
|
|
17335
|
+
prepare(sql) {
|
|
17336
|
+
return this.inner.prepare(sql);
|
|
17337
|
+
}
|
|
17338
|
+
exec(sql) {
|
|
17339
|
+
return this.inner.exec(sql);
|
|
17340
|
+
}
|
|
17341
|
+
close() {
|
|
17342
|
+
this.inner.close();
|
|
17343
|
+
}
|
|
17344
|
+
};
|
|
17345
|
+
|
|
17346
|
+
// plugins/kxm/src/vnext-bindings.ts
|
|
17269
17347
|
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, parse, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
17270
17348
|
var MAX_BINDING_RECORD_BYTES = 256 * 1024;
|
|
17271
17349
|
var BINDING_LABEL = "Runtime-local repository bindings";
|
|
@@ -17308,7 +17386,6 @@ import {
|
|
|
17308
17386
|
writeFileSync
|
|
17309
17387
|
} from "node:fs";
|
|
17310
17388
|
import { basename as basename2, dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
|
|
17311
|
-
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
17312
17389
|
function databaseError(code, file, message) {
|
|
17313
17390
|
const issue3 = { phase: "semantic", code, file, message };
|
|
17314
17391
|
return new VnextConfigError([issue3]);
|
|
@@ -17388,7 +17465,7 @@ function openDatabase(file, description, spec) {
|
|
|
17388
17465
|
}
|
|
17389
17466
|
}
|
|
17390
17467
|
}
|
|
17391
|
-
const database = new
|
|
17468
|
+
const database = new DatabaseSync(file);
|
|
17392
17469
|
let transaction = false;
|
|
17393
17470
|
try {
|
|
17394
17471
|
database.exec(`PRAGMA busy_timeout = ${spec.timeoutMs ?? 5e3}`);
|
|
@@ -22465,6 +22542,32 @@ function evaluateArtifactsGate(context, definition) {
|
|
|
22465
22542
|
// plugins/kxm/src/vnext-engine-command.ts
|
|
22466
22543
|
import { spawn as spawn2 } from "node:child_process";
|
|
22467
22544
|
import { createHash as createHash8 } from "node:crypto";
|
|
22545
|
+
|
|
22546
|
+
// plugins/kxm/src/safety-integrity.ts
|
|
22547
|
+
var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
|
|
22548
|
+
/^\s*rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f/i,
|
|
22549
|
+
/^\s*rm\s+.*-[a-zA-Z]*f[a-zA-Z]*r/i,
|
|
22550
|
+
/^\s*rm\s+.*(-[a-zA-Z]*r[a-zA-Z]*\s+.*-[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*\s+.*-[a-zA-Z]*r[a-zA-Z]*)/i,
|
|
22551
|
+
/^\s*git\s+reset\s+--hard/i,
|
|
22552
|
+
/^\s*git\s+clean\s+-[a-zA-Z]*f/i,
|
|
22553
|
+
/^\s*git\s+checkout\s+--\s+/i,
|
|
22554
|
+
/^\s*git\s+restore\s+(\.|\*|--staged\s+(\.|\*))/i
|
|
22555
|
+
]);
|
|
22556
|
+
function assertCommandSeatbelt(command) {
|
|
22557
|
+
for (const pattern of DESTRUCTIVE_COMMAND_PATTERNS) {
|
|
22558
|
+
if (pattern.test(command)) {
|
|
22559
|
+
throw new Error(
|
|
22560
|
+
`Command blocked by KXM safety seatbelt: "${command}". Destructive workspace mutations require explicit operator override.`
|
|
22561
|
+
);
|
|
22562
|
+
}
|
|
22563
|
+
}
|
|
22564
|
+
}
|
|
22565
|
+
var INSECURE_SSH_HOST_KEY_PATTERNS = Object.freeze([
|
|
22566
|
+
/StrictHostKeyChecking=(accept-new|no|off)/i,
|
|
22567
|
+
/UserKnownHostsFile=\/dev\/null/i
|
|
22568
|
+
]);
|
|
22569
|
+
|
|
22570
|
+
// plugins/kxm/src/vnext-engine-command.ts
|
|
22468
22571
|
var MAX_DIRECT_TIMER_MS = 2147483647;
|
|
22469
22572
|
var COMMAND_TERM_GRACE_MS = 2e3;
|
|
22470
22573
|
var COMMAND_FINAL_WAIT_MS = 2e3;
|
|
@@ -22581,6 +22684,7 @@ var CommandObserver = class {
|
|
|
22581
22684
|
if (!Number.isInteger(this.definition.timeoutMs) || this.definition.timeoutMs < 1 || this.definition.timeoutMs > MAX_DIRECT_TIMER_MS) {
|
|
22582
22685
|
throw runtimeError("run_events_illegal", "command", "command timeoutMs exceeds the direct timer bound");
|
|
22583
22686
|
}
|
|
22687
|
+
assertCommandSeatbelt(this.definition.argv.join(" "));
|
|
22584
22688
|
this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22585
22689
|
let child;
|
|
22586
22690
|
try {
|
|
@@ -23706,6 +23810,7 @@ async function stepLocked(context, runId, producer, token) {
|
|
|
23706
23810
|
return drivePanel(context, prepared.panel, producer);
|
|
23707
23811
|
}
|
|
23708
23812
|
async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
23813
|
+
assertCommandSeatbelt(definition.argv.join(" "));
|
|
23709
23814
|
const storePath = context.eventStore.path;
|
|
23710
23815
|
const observer = createCommandObserver({
|
|
23711
23816
|
definition,
|
|
@@ -25483,6 +25588,24 @@ function createVnextOneShotProducer(options = {}) {
|
|
|
25483
25588
|
"--output-format",
|
|
25484
25589
|
"json"
|
|
25485
25590
|
];
|
|
25591
|
+
} else if (harness === "agy") {
|
|
25592
|
+
args = [
|
|
25593
|
+
"-m",
|
|
25594
|
+
resolved.model,
|
|
25595
|
+
...permissionArgs,
|
|
25596
|
+
"--output-format",
|
|
25597
|
+
"json",
|
|
25598
|
+
"-p"
|
|
25599
|
+
];
|
|
25600
|
+
} else if (harness === "kimi") {
|
|
25601
|
+
args = [
|
|
25602
|
+
"-m",
|
|
25603
|
+
resolved.model,
|
|
25604
|
+
...permissionArgs,
|
|
25605
|
+
"--output-format",
|
|
25606
|
+
"stream-json",
|
|
25607
|
+
"-p"
|
|
25608
|
+
];
|
|
25486
25609
|
} else {
|
|
25487
25610
|
throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
|
|
25488
25611
|
}
|
package/plugins/kxm/package.json
CHANGED
|
@@ -23,6 +23,15 @@ or backup subcommands.
|
|
|
23
23
|
|
|
24
24
|
Durable hub SQLite default is `.kxm/state/kxm.db` (`KXM_DATA_PATH`).
|
|
25
25
|
|
|
26
|
+
`kxm hub start` requires no token setup on a fresh machine: when
|
|
27
|
+
`KXM_AUTH_TOKEN` is unset, a long random admin token is generated once and
|
|
28
|
+
persisted in `hub-env.json` (schema `kxm.hub-env.v1`, `0600`) under the user
|
|
29
|
+
state root, then reused by every restart, worker, and dashboard. Explicit
|
|
30
|
+
`KXM_AUTH_TOKEN` / `KXM_PROJECT_TOKENS` values win and are persisted too.
|
|
31
|
+
Hub PID claims record the wrapper and server child PID; a dead wrapper's
|
|
32
|
+
claim is reclaimed automatically, an orphaned server is terminated first,
|
|
33
|
+
and `kxm hub stop` recovers such orphans directly.
|
|
34
|
+
|
|
26
35
|
```bash
|
|
27
36
|
kxm hub start
|
|
28
37
|
kxm hub view --json
|
|
@@ -21,15 +21,22 @@ invent `force`, domain-trust, or extra migrate verbs.
|
|
|
21
21
|
| `kxm config get <key>` | Get a configuration value | `--json` |
|
|
22
22
|
| `kxm config set <key> <value>` | Set a configuration value | `--scope user\|project` |
|
|
23
23
|
| `kxm config list` | List resolved configuration | `--json` |
|
|
24
|
-
| `kxm completion
|
|
24
|
+
| `kxm completion install` | Install tab completion for the detected shell and ensure kxm is on `PATH` | `--shell <bash\|zsh\|fish>`, `--no-path`, `--dry-run`, `--json` |
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
kxm init --dry-run --json
|
|
28
28
|
kxm migrate plan --json
|
|
29
29
|
kxm trust diff --base HEAD --json
|
|
30
30
|
kxm config list --json
|
|
31
|
-
kxm completion
|
|
31
|
+
kxm completion install
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
`kxm completion install` detects the shell from `$SHELL`, writes the
|
|
35
|
+
completion script under the user config directory, appends one idempotent
|
|
36
|
+
stanza to the shell rc file, and adds the kxm bin directory to `PATH` when
|
|
37
|
+
missing. `kxm completion <shell>` (generate only) remains available for
|
|
38
|
+
manual setup. After `kxm init` succeeds in an interactive terminal, kxm
|
|
39
|
+
offers the same install once per shell.
|
|
40
|
+
|
|
34
41
|
`kxm trust` reviews configuration permission diffs; it does not add website
|
|
35
42
|
domains. `kxm init` is project-only and does not start the hub.
|
|
@@ -55,7 +55,7 @@ const SUBCOMMANDS: Record<string, string[]> = {
|
|
|
55
55
|
goal: ["create", "list", "get"],
|
|
56
56
|
task: ["create", "list", "get", "run", "sync"],
|
|
57
57
|
studio: ["layout", "serve"],
|
|
58
|
-
role: ["list", "get", "add", "remove", "modify"],
|
|
58
|
+
role: ["list", "get", "add", "remove", "modify", "hosts", "set-host", "resume"],
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
export function generateShellCompletion(shell: SupportedShell): string {
|