@timurproko/a1 0.1.7 → 0.1.8-dev.0b6d8cc

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
@@ -9,10 +9,11 @@ npm install --global @timurproko/a1@latest
9
9
  ## Commands
10
10
 
11
11
  ```sh
12
- a1 # A1-owned UI and profile: ~/.a1/agent
13
- a1 version # show Installed, Release (latest), and Next versions
14
- a1 update # update to npm latest
15
- a1 update:next # update to npm next
12
+ a1 # A1-owned UI and profile: ~/.a1/agent
13
+ a1 version # show Installed, Release (latest), and Next versions
14
+ a1 update # update to npm latest
15
+ a1 update:next # update to npm next (or a1 update:<commit> for a specific preview)
16
+ a1 update --models # refresh A1's model catalogs
16
17
  ```
17
18
 
18
19
  Prerelease builds — what `a1 update:next` installs — add two development profiles
@@ -30,16 +31,19 @@ Pi extension packages install into A1's own profile, so bare `a1` loads them and
30
31
  `a1 pi` and `a1 sandbox` do not. Sources are Pi's: `npm:`, git, or a local path.
31
32
 
32
33
  ```sh
33
- a1 install npm:pi-mcp-adapter # install a package into ~/.a1/agent
34
- a1 remove npm:pi-mcp-adapter # remove it again (alias: a1 uninstall)
35
- a1 list # list packages installed for a1
36
- a1 update --extensions # update every installed package
37
- a1 update npm:pi-mcp-adapter # update one of them
38
- a1 update --models # refresh model catalogs
34
+ a1 pi install npm:pi-mcp-adapter # install a package into ~/.a1/agent
35
+ a1 pi remove npm:pi-mcp-adapter # remove it again (alias: a1 pi uninstall)
36
+ a1 pi list # list packages installed for a1
37
+ a1 pi update --extensions # update every installed package
38
+ a1 pi update npm:pi-mcp-adapter # update one of them
39
39
  ```
40
40
 
41
41
  A running session loads a newly installed package after a restart. Pi's own
42
- profile at `~/.pi/agent` is managed by Pi itself.
42
+ profile at `~/.pi/agent` is managed by Pi itself. Extension configuration is
43
+ isolated too: for example, MCP configuration for bare `a1` belongs under
44
+ `~/.a1/agent` (or the project), so `~/.pi/agent/mcp.json` is not read. Run
45
+ `/mcp setup` inside bare `a1` to configure it; the MCP footer status appears
46
+ when that A1 configuration contains a server.
43
47
 
44
48
  ## Develop
45
49
 
@@ -68,9 +72,16 @@ time and never committed, so previews cost no commits and need no command, and a
68
72
  installed preview says exactly which source produced it.
69
73
 
70
74
  ```sh
71
- a1 update:next # install the newest preview
75
+ a1 update:next # install the newest preview
76
+ a1 update:7eabe9e # install the preview built from that commit
72
77
  ```
73
78
 
79
+ Naming a commit is what the version suffix is for: read it off `a1 version`, a
80
+ pull request, or a commit list, and install exactly that build — you never need to
81
+ know which version it went out under. A commit that was never published is refused
82
+ rather than guessed at. A full preview version works in the same place, so a string
83
+ pasted back from `a1 version` is understood too.
84
+
74
85
  ### Stable — npm `latest`
75
86
 
76
87
  One command, from a clean `develop` that matches its remote:
@@ -86,7 +97,7 @@ It lands the version on `develop` through a pull request that merges itself.
86
97
  **Landing it is what publishes** — the workflow builds, validates the packed release
87
98
  on Windows, Linux, and macOS, publishes to npm `latest` with provenance, and only
88
99
  then writes the `v<version>` tag and records the GitHub Release. The command waits
89
- for that to succeed, then lands the next `-dev.0` version so previews resume
100
+ for that to succeed, then opens the next `-dev` line so previews resume
90
101
  immediately.
91
102
 
