@timurproko/a1 0.1.8-dev.820b4a4 → 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 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 # install the newest preview
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-24T06:21:14.306Z",
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-24T06:21:16.988Z",
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-24T06:21:59.348Z",
8
+ "builtAt": "2026-08-24T06:36:28.685Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "a6130ee1d6a5008de29c91aba4ed592381aa9e192faa27a4b7d07b817555fbac",
11
+ "sha256": "1d535ec647852d21736769e0d418b80f9a99d02a9721609a13f251300986b9b9",
12
12
  "size": 172544
13
13
  },
14
14
  "provenance": {
@@ -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;
@@ -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 === "update:next")
40
- return withoutArguments(rest, { kind: "update", channel: "next" });
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;
@@ -221,6 +221,56 @@ function createUpdateProgress(output, enabled) {
221
221
  },
222
222
  };
223
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
+ }
224
274
  export async function runSelfUpdate(options) {
225
275
  const fileSystem = options.fileSystem ?? defaultFileSystem;
226
276
  const output = options.output ?? defaultOutput;
@@ -249,14 +299,13 @@ export async function runSelfUpdate(options) {
249
299
  output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its running package version: ${errorMessage(error)}`)}\n`);
250
300
  return 1;
251
301
  }
252
- const targetLookup = await measure("target-resolution", async () => await runNpm(runner, ["view", `${PRODUCT_PACKAGE}@${distTag}`, "version"], true, output, `query the npm ${distTag} channel`));
253
- if (targetLookup.result === null)
254
- return targetLookup.exitCode;
255
- const targetVersion = validSemver(targetLookup.result.stdout.trim());
256
- if (targetVersion === null) {
257
- output.stderr(`${PRODUCT_TEXT.diagnostic(`received a malformed ${distTag} version from npm: ${JSON.stringify(targetLookup.result.stdout.trim())}.`)}\n`);
258
- return 1;
259
- }
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;
260
309
  output.stdout(`${PRODUCT_TEXT.commandName} update (${UPDATE_CHANNEL_LABELS[channel]}): ${runningVersion} → ${targetVersion}.\n`);
261
310
  const progress = createUpdateProgress(output, options.progress ?? (options.output === undefined && process.stdout.isTTY === true));
262
311
  const rootLookup = await measure("global-root", async () => await runNpm(runner, ["root", "--global"], true, output, "resolve npm's global package root"));
@@ -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. `develop` therefore carries one open prerelease version between
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.8-dev.820b4a4",
3
+ "version": "0.1.8-dev.b449b5c",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.13.0",