@timurproko/a1 0.1.7 → 0.1.8-dev.b449b5c
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 +8 -2
- package/bin/cli.js +2 -2
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/src/cli/dispatch.d.ts +2 -1
- package/dist/src/cli/dispatch.js +22 -3
- package/dist/src/foundation/release/update.d.ts +2 -0
- package/dist/src/foundation/release/update.js +72 -11
- package/docs/ci-release-runbook.md +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install --global @timurproko/a1@latest
|
|
|
12
12
|
a1 # A1-owned UI and profile: ~/.a1/agent
|
|
13
13
|
a1 version # show Installed, Release (latest), and Next versions
|
|
14
14
|
a1 update # update to npm latest
|
|
15
|
-
a1 update:next # update to npm next
|
|
15
|
+
a1 update:next # update to npm next (or a1 update:<commit> for a specific preview)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Prerelease builds — what `a1 update:next` installs — add two development profiles
|
|
@@ -68,9 +68,15 @@ time and never committed, so previews cost no commits and need no command, and a
|
|
|
68
68
|
installed preview says exactly which source produced it.
|
|
69
69
|
|
|
70
70
|
```sh
|
|
71
|
-
a1 update:next
|
|
71
|
+
a1 update:next # install the newest preview
|
|
72
|
+
a1 update:7eabe9e # install the preview built from that commit
|
|
73
|
+
a1 update:0.1.8-dev.7eabe9e # or name the version outright
|
|
72
74
|
```
|
|
73
75
|
|
|
76
|
+
Naming a commit is what the version suffix is for: read it off `a1 version`, a
|
|
77
|
+
pull request, or a commit list, and install exactly that build. A commit that was
|
|
78
|
+
never published is refused rather than guessed at.
|
|
79
|
+
|
|
74
80
|
### Stable — npm `latest`
|
|
75
81
|
|
|
76
82
|
One command, from a clean `develop` that matches its remote:
|
package/bin/cli.js
CHANGED
|
@@ -22,9 +22,9 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
|
|
|
22
22
|
const { runVersionStats } = await import("../dist/src/cli/index.js");
|
|
23
23
|
return await runVersionStats({ packageRoot: fileURLToPath(packageRoot) });
|
|
24
24
|
},
|
|
25
|
-
update: async channel => {
|
|
25
|
+
update: async (channel, target) => {
|
|
26
26
|
const { runSelfUpdate } = await import("../dist/src/foundation/release/index.js");
|
|
27
|
-
return await runSelfUpdate({ packageRoot: fileURLToPath(packageRoot), channel });
|
|
27
|
+
return await runSelfUpdate({ packageRoot: fileURLToPath(packageRoot), channel, ...(target === undefined ? {} : { target }) });
|
|
28
28
|
},
|
|
29
29
|
packages: async request => {
|
|
30
30
|
const [{ runPackageCommand }, { createPiPackagesPort }] = await Promise.all([
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "unsupported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-24T06:35:58.522Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "7524bf568992517f50ed53196c861c5edeba0d7275633bb4735ab45bd5963a28",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-24T06:35:49.229Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "29e22fe29de2828982bc67ef418c4adcaab1490281477b7bea592ec6fe621bcd",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-24T06:36:28.685Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "1d535ec647852d21736769e0d418b80f9a99d02a9721609a13f251300986b9b9",
|
|
12
12
|
"size": 172544
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -5,7 +5,7 @@ export type UpdateChannel = "stable" | "next";
|
|
|
5
5
|
export interface CliHandlers {
|
|
6
6
|
readonly launch: (intent: InteractiveLaunchIntent) => Promise<number>;
|
|
7
7
|
readonly version: () => Promise<number>;
|
|
8
|
-
readonly update: (channel: UpdateChannel) => Promise<number>;
|
|
8
|
+
readonly update: (channel: UpdateChannel, target?: string) => Promise<number>;
|
|
9
9
|
readonly packages: (request: PackageCommandRequest) => Promise<number>;
|
|
10
10
|
}
|
|
11
11
|
export interface CliOutput {
|
|
@@ -21,6 +21,7 @@ export type CliCommand = {
|
|
|
21
21
|
} | {
|
|
22
22
|
readonly kind: "update";
|
|
23
23
|
readonly channel: UpdateChannel;
|
|
24
|
+
readonly target?: string;
|
|
24
25
|
} | {
|
|
25
26
|
readonly kind: "packages";
|
|
26
27
|
readonly request: PackageCommandRequest;
|
package/dist/src/cli/dispatch.js
CHANGED
|
@@ -7,6 +7,7 @@ export function cliUsage(capabilities) {
|
|
|
7
7
|
"version",
|
|
8
8
|
"update [self|<source>|--extensions|--models]",
|
|
9
9
|
"update:next",
|
|
10
|
+
"update:<commit>",
|
|
10
11
|
"install <source>",
|
|
11
12
|
"remove <source>",
|
|
12
13
|
"list",
|
|
@@ -25,7 +26,7 @@ export async function dispatchCli(arguments_, handlers, output, capabilities) {
|
|
|
25
26
|
return await handlers.version();
|
|
26
27
|
if (command.kind === "packages")
|
|
27
28
|
return await handlers.packages(command.request);
|
|
28
|
-
return await handlers.update(command.channel);
|
|
29
|
+
return await handlers.update(command.channel, command.target);
|
|
29
30
|
}
|
|
30
31
|
export function parseCliCommand(arguments_, capabilities) {
|
|
31
32
|
if (arguments_.length === 0)
|
|
@@ -36,8 +37,8 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
36
37
|
}
|
|
37
38
|
if (command === "version")
|
|
38
39
|
return withoutArguments(rest, { kind: "version" });
|
|
39
|
-
if (command
|
|
40
|
-
return
|
|
40
|
+
if (command !== undefined && command.startsWith("update:"))
|
|
41
|
+
return parseColonUpdate(command.slice("update:".length), rest);
|
|
41
42
|
if (command === "update")
|
|
42
43
|
return parseUpdate(rest);
|
|
43
44
|
if (command === "install" || command === "remove" || command === "uninstall") {
|
|
@@ -51,6 +52,24 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
51
52
|
return { kind: "error", message: `Bare ${PRODUCT_TEXT.commandName} is the ${PRODUCT_TEXT.displayName} agent experience; there is no agent subcommand.` };
|
|
52
53
|
return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unknown command: ${command ?? ""}`) };
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* What follows the colon says which build to move to. `next` is the newest
|
|
57
|
+
* preview; anything else names one outright, by the commit it was built from or by
|
|
58
|
+
* its full version — a preview is published as `<version>-dev.<commit>`, so the
|
|
59
|
+
* commit alone is enough to find it.
|
|
60
|
+
*/
|
|
61
|
+
function parseColonUpdate(suffix, rest) {
|
|
62
|
+
if (rest.length > 0)
|
|
63
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic("update takes what to move to after the colon, and nothing else.") };
|
|
64
|
+
if (suffix === "next")
|
|
65
|
+
return { kind: "update", channel: "next" };
|
|
66
|
+
if (suffix.length === 0)
|
|
67
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic(`update: needs a preview after the colon, as in ${PRODUCT_TEXT.commandName} update:next.`) };
|
|
68
|
+
if (!/^[0-9a-z][0-9a-z.+-]*$/i.test(suffix)) {
|
|
69
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unusable preview: ${suffix}`) };
|
|
70
|
+
}
|
|
71
|
+
return { kind: "update", channel: "next", target: suffix };
|
|
72
|
+
}
|
|
54
73
|
/**
|
|
55
74
|
* `update` carries both meanings pinned Pi gives it: itself by default, and the
|
|
56
75
|
* profile's packages when a target says so. Pi is refused as a target because A1
|
|
@@ -33,6 +33,8 @@ export interface UpdatePerformanceEvidence {
|
|
|
33
33
|
export interface SelfUpdateOptions {
|
|
34
34
|
packageRoot: string;
|
|
35
35
|
channel?: UpdateChannel;
|
|
36
|
+
/** A specific preview to install, named by its commit or its full version. */
|
|
37
|
+
target?: string;
|
|
36
38
|
environment?: NodeJS.ProcessEnv;
|
|
37
39
|
fileSystem?: UpdateFileSystem;
|
|
38
40
|
output?: UpdateOutput;
|
|
@@ -14,6 +14,13 @@ import { materializeRelease, readMaterializedRelease } from "./release-store.js"
|
|
|
14
14
|
import { UpdateTransactionStore } from "./update-transaction.js";
|
|
15
15
|
export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
|
|
16
16
|
const UPDATE_DIST_TAGS = { stable: "latest", next: "next" };
|
|
17
|
+
/**
|
|
18
|
+
* What each channel is called when A1 says it out loud. The internal name stays
|
|
19
|
+
* `stable` because that is what the channel is, but what a reader is moving to is
|
|
20
|
+
* a release, and that is the word the repository, its tags, and its GitHub
|
|
21
|
+
* releases all use.
|
|
22
|
+
*/
|
|
23
|
+
const UPDATE_CHANNEL_LABELS = { stable: "release", next: "next" };
|
|
17
24
|
const defaultFileSystem = {
|
|
18
25
|
async readFile(path) { return await readFile(path, "utf8"); },
|
|
19
26
|
realpath,
|
|
@@ -192,12 +199,17 @@ function createUpdateProgress(output, enabled) {
|
|
|
192
199
|
}, PROGRESS_TICK_MS);
|
|
193
200
|
timer.unref?.();
|
|
194
201
|
},
|
|
202
|
+
// The bar exists to say the update is still moving. Once it has finished
|
|
203
|
+
// there is a better line to occupy that row — the one naming what is now
|
|
204
|
+
// installed — so the bar gives the row back rather than leaving a full
|
|
205
|
+
// meter above a message that already implies it.
|
|
195
206
|
finish() {
|
|
196
207
|
stopCreep();
|
|
197
208
|
if (!visible)
|
|
198
209
|
return;
|
|
199
|
-
output.stdout(`\r${
|
|
210
|
+
output.stdout(`\r${" ".repeat(PROGRESS_BAR_WIDTH + 6)}\r`);
|
|
200
211
|
visible = false;
|
|
212
|
+
shown = -1;
|
|
201
213
|
},
|
|
202
214
|
clear() {
|
|
203
215
|
stopCreep();
|
|
@@ -209,6 +221,56 @@ function createUpdateProgress(output, enabled) {
|
|
|
209
221
|
},
|
|
210
222
|
};
|
|
211
223
|
}
|
|
224
|
+
/** The newest version the channel points at, which is what an unqualified update takes. */
|
|
225
|
+
async function resolveChannelHead(runner, distTag, output) {
|
|
226
|
+
const lookup = await runNpm(runner, ["view", `${PRODUCT_PACKAGE}@${distTag}`, "version"], true, output, `query the npm ${distTag} channel`);
|
|
227
|
+
if (lookup.result === null)
|
|
228
|
+
return { version: null, exitCode: lookup.exitCode };
|
|
229
|
+
const version = validSemver(lookup.result.stdout.trim());
|
|
230
|
+
if (version === null) {
|
|
231
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`received a malformed ${distTag} version from npm: ${JSON.stringify(lookup.result.stdout.trim())}.`)}\n`);
|
|
232
|
+
return { version: null, exitCode: 1 };
|
|
233
|
+
}
|
|
234
|
+
return { version, exitCode: 0 };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Resolve a preview the caller named.
|
|
238
|
+
*
|
|
239
|
+
* A preview is published as `<version>-dev.<commit>`, so its commit is enough to
|
|
240
|
+
* say which one is wanted — the version in front of it is not something anyone
|
|
241
|
+
* should have to remember. A full version is accepted too, for anyone reading one
|
|
242
|
+
* back from `a1 version` or a changelog.
|
|
243
|
+
*
|
|
244
|
+
* The published list is the authority: naming a commit that was never published,
|
|
245
|
+
* or one published more than once under different versions, is an error rather
|
|
246
|
+
* than a guess.
|
|
247
|
+
*/
|
|
248
|
+
async function resolveRequestedPreview(runner, requested, output) {
|
|
249
|
+
const lookup = await runNpm(runner, ["view", PRODUCT_PACKAGE, "versions", "--json"], true, output, "list the published versions");
|
|
250
|
+
if (lookup.result === null)
|
|
251
|
+
return { version: null, exitCode: lookup.exitCode };
|
|
252
|
+
let published;
|
|
253
|
+
try {
|
|
254
|
+
published = JSON.parse(lookup.result.stdout.trim() || "[]");
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`received a malformed version list from npm: ${JSON.stringify(lookup.result.stdout.trim())}.`)}\n`);
|
|
258
|
+
return { version: null, exitCode: 1 };
|
|
259
|
+
}
|
|
260
|
+
const versions = (Array.isArray(published) ? published : [published]).filter((value) => typeof value === "string");
|
|
261
|
+
const exact = versions.find(version => version === requested);
|
|
262
|
+
if (exact !== undefined)
|
|
263
|
+
return { version: exact, exitCode: 0 };
|
|
264
|
+
const matches = versions.filter(version => version.endsWith(`-dev.${requested}`));
|
|
265
|
+
if (matches.length === 1)
|
|
266
|
+
return { version: matches[0], exitCode: 0 };
|
|
267
|
+
if (matches.length > 1) {
|
|
268
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`found more than one preview for ${requested}: ${matches.join(", ")}. Name the version instead.`)}\n`);
|
|
269
|
+
return { version: null, exitCode: 1 };
|
|
270
|
+
}
|
|
271
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`published no preview for ${requested}.`)}\n`);
|
|
272
|
+
return { version: null, exitCode: 1 };
|
|
273
|
+
}
|
|
212
274
|
export async function runSelfUpdate(options) {
|
|
213
275
|
const fileSystem = options.fileSystem ?? defaultFileSystem;
|
|
214
276
|
const output = options.output ?? defaultOutput;
|
|
@@ -237,15 +299,14 @@ export async function runSelfUpdate(options) {
|
|
|
237
299
|
output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its running package version: ${errorMessage(error)}`)}\n`);
|
|
238
300
|
return 1;
|
|
239
301
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
output.stdout(`${PRODUCT_TEXT.commandName} update (${channel}): ${runningVersion} → ${targetVersion}.\n`);
|
|
302
|
+
const requested = options.target?.trim();
|
|
303
|
+
const resolved = await measure("target-resolution", async () => requested === undefined || requested.length === 0
|
|
304
|
+
? await resolveChannelHead(runner, distTag, output)
|
|
305
|
+
: await resolveRequestedPreview(runner, requested, output));
|
|
306
|
+
if (resolved.version === null)
|
|
307
|
+
return resolved.exitCode;
|
|
308
|
+
const targetVersion = resolved.version;
|
|
309
|
+
output.stdout(`${PRODUCT_TEXT.commandName} update (${UPDATE_CHANNEL_LABELS[channel]}): ${runningVersion} → ${targetVersion}.\n`);
|
|
249
310
|
const progress = createUpdateProgress(output, options.progress ?? (options.output === undefined && process.stdout.isTTY === true));
|
|
250
311
|
const rootLookup = await measure("global-root", async () => await runNpm(runner, ["root", "--global"], true, output, "resolve npm's global package root"));
|
|
251
312
|
if (rootLookup.result === null)
|
|
@@ -334,7 +395,7 @@ export async function runSelfUpdate(options) {
|
|
|
334
395
|
await transactionStore.clearCompleted();
|
|
335
396
|
options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
|
|
336
397
|
progress.finish();
|
|
337
|
-
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion}
|
|
398
|
+
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion}.\n`);
|
|
338
399
|
return 0;
|
|
339
400
|
}
|
|
340
401
|
catch (error) {
|
|
@@ -37,7 +37,9 @@ The version is stamped at publish time — `<major.minor.patch>-dev.<short commi
|
|
|
37
37
|
the base taken from whatever `package.json` declares and the suffix from the commit
|
|
38
38
|
being published — and is never written back to the repository. An installed preview
|
|
39
39
|
therefore names the exact source it came from, and rebuilding a commit produces the
|
|
40
|
-
same version rather than a new one.
|
|
40
|
+
same version rather than a new one. That suffix is also how a specific preview is
|
|
41
|
+
installed: `a1 update:<commit>` resolves it against the published list and
|
|
42
|
+
refuses a commit that was never published. `develop` therefore carries one open prerelease version between
|
|
41
43
|
releases, and no commit is ever spent on a preview.
|
|
42
44
|
|
|
43
45
|
One consequence worth knowing: a push that would republish an existing version
|