92
103
  ```sh
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-24T05:41:42.321Z",
8
+ "builtAt": "2026-08-25T08:02:28.988Z",
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-24T05:41:49.189Z",
8
+ "builtAt": "2026-08-25T08:02:31.153Z",
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-24T05:42:24.069Z",
8
+ "builtAt": "2026-08-25T08:02:58.479Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "c0b203cd361d080f7e5a2e108d7c090a5ef94ab466631e1e01b6af93cd7ab226",
11
+ "sha256": "fdbde2408643ffb897f5107d96ed40e8c8467505ef7daa64f86f9c2497c304ca",
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;
@@ -5,11 +5,13 @@ export function cliUsage(capabilities) {
5
5
  "",
6
6
  ...(capabilities.developmentProfiles ? ["pi", "sandbox"] : []),
7
7
  "version",
8
- "update [self|<source>|--extensions|--models]",
8
+ "update [self|--models]",
9
9
  "update:next",
10
- "install <source>",
11
- "remove <source>",
12
- "list",
10
+ "update:<commit>",
11
+ "pi install <source>",
12
+ "pi remove <source>",
13
+ "pi list",
14
+ "pi update [--extensions|<source>]",
13
15
  ]);
14
16
  }
15
17
  const PROFILE_WORDS = new Set(["pi", "sandbox"]);
@@ -25,26 +27,30 @@ export async function dispatchCli(arguments_, handlers, output, capabilities) {
25
27
  return await handlers.version();
26
28
  if (command.kind === "packages")
27
29
  return await handlers.packages(command.request);
28
- return await handlers.update(command.channel);
30
+ return await handlers.update(command.channel, command.target);
29
31
  }
30
32
  export function parseCliCommand(arguments_, capabilities) {
31
33
  if (arguments_.length === 0)
32
34
  return { kind: "launch", profileId: "a1" };
33
35
  const [command, ...rest] = arguments_;
34
- if (capabilities.developmentProfiles && (command === "pi" || command === "sandbox")) {
36
+ if (command === "pi") {
37
+ if (rest.length > 0)
38
+ return parsePiPackageCommand(rest);
39
+ if (capabilities.developmentProfiles)
40
+ return { kind: "launch", profileId: "pi" };
41
+ }
42
+ if (capabilities.developmentProfiles && command === "sandbox") {
35
43
  return withoutArguments(rest, { kind: "launch", profileId: command });
36
44
  }
37
45
  if (command === "version")
38
46
  return withoutArguments(rest, { kind: "version" });
39
- if (command === "update:next")
40
- return withoutArguments(rest, { kind: "update", channel: "next" });
47
+ if (command !== undefined && command.startsWith("update:"))
48
+ return parseColonUpdate(command.slice("update:".length), rest);
41
49
  if (command === "update")
42
50
  return parseUpdate(rest);
43
- if (command === "install" || command === "remove" || command === "uninstall") {
44
- return parseSourceCommand(command === "install" ? "install" : "remove", rest);
51
+ if (command === "install" || command === "remove" || command === "uninstall" || command === "list") {
52
+ return packageNamespaceRejection(command, rest.join(" ") || undefined);
45
53
  }
46
- if (command === "list")
47
- return withoutArguments(rest, { kind: "packages", request: { verb: "list", source: null } });
48
54
  if (command === "ui")
49
55
  return { kind: "error", message: `The ui subcommand was removed; run bare ${PRODUCT_TEXT.commandName} for the owned UI.` };
50
56
  if (command === "agent")
@@ -52,10 +58,27 @@ export function parseCliCommand(arguments_, capabilities) {
52
58
  return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unknown command: ${command ?? ""}`) };
53
59
  }
