@kontextmind/kxm 0.6.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.kxm/agents/coordinator.yaml +9 -0
- package/.kxm/agents/critic-arch.yaml +13 -0
- package/.kxm/agents/critic-cli.yaml +13 -0
- package/.kxm/agents/implementer.yaml +13 -0
- package/.kxm/gates.yaml +8 -0
- package/.kxm/producers.yaml +22 -0
- package/.kxm/project.yaml +15 -0
- package/.kxm/roles/writer.yaml +7 -0
- package/.kxm/workflows/default.yaml +47 -0
- package/CHANGELOG.md +39 -7
- package/README.md +1 -0
- package/docs/README.md +5 -0
- package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
- package/docs/agent-skills.md +135 -0
- package/docs/architecture.md +1 -1
- package/docs/assignment-runner.md +21 -8
- package/docs/browser-automation.md +116 -0
- package/docs/configuration.md +11 -2
- package/docs/getting-started.md +21 -0
- package/docs/kb/how-credentials-retrieved-safely.md +31 -0
- package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
- package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
- package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
- package/docs/kb/how-to-resume-after-mfa.md +28 -0
- package/docs/kb/how-to-take-over-session.md +32 -0
- package/docs/kb/why-authentication-disappeared.md +32 -0
- package/docs/kb/why-automation-opened-different-browser.md +32 -0
- package/docs/kb/why-session-viewer-cannot-control.md +31 -0
- package/docs/kxm-handbook.md +3 -3
- package/docs/operations.md +24 -0
- package/docs/operator-pi-packages.md +63 -0
- package/docs/prompts/browser-annotate-feedback.md +41 -0
- package/docs/prompts/browser-diagnose-recover.md +38 -0
- package/docs/prompts/browser-explore.md +42 -0
- package/docs/prompts/browser-repro-fix.md +48 -0
- package/docs/prompts/browser-start.md +41 -0
- package/docs/prompts/browser-takeover.md +50 -0
- package/docs/skills/repo-work-delivery.md +107 -0
- package/docs/skills.md +2 -0
- package/docs/test-matrix.md +4 -3
- package/docs/troubleshooting.md +41 -1
- package/docs/vnext/validation.md +9 -0
- package/docs/webhook-workflows.md +2 -2
- package/examples/README.md +1 -1
- package/package.json +16 -17
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/README.md +1 -1
- package/plugins/kxm/dist/cli.js +41620 -35578
- package/plugins/kxm/dist/core.js +271 -34
- package/plugins/kxm/dist/extension.js +7759 -86
- package/plugins/kxm/dist/mcp-server.js +75 -21
- package/plugins/kxm/dist/runtime.js +8218 -2328
- package/plugins/kxm/dist/server.js +3125 -2260
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +5961 -661
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/SUITE.md +5 -0
- package/plugins/kxm/skills/hints.json +103 -0
- package/plugins/kxm/skills/kxm/SKILL.md +30 -83
- package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
- package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
- package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
- package/plugins/kxm/skills/kxm-context-memory/SKILL.md +69 -0
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +65 -0
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +34 -0
- package/plugins/kxm/skills/kxm-harvest/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-insights/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-mind/SKILL.md +59 -0
- package/plugins/kxm/skills/kxm-peer/SKILL.md +110 -0
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +42 -0
- package/plugins/kxm/skills/kxm-projects/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-protocol/SKILL.md +66 -0
- package/plugins/kxm/skills/kxm-query/SKILL.md +45 -0
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +30 -0
- package/plugins/kxm/skills/kxm-runs/SKILL.md +29 -0
- package/plugins/kxm/skills/kxm-setup/SKILL.md +55 -0
- package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +31 -0
- package/plugins/kxm/skills/kxm-tasks/SKILL.md +33 -0
- package/plugins/kxm/skills/kxm-triage/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-work/SKILL.md +44 -0
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +45 -0
- package/plugins/kxm/src/autocomplete.ts +9 -3
- package/plugins/kxm/src/browser.ts +603 -0
- package/plugins/kxm/src/cli/context-skills.ts +373 -0
- package/plugins/kxm/src/cli/hub.ts +614 -0
- package/plugins/kxm/src/cli/roles.ts +615 -0
- package/plugins/kxm/src/cli/system.ts +906 -0
- package/plugins/kxm/src/cli/tasks.ts +364 -0
- package/plugins/kxm/src/cli/types.ts +270 -0
- package/plugins/kxm/src/cli/vnext.ts +698 -0
- package/plugins/kxm/src/cli/workflows.ts +699 -0
- package/plugins/kxm/src/cli.ts +362 -2849
- package/plugins/kxm/src/commands.ts +150 -8
- package/plugins/kxm/src/completion-install.ts +223 -0
- package/plugins/kxm/src/config.ts +7 -4
- package/plugins/kxm/src/context-packet.ts +172 -0
- package/plugins/kxm/src/database.ts +1 -1
- package/plugins/kxm/src/extension.ts +36 -1
- package/plugins/kxm/src/external-effects.ts +357 -8
- package/plugins/kxm/src/hub-env.ts +193 -0
- package/plugins/kxm/src/hub.ts +2 -4
- package/plugins/kxm/src/improve.ts +72 -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/model-inventory.ts +127 -0
- package/plugins/kxm/src/modes.ts +348 -0
- package/plugins/kxm/src/policy-draft.d.mts +55 -0
- package/plugins/kxm/src/policy-draft.mjs +565 -0
- package/plugins/kxm/src/price-calc.ts +17 -18
- package/plugins/kxm/src/prices.ts +32 -16
- package/plugins/kxm/src/producers.ts +71 -0
- package/plugins/kxm/src/protocol.ts +111 -0
- package/plugins/kxm/src/restricted-yaml.d.mts +31 -0
- package/plugins/kxm/src/restricted-yaml.mjs +145 -0
- package/plugins/kxm/src/role.ts +710 -0
- package/plugins/kxm/src/routing.ts +99 -1
- package/plugins/kxm/src/runtime.ts +4 -0
- package/plugins/kxm/src/safety-integrity.ts +76 -0
- package/plugins/kxm/src/session-work.ts +9 -2
- package/plugins/kxm/src/sqlite.ts +76 -0
- package/plugins/kxm/src/ssh-remote.ts +560 -0
- package/plugins/kxm/src/store.ts +1 -1
- package/plugins/kxm/src/studio-layout.ts +660 -17
- package/plugins/kxm/src/subagent-control.ts +312 -0
- package/plugins/kxm/src/suggest.ts +7 -13
- package/plugins/kxm/src/telemetry.ts +82 -0
- package/plugins/kxm/src/tui.ts +140 -0
- package/plugins/kxm/src/vnext-bindings.ts +1 -1
- package/plugins/kxm/src/vnext-config.ts +53 -111
- package/plugins/kxm/src/vnext-engine-command.ts +2 -0
- package/plugins/kxm/src/vnext-engine.ts +214 -62
- package/plugins/kxm/src/vnext-harness.ts +336 -84
- package/plugins/kxm/src/vnext-oneshot-evidence.ts +117 -0
- package/plugins/kxm/src/vnext-oneshot-process.ts +187 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +182 -224
- package/plugins/kxm/src/vnext-pi-producer.ts +11 -7
- package/plugins/kxm/src/vnext-runtime-store.ts +36 -2
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +122 -5
- package/plugins/kxm/src/vnext-runtime.ts +14 -0
- package/plugins/kxm/src/workflow-manager.ts +392 -0
- package/plugins/kxm/src/workflow-tui.ts +255 -0
- package/plugins/kxm/src/workflow.ts +144 -0
- package/schemas/policy-draft/README.md +17 -0
- package/schemas/policy-draft/model.v2.schema.json +140 -0
- package/schemas/policy-draft/role.v2.schema.json +91 -0
- package/schemas/vnext/modes.schema.json +56 -0
- package/schemas/vnext/role.schema.json +76 -0
- package/schemas/vnext/run-event.schema.json +1 -0
- package/scripts/assignment-run.d.mts +1 -1
- package/scripts/assignment-run.mjs +44 -35
- package/scripts/check-generated.mjs +33 -9
- package/scripts/emit-codex-artifacts.mjs +255 -11
- package/scripts/harness-run.d.mts +12 -4
- package/scripts/harness-run.mjs +65 -17
- package/scripts/kxm-bump-version.mjs +146 -0
- package/scripts/kxm-hub.mjs +150 -2
- package/scripts/kxm-publish-npm.mjs +3 -1
- package/scripts/kxm-release-github.mjs +3 -1
- package/scripts/kxm.mjs +0 -0
- package/scripts/native-critic.d.mts +5 -0
- package/scripts/native-critic.mjs +60 -0
- package/.kxm/config/README.md +0 -5
- package/.kxm/config/agents.json +0 -43
- package/.kxm/config/env.example +0 -56
- package/.kxm/config/update.example.yaml +0 -9
- package/.kxm/config/workflows/fix.json +0 -160
- package/.kxm/config/workflows/jira-development.json +0 -116
- package/.kxm/config/workflows/provenance-quorum.json +0 -150
- package/.kxm/config/workflows/v04-dogfood.json +0 -72
|
@@ -532,9 +532,106 @@ export interface RoutingReport {
|
|
|
532
532
|
export interface GenerateRoutingReportOptions {
|
|
533
533
|
catalog?: PriceCatalog | undefined;
|
|
534
534
|
includeEquivalentListCost?: boolean | undefined;
|
|
535
|
+
halfLifeDays?: number | undefined;
|
|
535
536
|
now?: () => string;
|
|
536
537
|
}
|
|
537
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Calculates exponential decay weight for historical telemetry (Decision Q14).
|
|
541
|
+
* Weight w = 2^(-delta_t / halfLifeDays).
|
|
542
|
+
*/
|
|
543
|
+
export function computeDecayedWeight(
|
|
544
|
+
recordedAt: string | number,
|
|
545
|
+
halfLifeDays: number = 14,
|
|
546
|
+
now: number = Date.now(),
|
|
547
|
+
): number {
|
|
548
|
+
const ts = typeof recordedAt === "number" ? recordedAt : Date.parse(recordedAt);
|
|
549
|
+
if (!Number.isFinite(ts)) return 1.0;
|
|
550
|
+
const deltaMs = Math.max(0, now - ts);
|
|
551
|
+
const halfLifeMs = halfLifeDays * 24 * 60 * 60 * 1000;
|
|
552
|
+
return Math.pow(2, -deltaMs / halfLifeMs);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export interface RouteCircuitStatus {
|
|
556
|
+
status: "healthy" | "demoted" | "quarantined";
|
|
557
|
+
consecutiveFailures: number;
|
|
558
|
+
penaltyMultiplier: number;
|
|
559
|
+
reason?: string | undefined;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Evaluates route health against the circuit breaker policy (Decision Q13).
|
|
564
|
+
* Detects failure streaks in the observation window and applies soft demotion or quarantine.
|
|
565
|
+
*/
|
|
566
|
+
export function evaluateCircuitBreaker(
|
|
567
|
+
records: Array<RoutingRecord | RoutingRecordV2>,
|
|
568
|
+
route: { harness: string; model: string },
|
|
569
|
+
config?: {
|
|
570
|
+
mode?: "soft_demotion" | "quarantine" | undefined;
|
|
571
|
+
failureThreshold?: number | undefined;
|
|
572
|
+
windowSeconds?: number | undefined;
|
|
573
|
+
penaltyMultiplier?: number | undefined;
|
|
574
|
+
},
|
|
575
|
+
now: number = Date.now(),
|
|
576
|
+
): RouteCircuitStatus {
|
|
577
|
+
const mode = config?.mode ?? "soft_demotion";
|
|
578
|
+
const failureThreshold = config?.failureThreshold ?? 3;
|
|
579
|
+
const windowSeconds = config?.windowSeconds ?? 3600;
|
|
580
|
+
const penaltyMultiplier = config?.penaltyMultiplier ?? 5.0;
|
|
581
|
+
const windowMs = windowSeconds * 1000;
|
|
582
|
+
|
|
583
|
+
const matching = records.filter((r) => {
|
|
584
|
+
const isV2 = r.schema === ROUTING_RECORD_V2_SCHEMA;
|
|
585
|
+
const harness = isV2 ? (r as RoutingRecordV2).harness : (r.providerMetadata?.harness as string | undefined);
|
|
586
|
+
const model = isV2
|
|
587
|
+
? ((r as RoutingRecordV2).effectiveModel || (r as RoutingRecordV2).requestedModel)
|
|
588
|
+
: (r.effectiveModel || r.requestedModel);
|
|
589
|
+
if (harness !== route.harness || model !== route.model) return false;
|
|
590
|
+
|
|
591
|
+
const recAt = (r as any).recordedAt;
|
|
592
|
+
const ts = typeof recAt === "string" ? Date.parse(recAt) : 0;
|
|
593
|
+
return ts >= (now - windowMs);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
matching.sort((a, b) => {
|
|
597
|
+
const ta = typeof (a as any).recordedAt === "string" ? Date.parse((a as any).recordedAt) : 0;
|
|
598
|
+
const tb = typeof (b as any).recordedAt === "string" ? Date.parse((b as any).recordedAt) : 0;
|
|
599
|
+
return tb - ta;
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
let consecutiveFailures = 0;
|
|
603
|
+
for (const r of matching) {
|
|
604
|
+
const passed = r.verifierOutcome === "passed" || r.finalOutcome === "accepted" || r.finalOutcome === "completed";
|
|
605
|
+
if (passed && (r.retries ?? 0) === 0) {
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
consecutiveFailures++;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (consecutiveFailures >= failureThreshold) {
|
|
612
|
+
if (mode === "quarantine") {
|
|
613
|
+
return {
|
|
614
|
+
status: "quarantined",
|
|
615
|
+
consecutiveFailures,
|
|
616
|
+
penaltyMultiplier,
|
|
617
|
+
reason: `Route quarantined after ${consecutiveFailures} consecutive failures within ${windowSeconds}s`,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
status: "demoted",
|
|
622
|
+
consecutiveFailures,
|
|
623
|
+
penaltyMultiplier,
|
|
624
|
+
reason: `Route demoted with ${penaltyMultiplier}x penalty after ${consecutiveFailures} consecutive failures`,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return {
|
|
629
|
+
status: "healthy",
|
|
630
|
+
consecutiveFailures,
|
|
631
|
+
penaltyMultiplier: 1.0,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
538
635
|
export function generateRoutingReport(
|
|
539
636
|
records: Array<RoutingRecord | RoutingRecordV2>,
|
|
540
637
|
options: GenerateRoutingReportOptions = {},
|
|
@@ -675,7 +772,8 @@ export function generateRoutingReport(
|
|
|
675
772
|
tokensIn: r.tokensIn ?? null,
|
|
676
773
|
tokensOut: r.tokensOut ?? null,
|
|
677
774
|
cacheReadTokens: r.cacheReadTokens ?? null,
|
|
678
|
-
|
|
775
|
+
cacheWriteTokens: v2?.cacheWriteTokens ?? null,
|
|
776
|
+
contextTokens: v2?.contextTokens ?? null,
|
|
679
777
|
});
|
|
680
778
|
if (calc) {
|
|
681
779
|
equivTotal += calc.costUsd;
|
|
@@ -7,3 +7,7 @@ export * from "./vnext-harness.ts";
|
|
|
7
7
|
export * from "./database.ts";
|
|
8
8
|
export * from "./logger.ts";
|
|
9
9
|
export * from "./improve.ts";
|
|
10
|
+
export * from "./browser.ts";
|
|
11
|
+
export * from "./modes.ts";
|
|
12
|
+
export * from "./ssh-remote.ts";
|
|
13
|
+
export * from "./subagent-control.ts";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety, security, process integrity, and command seatbelts.
|
|
3
|
+
*
|
|
4
|
+
* Implements deterministic safety invariants:
|
|
5
|
+
* - Literal blocklist intercepting destructive shell mutations (rm -rf, git reset --hard, etc.)
|
|
6
|
+
* - Unbypassable raw byte fidelity brake for verification gates and critic reviews (bypassing lossy compression)
|
|
7
|
+
* - Pinned SSH host key verification policy for headless remote execution
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const DESTRUCTIVE_COMMAND_PATTERNS: readonly RegExp[] = Object.freeze([
|
|
11
|
+
/^\s*rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f/i,
|
|
12
|
+
/^\s*rm\s+.*-[a-zA-Z]*f[a-zA-Z]*r/i,
|
|
13
|
+
/^\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,
|
|
14
|
+
/^\s*git\s+reset\s+--hard/i,
|
|
15
|
+
/^\s*git\s+clean\s+-[a-zA-Z]*f/i,
|
|
16
|
+
/^\s*git\s+checkout\s+--\s+/i,
|
|
17
|
+
/^\s*git\s+restore\s+(\.|\*|--staged\s+(\.|\*))/i,
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Literal command seatbelt: blocks destructive mutations from autonomous agents.
|
|
22
|
+
* Throws a descriptive error if command matches any destructive pattern.
|
|
23
|
+
*/
|
|
24
|
+
export function assertCommandSeatbelt(command: string): void {
|
|
25
|
+
for (const pattern of DESTRUCTIVE_COMMAND_PATTERNS) {
|
|
26
|
+
if (pattern.test(command)) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Command blocked by KXM safety seatbelt: "${command}". Destructive workspace mutations require explicit operator override.`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RtkBypassContext {
|
|
35
|
+
stepKind?: string | undefined;
|
|
36
|
+
agentRole?: string | undefined;
|
|
37
|
+
preset?: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const CRITIC_PRESETS = new Set(["critic-arch", "critic-cli", "auditor"]);
|
|
41
|
+
const CRITIC_ROLES = new Set(["critic-arch", "critic-cli", "auditor", "reviewer-arch", "reviewer-cli"]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gate and Critic RTK Brake:
|
|
45
|
+
* Hardcode an unbypassable check ensuring lossy command output compression (RTK)
|
|
46
|
+
* is strictly prohibited for:
|
|
47
|
+
* 1. Verification gates (stepKind === "gate")
|
|
48
|
+
* 2. Critic and auditor agents (critic-arch, critic-cli, auditor)
|
|
49
|
+
*/
|
|
50
|
+
export function isRtkBypassRequired(context: RtkBypassContext): boolean {
|
|
51
|
+
if (context.stepKind === "gate") return true;
|
|
52
|
+
if (context.agentRole && CRITIC_ROLES.has(context.agentRole)) return true;
|
|
53
|
+
if (context.preset && CRITIC_PRESETS.has(context.preset)) return true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const INSECURE_SSH_HOST_KEY_PATTERNS: readonly RegExp[] = Object.freeze([
|
|
58
|
+
/StrictHostKeyChecking=(accept-new|no|off)/i,
|
|
59
|
+
/UserKnownHostsFile=\/dev\/null/i,
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Pinned host key verification policy for headless SSH remote execution.
|
|
64
|
+
* Rejects TOFU (accept-new) and disabled host key checking.
|
|
65
|
+
*/
|
|
66
|
+
export function assertPinnedSshHostKeyPolicy(sshArgs: readonly string[]): void {
|
|
67
|
+
for (const arg of sshArgs) {
|
|
68
|
+
for (const pattern of INSECURE_SSH_HOST_KEY_PATTERNS) {
|
|
69
|
+
if (pattern.test(arg)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Insecure SSH host key policy rejected: "${arg}". KXM headless SSH requires pinned host keys with StrictHostKeyChecking=yes and BatchMode=yes.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -553,13 +553,20 @@ export function sessionBriefPickerEnabled(input: { env?: NodeJS.ProcessEnv; mode
|
|
|
553
553
|
return reason === "startup" || reason === "new" || reason === "fork";
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
-
export const KXM_SLASH_SUBCOMMANDS = ["status", "hub", "memory", "help"] as const;
|
|
556
|
+
export const KXM_SLASH_SUBCOMMANDS = ["status", "hub", "memory", "progress", "workflow", "help"] as const;
|
|
557
557
|
export type KxmSlashCommand = "brief" | typeof KXM_SLASH_SUBCOMMANDS[number];
|
|
558
558
|
|
|
559
559
|
export function parseKxmSlashArgs(args: string | undefined): KxmSlashCommand {
|
|
560
560
|
const raw = String(args ?? "").trim().toLowerCase();
|
|
561
561
|
if (raw === "" || raw === "brief") return "brief";
|
|
562
|
-
if (
|
|
562
|
+
if (
|
|
563
|
+
raw === "status"
|
|
564
|
+
|| raw === "hub"
|
|
565
|
+
|| raw === "memory"
|
|
566
|
+
|| raw === "progress"
|
|
567
|
+
|| raw === "workflow"
|
|
568
|
+
|| raw === "help"
|
|
569
|
+
) return raw;
|
|
563
570
|
return "help";
|
|
564
571
|
}
|
|
565
572
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Runtime-portable SQLite constructor.
|
|
2
|
+
*
|
|
3
|
+
* Prefers Node's built-in `node:sqlite` (the supported runtime for the kxm
|
|
4
|
+
* CLI). Pi loads extensions inside its embedded Bun runtime, which does not
|
|
5
|
+
* provide `node:sqlite`, so fall back to Bun's `bun:sqlite` there.
|
|
6
|
+
*
|
|
7
|
+
* The two runtimes differ in one option this codebase uses: Node spells the
|
|
8
|
+
* flag `readOnly`, Bun spells it `readonly`. Options are normalized here so
|
|
9
|
+
* callers keep using the Node spelling everywhere.
|
|
10
|
+
*/
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
|
|
13
|
+
export interface DatabaseSyncOptions {
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StatementSync {
|
|
19
|
+
run(...params: unknown[]): { changes: number | bigint; lastInsertRowid: number | bigint };
|
|
20
|
+
get(...params: unknown[]): Record<string, unknown> | undefined;
|
|
21
|
+
all(...params: unknown[]): Array<Record<string, unknown>>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type NativeDatabase = {
|
|
25
|
+
prepare(sql: string): StatementSync;
|
|
26
|
+
exec(sql: string): unknown;
|
|
27
|
+
close(): void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
31
|
+
|
|
32
|
+
function loadNative(): { Ctor: new (path: string, options?: object) => NativeDatabase; bun: boolean } {
|
|
33
|
+
try {
|
|
34
|
+
const mod = requireFromHere("node:sqlite") as { DatabaseSync?: new (path: string, options?: object) => NativeDatabase };
|
|
35
|
+
if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
|
|
36
|
+
} catch {
|
|
37
|
+
// Not a Node runtime exposing node:sqlite (e.g. pi's embedded Bun).
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const mod = requireFromHere("bun:sqlite") as
|
|
41
|
+
| { DatabaseSync?: new (path: string, options?: object) => NativeDatabase; Database?: new (path: string, options?: object) => NativeDatabase }
|
|
42
|
+
| undefined;
|
|
43
|
+
const Ctor = mod?.DatabaseSync ?? mod?.Database;
|
|
44
|
+
if (Ctor) return { Ctor, bun: true };
|
|
45
|
+
} catch {
|
|
46
|
+
// No bun:sqlite either.
|
|
47
|
+
}
|
|
48
|
+
throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const native = loadNative();
|
|
52
|
+
|
|
53
|
+
export class DatabaseSync {
|
|
54
|
+
private readonly inner: NativeDatabase;
|
|
55
|
+
|
|
56
|
+
constructor(path: string, options?: DatabaseSyncOptions) {
|
|
57
|
+
let normalized: object | undefined = options;
|
|
58
|
+
if (native.bun && options) {
|
|
59
|
+
const { readOnly, ...rest } = options;
|
|
60
|
+
normalized = readOnly === undefined ? rest : { ...rest, readonly: readOnly };
|
|
61
|
+
}
|
|
62
|
+
this.inner = normalized === undefined ? new native.Ctor(path) : new native.Ctor(path, normalized);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
prepare(sql: string): StatementSync {
|
|
66
|
+
return this.inner.prepare(sql);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
exec(sql: string): unknown {
|
|
70
|
+
return this.inner.exec(sql);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
close(): void {
|
|
74
|
+
this.inner.close();
|
|
75
|
+
}
|
|
76
|
+
}
|