akm-opencode 0.5.1 → 0.5.2
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/README.md +1 -1
- package/index.ts +136 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ At least one of `ref` or `query` is required.
|
|
|
112
112
|
|
|
113
113
|
## Prerequisites
|
|
114
114
|
|
|
115
|
-
When the plugin loads, it runs `bun install -g akm-cli@latest`
|
|
115
|
+
When the plugin loads, it checks the installed `akm` version first and only runs `bun install -g akm-cli@latest` when `akm` is missing or older than the latest stable npm release. Newer pre-releases and local builds are left in place. It then prefers the Bun-installed binary and falls back to an existing `akm` on PATH when needed. It does not run the standalone shell installers automatically.
|
|
116
116
|
|
|
117
117
|
```sh
|
|
118
118
|
# macOS / Linux
|
package/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url"
|
|
|
7
7
|
let resolvedAkmCommand = "akm"
|
|
8
8
|
const autoInstallPackageRef = "akm-cli@latest"
|
|
9
9
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const SEMVER_PATTERN = /\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?\b/
|
|
10
11
|
|
|
11
12
|
const AKM_AUTO_FEEDBACK = (process.env.AKM_AUTO_FEEDBACK ?? "1") !== "0"
|
|
12
13
|
const AKM_AUTO_MEMORY = (process.env.AKM_AUTO_MEMORY ?? "1") !== "0"
|
|
@@ -32,6 +33,7 @@ const sessionContextEpoch = new Map<string, number>()
|
|
|
32
33
|
const sessionContextInjectedEpoch = new Map<string, number>()
|
|
33
34
|
const sessionCuratedVersion = new Map<string, number>()
|
|
34
35
|
const sessionCuratedInjectedVersion = new Map<string, number>()
|
|
36
|
+
type ParsedSemver = { core: [number, number, number]; prerelease: Array<string | number> | null }
|
|
35
37
|
type SessionBufferEntry = {
|
|
36
38
|
timestamp: string
|
|
37
39
|
kind: "memory-intent" | "tool-ref"
|
|
@@ -742,6 +744,72 @@ function getCommandStatus(command: string): "ok" | "missing" | "error" {
|
|
|
742
744
|
}
|
|
743
745
|
}
|
|
744
746
|
|
|
747
|
+
function extractFirstSemverMatch(value: string): string | null {
|
|
748
|
+
return value.match(SEMVER_PATTERN)?.[0] ?? null
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function getCommandVersion(command: string): string | null {
|
|
752
|
+
try {
|
|
753
|
+
const version = execFileSync(command, ["--version"], {
|
|
754
|
+
encoding: "utf8",
|
|
755
|
+
timeout: 10_000,
|
|
756
|
+
})
|
|
757
|
+
return extractFirstSemverMatch(version)
|
|
758
|
+
} catch {
|
|
759
|
+
return null
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function parseSemver(version: string): ParsedSemver | null {
|
|
764
|
+
const normalized = extractFirstSemverMatch(version)
|
|
765
|
+
if (!normalized) return null
|
|
766
|
+
|
|
767
|
+
const [withoutBuildMetadata] = normalized.split("+", 1)
|
|
768
|
+
const [release, prereleaseText] = withoutBuildMetadata.split("-", 2)
|
|
769
|
+
const parts = release.split(".").map((part) => Number(part))
|
|
770
|
+
if (parts.length !== 3 || parts.some((part) => !Number.isInteger(part) || part < 0)) {
|
|
771
|
+
return null
|
|
772
|
+
}
|
|
773
|
+
const core = [parts[0], parts[1], parts[2]] as [number, number, number]
|
|
774
|
+
|
|
775
|
+
return {
|
|
776
|
+
core,
|
|
777
|
+
prerelease: prereleaseText
|
|
778
|
+
? prereleaseText.split(".").map((part) => (/^\d+$/.test(part) ? Number(part) : part))
|
|
779
|
+
: null,
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function compareSemver(left: string, right: string): number {
|
|
784
|
+
const leftParsed = parseSemver(left)
|
|
785
|
+
const rightParsed = parseSemver(right)
|
|
786
|
+
if (!leftParsed || !rightParsed) return left.localeCompare(right)
|
|
787
|
+
|
|
788
|
+
for (let index = 0; index < leftParsed.core.length; index += 1) {
|
|
789
|
+
const delta = leftParsed.core[index] - rightParsed.core[index]
|
|
790
|
+
if (delta !== 0) return delta
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (!leftParsed.prerelease && !rightParsed.prerelease) return 0
|
|
794
|
+
if (!leftParsed.prerelease) return 1
|
|
795
|
+
if (!rightParsed.prerelease) return -1
|
|
796
|
+
|
|
797
|
+
const length = Math.max(leftParsed.prerelease.length, rightParsed.prerelease.length)
|
|
798
|
+
for (let index = 0; index < length; index += 1) {
|
|
799
|
+
const leftPart = leftParsed.prerelease[index]
|
|
800
|
+
const rightPart = rightParsed.prerelease[index]
|
|
801
|
+
if (leftPart === undefined) return -1
|
|
802
|
+
if (rightPart === undefined) return 1
|
|
803
|
+
if (leftPart === rightPart) continue
|
|
804
|
+
if (typeof leftPart === "number" && typeof rightPart === "number") return leftPart - rightPart
|
|
805
|
+
if (typeof leftPart === "number") return -1
|
|
806
|
+
if (typeof rightPart === "number") return 1
|
|
807
|
+
return leftPart.localeCompare(rightPart)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return 0
|
|
811
|
+
}
|
|
812
|
+
|
|
745
813
|
function getBunGlobalAkmCommand(): string | null {
|
|
746
814
|
try {
|
|
747
815
|
const globalBin = execFileSync("bun", ["pm", "bin", "-g"], {
|
|
@@ -757,6 +825,39 @@ function getBunGlobalAkmCommand(): string | null {
|
|
|
757
825
|
}
|
|
758
826
|
}
|
|
759
827
|
|
|
828
|
+
function getInstalledAkmDetails(): { command: string; version: string } | null {
|
|
829
|
+
const candidates = [resolvedAkmCommand, getBunGlobalAkmCommand(), "akm"]
|
|
830
|
+
const seen = new Set<string>()
|
|
831
|
+
for (const candidate of candidates) {
|
|
832
|
+
if (!candidate || seen.has(candidate)) continue
|
|
833
|
+
seen.add(candidate)
|
|
834
|
+
const version = getCommandVersion(candidate)
|
|
835
|
+
if (version) return { command: candidate, version }
|
|
836
|
+
}
|
|
837
|
+
return null
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function getLatestNpmPackageVersion(packageName: string): Promise<string | null> {
|
|
841
|
+
if (typeof fetch !== "function") return null
|
|
842
|
+
|
|
843
|
+
const controller = new AbortController()
|
|
844
|
+
const timeout = setTimeout(() => controller.abort(), 10_000)
|
|
845
|
+
try {
|
|
846
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
847
|
+
headers: { accept: "application/json" },
|
|
848
|
+
signal: controller.signal,
|
|
849
|
+
})
|
|
850
|
+
if (!response.ok) return null
|
|
851
|
+
const body = await response.json()
|
|
852
|
+
if (!body || typeof body !== "object") return null
|
|
853
|
+
return typeof body.version === "string" ? extractFirstSemverMatch(body.version) : null
|
|
854
|
+
} catch {
|
|
855
|
+
return null
|
|
856
|
+
} finally {
|
|
857
|
+
clearTimeout(timeout)
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
760
861
|
async function ensureLatestAkmInstalled(client: LogCapableClient): Promise<void> {
|
|
761
862
|
try {
|
|
762
863
|
execFileSync("bun", ["--version"], {
|
|
@@ -772,6 +873,39 @@ async function ensureLatestAkmInstalled(client: LogCapableClient): Promise<void>
|
|
|
772
873
|
return
|
|
773
874
|
}
|
|
774
875
|
|
|
876
|
+
const installedAkm = getInstalledAkmDetails()
|
|
877
|
+
const latestStable = await getLatestNpmPackageVersion("akm-cli")
|
|
878
|
+
|
|
879
|
+
if (installedAkm) {
|
|
880
|
+
if (!latestStable) {
|
|
881
|
+
resolvedAkmCommand = installedAkm.command
|
|
882
|
+
await writePluginLog(client, "info", "AKM auto-install skipped", {
|
|
883
|
+
subsystem: "akm",
|
|
884
|
+
installer: "bun",
|
|
885
|
+
package: autoInstallPackageRef,
|
|
886
|
+
command: resolvedAkmCommand,
|
|
887
|
+
installedVersion: installedAkm.version,
|
|
888
|
+
latestStable,
|
|
889
|
+
reason: "latest_version_unavailable",
|
|
890
|
+
})
|
|
891
|
+
return
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (compareSemver(installedAkm.version, latestStable) >= 0) {
|
|
895
|
+
resolvedAkmCommand = installedAkm.command
|
|
896
|
+
await writePluginLog(client, "info", "AKM auto-install skipped", {
|
|
897
|
+
subsystem: "akm",
|
|
898
|
+
installer: "bun",
|
|
899
|
+
package: autoInstallPackageRef,
|
|
900
|
+
command: resolvedAkmCommand,
|
|
901
|
+
installedVersion: installedAkm.version,
|
|
902
|
+
latestStable,
|
|
903
|
+
reason: "installed_version_not_older",
|
|
904
|
+
})
|
|
905
|
+
return
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
775
909
|
try {
|
|
776
910
|
execFileSync("bun", ["install", "-g", autoInstallPackageRef], {
|
|
777
911
|
encoding: "utf8",
|
|
@@ -791,6 +925,8 @@ async function ensureLatestAkmInstalled(client: LogCapableClient): Promise<void>
|
|
|
791
925
|
installer: "bun",
|
|
792
926
|
package: autoInstallPackageRef,
|
|
793
927
|
command: resolvedAkmCommand,
|
|
928
|
+
installedVersion: installedAkm?.version ?? null,
|
|
929
|
+
latestStable,
|
|
794
930
|
})
|
|
795
931
|
} catch (error: unknown) {
|
|
796
932
|
await writePluginLog(client, "warn", "AKM auto-install failed", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-opencode",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for AKM - search, show, and manage extension assets via the akm CLI, including v0.5.0 vaults, wikis, and workflows, with agentic hooks that auto-load relevant stash assets, record feedback, and harvest session memories so the stash improves every session.",
|
|
6
6
|
"keywords": [
|