54
60
  /**
55
- * `update` carries both meanings pinned Pi gives it: itself by default, and the
56
- * profile's packages when a target says so. Pi is refused as a target because A1
57
- * certifies each release against one pinned Pi, so moving Pi underneath it would
58
- * invalidate what was certified.
61
+ * What follows the colon says which build to move to. `next` is the newest
62
+ * preview; anything else names one outright, by the commit it was built from or by
63
+ * its full version a preview is published as `<version>-dev.<commit>`, so the
64
+ * commit alone is enough to find it.
65
+ */
66
+ function parseColonUpdate(suffix, rest) {
67
+ if (rest.length > 0)
68
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic("update takes what to move to after the colon, and nothing else.") };
69
+ if (suffix === "next")
70
+ return { kind: "update", channel: "next" };
71
+ if (suffix.length === 0)
72
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`update: needs a preview after the colon, as in ${PRODUCT_TEXT.commandName} update:next.`) };
73
+ if (!/^[0-9a-z][0-9a-z.+-]*$/i.test(suffix)) {
74
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unusable preview: ${suffix}`) };
75
+ }
76
+ return { kind: "update", channel: "next", target: suffix };
77
+ }
78
+ /**
79
+ * Top-level `update` owns A1 itself and A1's model catalogs. Extension package
80
+ * maintenance lives under the `pi` compatibility namespace, while updating the
81
+ * pinned Pi runtime remains impossible.
59
82
  */
60
83
  function parseUpdate(rest) {
61
84
  if (rest.length === 0)
@@ -71,18 +94,42 @@ function parseUpdate(rest) {
71
94
  message: PRODUCT_TEXT.diagnostic(`pins the Pi version it was certified against; run ${PRODUCT_TEXT.commandName} update to move ${PRODUCT_TEXT.displayName} itself.`),
72
95
  };
73
96
  }
74
- // A release channel is spelled with the colon. Taking the bare word as a package
75
- // source would turn a near miss into a confident search for a package nobody has.
76
97
  if (target === "next" || target === "stable") {
77
98
  const form = target === "next" ? `${PRODUCT_TEXT.commandName} update:next` : `${PRODUCT_TEXT.commandName} update`;
78
99
  return { kind: "error", message: PRODUCT_TEXT.diagnostic(`selects a release channel with a colon; run ${form}.`) };
79
100
  }
80
- if (target === "--extensions")
81
- return { kind: "packages", request: { verb: "update", source: null } };
82
101
  if (target === "--models")
83
102
  return { kind: "packages", request: { verb: "refresh-models", source: null } };
103
+ if (target === "--extensions" || (target !== undefined && !target.startsWith("-"))) {
104
+ return packageNamespaceRejection("update", target === "--extensions" ? "--extensions" : target);
105
+ }
106
+ return unknownOption(target ?? "", "update");
107
+ }
108
+ function parsePiPackageCommand(arguments_) {
109
+ const [verb, ...rest] = arguments_;
110
+ if (verb === "install" || verb === "remove" || verb === "uninstall") {
111
+ return parseSourceCommand(verb === "install" ? "install" : "remove", rest);
112
+ }
113
+ if (verb === "list")
114
+ return withoutArguments(rest, { kind: "packages", request: { verb: "list", source: null } });
115
+ if (verb === "update")
116
+ return parsePiPackageUpdate(rest);
117
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unknown pi package command: ${verb ?? ""}`) };
118
+ }
119
+ function parsePiPackageUpdate(rest) {
120
+ if (rest.length === 0) {
121
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`pi update needs --extensions or a package source.`) };
122
+ }
123
+ if (rest.length > 1)
124
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic("pi update accepts one target.") };
125
+ const [target] = rest;
126
+ if (target === "--extensions")
127
+ return { kind: "packages", request: { verb: "update", source: null } };
128
+ if (target === "--models") {
129
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`refreshes its model catalogs at the top level; run ${PRODUCT_TEXT.commandName} update --models.`) };
130
+ }
84
131
  if (target === undefined || target.startsWith("-"))
85
- return unknownOption(target ?? "", "update");
132
+ return unknownOption(target ?? "", "pi update");
86
133
  if (PROFILE_WORDS.has(target))
87
134
  return profileRejection("update");
88
135
  return { kind: "packages", request: { verb: "update", source: target } };
@@ -110,6 +157,13 @@ function withoutArguments(rest, command) {
110
157
  return profileRejection("list");
111
158
  return { kind: "error", message: PRODUCT_TEXT.diagnostic("commands do not accept additional arguments.") };
112
159
  }
