@kontextmind/kxm 0.7.0 → 0.7.10

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.
Files changed (94) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/roles/writer.yaml +2 -0
  3. package/docs/README.md +3 -0
  4. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
  5. package/docs/agent-skills.md +19 -2
  6. package/docs/browser-automation.md +116 -0
  7. package/docs/configuration.md +10 -1
  8. package/docs/getting-started.md +21 -0
  9. package/docs/kb/how-credentials-retrieved-safely.md +31 -0
  10. package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
  11. package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
  12. package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
  13. package/docs/kb/how-to-resume-after-mfa.md +28 -0
  14. package/docs/kb/how-to-take-over-session.md +32 -0
  15. package/docs/kb/why-authentication-disappeared.md +32 -0
  16. package/docs/kb/why-automation-opened-different-browser.md +32 -0
  17. package/docs/kb/why-session-viewer-cannot-control.md +31 -0
  18. package/docs/operations.md +24 -0
  19. package/docs/prompts/browser-annotate-feedback.md +41 -0
  20. package/docs/prompts/browser-diagnose-recover.md +38 -0
  21. package/docs/prompts/browser-explore.md +42 -0
  22. package/docs/prompts/browser-repro-fix.md +48 -0
  23. package/docs/prompts/browser-start.md +41 -0
  24. package/docs/prompts/browser-takeover.md +50 -0
  25. package/docs/skills/repo-work-delivery.md +5 -0
  26. package/docs/skills.md +2 -0
  27. package/docs/troubleshooting.md +22 -1
  28. package/package.json +1 -1
  29. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  30. package/plugins/kxm/dist/cli.js +41203 -38364
  31. package/plugins/kxm/dist/core.js +57 -0
  32. package/plugins/kxm/dist/extension.js +40 -3
  33. package/plugins/kxm/dist/mcp-server.js +1 -1
  34. package/plugins/kxm/dist/runtime.js +1582 -81
  35. package/plugins/kxm/dist/server.js +129 -4
  36. package/plugins/kxm/dist/vnext-runtime-supervisor.js +221 -41
  37. package/plugins/kxm/package.json +1 -1
  38. package/plugins/kxm/skills/hints.json +30 -0
  39. package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
  40. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
  41. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
  42. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
  43. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
  44. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
  45. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
  46. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +9 -0
  47. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +9 -2
  48. package/plugins/kxm/src/autocomplete.ts +1 -1
  49. package/plugins/kxm/src/browser.ts +603 -0
  50. package/plugins/kxm/src/cli/context-skills.ts +373 -0
  51. package/plugins/kxm/src/cli/hub.ts +614 -0
  52. package/plugins/kxm/src/cli/roles.ts +615 -0
  53. package/plugins/kxm/src/cli/system.ts +906 -0
  54. package/plugins/kxm/src/cli/tasks.ts +364 -0
  55. package/plugins/kxm/src/cli/types.ts +270 -0
  56. package/plugins/kxm/src/cli/vnext.ts +698 -0
  57. package/plugins/kxm/src/cli/workflows.ts +699 -0
  58. package/plugins/kxm/src/cli.ts +238 -3791
  59. package/plugins/kxm/src/completion-install.ts +223 -0
  60. package/plugins/kxm/src/database.ts +1 -1
  61. package/plugins/kxm/src/external-effects.ts +1 -1
  62. package/plugins/kxm/src/hub-env.ts +193 -0
  63. package/plugins/kxm/src/init-guide-setup.ts +547 -0
  64. package/plugins/kxm/src/local-snapshot.ts +1 -1
  65. package/plugins/kxm/src/mcp-server.ts +1 -1
  66. package/plugins/kxm/src/model-inventory.ts +8 -8
  67. package/plugins/kxm/src/modes.ts +348 -0
  68. package/plugins/kxm/src/protocol.ts +111 -0
  69. package/plugins/kxm/src/role.ts +335 -0
  70. package/plugins/kxm/src/runtime.ts +4 -0
  71. package/plugins/kxm/src/safety-integrity.ts +76 -0
  72. package/plugins/kxm/src/sqlite.ts +76 -0
  73. package/plugins/kxm/src/ssh-remote.ts +560 -0
  74. package/plugins/kxm/src/store.ts +1 -1
  75. package/plugins/kxm/src/subagent-control.ts +312 -0
  76. package/plugins/kxm/src/vnext-bindings.ts +1 -1
  77. package/plugins/kxm/src/vnext-config.ts +38 -1
  78. package/plugins/kxm/src/vnext-engine-command.ts +2 -0
  79. package/plugins/kxm/src/vnext-engine.ts +16 -0
  80. package/plugins/kxm/src/vnext-harness.ts +92 -22
  81. package/plugins/kxm/src/vnext-oneshot-evidence.ts +39 -7
  82. package/plugins/kxm/src/vnext-oneshot-process.ts +46 -8
  83. package/plugins/kxm/src/vnext-oneshot-producer.ts +22 -1
  84. package/plugins/kxm/src/vnext-pi-producer.ts +11 -7
  85. package/plugins/kxm/src/vnext-runtime-store.ts +1 -1
  86. package/plugins/kxm/src/vnext-runtime-supervisor.ts +5 -3
  87. package/plugins/kxm/src/workflow-tui.ts +1 -1
  88. package/plugins/kxm/src/workflow.ts +144 -0
  89. package/schemas/vnext/modes.schema.json +56 -0
  90. package/scripts/kxm-bump-version.mjs +146 -0
  91. package/scripts/kxm-hub.mjs +145 -3
  92. package/scripts/kxm-publish-npm.mjs +3 -1
  93. package/scripts/kxm-release-github.mjs +3 -1
  94. package/scripts/kxm.mjs +0 -0
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Bump the package version across all tracked surfaces.
5
+ *
6
+ * Usage:
7
+ * node scripts/kxm-bump-version.mjs <new-version> # explicit version
8
+ * node scripts/kxm-bump-version.mjs patch # bump patch
9
+ * node scripts/kxm-bump-version.mjs minor # bump minor
10
+ * node scripts/kxm-bump-version.mjs major # bump major
11
+ *
12
+ * Surfaces updated (must stay in sync with scripts/check-versions.mjs):
13
+ * 1. package.json .version
14
+ * 2. package-lock.json .version + .packages[""].version
15
+ * 3. plugins/kxm/package.json .version
16
+ * 4. plugins/kxm/.claude-plugin/plugin.json .version
17
+ * 5. .claude-plugin/marketplace.json .plugins[name=kxm].version
18
+ * 6. plugins/kxm/src/mcp-server.ts const VERSION = "..."
19
+ * 7. plugins/kxm/dist/mcp-server.js const VERSION = "..." (rebuilt)
20
+ *
21
+ * After running this script, run `npm run build` to rebuild dist bundles.
22
+ */
23
+
24
+ import { readFileSync, writeFileSync } from "node:fs";
25
+ import { resolve } from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+
28
+ const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
29
+
30
+ export function parseSemver(version) {
31
+ const match = SEMVER_RE.exec(version);
32
+ if (!match) return undefined;
33
+ return {
34
+ major: Number(match[1]),
35
+ minor: Number(match[2]),
36
+ patch: Number(match[3]),
37
+ prerelease: match[4],
38
+ build: match[5],
39
+ };
40
+ }
41
+
42
+ export function bumpVersion(current, kind) {
43
+ const parsed = parseSemver(current);
44
+ if (!parsed) throw new Error(`invalid current version: ${current}`);
45
+ switch (kind) {
46
+ case "major":
47
+ return `${parsed.major + 1}.0.0`;
48
+ case "minor":
49
+ return `${parsed.major}.${parsed.minor + 1}.0`;
50
+ case "patch":
51
+ return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
52
+ default:
53
+ throw new Error(`unknown bump kind: ${kind} (use major, minor, or patch)`);
54
+ }
55
+ }
56
+
57
+ /** Replace a version string in a JSON file using targeted string patching. */
58
+ function patchJsonVersion(filePath, oldVersion, newVersion) {
59
+ const raw = readFileSync(filePath, "utf8");
60
+ const escaped = oldVersion.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
61
+ const pattern = new RegExp(`("version"\\s*:\\s*)"${escaped}"`, "g");
62
+ const patched = raw.replace(pattern, `$1"${newVersion}"`);
63
+ if (patched === raw) {
64
+ throw new Error(`no version field found to patch in ${filePath}`);
65
+ }
66
+ writeFileSync(filePath, patched, "utf8");
67
+ }
68
+
69
+ /** Replace a version string in a TypeScript/JavaScript source file. */
70
+ function patchSourceVersion(filePath, newVersion) {
71
+ const raw = readFileSync(filePath, "utf8");
72
+ const patched = raw.replace(
73
+ /const VERSION = "[^"]+"/,
74
+ `const VERSION = "${newVersion}"`,
75
+ );
76
+ if (patched === raw) {
77
+ throw new Error(`no VERSION constant found in ${filePath}`);
78
+ }
79
+ writeFileSync(filePath, patched, "utf8");
80
+ }
81
+
82
+ export function applyVersionBump(root, newVersion) {
83
+ if (!parseSemver(newVersion)) {
84
+ throw new Error(`invalid semver: ${newVersion}`);
85
+ }
86
+
87
+ // Read current version
88
+ const pkgPath = resolve(root, "package.json");
89
+ const oldVersion = JSON.parse(readFileSync(pkgPath, "utf8")).version;
90
+
91
+ // JSON surfaces (targeted patch preserves formatting)
92
+ const jsonFiles = [
93
+ "package.json",
94
+ "package-lock.json",
95
+ "plugins/kxm/package.json",
96
+ "plugins/kxm/.claude-plugin/plugin.json",
97
+ ".claude-plugin/marketplace.json",
98
+ ];
99
+ for (const rel of jsonFiles) {
100
+ patchJsonVersion(resolve(root, rel), oldVersion, newVersion);
101
+ }
102
+
103
+ // TypeScript source
104
+ patchSourceVersion(resolve(root, "plugins/kxm/src/mcp-server.ts"), newVersion);
105
+
106
+ // Generated dist (update in place if it exists; check:generated rebuilds anyway)
107
+ const distPath = resolve(root, "plugins/kxm/dist/mcp-server.js");
108
+ try {
109
+ patchSourceVersion(distPath, newVersion);
110
+ } catch {
111
+ // dist may not exist yet; will be rebuilt by npm run build
112
+ }
113
+
114
+ return { oldVersion, newVersion };
115
+ }
116
+
117
+ export function main(argv = process.argv.slice(2), root = process.cwd()) {
118
+ const arg = argv[0];
119
+ if (!arg) {
120
+ process.stderr.write("usage: node scripts/kxm-bump-version.mjs <version|patch|minor|major>\n");
121
+ return 1;
122
+ }
123
+
124
+ const pkgPath = resolve(root, "package.json");
125
+ const currentVersion = JSON.parse(readFileSync(pkgPath, "utf8")).version;
126
+
127
+ let newVersion;
128
+ if (SEMVER_RE.test(arg)) {
129
+ newVersion = arg;
130
+ } else {
131
+ newVersion = bumpVersion(currentVersion, arg);
132
+ }
133
+
134
+ if (newVersion === currentVersion) {
135
+ process.stdout.write(`version unchanged: ${currentVersion}\n`);
136
+ return 0;
137
+ }
138
+
139
+ const result = applyVersionBump(root, newVersion);
140
+ process.stdout.write(`bumped ${result.oldVersion} -> ${result.newVersion}\n`);
141
+ return 0;
142
+ }
143
+
144
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
145
+ process.exitCode = main();
146
+ }
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn, spawnSync } from "node:child_process";
4
- import { closeSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { randomBytes } from "node:crypto";
5
+ import { closeSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
5
7
  import { join, resolve } from "node:path";
6
8
  import { fileURLToPath } from "node:url";
7
9
 
@@ -15,6 +17,19 @@ const startedAt = new Date().toISOString();
15
17
  const controlFile = "hub.stop";
16
18
  const controlPath = join(stateDir, controlFile);
17
19
 
20
+ // --- Persistent hub credentials (kxm.hub-env.v1) -------------------------
21
+ // Precedence: KXM_AUTH_TOKEN/KXM_PROJECT_TOKENS env, then the persisted
22
+ // user-state file, then a generated admin token that is persisted so later
23
+ // restarts, workers, and dashboards reuse the same credential.
24
+ function resolveUserStateRoot() {
25
+ const explicit = process.env.KXM_STATE_HOME?.trim();
26
+ if (explicit) return resolve(explicit);
27
+ const home = homedir();
28
+ if (process.platform === "win32") return resolve(process.env.LOCALAPPDATA?.trim() || join(home, "AppData", "Local"), "KXM");
29
+ if (process.platform === "darwin") return resolve(home, "Library", "Application Support", "KXM");
30
+ return resolve(process.env.XDG_STATE_HOME?.trim() || join(home, ".local", "state"), "kxm");
31
+ }
32
+
18
33
  function processExists(pid) {
19
34
  try {
20
35
  process.kill(pid, 0);
@@ -24,6 +39,119 @@ function processExists(pid) {
24
39
  }
25
40
  }
26
41
 
42
+ function parseHubEnvFile() {
43
+ const file = join(resolveUserStateRoot(), "hub-env.json");
44
+ let raw;
45
+ try {
46
+ raw = readFileSync(file, "utf8");
47
+ } catch (error) {
48
+ if (error?.code === "ENOENT") return { file, record: undefined };
49
+ throw new Error(`KXM hub could not read the persisted hub env at ${file}: ${error.message}`);
50
+ }
51
+ let parsed;
52
+ try {
53
+ parsed = JSON.parse(raw);
54
+ } catch (error) {
55
+ throw new Error(`KXM hub env file is malformed at ${file}; fix or remove it (it holds only KXM_AUTH_TOKEN/KXM_PROJECT_TOKENS values): ${error.message}`);
56
+ }
57
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.schema !== "kxm.hub-env.v1") {
58
+ throw new Error(`KXM hub env file at ${file} does not use schema kxm.hub-env.v1`);
59
+ }
60
+ return { file, record: parsed };
61
+ }
62
+
63
+ function generateAdminToken() {
64
+ return `kxm_admin_${randomBytes(24).toString("base64url")}`;
65
+ }
66
+
67
+ function persistHubEnv(file, record) {
68
+ mkdirSync(resolve(file, ".."), { recursive: true });
69
+ const temporary = join(resolve(file, ".."), `.hub-env-${process.pid}.tmp`);
70
+ writeFileSync(temporary, `${JSON.stringify(record, undefined, 2)}\n`, { encoding: "utf8", mode: 0o600 });
71
+ try {
72
+ renameSync(temporary, file);
73
+ } catch (error) {
74
+ rmSync(temporary, { force: true });
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ function parseProjectTokensEnv(raw) {
80
+ const trimmed = raw?.trim();
81
+ if (!trimmed) return undefined;
82
+ let parsed;
83
+ try {
84
+ parsed = JSON.parse(trimmed);
85
+ } catch {
86
+ throw new Error("KXM_PROJECT_TOKENS must be a JSON object of project names to tokens");
87
+ }
88
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
89
+ throw new Error("KXM_PROJECT_TOKENS must be a JSON object of project names to tokens");
90
+ }
91
+ for (const [project, token] of Object.entries(parsed)) {
92
+ if (!project.trim() || typeof token !== "string" || !token.trim()) {
93
+ throw new Error("KXM_PROJECT_TOKENS must contain non-empty project names and token strings");
94
+ }
95
+ }
96
+ return parsed;
97
+ }
98
+
99
+ /** Resolve credentials, persisting generated/env values into the user-state file. */
100
+ function resolveCredentials() {
101
+ const { file, record } = parseHubEnvFile();
102
+ const envToken = process.env.KXM_AUTH_TOKEN?.trim();
103
+ const envProjectTokens = parseProjectTokensEnv(process.env.KXM_PROJECT_TOKENS);
104
+ let authToken = envToken || record?.authToken;
105
+ let authTokenSource = envToken ? "env" : record?.authToken ? "file" : "none";
106
+ if (!authToken) {
107
+ authToken = generateAdminToken();
108
+ authTokenSource = "generated";
109
+ }
110
+ const projectTokens = envProjectTokens || record?.projectTokens;
111
+ const projectTokensSource = envProjectTokens ? "env" : record?.projectTokens ? "file" : "none";
112
+ const envChanged = (authTokenSource === "env" && record?.authToken !== authToken)
113
+ || (projectTokensSource === "env" && JSON.stringify(record?.projectTokens ?? undefined) !== JSON.stringify(projectTokens ?? undefined));
114
+ if (authTokenSource === "generated" || envChanged) {
115
+ persistHubEnv(file, {
116
+ schema: "kxm.hub-env.v1",
117
+ createdAt: record?.createdAt || new Date().toISOString(),
118
+ ...(authToken ? { authToken } : {}),
119
+ ...(projectTokens ? { projectTokens } : {}),
120
+ });
121
+ }
122
+ return { authToken, authTokenSource, projectTokens, projectTokensSource, file };
123
+ }
124
+
125
+ let credentials;
126
+ try {
127
+ credentials = resolveCredentials();
128
+ } catch (error) {
129
+ process.stderr.write(`kxm hub: ${error.message}\n`);
130
+ process.exit(1);
131
+ }
132
+ const launchEnv = { ...process.env };
133
+ if (credentials.authToken) launchEnv.KXM_AUTH_TOKEN = credentials.authToken;
134
+ if (credentials.projectTokens) launchEnv.KXM_PROJECT_TOKENS = JSON.stringify(credentials.projectTokens);
135
+ if (credentials.authTokenSource !== "env") {
136
+ process.stdout.write(`kxm hub: using ${credentials.authTokenSource === "generated" ? "newly generated" : "persisted"} KXM_AUTH_TOKEN from ${credentials.file}\n`);
137
+ }
138
+
139
+ function terminateTree(pid) {
140
+ // Best-effort cleanup of an orphaned hub server whose wrapper died
141
+ // without reclaiming (e.g. SIGKILL). The wrapper could not be stopped
142
+ // gracefully in that state, so the server is asked to shut down first and
143
+ // forced after a short grace period.
144
+ if (!Number.isInteger(pid) || pid <= 0) return;
145
+ try { process.kill(pid, "SIGTERM"); } catch { /* already gone */ }
146
+ const deadline = Date.now() + 3000;
147
+ while (Date.now() < deadline) {
148
+ if (!processExists(pid)) return;
149
+ const wait = Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
150
+ if (wait !== "timed-out") return;
151
+ }
152
+ try { process.kill(pid, "SIGKILL"); } catch { /* already gone */ }
153
+ }
154
+
27
155
  function claimPidFile() {
28
156
  const record = { version: 1, pid: process.pid, role: "hub", startedAt, controlFile };
29
157
  for (let attempt = 0; attempt < 3; attempt += 1) {
@@ -50,9 +178,16 @@ function claimPidFile() {
50
178
  if (existing?.version === 1 && existing.role === "hub" && Number.isInteger(existing.pid) && processExists(existing.pid)) {
51
179
  throw new Error(`KXM hub is already managed by PID ${existing.pid}`);
52
180
  }
53
- // A well-formed claim whose owner is dead is safe to reclaim. Keep
181
+ // A well-formed claim whose wrapper is dead is safe to reclaim. Keep
54
182
  // malformed claims fail-closed because they may belong to another tool.
55
183
  if (existing?.version === 1 && existing.role === "hub" && Number.isInteger(existing.pid) && !processExists(existing.pid)) {
184
+ // The wrapper is gone. An orphaned server child recorded by the dead
185
+ // wrapper is cleaned up before reclaiming so a SIGKILLed wrapper does
186
+ // not leave a port-holding server behind.
187
+ if (Number.isInteger(existing.serverPid) && existing.serverPid !== existing.pid && processExists(existing.serverPid)) {
188
+ process.stderr.write(`kxm hub: cleaning up orphaned hub server PID ${existing.serverPid} from a dead wrapper\n`);
189
+ terminateTree(existing.serverPid);
190
+ }
56
191
  rmSync(pidPath, { force: true });
57
192
  continue;
58
193
  }
@@ -79,8 +214,15 @@ const child = spawn(process.execPath, [
79
214
  ...process.argv.slice(2),
80
215
  ], {
81
216
  stdio: ["inherit", "inherit", "inherit", "ipc"],
82
- env: process.env,
217
+ env: launchEnv,
83
218
  });
219
+ // Record the server child PID inside the claim so a later wrapper can clean
220
+ // up an orphaned server if this wrapper is killed without running cleanupPid.
221
+ if (child.pid) {
222
+ try {
223
+ writeFileSync(pidPath, `${JSON.stringify({ version: 1, pid: process.pid, serverPid: child.pid, role: "hub", startedAt, controlFile })}\n`, "utf8");
224
+ } catch { /* claim already written; serverPid tracking is best-effort */ }
225
+ }
84
226
  let stopping = false;
85
227
 
86
228
  child.once("error", (error) => {
@@ -275,7 +275,9 @@ export async function main(env = process.env, stdout = process.stdout, stderr =
275
275
  const token = env.GITHUB_TOKEN || env.GH_TOKEN;
276
276
  const npmToken = env.NODE_AUTH_TOKEN || env.NPM_TOKEN;
277
277
  const repo = env.GITHUB_REPOSITORY;
278
- const tag = env.GITHUB_REF_NAME || env.TAG;
278
+ // KXM_RELEASE_TAG is explicit for workflow_dispatch runs; GITHUB_REF_NAME
279
+ // is the branch name for that event, not the requested release tag.
280
+ const tag = env.KXM_RELEASE_TAG || env.TAG || env.GITHUB_REF_NAME;
279
281
  const assetName = env.KXM_ASSET;
280
282
  const assetPath = env.KXM_ASSET_PATH;
281
283
  const declaredSha256 = env.KXM_ASSET_SHA256;
@@ -427,7 +427,9 @@ export function fileSha256(path) {
427
427
  export async function main(env = process.env, stdout = process.stdout, stderr = process.stderr) {
428
428
  const token = env.GITHUB_TOKEN || env.GH_TOKEN;
429
429
  const repo = env.GITHUB_REPOSITORY;
430
- const tag = env.GITHUB_REF_NAME;
430
+ // KXM_RELEASE_TAG is explicit for workflow_dispatch runs; GITHUB_REF_NAME
431
+ // is the branch name for that event, not the requested release tag.
432
+ const tag = env.KXM_RELEASE_TAG || env.TAG || env.GITHUB_REF_NAME;
431
433
  const assetName = env.KXM_ASSET;
432
434
  const assetPath = env.KXM_ASSET_PATH;
433
435
  const declared = env.KXM_ASSET_SHA256;
package/scripts/kxm.mjs CHANGED
File without changes