akm-cli 0.9.28-alpha.4 → 0.9.28-alpha.5
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/CHANGELOG.md +14 -0
- package/dist/assets/hints/cli-hints-full.md +1 -0
- package/dist/commands/sources/plugin-upgrade.js +113 -28
- package/dist/commands/sources/self-update.js +24 -2
- package/dist/commands/sources/sources-cli.js +9 -1
- package/dist/output/text/command-format.js +1 -1
- package/dist/scripts/akm-migrate-node.js +1 -1
- package/dist/scripts/akm-migrate.js +1 -1
- package/docs/reference/cli.md +37 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.28-alpha.5] - 2026-10-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`akm upgrade --next` installs the `@next` prerelease of akm and its OpenCode plugin.** The CLI target is
|
|
14
|
+
the `next` dist-tag of `akm-cli` when it is newer than the latest stable release, else the stable release
|
|
15
|
+
(never a downgrade); npm/Bun/pnpm installs name that exact version and binary installs take its GitHub
|
|
16
|
+
release. The OpenCode lockstep and cache refresh follow `akm-opencode@next` when the OpenCode config's
|
|
17
|
+
`plugin` list names it (akm never edits that config); with a bare `"akm-opencode"` the entry is `skipped`
|
|
18
|
+
with a message saying to set `"plugin": ["akm-opencode@next"]`, and lockstep stays on the `@latest` pin.
|
|
19
|
+
Missing, older-than-`@latest` or unpinned `@next` fails closed like the stable lockstep. Claude Code and
|
|
20
|
+
Codex are unchanged (no prerelease channel). The result gains `channel`. Works with `--check`, `--force`
|
|
21
|
+
and `-q`. See [`akm upgrade`](docs/reference/cli.md#prereleases-next).
|
|
22
|
+
|
|
9
23
|
## [0.9.28-alpha.4] - 2026-10-08
|
|
10
24
|
|
|
11
25
|
### Added
|
|
@@ -331,6 +331,7 @@ akm lint --fix # Auto-fix Tier 1 issues
|
|
|
331
331
|
akm lint --fail-on-flagged # Exit non-zero when summary.flagged > 0 (CI-friendly)
|
|
332
332
|
akm upgrade # Upgrade akm using its install method, then update installed harness plugins
|
|
333
333
|
akm upgrade --check # Report pending CLI and plugin updates, changing nothing
|
|
334
|
+
akm upgrade --next # Follow prereleases (@next); OpenCode needs "akm-opencode@next" in its plugin list
|
|
334
335
|
akm help migrate 0.6.0 # Print migration notes for a release (or: latest)
|
|
335
336
|
akm help bundle # Print options and subcommands for one command
|
|
336
337
|
akm help agents --full # Print this reference
|
|
@@ -13,12 +13,13 @@ import * as childProcess from "node:child_process";
|
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import path from "node:path";
|
|
16
|
-
import { IS_WINDOWS } from "../../core/common.js";
|
|
16
|
+
import { IS_WINDOWS, stripJsonComments } from "../../core/common.js";
|
|
17
17
|
import { moveToTrash } from "../../core/trash.js";
|
|
18
18
|
import { semverOrder } from "../../runtime.js";
|
|
19
19
|
const MARKETPLACE = "akm-plugins";
|
|
20
20
|
const PLUGIN_ID = `akm@${MARKETPLACE}`;
|
|
21
21
|
const OPENCODE_PACKAGE = "akm-opencode";
|
|
22
|
+
const OPENCODE_NEXT_SPEC = `${OPENCODE_PACKAGE}@next`;
|
|
22
23
|
const READ_TIMEOUT_MS = 30_000;
|
|
23
24
|
const REFRESH_TIMEOUT_MS = 120_000;
|
|
24
25
|
const PREFETCH_TIMEOUT_MS = 180_000;
|
|
@@ -136,11 +137,48 @@ function upgradeCodex(dryRun) {
|
|
|
136
137
|
const after = codexPluginVersion();
|
|
137
138
|
return refreshed("codex", before.version, "version" in after ? after.version : undefined);
|
|
138
139
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
function homeDir() {
|
|
141
|
+
return process.env.HOME?.trim() || process.env.USERPROFILE?.trim() || os.homedir();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether the user's global OpenCode config asks for `akm-opencode@next`.
|
|
145
|
+
* A bare `akm-opencode` resolves to `@latest` when OpenCode prefetches it, so
|
|
146
|
+
* only a config that names the tag can follow prereleases. The config is only
|
|
147
|
+
* read, never written; project configs are not looked at (akm does not know
|
|
148
|
+
* which project OpenCode runs in).
|
|
149
|
+
*/
|
|
150
|
+
export function openCodeConfigRequestsNext() {
|
|
151
|
+
const configHome = process.env.XDG_CONFIG_HOME?.trim() || path.join(homeDir(), ".config");
|
|
152
|
+
const files = [
|
|
153
|
+
process.env.OPENCODE_CONFIG?.trim(),
|
|
154
|
+
path.join(configHome, "opencode", "opencode.json"),
|
|
155
|
+
path.join(configHome, "opencode", "opencode.jsonc"),
|
|
156
|
+
];
|
|
157
|
+
for (const file of files) {
|
|
158
|
+
if (!file)
|
|
159
|
+
continue;
|
|
160
|
+
try {
|
|
161
|
+
const config = JSON.parse(stripJsonComments(fs.readFileSync(file, "utf8")));
|
|
162
|
+
if (!Array.isArray(config.plugin))
|
|
163
|
+
continue;
|
|
164
|
+
// An entry is a spec string or a `[spec, options]` pair.
|
|
165
|
+
if (config.plugin.some((entry) => (Array.isArray(entry) ? entry[0] : entry) === OPENCODE_NEXT_SPEC))
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Missing or unparseable: this file does not request it.
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The cached `akm-opencode` OpenCode installed on first use, or undefined when
|
|
176
|
+
* there is none. OpenCode names the folder after the spec it resolved, so a
|
|
177
|
+
* bare `akm-opencode` lands in `@latest` and `akm-opencode@next` in `@next`.
|
|
178
|
+
*/
|
|
179
|
+
export function detectOpenCodeCache(tag = "latest") {
|
|
180
|
+
const cacheHome = process.env.XDG_CACHE_HOME?.trim() || path.join(homeDir(), ".cache");
|
|
181
|
+
const dir = path.join(cacheHome, "opencode", "packages", `${OPENCODE_PACKAGE}@${tag}`);
|
|
144
182
|
if (!fs.existsSync(dir))
|
|
145
183
|
return undefined;
|
|
146
184
|
const readVersion = (file) => {
|
|
@@ -154,17 +192,33 @@ export function detectOpenCodeCache() {
|
|
|
154
192
|
};
|
|
155
193
|
return { dir, version: readVersion(path.join(dir, "node_modules", OPENCODE_PACKAGE, "package.json")) };
|
|
156
194
|
}
|
|
157
|
-
/** What npm's `akm-opencode
|
|
158
|
-
export function lookupOpenCodeLatest() {
|
|
159
|
-
const
|
|
195
|
+
/** What npm's `akm-opencode@<tag>` is, and which akm-cli it pins. */
|
|
196
|
+
export function lookupOpenCodeLatest(tag = "latest") {
|
|
197
|
+
const spec = `${OPENCODE_PACKAGE}@${tag}`;
|
|
198
|
+
const view = runCommand(IS_WINDOWS ? "npm.cmd" : "npm", ["view", spec, "version", "dependencies.akm-cli", "--json"], READ_TIMEOUT_MS);
|
|
160
199
|
if (!view.ok)
|
|
161
200
|
return { error: view.error };
|
|
162
201
|
const parsed = parseJson(view.stdout);
|
|
163
202
|
if (typeof parsed?.version !== "string")
|
|
164
|
-
return { error:
|
|
203
|
+
return { error: `npm did not report a version for ${spec}` };
|
|
165
204
|
const akmCli = parsed["dependencies.akm-cli"];
|
|
166
205
|
return { version: parsed.version, akmCli: typeof akmCli === "string" ? akmCli : undefined };
|
|
167
206
|
}
|
|
207
|
+
/** `akm-opencode@next`, which must exist and be no older than `@latest`: a prerelease channel that trails the stable one is not a target. */
|
|
208
|
+
export function lookupOpenCodeNext() {
|
|
209
|
+
const next = lookupOpenCodeLatest("next");
|
|
210
|
+
if ("error" in next)
|
|
211
|
+
return next;
|
|
212
|
+
const latest = lookupOpenCodeLatest("latest");
|
|
213
|
+
if ("error" in latest)
|
|
214
|
+
return { error: `could not compare with ${OPENCODE_PACKAGE}@latest: ${latest.error}` };
|
|
215
|
+
if (semverOrder(next.version, latest.version) < 0) {
|
|
216
|
+
return {
|
|
217
|
+
error: `${OPENCODE_NEXT_SPEC} (${next.version}) is older than ${OPENCODE_PACKAGE}@latest (${latest.version})`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return next;
|
|
221
|
+
}
|
|
168
222
|
/** Whether an OpenCode process is running (its prefetch would be replaced under it). `undefined` when that cannot be told. */
|
|
169
223
|
function openCodeRunning() {
|
|
170
224
|
if (IS_WINDOWS) {
|
|
@@ -198,11 +252,15 @@ function openCodeRunning() {
|
|
|
198
252
|
return undefined;
|
|
199
253
|
}
|
|
200
254
|
}
|
|
201
|
-
|
|
255
|
+
const NOT_FOLLOWING_NEXT = `--next: OpenCode resolves a bare "${OPENCODE_PACKAGE}" to @latest, so it is not updated to a prerelease; ` +
|
|
256
|
+
`set "plugin": ["${OPENCODE_NEXT_SPEC}"] in your OpenCode config to follow prereleases`;
|
|
257
|
+
function upgradeOpenCode(dryRun, cache, latest, tag, notFollowingNext) {
|
|
202
258
|
if (!cache || !latest)
|
|
203
|
-
return skipped("opencode",
|
|
259
|
+
return skipped("opencode", `no cached ${OPENCODE_PACKAGE}@${tag} plugin`);
|
|
204
260
|
if ("error" in latest)
|
|
205
261
|
return failed("opencode", latest.error);
|
|
262
|
+
if (notFollowingNext)
|
|
263
|
+
return skipped("opencode", NOT_FOLLOWING_NEXT);
|
|
206
264
|
if (cache.version === latest.version)
|
|
207
265
|
return { harness: "opencode", outcome: "current", from: cache.version, to: latest.version };
|
|
208
266
|
const base = { harness: "opencode", from: cache.version, to: latest.version };
|
|
@@ -239,17 +297,27 @@ function upgradeOpenCode(dryRun, cache, latest) {
|
|
|
239
297
|
finally {
|
|
240
298
|
fs.rmSync(workDir, { recursive: true, force: true });
|
|
241
299
|
}
|
|
242
|
-
const after = detectOpenCodeCache();
|
|
300
|
+
const after = detectOpenCodeCache(tag);
|
|
243
301
|
if (!after)
|
|
244
302
|
return failed("opencode", "opencode did not re-create the plugin cache (the old cache is in the trash)");
|
|
245
303
|
return { ...base, outcome: "updated", to: after.version ?? latest.version };
|
|
246
304
|
}
|
|
247
305
|
// ── Orchestration ───────────────────────────────────────────────────────────
|
|
306
|
+
const NO_PRERELEASE_CHANNEL = "--next: no prerelease channel; the akm-plugins marketplace is followed as usual and works with any 0.9.x akm";
|
|
307
|
+
/** The note `--next` adds to a harness whose plugin has no prerelease channel. */
|
|
308
|
+
function withNextNote(entry) {
|
|
309
|
+
if (entry.outcome === "skipped")
|
|
310
|
+
return entry;
|
|
311
|
+
return { ...entry, message: entry.message ? `${entry.message}; ${NO_PRERELEASE_CHANNEL}` : NO_PRERELEASE_CHANNEL };
|
|
312
|
+
}
|
|
248
313
|
export function upgradePlugins(opts) {
|
|
314
|
+
const { cache, latest, tag, notFollowingNext } = opts.openCode;
|
|
315
|
+
const claude = upgradeClaudeCode(opts.dryRun);
|
|
316
|
+
const codex = upgradeCodex(opts.dryRun);
|
|
249
317
|
return [
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
upgradeOpenCode(opts.dryRun,
|
|
318
|
+
opts.next ? withNextNote(claude) : claude,
|
|
319
|
+
opts.next ? withNextNote(codex) : codex,
|
|
320
|
+
upgradeOpenCode(opts.dryRun, cache, latest, tag, notFollowingNext),
|
|
253
321
|
];
|
|
254
322
|
}
|
|
255
323
|
/**
|
|
@@ -264,7 +332,7 @@ export function upgradePlugins(opts) {
|
|
|
264
332
|
* `lockstep.reason` says why. Moving the CLI ahead of a pin nobody could read is
|
|
265
333
|
* what lockstep exists to prevent; the OpenCode entry reports the failure.
|
|
266
334
|
*/
|
|
267
|
-
export function applyLockstep(check, latest) {
|
|
335
|
+
export function applyLockstep(check, latest, tag = "latest") {
|
|
268
336
|
if (!latest)
|
|
269
337
|
return check;
|
|
270
338
|
// Held back only when there is a release this upgrade would otherwise install.
|
|
@@ -272,8 +340,8 @@ export function applyLockstep(check, latest) {
|
|
|
272
340
|
const pinned = "error" in latest ? undefined : latest.akmCli;
|
|
273
341
|
if (!pinned) {
|
|
274
342
|
const reason = "error" in latest
|
|
275
|
-
? `could not read the akm-cli pin of ${OPENCODE_PACKAGE}
|
|
276
|
-
: `${OPENCODE_PACKAGE}
|
|
343
|
+
? `could not read the akm-cli pin of ${OPENCODE_PACKAGE}@${tag}: ${latest.error}`
|
|
344
|
+
: `${OPENCODE_PACKAGE}@${tag} declares no akm-cli dependency`;
|
|
277
345
|
return {
|
|
278
346
|
...check,
|
|
279
347
|
latestVersion: check.currentVersion,
|
|
@@ -303,23 +371,40 @@ export function applyLockstep(check, latest) {
|
|
|
303
371
|
lockstep,
|
|
304
372
|
};
|
|
305
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Which OpenCode plugin build the CLI is held to, and which cache refreshes.
|
|
376
|
+
* Under `--next` that is `akm-opencode@next`, but only when the user's OpenCode
|
|
377
|
+
* config names that tag: otherwise OpenCode keeps resolving `@latest`, so the
|
|
378
|
+
* lockstep stays against the `@latest` pin and the entry is reported as skipped.
|
|
379
|
+
*/
|
|
380
|
+
function resolveOpenCodeTarget(next) {
|
|
381
|
+
const followNext = next && openCodeConfigRequestsNext();
|
|
382
|
+
const tag = followNext ? "next" : "latest";
|
|
383
|
+
const cache = detectOpenCodeCache(tag);
|
|
384
|
+
const latest = cache ? (followNext ? lookupOpenCodeNext() : lookupOpenCodeLatest()) : undefined;
|
|
385
|
+
return { cache, latest, tag, notFollowingNext: next && !followNext };
|
|
386
|
+
}
|
|
306
387
|
/** `akm upgrade`: the CLI step (held to the OpenCode plugin's akm), then the plugins. */
|
|
307
388
|
export async function runUpgrade(args, currentVersion, deps) {
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
const
|
|
389
|
+
const next = args.next === true;
|
|
390
|
+
const channel = next ? "next" : "latest";
|
|
391
|
+
const openCode = resolveOpenCodeTarget(next);
|
|
392
|
+
const check = {
|
|
393
|
+
...applyLockstep(await deps.checkForUpdate(currentVersion, channel), openCode.latest, openCode.tag),
|
|
394
|
+
channel,
|
|
395
|
+
};
|
|
312
396
|
if (args.check) {
|
|
313
|
-
return { mode: "check", result: { ...check, plugins: upgradePlugins({ dryRun: true, openCode }) } };
|
|
397
|
+
return { mode: "check", result: { ...check, plugins: upgradePlugins({ dryRun: true, next, openCode }) } };
|
|
314
398
|
}
|
|
315
399
|
const upgraded = await deps.performUpgrade(check, {
|
|
316
400
|
force: args.force,
|
|
317
401
|
skipPostUpgrade: args.skipPostUpgrade,
|
|
318
|
-
// A package manager install must name the version, or `@latest` goes past the pin
|
|
319
|
-
|
|
402
|
+
// A package manager install must name the version, or `@latest` goes past the pin
|
|
403
|
+
// (or past the prerelease that `--next` chose).
|
|
404
|
+
...(check.lockstep?.heldBack || (next && check.latestVersion) ? { targetVersion: check.latestVersion } : {}),
|
|
320
405
|
});
|
|
321
|
-
const plugins = upgradePlugins({ dryRun: false, openCode });
|
|
322
|
-
const result = { ...upgraded, ...(check.lockstep ? { lockstep: check.lockstep } : {}), plugins };
|
|
406
|
+
const plugins = upgradePlugins({ dryRun: false, next, openCode });
|
|
407
|
+
const result = { ...upgraded, channel, ...(check.lockstep ? { lockstep: check.lockstep } : {}), plugins };
|
|
323
408
|
// The install may have succeeded, but an upgrade whose migration is
|
|
324
409
|
// blocked or could not run is not done, and neither is one whose plugin
|
|
325
410
|
// step failed.
|
|
@@ -174,7 +174,24 @@ export function getAkmBinaryName() {
|
|
|
174
174
|
return "akm-windows-x64.exe";
|
|
175
175
|
throw new ConfigError(`Unsupported platform for binary upgrade: ${platform}/${arch}`, "UNSUPPORTED_PLATFORM");
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
/** The `next` dist-tag of this package on the npm registry, or undefined when none is published. */
|
|
178
|
+
async function lookupNextVersion(fetchOptions) {
|
|
179
|
+
const url = `https://registry.npmjs.org/-/package/${encodeURIComponent(getInstalledPackageName())}/dist-tags`;
|
|
180
|
+
const response = await fetchWithRetry(url, { headers: { accept: "application/json" } }, fetchOptions);
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
throw new Error(`Failed to check for the next prerelease: ${response.status} ${response.statusText}`);
|
|
183
|
+
}
|
|
184
|
+
const tags = JSON.parse(await readBodyWithByteCap(response, MAX_CHECKSUM_METADATA_BYTES));
|
|
185
|
+
return typeof tags.next === "string" && tags.next !== "" ? tags.next : undefined;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The newest release. With `channel: "next"` that includes prereleases: the
|
|
189
|
+
* `next` dist-tag when it is newer than the latest stable release, else the
|
|
190
|
+
* stable release (a prerelease older than a stable one is never a target).
|
|
191
|
+
* A binary install downloads the GitHub release tagged `v<version>`, which a
|
|
192
|
+
* prerelease has too, so the version is all the install step needs.
|
|
193
|
+
*/
|
|
194
|
+
export async function checkForUpdate(currentVersion, fetchOptions, channel = "latest") {
|
|
178
195
|
const installMethod = detectInstallMethod();
|
|
179
196
|
const url = `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
180
197
|
const response = await fetchWithRetry(url, { headers: githubHeaders() }, fetchOptions);
|
|
@@ -183,7 +200,12 @@ export async function checkForUpdate(currentVersion, fetchOptions) {
|
|
|
183
200
|
}
|
|
184
201
|
const release = JSON.parse(await readBodyWithByteCap(response, MAX_CHECKSUM_METADATA_BYTES));
|
|
185
202
|
const latestTag = release.tag_name ?? "";
|
|
186
|
-
|
|
203
|
+
let latestVersion = latestTag.replace(/^v/, "");
|
|
204
|
+
if (channel === "next") {
|
|
205
|
+
const next = await lookupNextVersion(fetchOptions);
|
|
206
|
+
if (next && (latestVersion === "" || semverOrder(latestVersion, next) < 0))
|
|
207
|
+
latestVersion = next;
|
|
208
|
+
}
|
|
187
209
|
return {
|
|
188
210
|
currentVersion,
|
|
189
211
|
latestVersion,
|
|
@@ -54,9 +54,17 @@ export const upgradeCommand = defineJsonCommand({
|
|
|
54
54
|
description: "Skip the post-upgrade index rebuild",
|
|
55
55
|
default: false,
|
|
56
56
|
},
|
|
57
|
+
next: {
|
|
58
|
+
type: "boolean",
|
|
59
|
+
description: "Install the newest prerelease (the @next npm tag) of akm and, if OpenCode's config names akm-opencode@next, of its plugin",
|
|
60
|
+
default: false,
|
|
61
|
+
},
|
|
57
62
|
},
|
|
58
63
|
async run({ args }) {
|
|
59
|
-
const run = await runUpgrade({ check: args.check, force: args.force, skipPostUpgrade: args["skip-post-upgrade"] }, pkgVersion, {
|
|
64
|
+
const run = await runUpgrade({ check: args.check, force: args.force, skipPostUpgrade: args["skip-post-upgrade"], next: args.next }, pkgVersion, {
|
|
65
|
+
checkForUpdate: (version, channel) => checkForUpdate(version, undefined, channel),
|
|
66
|
+
performUpgrade: (check, opts) => performUpgrade(check, opts),
|
|
67
|
+
});
|
|
60
68
|
if (run.mode === "check") {
|
|
61
69
|
output("upgrade", run.result);
|
|
62
70
|
return;
|
|
@@ -693,7 +693,7 @@ function formatUpgradeHead(r) {
|
|
|
693
693
|
return `akm upgraded: v${r.currentVersion} → v${r.newVersion}`;
|
|
694
694
|
}
|
|
695
695
|
if (r.updateAvailable === true) {
|
|
696
|
-
return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade' to install)`;
|
|
696
|
+
return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade${r.channel === "next" ? " --next" : ""}' to install)`;
|
|
697
697
|
}
|
|
698
698
|
if (r.updateAvailable === false && r.latestVersion) {
|
|
699
699
|
return `akm v${r.currentVersion} is already the latest version`;
|
|
@@ -30493,7 +30493,7 @@ function formatUpgradeHead(r) {
|
|
|
30493
30493
|
return `akm upgraded: v${r.currentVersion} → v${r.newVersion}`;
|
|
30494
30494
|
}
|
|
30495
30495
|
if (r.updateAvailable === true) {
|
|
30496
|
-
return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade' to install)`;
|
|
30496
|
+
return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade${r.channel === "next" ? " --next" : ""}' to install)`;
|
|
30497
30497
|
}
|
|
30498
30498
|
if (r.updateAvailable === false && r.latestVersion) {
|
|
30499
30499
|
return `akm v${r.currentVersion} is already the latest version`;
|
|
@@ -29821,7 +29821,7 @@ function formatUpgradeHead(r) {
|
|
|
29821
29821
|
return `akm upgraded: v${r.currentVersion} \u2192 v${r.newVersion}`;
|
|
29822
29822
|
}
|
|
29823
29823
|
if (r.updateAvailable === true) {
|
|
29824
|
-
return `akm v${r.currentVersion} \u2192 v${r.latestVersion} available (run 'akm upgrade' to install)`;
|
|
29824
|
+
return `akm v${r.currentVersion} \u2192 v${r.latestVersion} available (run 'akm upgrade${r.channel === "next" ? " --next" : ""}' to install)`;
|
|
29825
29825
|
}
|
|
29826
29826
|
if (r.updateAvailable === false && r.latestVersion) {
|
|
29827
29827
|
return `akm v${r.currentVersion} is already the latest version`;
|
package/docs/reference/cli.md
CHANGED
|
@@ -1339,6 +1339,7 @@ computed, with a 256 MiB binary limit. Release/checksum metadata is capped at
|
|
|
1339
1339
|
akm upgrade # Install a newer release if there is one, run every pending migration, then update the harness plugins
|
|
1340
1340
|
akm upgrade --check # Report pending updates, CLI and plugins, without changing anything (no migration step)
|
|
1341
1341
|
akm upgrade --force # Force the install even if already on latest
|
|
1342
|
+
akm upgrade --next # Install the newest prerelease (@next) instead of the latest stable release
|
|
1342
1343
|
```
|
|
1343
1344
|
|
|
1344
1345
|
| Flag | Description |
|
|
@@ -1346,6 +1347,7 @@ akm upgrade --force # Force the install even if already on latest
|
|
|
1346
1347
|
| `--check` | Report pending updates (CLI and per-harness plugins) without changing anything |
|
|
1347
1348
|
| `--force` | Force upgrade even if on latest version |
|
|
1348
1349
|
| `--skip-post-upgrade` | Skip the post-upgrade index rebuild |
|
|
1350
|
+
| `--next` | Follow the `next` prerelease channel: install the newest prerelease of akm (and of the OpenCode plugin, see [Prereleases](#prereleases-next)). Works with `--check`, `--force` and `-q` |
|
|
1349
1351
|
|
|
1350
1352
|
Offline, or to migrate without a release check, run `akm migrate apply`
|
|
1351
1353
|
directly: it is the same step.
|
|
@@ -1399,6 +1401,41 @@ be read (the npm lookup failed, or `akm-opencode@latest` declares no
|
|
|
1399
1401
|
OpenCode entry is `failed` and the run exits `1`; the next `akm upgrade`
|
|
1400
1402
|
retries.
|
|
1401
1403
|
|
|
1404
|
+
#### Prereleases (`--next`)
|
|
1405
|
+
|
|
1406
|
+
`akm upgrade --next` means "newest available, prereleases included". The result
|
|
1407
|
+
carries `channel: "next"` (`"latest"` otherwise); no other field changes.
|
|
1408
|
+
|
|
1409
|
+
- **CLI.** The target is the `next` dist-tag of `akm-cli` (read from the npm
|
|
1410
|
+
registry, so it works for binary installs too) when that is newer than the
|
|
1411
|
+
latest stable release; otherwise the latest stable release. It never moves
|
|
1412
|
+
backwards. npm, Bun and pnpm installs run `<manager> add|install -g akm-cli@<that exact version>`;
|
|
1413
|
+
standalone binaries download the GitHub release tagged `v<that version>`
|
|
1414
|
+
(prereleases are GitHub releases too) and verify its checksum as usual.
|
|
1415
|
+
- **OpenCode.** OpenCode resolves a bare `"akm-opencode"` in its `plugin` list
|
|
1416
|
+
to `@latest`, and caches `@next` in its own folder
|
|
1417
|
+
(`$XDG_CACHE_HOME/opencode/packages/akm-opencode@next`). akm does not edit
|
|
1418
|
+
your OpenCode config, so it can only follow `@next` when you ask for it:
|
|
1419
|
+
|
|
1420
|
+
```json
|
|
1421
|
+
{ "plugin": ["akm-opencode@next"] }
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
With that line in the global OpenCode config (`~/.config/opencode/opencode.json`
|
|
1425
|
+
or `.jsonc`, or the file named by `OPENCODE_CONFIG`), lockstep and the cache
|
|
1426
|
+
refresh use `akm-opencode@next` (its version and its `akm-cli` pin) instead of
|
|
1427
|
+
`@latest`. If `akm-opencode@next` is missing, older than `@latest`, or its
|
|
1428
|
+
`akm-cli` pin is unreadable, the CLI is held where it is and the OpenCode entry
|
|
1429
|
+
is `failed`, exactly as in the stable lockstep above.
|
|
1430
|
+
|
|
1431
|
+
Without that line the OpenCode entry is `skipped`, its message names the line
|
|
1432
|
+
to add, and lockstep stays against the `@latest` pin, so the CLI does not go
|
|
1433
|
+
past what OpenCode will run.
|
|
1434
|
+
- **Claude Code and Codex.** Unchanged: their plugin comes from the `akm-plugins`
|
|
1435
|
+
git marketplace, which has no prerelease channel and accepts any 0.9.x akm,
|
|
1436
|
+
prereleases included. Their entries carry a note saying so.
|
|
1437
|
+
- `--check --next` reports all of this and changes nothing.
|
|
1438
|
+
|
|
1402
1439
|
#### Containers
|
|
1403
1440
|
|
|
1404
1441
|
With the plugin step, a container entrypoint needs only:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.28-alpha.
|
|
3
|
+
"version": "0.9.28-alpha.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
|
|
6
6
|
"keywords": [
|