160
+ function packageNamespaceRejection(verb, target) {
161
+ const suffix = target === undefined ? "" : ` ${target}`;
162
+ return {
163
+ kind: "error",
164
+ message: PRODUCT_TEXT.diagnostic(`manages extension packages under its pi namespace; run ${PRODUCT_TEXT.commandName} pi ${verb}${suffix}.`),
165
+ };
166
+ }
113
167
  function profileRejection(verb) {
114
168
  return {
115
169
  kind: "error",
@@ -3,7 +3,7 @@ export * from "./conformance.js";
3
3
  export * from "./shell-components.js";
4
4
  export * from "./theme.js";
5
5
  export * from "./upstream/theme/theme-controller.js";
6
- export { applyConfiguredPiTheme, getAvailablePiThemes } from "./upstream/theme/theme.js";
6
+ export { applyConfiguredPiTheme, getAvailablePiThemes, onPiThemeChange } from "./upstream/theme/theme.js";
7
7
  export * from "./upstream/components/countdown-timer.js";
8
8
  export * from "./upstream/components/extension-editor.js";
9
9
  export * from "./upstream/components/session-selector.js";
@@ -3,7 +3,7 @@ export * from "./conformance.js";
3
3
  export * from "./shell-components.js";
4
4
  export * from "./theme.js";
5
5
  export * from "./upstream/theme/theme-controller.js";
6
- export { applyConfiguredPiTheme, getAvailablePiThemes } from "./upstream/theme/theme.js";
6
+ export { applyConfiguredPiTheme, getAvailablePiThemes, onPiThemeChange } from "./upstream/theme/theme.js";
7
7
  export * from "./upstream/components/countdown-timer.js";
8
8
  export * from "./upstream/components/extension-editor.js";
9
9
  export * from "./upstream/components/session-selector.js";
@@ -1,6 +1,7 @@
1
1
  import { AssistantMessageComponent, BashExecutionComponent, CompactionSummaryMessageComponent, CustomMessageComponent, DynamicBorder, getMarkdownTheme, parseSkillBlock, ToolExecutionComponent, UserMessageComponent, } from "@earendil-works/pi-coding-agent";
2
2
  import { SkillInvocationMessageComponent, } from "./upstream/components/skill-invocation-message.js";
3
3
  import { Container, Markdown, Spacer, Text, } from "#pi-tui";
4
+ import { PRODUCT_TEXT } from "../../product-identity.js";
4
5
  import { KeybindingsManager, } from "./upstream/adjacent/core/keybindings.js";
5
6
  import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
6
7
  import { componentPort, createTuiFacade, ensureTheme, formatSessionTokens, isRecord, } from "./shell-shared-facade.js";
@@ -168,7 +169,7 @@ export function renderPiShellPackageUpdateNotice(packages, width) {
168
169
  container.addChild(new Spacer(1));
169
170
  container.addChild(new DynamicBorder(text => theme.fg("warning", text)));
170
171
  container.addChild(new Text(`${theme.bold(theme.fg("warning", "Package Updates Available"))}\n`
171
- + `${theme.fg("muted", "Package updates are available. Run ")}${theme.fg("accent", "pi update --extensions")}\n`
172
+ + `${theme.fg("muted", "Package updates are available. Run ")}${theme.fg("accent", `${PRODUCT_TEXT.commandName} pi update --extensions`)}\n`
172
173
  + `${theme.fg("muted", "Packages:")}\n`
173
174
  + packages.map(name => `- ${name}`).join("\n"), 1, 0));
174
175
  container.addChild(new DynamicBorder(text => theme.fg("warning", text)));
@@ -60,11 +60,18 @@ export interface OwnedPiResourceSummary {
60
60
  readonly sourcePath: string | null;
61
61
  readonly diagnostic: string | null;
62
62
  }
63
+ export interface OwnedPiExtensionSourceSummary {
64
+ readonly source: string;
65
+ readonly scope: "user" | "project" | "temporary";
66
+ readonly origin: "package" | "top-level";
67
+ readonly baseDir: string | null;
68
+ }
63
69
  export interface OwnedPiExtensionResourceSummary {
64
70
  readonly kind: "extension";
65
71
  readonly id: string;
66
72
  readonly sourcePath: string | null;
67
73
  readonly resolvedPath: string | null;
74
+ readonly sourceInfo: OwnedPiExtensionSourceSummary | null;
68
75
  readonly loaded: boolean;
69
76
  readonly hidden: boolean;
70
77
  readonly diagnostic: string | null;